From 40d9c0a3db9696d3eb53a6f821b9091928562855 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 2 Jul 2025 07:58:35 -0500 Subject: [PATCH] wip --- esphome/analyze_memory.py | 283 ++++++++++++++++++++++++++++---------- 1 file changed, 212 insertions(+), 71 deletions(-) diff --git a/esphome/analyze_memory.py b/esphome/analyze_memory.py index c5542c3362..91b3d3228b 100644 --- a/esphome/analyze_memory.py +++ b/esphome/analyze_memory.py @@ -9,70 +9,33 @@ import subprocess _LOGGER = logging.getLogger(__name__) -# Component namespace patterns -COMPONENT_PATTERNS = { - "api": re.compile(r"esphome::api::"), - "wifi": re.compile(r"esphome::wifi::"), - "mqtt": re.compile(r"esphome::mqtt::"), - "web_server": re.compile(r"esphome::web_server::"), - "sensor": re.compile(r"esphome::sensor::"), - "binary_sensor": re.compile(r"esphome::binary_sensor::"), - "switch": re.compile(r"esphome::switch_::"), - "light": re.compile(r"esphome::light::"), - "cover": re.compile(r"esphome::cover::"), - "climate": re.compile(r"esphome::climate::"), - "fan": re.compile(r"esphome::fan::"), - "display": re.compile(r"esphome::display::"), - "logger": re.compile(r"esphome::logger::"), - "ota": re.compile(r"esphome::ota::"), - "time": re.compile(r"esphome::time::"), - "sun": re.compile(r"esphome::sun::"), - "text_sensor": re.compile(r"esphome::text_sensor::"), - "script": re.compile(r"esphome::script::"), - "interval": re.compile(r"esphome::interval::"), - "json": re.compile(r"esphome::json::"), - "network": re.compile(r"esphome::network::"), - "mdns": re.compile(r"esphome::mdns::"), - "i2c": re.compile(r"esphome::i2c::"), - "spi": re.compile(r"esphome::spi::"), - "uart": re.compile(r"esphome::uart::"), - "dallas": re.compile(r"esphome::dallas::"), - "dht": re.compile(r"esphome::dht::"), - "adc": re.compile(r"esphome::adc::"), - "pwm": re.compile(r"esphome::pwm::"), - "ledc": re.compile(r"esphome::ledc::"), - "gpio": re.compile(r"esphome::gpio::"), - "esp32": re.compile(r"esphome::esp32::"), - "esp8266": re.compile(r"esphome::esp8266::"), - "remote": re.compile(r"esphome::remote_"), - "rf_bridge": re.compile(r"esphome::rf_bridge::"), - "captive_portal": re.compile(r"esphome::captive_portal::"), - "deep_sleep": re.compile(r"esphome::deep_sleep::"), - "bluetooth_proxy": re.compile(r"esphome::bluetooth_proxy::"), - "esp32_ble": re.compile(r"esphome::esp32_ble::"), - "esp32_ble_tracker": re.compile(r"esphome::esp32_ble_tracker::"), - "ethernet": re.compile(r"esphome::ethernet::"), - "valve": re.compile(r"esphome::valve::"), - "lock": re.compile(r"esphome::lock::"), - "alarm_control_panel": re.compile(r"esphome::alarm_control_panel::"), - "number": re.compile(r"esphome::number::"), - "select": re.compile(r"esphome::select::"), - "button": re.compile(r"esphome::button::"), - "datetime": re.compile(r"esphome::datetime::"), - "text": re.compile(r"esphome::text::"), - "media_player": re.compile(r"esphome::media_player::"), - "microphone": re.compile(r"esphome::microphone::"), - "speaker": re.compile(r"esphome::speaker::"), - "voice_assistant": re.compile(r"esphome::voice_assistant::"), - "update": re.compile(r"esphome::update::"), - "image": re.compile(r"esphome::image::"), - "font": re.compile(r"esphome::font::"), - "color": re.compile(r"esphome::color::"), - "graph": re.compile(r"esphome::graph::"), - "qr_code": re.compile(r"esphome::qr_code::"), - "touchscreen": re.compile(r"esphome::touchscreen::"), - "lvgl": re.compile(r"esphome::lvgl::"), -} +# Pattern to extract ESPHome component namespaces dynamically +ESPHOME_COMPONENT_PATTERN = re.compile(r"esphome::([a-zA-Z0-9_]+)::") + + +# Get the list of actual ESPHome components by scanning the components directory +def get_esphome_components(): + """Get set of actual ESPHome components from the components directory.""" + components = set() + + # Find the components directory relative to this file + current_dir = Path(__file__).parent + components_dir = current_dir / "components" + + if components_dir.exists() and components_dir.is_dir(): + for item in components_dir.iterdir(): + if ( + item.is_dir() + and not item.name.startswith(".") + and not item.name.startswith("__") + ): + components.add(item.name) + + return components + + +# Cache the component list +ESPHOME_COMPONENTS = get_esphome_components() class MemorySection: @@ -277,14 +240,22 @@ class MemoryAnalyzer: # Demangle C++ names if needed demangled = self._demangle_symbol(symbol_name) - # Check against specific component patterns first (skip 'core') - for component, pattern in COMPONENT_PATTERNS.items(): - if component == "core": - continue - if pattern.search(demangled): - return f"[esphome]{component}" + # Check for ESPHome component namespaces dynamically + # Pattern: esphome::component_name:: (with trailing ::) + match = ESPHOME_COMPONENT_PATTERN.search(demangled) + if match: + component_name = match.group(1) + # Strip trailing underscore if present (e.g., switch_ -> switch) + component_name = component_name.rstrip("_") - # Check for esphome core namespace last + # Check if this is an actual component or core + if component_name in ESPHOME_COMPONENTS: + return f"[esphome]{component_name}" + else: + return "[esphome]core" + + # Check for esphome core namespace (no component namespace) + # This catches esphome::ClassName or esphome::function_name if "esphome::" in demangled: return "[esphome]core" @@ -480,6 +451,11 @@ class MemoryAnalyzer: return "bluetooth" elif "coex" in symbol_name: return "wifi_bt_coex" + elif "r_" in symbol_name and any( + bt in symbol_name for bt in ["ble", "lld", "llc", "llm"] + ): + # ROM bluetooth functions + return "bluetooth_rom" # Power management if any( @@ -556,6 +532,171 @@ class MemoryAnalyzer: if "dhcp" in symbol_name or "handle_dhcp" in symbol_name: return "dhcp" + # JSON parsing + if any( + json in demangled + for json in [ + "ArduinoJson", + "JsonDocument", + "JsonArray", + "JsonObject", + "deserialize", + "serialize", + ] + ): + return "json_lib" + + # HTTP/Web related + if any( + http in demangled + for http in ["HTTP", "http_", "Request", "Response", "Uri", "WebSocket"] + ): + return "http_lib" + + # Ethernet PHY drivers + if any( + eth in symbol_name + for eth in [ + "emac_", + "eth_phy_", + "phy_tlk110", + "phy_lan87", + "phy_ip101", + "phy_rtl", + "phy_dp83", + "phy_ksz", + ] + ): + return "ethernet_phy" + + # Task/Thread management + if any(task in symbol_name for task in ["pthread_", "thread_", "_task_"]): + return "threading" + + # Mutex/Semaphore + if any( + sync in symbol_name + for sync in ["mutex", "semaphore", "spinlock", "portMUX"] + ): + return "synchronization" + + # String formatting + if any( + fmt in symbol_name + for fmt in [ + "snprintf", + "vsnprintf", + "sprintf", + "vsprintf", + "sscanf", + "vsscanf", + ] + ): + return "string_formatting" + + # Math functions + if ( + any( + math in symbol_name + for math in [ + "sin", + "cos", + "tan", + "sqrt", + "pow", + "exp", + "log", + "atan", + "asin", + "acos", + "floor", + "ceil", + "fabs", + "round", + ] + ) + and len(symbol_name) < 20 + ): + return "math_lib" + + # Random number generation + if any(rng in symbol_name for rng in ["rand", "random", "rng_", "prng"]): + return "random" + + # Time functions + if any( + time in symbol_name + for time in [ + "time", + "clock", + "gettimeofday", + "settimeofday", + "localtime", + "gmtime", + "mktime", + "strftime", + ] + ): + return "time_lib" + + # Console/UART output + if any( + console in symbol_name + for console in [ + "console_", + "uart_tx", + "uart_rx", + "puts", + "putchar", + "getchar", + ] + ): + return "console_io" + + # ROM functions + if symbol_name.startswith("r_") or symbol_name.startswith("rom_"): + return "rom_functions" + + # Compiler generated code + if any( + gen in symbol_name + for gen in [ + "__divdi3", + "__udivdi3", + "__moddi3", + "__muldi3", + "__ashldi3", + "__ashrdi3", + "__lshrdi3", + "__cmpdi2", + "__fixdfdi", + "__floatdidf", + ] + ): + return "compiler_runtime" + + # Exception handling + if any( + exc in symbol_name for exc in ["__cxa_", "_Unwind_", "__gcc_personality"] + ): + return "exception_handling" + + # RTTI (Run-Time Type Information) + if "__type_info" in demangled or "__class_type_info" in demangled: + return "rtti" + + # Static initializers + if "_GLOBAL__sub_I_" in symbol_name or "__static_initialization" in demangled: + return "static_init" + + # Weak symbols + if "__weak_" in symbol_name: + return "weak_symbols" + + # Compiler builtins + if "__builtin_" in symbol_name: + return "compiler_builtins" + return "other" def _batch_demangle_symbols(self, symbols: list[str]) -> None: