mirror of
https://github.com/esphome/esphome.git
synced 2025-08-05 09:57:47 +00:00
Merge branch 'memory' into api_flash_memory
This commit is contained in:
commit
163a5747f0
@ -458,6 +458,13 @@ def command_vscode(args):
|
|||||||
|
|
||||||
|
|
||||||
def command_compile(args, config):
|
def command_compile(args, config):
|
||||||
|
# Set memory analysis options in config
|
||||||
|
if args.analyze_memory:
|
||||||
|
config.setdefault(CONF_ESPHOME, {})["analyze_memory"] = True
|
||||||
|
|
||||||
|
if args.memory_report:
|
||||||
|
config.setdefault(CONF_ESPHOME, {})["memory_report_file"] = args.memory_report
|
||||||
|
|
||||||
exit_code = write_cpp(config)
|
exit_code = write_cpp(config)
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
return exit_code
|
return exit_code
|
||||||
@ -837,6 +844,17 @@ def parse_args(argv):
|
|||||||
help="Only generate source code, do not compile.",
|
help="Only generate source code, do not compile.",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
parser_compile.add_argument(
|
||||||
|
"--analyze-memory",
|
||||||
|
help="Analyze and display memory usage by component after compilation.",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
parser_compile.add_argument(
|
||||||
|
"--memory-report",
|
||||||
|
help="Save memory analysis report to a file (supports .json or .txt).",
|
||||||
|
type=str,
|
||||||
|
metavar="FILE",
|
||||||
|
)
|
||||||
|
|
||||||
parser_upload = subparsers.add_parser(
|
parser_upload = subparsers.add_parser(
|
||||||
"upload",
|
"upload",
|
||||||
|
1600
esphome/analyze_memory.py
Normal file
1600
esphome/analyze_memory.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@ import os
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from esphome.const import CONF_COMPILE_PROCESS_LIMIT, CONF_ESPHOME, KEY_CORE
|
from esphome.const import CONF_COMPILE_PROCESS_LIMIT, CONF_ESPHOME, KEY_CORE
|
||||||
from esphome.core import CORE, EsphomeError
|
from esphome.core import CORE, EsphomeError
|
||||||
@ -104,7 +105,16 @@ def run_compile(config, verbose):
|
|||||||
args = []
|
args = []
|
||||||
if CONF_COMPILE_PROCESS_LIMIT in config[CONF_ESPHOME]:
|
if CONF_COMPILE_PROCESS_LIMIT in config[CONF_ESPHOME]:
|
||||||
args += [f"-j{config[CONF_ESPHOME][CONF_COMPILE_PROCESS_LIMIT]}"]
|
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):
|
def _run_idedata(config):
|
||||||
@ -331,3 +341,93 @@ class IDEData:
|
|||||||
return f"{self.cc_path[:-7]}addr2line.exe"
|
return f"{self.cc_path[:-7]}addr2line.exe"
|
||||||
|
|
||||||
return f"{self.cc_path[:-3]}addr2line"
|
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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user