[runtime_stats] Eliminate heap churn by using stack-allocated buffer for sorting (#13586)

This commit is contained in:
J. Nick Koston
2026-01-28 06:06:33 -10:00
committed by GitHub
parent 7385c4cf3d
commit e1355de4cb
2 changed files with 37 additions and 34 deletions
@@ -27,46 +27,61 @@ void RuntimeStatsCollector::record_component_time(Component *component, uint32_t
} }
void RuntimeStatsCollector::log_stats_() { void RuntimeStatsCollector::log_stats_() {
// First pass: count active components
size_t count = 0;
for (const auto &it : this->component_stats_) {
if (it.second.get_period_count() > 0) {
count++;
}
}
ESP_LOGI(TAG, ESP_LOGI(TAG,
"Component Runtime Statistics\n" "Component Runtime Statistics\n"
" Period stats (last %" PRIu32 "ms):", " Period stats (last %" PRIu32 "ms): %zu active components",
this->log_interval_); this->log_interval_, count);
// First collect stats we want to display if (count == 0) {
std::vector<ComponentStatPair> stats_to_display; return;
}
// Stack buffer sized to actual active count (up to 256 components), heap fallback for larger
SmallBufferWithHeapFallback<256, Component *> buffer(count);
Component **sorted = buffer.get();
// Second pass: fill buffer with active components
size_t idx = 0;
for (const auto &it : this->component_stats_) { for (const auto &it : this->component_stats_) {
Component *component = it.first; if (it.second.get_period_count() > 0) {
const ComponentRuntimeStats &stats = it.second; sorted[idx++] = it.first;
if (stats.get_period_count() > 0) {
ComponentStatPair pair = {component, &stats};
stats_to_display.push_back(pair);
} }
} }
// Sort by period runtime (descending) // Sort by period runtime (descending)
std::sort(stats_to_display.begin(), stats_to_display.end(), std::greater<ComponentStatPair>()); std::sort(sorted, sorted + count, [this](Component *a, Component *b) {
return this->component_stats_[a].get_period_time_ms() > this->component_stats_[b].get_period_time_ms();
});
// Log top components by period runtime // Log top components by period runtime
for (const auto &it : stats_to_display) { for (size_t i = 0; i < count; i++) {
const auto &stats = this->component_stats_[sorted[i]];
ESP_LOGI(TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms", ESP_LOGI(TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms",
LOG_STR_ARG(it.component->get_component_log_str()), it.stats->get_period_count(), LOG_STR_ARG(sorted[i]->get_component_log_str()), stats.get_period_count(), stats.get_period_avg_time_ms(),
it.stats->get_period_avg_time_ms(), it.stats->get_period_max_time_ms(), it.stats->get_period_time_ms()); stats.get_period_max_time_ms(), stats.get_period_time_ms());
} }
// Log total stats since boot // Log total stats since boot (only for active components - idle ones haven't changed)
ESP_LOGI(TAG, " Total stats (since boot):"); ESP_LOGI(TAG, " Total stats (since boot): %zu active components", count);
// Re-sort by total runtime for all-time stats // Re-sort by total runtime for all-time stats
std::sort(stats_to_display.begin(), stats_to_display.end(), std::sort(sorted, sorted + count, [this](Component *a, Component *b) {
[](const ComponentStatPair &a, const ComponentStatPair &b) { return this->component_stats_[a].get_total_time_ms() > this->component_stats_[b].get_total_time_ms();
return a.stats->get_total_time_ms() > b.stats->get_total_time_ms(); });
});
for (const auto &it : stats_to_display) { for (size_t i = 0; i < count; i++) {
const auto &stats = this->component_stats_[sorted[i]];
ESP_LOGI(TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms", ESP_LOGI(TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms",
LOG_STR_ARG(it.component->get_component_log_str()), it.stats->get_total_count(), LOG_STR_ARG(sorted[i]->get_component_log_str()), stats.get_total_count(), stats.get_total_avg_time_ms(),
it.stats->get_total_avg_time_ms(), it.stats->get_total_max_time_ms(), it.stats->get_total_time_ms()); stats.get_total_max_time_ms(), stats.get_total_time_ms());
} }
} }
@@ -5,7 +5,6 @@
#ifdef USE_RUNTIME_STATS #ifdef USE_RUNTIME_STATS
#include <map> #include <map>
#include <vector>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
@@ -77,17 +76,6 @@ class ComponentRuntimeStats {
uint32_t total_max_time_ms_; uint32_t total_max_time_ms_;
}; };
// For sorting components by run time
struct ComponentStatPair {
Component *component;
const ComponentRuntimeStats *stats;
bool operator>(const ComponentStatPair &other) const {
// Sort by period time as that's what we're displaying in the logs
return stats->get_period_time_ms() > other.stats->get_period_time_ms();
}
};
class RuntimeStatsCollector { class RuntimeStatsCollector {
public: public:
RuntimeStatsCollector(); RuntimeStatsCollector();