From fbfb5d401f99cf40d60bdced6db36ba00262f27e Mon Sep 17 00:00:00 2001 From: Edward Firmo <94725493+edwardtfn@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:34:29 +0200 Subject: [PATCH 1/6] [nextion] Fix memory leak in `reset_()` (#15344) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> --- esphome/components/nextion/nextion.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index bb3e12be50..d141ef7906 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -143,8 +143,17 @@ void Nextion::reset_(bool reset_nextion) { while (this->available()) { // Clear receive buffer this->read_byte(&d); - }; + } + for (auto *entry : this->nextion_queue_) { + if (entry->component != nullptr && entry->component->get_queue_type() == NextionQueueType::NO_RESULT) { + delete entry->component; // NOLINT(cppcoreguidelines-owning-memory) + } + delete entry; // NOLINT(cppcoreguidelines-owning-memory) + } this->nextion_queue_.clear(); + for (auto *entry : this->waveform_queue_) { + delete entry; // NOLINT(cppcoreguidelines-owning-memory) + } this->waveform_queue_.clear(); } From d1ef6bf6045a9998d1a464645eb8d7a3d5f062cf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 31 Mar 2026 22:58:18 -1000 Subject: [PATCH 2/6] [runtime_stats] Store stats inline on Component to eliminate std::map lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The runtime_stats component used a std::map to record per-component timing data. Every loop iteration for every component required a red-black tree lookup to record its time, adding ~2-3µs of measurement overhead per component — often exceeding the actual work done by lightweight components like OTA idle checks. Move RuntimeStats struct directly onto Component (behind #ifdef USE_RUNTIME_STATS) so recording is a direct member access with zero lookup cost. The RuntimeStatsCollector now iterates App.components_ to collect stats instead of maintaining its own map, eliminating ~2KB of red-black tree code. --- .../runtime_stats/runtime_stats.cpp | 51 +++++++------ .../components/runtime_stats/runtime_stats.h | 72 ------------------- esphome/core/application.h | 9 +++ esphome/core/component.cpp | 12 +--- esphome/core/component.h | 30 ++++++++ esphome/core/defines.h | 1 + 6 files changed, 67 insertions(+), 108 deletions(-) diff --git a/esphome/components/runtime_stats/runtime_stats.cpp b/esphome/components/runtime_stats/runtime_stats.cpp index cb28acc96c..1ece670ed2 100644 --- a/esphome/components/runtime_stats/runtime_stats.cpp +++ b/esphome/components/runtime_stats/runtime_stats.cpp @@ -2,6 +2,7 @@ #ifdef USE_RUNTIME_STATS +#include "esphome/core/application.h" #include "esphome/core/component.h" #include @@ -13,19 +14,13 @@ RuntimeStatsCollector::RuntimeStatsCollector() : log_interval_(60000), next_log_ global_runtime_stats = this; } -void RuntimeStatsCollector::record_component_time(Component *component, uint32_t duration_us) { - if (component == nullptr) - return; - - // Record stats using component pointer as key - this->component_stats_[component].record_time(duration_us); -} - void RuntimeStatsCollector::log_stats_() { + auto &components = App.components_; + // First pass: count active components size_t count = 0; - for (const auto &it : this->component_stats_) { - if (it.second.get_period_count() > 0) { + for (auto *component : components) { + if (component->runtime_stats_.period_count > 0) { count++; } } @@ -45,47 +40,51 @@ void RuntimeStatsCollector::log_stats_() { // Second pass: fill buffer with active components size_t idx = 0; - for (const auto &it : this->component_stats_) { - if (it.second.get_period_count() > 0) { - sorted[idx++] = it.first; + for (auto *component : components) { + if (component->runtime_stats_.period_count > 0) { + sorted[idx++] = component; } } // Sort by period runtime (descending) - std::sort(sorted, sorted + count, [this](Component *a, Component *b) { - return this->component_stats_[a].get_period_time_us() > this->component_stats_[b].get_period_time_us(); + std::sort(sorted, sorted + count, [](Component *a, Component *b) { + return a->runtime_stats_.period_time_us > b->runtime_stats_.period_time_us; }); // Log top components by period runtime for (size_t i = 0; i < count; i++) { - const auto &stats = this->component_stats_[sorted[i]]; + const auto &stats = sorted[i]->runtime_stats_; ESP_LOGI(TAG, " %s: count=%" PRIu32 ", avg=%.3fms, max=%.2fms, total=%.1fms", - LOG_STR_ARG(sorted[i]->get_component_log_str()), stats.get_period_count(), - stats.get_period_avg_time_us() / 1000.0f, stats.get_period_max_time_us() / 1000.0f, - stats.get_period_time_us() / 1000.0f); + LOG_STR_ARG(sorted[i]->get_component_log_str()), stats.period_count, + stats.period_count > 0 ? stats.period_time_us / (float) stats.period_count / 1000.0f : 0.0f, + stats.period_max_time_us / 1000.0f, stats.period_time_us / 1000.0f); } // Log total stats since boot (only for active components - idle ones haven't changed) ESP_LOGI(TAG, " Total stats (since boot): %zu active components", count); // Re-sort by total runtime for all-time stats - std::sort(sorted, sorted + count, [this](Component *a, Component *b) { - return this->component_stats_[a].get_total_time_us() > this->component_stats_[b].get_total_time_us(); + std::sort(sorted, sorted + count, [](Component *a, Component *b) { + return a->runtime_stats_.total_time_us > b->runtime_stats_.total_time_us; }); for (size_t i = 0; i < count; i++) { - const auto &stats = this->component_stats_[sorted[i]]; + const auto &stats = sorted[i]->runtime_stats_; ESP_LOGI(TAG, " %s: count=%" PRIu32 ", avg=%.3fms, max=%.2fms, total=%.1fms", - LOG_STR_ARG(sorted[i]->get_component_log_str()), stats.get_total_count(), - stats.get_total_avg_time_us() / 1000.0f, stats.get_total_max_time_us() / 1000.0f, - stats.get_total_time_us() / 1000.0); + LOG_STR_ARG(sorted[i]->get_component_log_str()), stats.total_count, + stats.total_count > 0 ? stats.total_time_us / (float) stats.total_count / 1000.0f : 0.0f, + stats.total_max_time_us / 1000.0f, stats.total_time_us / 1000.0); + } + + // Reset period stats + for (auto *component : components) { + component->runtime_stats_.reset_period(); } } void RuntimeStatsCollector::process_pending_stats(uint32_t current_time) { if ((int32_t) (current_time - this->next_log_time_) >= 0) { this->log_stats_(); - this->reset_stats_(); this->next_log_time_ = current_time + this->log_interval_; } } diff --git a/esphome/components/runtime_stats/runtime_stats.h b/esphome/components/runtime_stats/runtime_stats.h index 303d895985..f81ca83e38 100644 --- a/esphome/components/runtime_stats/runtime_stats.h +++ b/esphome/components/runtime_stats/runtime_stats.h @@ -4,11 +4,8 @@ #ifdef USE_RUNTIME_STATS -#include #include -#include #include "esphome/core/hal.h" -#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -19,64 +16,6 @@ namespace runtime_stats { static const char *const TAG = "runtime_stats"; -class ComponentRuntimeStats { - public: - ComponentRuntimeStats() - : period_count_(0), - period_time_us_(0), - period_max_time_us_(0), - total_count_(0), - total_time_us_(0), - total_max_time_us_(0) {} - - void record_time(uint32_t duration_us) { - // Update period counters - this->period_count_++; - this->period_time_us_ += duration_us; - if (duration_us > this->period_max_time_us_) - this->period_max_time_us_ = duration_us; - - // Update total counters (uint64_t to avoid overflow — uint32_t would overflow after ~10 hours) - this->total_count_++; - this->total_time_us_ += duration_us; - if (duration_us > this->total_max_time_us_) - this->total_max_time_us_ = duration_us; - } - - void reset_period_stats() { - this->period_count_ = 0; - this->period_time_us_ = 0; - this->period_max_time_us_ = 0; - } - - // Period stats (reset each logging interval) - uint32_t get_period_count() const { return this->period_count_; } - uint32_t get_period_time_us() const { return this->period_time_us_; } - uint32_t get_period_max_time_us() const { return this->period_max_time_us_; } - float get_period_avg_time_us() const { - return this->period_count_ > 0 ? this->period_time_us_ / static_cast(this->period_count_) : 0.0f; - } - - // Total stats (persistent until reboot, uint64_t to avoid overflow) - uint32_t get_total_count() const { return this->total_count_; } - uint64_t get_total_time_us() const { return this->total_time_us_; } - uint32_t get_total_max_time_us() const { return this->total_max_time_us_; } - float get_total_avg_time_us() const { - return this->total_count_ > 0 ? this->total_time_us_ / static_cast(this->total_count_) : 0.0f; - } - - protected: - // Period stats (reset each logging interval) - uint32_t period_count_; - uint32_t period_time_us_; - uint32_t period_max_time_us_; - - // Total stats (persistent until reboot) - uint32_t total_count_; - uint64_t total_time_us_; - uint32_t total_max_time_us_; -}; - class RuntimeStatsCollector { public: RuntimeStatsCollector(); @@ -87,23 +26,12 @@ class RuntimeStatsCollector { } uint32_t get_log_interval() const { return this->log_interval_; } - void record_component_time(Component *component, uint32_t duration_us); - // Process any pending stats printing (should be called after component loop) void process_pending_stats(uint32_t current_time); protected: void log_stats_(); - void reset_stats_() { - for (auto &it : this->component_stats_) { - it.second.reset_period_stats(); - } - } - - // Map from component to its stats - // We use Component* as the key since each component is unique - std::map component_stats_; uint32_t log_interval_; uint32_t next_log_time_{0}; }; diff --git a/esphome/core/application.h b/esphome/core/application.h index 06ff30e81f..6cc61bc954 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -130,6 +130,12 @@ bool socket_ready_fd(int fd, bool loop_monitored); // NOLINT(readability-redund #endif } // namespace esphome::socket +#ifdef USE_RUNTIME_STATS +namespace esphome::runtime_stats { +class RuntimeStatsCollector; +} // namespace esphome::runtime_stats +#endif + // Forward declarations for friend access from codegen-generated setup() void setup(); // NOLINT(readability-redundant-declaration) - may be declared in Arduino.h void original_setup(); // NOLINT(readability-redundant-declaration) - used by cpp unit tests @@ -590,6 +596,9 @@ class Application { friend Component; #if defined(USE_SOCKET_SELECT_SUPPORT) && !defined(USE_LWIP_FAST_SELECT) friend bool socket::socket_ready_fd(int fd, bool loop_monitored); +#endif +#ifdef USE_RUNTIME_STATS + friend class runtime_stats::RuntimeStatsCollector; #endif friend void ::setup(); friend void ::original_setup(); diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 00a36fce3d..955596ce95 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -9,9 +9,6 @@ #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#ifdef USE_RUNTIME_STATS -#include "esphome/components/runtime_stats/runtime_stats.h" -#endif namespace esphome { @@ -524,13 +521,8 @@ WarnIfComponentBlockingGuard::warn_blocking(Component *component, uint32_t block #ifdef USE_RUNTIME_STATS void WarnIfComponentBlockingGuard::record_runtime_stats_() { - // Use micros() for accurate sub-millisecond timing. millis() has insufficient - // resolution — most components complete in microseconds but millis() only has - // 1ms granularity, so results were essentially random noise. - if (global_runtime_stats != nullptr) { - uint32_t duration_us = micros() - this->started_us_; - global_runtime_stats->record_component_time(this->component_, duration_us); - } + uint32_t duration_us = micros() - this->started_us_; + this->component_->runtime_stats_.record_time(duration_us); } #endif diff --git a/esphome/core/component.h b/esphome/core/component.h index c390a205f0..f62904877a 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -529,6 +529,36 @@ class Component { /// Bits 6-7: Unused - reserved for future expansion uint8_t component_state_{0x00}; volatile bool pending_enable_loop_{false}; ///< ISR-safe flag for enable_loop_soon_any_context + +#ifdef USE_RUNTIME_STATS + /// Inline runtime statistics — eliminates std::map lookup on every loop iteration. + struct RuntimeStats { + // Period stats (reset each logging interval) + uint32_t period_count{0}; + uint32_t period_time_us{0}; + uint32_t period_max_time_us{0}; + // Total stats (persistent until reboot, uint64_t to avoid overflow) + uint32_t total_count{0}; + uint64_t total_time_us{0}; + uint32_t total_max_time_us{0}; + + void record_time(uint32_t duration_us) { + this->period_count++; + this->period_time_us += duration_us; + if (duration_us > this->period_max_time_us) + this->period_max_time_us = duration_us; + this->total_count++; + this->total_time_us += duration_us; + if (duration_us > this->total_max_time_us) + this->total_max_time_us = duration_us; + } + void reset_period() { + this->period_count = 0; + this->period_time_us = 0; + this->period_max_time_us = 0; + } + } runtime_stats_; +#endif }; /** This class simplifies creating components that periodically check a state. diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 7259167a52..23e65f55bc 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -180,6 +180,7 @@ #define USE_RUNTIME_IMAGE_BMP #define USE_RUNTIME_IMAGE_PNG #define USE_RUNTIME_IMAGE_JPEG +#define USE_RUNTIME_STATS #define USE_OTA #define USE_OTA_PASSWORD #define USE_OTA_STATE_LISTENER From b63dc719e5de2eb4b953f61a9c595ca9e61ce40b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 31 Mar 2026 23:07:52 -1000 Subject: [PATCH 3/6] Fix protected access: move ComponentRuntimeStats before Component class --- esphome/core/application.h | 9 ------ esphome/core/component.h | 60 ++++++++++++++++++++------------------ 2 files changed, 32 insertions(+), 37 deletions(-) diff --git a/esphome/core/application.h b/esphome/core/application.h index 6cc61bc954..06ff30e81f 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -130,12 +130,6 @@ bool socket_ready_fd(int fd, bool loop_monitored); // NOLINT(readability-redund #endif } // namespace esphome::socket -#ifdef USE_RUNTIME_STATS -namespace esphome::runtime_stats { -class RuntimeStatsCollector; -} // namespace esphome::runtime_stats -#endif - // Forward declarations for friend access from codegen-generated setup() void setup(); // NOLINT(readability-redundant-declaration) - may be declared in Arduino.h void original_setup(); // NOLINT(readability-redundant-declaration) - used by cpp unit tests @@ -596,9 +590,6 @@ class Application { friend Component; #if defined(USE_SOCKET_SELECT_SUPPORT) && !defined(USE_LWIP_FAST_SELECT) friend bool socket::socket_ready_fd(int fd, bool loop_monitored); -#endif -#ifdef USE_RUNTIME_STATS - friend class runtime_stats::RuntimeStatsCollector; #endif friend void ::setup(); friend void ::original_setup(); diff --git a/esphome/core/component.h b/esphome/core/component.h index f62904877a..e85f45bbf5 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -92,6 +92,37 @@ inline constexpr uint8_t WARN_IF_BLOCKING_OVER_CS = 5U; // 50ms in centiseconds /// Weak default returns "" so builds without codegen still link. const LogString *component_source_lookup(uint8_t index); +#ifdef USE_RUNTIME_STATS +/// Inline runtime statistics — eliminates std::map lookup on every loop iteration. +/// Only present when USE_RUNTIME_STATS is defined (profiling builds). +struct ComponentRuntimeStats { + // Period stats (reset each logging interval) + uint32_t period_count{0}; + uint32_t period_time_us{0}; + uint32_t period_max_time_us{0}; + // Total stats (persistent until reboot, uint64_t to avoid overflow) + uint32_t total_count{0}; + uint64_t total_time_us{0}; + uint32_t total_max_time_us{0}; + + void record_time(uint32_t duration_us) { + this->period_count++; + this->period_time_us += duration_us; + if (duration_us > this->period_max_time_us) + this->period_max_time_us = duration_us; + this->total_count++; + this->total_time_us += duration_us; + if (duration_us > this->total_max_time_us) + this->total_max_time_us = duration_us; + } + void reset_period() { + this->period_count = 0; + this->period_time_us = 0; + this->period_max_time_us = 0; + } +}; +#endif + class Component { public: /** Where the component's initialization should happen. @@ -529,35 +560,8 @@ class Component { /// Bits 6-7: Unused - reserved for future expansion uint8_t component_state_{0x00}; volatile bool pending_enable_loop_{false}; ///< ISR-safe flag for enable_loop_soon_any_context - #ifdef USE_RUNTIME_STATS - /// Inline runtime statistics — eliminates std::map lookup on every loop iteration. - struct RuntimeStats { - // Period stats (reset each logging interval) - uint32_t period_count{0}; - uint32_t period_time_us{0}; - uint32_t period_max_time_us{0}; - // Total stats (persistent until reboot, uint64_t to avoid overflow) - uint32_t total_count{0}; - uint64_t total_time_us{0}; - uint32_t total_max_time_us{0}; - - void record_time(uint32_t duration_us) { - this->period_count++; - this->period_time_us += duration_us; - if (duration_us > this->period_max_time_us) - this->period_max_time_us = duration_us; - this->total_count++; - this->total_time_us += duration_us; - if (duration_us > this->total_max_time_us) - this->total_max_time_us = duration_us; - } - void reset_period() { - this->period_count = 0; - this->period_time_us = 0; - this->period_max_time_us = 0; - } - } runtime_stats_; + ComponentRuntimeStats runtime_stats_; #endif }; From 0b59acb922f23606b8aae5fccfb0a9ba3fe7d7b6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 31 Mar 2026 23:11:33 -1000 Subject: [PATCH 4/6] Fix access: use friend + static comparators for protected member access --- .../components/runtime_stats/runtime_stats.cpp | 16 ++++++++++------ esphome/components/runtime_stats/runtime_stats.h | 3 +++ esphome/core/application.h | 9 +++++++++ esphome/core/component.h | 7 +++++++ 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/esphome/components/runtime_stats/runtime_stats.cpp b/esphome/components/runtime_stats/runtime_stats.cpp index 1ece670ed2..b3f96c6ff6 100644 --- a/esphome/components/runtime_stats/runtime_stats.cpp +++ b/esphome/components/runtime_stats/runtime_stats.cpp @@ -47,9 +47,7 @@ void RuntimeStatsCollector::log_stats_() { } // Sort by period runtime (descending) - std::sort(sorted, sorted + count, [](Component *a, Component *b) { - return a->runtime_stats_.period_time_us > b->runtime_stats_.period_time_us; - }); + std::sort(sorted, sorted + count, compare_period_time_); // Log top components by period runtime for (size_t i = 0; i < count; i++) { @@ -64,9 +62,7 @@ void RuntimeStatsCollector::log_stats_() { ESP_LOGI(TAG, " Total stats (since boot): %zu active components", count); // Re-sort by total runtime for all-time stats - std::sort(sorted, sorted + count, [](Component *a, Component *b) { - return a->runtime_stats_.total_time_us > b->runtime_stats_.total_time_us; - }); + std::sort(sorted, sorted + count, compare_total_time_); for (size_t i = 0; i < count; i++) { const auto &stats = sorted[i]->runtime_stats_; @@ -82,6 +78,14 @@ void RuntimeStatsCollector::log_stats_() { } } +bool RuntimeStatsCollector::compare_period_time_(Component *a, Component *b) { + return a->runtime_stats_.period_time_us > b->runtime_stats_.period_time_us; +} + +bool RuntimeStatsCollector::compare_total_time_(Component *a, Component *b) { + return a->runtime_stats_.total_time_us > b->runtime_stats_.total_time_us; +} + void RuntimeStatsCollector::process_pending_stats(uint32_t current_time) { if ((int32_t) (current_time - this->next_log_time_) >= 0) { this->log_stats_(); diff --git a/esphome/components/runtime_stats/runtime_stats.h b/esphome/components/runtime_stats/runtime_stats.h index f81ca83e38..5f7c59d0b5 100644 --- a/esphome/components/runtime_stats/runtime_stats.h +++ b/esphome/components/runtime_stats/runtime_stats.h @@ -31,6 +31,9 @@ class RuntimeStatsCollector { protected: void log_stats_(); + // Static comparators — member functions have friend access, lambdas do not + static bool compare_period_time_(Component *a, Component *b); + static bool compare_total_time_(Component *a, Component *b); uint32_t log_interval_; uint32_t next_log_time_{0}; diff --git a/esphome/core/application.h b/esphome/core/application.h index 06ff30e81f..6cc61bc954 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -130,6 +130,12 @@ bool socket_ready_fd(int fd, bool loop_monitored); // NOLINT(readability-redund #endif } // namespace esphome::socket +#ifdef USE_RUNTIME_STATS +namespace esphome::runtime_stats { +class RuntimeStatsCollector; +} // namespace esphome::runtime_stats +#endif + // Forward declarations for friend access from codegen-generated setup() void setup(); // NOLINT(readability-redundant-declaration) - may be declared in Arduino.h void original_setup(); // NOLINT(readability-redundant-declaration) - used by cpp unit tests @@ -590,6 +596,9 @@ class Application { friend Component; #if defined(USE_SOCKET_SELECT_SUPPORT) && !defined(USE_LWIP_FAST_SELECT) friend bool socket::socket_ready_fd(int fd, bool loop_monitored); +#endif +#ifdef USE_RUNTIME_STATS + friend class runtime_stats::RuntimeStatsCollector; #endif friend void ::setup(); friend void ::original_setup(); diff --git a/esphome/core/component.h b/esphome/core/component.h index e85f45bbf5..2b2ae78f55 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -20,6 +20,12 @@ namespace esphome { // Forward declaration for LogString struct LogString; +#ifdef USE_RUNTIME_STATS +namespace runtime_stats { +class RuntimeStatsCollector; +} // namespace runtime_stats +#endif + /** Default setup priorities for components of different types. * * Components should return one of these setup priorities in get_setup_priority. @@ -561,6 +567,7 @@ class Component { uint8_t component_state_{0x00}; volatile bool pending_enable_loop_{false}; ///< ISR-safe flag for enable_loop_soon_any_context #ifdef USE_RUNTIME_STATS + friend class runtime_stats::RuntimeStatsCollector; ComponentRuntimeStats runtime_stats_; #endif }; From 695b41763a5c24aa0f4e1f557636954bd60b5d43 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 31 Mar 2026 23:12:42 -1000 Subject: [PATCH 5/6] Add WarnIfComponentBlockingGuard as friend for runtime_stats_ access --- esphome/core/component.h | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/core/component.h b/esphome/core/component.h index 2b2ae78f55..c5a331ee29 100644 --- a/esphome/core/component.h +++ b/esphome/core/component.h @@ -568,6 +568,7 @@ class Component { volatile bool pending_enable_loop_{false}; ///< ISR-safe flag for enable_loop_soon_any_context #ifdef USE_RUNTIME_STATS friend class runtime_stats::RuntimeStatsCollector; + friend class WarnIfComponentBlockingGuard; ComponentRuntimeStats runtime_stats_; #endif }; From 0daa0fb29d5591607489f158e39d8b28e6aff588 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 31 Mar 2026 23:17:18 -1000 Subject: [PATCH 6/6] Fix clang-tidy: remove trailing underscore from static methods --- esphome/components/runtime_stats/runtime_stats.cpp | 8 ++++---- esphome/components/runtime_stats/runtime_stats.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/runtime_stats/runtime_stats.cpp b/esphome/components/runtime_stats/runtime_stats.cpp index b3f96c6ff6..34aa31ec65 100644 --- a/esphome/components/runtime_stats/runtime_stats.cpp +++ b/esphome/components/runtime_stats/runtime_stats.cpp @@ -47,7 +47,7 @@ void RuntimeStatsCollector::log_stats_() { } // Sort by period runtime (descending) - std::sort(sorted, sorted + count, compare_period_time_); + std::sort(sorted, sorted + count, compare_period_time); // Log top components by period runtime for (size_t i = 0; i < count; i++) { @@ -62,7 +62,7 @@ void RuntimeStatsCollector::log_stats_() { ESP_LOGI(TAG, " Total stats (since boot): %zu active components", count); // Re-sort by total runtime for all-time stats - std::sort(sorted, sorted + count, compare_total_time_); + std::sort(sorted, sorted + count, compare_total_time); for (size_t i = 0; i < count; i++) { const auto &stats = sorted[i]->runtime_stats_; @@ -78,11 +78,11 @@ void RuntimeStatsCollector::log_stats_() { } } -bool RuntimeStatsCollector::compare_period_time_(Component *a, Component *b) { +bool RuntimeStatsCollector::compare_period_time(Component *a, Component *b) { return a->runtime_stats_.period_time_us > b->runtime_stats_.period_time_us; } -bool RuntimeStatsCollector::compare_total_time_(Component *a, Component *b) { +bool RuntimeStatsCollector::compare_total_time(Component *a, Component *b) { return a->runtime_stats_.total_time_us > b->runtime_stats_.total_time_us; } diff --git a/esphome/components/runtime_stats/runtime_stats.h b/esphome/components/runtime_stats/runtime_stats.h index 5f7c59d0b5..3c2c9f78ad 100644 --- a/esphome/components/runtime_stats/runtime_stats.h +++ b/esphome/components/runtime_stats/runtime_stats.h @@ -32,8 +32,8 @@ class RuntimeStatsCollector { protected: void log_stats_(); // Static comparators — member functions have friend access, lambdas do not - static bool compare_period_time_(Component *a, Component *b); - static bool compare_total_time_(Component *a, Component *b); + static bool compare_period_time(Component *a, Component *b); + static bool compare_total_time(Component *a, Component *b); uint32_t log_interval_; uint32_t next_log_time_{0};