1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-22 11:43:51 +01:00
This commit is contained in:
J. Nick Koston
2025-10-17 14:05:24 -10:00
parent 79aafe2cd5
commit 86c12079b4
3 changed files with 104 additions and 103 deletions

View File

@@ -13,6 +13,7 @@ from .const import (
CORE_SUBCATEGORY_PATTERNS, CORE_SUBCATEGORY_PATTERNS,
DEMANGLED_PATTERNS, DEMANGLED_PATTERNS,
ESPHOME_COMPONENT_PATTERN, ESPHOME_COMPONENT_PATTERN,
SECTION_TO_ATTR,
SYMBOL_PATTERNS, SYMBOL_PATTERNS,
) )
from .helpers import map_section_name, parse_symbol_line from .helpers import map_section_name, parse_symbol_line
@@ -219,14 +220,9 @@ class MemoryAnalyzer:
comp_mem = self.components[component] comp_mem = self.components[component]
comp_mem.symbol_count += 1 comp_mem.symbol_count += 1
if section_name == ".text": # Update the appropriate size attribute based on section
comp_mem.text_size += size if attr_name := SECTION_TO_ATTR.get(section_name):
elif section_name == ".rodata": setattr(comp_mem, attr_name, getattr(comp_mem, attr_name) + size)
comp_mem.rodata_size += size
elif section_name == ".data":
comp_mem.data_size += size
elif section_name == ".bss":
comp_mem.bss_size += size
# Track uncategorized symbols # Track uncategorized symbols
if component == "other" and size > 0: if component == "other" and size > 0:

View File

@@ -10,37 +10,24 @@ from . import MemoryAnalyzer
class MemoryAnalyzerCLI(MemoryAnalyzer): class MemoryAnalyzerCLI(MemoryAnalyzer):
"""Memory analyzer with CLI-specific report generation.""" """Memory analyzer with CLI-specific report generation."""
def generate_report(self, detailed: bool = False) -> str:
"""Generate a formatted memory report."""
components = sorted(
self.components.items(), key=lambda x: x[1].flash_total, reverse=True
)
# Calculate totals
total_flash = sum(c.flash_total for _, c in components)
total_ram = sum(c.ram_total for _, c in components)
# Build report
lines = []
# Column width constants # Column width constants
COL_COMPONENT = 29 COL_COMPONENT: int = 29
COL_FLASH_TEXT = 14 COL_FLASH_TEXT: int = 14
COL_FLASH_DATA = 14 COL_FLASH_DATA: int = 14
COL_RAM_DATA = 12 COL_RAM_DATA: int = 12
COL_RAM_BSS = 12 COL_RAM_BSS: int = 12
COL_TOTAL_FLASH = 15 COL_TOTAL_FLASH: int = 15
COL_TOTAL_RAM = 12 COL_TOTAL_RAM: int = 12
COL_SEPARATOR = 3 # " | " COL_SEPARATOR: int = 3 # " | "
# Core analysis column widths # Core analysis column widths
COL_CORE_SUBCATEGORY = 30 COL_CORE_SUBCATEGORY: int = 30
COL_CORE_SIZE = 12 COL_CORE_SIZE: int = 12
COL_CORE_COUNT = 6 COL_CORE_COUNT: int = 6
COL_CORE_PERCENT = 10 COL_CORE_PERCENT: int = 10
# Calculate the exact table width # Calculate table width once at class level
table_width = ( TABLE_WIDTH: int = (
COL_COMPONENT COL_COMPONENT
+ COL_SEPARATOR + COL_SEPARATOR
+ COL_FLASH_TEXT + COL_FLASH_TEXT
@@ -56,59 +43,74 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
+ COL_TOTAL_RAM + COL_TOTAL_RAM
) )
lines.append("=" * table_width) @staticmethod
lines.append("Component Memory Analysis".center(table_width)) def _make_separator_line(*widths: int) -> str:
lines.append("=" * table_width) """Create a separator line with given column widths.
Args:
widths: Column widths to create separators for
Returns:
Separator line like "----+---------+-----"
"""
return "-+-".join("-" * width for width in widths)
# Pre-computed separator lines
MAIN_TABLE_SEPARATOR: str = _make_separator_line(
COL_COMPONENT,
COL_FLASH_TEXT,
COL_FLASH_DATA,
COL_RAM_DATA,
COL_RAM_BSS,
COL_TOTAL_FLASH,
COL_TOTAL_RAM,
)
CORE_TABLE_SEPARATOR: str = _make_separator_line(
COL_CORE_SUBCATEGORY,
COL_CORE_SIZE,
COL_CORE_COUNT,
COL_CORE_PERCENT,
)
def generate_report(self, detailed: bool = False) -> str:
"""Generate a formatted memory report."""
components = sorted(
self.components.items(), key=lambda x: x[1].flash_total, reverse=True
)
# Calculate totals
total_flash = sum(c.flash_total for _, c in components)
total_ram = sum(c.ram_total for _, c in components)
# Build report
lines = []
lines.append("=" * self.TABLE_WIDTH)
lines.append("Component Memory Analysis".center(self.TABLE_WIDTH))
lines.append("=" * self.TABLE_WIDTH)
lines.append("") lines.append("")
# Main table - fixed column widths # Main table - fixed column widths
lines.append( lines.append(
f"{'Component':<{COL_COMPONENT}} | {'Flash (text)':>{COL_FLASH_TEXT}} | {'Flash (data)':>{COL_FLASH_DATA}} | {'RAM (data)':>{COL_RAM_DATA}} | {'RAM (bss)':>{COL_RAM_BSS}} | {'Total Flash':>{COL_TOTAL_FLASH}} | {'Total RAM':>{COL_TOTAL_RAM}}" f"{'Component':<{self.COL_COMPONENT}} | {'Flash (text)':>{self.COL_FLASH_TEXT}} | {'Flash (data)':>{self.COL_FLASH_DATA}} | {'RAM (data)':>{self.COL_RAM_DATA}} | {'RAM (bss)':>{self.COL_RAM_BSS}} | {'Total Flash':>{self.COL_TOTAL_FLASH}} | {'Total RAM':>{self.COL_TOTAL_RAM}}"
)
lines.append(
"-" * COL_COMPONENT
+ "-+-"
+ "-" * COL_FLASH_TEXT
+ "-+-"
+ "-" * COL_FLASH_DATA
+ "-+-"
+ "-" * COL_RAM_DATA
+ "-+-"
+ "-" * COL_RAM_BSS
+ "-+-"
+ "-" * COL_TOTAL_FLASH
+ "-+-"
+ "-" * COL_TOTAL_RAM
) )
lines.append(self.MAIN_TABLE_SEPARATOR)
for name, mem in components: for name, mem in components:
if mem.flash_total > 0 or mem.ram_total > 0: if mem.flash_total > 0 or mem.ram_total > 0:
flash_rodata = mem.rodata_size + mem.data_size flash_rodata = mem.rodata_size + mem.data_size
lines.append( lines.append(
f"{name:<{COL_COMPONENT}} | {mem.text_size:>{COL_FLASH_TEXT - 2},} B | {flash_rodata:>{COL_FLASH_DATA - 2},} B | " f"{name:<{self.COL_COMPONENT}} | {mem.text_size:>{self.COL_FLASH_TEXT - 2},} B | {flash_rodata:>{self.COL_FLASH_DATA - 2},} B | "
f"{mem.data_size:>{COL_RAM_DATA - 2},} B | {mem.bss_size:>{COL_RAM_BSS - 2},} B | " f"{mem.data_size:>{self.COL_RAM_DATA - 2},} B | {mem.bss_size:>{self.COL_RAM_BSS - 2},} B | "
f"{mem.flash_total:>{COL_TOTAL_FLASH - 2},} B | {mem.ram_total:>{COL_TOTAL_RAM - 2},} B" f"{mem.flash_total:>{self.COL_TOTAL_FLASH - 2},} B | {mem.ram_total:>{self.COL_TOTAL_RAM - 2},} B"
) )
lines.append(self.MAIN_TABLE_SEPARATOR)
lines.append( lines.append(
"-" * COL_COMPONENT f"{'TOTAL':<{self.COL_COMPONENT}} | {' ':>{self.COL_FLASH_TEXT}} | {' ':>{self.COL_FLASH_DATA}} | "
+ "-+-" f"{' ':>{self.COL_RAM_DATA}} | {' ':>{self.COL_RAM_BSS}} | "
+ "-" * COL_FLASH_TEXT f"{total_flash:>{self.COL_TOTAL_FLASH - 2},} B | {total_ram:>{self.COL_TOTAL_RAM - 2},} B"
+ "-+-"
+ "-" * COL_FLASH_DATA
+ "-+-"
+ "-" * COL_RAM_DATA
+ "-+-"
+ "-" * COL_RAM_BSS
+ "-+-"
+ "-" * COL_TOTAL_FLASH
+ "-+-"
+ "-" * COL_TOTAL_RAM
)
lines.append(
f"{'TOTAL':<{COL_COMPONENT}} | {' ':>{COL_FLASH_TEXT}} | {' ':>{COL_FLASH_DATA}} | "
f"{' ':>{COL_RAM_DATA}} | {' ':>{COL_RAM_BSS}} | "
f"{total_flash:>{COL_TOTAL_FLASH - 2},} B | {total_ram:>{COL_TOTAL_RAM - 2},} B"
) )
# Top consumers # Top consumers
@@ -137,14 +139,14 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
lines.append( lines.append(
"Note: This analysis covers symbols in the ELF file. Some runtime allocations may not be included." "Note: This analysis covers symbols in the ELF file. Some runtime allocations may not be included."
) )
lines.append("=" * table_width) lines.append("=" * self.TABLE_WIDTH)
# Add ESPHome core detailed analysis if there are core symbols # Add ESPHome core detailed analysis if there are core symbols
if self._esphome_core_symbols: if self._esphome_core_symbols:
lines.append("") lines.append("")
lines.append("=" * table_width) lines.append("=" * self.TABLE_WIDTH)
lines.append("[esphome]core Detailed Analysis".center(table_width)) lines.append("[esphome]core Detailed Analysis".center(self.TABLE_WIDTH))
lines.append("=" * table_width) lines.append("=" * self.TABLE_WIDTH)
lines.append("") lines.append("")
# Group core symbols by subcategory # Group core symbols by subcategory
@@ -168,26 +170,18 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
) )
lines.append( lines.append(
f"{'Subcategory':<{COL_CORE_SUBCATEGORY}} | {'Size':>{COL_CORE_SIZE}} | " f"{'Subcategory':<{self.COL_CORE_SUBCATEGORY}} | {'Size':>{self.COL_CORE_SIZE}} | "
f"{'Count':>{COL_CORE_COUNT}} | {'% of Core':>{COL_CORE_PERCENT}}" f"{'Count':>{self.COL_CORE_COUNT}} | {'% of Core':>{self.COL_CORE_PERCENT}}"
)
lines.append(
"-" * COL_CORE_SUBCATEGORY
+ "-+-"
+ "-" * COL_CORE_SIZE
+ "-+-"
+ "-" * COL_CORE_COUNT
+ "-+-"
+ "-" * COL_CORE_PERCENT
) )
lines.append(self.CORE_TABLE_SEPARATOR)
core_total = sum(size for _, _, size in self._esphome_core_symbols) core_total = sum(size for _, _, size in self._esphome_core_symbols)
for subcategory, symbols, total_size in sorted_subcategories: for subcategory, symbols, total_size in sorted_subcategories:
percentage = (total_size / core_total * 100) if core_total > 0 else 0 percentage = (total_size / core_total * 100) if core_total > 0 else 0
lines.append( lines.append(
f"{subcategory:<{COL_CORE_SUBCATEGORY}} | {total_size:>{COL_CORE_SIZE - 2},} B | " f"{subcategory:<{self.COL_CORE_SUBCATEGORY}} | {total_size:>{self.COL_CORE_SIZE - 2},} B | "
f"{len(symbols):>{COL_CORE_COUNT}} | {percentage:>{COL_CORE_PERCENT - 1}.1f}%" f"{len(symbols):>{self.COL_CORE_COUNT}} | {percentage:>{self.COL_CORE_PERCENT - 1}.1f}%"
) )
# Top 10 largest core symbols # Top 10 largest core symbols
@@ -200,7 +194,7 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
for i, (symbol, demangled, size) in enumerate(sorted_core_symbols[:15]): for i, (symbol, demangled, size) in enumerate(sorted_core_symbols[:15]):
lines.append(f"{i + 1}. {demangled} ({size:,} B)") lines.append(f"{i + 1}. {demangled} ({size:,} B)")
lines.append("=" * table_width) lines.append("=" * self.TABLE_WIDTH)
# Add detailed analysis for top ESPHome and external components # Add detailed analysis for top ESPHome and external components
esphome_components = [ esphome_components = [
@@ -240,9 +234,11 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
comp_symbols = self._component_symbols.get(comp_name, []) comp_symbols = self._component_symbols.get(comp_name, [])
if comp_symbols: if comp_symbols:
lines.append("") lines.append("")
lines.append("=" * table_width) lines.append("=" * self.TABLE_WIDTH)
lines.append(f"{comp_name} Detailed Analysis".center(table_width)) lines.append(
lines.append("=" * table_width) f"{comp_name} Detailed Analysis".center(self.TABLE_WIDTH)
)
lines.append("=" * self.TABLE_WIDTH)
lines.append("") lines.append("")
# Sort symbols by size # Sort symbols by size
@@ -267,7 +263,7 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
for i, (symbol, demangled, size) in enumerate(large_symbols): for i, (symbol, demangled, size) in enumerate(large_symbols):
lines.append(f"{i + 1}. {demangled} ({size:,} B)") lines.append(f"{i + 1}. {demangled} ({size:,} B)")
lines.append("=" * table_width) lines.append("=" * self.TABLE_WIDTH)
return "\n".join(lines) return "\n".join(lines)

View File

@@ -14,6 +14,15 @@ SECTION_MAPPING = {
".bss": frozenset([".bss"]), ".bss": frozenset([".bss"]),
} }
# Section to ComponentMemory attribute mapping
# Maps section names to the attribute name in ComponentMemory dataclass
SECTION_TO_ATTR = {
".text": "text_size",
".rodata": "rodata_size",
".data": "data_size",
".bss": "bss_size",
}
# Component identification rules # Component identification rules
# Symbol patterns: patterns found in raw symbol names # Symbol patterns: patterns found in raw symbol names
SYMBOL_PATTERNS = { SYMBOL_PATTERNS = {