1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-01 15:41:52 +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 logging
from esphome import automation
from esphome.automation import Condition
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option
import esphome.config_validation as cv
from esphome.const import (
CONF_ACTION,
@@ -23,12 +25,14 @@ from esphome.const import (
CONF_TRIGGER_ID,
CONF_VARIABLES,
)
from esphome.core import coroutine_with_priority
from esphome.core import CORE, coroutine_with_priority
DEPENDENCIES = ["network"]
AUTO_LOAD = ["socket"]
CODEOWNERS = ["@OttoWinter"]
_LOGGER = logging.getLogger(__name__)
api_ns = cg.esphome_ns.namespace("api")
APIServer = api_ns.class_("APIServer", cg.Component, cg.Controller)
HomeAssistantServiceCallAction = api_ns.class_(
@@ -49,6 +53,11 @@ SERVICE_ARG_NATIVE_TYPES = {
"string[]": cg.std_vector.template(cg.std_string),
}
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):
@@ -95,6 +104,22 @@ def _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(
cv.Schema(
{
@@ -109,6 +134,7 @@ CONFIG_SCHEMA = cv.All(
): ACTIONS_SCHEMA,
cv.Exclusive(CONF_ACTIONS, group_of_exclusion=CONF_ACTIONS): ACTIONS_SCHEMA,
cv.Optional(CONF_ENCRYPTION): _encryption_schema,
cv.Optional(CONF_HEAP_TRACING): _heap_tracing_schema,
cv.Optional(CONF_ON_CLIENT_CONNECTED): automation.validate_automation(
single=True
),
@@ -176,6 +202,74 @@ async def to_code(config):
else:
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_global(api_ns.using)

View File

@@ -14,6 +14,96 @@
#include "esphome/components/logger/logger.h"
#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>
namespace esphome {
@@ -30,6 +120,11 @@ void APIServer::setup() {
ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server...");
this->setup_controller();
#ifdef USE_API_HEAP_TRACE
ESP_LOGI(TAG, "Initializing heap tracing");
start_heap_trace();
#endif
#ifdef USE_API_NOISE
uint32_t hash = 88491486UL;
@@ -154,6 +249,24 @@ void APIServer::loop() {
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() {
@@ -466,6 +579,16 @@ void APIServer::on_shutdown() {
c->send_disconnect_request(DisconnectRequest());
}
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

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"