From 4f0894e970468d1ecf375866d25d46977903e13c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 2 Feb 2026 22:05:39 +0100 Subject: [PATCH] [analyze-memory] Add top 30 largest symbols to report (#13673) --- esphome/analyze_memory/cli.py | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/esphome/analyze_memory/cli.py b/esphome/analyze_memory/cli.py index a77e17afce..72a73dbdd4 100644 --- a/esphome/analyze_memory/cli.py +++ b/esphome/analyze_memory/cli.py @@ -4,6 +4,8 @@ from __future__ import annotations from collections import defaultdict from collections.abc import Callable +import heapq +from operator import itemgetter import sys from typing import TYPE_CHECKING @@ -29,6 +31,10 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): ) # Lower threshold for RAM symbols (RAM is more constrained) RAM_SYMBOL_SIZE_THRESHOLD: int = 24 + # Number of top symbols to show in the largest symbols report + TOP_SYMBOLS_LIMIT: int = 30 + # Width for symbol name display in top symbols report + COL_TOP_SYMBOL_NAME: int = 55 # Column width constants COL_COMPONENT: int = 29 @@ -147,6 +153,37 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): section_label = f" [{section[1:]}]" # .data -> [data], .bss -> [bss] return f"{demangled} ({size:,} B){section_label}" + def _add_top_symbols(self, lines: list[str]) -> None: + """Add a section showing the top largest symbols in the binary.""" + # Collect all symbols from all components: (symbol, demangled, size, section, component) + all_symbols = [ + (symbol, demangled, size, section, component) + for component, symbols in self._component_symbols.items() + for symbol, demangled, size, section in symbols + ] + + # Get top N symbols by size using heapq for efficiency + top_symbols = heapq.nlargest( + self.TOP_SYMBOLS_LIMIT, all_symbols, key=itemgetter(2) + ) + + lines.append("") + lines.append(f"Top {self.TOP_SYMBOLS_LIMIT} Largest Symbols:") + # Calculate truncation limit from column width (leaving room for "...") + truncate_limit = self.COL_TOP_SYMBOL_NAME - 3 + for i, (_, demangled, size, section, component) in enumerate(top_symbols): + # Format section label + section_label = f"[{section[1:]}]" if section else "" + # Truncate demangled name if too long + demangled_display = ( + f"{demangled[:truncate_limit]}..." + if len(demangled) > self.COL_TOP_SYMBOL_NAME + else demangled + ) + lines.append( + f"{i + 1:>2}. {size:>7,} B {section_label:<8} {demangled_display:<{self.COL_TOP_SYMBOL_NAME}} {component}" + ) + def generate_report(self, detailed: bool = False) -> str: """Generate a formatted memory report.""" components = sorted( @@ -248,6 +285,9 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): "RAM", ) + # Top largest symbols in the binary + self._add_top_symbols(lines) + # Add ESPHome core detailed analysis if there are core symbols if self._esphome_core_symbols: self._add_section_header(lines, f"{_COMPONENT_CORE} Detailed Analysis")