mirror of
https://github.com/esphome/esphome.git
synced 2025-11-08 02:51:49 +00:00
Compare commits
8 Commits
2025.10.0b
...
add-heap-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcdf209ac3 | ||
|
|
4c1111a395 | ||
|
|
57425a765b | ||
|
|
abb09b7fee | ||
|
|
b69fd2762e | ||
|
|
a38f0067ae | ||
|
|
00e128bdd5 | ||
|
|
35238c1437 |
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
35
heap_trace_example.yaml
Normal 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"
|
||||||
Reference in New Issue
Block a user