mirror of
https://github.com/esphome/esphome.git
synced 2026-06-01 01:19:45 +08:00
[runtime_stats] Eliminate heap churn by using stack-allocated buffer for sorting (#13586)
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user