ESPHome 2026.5.0-dev
Loading...
Searching...
No Matches
runtime_stats.cpp
Go to the documentation of this file.
1#include "runtime_stats.h"
2
3#ifdef USE_RUNTIME_STATS
4
7#include <algorithm>
8
9namespace esphome {
10
11namespace runtime_stats {
12
13RuntimeStatsCollector::RuntimeStatsCollector() : log_interval_(60000), next_log_time_(60000) {
15}
16
18 auto &components = App.components_;
19
20 // Single pass: collect active components into stack buffer
21 SmallBufferWithHeapFallback<256, Component *> buffer(components.size());
22 Component **sorted = buffer.get();
23 size_t count = 0;
24 for (auto *component : components) {
25 if (component->runtime_stats_.period_count > 0) {
26 sorted[count++] = component;
27 }
28 }
29
30 ESP_LOGI(TAG,
31 "Component Runtime Statistics\n"
32 " Period stats (last %" PRIu32 "ms): %zu active components",
33 this->log_interval_, count);
34
35 // Sum component time so we can derive main-loop overhead
36 // (active loop time minus time attributable to component loop()s).
37 // Period sum iterates the active-in-period subset; total sum must iterate
38 // all components since total_active_time_us_ includes iterations where
39 // currently-idle components previously ran.
40 uint64_t period_component_sum_us = 0;
41 for (size_t i = 0; i < count; i++) {
42 period_component_sum_us += sorted[i]->runtime_stats_.period_time_us;
43 }
44 uint64_t total_component_sum_us = 0;
45 for (auto *component : components) {
46 total_component_sum_us += component->runtime_stats_.total_time_us;
47 }
48
49 if (count > 0) {
50 // Sort by period runtime (descending)
51 std::sort(sorted, sorted + count, compare_period_time);
52
53 // Log top components by period runtime
54 for (size_t i = 0; i < count; i++) {
55 const auto &stats = sorted[i]->runtime_stats_;
56 ESP_LOGI(TAG, " %s: count=%" PRIu32 ", avg=%.3fms, max=%.2fms, total=%.1fms",
57 LOG_STR_ARG(sorted[i]->get_component_log_str()), stats.period_count,
58 stats.period_count > 0 ? stats.period_time_us / (float) stats.period_count / 1000.0f : 0.0f,
59 stats.period_max_time_us / 1000.0f, stats.period_time_us / 1000.0f);
60 }
61 }
62
63 // Main-loop overhead for the period: active wall time minus component time.
64 // active = sum of per-iteration loop time excluding yield/sleep.
65 if (this->period_active_count_ > 0) {
66 uint64_t active = this->period_active_time_us_;
67 uint64_t overhead = active > period_component_sum_us ? active - period_component_sum_us : 0;
68 // Use double for µs→ms conversion so multi-day uptimes (where total
69 // microsecond counters exceed float's ~7-digit mantissa) keep resolution.
70 ESP_LOGI(TAG,
71 " main_loop: iters=%" PRIu64 ", active_avg=%.3fms, active_max=%.2fms, active_total=%.1fms, "
72 "overhead_total=%.1fms",
74 static_cast<double>(active) / static_cast<double>(this->period_active_count_) / 1000.0,
75 static_cast<double>(this->period_active_max_us_) / 1000.0, static_cast<double>(active) / 1000.0,
76 static_cast<double>(overhead) / 1000.0);
77 uint64_t before = this->period_before_time_us_;
78 uint64_t tail = this->period_tail_time_us_;
79 uint64_t accounted = before + tail;
80 uint64_t inter = overhead > accounted ? overhead - accounted : 0;
81 ESP_LOGI(TAG, " main_loop_overhead_section: before=%.1fms, tail=%.1fms, inter_component=%.1fms",
82 static_cast<double>(before) / 1000.0, static_cast<double>(tail) / 1000.0,
83 static_cast<double>(inter) / 1000.0);
84 }
85
86 // Log total stats since boot (only for active components - idle ones haven't changed)
87 ESP_LOGI(TAG, " Total stats (since boot): %zu active components", count);
88
89 if (count > 0) {
90 // Re-sort by total runtime for all-time stats
91 std::sort(sorted, sorted + count, compare_total_time);
92
93 for (size_t i = 0; i < count; i++) {
94 const auto &stats = sorted[i]->runtime_stats_;
95 ESP_LOGI(TAG, " %s: count=%" PRIu32 ", avg=%.3fms, max=%.2fms, total=%.1fms",
96 LOG_STR_ARG(sorted[i]->get_component_log_str()), stats.total_count,
97 stats.total_count > 0 ? stats.total_time_us / (float) stats.total_count / 1000.0f : 0.0f,
98 stats.total_max_time_us / 1000.0f, stats.total_time_us / 1000.0);
99 }
100 }
101
102 if (this->total_active_count_ > 0) {
103 uint64_t active = this->total_active_time_us_;
104 uint64_t overhead = active > total_component_sum_us ? active - total_component_sum_us : 0;
105 ESP_LOGI(TAG,
106 " main_loop: iters=%" PRIu64 ", active_avg=%.3fms, active_max=%.2fms, active_total=%.1fms, "
107 "overhead_total=%.1fms",
109 static_cast<double>(active) / static_cast<double>(this->total_active_count_) / 1000.0,
110 static_cast<double>(this->total_active_max_us_) / 1000.0, static_cast<double>(active) / 1000.0,
111 static_cast<double>(overhead) / 1000.0);
112 uint64_t before = this->total_before_time_us_;
113 uint64_t tail = this->total_tail_time_us_;
114 uint64_t accounted = before + tail;
115 uint64_t inter = overhead > accounted ? overhead - accounted : 0;
116 ESP_LOGI(TAG, " main_loop_overhead_section: before=%.1fms, tail=%.1fms, inter_component=%.1fms",
117 static_cast<double>(before) / 1000.0, static_cast<double>(tail) / 1000.0,
118 static_cast<double>(inter) / 1000.0);
119 }
120
121 // Reset period stats
122 for (auto *component : components) {
123 component->runtime_stats_.reset_period();
124 }
125 this->period_active_count_ = 0;
126 this->period_active_time_us_ = 0;
127 this->period_active_max_us_ = 0;
128 this->period_before_time_us_ = 0;
129 this->period_tail_time_us_ = 0;
130}
131
133 return a->runtime_stats_.period_time_us > b->runtime_stats_.period_time_us;
134}
135
137 return a->runtime_stats_.total_time_us > b->runtime_stats_.total_time_us;
138}
139
141 if ((int32_t) (current_time - this->next_log_time_) >= 0) {
142 this->log_stats_();
143 this->next_log_time_ = current_time + this->log_interval_;
144 }
145}
146
147} // namespace runtime_stats
148
150 *global_runtime_stats = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
151 nullptr;
152
153} // namespace esphome
154
155#endif // USE_RUNTIME_STATS
StaticVector< Component *, ESPHOME_COMPONENT_COUNT > components_
ComponentRuntimeStats runtime_stats_
Definition component.h:592
Helper class for efficient buffer allocation - uses stack for small sizes, heap for large This is use...
Definition helpers.h:706
static bool compare_total_time(Component *a, Component *b)
static bool compare_period_time(Component *a, Component *b)
void process_pending_stats(uint32_t current_time)
const Component * component
Definition component.cpp:34
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
runtime_stats::RuntimeStatsCollector * global_runtime_stats
Application App
Global storage of Application pointer - only one Application can exist.
static void uint32_t