Compare commits
10 Commits
api_reboot
...
runtime_wi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1532b8a545 | ||
|
|
f1cbd78aa0 | ||
|
|
83db3eddd9 | ||
|
|
cc2c5a544e | ||
|
|
8fba8c2800 | ||
|
|
51d1da8460 | ||
|
|
2f1257056d | ||
|
|
2f8f6967bf | ||
|
|
246527e618 | ||
|
|
3857cc9c83 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -143,3 +143,5 @@ sdkconfig.*
|
||||
/components
|
||||
/managed_components
|
||||
|
||||
|
||||
**/.claude/settings.local.json
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
#include <nvs_flash.h>
|
||||
#include <cinttypes>
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
#include "esphome/components/bluetooth_proxy/bluetooth_proxy.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_OTA
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#endif
|
||||
@@ -75,7 +79,90 @@ void ESP32BLETracker::setup() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void ESP32BLETracker::log_timing_stats_() {
|
||||
// Only log if we have data to report
|
||||
if (this->timing_stats_.loop_processing_count == 0)
|
||||
return;
|
||||
|
||||
ESP_LOGI(TAG, "BLE Tracker Timing Statistics (last %dms):", this->timing_stats_.log_interval_ms);
|
||||
|
||||
if (this->timing_stats_.loop_processing_count > 0) {
|
||||
float avg_loop_time = this->timing_stats_.loop_processing_time / (float) this->timing_stats_.loop_processing_count;
|
||||
ESP_LOGI(TAG, " BLE Scan Processing: count=%d, avg=%.2fms, total=%dms", this->timing_stats_.loop_processing_count,
|
||||
avg_loop_time, this->timing_stats_.loop_processing_time);
|
||||
|
||||
// Log scan results processed
|
||||
ESP_LOGI(TAG, " BLE Scan Results Processed: count=%d", this->timing_stats_.scan_result_count);
|
||||
}
|
||||
|
||||
// Log raw advertisement processing statistics
|
||||
if (this->timing_stats_.raw_adv_count > 0) {
|
||||
float avg_raw_time = this->timing_stats_.raw_adv_time / (float) this->timing_stats_.raw_adv_count;
|
||||
ESP_LOGI(TAG, " Raw Advertisement Processing: count=%d, avg=%.2fms, total=%dms", this->timing_stats_.raw_adv_count,
|
||||
avg_raw_time, this->timing_stats_.raw_adv_time);
|
||||
}
|
||||
|
||||
// Log parsed advertisement processing statistics
|
||||
if (this->timing_stats_.parse_adv_count > 0) {
|
||||
float avg_parse_time = this->timing_stats_.parse_adv_time / (float) this->timing_stats_.parse_adv_count;
|
||||
ESP_LOGI(TAG, " Parsed Advertisement Processing: count=%d, avg=%.2fms, total=%dms",
|
||||
this->timing_stats_.parse_adv_count, avg_parse_time, this->timing_stats_.parse_adv_time);
|
||||
}
|
||||
|
||||
// Log individual device parsing statistics
|
||||
if (this->timing_stats_.parse_device_count > 0) {
|
||||
float avg_device_time = this->timing_stats_.parse_device_time / (float) this->timing_stats_.parse_device_count;
|
||||
ESP_LOGI(TAG, " Device Parsing: count=%d, avg=%.2fms, total=%dms", this->timing_stats_.parse_device_count,
|
||||
avg_device_time, this->timing_stats_.parse_device_time);
|
||||
}
|
||||
|
||||
// Log component-specific timings
|
||||
if (this->timing_stats_.bluetooth_proxy_count > 0) {
|
||||
float avg_proxy_time = this->timing_stats_.bluetooth_proxy_time / (float) this->timing_stats_.bluetooth_proxy_count;
|
||||
ESP_LOGI(TAG, " Bluetooth Proxy: count=%d, avg=%.2fms, total=%dms", this->timing_stats_.bluetooth_proxy_count,
|
||||
avg_proxy_time, this->timing_stats_.bluetooth_proxy_time);
|
||||
}
|
||||
|
||||
if (this->timing_stats_.client_listeners_count > 0) {
|
||||
float avg_client_time =
|
||||
this->timing_stats_.client_listeners_time / (float) this->timing_stats_.client_listeners_count;
|
||||
ESP_LOGI(TAG, " Client Listeners: count=%d, avg=%.2fms, total=%dms", this->timing_stats_.client_listeners_count,
|
||||
avg_client_time, this->timing_stats_.client_listeners_time);
|
||||
}
|
||||
|
||||
// Log gap scan result processing statistics
|
||||
if (this->timing_stats_.gap_scan_result_count > 0) {
|
||||
float avg_gap_time = this->timing_stats_.gap_scan_result_time / (float) this->timing_stats_.gap_scan_result_count;
|
||||
ESP_LOGI(TAG, " GAP Scan Result Processing: count=%d, avg=%.2fms, total=%dms",
|
||||
this->timing_stats_.gap_scan_result_count, avg_gap_time, this->timing_stats_.gap_scan_result_time);
|
||||
}
|
||||
|
||||
// Reset timers for next period
|
||||
this->timing_stats_.loop_processing_time = 0;
|
||||
this->timing_stats_.loop_processing_count = 0;
|
||||
this->timing_stats_.bluetooth_proxy_time = 0;
|
||||
this->timing_stats_.bluetooth_proxy_count = 0;
|
||||
this->timing_stats_.client_listeners_time = 0;
|
||||
this->timing_stats_.client_listeners_count = 0;
|
||||
this->timing_stats_.scan_result_count = 0;
|
||||
this->timing_stats_.raw_adv_time = 0;
|
||||
this->timing_stats_.raw_adv_count = 0;
|
||||
this->timing_stats_.parse_adv_time = 0;
|
||||
this->timing_stats_.parse_adv_count = 0;
|
||||
this->timing_stats_.parse_device_time = 0;
|
||||
this->timing_stats_.parse_device_count = 0;
|
||||
this->timing_stats_.gap_scan_result_time = 0;
|
||||
this->timing_stats_.gap_scan_result_count = 0;
|
||||
}
|
||||
|
||||
void ESP32BLETracker::loop() {
|
||||
// Check if we need to log stats first
|
||||
uint32_t now = millis();
|
||||
if (now - this->timing_stats_.last_log_time >= this->timing_stats_.log_interval_ms) {
|
||||
this->log_timing_stats_();
|
||||
this->timing_stats_.last_log_time = now;
|
||||
}
|
||||
|
||||
if (!this->parent_->is_active()) {
|
||||
this->ble_was_disabled_ = true;
|
||||
return;
|
||||
@@ -123,25 +210,75 @@ void ESP32BLETracker::loop() {
|
||||
if (this->scanner_state_ == ScannerState::RUNNING &&
|
||||
this->scan_result_index_ && // if it looks like we have a scan result we will take the lock
|
||||
xSemaphoreTake(this->scan_result_lock_, 5L / portTICK_PERIOD_MS)) {
|
||||
// Start timing the BLE scan processing
|
||||
uint32_t processing_start = millis();
|
||||
|
||||
uint32_t index = this->scan_result_index_;
|
||||
if (index >= ESP32BLETracker::SCAN_RESULT_BUFFER_SIZE) {
|
||||
ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up.");
|
||||
}
|
||||
|
||||
if (this->raw_advertisements_) {
|
||||
this->timing_stats_.scan_result_count += this->scan_result_index_;
|
||||
|
||||
// Track overall raw advertisement processing time
|
||||
uint32_t raw_adv_start = millis();
|
||||
|
||||
// Time each listener type separately
|
||||
for (auto *listener : this->listeners_) {
|
||||
// Check if this is the bluetooth_proxy component
|
||||
bool is_bluetooth_proxy = false;
|
||||
|
||||
#ifdef USE_BLUETOOTH_PROXY
|
||||
// Since RTTI is disabled (typeid can't be used), check if the address is the global bluetooth proxy
|
||||
// This is a safe pointer comparison since we're not calling any methods on it
|
||||
if (esphome::bluetooth_proxy::global_bluetooth_proxy != nullptr) {
|
||||
is_bluetooth_proxy =
|
||||
(listener == static_cast<ESPBTDeviceListener *>(esphome::bluetooth_proxy::global_bluetooth_proxy));
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t listener_start = millis();
|
||||
listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
|
||||
uint32_t listener_time = millis() - listener_start;
|
||||
|
||||
// Track times for specific components
|
||||
if (is_bluetooth_proxy) {
|
||||
this->timing_stats_.bluetooth_proxy_time += listener_time;
|
||||
this->timing_stats_.bluetooth_proxy_count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Time the client listeners
|
||||
uint32_t clients_start = millis();
|
||||
for (auto *client : this->clients_) {
|
||||
client->parse_devices(this->scan_result_buffer_, this->scan_result_index_);
|
||||
}
|
||||
uint32_t clients_time = millis() - clients_start;
|
||||
this->timing_stats_.client_listeners_time += clients_time;
|
||||
this->timing_stats_.client_listeners_count++;
|
||||
|
||||
// Calculate total raw advertisement processing time
|
||||
uint32_t raw_adv_time = millis() - raw_adv_start;
|
||||
this->timing_stats_.raw_adv_time += raw_adv_time;
|
||||
this->timing_stats_.raw_adv_count++;
|
||||
}
|
||||
|
||||
if (this->parse_advertisements_) {
|
||||
// Track overall time for parsed advertisements
|
||||
uint32_t parse_adv_start = millis();
|
||||
|
||||
for (size_t i = 0; i < index; i++) {
|
||||
// Time individual device parsing
|
||||
uint32_t device_start = millis();
|
||||
|
||||
ESPBTDevice device;
|
||||
device.parse_scan_rst(this->scan_result_buffer_[i]);
|
||||
|
||||
uint32_t device_parse_time = millis() - device_start;
|
||||
this->timing_stats_.parse_device_time += device_parse_time;
|
||||
this->timing_stats_.parse_device_count++;
|
||||
|
||||
bool found = false;
|
||||
for (auto *listener : this->listeners_) {
|
||||
if (listener->parse_device(device))
|
||||
@@ -161,9 +298,19 @@ void ESP32BLETracker::loop() {
|
||||
this->print_bt_device_info(device);
|
||||
}
|
||||
}
|
||||
|
||||
// Record total time for parsed advertisements processing
|
||||
uint32_t parse_adv_time = millis() - parse_adv_start;
|
||||
this->timing_stats_.parse_adv_time += parse_adv_time;
|
||||
this->timing_stats_.parse_adv_count++;
|
||||
}
|
||||
this->scan_result_index_ = 0;
|
||||
xSemaphoreGive(this->scan_result_lock_);
|
||||
|
||||
// Record timing for the BLE scan processing
|
||||
uint32_t processing_time = millis() - processing_start;
|
||||
this->timing_stats_.loop_processing_time += processing_time;
|
||||
this->timing_stats_.loop_processing_count++;
|
||||
}
|
||||
if (this->scanner_state_ == ScannerState::STOPPED) {
|
||||
this->end_of_scan_(); // Change state to IDLE
|
||||
@@ -445,6 +592,8 @@ void ESP32BLETracker::gap_scan_stop_complete_(const esp_ble_gap_cb_param_t::ble_
|
||||
}
|
||||
|
||||
void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) {
|
||||
uint32_t start_time = millis();
|
||||
|
||||
ESP_LOGV(TAG, "gap_scan_result - event %d", param.search_evt);
|
||||
if (param.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
|
||||
if (xSemaphoreTake(this->scan_result_lock_, 0L)) {
|
||||
@@ -470,6 +619,18 @@ void ESP32BLETracker::gap_scan_result_(const esp_ble_gap_cb_param_t::ble_scan_re
|
||||
}
|
||||
this->set_scanner_state_(ScannerState::STOPPED);
|
||||
}
|
||||
|
||||
// Record timing statistics
|
||||
uint32_t duration = millis() - start_time;
|
||||
this->timing_stats_.gap_scan_result_time += duration;
|
||||
this->timing_stats_.gap_scan_result_count++;
|
||||
|
||||
// Periodically log statistics
|
||||
uint32_t now = millis();
|
||||
if (now - this->timing_stats_.last_log_time >= this->timing_stats_.log_interval_ms) {
|
||||
this->log_timing_stats_();
|
||||
this->timing_stats_.last_log_time = now;
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32BLETracker::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
|
||||
@@ -247,6 +247,9 @@ class ESP32BLETracker : public Component,
|
||||
}
|
||||
ScannerState get_scanner_state() const { return this->scanner_state_; }
|
||||
|
||||
// Logs timing statistics for BLE operations
|
||||
void log_timing_stats_();
|
||||
|
||||
protected:
|
||||
void stop_scan_();
|
||||
/// Start a single scan by setting up the parameters and doing some esp-idf calls.
|
||||
@@ -299,6 +302,39 @@ class ESP32BLETracker : public Component,
|
||||
int discovered_{0};
|
||||
int searching_{0};
|
||||
int disconnecting_{0};
|
||||
|
||||
// Timing statistics
|
||||
struct {
|
||||
// Loop processing timing
|
||||
uint32_t loop_processing_time{0};
|
||||
uint32_t loop_processing_count{0};
|
||||
|
||||
// Detailed component-level timings
|
||||
uint32_t bluetooth_proxy_time{0};
|
||||
uint32_t bluetooth_proxy_count{0};
|
||||
uint32_t client_listeners_time{0};
|
||||
uint32_t client_listeners_count{0};
|
||||
uint32_t scan_result_count{0};
|
||||
|
||||
// Raw advertisement processing stats
|
||||
uint32_t raw_adv_time{0};
|
||||
uint32_t raw_adv_count{0};
|
||||
|
||||
// Parsed advertisement processing stats
|
||||
uint32_t parse_adv_time{0};
|
||||
uint32_t parse_adv_count{0};
|
||||
uint32_t parse_device_time{0};
|
||||
uint32_t parse_device_count{0};
|
||||
|
||||
// Gap scan result stats
|
||||
uint32_t gap_scan_result_time{0};
|
||||
uint32_t gap_scan_result_count{0};
|
||||
|
||||
// Logging control
|
||||
uint32_t last_log_time{0};
|
||||
uint32_t log_interval_ms{30000}; // 30 seconds
|
||||
} timing_stats_;
|
||||
|
||||
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
|
||||
bool coex_prefer_ble_{false};
|
||||
#endif
|
||||
|
||||
26
esphome/components/runtime_stats/__init__.py
Normal file
26
esphome/components/runtime_stats/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Runtime statistics component for ESPHome.
|
||||
"""
|
||||
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
||||
DEPENDENCIES = []
|
||||
|
||||
CONF_ENABLED = "enabled"
|
||||
CONF_LOG_INTERVAL = "log_interval"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ENABLED, default=True): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_LOG_INTERVAL, default=60000
|
||||
): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
"""Generate code for the runtime statistics component."""
|
||||
cg.add(cg.App.set_runtime_stats_enabled(config[CONF_ENABLED]))
|
||||
cg.add(cg.App.set_runtime_stats_log_interval(config[CONF_LOG_INTERVAL]))
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/runtime_stats.h"
|
||||
#include "esphome/core/scheduler.h"
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
@@ -234,6 +235,18 @@ class Application {
|
||||
|
||||
uint32_t get_loop_interval() const { return this->loop_interval_; }
|
||||
|
||||
/** Enable or disable runtime statistics collection.
|
||||
*
|
||||
* @param enable Whether to enable runtime statistics collection.
|
||||
*/
|
||||
void set_runtime_stats_enabled(bool enable) { runtime_stats.set_enabled(enable); }
|
||||
|
||||
/** Set the interval at which runtime statistics are logged.
|
||||
*
|
||||
* @param interval The interval in milliseconds between logging of runtime statistics.
|
||||
*/
|
||||
void set_runtime_stats_log_interval(uint32_t interval) { runtime_stats.set_log_interval(interval); }
|
||||
|
||||
void schedule_dump_config() { this->dump_config_at_ = 0; }
|
||||
|
||||
void feed_wdt();
|
||||
|
||||
@@ -243,7 +243,13 @@ void PollingComponent::set_update_interval(uint32_t update_interval) { this->upd
|
||||
WarnIfComponentBlockingGuard::WarnIfComponentBlockingGuard(Component *component)
|
||||
: started_(millis()), component_(component) {}
|
||||
WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {
|
||||
uint32_t blocking_time = millis() - this->started_;
|
||||
uint32_t current_time = millis();
|
||||
uint32_t blocking_time = current_time - this->started_;
|
||||
|
||||
// Record component runtime stats
|
||||
runtime_stats.record_component_time(this->component_, blocking_time, current_time);
|
||||
|
||||
// Original blocking check logic
|
||||
bool should_warn;
|
||||
if (this->component_ != nullptr) {
|
||||
should_warn = this->component_->should_warn_of_blocking(blocking_time);
|
||||
@@ -254,7 +260,6 @@ WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() {
|
||||
const char *src = component_ == nullptr ? "<null>" : component_->get_component_source();
|
||||
ESP_LOGW(TAG, "Component %s took a long time for an operation (%" PRIu32 " ms).", src, blocking_time);
|
||||
ESP_LOGW(TAG, "Components should block for at most 30 ms.");
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "esphome/core/optional.h"
|
||||
#include "esphome/core/runtime_stats.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
|
||||
28
esphome/core/runtime_stats.cpp
Normal file
28
esphome/core/runtime_stats.cpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#include "esphome/core/runtime_stats.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
RuntimeStatsCollector runtime_stats;
|
||||
|
||||
void RuntimeStatsCollector::record_component_time(Component *component, uint32_t duration_ms, uint32_t current_time) {
|
||||
if (!this->enabled_ || component == nullptr)
|
||||
return;
|
||||
|
||||
const char *component_source = component->get_component_source();
|
||||
this->component_stats_[component_source].record_time(duration_ms);
|
||||
|
||||
// If next_log_time_ is 0, initialize it
|
||||
if (this->next_log_time_ == 0) {
|
||||
this->next_log_time_ = current_time + this->log_interval_;
|
||||
return;
|
||||
}
|
||||
|
||||
if (current_time >= this->next_log_time_) {
|
||||
this->log_stats_();
|
||||
this->reset_stats_();
|
||||
this->next_log_time_ = current_time + this->log_interval_;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
161
esphome/core/runtime_stats.h
Normal file
161
esphome/core/runtime_stats.h
Normal file
@@ -0,0 +1,161 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
static const char *const RUNTIME_TAG = "runtime";
|
||||
|
||||
class Component; // Forward declaration
|
||||
|
||||
class ComponentRuntimeStats {
|
||||
public:
|
||||
ComponentRuntimeStats()
|
||||
: period_count_(0),
|
||||
total_count_(0),
|
||||
period_time_ms_(0),
|
||||
total_time_ms_(0),
|
||||
period_max_time_ms_(0),
|
||||
total_max_time_ms_(0) {}
|
||||
|
||||
void record_time(uint32_t duration_ms) {
|
||||
// Update period counters
|
||||
this->period_count_++;
|
||||
this->period_time_ms_ += duration_ms;
|
||||
if (duration_ms > this->period_max_time_ms_)
|
||||
this->period_max_time_ms_ = duration_ms;
|
||||
|
||||
// Update total counters
|
||||
this->total_count_++;
|
||||
this->total_time_ms_ += duration_ms;
|
||||
if (duration_ms > this->total_max_time_ms_)
|
||||
this->total_max_time_ms_ = duration_ms;
|
||||
}
|
||||
|
||||
void reset_period_stats() {
|
||||
this->period_count_ = 0;
|
||||
this->period_time_ms_ = 0;
|
||||
this->period_max_time_ms_ = 0;
|
||||
}
|
||||
|
||||
// Period stats (reset each logging interval)
|
||||
uint32_t get_period_count() const { return this->period_count_; }
|
||||
uint32_t get_period_time_ms() const { return this->period_time_ms_; }
|
||||
uint32_t get_period_max_time_ms() const { return this->period_max_time_ms_; }
|
||||
float get_period_avg_time_ms() const {
|
||||
return this->period_count_ > 0 ? this->period_time_ms_ / static_cast<float>(this->period_count_) : 0.0f;
|
||||
}
|
||||
|
||||
// Total stats (persistent until reboot)
|
||||
uint32_t get_total_count() const { return this->total_count_; }
|
||||
uint32_t get_total_time_ms() const { return this->total_time_ms_; }
|
||||
uint32_t get_total_max_time_ms() const { return this->total_max_time_ms_; }
|
||||
float get_total_avg_time_ms() const {
|
||||
return this->total_count_ > 0 ? this->total_time_ms_ / static_cast<float>(this->total_count_) : 0.0f;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Period stats (reset each logging interval)
|
||||
uint32_t period_count_;
|
||||
uint32_t period_time_ms_;
|
||||
uint32_t period_max_time_ms_;
|
||||
|
||||
// Total stats (persistent until reboot)
|
||||
uint32_t total_count_;
|
||||
uint32_t total_time_ms_;
|
||||
uint32_t total_max_time_ms_;
|
||||
};
|
||||
|
||||
// For sorting components by run time
|
||||
struct ComponentStatPair {
|
||||
std::string name;
|
||||
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 {
|
||||
public:
|
||||
RuntimeStatsCollector() : log_interval_(60000), next_log_time_(0), enabled_(true) {}
|
||||
|
||||
void set_log_interval(uint32_t log_interval) { this->log_interval_ = log_interval; }
|
||||
uint32_t get_log_interval() const { return this->log_interval_; }
|
||||
|
||||
void set_enabled(bool enabled) { this->enabled_ = enabled; }
|
||||
bool is_enabled() const { return this->enabled_; }
|
||||
|
||||
void record_component_time(Component *component, uint32_t duration_ms, uint32_t current_time);
|
||||
|
||||
protected:
|
||||
void log_stats_() {
|
||||
ESP_LOGI(RUNTIME_TAG, "Component Runtime Statistics");
|
||||
ESP_LOGI(RUNTIME_TAG, "Period stats (last %" PRIu32 "ms):", this->log_interval_);
|
||||
|
||||
// First collect stats we want to display
|
||||
std::vector<ComponentStatPair> stats_to_display;
|
||||
|
||||
for (const auto &it : this->component_stats_) {
|
||||
const ComponentRuntimeStats &stats = it.second;
|
||||
if (stats.get_period_count() > 0) {
|
||||
ComponentStatPair pair = {it.first, &stats};
|
||||
stats_to_display.push_back(pair);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by period runtime (descending)
|
||||
std::sort(stats_to_display.begin(), stats_to_display.end(), std::greater<ComponentStatPair>());
|
||||
|
||||
// Log top components by period runtime
|
||||
for (const auto &it : stats_to_display) {
|
||||
const std::string &source = it.name;
|
||||
const ComponentRuntimeStats *stats = it.stats;
|
||||
|
||||
ESP_LOGI(RUNTIME_TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms",
|
||||
source.c_str(), stats->get_period_count(), stats->get_period_avg_time_ms(),
|
||||
stats->get_period_max_time_ms(), stats->get_period_time_ms());
|
||||
}
|
||||
|
||||
// Log total stats since boot
|
||||
ESP_LOGI(RUNTIME_TAG, "Total stats (since boot):");
|
||||
|
||||
// Re-sort by total runtime for all-time stats
|
||||
std::sort(stats_to_display.begin(), stats_to_display.end(),
|
||||
[](const ComponentStatPair &a, const ComponentStatPair &b) {
|
||||
return a.stats->get_total_time_ms() > b.stats->get_total_time_ms();
|
||||
});
|
||||
|
||||
for (const auto &it : stats_to_display) {
|
||||
const std::string &source = it.name;
|
||||
const ComponentRuntimeStats *stats = it.stats;
|
||||
|
||||
ESP_LOGI(RUNTIME_TAG, " %s: count=%" PRIu32 ", avg=%.2fms, max=%" PRIu32 "ms, total=%" PRIu32 "ms",
|
||||
source.c_str(), stats->get_total_count(), stats->get_total_avg_time_ms(), stats->get_total_max_time_ms(),
|
||||
stats->get_total_time_ms());
|
||||
}
|
||||
}
|
||||
|
||||
void reset_stats_() {
|
||||
for (auto &it : this->component_stats_) {
|
||||
it.second.reset_period_stats();
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, ComponentRuntimeStats> component_stats_;
|
||||
uint32_t log_interval_;
|
||||
uint32_t next_log_time_;
|
||||
bool enabled_;
|
||||
};
|
||||
|
||||
// Global instance for runtime stats collection
|
||||
extern RuntimeStatsCollector runtime_stats;
|
||||
|
||||
} // namespace esphome
|
||||
Reference in New Issue
Block a user