From 487d311dbc35a01d0f95fe10ff8be25c1fa34acb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 20 Feb 2026 20:51:36 -0600 Subject: [PATCH] tweak --- .../setup_heap_stats/setup_heap_stats.cpp | 41 +++-- .../setup_heap_stats/setup_heap_stats.h | 16 +- esphome/core/application.h | 161 +++++++++++++++--- 3 files changed, 183 insertions(+), 35 deletions(-) diff --git a/esphome/components/setup_heap_stats/setup_heap_stats.cpp b/esphome/components/setup_heap_stats/setup_heap_stats.cpp index e908cefff4..9b46e9371a 100644 --- a/esphome/components/setup_heap_stats/setup_heap_stats.cpp +++ b/esphome/components/setup_heap_stats/setup_heap_stats.cpp @@ -25,7 +25,9 @@ static const char *const TAG = "setup_heap_stats"; SetupHeapStatsCollector::SetupHeapStatsCollector(uint32_t initial_baseline) { global_setup_heap_stats = this; uint32_t before_alloc = get_free_internal_heap(); - this->entries_ = new ComponentHeapEntry[ESPHOME_COMPONENT_COUNT]; // NOLINT + // Allocate enough entries for both components and entity-only registrations + this->max_entries_ = ESPHOME_COMPONENT_COUNT * 2; + this->entries_ = new HeapEntry[this->max_entries_]; // NOLINT uint32_t after_alloc = get_free_internal_heap(); // Use the pre_setup() baseline but subtract our own entries allocation // so the collector's overhead is not attributed to the first component @@ -46,22 +48,32 @@ uint32_t SetupHeapStatsCollector::get_free_internal_heap() { #endif } -void SetupHeapStatsCollector::record_component_registered(Component *comp) { +void SetupHeapStatsCollector::record_entry_(Component *comp, const LogString *label) { uint32_t current_heap = get_free_internal_heap(); int32_t delta = static_cast(this->last_heap_snapshot_) - static_cast(current_heap); this->last_heap_snapshot_ = current_heap; - if (this->entry_count_ < ESPHOME_COMPONENT_COUNT) { + if (this->entry_count_ < this->max_entries_) { this->entries_[this->entry_count_].component = comp; + this->entries_[this->entry_count_].entity_label = label; this->entries_[this->entry_count_].construction_delta = delta; this->entries_[this->entry_count_].setup_delta = 0; this->entry_count_++; } - ESP_LOGI(TAG, "Constructed %s: %" PRId32 " bytes (free: %" PRIu32 ")", LOG_STR_ARG(comp->get_component_log_str()), - delta, current_heap); + if (comp != nullptr) { + ESP_LOGI(TAG, "Constructed %s: %" PRId32 " bytes (free: %" PRIu32 ")", LOG_STR_ARG(comp->get_component_log_str()), + delta, current_heap); + } else if (label != nullptr) { + ESP_LOGI(TAG, "Constructed %s (entity): %" PRId32 " bytes (free: %" PRIu32 ")", LOG_STR_ARG(label), delta, + current_heap); + } } +void SetupHeapStatsCollector::record_component_registered(Component *comp) { this->record_entry_(comp, nullptr); } + +void SetupHeapStatsCollector::record_entity_registered(const LogString *label) { this->record_entry_(nullptr, label); } + void SetupHeapStatsCollector::record_before_setup(Component *comp) { this->setup_component_ = comp; this->setup_before_heap_ = get_free_internal_heap(); @@ -93,10 +105,9 @@ void SetupHeapStatsCollector::log_summary() { return; // Sort by total (construction + setup) descending - std::sort(this->entries_, this->entries_ + this->entry_count_, - [](const ComponentHeapEntry &a, const ComponentHeapEntry &b) { - return (a.construction_delta + a.setup_delta) > (b.construction_delta + b.setup_delta); - }); + std::sort(this->entries_, this->entries_ + this->entry_count_, [](const HeapEntry &a, const HeapEntry &b) { + return (a.construction_delta + a.setup_delta) > (b.construction_delta + b.setup_delta); + }); ESP_LOGI(TAG, "Setup Heap Stats Summary (sorted by total, construction + setup):"); for (uint16_t i = 0; i < this->entry_count_; i++) { @@ -104,8 +115,16 @@ void SetupHeapStatsCollector::log_summary() { int32_t total = entry.construction_delta + entry.setup_delta; if (total == 0 && entry.construction_delta == 0 && entry.setup_delta == 0) continue; - ESP_LOGI(TAG, " %s: %" PRId32 " bytes (construction: %" PRId32 ", setup: %" PRId32 ")", - LOG_STR_ARG(entry.component->get_component_log_str()), total, entry.construction_delta, entry.setup_delta); + const char *name; + if (entry.component != nullptr) { + name = LOG_STR_ARG(entry.component->get_component_log_str()); + } else if (entry.entity_label != nullptr) { + name = LOG_STR_ARG(entry.entity_label); + } else { + name = "unknown"; + } + ESP_LOGI(TAG, " %s: %" PRId32 " bytes (construction: %" PRId32 ", setup: %" PRId32 ")", name, total, + entry.construction_delta, entry.setup_delta); } // Free storage diff --git a/esphome/components/setup_heap_stats/setup_heap_stats.h b/esphome/components/setup_heap_stats/setup_heap_stats.h index 10ca91146d..478adf92fc 100644 --- a/esphome/components/setup_heap_stats/setup_heap_stats.h +++ b/esphome/components/setup_heap_stats/setup_heap_stats.h @@ -5,6 +5,7 @@ #ifdef USE_SETUP_HEAP_STATS #include +#include "esphome/core/log.h" namespace esphome { @@ -12,8 +13,9 @@ class Component; // Forward declaration namespace setup_heap_stats { -struct ComponentHeapEntry { - Component *component; +struct HeapEntry { + Component *component; // Non-null for component entries + const LogString *entity_label; // Non-null for entity-only entries (no component) int32_t construction_delta; int32_t setup_delta; }; @@ -25,6 +27,11 @@ class SetupHeapStatsCollector { /// Called from register_component_() to record heap delta since last registration void record_component_registered(Component *comp); + /// Called from register_sensor(), register_button(), etc. to record heap delta for entities + /// that are not registered as components. This prevents their cost from being attributed + /// to the next register_component() call. + void record_entity_registered(const LogString *label); + /// Called before component->call_setup() void record_before_setup(Component *comp); @@ -38,8 +45,11 @@ class SetupHeapStatsCollector { static uint32_t get_free_internal_heap(); protected: - ComponentHeapEntry *entries_{nullptr}; + void record_entry_(Component *comp, const LogString *label); + + HeapEntry *entries_{nullptr}; uint16_t entry_count_{0}; + uint16_t max_entries_{0}; uint32_t last_heap_snapshot_{0}; // Temporary storage for before/after setup measurement Component *setup_component_{nullptr}; diff --git a/esphome/core/application.h b/esphome/core/application.h index 6cca5f197d..96398e5566 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -100,6 +100,9 @@ #ifdef USE_UPDATE #include "esphome/components/update/update_entity.h" #endif +#ifdef USE_SETUP_HEAP_STATS +#include "esphome/components/setup_heap_stats/setup_heap_stats.h" +#endif namespace esphome::socket { class Socket; @@ -152,97 +155,208 @@ class Application { #ifdef USE_BINARY_SENSOR void register_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { this->binary_sensors_.push_back(binary_sensor); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("binary_sensor")); +#endif } #endif #ifdef USE_SENSOR - void register_sensor(sensor::Sensor *sensor) { this->sensors_.push_back(sensor); } + void register_sensor(sensor::Sensor *sensor) { + this->sensors_.push_back(sensor); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("sensor")); +#endif + } #endif #ifdef USE_SWITCH - void register_switch(switch_::Switch *a_switch) { this->switches_.push_back(a_switch); } + void register_switch(switch_::Switch *a_switch) { + this->switches_.push_back(a_switch); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("switch")); +#endif + } #endif #ifdef USE_BUTTON - void register_button(button::Button *button) { this->buttons_.push_back(button); } + void register_button(button::Button *button) { + this->buttons_.push_back(button); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("button")); +#endif + } #endif #ifdef USE_TEXT_SENSOR - void register_text_sensor(text_sensor::TextSensor *sensor) { this->text_sensors_.push_back(sensor); } + void register_text_sensor(text_sensor::TextSensor *sensor) { + this->text_sensors_.push_back(sensor); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("text_sensor")); +#endif + } #endif #ifdef USE_FAN - void register_fan(fan::Fan *state) { this->fans_.push_back(state); } + void register_fan(fan::Fan *state) { + this->fans_.push_back(state); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("fan")); +#endif + } #endif #ifdef USE_COVER - void register_cover(cover::Cover *cover) { this->covers_.push_back(cover); } + void register_cover(cover::Cover *cover) { + this->covers_.push_back(cover); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("cover")); +#endif + } #endif #ifdef USE_CLIMATE - void register_climate(climate::Climate *climate) { this->climates_.push_back(climate); } + void register_climate(climate::Climate *climate) { + this->climates_.push_back(climate); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("climate")); +#endif + } #endif #ifdef USE_LIGHT - void register_light(light::LightState *light) { this->lights_.push_back(light); } + void register_light(light::LightState *light) { + this->lights_.push_back(light); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("light")); +#endif + } #endif #ifdef USE_NUMBER - void register_number(number::Number *number) { this->numbers_.push_back(number); } + void register_number(number::Number *number) { + this->numbers_.push_back(number); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("number")); +#endif + } #endif #ifdef USE_DATETIME_DATE - void register_date(datetime::DateEntity *date) { this->dates_.push_back(date); } + void register_date(datetime::DateEntity *date) { + this->dates_.push_back(date); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("date")); +#endif + } #endif #ifdef USE_DATETIME_TIME - void register_time(datetime::TimeEntity *time) { this->times_.push_back(time); } + void register_time(datetime::TimeEntity *time) { + this->times_.push_back(time); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("time")); +#endif + } #endif #ifdef USE_DATETIME_DATETIME - void register_datetime(datetime::DateTimeEntity *datetime) { this->datetimes_.push_back(datetime); } + void register_datetime(datetime::DateTimeEntity *datetime) { + this->datetimes_.push_back(datetime); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("datetime")); +#endif + } #endif #ifdef USE_TEXT - void register_text(text::Text *text) { this->texts_.push_back(text); } + void register_text(text::Text *text) { + this->texts_.push_back(text); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("text")); +#endif + } #endif #ifdef USE_SELECT - void register_select(select::Select *select) { this->selects_.push_back(select); } + void register_select(select::Select *select) { + this->selects_.push_back(select); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("select")); +#endif + } #endif #ifdef USE_LOCK - void register_lock(lock::Lock *a_lock) { this->locks_.push_back(a_lock); } + void register_lock(lock::Lock *a_lock) { + this->locks_.push_back(a_lock); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("lock")); +#endif + } #endif #ifdef USE_VALVE - void register_valve(valve::Valve *valve) { this->valves_.push_back(valve); } + void register_valve(valve::Valve *valve) { + this->valves_.push_back(valve); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("valve")); +#endif + } #endif #ifdef USE_MEDIA_PLAYER - void register_media_player(media_player::MediaPlayer *media_player) { this->media_players_.push_back(media_player); } + void register_media_player(media_player::MediaPlayer *media_player) { + this->media_players_.push_back(media_player); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("media_player")); +#endif + } #endif #ifdef USE_ALARM_CONTROL_PANEL void register_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { this->alarm_control_panels_.push_back(a_alarm_control_panel); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("alarm_control_panel")); +#endif } #endif #ifdef USE_WATER_HEATER - void register_water_heater(water_heater::WaterHeater *water_heater) { this->water_heaters_.push_back(water_heater); } + void register_water_heater(water_heater::WaterHeater *water_heater) { + this->water_heaters_.push_back(water_heater); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("water_heater")); +#endif + } #endif #ifdef USE_INFRARED - void register_infrared(infrared::Infrared *infrared) { this->infrareds_.push_back(infrared); } + void register_infrared(infrared::Infrared *infrared) { + this->infrareds_.push_back(infrared); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("infrared")); +#endif + } #endif #ifdef USE_EVENT - void register_event(event::Event *event) { this->events_.push_back(event); } + void register_event(event::Event *event) { + this->events_.push_back(event); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("event")); +#endif + } #endif #ifdef USE_UPDATE - void register_update(update::UpdateEntity *update) { this->updates_.push_back(update); } + void register_update(update::UpdateEntity *update) { + this->updates_.push_back(update); +#ifdef USE_SETUP_HEAP_STATS + this->record_entity_heap_stats_(LOG_STR("update")); +#endif + } #endif /// Reserve space for components to avoid memory fragmentation @@ -553,6 +667,11 @@ class Application { #ifdef USE_SETUP_HEAP_STATS void init_setup_heap_stats_baseline_(); + void record_entity_heap_stats_(const LogString *label) { + if (global_setup_heap_stats != nullptr) { + global_setup_heap_stats->record_entity_registered(label); + } + } #endif // === Member variables ordered by size to minimize padding ===