mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-03 16:41:50 +00:00 
			
		
		
		
	Compare commits
	
		
			8 Commits
		
	
	
		
			select_opt
			...
			add-heap-t
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					fcdf209ac3 | ||
| 
						 | 
					4c1111a395 | ||
| 
						 | 
					57425a765b | ||
| 
						 | 
					abb09b7fee | ||
| 
						 | 
					b69fd2762e | ||
| 
						 | 
					a38f0067ae | ||
| 
						 | 
					00e128bdd5 | ||
| 
						 | 
					35238c1437 | 
@@ -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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
									
								
							
							
						
						
									
										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