From e1e047c53fd5a8cf90d16ade711fb81a9360017d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 17 Oct 2025 15:02:09 -1000 Subject: [PATCH] tweak --- .github/workflows/ci.yml | 4 ++ esphome/platformio_api.py | 82 +----------------------------- script/ci_memory_impact_extract.py | 24 ++++----- script/determine-jobs.py | 22 ++++---- 4 files changed, 29 insertions(+), 103 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 440f64298b..0935fe609c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -565,10 +565,12 @@ jobs: echo "Compiling with test_build_components.py..." # Run build and extract memory with auto-detection of build directory for detailed analysis + # Use tee to show output in CI while also piping to extraction script python script/test_build_components.py \ -e compile \ -c "$component_list" \ -t "$platform" 2>&1 | \ + tee /dev/stderr | \ python script/ci_memory_impact_extract.py \ --output-env \ --output-json memory-analysis-target.json @@ -620,10 +622,12 @@ jobs: echo "Compiling with test_build_components.py..." # Run build and extract memory with auto-detection of build directory for detailed analysis + # Use tee to show output in CI while also piping to extraction script python script/test_build_components.py \ -e compile \ -c "$component_list" \ -t "$platform" 2>&1 | \ + tee /dev/stderr | \ python script/ci_memory_impact_extract.py \ --output-env \ --output-json memory-analysis-pr.json diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 065a8cf896..cc48562b4c 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -145,16 +145,7 @@ 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]}"] - 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 + return run_platformio_cli_run(config, verbose, *args) def _run_idedata(config): @@ -403,74 +394,3 @@ class IDEData: 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 ELF path - elf_path = idedata.firmware_elf_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 - # Pass idedata to auto-detect toolchain paths - analyzer = MemoryAnalyzer( - elf_path, external_components=external_components, idedata=idedata - ) - 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) diff --git a/script/ci_memory_impact_extract.py b/script/ci_memory_impact_extract.py index 97f3750950..7b722fcfd4 100755 --- a/script/ci_memory_impact_extract.py +++ b/script/ci_memory_impact_extract.py @@ -137,21 +137,21 @@ def run_detailed_analysis(build_dir: str) -> dict | None: # Convert to JSON-serializable format result = { - "components": {}, + "components": { + name: { + "text": mem.text_size, + "rodata": mem.rodata_size, + "data": mem.data_size, + "bss": mem.bss_size, + "flash_total": mem.flash_total, + "ram_total": mem.ram_total, + "symbol_count": mem.symbol_count, + } + for name, mem in components.items() + }, "symbols": {}, } - for name, mem in components.items(): - result["components"][name] = { - "text": mem.text_size, - "rodata": mem.rodata_size, - "data": mem.data_size, - "bss": mem.bss_size, - "flash_total": mem.flash_total, - "ram_total": mem.ram_total, - "symbol_count": mem.symbol_count, - } - # Build symbol map for section in analyzer.sections.values(): for symbol_name, size, _ in section.symbols: diff --git a/script/determine-jobs.py b/script/determine-jobs.py index 56de0e77ba..bd21926c53 100755 --- a/script/determine-jobs.py +++ b/script/determine-jobs.py @@ -303,16 +303,18 @@ def detect_memory_impact_config( # Check if component has tests for any preferred platform for test_file in test_files: parts = test_file.stem.split(".") - if len(parts) >= 2: - platform = parts[1] - if platform in PLATFORM_PREFERENCE: - components_with_tests.append(component) - # Select the most preferred platform across all components - if selected_platform is None or PLATFORM_PREFERENCE.index( - platform - ) < PLATFORM_PREFERENCE.index(selected_platform): - selected_platform = platform - break + if len(parts) < 2: + continue + platform = parts[1] + if platform not in PLATFORM_PREFERENCE: + continue + components_with_tests.append(component) + # Select the most preferred platform across all components + if selected_platform is None or PLATFORM_PREFERENCE.index( + platform + ) < PLATFORM_PREFERENCE.index(selected_platform): + selected_platform = platform + break # If no components have tests, don't run memory impact if not components_with_tests: