mirror of
https://github.com/esphome/esphome.git
synced 2025-10-22 19:53:46 +01:00
merge
This commit is contained in:
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from functools import cache
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -16,52 +15,16 @@ from .const import (
|
|||||||
SECTION_TO_ATTR,
|
SECTION_TO_ATTR,
|
||||||
SYMBOL_PATTERNS,
|
SYMBOL_PATTERNS,
|
||||||
)
|
)
|
||||||
from .helpers import map_section_name, parse_symbol_line
|
from .helpers import (
|
||||||
|
get_component_class_patterns,
|
||||||
|
get_esphome_components,
|
||||||
|
map_section_name,
|
||||||
|
parse_symbol_line,
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# Get the list of actual ESPHome components by scanning the components directory
|
|
||||||
@cache
|
|
||||||
def get_esphome_components():
|
|
||||||
"""Get set of actual ESPHome components from the components directory."""
|
|
||||||
# Find the components directory relative to this file
|
|
||||||
# Go up two levels from analyze_memory/__init__.py to esphome/
|
|
||||||
current_dir = Path(__file__).parent.parent
|
|
||||||
components_dir = current_dir / "components"
|
|
||||||
|
|
||||||
if not components_dir.exists() or not components_dir.is_dir():
|
|
||||||
return frozenset()
|
|
||||||
|
|
||||||
return frozenset(
|
|
||||||
item.name
|
|
||||||
for item in components_dir.iterdir()
|
|
||||||
if item.is_dir()
|
|
||||||
and not item.name.startswith(".")
|
|
||||||
and not item.name.startswith("__")
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@cache
|
|
||||||
def get_component_class_patterns(component_name: str) -> list[str]:
|
|
||||||
"""Generate component class name patterns for symbol matching.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
component_name: The component name (e.g., "ota", "wifi", "api")
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of pattern strings to match against demangled symbols
|
|
||||||
"""
|
|
||||||
component_upper = component_name.upper()
|
|
||||||
component_camel = component_name.replace("_", "").title()
|
|
||||||
return [
|
|
||||||
f"esphome::{component_upper}Component", # e.g., esphome::OTAComponent
|
|
||||||
f"esphome::ESPHome{component_upper}Component", # e.g., esphome::ESPHomeOTAComponent
|
|
||||||
f"esphome::{component_camel}Component", # e.g., esphome::OtaComponent
|
|
||||||
f"esphome::ESPHome{component_camel}Component", # e.g., esphome::ESPHomeOtaComponent
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MemorySection:
|
class MemorySection:
|
||||||
"""Represents a memory section with its symbols."""
|
"""Represents a memory section with its symbols."""
|
||||||
@@ -146,22 +109,25 @@ class MemoryAnalyzer:
|
|||||||
# Parse section headers
|
# Parse section headers
|
||||||
for line in result.stdout.splitlines():
|
for line in result.stdout.splitlines():
|
||||||
# Look for section entries
|
# Look for section entries
|
||||||
match = re.match(
|
if not (
|
||||||
|
match := re.match(
|
||||||
r"\s*\[\s*\d+\]\s+([\.\w]+)\s+\w+\s+[\da-fA-F]+\s+[\da-fA-F]+\s+([\da-fA-F]+)",
|
r"\s*\[\s*\d+\]\s+([\.\w]+)\s+\w+\s+[\da-fA-F]+\s+[\da-fA-F]+\s+([\da-fA-F]+)",
|
||||||
line,
|
line,
|
||||||
)
|
)
|
||||||
if match:
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
section_name = match.group(1)
|
section_name = match.group(1)
|
||||||
size_hex = match.group(2)
|
size_hex = match.group(2)
|
||||||
size = int(size_hex, 16)
|
size = int(size_hex, 16)
|
||||||
|
|
||||||
# Map to standard section name
|
# Map to standard section name
|
||||||
mapped_section = map_section_name(section_name)
|
mapped_section = map_section_name(section_name)
|
||||||
if mapped_section:
|
if not mapped_section:
|
||||||
|
continue
|
||||||
|
|
||||||
if mapped_section not in self.sections:
|
if mapped_section not in self.sections:
|
||||||
self.sections[mapped_section] = MemorySection(
|
self.sections[mapped_section] = MemorySection(mapped_section)
|
||||||
mapped_section
|
|
||||||
)
|
|
||||||
self.sections[mapped_section].total_size += size
|
self.sections[mapped_section].total_size += size
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
@@ -201,10 +167,11 @@ class MemoryAnalyzer:
|
|||||||
def _categorize_symbols(self) -> None:
|
def _categorize_symbols(self) -> None:
|
||||||
"""Categorize symbols by component."""
|
"""Categorize symbols by component."""
|
||||||
# First, collect all unique symbol names for batch demangling
|
# First, collect all unique symbol names for batch demangling
|
||||||
all_symbols = set()
|
all_symbols = {
|
||||||
for section in self.sections.values():
|
symbol_name
|
||||||
for symbol_name, _, _ in section.symbols:
|
for section in self.sections.values()
|
||||||
all_symbols.add(symbol_name)
|
for symbol_name, _, _ in section.symbols
|
||||||
|
}
|
||||||
|
|
||||||
# Batch demangle all symbols at once
|
# Batch demangle all symbols at once
|
||||||
self._batch_demangle_symbols(list(all_symbols))
|
self._batch_demangle_symbols(list(all_symbols))
|
||||||
|
@@ -231,20 +231,16 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
|||||||
|
|
||||||
if components_to_analyze:
|
if components_to_analyze:
|
||||||
for comp_name, comp_mem in components_to_analyze:
|
for comp_name, comp_mem in components_to_analyze:
|
||||||
comp_symbols = self._component_symbols.get(comp_name, [])
|
if not (comp_symbols := self._component_symbols.get(comp_name, [])):
|
||||||
if comp_symbols:
|
continue
|
||||||
lines.append("")
|
lines.append("")
|
||||||
lines.append("=" * self.TABLE_WIDTH)
|
lines.append("=" * self.TABLE_WIDTH)
|
||||||
lines.append(
|
lines.append(f"{comp_name} Detailed Analysis".center(self.TABLE_WIDTH))
|
||||||
f"{comp_name} Detailed Analysis".center(self.TABLE_WIDTH)
|
|
||||||
)
|
|
||||||
lines.append("=" * self.TABLE_WIDTH)
|
lines.append("=" * self.TABLE_WIDTH)
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
# Sort symbols by size
|
# Sort symbols by size
|
||||||
sorted_symbols = sorted(
|
sorted_symbols = sorted(comp_symbols, key=lambda x: x[2], reverse=True)
|
||||||
comp_symbols, key=lambda x: x[2], reverse=True
|
|
||||||
)
|
|
||||||
|
|
||||||
lines.append(f"Total symbols: {len(sorted_symbols)}")
|
lines.append(f"Total symbols: {len(sorted_symbols)}")
|
||||||
lines.append(f"Total size: {comp_mem.flash_total:,} B")
|
lines.append(f"Total size: {comp_mem.flash_total:,} B")
|
||||||
@@ -252,9 +248,7 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
|||||||
|
|
||||||
# Show all symbols > 100 bytes for better visibility
|
# Show all symbols > 100 bytes for better visibility
|
||||||
large_symbols = [
|
large_symbols = [
|
||||||
(sym, dem, size)
|
(sym, dem, size) for sym, dem, size in sorted_symbols if size > 100
|
||||||
for sym, dem, size in sorted_symbols
|
|
||||||
if size > 100
|
|
||||||
]
|
]
|
||||||
|
|
||||||
lines.append(
|
lines.append(
|
||||||
@@ -284,10 +278,10 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
|
|||||||
lines.append("-" * 10 + "-+-" + "-" * 60 + "-+-" + "-" * 40)
|
lines.append("-" * 10 + "-+-" + "-" * 60 + "-+-" + "-" * 40)
|
||||||
|
|
||||||
for symbol, demangled, size in sorted_symbols[:100]: # Top 100
|
for symbol, demangled, size in sorted_symbols[:100]: # Top 100
|
||||||
if symbol != demangled:
|
demangled_display = (
|
||||||
lines.append(f"{size:>10,} | {symbol[:60]:<60} | {demangled[:100]}")
|
demangled[:100] if symbol != demangled else "[not demangled]"
|
||||||
else:
|
)
|
||||||
lines.append(f"{size:>10,} | {symbol[:60]:<60} | [not demangled]")
|
lines.append(f"{size:>10,} | {symbol[:60]:<60} | {demangled_display}")
|
||||||
|
|
||||||
if len(sorted_symbols) > 100:
|
if len(sorted_symbols) > 100:
|
||||||
lines.append(f"\n... and {len(sorted_symbols) - 100} more symbols")
|
lines.append(f"\n... and {len(sorted_symbols) - 100} more symbols")
|
||||||
|
@@ -1,8 +1,52 @@
|
|||||||
"""Helper functions for memory analysis."""
|
"""Helper functions for memory analysis."""
|
||||||
|
|
||||||
|
from functools import cache
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from .const import SECTION_MAPPING
|
from .const import SECTION_MAPPING
|
||||||
|
|
||||||
|
|
||||||
|
# Get the list of actual ESPHome components by scanning the components directory
|
||||||
|
@cache
|
||||||
|
def get_esphome_components():
|
||||||
|
"""Get set of actual ESPHome components from the components directory."""
|
||||||
|
# Find the components directory relative to this file
|
||||||
|
# Go up two levels from analyze_memory/helpers.py to esphome/
|
||||||
|
current_dir = Path(__file__).parent.parent
|
||||||
|
components_dir = current_dir / "components"
|
||||||
|
|
||||||
|
if not components_dir.exists() or not components_dir.is_dir():
|
||||||
|
return frozenset()
|
||||||
|
|
||||||
|
return frozenset(
|
||||||
|
item.name
|
||||||
|
for item in components_dir.iterdir()
|
||||||
|
if item.is_dir()
|
||||||
|
and not item.name.startswith(".")
|
||||||
|
and not item.name.startswith("__")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@cache
|
||||||
|
def get_component_class_patterns(component_name: str) -> list[str]:
|
||||||
|
"""Generate component class name patterns for symbol matching.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
component_name: The component name (e.g., "ota", "wifi", "api")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of pattern strings to match against demangled symbols
|
||||||
|
"""
|
||||||
|
component_upper = component_name.upper()
|
||||||
|
component_camel = component_name.replace("_", "").title()
|
||||||
|
return [
|
||||||
|
f"esphome::{component_upper}Component", # e.g., esphome::OTAComponent
|
||||||
|
f"esphome::ESPHome{component_upper}Component", # e.g., esphome::ESPHomeOTAComponent
|
||||||
|
f"esphome::{component_camel}Component", # e.g., esphome::OtaComponent
|
||||||
|
f"esphome::ESPHome{component_camel}Component", # e.g., esphome::ESPHomeOtaComponent
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def map_section_name(raw_section: str) -> str | None:
|
def map_section_name(raw_section: str) -> str | None:
|
||||||
"""Map raw section name to standard section.
|
"""Map raw section name to standard section.
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user