1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-04 09:01:49 +00:00

Compare commits

...

8 Commits

Author SHA1 Message Date
J. Nick Koston
fcdf209ac3 wip 2025-05-08 18:45:57 -05:00
J. Nick Koston
4c1111a395 devug 2025-05-08 17:20:06 -05:00
J. Nick Koston
57425a765b devug 2025-05-08 17:18:43 -05:00
J. Nick Koston
abb09b7fee wip 2025-05-08 16:43:00 -05:00
J. Nick Koston
b69fd2762e wip 2025-05-08 16:42:26 -05:00
J. Nick Koston
a38f0067ae debug 2025-05-08 16:10:52 -05:00
J. Nick Koston
00e128bdd5 Fix heap tracing function scope issues
- Add extern \C\ linkage to heap tracing functions
- Forward declare the functions in the API server implementation
- Ensures the heap tracing functions are accessible from any namespace
2025-05-08 15:29:11 -05:00
J. Nick Koston
35238c1437 Add heap tracing capability to API component
- Add heap tracing configuration options to API component
- Implement periodic heap trace dumping (every 30 seconds)
- Configure ESP-IDF settings for heap tracing
- Add sample YAML configuration
- Useful for debugging memory reallocation overhead issues
2025-05-08 15:25:06 -05:00
3 changed files with 253 additions and 1 deletions

View File

@@ -1,8 +1,10 @@
import base64 import base64
import logging
from esphome import automation from esphome import automation
from esphome.automation import Condition from esphome.automation import Condition
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ACTION, CONF_ACTION,
@@ -23,12 +25,14 @@ from esphome.const import (
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_VARIABLES, CONF_VARIABLES,
) )
from esphome.core import coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
DEPENDENCIES = ["network"] DEPENDENCIES = ["network"]
AUTO_LOAD = ["socket"] AUTO_LOAD = ["socket"]
CODEOWNERS = ["@OttoWinter"] CODEOWNERS = ["@OttoWinter"]
_LOGGER = logging.getLogger(__name__)
api_ns = cg.esphome_ns.namespace("api") api_ns = cg.esphome_ns.namespace("api")
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller) APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
HomeAssistantServiceCallAction = api_ns.class_( HomeAssistantServiceCallAction = api_ns.class_(
@@ -49,6 +53,11 @@ SERVICE_ARG_NATIVE_TYPES = {
"string[]": cg.std_vector.template(cg.std_string), "string[]": cg.std_vector.template(cg.std_string),
} }
CONF_ENCRYPTION = "encryption" CONF_ENCRYPTION = "encryption"
CONF_HEAP_TRACING = "heap_tracing"
CONF_HEAP_TRACING_STANDALONE = "standalone" # vs SYSTEM
CONF_HEAP_TRACING_RECORDS = "num_records"
CONF_HEAP_TASK_TRACKING = "task_tracking"
CONF_HEAP_TASK_MAX = "max_tasks"
def validate_encryption_key(value): def validate_encryption_key(value):
@@ -95,6 +104,22 @@ def _encryption_schema(config):
return ENCRYPTION_SCHEMA(config) return ENCRYPTION_SCHEMA(config)
HEAP_TRACING_SCHEMA = cv.Schema(
{
cv.Optional(CONF_HEAP_TRACING_STANDALONE, default=True): cv.boolean,
cv.Optional(CONF_HEAP_TRACING_RECORDS, default=100): cv.positive_int,
cv.Optional(CONF_HEAP_TASK_TRACKING, default=True): cv.boolean,
cv.Optional(CONF_HEAP_TASK_MAX, default=10): cv.positive_int,
}
)
def _heap_tracing_schema(config):
if config is None:
config = {}
return HEAP_TRACING_SCHEMA(config)
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
cv.Schema( cv.Schema(
{ {
@@ -109,6 +134,7 @@ CONFIG_SCHEMA = cv.All(
): ACTIONS_SCHEMA, ): ACTIONS_SCHEMA,
cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA, cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA,
cv.Optional(CONF_ENCRYPTION): _encryption_schema, cv.Optional(CONF_ENCRYPTION): _encryption_schema,
cv.Optional(CONF_HEAP_TRACING): _heap_tracing_schema,
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation( cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
single=True single=True
), ),
@@ -176,6 +202,74 @@ async def to_code(config):
else: else:
cg.add_define("USE_API_PLAINTEXT") cg.add_define("USE_API_PLAINTEXT")
# Handle heap tracing configuration if ESP32 platform and using ESP-IDF
if (heap_tracing_config := config.get(CONF_HEAP_TRACING, None)) is not None:
if CORE.using_esp_idf:
# Enable heap tracing in sdkconfig
add_idf_sdkconfig_option("CONFIG_HEAP_TRACING", True)
add_idf_sdkconfig_option("CONFIG_HEAP_TRACE_STACK_DEPTH", "30")
add_idf_sdkconfig_option("CONFIG_ESP32_APPTRACE_ENABLE", True)
# Set tracing mode (standalone or system)
if heap_tracing_config[CONF_HEAP_TRACING_STANDALONE]:
add_idf_sdkconfig_option("CONFIG_HEAP_TRACING_STANDALONE", True)
else:
add_idf_sdkconfig_option("CONFIG_HEAP_TRACING_SYSTEM", True)
# Enable runtime stats gathering for task info
if heap_tracing_config[CONF_HEAP_TASK_TRACKING]:
add_idf_sdkconfig_option(
"CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS", True
)
add_idf_sdkconfig_option(
"CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS", True
)
add_idf_sdkconfig_option("CONFIG_FREERTOS_USE_TRACE_FACILITY", True)
# Generate code to implement heap tracing
cg.add_global(cg.RawStatement('#include "esp_heap_trace.h"'))
# Define the trace record buffer
num_records = heap_tracing_config[CONF_HEAP_TRACING_RECORDS]
cg.add_global(
cg.RawStatement(
f"static heap_trace_record_t trace_record[{num_records}];"
)
)
# No additional setup needed for task tracking
# Add helper functions for heap tracing with extern "C" to make them globally accessible
cg.add_global(
cg.RawStatement(
"""
// Global heap tracing functions that can be called from any context
extern "C" void start_heap_trace() {
heap_trace_init_standalone(trace_record, """
+ str(num_records)
+ """);
heap_trace_start(HEAP_TRACE_LEAKS);
}
extern "C" void stop_and_dump_heap_trace() {
heap_trace_stop();
heap_trace_dump();
}
"""
)
)
# Add periodic heap trace dumping to the api_server.cpp file
# This will be added in C++ code
cg.add_define("USE_API_HEAP_TRACE")
else:
# Not using ESP-IDF, so we can't use heap tracing
_LOGGER.warning(
"Heap tracing is only available when using ESP-IDF. "
"Disabling heap tracing configuration."
)
cg.add_define("USE_API") cg.add_define("USE_API")
cg.add_global(api_ns.using) cg.add_global(api_ns.using)

View File

@@ -14,6 +14,96 @@
#include "esphome/components/logger/logger.h" #include "esphome/components/logger/logger.h"
#endif #endif
#ifdef USE_API_HEAP_TRACE
#include "esp_heap_trace.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
// Forward declare heap tracing functions that will be used in the API class
extern "C" void start_heap_trace();
extern "C" void stop_and_dump_heap_trace();
// Task heap information tracking
extern "C" void dump_task_heap_info() {
// Get basic heap statistics
multi_heap_info_t info;
heap_caps_get_info(&info, MALLOC_CAP_INTERNAL);
ESP_LOGI("HEAP", "=== Task Heap Information ===");
ESP_LOGI("HEAP", "-------------------------------------");
ESP_LOGI("HEAP", "Total free bytes: %u", info.total_free_bytes);
ESP_LOGI("HEAP", "Total allocated bytes: %u", info.total_allocated_bytes);
ESP_LOGI("HEAP", "Minimum free bytes: %u", info.minimum_free_bytes);
ESP_LOGI("HEAP", "Largest free block: %u", info.largest_free_block);
ESP_LOGI("HEAP", "Free blocks: %u", info.free_blocks);
ESP_LOGI("HEAP", "Allocated blocks: %u", info.allocated_blocks);
ESP_LOGI("HEAP", "Total blocks: %u", info.total_blocks);
ESP_LOGI("HEAP", "-------------------------------------");
// Get information about running tasks with a much larger buffer to prevent overflow
// The FreeRTOS functions don't provide a way to check buffer size requirements in advance
static char buffer[2048];
// Zero out the buffer for safety
memset(buffer, 0, sizeof(buffer));
// Get task list
vTaskList(buffer);
// Check if buffer has valid content
if (buffer[0] != '\0') {
ESP_LOGI("HEAP", "Task Information:");
ESP_LOGI("HEAP", "Name State Priority Stack Num");
ESP_LOGI("HEAP", "-------------------------------------");
// Process the buffer line by line to add the log prefix to each line
char *line = strtok(buffer, "\n\r");
int count = 0;
while (line != nullptr && strlen(line) > 0 && count < 20) {
ESP_LOGI("HEAP", "%s", line);
line = strtok(nullptr, "\n\r");
count++;
}
} else {
ESP_LOGE("HEAP", "Could not get task information");
}
ESP_LOGI("HEAP", "-------------------------------------");
// Runtime statistics - use a separate section with a different buffer to avoid corruption
static char stats_buffer[2048];
memset(stats_buffer, 0, sizeof(stats_buffer));
// Get runtime stats
vTaskGetRunTimeStats(stats_buffer);
// Check if buffer has valid content
if (stats_buffer[0] != '\0') {
ESP_LOGI("HEAP", "Task Runtime Statistics:");
ESP_LOGI("HEAP", "Name Time Percentage");
ESP_LOGI("HEAP", "-------------------------------------");
// Process the runtime stats buffer line by line safely
char *line = strtok(stats_buffer, "\n\r");
int count = 0;
// Limit to 20 lines to prevent buffer overruns
while (line != nullptr && count < 20) {
// Skip empty lines
if (strlen(line) > 0) {
ESP_LOGI("HEAP", "%s", line);
}
line = strtok(nullptr, "\n\r");
count++;
}
} else {
ESP_LOGE("HEAP", "Could not get task runtime statistics");
}
ESP_LOGI("HEAP", "-------------------------------------");
}
#endif
#include <algorithm> #include <algorithm>
namespace esphome { namespace esphome {
@@ -30,6 +120,11 @@ void APIServer::setup() {
ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server..."); ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
this->setup_controller(); this->setup_controller();
#ifdef USE_API_HEAP_TRACE
ESP_LOGI(TAG, "Initializing heap tracing");
start_heap_trace();
#endif
#ifdef USE_API_NOISE #ifdef USE_API_NOISE
uint32_t hash = 88491486UL; uint32_t hash = 88491486UL;
@@ -154,6 +249,24 @@ void APIServer::loop() {
this->status_clear_warning(); this->status_clear_warning();
} }
} }
#ifdef USE_API_HEAP_TRACE
// Periodically dump heap trace information (every 30 seconds)
static uint32_t last_heap_trace_dump = 0;
const uint32_t now = millis();
if (now - last_heap_trace_dump > 30000) { // 30 seconds
ESP_LOGI(TAG, "Dumping heap trace information");
stop_and_dump_heap_trace();
// Also dump task-specific heap information
dump_task_heap_info();
// Start a new trace for the next period
start_heap_trace();
last_heap_trace_dump = now;
}
#endif
} }
void APIServer::dump_config() { void APIServer::dump_config() {
@@ -466,6 +579,16 @@ void APIServer::on_shutdown() {
c->send_disconnect_request(DisconnectRequest()); c->send_disconnect_request(DisconnectRequest());
} }
delay(10); delay(10);
#ifdef USE_API_HEAP_TRACE
// Make sure to stop tracing on shutdown to get final results
ESP_LOGI(TAG, "Final heap trace dump on shutdown");
stop_and_dump_heap_trace();
// Dump final task heap information
ESP_LOGI(TAG, "Final task heap information dump on shutdown");
dump_task_heap_info();
#endif
} }
} // namespace api } // namespace api

35
heap_trace_example.yaml Normal file
View File

@@ -0,0 +1,35 @@
esphome:
name: esp32-heap-trace
platform: ESP32
board: esp32dev
# Use ESP-IDF framework which is required for heap tracing
framework:
type: esp-idf
version: recommended
# Enable logging
logger:
level: INFO
# Enable Home Assistant API
api:
# Enable heap tracing with the following configuration
heap_tracing:
# Use standalone tracing (vs system tracing)
standalone: true
# Number of trace records to keep (more records = more memory usage)
num_records: 100
# Enable task statistics tracking (shows task names, stack usage, etc.)
task_tracking: true
# Enable OTA updates
ota:
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot in case wifi connection fails
ap:
ssid: "Esp32-Heap-Trace"
password: "12345678"