This commit is contained in:
J. Nick Koston
2026-02-20 20:51:36 -06:00
parent c6c01f85af
commit 487d311dbc
3 changed files with 183 additions and 35 deletions

View File

@@ -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<int32_t>(this->last_heap_snapshot_) - static_cast<int32_t>(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

View File

@@ -5,6 +5,7 @@
#ifdef USE_SETUP_HEAP_STATS
#include <cstdint>
#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};

View File

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