I would like to share with you how I solved the memory leak problem in a very complex firmware.
With many tasks running at the same time.
I have a product that works as a monitoring machine.
It pings devices all the time, gets the result and sends it via MQTT to a platform.
In addition to pinging, it also performs several other network tasks such as:
SNMP Traffic/Interface queries and other SNMP queries.
In addition to telnet scanning on devices.
The first thing I did was review all the tasks that were running.
But still, I couldnât find the leak because it was too small and since the program oscillates all the time, just running it for 10 minutes wasnât enough.
After many interactions with chatgpt I managed to research a feature of ESP IDF called Memory Debug Heap
It was at this point that I created a task that is documented in the ESP IDF and it can tell me which dynamic allocation was made and not released.
Below is the code that prints these allocations and you can figure out the right trace.
#define CONFIG_HEAP_TASK_TRACKING 1
#include "esp_heap_trace.h"
#include "f_memory.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "esp_log.h"
#define TAG "Memory:"
#define HIST_SIZE 20
#define NUM_RECORDS 100
static heap_trace_record_t trace_record[NUM_RECORDS];
static uint32_t heap_history[HIST_SIZE] = {0};
static uint8_t heap_index = 0;
static bool heap_history_full = false;
static bool StopMemory = false;
SemaphoreHandle_t SemaphorePrintMem;
TaskHandle_t hPrintMem = NULL;
TaskHandle_t hHeapMonitor = NULL;
void f_startMemory(){
if(hPrintMem == NULL && hHeapMonitor == NULL){
xTaskCreate(f_PrintTasks, "f_PrintTasks", 2400, NULL, tskIDLE_PRIORITY+5, &hPrintMem);
xTaskCreate(f_HeapMonitor, "f_HeapMonitor", 4096, NULL, tskIDLE_PRIORITY+5, &hHeapMonitor);
}
}
void f_stopMemory(){
StopMemory = true;
vTaskPrioritySet(hPrintMem, 0);
vTaskPrioritySet(hHeapMonitor, 0);
if(SemaphorePrintMem){xSemaphoreTake(SemaphorePrintMem, portMAX_DELAY);}
vTaskDelete(NULL);
}
void f_startStopMemory(){
xTaskCreate(f_stopMemory, "f_stopMemory", 2500, NULL, tskIDLE_PRIORITY+10, NULL);
}
void setupMemory(){
SemaphorePrintMem = xSemaphoreCreateBinary();
}
void f_PrintTasks(void *parameters) {
StopMemory= false;
if (SemaphorePrintMem==NULL){
SemaphorePrintMem = xSemaphoreCreateBinary();
}
xSemaphoreGive(SemaphorePrintMem);
while (!StopMemory) {
xSemaphoreTake(SemaphorePrintMem, portMAX_DELAY);
uint32_t min_heap_atual = esp_get_free_heap_size();
uint32_t min_heap = esp_get_minimum_free_heap_size();
UBaseType_t numTasks;
TaskStatus_t *taskStatusArray;
numTasks = uxTaskGetNumberOfTasks();
taskStatusArray = (TaskStatus_t *)pvPortMalloc(numTasks * sizeof(TaskStatus_t));
if (taskStatusArray != NULL) {
numTasks = uxTaskGetSystemState(taskStatusArray, numTasks, NULL);
printf("--------------------------------------------------------------------------------------------\n");
printf("| %-17s | %-9s | %-8s | %-20s | %-6s |\n", "Task Name", "Status", "Priority", "Stack High Water Mark", "Core");
printf("--------------------------------------------------------------------------------------------\n");
for (int i = 0; i < numTasks; i++) {
const char *core;
if (taskStatusArray[i].xCoreID == tskNO_AFFINITY) {
core = "N/A"; // Sem afinidade de nĂșcleo
} else {
core = (taskStatusArray[i].xCoreID == 0) ? "0" : "1"; // NĂșcleos 0 e 1
}
printf("| %-17s | %-9s | %-8u | %-20lu | %-6s |\n",
taskStatusArray[i].pcTaskName,
(taskStatusArray[i].eCurrentState == eRunning ? "Running" : "Blocked"),
taskStatusArray[i].uxCurrentPriority,
taskStatusArray[i].usStackHighWaterMark,
core);
}
printf("--------------------------------------------------------------------------------------------\n");
printf("Memoria atual: %u, Menor valor: %u\n", (unsigned int)min_heap_atual, (unsigned int)min_heap);
printf("--------------------------------------------------------------------------------------------\n");
printf("Heap DRAM total: %d bytes\n", heap_caps_get_total_size(MALLOC_CAP_8BIT));
printf("Heap IRAM total: %d bytes\n", heap_caps_get_total_size(MALLOC_CAP_32BIT));
printf("--------------------------------------------------------------------------------------------\n");
f_PrintHeapHistorico(min_heap_atual);
// print_mem_chart(min_heap_atual);
// Libera a memĂłria alocada
vPortFree(taskStatusArray);
}
xSemaphoreGive(SemaphorePrintMem);
vTaskDelay(pdMS_TO_TICKS(3000));
}
hPrintMem = NULL;
vTaskDelete(NULL); // Encerra a prĂłpria tarefa
}
void f_memoria(char *tarefa){
ESP_LOGI(TAG, "Uso de memoria da tarefa(%s): %d bytes", tarefa, uxTaskGetStackHighWaterMark(NULL) * sizeof(StackType_t));
}
void f_PrintHeapHistorico(uint32_t atual) {
heap_history[heap_index] = atual;
heap_index = (heap_index + 1) % HIST_SIZE;
if (heap_index == 0) heap_history_full = true;
uint32_t min = heap_history[0];
uint32_t max = heap_history[0];
uint8_t limite = heap_history_full ? HIST_SIZE : heap_index;
for (int i = 1; i < limite; i++) {
if (heap_history[i] < min) min = heap_history[i];
if (heap_history[i] > max) max = heap_history[i];
}
printf("Memoria atual: %lu, Min Ășltimos 60s: %lu, Max Ășltimos 60s: %lu\n", atual, min, max);
}
void print_mem_chart(uint32_t mem_atual) {
// Ajusta esses valores conforme sua faixa de memĂłria
const uint32_t mem_min = 169000;
const uint32_t mem_max = 230000;
const int largura_barra = 50;
// Normaliza o valor da memĂłria para o tamanho da barra
int barra = (int)(((float)(mem_atual - mem_min) / (mem_max - mem_min)) * largura_barra);
if (barra < 0) barra = 0;
if (barra > largura_barra) barra = largura_barra;
// Print visual
printf("Mem: %6lu |", mem_atual);
for (int i = 0; i < barra; i++) printf("#");
for (int i = barra; i < largura_barra; i++) printf(" ");
printf("|\n");
}
void f_HeapMonitor(void *pvParameter) {
ESP_LOGI("HEAP_MONITOR", "Iniciando rastreamento de heap...");
heap_trace_init_standalone(trace_record, NUM_RECORDS);
heap_trace_start(HEAP_TRACE_LEAKS);
vTaskDelay(pdMS_TO_TICKS(600000)); // rastreia por 5 minutos
heap_trace_stop();
ESP_LOGI("HEAP_MONITOR", "Encerrando rastreamento. Vazamentos detectados:");
int count = heap_trace_get_count();
for (int i = 0; i < count; i++) {
const heap_trace_record_t *rec = &trace_record[i];
if (rec->address == NULL || rec->size == 0) continue;
ESP_LOGW("MemoryLeak", "Alocação #%d: %u bytes em %p", i, rec->size, rec->address);
for (int j = 0; j < CONFIG_HEAP_TRACING_STACK_DEPTH; j++) {
if (rec->alloced_by[j]) {
const void *addr = rec->alloced_by[j];
//esp_rom_printf(" âł [%d] %p (%s)\n", j, addr, esp_backtrace_symbol(addr));
esp_rom_printf(" âł [%d] %p\n", j, rec->alloced_by[j]);
}
}
vTaskDelay(pdMS_TO_TICKS(10)); // evitar travar watchdog
}
vTaskDelete(NULL);
}