This commit is contained in:
J. Nick Koston 2025-07-02 07:58:35 -05:00
parent 548cd39496
commit 40d9c0a3db
No known key found for this signature in database

View File

@ -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: