1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-25 13:13:48 +01:00
This commit is contained in:
J. Nick Koston
2025-10-17 14:26:44 -10:00
parent 843f590db4
commit f011c44130
6 changed files with 345 additions and 124 deletions

View File

@@ -316,6 +316,7 @@ def main():
print(
"Usage: python -m esphome.analyze_memory <elf_file> [objdump_path] [readelf_path]"
)
print("\nIf objdump/readelf paths are not provided, you must specify them.")
print("\nExample for ESP8266:")
print(" python -m esphome.analyze_memory firmware.elf \\")
print(
@@ -332,6 +333,14 @@ def main():
print(
" ~/.platformio/packages/toolchain-xtensa-esp-elf/bin/xtensa-esp32-elf-readelf"
)
print("\nExample for ESP32-C3 (RISC-V):")
print(" python -m esphome.analyze_memory firmware.elf \\")
print(
" ~/.platformio/packages/toolchain-riscv32-esp/bin/riscv32-esp-elf-objdump \\"
)
print(
" ~/.platformio/packages/toolchain-riscv32-esp/bin/riscv32-esp-elf-readelf"
)
sys.exit(1)
elf_file = sys.argv[1]

View File

@@ -145,7 +145,16 @@ def run_compile(config, verbose):
args = []
if CONF_COMPILE_PROCESS_LIMIT in config[CONF_ESPHOME]:
args += [f"-j{config[CONF_ESPHOME][CONF_COMPILE_PROCESS_LIMIT]}"]
return run_platformio_cli_run(config, verbose, *args)
result = run_platformio_cli_run(config, verbose, *args)
# Run memory analysis if enabled
if config.get(CONF_ESPHOME, {}).get("analyze_memory", False):
try:
analyze_memory_usage(config)
except Exception as e:
_LOGGER.warning("Failed to analyze memory usage: %s", e)
return result
def _run_idedata(config):
@@ -374,3 +383,93 @@ class IDEData:
return f"{self.cc_path[:-7]}addr2line.exe"
return f"{self.cc_path[:-3]}addr2line"
@property
def objdump_path(self) -> str:
# replace gcc at end with objdump
# Windows
if self.cc_path.endswith(".exe"):
return f"{self.cc_path[:-7]}objdump.exe"
return f"{self.cc_path[:-3]}objdump"
@property
def readelf_path(self) -> str:
# replace gcc at end with readelf
# Windows
if self.cc_path.endswith(".exe"):
return f"{self.cc_path[:-7]}readelf.exe"
return f"{self.cc_path[:-3]}readelf"
def analyze_memory_usage(config: dict[str, Any]) -> None:
"""Analyze memory usage by component after compilation."""
# Lazy import to avoid overhead when not needed
from esphome.analyze_memory import MemoryAnalyzer
idedata = get_idedata(config)
# Get paths to tools
elf_path = idedata.firmware_elf_path
objdump_path = idedata.objdump_path
readelf_path = idedata.readelf_path
# Debug logging
_LOGGER.debug("ELF path from idedata: %s", elf_path)
# Check if file exists
if not Path(elf_path).exists():
# Try alternate path
alt_path = Path(CORE.relative_build_path(".pioenvs", CORE.name, "firmware.elf"))
if alt_path.exists():
elf_path = str(alt_path)
_LOGGER.debug("Using alternate ELF path: %s", elf_path)
else:
_LOGGER.warning("ELF file not found at %s or %s", elf_path, alt_path)
return
# Extract external components from config
external_components = set()
# Get the list of built-in ESPHome components
from esphome.analyze_memory import get_esphome_components
builtin_components = get_esphome_components()
# Special non-component keys that appear in configs
NON_COMPONENT_KEYS = {
CONF_ESPHOME,
"substitutions",
"packages",
"globals",
"<<",
}
# Check all top-level keys in config
for key in config:
if key not in builtin_components and key not in NON_COMPONENT_KEYS:
# This is an external component
external_components.add(key)
_LOGGER.debug("Detected external components: %s", external_components)
# Create analyzer and run analysis
analyzer = MemoryAnalyzer(elf_path, objdump_path, readelf_path, external_components)
analyzer.analyze()
# Generate and print report
report = analyzer.generate_report()
_LOGGER.info("\n%s", report)
# Optionally save to file
if config.get(CONF_ESPHOME, {}).get("memory_report_file"):
report_file = Path(config[CONF_ESPHOME]["memory_report_file"])
if report_file.suffix == ".json":
report_file.write_text(analyzer.to_json())
_LOGGER.info("Memory report saved to %s", report_file)
else:
report_file.write_text(report)
_LOGGER.info("Memory report saved to %s", report_file)