mirror of
https://github.com/esphome/esphome.git
synced 2026-10-02 01:10:22 +00:00
Merge branch 'setup_heap_stats' into integration
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
||||
CODEOWNERS = ["@bdraco"]
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema({}),
|
||||
cv.only_on(["esp32", "esp8266", "rp2040", "bk72xx", "rtl87xx", "ln882x"]),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_SETUP_HEAP_STATS")
|
||||
@@ -0,0 +1,124 @@
|
||||
#include "setup_heap_stats.h"
|
||||
|
||||
#ifdef USE_SETUP_HEAP_STATS
|
||||
|
||||
#include <algorithm>
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
#include <esp_heap_caps.h>
|
||||
#endif
|
||||
#ifdef USE_ESP8266
|
||||
#include <Esp.h>
|
||||
#endif
|
||||
#ifdef USE_RP2040
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
|
||||
namespace setup_heap_stats {
|
||||
|
||||
static const char *const TAG = "setup_heap_stats";
|
||||
|
||||
SetupHeapStatsCollector::SetupHeapStatsCollector(uint32_t initial_baseline) {
|
||||
global_setup_heap_stats = this;
|
||||
uint32_t before_alloc = get_free_internal_heap();
|
||||
this->entries_ = new ComponentHeapEntry[ESPHOME_COMPONENT_COUNT]; // NOLINT
|
||||
uint32_t after_alloc = get_free_internal_heap();
|
||||
// Use the pre_setup() baseline but subtract our own entries allocation
|
||||
// so the collector's overhead is not attributed to the first component
|
||||
this->last_heap_snapshot_ = initial_baseline - (before_alloc - after_alloc);
|
||||
}
|
||||
|
||||
uint32_t SetupHeapStatsCollector::get_free_internal_heap() {
|
||||
#ifdef USE_ESP32
|
||||
return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
#elif defined(USE_ESP8266)
|
||||
return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance)
|
||||
#elif defined(USE_RP2040)
|
||||
return rp2040.getFreeHeap();
|
||||
#elif defined(USE_LIBRETINY)
|
||||
return lt_heap_get_free();
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetupHeapStatsCollector::record_component_registered(Component *comp) {
|
||||
uint32_t current_heap = get_free_internal_heap();
|
||||
int32_t delta = static_cast<int32_t>(this->last_heap_snapshot_) - static_cast<int32_t>(current_heap);
|
||||
this->last_heap_snapshot_ = current_heap;
|
||||
|
||||
if (this->entry_count_ < ESPHOME_COMPONENT_COUNT) {
|
||||
this->entries_[this->entry_count_].component = comp;
|
||||
this->entries_[this->entry_count_].construction_delta = delta;
|
||||
this->entries_[this->entry_count_].setup_delta = 0;
|
||||
this->entry_count_++;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Constructed %s: %" PRId32 " bytes (free: %" PRIu32 ")", LOG_STR_ARG(comp->get_component_log_str()),
|
||||
delta, current_heap);
|
||||
}
|
||||
|
||||
void SetupHeapStatsCollector::record_before_setup(Component *comp) {
|
||||
this->setup_component_ = comp;
|
||||
this->setup_before_heap_ = get_free_internal_heap();
|
||||
}
|
||||
|
||||
void SetupHeapStatsCollector::record_after_setup(Component *comp) {
|
||||
if (this->setup_component_ != comp)
|
||||
return;
|
||||
|
||||
uint32_t current_heap = get_free_internal_heap();
|
||||
int32_t delta = static_cast<int32_t>(this->setup_before_heap_) - static_cast<int32_t>(current_heap);
|
||||
|
||||
// Find the entry for this component and update setup delta
|
||||
for (uint16_t i = 0; i < this->entry_count_; i++) {
|
||||
if (this->entries_[i].component == comp) {
|
||||
this->entries_[i].setup_delta = delta;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Setup %s: %" PRId32 " bytes (free: %" PRIu32 ")", LOG_STR_ARG(comp->get_component_log_str()), delta,
|
||||
current_heap);
|
||||
|
||||
this->setup_component_ = nullptr;
|
||||
}
|
||||
|
||||
void SetupHeapStatsCollector::log_summary() {
|
||||
if (this->entries_ == nullptr || this->entry_count_ == 0)
|
||||
return;
|
||||
|
||||
// Sort by total (construction + setup) descending
|
||||
std::sort(this->entries_, this->entries_ + this->entry_count_,
|
||||
[](const ComponentHeapEntry &a, const ComponentHeapEntry &b) {
|
||||
return (a.construction_delta + a.setup_delta) > (b.construction_delta + b.setup_delta);
|
||||
});
|
||||
|
||||
ESP_LOGI(TAG, "Setup Heap Stats Summary (sorted by total, construction + setup):");
|
||||
for (uint16_t i = 0; i < this->entry_count_; i++) {
|
||||
const auto &entry = this->entries_[i];
|
||||
int32_t total = entry.construction_delta + entry.setup_delta;
|
||||
if (total == 0 && entry.construction_delta == 0 && entry.setup_delta == 0)
|
||||
continue;
|
||||
ESP_LOGI(TAG, " %s: %" PRId32 " bytes (construction: %" PRId32 ", setup: %" PRId32 ")",
|
||||
LOG_STR_ARG(entry.component->get_component_log_str()), total, entry.construction_delta, entry.setup_delta);
|
||||
}
|
||||
|
||||
// Free storage
|
||||
delete[] this->entries_; // NOLINT
|
||||
this->entries_ = nullptr;
|
||||
this->entry_count_ = 0;
|
||||
}
|
||||
|
||||
} // namespace setup_heap_stats
|
||||
|
||||
setup_heap_stats::SetupHeapStatsCollector *global_setup_heap_stats =
|
||||
nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_SETUP_HEAP_STATS
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_SETUP_HEAP_STATS
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
class Component; // Forward declaration
|
||||
|
||||
namespace setup_heap_stats {
|
||||
|
||||
struct ComponentHeapEntry {
|
||||
Component *component;
|
||||
int32_t construction_delta;
|
||||
int32_t setup_delta;
|
||||
};
|
||||
|
||||
class SetupHeapStatsCollector {
|
||||
public:
|
||||
explicit SetupHeapStatsCollector(uint32_t initial_baseline);
|
||||
|
||||
/// Called from register_component_() to record heap delta since last registration
|
||||
void record_component_registered(Component *comp);
|
||||
|
||||
/// Called before component->call_setup()
|
||||
void record_before_setup(Component *comp);
|
||||
|
||||
/// Called after component->call_setup()
|
||||
void record_after_setup(Component *comp);
|
||||
|
||||
/// Log sorted summary and free storage
|
||||
void log_summary();
|
||||
|
||||
/// Get free internal heap size (platform-specific)
|
||||
static uint32_t get_free_internal_heap();
|
||||
|
||||
protected:
|
||||
ComponentHeapEntry *entries_{nullptr};
|
||||
uint16_t entry_count_{0};
|
||||
uint32_t last_heap_snapshot_{0};
|
||||
// Temporary storage for before/after setup measurement
|
||||
Component *setup_component_{nullptr};
|
||||
uint32_t setup_before_heap_{0};
|
||||
};
|
||||
|
||||
} // namespace setup_heap_stats
|
||||
|
||||
extern setup_heap_stats::SetupHeapStatsCollector
|
||||
*global_setup_heap_stats; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_SETUP_HEAP_STATS
|
||||
@@ -21,6 +21,9 @@
|
||||
#ifdef USE_STATUS_LED
|
||||
#include "esphome/components/status_led/status_led.h"
|
||||
#endif
|
||||
#ifdef USE_SETUP_HEAP_STATS
|
||||
#include "esphome/components/setup_heap_stats/setup_heap_stats.h"
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESP8266) && defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
#include "esphome/components/socket/socket.h"
|
||||
@@ -86,6 +89,14 @@ void Application::register_component_(Component *comp) {
|
||||
return;
|
||||
}
|
||||
this->components_.push_back(comp);
|
||||
#ifdef USE_SETUP_HEAP_STATS
|
||||
if (global_setup_heap_stats == nullptr) {
|
||||
// Lazily create collector on first component registration
|
||||
// Pass the baseline captured in pre_setup() so the first component's cost is measured
|
||||
new setup_heap_stats::SetupHeapStatsCollector(this->setup_heap_stats_baseline_);
|
||||
}
|
||||
global_setup_heap_stats->record_component_registered(comp);
|
||||
#endif
|
||||
}
|
||||
void Application::setup() {
|
||||
ESP_LOGI(TAG, "Running through setup()");
|
||||
@@ -137,6 +148,12 @@ void Application::setup() {
|
||||
|
||||
ESP_LOGI(TAG, "setup() finished successfully!");
|
||||
|
||||
#ifdef USE_SETUP_HEAP_STATS
|
||||
if (global_setup_heap_stats != nullptr) {
|
||||
global_setup_heap_stats->log_summary();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SETUP_PRIORITY_OVERRIDE
|
||||
// Clear setup priority overrides to free memory
|
||||
clear_setup_priority_overrides();
|
||||
@@ -755,4 +772,10 @@ void Application::get_build_time_string(std::span<char, BUILD_TIME_STR_SIZE> buf
|
||||
buffer[buffer.size() - 1] = '\0';
|
||||
}
|
||||
|
||||
#ifdef USE_SETUP_HEAP_STATS
|
||||
void Application::init_setup_heap_stats_baseline_() {
|
||||
this->setup_heap_stats_baseline_ = setup_heap_stats::SetupHeapStatsCollector::get_free_internal_heap();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -134,6 +134,9 @@ class Application {
|
||||
this->name_ = name;
|
||||
this->friendly_name_ = friendly_name;
|
||||
}
|
||||
#ifdef USE_SETUP_HEAP_STATS
|
||||
this->init_setup_heap_stats_baseline_();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_DEVICES
|
||||
@@ -548,6 +551,10 @@ class Application {
|
||||
inline void drain_wake_notifications_(); // Read pending wake notifications in main loop (hot path - inlined)
|
||||
#endif
|
||||
|
||||
#ifdef USE_SETUP_HEAP_STATS
|
||||
void init_setup_heap_stats_baseline_();
|
||||
#endif
|
||||
|
||||
// === Member variables ordered by size to minimize padding ===
|
||||
|
||||
// Pointer-sized members first
|
||||
@@ -585,6 +592,9 @@ class Application {
|
||||
// 4-byte members
|
||||
uint32_t last_loop_{0};
|
||||
uint32_t loop_component_start_time_{0};
|
||||
#ifdef USE_SETUP_HEAP_STATS
|
||||
uint32_t setup_heap_stats_baseline_{0};
|
||||
#endif
|
||||
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
int max_fd_{-1}; // Highest file descriptor number for select()
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
#ifdef USE_RUNTIME_STATS
|
||||
#include "esphome/components/runtime_stats/runtime_stats.h"
|
||||
#endif
|
||||
#ifdef USE_SETUP_HEAP_STATS
|
||||
#include "esphome/components/setup_heap_stats/setup_heap_stats.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -241,8 +244,18 @@ void Component::call() {
|
||||
ESP_LOGV(TAG, "Setup %s", LOG_STR_ARG(this->get_component_log_str()));
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
|
||||
uint32_t start_time = millis();
|
||||
#endif
|
||||
#ifdef USE_SETUP_HEAP_STATS
|
||||
if (global_setup_heap_stats != nullptr) {
|
||||
global_setup_heap_stats->record_before_setup(this);
|
||||
}
|
||||
#endif
|
||||
this->call_setup();
|
||||
#ifdef USE_SETUP_HEAP_STATS
|
||||
if (global_setup_heap_stats != nullptr) {
|
||||
global_setup_heap_stats->record_after_setup(this);
|
||||
}
|
||||
#endif
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
|
||||
uint32_t setup_time = millis() - start_time;
|
||||
// Only log at CONFIG level if setup took longer than the blocking threshold
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
#define USE_SAFE_MODE_CALLBACK
|
||||
#define USE_SELECT
|
||||
#define USE_SENSOR
|
||||
#define USE_SETUP_HEAP_STATS
|
||||
#define USE_SETUP_PRIORITY_OVERRIDE
|
||||
#define USE_STATUS_LED
|
||||
#define USE_STATUS_SENSOR
|
||||
|
||||
@@ -554,7 +554,7 @@ static inline bool is_base64(char c) { return (isalnum(c) || (c == '+') || (c ==
|
||||
std::string base64_encode(const std::vector<uint8_t> &buf) { return base64_encode(buf.data(), buf.size()); }
|
||||
|
||||
// Encode 3 input bytes to 4 base64 characters, append 'count' to ret.
|
||||
static inline void base64_encode_triple_(const char *char_array_3, int count, std::string &ret) {
|
||||
static inline void base64_encode_triple(const char *char_array_3, int count, std::string &ret) {
|
||||
char char_array_4[4];
|
||||
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
|
||||
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
|
||||
@@ -573,7 +573,7 @@ std::string base64_encode(const uint8_t *buf, size_t buf_len) {
|
||||
while (buf_len--) {
|
||||
char_array_3[i++] = *(buf++);
|
||||
if (i == 3) {
|
||||
base64_encode_triple_(char_array_3, 4, ret);
|
||||
base64_encode_triple(char_array_3, 4, ret);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
@@ -582,7 +582,7 @@ std::string base64_encode(const uint8_t *buf, size_t buf_len) {
|
||||
for (int j = i; j < 3; j++)
|
||||
char_array_3[j] = '\0';
|
||||
|
||||
base64_encode_triple_(char_array_3, i + 1, ret);
|
||||
base64_encode_triple(char_array_3, i + 1, ret);
|
||||
|
||||
while ((i++ < 3))
|
||||
ret += '=';
|
||||
@@ -596,7 +596,7 @@ size_t base64_decode(const std::string &encoded_string, uint8_t *buf, size_t buf
|
||||
}
|
||||
|
||||
// Decode 4 base64 characters to up to 'count' output bytes, returns true if truncated.
|
||||
static inline bool base64_decode_quad_(uint8_t *char_array_4, int count, uint8_t *buf, size_t buf_len, size_t &out) {
|
||||
static inline bool base64_decode_quad(uint8_t *char_array_4, int count, uint8_t *buf, size_t buf_len, size_t &out) {
|
||||
for (int i = 0; i < 4; i++)
|
||||
char_array_4[i] = base64_find_char(char_array_4[i]);
|
||||
|
||||
@@ -631,7 +631,7 @@ size_t base64_decode(const uint8_t *encoded_data, size_t encoded_len, uint8_t *b
|
||||
char_array_4[i++] = encoded_data[in];
|
||||
in++;
|
||||
if (i == 4) {
|
||||
truncated |= base64_decode_quad_(char_array_4, 3, buf, buf_len, out);
|
||||
truncated |= base64_decode_quad(char_array_4, 3, buf, buf_len, out);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
@@ -640,7 +640,7 @@ size_t base64_decode(const uint8_t *encoded_data, size_t encoded_len, uint8_t *b
|
||||
for (int j = i; j < 4; j++)
|
||||
char_array_4[j] = 0;
|
||||
|
||||
truncated |= base64_decode_quad_(char_array_4, i - 1, buf, buf_len, out);
|
||||
truncated |= base64_decode_quad(char_array_4, i - 1, buf, buf_len, out);
|
||||
}
|
||||
|
||||
if (truncated) {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
setup_heap_stats:
|
||||
@@ -0,0 +1 @@
|
||||
setup_heap_stats:
|
||||
@@ -0,0 +1 @@
|
||||
setup_heap_stats:
|
||||
@@ -0,0 +1 @@
|
||||
setup_heap_stats:
|
||||
@@ -0,0 +1 @@
|
||||
setup_heap_stats:
|
||||
@@ -0,0 +1 @@
|
||||
setup_heap_stats:
|
||||
Reference in New Issue
Block a user