Merge branch 'feat/per-task-peak-usage' into 'master'

feat(heap): Add per task peak heap usage feature

Closes IDF-1811 and IDFGH-11277

See merge request espressif/esp-idf!26462
This commit is contained in:
Guillaume Souchere
2025-03-26 15:20:04 +08:00
30 changed files with 1999 additions and 195 deletions

View File

@@ -22,7 +22,8 @@ set(includes "include"
# inside the heap component and are therefore added
# to the list of the private includes from the heap
# component perspective
set(priv_includes "tlsf/include")
set(priv_includes "private_include"
"tlsf/include")
if(NOT CONFIG_HEAP_TLSF_USE_ROM_IMPL)
list(APPEND srcs "tlsf/tlsf.c")
@@ -50,7 +51,6 @@ if(NOT BOOTLOADER_BUILD)
list(APPEND srcs "port/${target}/memory_layout.c")
endif()
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS ${includes}
PRIV_INCLUDE_DIRS ${priv_includes}

View File

@@ -96,8 +96,21 @@ menu "Heap memory debugging"
help
Enables tracking the task responsible for each heap allocation.
This function depends on heap poisoning being enabled and adds four more bytes of overhead for each block
allocated.
Note: Allocating or freeing memory or using the task tracking API will lead to a crash when the
scheduler is not working (e.g, after calling vTaskSuspendAll).
config HEAP_TRACK_DELETED_TASKS
bool "Keep information about the memory usage of deleted tasks"
depends on HEAP_TASK_TRACKING
default n
help
When enabled, this configuration allows the user to keep trace of the memory usage
of a task that has been deleted.
This allows the user to verify that no memory allocated within a task remains unfreed
before terminating the task
Note that this feature cannot keep track of a task deletion if the task is allocated statically
config HEAP_ABORT_WHEN_ALLOCATION_FAILS
bool "Abort if memory allocation fails"

View File

@@ -12,6 +12,11 @@
#include "multi_heap.h"
#include "esp_log.h"
#include "heap_private.h"
#if CONFIG_HEAP_TASK_TRACKING
#include "esp_heap_task_info.h"
#include "esp_heap_task_info_internal.h"
#include "multi_heap_internal.h"
#endif
#ifdef CONFIG_HEAP_USE_HOOKS
#define CALL_HOOK(hook, ...) { \
@@ -73,6 +78,11 @@ HEAP_IRAM_ATTR void heap_caps_free( void *ptr)
void *block_owner_ptr = MULTI_HEAP_REMOVE_BLOCK_OWNER_OFFSET(ptr);
heap_t *heap = find_containing_heap(block_owner_ptr);
assert(heap != NULL && "free() target pointer is outside heap areas");
#if CONFIG_HEAP_TASK_TRACKING
heap_caps_update_per_task_info_free(heap, ptr);
#endif
multi_heap_free(heap->heap, block_owner_ptr);
CALL_HOOK(esp_heap_trace_free_hook, ptr);
@@ -147,6 +157,13 @@ HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_aligned_alloc_base(size_t alignment
ret = aligned_or_unaligned_alloc(heap->heap, MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(size) + 4,
alignment, MULTI_HEAP_BLOCK_OWNER_SIZE()); // int overflow checked above
if (ret != NULL) {
#if CONFIG_HEAP_TASK_TRACKING
heap_caps_update_per_task_info_alloc(heap,
MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(ret),
multi_heap_get_full_block_size(heap->heap, ret),
get_all_caps(heap));
#endif
MULTI_HEAP_SET_BLOCK_OWNER(ret);
ret = MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(ret);
uint32_t *iptr = dram_alloc_to_iram_addr(ret, size + 4); // int overflow checked above
@@ -158,6 +175,13 @@ HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_aligned_alloc_base(size_t alignment
ret = aligned_or_unaligned_alloc(heap->heap, MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(size),
alignment, MULTI_HEAP_BLOCK_OWNER_SIZE());
if (ret != NULL) {
#if CONFIG_HEAP_TASK_TRACKING
heap_caps_update_per_task_info_alloc(heap,
MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(ret),
multi_heap_get_full_block_size(heap->heap, ret),
get_all_caps(heap));
#endif
MULTI_HEAP_SET_BLOCK_OWNER(ret);
ret = MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(ret);
CALL_HOOK(esp_heap_trace_alloc_hook, ret, size, caps);
@@ -242,9 +266,25 @@ HEAP_IRAM_ATTR NOINLINE_ATTR void *heap_caps_realloc_base( void *ptr, size_t siz
if (compatible_caps && !ptr_in_diram_case && alignment<=UNALIGNED_MEM_ALIGNMENT_BYTES) {
// try to reallocate this memory within the same heap
// (which will resize the block if it can)
#if CONFIG_HEAP_TASK_TRACKING
size_t old_size = multi_heap_get_full_block_size(heap->heap, ptr);
TaskHandle_t old_task = MULTI_HEAP_GET_BLOCK_OWNER(ptr);
#endif
void *r = multi_heap_realloc(heap->heap, ptr, MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(size));
if (r != NULL) {
MULTI_HEAP_SET_BLOCK_OWNER(r);
#if CONFIG_HEAP_TASK_TRACKING
heap_caps_update_per_task_info_realloc(heap,
MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(ptr),
MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(r),
old_size, old_task,
multi_heap_get_full_block_size(heap->heap, r),
get_all_caps(heap));
#endif
r = MULTI_HEAP_ADD_BLOCK_OWNER_OFFSET(r);
CALL_HOOK(esp_heap_trace_alloc_hook, r, size, caps);
return r;

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -12,6 +12,7 @@
#include "multi_heap.h"
#include "multi_heap_platform.h"
#include "esp_heap_caps_init.h"
#include "esp_heap_task_info_internal.h"
#include "heap_memory_layout.h"
#include "esp_private/startup_internal.h"
@@ -144,6 +145,10 @@ void heap_caps_init(void)
heap_idx++;
assert(heap_idx <= num_heaps);
// add the name of the newly created heap to match the region name in which it will be created
#if CONFIG_HEAP_TASK_TRACKING
heap->name = type->name;
#endif // CONFIG_HEAP_TASK_TRACKING
memcpy(heap->caps, type->caps, sizeof(heap->caps));
heap->start = region->start;
heap->end = region->start + region->size;
@@ -168,13 +173,15 @@ void heap_caps_init(void)
assert(SLIST_EMPTY(&registered_heaps));
heap_t *heaps_array = NULL;
heap_t *used_heap = NULL;
for (size_t i = 0; i < num_heaps; i++) {
if (heap_caps_match(&temp_heaps[i], MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL)) {
used_heap = temp_heaps + i;
if (heap_caps_match(used_heap, MALLOC_CAP_8BIT|MALLOC_CAP_INTERNAL)) {
/* use the first DRAM heap which can fit the data.
* the allocated block won't include the block owner bytes since this operation
* is done by the top level API heap_caps_malloc(). So we need to add it manually
* after successful allocation. Allocate extra 4 bytes for that purpose. */
heaps_array = multi_heap_malloc(temp_heaps[i].heap, MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(sizeof(heap_t) * num_heaps));
heaps_array = multi_heap_malloc(used_heap->heap, MULTI_HEAP_ADD_BLOCK_OWNER_SIZE(sizeof(heap_t) * num_heaps));
if (heaps_array != NULL) {
break;
}
@@ -199,6 +206,13 @@ void heap_caps_init(void)
* until the smaller heaps are full. */
sorted_add_to_registered_heaps(&heaps_array[i]);
}
#if CONFIG_HEAP_TASK_TRACKING
heap_caps_update_per_task_info_alloc(used_heap,
MULTI_HEAP_REMOVE_BLOCK_OWNER_OFFSET(heaps_array),
multi_heap_get_full_block_size(used_heap->heap, MULTI_HEAP_REMOVE_BLOCK_OWNER_OFFSET(heaps_array)),
get_all_caps(used_heap));
#endif
}
esp_err_t heap_caps_add_region(intptr_t start, intptr_t end)
@@ -279,6 +293,15 @@ esp_err_t heap_caps_add_region_with_caps(const uint32_t caps[], intptr_t start,
err = ESP_ERR_NO_MEM;
goto done;
}
#if CONFIG_HEAP_TASK_TRACKING
// add the name of the newly created heap to match the region name in which it will be created
for(size_t i = 0; i < soc_memory_type_count; i++) {
if (get_ored_caps(caps) == get_ored_caps(soc_memory_types[i].caps)) {
p_new->name = soc_memory_types[i].name;
break;
}
}
#endif // CONFIG_HEAP_TASK_TRACKING
memcpy(p_new->caps, caps, sizeof(p_new->caps));
p_new->start = start;
p_new->end = end;

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -25,6 +25,9 @@ extern "C" {
/* Type for describing each registered heap */
typedef struct heap_t_ {
#if CONFIG_HEAP_TASK_TRACKING
const char *name;
#endif // CONFIG_HEAP_TASK_TRACKING
uint32_t caps[SOC_MEMORY_TYPE_NO_PRIOS]; ///< Capabilities for the type of memory in this heap (as a prioritised set). Copied from soc_memory_types so it's in RAM not flash.
intptr_t start;
intptr_t end;
@@ -43,17 +46,22 @@ extern SLIST_HEAD(registered_heap_ll, heap_t_) registered_heaps;
bool heap_caps_match(const heap_t *heap, uint32_t caps);
FORCE_INLINE_ATTR uint32_t get_ored_caps(const uint32_t caps[SOC_MEMORY_TYPE_NO_PRIOS])
{
uint32_t all_caps = 0;
for (int prio = 0; prio < SOC_MEMORY_TYPE_NO_PRIOS; prio++) {
all_caps |= caps[prio];
}
return all_caps;
}
/* return all possible capabilities (across all priorities) for a given heap */
FORCE_INLINE_ATTR uint32_t get_all_caps(const heap_t *heap)
{
if (heap->heap == NULL) {
return 0;
}
uint32_t all_caps = 0;
for (int prio = 0; prio < SOC_MEMORY_TYPE_NO_PRIOS; prio++) {
all_caps |= heap->caps[prio];
}
return all_caps;
return get_ored_caps(heap->caps);
}
/* Find the heap which belongs to ptr, or return NULL if it's

View File

@@ -1,18 +1,893 @@
/*
* SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <inttypes.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <multi_heap.h>
#include "multi_heap_internal.h"
#include "heap_private.h"
#include "esp_heap_task_info.h"
#include "esp_heap_task_info_internal.h"
#include "heap_memory_layout.h"
#include "esp_log.h"
#ifdef CONFIG_HEAP_TASK_TRACKING
const static char *TAG = "heap_task_tracking";
static SemaphoreHandle_t s_task_tracking_mutex = NULL;
static StaticSemaphore_t s_task_tracking_mutex_buf;
typedef struct alloc_stats {
heap_task_block_t alloc_stat;
STAILQ_ENTRY(alloc_stats) next_alloc_stat;
} alloc_stats_t;
/**
* @brief Internal singly linked list used to gather information of the heap used
* by a given task.
*/
typedef struct heap_stats {
multi_heap_handle_t heap;
heap_stat_t heap_stat;
STAILQ_HEAD(alloc_stats_ll, alloc_stats) allocs_stats;
STAILQ_ENTRY(heap_stats) next_heap_stat;
} heap_stats_t;
/** @brief Internal singly linked list used to gather information on all created
* tasks since startup.
*/
typedef struct task_stats {
task_stat_t task_stat;
STAILQ_HEAD(heap_stats_ll, heap_stats) heaps_stats;
SLIST_ENTRY(task_stats) next_task_info;
} task_info_t;
static SLIST_HEAD(task_stats_ll, task_stats) task_stats = SLIST_HEAD_INITIALIZER(task_stats);
FORCE_INLINE_ATTR heap_t* find_biggest_heap(void)
{
heap_t *heap = NULL;
heap_t *biggest_heap = NULL;
SLIST_FOREACH(heap, &registered_heaps, next) {
if (biggest_heap == NULL) {
biggest_heap = heap;
} else if ((biggest_heap->end - biggest_heap->start) < (heap->end - heap->start)) {
biggest_heap = heap;
} else {
// nothing to do here
}
}
return biggest_heap;
}
/**
* @brief Create a new alloc stats entry object
*
* @param heap_stats The heap statistics of the heap used for the allocation
* @param task_handle The task handler of the task which performed the allocation
* @param ptr The address of the allocation
* @param size The size of the allocation
*/
static HEAP_IRAM_ATTR void create_new_alloc_stats_entry(heap_stats_t *heap_stats, alloc_stats_t *alloc_stats, TaskHandle_t task_handle, void *ptr, size_t size)
{
// init the list of allocs with a new entry in heap_stats->allocs_stats. No need
// to memset the memory since all field will be set later in the function.
if (!alloc_stats) {
// find the heap with the most available free memory to store the statistics
heap_t *heap_used_for_alloc = find_biggest_heap();
alloc_stats = multi_heap_malloc(heap_used_for_alloc->heap, sizeof(alloc_stats_t));
if (!alloc_stats) {
ESP_LOGE(TAG, "Could not allocate memory to add new task statistics");
return;
}
}
alloc_stats->alloc_stat.task = task_handle;
alloc_stats->alloc_stat.address = ptr;
alloc_stats->alloc_stat.size = size;
STAILQ_INSERT_TAIL(&heap_stats->allocs_stats, alloc_stats, next_alloc_stat);
}
/**
* @brief Create a new heap stats entry object
*
* @param task_stats The task statistics of the task that triggered the allocation
* @param used_heap Information about the heap used for the allocation
* @param caps The caps of the heap used for the allocation
* @param size The size of the allocation
*/
static HEAP_IRAM_ATTR void create_new_heap_stats_entry(task_info_t *task_stats, heap_t *used_heap, void *ptr, uint32_t caps, size_t size)
{
// find the heap with the most available free memory to store the statistics
heap_t *heap_used_for_alloc = find_biggest_heap();
// init the list of heap with a new entry in task_stats->heaps_stats. No need
// to memset the memory since all field will be set later in the function.
heap_stats_t *heap_stats = multi_heap_malloc(heap_used_for_alloc->heap, sizeof(heap_stats_t));
if (!heap_stats) {
ESP_LOGE(TAG, "Could not allocate memory to add new task statistics");
return;
}
// create the alloc stats for the new heap entry
STAILQ_INIT(&heap_stats->allocs_stats);
task_stats->task_stat.heap_count += 1;
heap_stats->heap = used_heap->heap;
heap_stats->heap_stat.name = used_heap->name;
heap_stats->heap_stat.size = used_heap->end - used_heap->start;
heap_stats->heap_stat.caps = caps;
heap_stats->heap_stat.current_usage = size;
heap_stats->heap_stat.peak_usage = size;
heap_stats->heap_stat.alloc_count = 1;
heap_stats->heap_stat.alloc_stat = NULL; // this will be used to point at the user defined array of alloc_stat
STAILQ_INSERT_TAIL(&task_stats->heaps_stats, heap_stats, next_heap_stat);
create_new_alloc_stats_entry(heap_stats, NULL, task_stats->task_stat.handle, ptr, size);
}
/**
* @brief Create a new task info entry in task_stats if the tasks allocating memory is not in task_stats already.
*
* @param heap The heap by the task to allocate memory
* @param task_handle The task handle of the task allocating memory
* @param task_stats The task entry in task_stats. If NULL, the task allocating memory is allocating for the first time
* @param ptr The address of the allocation
* @param size The size of the allocation
* @param caps The ORED caps of the heap used for the allocation
*/
static HEAP_IRAM_ATTR void create_new_task_stats_entry(heap_t *used_heap, TaskHandle_t task_handle, task_info_t *task_info, void *ptr, size_t size, uint32_t caps)
{
// If task_info passed as parameter is NULL, it means the this task is doing
// its first allocation. Add the task entry to task_info and add heap_stats
// to this new task_info entry.
// If task_info is not NULL, it means that the task already allocated memory
// but now it is allocating in a new heap for the first time. Don't add a new
// task entry to task_info but add a new heap_stats to the task_info
if (!task_info) {
// find the heap with the most available free memory to store the statistics
heap_t *heap_used_for_alloc = find_biggest_heap();
// create the task_stats entry. No need to memset since all fields are set later
task_info = multi_heap_malloc(heap_used_for_alloc->heap, sizeof(task_info_t));
if (!task_info) {
ESP_LOGE(TAG, "Could not allocate memory to add new task statistics");
return;
}
// create the heap stats for the new task entry
STAILQ_INIT(&task_info->heaps_stats);
task_info->task_stat.handle = task_handle;
task_info->task_stat.is_alive = true;
task_info->task_stat.overall_peak_usage = size;
task_info->task_stat.overall_current_usage = size;
task_info->task_stat.heap_count = 0;
task_info->task_stat.heap_stat = NULL; // this will be used to point at the user defined array of heap_stat
if (task_handle == 0x00) {
char task_name[] = "Pre-scheduler";
strcpy(task_info->task_stat.name, task_name);
} else {
strcpy(task_info->task_stat.name, pcTaskGetName(task_handle));
}
// Add the new / first task_info in the list (sorted by decreasing address).
// The decreasing order is chosen because the task_handle 0x00000000 is used for pre-scheduler
// operations and therefore need to appear last so it is not parsed when trying to find a suitable
// task to update the stats from.
if (SLIST_EMPTY(&task_stats) || task_info->task_stat.handle >= SLIST_FIRST(&task_stats)->task_stat.handle) {
// the list is empty, or the new task handler is at a higher address than the one from the first item
SLIST_INSERT_HEAD(&task_stats, task_info, next_task_info);
} else {
// the new task handle is at a lower address than the first item in the list, go through the list to
// properly insert the new item
task_info_t *cur_task_info = NULL;
task_info_t *prev_task_info = NULL;
SLIST_FOREACH(cur_task_info, &task_stats, next_task_info) {
if (cur_task_info->task_stat.handle < task_info->task_stat.handle) {
SLIST_INSERT_AFTER(prev_task_info, task_info, next_task_info);
break;
} else {
prev_task_info = cur_task_info;
}
}
// here should be a last case handling: new task info as a task handle address smaller than all existing
// items in the list. But this is case is impossible given that the pre-scheduler allocations always
// happen first and the task handle defaults to 0x00000000 for the pre-scheduler so it will always be
// last in the list.
}
}
create_new_heap_stats_entry(task_info, used_heap, ptr, caps, size);
}
#if !CONFIG_HEAP_TRACK_DELETED_TASKS
/**
* @brief Delete an entry from the list of task statistics
*
* @param task_info The task statistics to delete from the list of task statistics
*/
static HEAP_IRAM_ATTR void delete_task_info_entry(task_info_t *task_info)
{
if (task_info == NULL) {
return;
}
heap_stats_t *current_heap_stat = STAILQ_FIRST(&task_info->heaps_stats);
heap_stats_t *prev_heap_stat = NULL;
// pointer used to free the memory of the statistics
heap_t *containing_heap = NULL;
// remove all entries from task_info->heaps_stats and free the memory
while(current_heap_stat != NULL) {
prev_heap_stat = current_heap_stat;
current_heap_stat = STAILQ_NEXT(current_heap_stat, next_heap_stat);
/* remove all entries from heap_stats->allocs_stats */
alloc_stats_t *alloc_stat = NULL;
while ((alloc_stat = STAILQ_FIRST( &prev_heap_stat->allocs_stats)) != NULL) {
STAILQ_REMOVE(&prev_heap_stat->allocs_stats, alloc_stat, alloc_stats, next_alloc_stat);
containing_heap = find_containing_heap(alloc_stat);
// prev_heap_stat must be allocated somewhere
if (containing_heap != NULL) {
multi_heap_free(containing_heap->heap, alloc_stat);
}
}
if (STAILQ_EMPTY(&prev_heap_stat->allocs_stats)) {
STAILQ_REMOVE(&task_info->heaps_stats, prev_heap_stat, heap_stats, next_heap_stat);
containing_heap = find_containing_heap(prev_heap_stat);
// prev_heap_stat must be allocated somewhere
if (containing_heap != NULL) {
multi_heap_free(containing_heap->heap, prev_heap_stat);
}
}
}
if (STAILQ_EMPTY(&task_info->heaps_stats)) {
// remove task_info from task_stats (and free the memory)
SLIST_REMOVE(&task_stats, task_info, task_stats, next_task_info);
containing_heap = find_containing_heap(task_info);
if (containing_heap != NULL) {
multi_heap_free(containing_heap->heap, task_info);
}
}
}
#endif // !CONFIG_HEAP_TRACK_DELETED_TASKS
HEAP_IRAM_ATTR void heap_caps_update_per_task_info_alloc(heap_t *heap, void *ptr, size_t size, uint32_t caps)
{
if (s_task_tracking_mutex == NULL) {
s_task_tracking_mutex = xSemaphoreCreateMutexStatic(&s_task_tracking_mutex_buf);
assert(s_task_tracking_mutex);
}
TaskHandle_t task_handle = xTaskGetCurrentTaskHandle();
task_info_t *task_info = NULL;
xSemaphoreTake(s_task_tracking_mutex, portMAX_DELAY);
/* find the task in the list and update the overall stats */
SLIST_FOREACH(task_info, &task_stats, next_task_info) {
if (task_info->task_stat.handle == task_handle && task_info->task_stat.is_alive) {
task_info->task_stat.overall_current_usage += size;
if (task_info->task_stat.overall_current_usage > task_info->task_stat.overall_peak_usage) {
task_info->task_stat.overall_peak_usage = task_info->task_stat.overall_current_usage;
}
heap_stats_t *heap_stats = NULL;
/* find the heap in the list and update the overall stats */
STAILQ_FOREACH(heap_stats, &task_info->heaps_stats, next_heap_stat) {
if (heap_stats->heap == heap->heap) {
heap_stats->heap_stat.current_usage += size;
heap_stats->heap_stat.alloc_count++;
if (heap_stats->heap_stat.current_usage > heap_stats->heap_stat.peak_usage) {
heap_stats->heap_stat.peak_usage = heap_stats->heap_stat.current_usage;
}
/* add the alloc info to the list */
create_new_alloc_stats_entry(heap_stats, NULL, task_handle, ptr, size);
xSemaphoreGive(s_task_tracking_mutex);
return;
}
}
break;
}
// since the list of task info is sorted by decreasing size, if the current task info
// has a smaller task handle address than the one we are checking against, we can be sure
// the task handle will not be found in the list, and we can break the loop.
if (task_info->task_stat.handle < task_handle) {
task_info = NULL;
break;
}
}
// No task entry was found OR no heap in the task entry was found.
// Add the info to the list (either new task stats or new heap stat if task_info not NULL)
create_new_task_stats_entry(heap, task_handle, task_info, ptr, size, caps);
xSemaphoreGive(s_task_tracking_mutex);
}
HEAP_IRAM_ATTR void heap_caps_update_per_task_info_realloc(heap_t *heap, void *old_ptr, void *new_ptr,
size_t old_size, TaskHandle_t old_task,
size_t new_size, uint32_t caps)
{
TaskHandle_t task_handle = xTaskGetCurrentTaskHandle();
bool task_in_list = false;
task_info_t *task_info = NULL;
alloc_stats_t *alloc_stat = NULL;
xSemaphoreTake(s_task_tracking_mutex, portMAX_DELAY);
SLIST_FOREACH(task_info, &task_stats, next_task_info) {
if (task_info->task_stat.handle == old_task) {
heap_stats_t *heap_stats = NULL;
task_info->task_stat.overall_current_usage -= old_size;
STAILQ_FOREACH(heap_stats, &task_info->heaps_stats, next_heap_stat) {
if (heap_stats->heap == heap->heap) {
heap_stats->heap_stat.current_usage -= old_size;
heap_stats->heap_stat.alloc_count--;
/* remove the alloc from the list. The updated alloc stats are added later
* in the function */
STAILQ_FOREACH(alloc_stat, &heap_stats->allocs_stats, next_alloc_stat) {
if (alloc_stat->alloc_stat.address == old_ptr) {
STAILQ_REMOVE(&heap_stats->allocs_stats, alloc_stat, alloc_stats, next_alloc_stat);
/* keep the memory used to store alloc_stat since we will fill it with new alloc
* info later in the function */
break;
}
}
break;
}
}
}
if (task_info->task_stat.handle == task_handle && task_info->task_stat.is_alive) {
heap_stats_t *heap_stats = NULL;
task_info->task_stat.overall_current_usage += new_size;
STAILQ_FOREACH(heap_stats, &task_info->heaps_stats, next_heap_stat) {
if (heap_stats->heap == heap->heap) {
heap_stats->heap_stat.current_usage += new_size;
heap_stats->heap_stat.alloc_count++;
if (heap_stats->heap_stat.current_usage > heap_stats->heap_stat.peak_usage) {
heap_stats->heap_stat.peak_usage = heap_stats->heap_stat.current_usage;
}
create_new_alloc_stats_entry(heap_stats, alloc_stat, task_handle, new_ptr, new_size);
break;
}
}
task_in_list = true;
}
if (task_info->task_stat.overall_current_usage > task_info->task_stat.overall_peak_usage) {
task_info->task_stat.overall_peak_usage = task_info->task_stat.overall_current_usage;
}
}
if (!task_in_list) {
// No task entry was found OR no heap in the task entry was found.
// Add the info to the list (either new task stats or new heap stat if task_info not NULL)
create_new_task_stats_entry(heap, task_handle, task_info, new_ptr, new_size, caps);
}
xSemaphoreGive(s_task_tracking_mutex);
}
HEAP_IRAM_ATTR void heap_caps_update_per_task_info_free(heap_t *heap, void *ptr)
{
void *block_owner_ptr = MULTI_HEAP_REMOVE_BLOCK_OWNER_OFFSET(ptr);
TaskHandle_t task_handle = MULTI_HEAP_GET_BLOCK_OWNER(block_owner_ptr);
if (!task_handle) {
return;
}
task_info_t *task_info = NULL;
#if !CONFIG_HEAP_TRACK_DELETED_TASKS
task_info_t *task_info_to_delete = NULL;
#endif // !CONFIG_HEAP_TRACK_DELETED_TASKS
xSemaphoreTake(s_task_tracking_mutex, portMAX_DELAY);
/* find the matching task */
SLIST_FOREACH(task_info, &task_stats, next_task_info) {
/* check all tasks (alive and deleted) since the free can come from any tasks,
* not necessarily the one which allocated the memory. */
if (task_info->task_stat.handle == task_handle) {
heap_stats_t *heap_stats = NULL;
alloc_stats_t *alloc_stat = NULL;
/* find the matching heap */
STAILQ_FOREACH(heap_stats, &task_info->heaps_stats, next_heap_stat) {
if(heap_stats->heap == heap->heap) {
/* find the matching allocation and remove it from the list*/
STAILQ_FOREACH(alloc_stat, &heap_stats->allocs_stats, next_alloc_stat) {
if (alloc_stat->alloc_stat.address == ptr) {
STAILQ_REMOVE(&heap_stats->allocs_stats, alloc_stat, alloc_stats, next_alloc_stat);
/* keep the memory used to store alloc_stat since we will fill it with new alloc
* info later in the function */
break;
}
}
if (alloc_stat != NULL) {
heap_stats->heap_stat.alloc_count--;
heap_stats->heap_stat.current_usage -= alloc_stat->alloc_stat.size;
task_info->task_stat.overall_current_usage -= alloc_stat->alloc_stat.size;
}
}
}
/* free the memory used to store alloc_stat */
heap_t *containing_heap = find_containing_heap(alloc_stat);
// task_stats must be allocated somewhere
if (containing_heap != NULL) {
multi_heap_free(containing_heap->heap, alloc_stat);
}
}
// when a task is deleted, esp_caps_free is called to delete the TCB of the task from vTaskDelete.
// Try to make a TaskHandle out of ptr and compare it to the list of tasks in task_stats.
// If one task_info contains the newly made TaskHandle from ptr it means that esp_caps_free
// was indeed called from vTaskDelete. We can then update the task_stats by marking the corresponding
// task as deleted.
if (task_info->task_stat.handle == ptr) {
// we found the task info from the task that is being deleted.
task_info->task_stat.is_alive = false;
#if !CONFIG_HEAP_TRACK_DELETED_TASKS
task_info_to_delete = task_info;
#endif // !CONFIG_HEAP_TRACK_DELETED_TASKS
}
}
#if !CONFIG_HEAP_TRACK_DELETED_TASKS
// remove the entry related to the task that was just deleted.
if (task_info_to_delete != NULL) {
delete_task_info_entry(task_info_to_delete);
}
#endif // !CONFIG_HEAP_TRACK_DELETED_TASKS
xSemaphoreGive(s_task_tracking_mutex);
}
esp_err_t heap_caps_get_all_task_stat(heap_all_tasks_stat_t *tasks_stat)
{
if (tasks_stat == NULL ||
(tasks_stat->stat_arr == NULL && tasks_stat->task_count != 0) ||
(tasks_stat->heap_stat_start == NULL && tasks_stat->heap_count != 0) ||
(tasks_stat->alloc_stat_start == NULL && tasks_stat->alloc_count != 0)) {
return ESP_ERR_INVALID_ARG;
}
size_t task_index = 0;
size_t heap_index = 0;
size_t alloc_index = 0;
task_info_t *task_info = NULL;
xSemaphoreTake(s_task_tracking_mutex, portMAX_DELAY);
SLIST_FOREACH(task_info, &task_stats, next_task_info) {
// If there is no more task stat entries available in tasks_stat->stat_arr
// break the loop and return the function.
if (task_index >= tasks_stat->task_count) {
break;
}
memcpy(tasks_stat->stat_arr + task_index, &task_info->task_stat, sizeof(task_stat_t));
task_stat_t *current_task_stat = tasks_stat->stat_arr + task_index;
task_index++;
// If no more heap stat entries in the array are available, just proceed
// with filling task stats but skip filling info on heap stat and alloc stat.
if (heap_index + task_info->task_stat.heap_count > tasks_stat->heap_count) {
current_task_stat->heap_stat = NULL;
continue;
}
// set the pointer where the heap info for the given task will
// be in the user array
current_task_stat->heap_stat = tasks_stat->heap_stat_start + heap_index;
heap_index += task_info->task_stat.heap_count;
// copy the stats of the different heaps the task has used and the different allocs
// allocated in those heaps. If the number of entries remaining for alloc stats is
// inferior to the number of allocs allocated on the current heap no alloc stat will
// be copied at all.
size_t h_index = 0;
heap_stats_t *heap_info = STAILQ_FIRST(&task_info->heaps_stats);
while(h_index < task_info->task_stat.heap_count || heap_info != NULL) {
// increase alloc_index before filling the alloc info of the given heap
// to avoid running out of alloc stat entry while doing it.
if (alloc_index + heap_info->heap_stat.alloc_count > tasks_stat->alloc_count) {
heap_info->heap_stat.alloc_stat = NULL;
} else {
// set the pointer where the alloc info for the given heap will
// be in the user array
heap_info->heap_stat.alloc_stat = tasks_stat->alloc_stat_start + alloc_index;
// fill the alloc array in heap_info by running through all blocks of a given heap
// and storing info about the blocks allocated by the given task
alloc_stats_t *alloc_stats = NULL;
size_t a_index = 0;
STAILQ_FOREACH(alloc_stats, &heap_info->allocs_stats, next_alloc_stat) {
heap_info->heap_stat.alloc_stat[a_index] = alloc_stats->alloc_stat;
a_index++;
}
alloc_index += heap_info->heap_stat.alloc_count;
}
memcpy(current_task_stat->heap_stat + h_index, &heap_info->heap_stat, sizeof(heap_stat_t));
h_index++;
heap_info = STAILQ_NEXT(heap_info, next_heap_stat);
}
}
xSemaphoreGive(s_task_tracking_mutex);
tasks_stat->task_count = task_index;
tasks_stat->heap_count = heap_index;
tasks_stat->alloc_count = alloc_index;
return ESP_OK;
}
esp_err_t heap_caps_get_single_task_stat(heap_single_task_stat_t *task_stat, TaskHandle_t task_handle)
{
if (task_stat == NULL ||
(task_stat->heap_stat_start == NULL && task_stat->heap_count != 0) ||
(task_stat->alloc_stat_start == NULL && task_stat->alloc_count != 0)) {
return ESP_ERR_INVALID_ARG;
}
if (task_handle == NULL) {
task_handle = xTaskGetCurrentTaskHandle();
}
task_info_t *task_info = NULL;
xSemaphoreTake(s_task_tracking_mutex, portMAX_DELAY);
SLIST_FOREACH(task_info, &task_stats, next_task_info) {
if(task_info->task_stat.handle == task_handle) {
// copy the task_stat of the task itself
memcpy(&task_stat->stat, &task_info->task_stat, sizeof(task_stat_t));
break;
}
}
xSemaphoreGive(s_task_tracking_mutex);
if (task_info == NULL) {
return ESP_FAIL;
}
task_stat->stat.heap_stat = task_stat->heap_stat_start;
// copy the stats of the different heaps the task has used and the different blocks
// allocated in those heaps. If the number of entries remaining for block stats is
// inferior to the number of blocks allocated on the current heap no block stat will
// be copied at all.
size_t heap_index = 0;
size_t alloc_index = 0;
xSemaphoreTake(s_task_tracking_mutex, portMAX_DELAY);
heap_stats_t *heap_info = STAILQ_FIRST(&task_info->heaps_stats);
while(heap_index < task_info->task_stat.heap_count || heap_info != NULL) {
// check that there is enough heap_stat entry left to add another one to the user defined
// array of heap_stat
if (heap_index >= task_stat->heap_count) {
break;
}
// increase alloc_index before filling the block info of the given heap
// to avoid running out of block stat entry while doing it.
if (alloc_index + heap_info->heap_stat.alloc_count > task_stat->alloc_count) {
heap_info->heap_stat.alloc_stat = NULL;
} else {
// set the pointer where the block info for the given heap will
// be in the user array
heap_info->heap_stat.alloc_stat = task_stat->alloc_stat_start + alloc_index;
// fill the alloc array in heap_info by running through all blocks of a given heap
// and storing info about the blocks allocated by the given task
alloc_stats_t *alloc_stats = NULL;
size_t a_index = 0;
STAILQ_FOREACH(alloc_stats, &heap_info->allocs_stats, next_alloc_stat) {
heap_info->heap_stat.alloc_stat[a_index] = alloc_stats->alloc_stat;
a_index++;
}
alloc_index += heap_info->heap_stat.alloc_count;
}
memcpy(task_stat->stat.heap_stat + heap_index, &heap_info->heap_stat, sizeof(heap_stat_t));
heap_index++;
heap_info = STAILQ_NEXT(heap_info, next_heap_stat);
}
xSemaphoreGive(s_task_tracking_mutex);
task_stat->heap_count = heap_index;
task_stat->alloc_count = alloc_index;
return ESP_OK;
}
static void heap_caps_print_task_info(FILE *stream, task_info_t *task_info, bool is_last_task_info)
{
if (stream == NULL) {
stream = stdout;
}
const char *task_info_visual = is_last_task_info ? " " : "";
const char *task_info_visual_start = is_last_task_info ? "" : "";
fprintf(stream, "%s %s: %s, CURRENT MEMORY USAGE %d, PEAK MEMORY USAGE %d, TOTAL HEAP USED %d:\n", task_info_visual_start,
task_info->task_stat.is_alive ? "ALIVE" : "DELETED",
task_info->task_stat.name,
task_info->task_stat.overall_current_usage,
task_info->task_stat.overall_peak_usage,
task_info->task_stat.heap_count);
heap_stats_t *heap_info = NULL;
STAILQ_FOREACH(heap_info, &task_info->heaps_stats, next_heap_stat) {
char *next_heap_visual = !STAILQ_NEXT(heap_info, next_heap_stat) ? " " : "";
char *next_heap_visual_start = !STAILQ_NEXT(heap_info, next_heap_stat) ? "" : "";
fprintf(stream, "%s %s HEAP: %s, CAPS: 0x%08lx, SIZE: %d, USAGE: CURRENT %d (%d%%), PEAK %d (%d%%), ALLOC COUNT: %d\n",
task_info_visual,
next_heap_visual_start,
heap_info->heap_stat.name,
heap_info->heap_stat.caps,
heap_info->heap_stat.size,
heap_info->heap_stat.current_usage,
(heap_info->heap_stat.current_usage * 100) / heap_info->heap_stat.size,
heap_info->heap_stat.peak_usage,
(heap_info->heap_stat.peak_usage * 100) / heap_info->heap_stat.size,
heap_info->heap_stat.alloc_count);
alloc_stats_t *alloc_stats = NULL;
STAILQ_FOREACH(alloc_stats, &heap_info->allocs_stats, next_alloc_stat) {
fprintf(stream, "%s %s ├ ALLOC %p, SIZE %" PRIu32 "\n", task_info_visual,
next_heap_visual,
alloc_stats->alloc_stat.address,
alloc_stats->alloc_stat.size);
}
}
}
static void heap_caps_print_task_overview(FILE *stream, task_info_t *task_info, bool is_first_task_info, bool is_last_task_info)
{
if (stream == NULL) {
stream = stdout;
}
if (is_first_task_info) {
fprintf(stream, "┌────────────────────┬─────────┬──────────────────────┬───────────────────┬─────────────────┐\n");
fprintf(stream, "│ TASK │ STATUS │ CURRENT MEMORY USAGE │ PEAK MEMORY USAGE │ TOTAL HEAP USED │\n");
fprintf(stream, "├────────────────────┼─────────┼──────────────────────┼───────────────────┼─────────────────┤\n");
}
task_stat_t task_stat = task_info->task_stat;
fprintf(stream, "│ %18s │ %7s │ %20d │ %17d │ %15d │\n",
task_stat.name,
task_stat.is_alive ? "ALIVE " : "DELETED",
task_stat.overall_current_usage,
task_stat.overall_peak_usage,
task_stat.heap_count);
if (is_last_task_info) {
fprintf(stream, "└────────────────────┴─────────┴──────────────────────┴───────────────────┴─────────────────┘\n");
}
}
void heap_caps_print_single_task_stat(FILE *stream, TaskHandle_t task_handle)
{
if (task_handle == NULL) {
task_handle = xTaskGetCurrentTaskHandle();
}
task_info_t *task_info = NULL;
xSemaphoreTake(s_task_tracking_mutex, portMAX_DELAY);
SLIST_FOREACH(task_info, &task_stats, next_task_info) {
if (task_info->task_stat.handle == task_handle) {
heap_caps_print_task_info(stream, task_info, true);
xSemaphoreGive(s_task_tracking_mutex);
return;
}
}
xSemaphoreGive(s_task_tracking_mutex);
}
void heap_caps_print_all_task_stat(FILE *stream)
{
task_info_t *task_info = NULL;
xSemaphoreTake(s_task_tracking_mutex, portMAX_DELAY);
SLIST_FOREACH(task_info, &task_stats, next_task_info) {
const bool last_task_info = (SLIST_NEXT(task_info, next_task_info) == NULL);
heap_caps_print_task_info(stream, task_info, last_task_info);
}
xSemaphoreGive(s_task_tracking_mutex);
}
void heap_caps_print_single_task_stat_overview(FILE *stream, TaskHandle_t task_handle)
{
if (task_handle == NULL) {
task_handle = xTaskGetCurrentTaskHandle();
}
task_info_t *task_info = NULL;
xSemaphoreTake(s_task_tracking_mutex, portMAX_DELAY);
SLIST_FOREACH(task_info, &task_stats, next_task_info) {
if (task_info->task_stat.handle == task_handle) {
heap_caps_print_task_overview(stream, task_info, true, true);
xSemaphoreGive(s_task_tracking_mutex);
return;
}
}
xSemaphoreGive(s_task_tracking_mutex);
}
void heap_caps_print_all_task_stat_overview(FILE *stream)
{
task_info_t *task_info = NULL;
bool is_first_task_info = true;
xSemaphoreTake(s_task_tracking_mutex, portMAX_DELAY);
SLIST_FOREACH(task_info, &task_stats, next_task_info) {
const bool last_task_info = (SLIST_NEXT(task_info, next_task_info) == NULL);
heap_caps_print_task_overview(stream, task_info, is_first_task_info, last_task_info);
is_first_task_info = false;
}
xSemaphoreGive(s_task_tracking_mutex);
}
esp_err_t heap_caps_alloc_single_task_stat_arrays(heap_single_task_stat_t *task_stat, TaskHandle_t task_handle)
{
if (task_handle == NULL) {
task_handle = xTaskGetCurrentTaskHandle();
}
task_stat->heap_stat_start = NULL;
task_stat->alloc_stat_start = NULL;
task_stat->heap_count = 0;
task_stat->alloc_count = 0;
task_info_t *task_info = NULL;
xSemaphoreTake(s_task_tracking_mutex, portMAX_DELAY);
SLIST_FOREACH(task_info, &task_stats, next_task_info) {
if(task_info->task_stat.handle == task_handle && task_info->task_stat.is_alive) {
task_stat->heap_count = task_info->task_stat.heap_count;
heap_stats_t *heap_info = NULL;
STAILQ_FOREACH(heap_info, &task_info->heaps_stats, next_heap_stat) {
task_stat->alloc_count += heap_info->heap_stat.alloc_count;
}
break;
}
}
xSemaphoreGive(s_task_tracking_mutex);
// allocate the memory used to store the statistics of allocs, heaps
if (task_stat->heap_count != 0) {
heap_t *heap_used_for_alloc = find_biggest_heap();
task_stat->heap_stat_start = multi_heap_malloc(heap_used_for_alloc->heap, task_stat->heap_count * sizeof(heap_stat_t));
if (task_stat->heap_stat_start == NULL) {
return ESP_FAIL;
}
}
if (task_stat->alloc_count != 0) {
heap_t *heap_used_for_alloc = find_biggest_heap();
task_stat->alloc_stat_start = multi_heap_malloc(heap_used_for_alloc->heap, task_stat->alloc_count * sizeof(heap_task_block_t));
if (task_stat->alloc_stat_start == NULL) {
return ESP_FAIL;
}
}
return ESP_OK;
}
void heap_caps_free_single_task_stat_arrays(heap_single_task_stat_t *task_stat)
{
if (task_stat->heap_stat_start != NULL) {
heap_t *heap_used_for_alloc = find_containing_heap(task_stat->heap_stat_start);
assert(heap_used_for_alloc != NULL);
multi_heap_free(heap_used_for_alloc->heap, task_stat->heap_stat_start);
task_stat->heap_stat_start = NULL;
task_stat->heap_count = 0;
}
if (task_stat->alloc_stat_start != NULL) {
heap_t *heap_used_for_alloc = find_containing_heap(task_stat->alloc_stat_start);
assert(heap_used_for_alloc != NULL);
multi_heap_free(heap_used_for_alloc->heap, task_stat->alloc_stat_start);
task_stat->alloc_stat_start = NULL;
task_stat->alloc_count = 0;
}
}
esp_err_t heap_caps_alloc_all_task_stat_arrays(heap_all_tasks_stat_t *tasks_stat)
{
tasks_stat->stat_arr = NULL;
tasks_stat->heap_stat_start = NULL;
tasks_stat->alloc_stat_start = NULL;
tasks_stat->task_count = 0;
tasks_stat->heap_count = 0;
tasks_stat->alloc_count = 0;
task_info_t *task_info = NULL;
xSemaphoreTake(s_task_tracking_mutex, portMAX_DELAY);
SLIST_FOREACH(task_info, &task_stats, next_task_info) {
tasks_stat->task_count += 1;
tasks_stat->heap_count += task_info->task_stat.heap_count;
heap_stats_t *heap_info = NULL;
STAILQ_FOREACH(heap_info, &task_info->heaps_stats, next_heap_stat) {
tasks_stat->alloc_count += heap_info->heap_stat.alloc_count;
}
}
xSemaphoreGive(s_task_tracking_mutex);
// allocate the memory used to store the statistics of allocs, heaps and tasks
if (tasks_stat->task_count != 0) {
heap_t *heap_used_for_alloc = find_biggest_heap();
tasks_stat->stat_arr = multi_heap_malloc(heap_used_for_alloc->heap, tasks_stat->task_count * sizeof(task_stat_t));
if (tasks_stat->stat_arr == NULL) {
return ESP_FAIL;
}
}
if (tasks_stat->heap_count != 0) {
heap_t *heap_used_for_alloc = find_biggest_heap();
tasks_stat->heap_stat_start = multi_heap_malloc(heap_used_for_alloc->heap, tasks_stat->heap_count * sizeof(heap_stat_t));
if (tasks_stat->heap_stat_start == NULL) {
return ESP_FAIL;
}
}
if (tasks_stat->alloc_count != 0) {
heap_t *heap_used_for_alloc = find_biggest_heap();
tasks_stat->alloc_stat_start = multi_heap_malloc(heap_used_for_alloc->heap, tasks_stat->alloc_count * sizeof(heap_task_block_t));
if (tasks_stat->alloc_stat_start == NULL) {
return ESP_FAIL;
}
}
return ESP_OK;
}
void heap_caps_free_all_task_stat_arrays(heap_all_tasks_stat_t *tasks_stat)
{
if (tasks_stat->stat_arr != NULL) {
heap_t *heap_used_for_alloc = find_containing_heap(tasks_stat->stat_arr);
assert(heap_used_for_alloc != NULL);
multi_heap_free(heap_used_for_alloc->heap, tasks_stat->stat_arr);
tasks_stat->stat_arr = NULL;
tasks_stat->task_count = 0;
}
if (tasks_stat->heap_stat_start != NULL) {
heap_t *heap_used_for_alloc = find_containing_heap(tasks_stat->heap_stat_start);
assert(heap_used_for_alloc != NULL);
multi_heap_free(heap_used_for_alloc->heap, tasks_stat->heap_stat_start);
tasks_stat->heap_stat_start = NULL;
tasks_stat->heap_count = 0;
}
if (tasks_stat->alloc_stat_start != NULL) {
heap_t *heap_used_for_alloc = find_containing_heap(tasks_stat->alloc_stat_start);
assert(heap_used_for_alloc != NULL);
multi_heap_free(heap_used_for_alloc->heap, tasks_stat->alloc_stat_start);
tasks_stat->alloc_stat_start = NULL;
tasks_stat->alloc_count = 0;
}
}
/*
* Return per-task heap allocation totals and lists of blocks.
*
@@ -80,8 +955,7 @@ size_t heap_caps_get_per_task_info(heap_task_info_params_t *params)
if (i < count) {
params->totals[i].size[type] += bsize;
params->totals[i].count[type] += 1;
}
else {
} else {
if (count < params->max_totals) {
params->totals[count].task = btask;
params->totals[count].size[type] = bsize;

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -10,6 +10,7 @@
#ifdef CONFIG_HEAP_TASK_TRACKING
#include <stdint.h>
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
@@ -74,6 +75,53 @@ typedef struct {
size_t max_blocks; ///< Capacity of array of task block info structs
} heap_task_info_params_t;
/** @brief Structure providing details about memory usage of a given task on a heap. */
typedef struct {
const char *name; ///< Pointer to the name of the heap defined in soc_memory_types[]
uint32_t caps; ///< All caps supported by the heap (ORED)
size_t size; ///< The available size of the heap
size_t current_usage; ///< The current usage of a given task on the heap
size_t peak_usage; ///< The peak usage since startup on a given task on the heap
size_t alloc_count; ///< The current number of allocation by a given task on the heap
heap_task_block_t *alloc_stat; ///< Pointer to an array of allocation stats for a given task on the heap
} heap_stat_t;
/** @brief Structure providing details about a task. */
typedef struct {
char name[configMAX_TASK_NAME_LEN]; ///< Name of the task
TaskHandle_t handle; ///< Pointer to the task handle.
bool is_alive; ///< Information whether the task is alive (true) or deleted (false)
size_t overall_peak_usage; ///< Information about the memory peak usage across all heaps of a given task
size_t overall_current_usage; ///< Information about the memory current usage across all heaps of a given task
size_t heap_count; ///< Number of different heaps the task has used since its creation
heap_stat_t *heap_stat; ///< Pointer to an array containing statistics of the heaps used by the task
} task_stat_t;
/**
* @brief User interface containing the statistics of a given task
* and the associated memory usage of the task on each heap.
*/
typedef struct {
task_stat_t stat; ///< Statistics of the task
size_t heap_count; ///< size of user defined heap_stat array
heap_stat_t *heap_stat_start; ///> Pointer to the start to the user defined heap_stat array
size_t alloc_count; ///< size of user defined alloc_stat array
heap_task_block_t *alloc_stat_start; ///> Pointer to the start to the user defined alloc_stat array
} heap_single_task_stat_t;
/**
* @brief User interface containing the statistics of all tasks and the associated
* memory usage of those tasks on each heap they use.
*/
typedef struct {
size_t task_count; ///< user defined size of heap_single_task_stat_t array
task_stat_t *stat_arr; ///< Pointer to the user defined array of heap_single_task_stat_t
size_t heap_count; ///< size of user defined heap_stat array
heap_stat_t *heap_stat_start; ///> Pointer to the start to the user defined heap_stat array
size_t alloc_count; ///< size of user defined alloc_stat array
heap_task_block_t *alloc_stat_start; ///> Pointer to the start to the user defined alloc_stat array
} heap_all_tasks_stat_t;
/**
* @brief Return per-task heap allocation totals and lists of blocks.
*
@@ -89,6 +137,121 @@ typedef struct {
*/
extern size_t heap_caps_get_per_task_info(heap_task_info_params_t *params);
/**
* @brief Return per-task heap memory usage and associated allocation information on each heap
* for all tasks.
*
* For each task that has allocated memory from the heap, return information of memory usage and
* allocation information of the task on each heap the task has used.
*
* @param tasks_stat Structure to hold the memory usage statistics of all tasks
* (@see heap_all_tasks_stat_t).
* @return ESP_OK if the information were gathered successfully.
* ESP_ERR_INVALID_ARG if the user defined field in heap_all_tasks_stat_t are not set properly
*/
esp_err_t heap_caps_get_all_task_stat(heap_all_tasks_stat_t *tasks_stat);
/**
* @brief Return heap memory usage and associated allocation information on each heap for a given task.
*
* @param[in] task_handle handle of the task. If NULL, the function will get the current task
* handle and return the statistics of this task.
* @param[out] task_stat Structure to hold the memory usage statistics of the task defined by task_handle
* @return ESP_OK if the information were gathered successfully.
* ESP_ERR_INVALID_ARG if the user defined field in heap_single_task_stat_t are not set properly
*/
esp_err_t heap_caps_get_single_task_stat(heap_single_task_stat_t *task_stat, TaskHandle_t task_handle);
/**
* @brief Print heap memory usage and associated allocation information on each heap for all created tasks
* since startup (running and deleted ones when CONFIG_HEAP_TRACK_DELETED_TASKS is enabled).
*
* @note This function is an alternative to heap_caps_get_all_task_stat if the goal is just to print information
* and not manipulate them.
*
* @param steam The stream to dump to, if NULL then stdout is used
*/
void heap_caps_print_all_task_stat(FILE *stream);
/**
* @brief Print summary information of all tasks
*
* @note The information printed by this function is an array formatted log of task_stat_t content for each running
* task (and deleted ones if HEAP_TRACK_DELETED_TASKS is enabled)
*
* @param steam The stream to dump to, if NULL then stdout is used
*/
void heap_caps_print_all_task_stat_overview(FILE *stream);
/**
* @brief Print heap memory usage and associated allocation information on each heap for a given task.
*
* @note This function is an alternative to heap_caps_get_single_task_stat if the goal is just to print information
* and not manipulate them.
*
* @param steam The stream to dump to, if NULL then stdout is used
* @param task_handle The task handle of the task to get memory usage and associated allocation information from.
*/
void heap_caps_print_single_task_stat(FILE *stream, TaskHandle_t task_handle);
/**
* @brief Print summary information of a given task
*
* @note The information printed by this function is an array formatted log of task_stat_t content for the given
* task. This function will not print the task summary information if the given task is deleted and
* HEAP_TRACK_DELETED_TASKS is disabled.
*
* @param steam The stream to dump to, if NULL then stdout is used
* @param task_handle The task handle of the task to get memory usage and associated allocation information from.
*/
void heap_caps_print_single_task_stat_overview(FILE *stream, TaskHandle_t task_handle);
/**
* @brief Allocate the memory used to store the heap and alloc statistics and fill task_stat
* with the pointer to those allocations and the number of heaps and allocs statistics available
* for the given task.
*
* @note If NULL is passed as parameter for the task_handle, the information on the currently running
* task will be returned. This function should be called prior to heap_caps_get_single_task_stat() if the user
* wishes to use dynamic allocation to store statistics.
*
* @param task_handle The task from which to get the information. If NULL,
* this function will return the number of heap used by the calling task.
* @param task_stat Structure containing information filled by this function.
* @return ESP_OK if the memory necessary to gather the statistics was allocated successfully.
* ESP_FAIL if not enough memory space is available to store all statistics.
*/
esp_err_t heap_caps_alloc_single_task_stat_arrays(heap_single_task_stat_t *task_stat, TaskHandle_t task_handle);
/**
* @brief Free the memory allocated to store heap and alloc statistics by calling
* heap_caps_alloc_single_task_stat_arrays.
*
* @param task_stat Structure from which to free the allocated memory used to store statistics
*/
void heap_caps_free_single_task_stat_arrays(heap_single_task_stat_t *task_stat);
/**
* @brief Allocate the memory used to store the tasks, heaps and allocs statistics and fill tasks_stat
* with the pointer to those allocations and the number of tasks, heaps and allocs statistics available.
*
* @note This function should be called prior to heap_caps_get_all_task_stat() if the user
* wishes to use dynamic allocation to store statistics.
*
* @param tasks_stat Structure containing information filled by this function.
* @return ESP_OK if the memory necessary to gather the statistics was allocated successfully.
* ESP_FAIL if not enough memory space is available to store all statistics.
*/
esp_err_t heap_caps_alloc_all_task_stat_arrays(heap_all_tasks_stat_t *tasks_stat);
/**
* @brief Free the memory allocated to store task, heap and alloc statistics
* by calling heap_caps_alloc_all_task_stat_arrays.
*
* @param tasks_stat Structure from which to free the allocated memory used to store statistics
*/
void heap_caps_free_all_task_stat_arrays(heap_all_tasks_stat_t *tasks_stat);
#ifdef __cplusplus
}
#endif

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -9,7 +9,7 @@
#include <stdbool.h>
/* multi_heap is a heap implementation for handling multiple
heterogenous heaps in a single program.
heterogeneous heaps in a single program.
Any contiguous block of memory can be registered as a heap.
*/
@@ -230,6 +230,15 @@ typedef bool (*multi_heap_walker_cb_t)(void *block_ptr, size_t block_size, int b
*/
void multi_heap_walk(multi_heap_handle_t heap, multi_heap_walker_cb_t walker_func, void *user_data);
/*
* @brief Get the size of the block (including eventual metadata added by the heap component) located at p
*
* @param heap The heap in which the pointer p is located
* @param p The pointer to the data block to retrieve the same from
* @return size_t The size of the data block in bytes.
*/
size_t multi_heap_get_full_block_size(multi_heap_handle_t heap, void *p);
#ifdef __cplusplus
}
#endif

View File

@@ -46,8 +46,10 @@ entries:
multi_heap_poisoning:multi_heap_internal_check_block_poisoning (noflash)
multi_heap_poisoning:multi_heap_internal_poison_fill_region (noflash)
multi_heap_poisoning:multi_heap_aligned_alloc_offs (noflash)
multi_heap_poisoning:multi_heap_get_full_block_size (noflash)
else:
multi_heap:multi_heap_aligned_alloc_offs (noflash)
multi_heap:multi_heap_get_full_block_size (noflash)
if HEAP_POISONING_COMPREHENSIVE = y:
multi_heap_poisoning:verify_fill_pattern (noflash)

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -32,7 +32,12 @@ void *multi_heap_aligned_alloc_offs(multi_heap_handle_t heap, size_t size, size_
return multi_heap_aligned_alloc_impl_offs(heap, size, alignment, offset);
}
#if (!defined CONFIG_HEAP_TLSF_USE_ROM_IMPL)
size_t multi_heap_get_full_block_size(multi_heap_handle_t heap, void *p)
{
return multi_heap_get_allocated_size_impl(heap, p);
}
#if(!defined CONFIG_HEAP_TLSF_USE_ROM_IMPL)
/* if no heap poisoning, public API aliases directly to these implementations */
void *multi_heap_malloc(multi_heap_handle_t heap, size_t size)
__attribute__((alias("multi_heap_malloc_impl")));
@@ -74,7 +79,6 @@ void *multi_heap_get_block_address(multi_heap_block_handle_t block)
#define ALIGN_UP(X) ALIGN((X)+sizeof(void *)-1)
#define ALIGN_UP_BY(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
typedef struct multi_heap_info {
void *lock;
size_t free_bytes;

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -32,7 +32,7 @@
#ifdef MULTI_HEAP_POISONING
/* Alias MULTI_HEAP_POISONING_SLOW to SLOW for better readabilty */
/* Alias MULTI_HEAP_POISONING_SLOW to SLOW for better readability */
#ifdef SLOW
#error "external header has defined SLOW"
#endif
@@ -354,6 +354,13 @@ void *multi_heap_get_block_address(multi_heap_block_handle_t block)
return head + sizeof(poison_head_t);
}
size_t multi_heap_get_full_block_size(multi_heap_handle_t heap, void *p)
{
poison_head_t *head = verify_allocated_region(p, true);
assert(head != NULL);
return multi_heap_get_allocated_size_impl(heap, head);
}
multi_heap_handle_t multi_heap_register(void *start, size_t size)
{
#ifdef SLOW

View File

@@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef CONFIG_HEAP_TASK_TRACKING
#ifdef __cplusplus
extern "C" {
#endif
void heap_caps_update_per_task_info_alloc(heap_t *heap, void *ptr, size_t size, uint32_t caps);
void heap_caps_update_per_task_info_free(heap_t *heap, void *ptr);
void heap_caps_update_per_task_info_realloc(heap_t *heap, void *old_ptr, void *new_ptr, size_t old_size, TaskHandle_t old_task, size_t new_size, uint32_t caps);
#ifdef __cplusplus
}
#endif
#endif // CONFIG_HEAP_TASK_TRACKING

View File

@@ -18,7 +18,7 @@
#include <stdlib.h>
#include <sys/param.h>
#if !(CONFIG_ESP_SYSTEM_MEMPROT_FEATURE || CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT)
#if !(CONFIG_ESP_SYSTEM_MEMPROT_FEATURE || CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT) && !(CONFIG_HEAP_TASK_TRACKING)
TEST_CASE("Capabilities allocator test", "[heap]")
{
char *m1, *m2[10];
@@ -108,7 +108,7 @@ TEST_CASE("Capabilities allocator test", "[heap]")
free(m1);
printf("Done.\n");
}
#endif // !(CONFIG_ESP_SYSTEM_MEMPROT_FEATURE || CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT)
#endif // !(CONFIG_ESP_SYSTEM_MEMPROT_FEATURE || CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT) && !(CONFIG_HEAP_TASK_TRACKING)
#ifdef CONFIG_ESP32_IRAM_AS_8BIT_ACCESSIBLE_MEMORY
TEST_CASE("IRAM_8BIT capability test", "[heap]")
@@ -230,7 +230,7 @@ TEST_CASE("heap caps minimum free bytes fault cases", "[heap]")
/* Small function runs from IRAM to check that malloc/free/realloc
all work OK when cache is disabled...
*/
#if !(CONFIG_ESP_SYSTEM_MEMPROT_FEATURE || CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT) && !CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH
#if !(CONFIG_ESP_SYSTEM_MEMPROT_FEATURE || CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT) && !CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH && !CONFIG_HEAP_TASK_TRACKING
static IRAM_ATTR __attribute__((noinline)) bool iram_malloc_test(void)
{
spi_flash_guard_get()->start(); // Disables flash cache
@@ -252,7 +252,7 @@ TEST_CASE("heap_caps_xxx functions work with flash cache disabled", "[heap]")
{
TEST_ASSERT( iram_malloc_test() );
}
#endif // !(CONFIG_ESP_SYSTEM_MEMPROT_FEATURE || CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT) && !CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH
#endif // !(CONFIG_ESP_SYSTEM_MEMPROT_FEATURE || CONFIG_ESP_SYSTEM_PMP_IDRAM_SPLIT) && !CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH && !CONFIG_HEAP_TASK_TRACKING
#ifdef CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS
TEST_CASE("When enabled, allocation operation failure generates an abort", "[heap][reset=abort,SW_CPU_RESET]")
@@ -272,6 +272,7 @@ void heap_caps_alloc_failed_hook(size_t requested_size, uint32_t caps, const cha
called_user_failed_hook = true;
}
TEST_CASE("user provided alloc failed hook must be called when allocation fails", "[heap]")
{
TEST_ASSERT(heap_caps_register_failed_alloc_callback(heap_caps_alloc_failed_hook) == ESP_OK);

View File

@@ -1,10 +1,11 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "unity.h"
#include "stdio.h"
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
@@ -12,52 +13,34 @@
#include "esp_heap_task_info.h"
// This test only apply when task tracking is enabled
#if defined(CONFIG_HEAP_TASK_TRACKING)
#if defined(CONFIG_HEAP_TASK_TRACKING) && defined(CONFIG_HEAP_TRACK_DELETED_TASKS)
#define MAX_TASK_NUM 10 // Max number of per tasks info that it can store
#define MAX_BLOCK_NUM 10 // Max number of per block info that it can store
#define ALLOC_BYTES 36
static void check_heap_task_info(TaskHandle_t taskHdl)
static void check_heap_task_info(const char *task_name, const bool task_active)
{
size_t num_totals = 0;
heap_task_totals_t s_totals_arr[MAX_TASK_NUM];
heap_task_block_t s_block_arr[MAX_BLOCK_NUM];
heap_all_tasks_stat_t heap_tasks_stat;
heap_task_info_params_t heap_info = {0};
heap_info.caps[0] = MALLOC_CAP_32BIT; // Gets heap info with CAP_32BIT capabilities
heap_info.mask[0] = MALLOC_CAP_32BIT;
heap_info.tasks = NULL; // Passing NULL captures heap info for all tasks
heap_info.num_tasks = 0;
heap_info.totals = s_totals_arr; // Gets task wise allocation details
heap_info.num_totals = &num_totals;
heap_info.max_totals = MAX_TASK_NUM; // Maximum length of "s_totals_arr"
heap_info.blocks = s_block_arr; // Gets block wise allocation details. For each block, gets owner task, address and size
heap_info.max_blocks = MAX_BLOCK_NUM; // Maximum length of "s_block_arr"
heap_tasks_stat.task_count = 10;
heap_tasks_stat.heap_count = 20;
heap_tasks_stat.alloc_count = 60;
task_stat_t arr_task_stat[heap_tasks_stat.task_count];
heap_stat_t arr_heap_stat[heap_tasks_stat.heap_count];
heap_task_block_t arr_alloc_stat[heap_tasks_stat.alloc_count];
heap_tasks_stat.stat_arr = arr_task_stat;
heap_tasks_stat.heap_stat_start = arr_heap_stat;
heap_tasks_stat.alloc_stat_start = arr_alloc_stat;
heap_caps_get_per_task_info(&heap_info);
heap_caps_get_all_task_stat(&heap_tasks_stat);
bool task_found = false;
for (int i = 0 ; i < *heap_info.num_totals; i++) {
for (size_t task_index = 0; task_index < heap_tasks_stat.task_count; task_index++) {
// the prescheduler allocs and free are stored as a
// task with a handle set to 0, avoid calling pcTaskGetName
// in that case.
if (heap_info.totals[i].task != 0 && (uint32_t*)(heap_info.totals[i].task) == (uint32_t*)taskHdl) {
task_stat_t task_stat = heap_tasks_stat.stat_arr[task_index];
if (0 == strcmp(task_stat.name, task_name) && task_stat.is_alive == task_active) {
task_found = true;
// check the number of byte allocated according to the task tracking feature
// and make sure it matches the expected value. The size returned by the
// heap_caps_get_per_task_info includes the size of the block owner (4 bytes)
TEST_ASSERT(heap_info.totals[i].size[0] == ALLOC_BYTES + 4);
}
// test that if not 0, the task handle corresponds to an actual task.
// this test is to make sure no rubbish is stored as a task handle.
if (heap_info.totals[i].task != 0) {
// feeding the task name returned by pcTaskGetName() to xTaskGetHandle().
// xTaskGetHandle would return the task handler used as parameter in
// pcTaskGetName if the task handle is valid. Otherwise, it will return
// NULL or just crash if the pointer to the task name is complete nonsense.
TEST_ASSERT_EQUAL(heap_info.totals[i].task, xTaskGetHandle(pcTaskGetName(heap_info.totals[i].task)));
}
}
TEST_ASSERT_TRUE(task_found);
@@ -70,36 +53,196 @@ static void test_task(void *args)
abort();
}
// unlock main too check task tracking feature
// unlock main to check task tracking feature
xTaskNotifyGive((TaskHandle_t)args);
// wait for main to delete this task
// wait for main to give back the hand to the task to delete the pointer
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
heap_caps_free(ptr);
// unlock main to delete the task
xTaskNotifyGive((TaskHandle_t)args);
// wait for main to delete the task
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
}
static void test_task_a(void *args)
{
test_task(args);
}
static void test_task_b(void *args)
{
test_task(args);
}
/* This test will create a task, wait for the task to allocate / free memory
* so it is added to the task tracking info in the heap component and then
* call heap_caps_get_per_task_info() and make sure a task with the name test_task
* call heap_caps_get_all_task_stat() and make sure a task with the name test_task
* is in the list, and that the right ALLOC_BYTES are shown.
*
* Note: The memory allocated in the task is not freed for the sake of the test
* so it is normal that memory leak will be reported by the test environment. It
* shouldn't be more than the byte allocated by the task + associated metadata
*/
TEST_CASE("heap task tracking reports created task", "[heap]")
TEST_CASE("heap task tracking reports created / deleted task", "[heap]")
{
TaskHandle_t test_task_handle;
xTaskCreate(&test_task, "test_task", 3072, (void *)xTaskGetCurrentTaskHandle(), 5, &test_task_handle);
const char *task_name = "test_task_a";
xTaskCreate(&test_task_a, task_name, 3072, (void *)xTaskGetCurrentTaskHandle(), 5, &test_task_handle);
// wait for task to allocate memory and give the hand back to the test
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// check that the task is referenced in the list of task
// by the task tracking feature. Check the number of bytes
// the task has allocated and make sure it is matching the
// expected value.
check_heap_task_info(test_task_handle);
// by the task tracking feature. check that the task name is
// matching and the task is running.
check_heap_task_info(task_name, true);
// unlock main to check task tracking feature
xTaskNotifyGive(test_task_handle);
// wait for the task to free the memory
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// delete the task.
vTaskDelete(test_task_handle);
// check that the task is referenced in the list of task
// by the task tracking feature. check that the task name is
// matching and the task is marked as deleted.
check_heap_task_info(task_name, false);
}
/* The test case calls heap_caps_alloc_all_task_stat_arrays and heap_caps_get_all_task_stat
* after creating new tasks and allocating in new heaps to check that the number of tasks, heaps and
* allocation statistics provided by heap_caps_get_all_task_stat is updated accordingly.
*/
TEST_CASE("heap task tracking check alloc array and get all tasks info", "[heap]")
{
// call heap_caps_alloc_all_task_stat_arrays and save the number of tasks, heaps and allocs
// statistics available when the test starts
heap_all_tasks_stat_t tasks_stat;
esp_err_t ret_val = heap_caps_alloc_all_task_stat_arrays(&tasks_stat);
TEST_ASSERT_EQUAL(ret_val, ESP_OK);
ret_val = heap_caps_get_all_task_stat(&tasks_stat);
TEST_ASSERT_EQUAL(ret_val, ESP_OK);
const size_t nb_of_tasks_stat = tasks_stat.task_count;
const size_t nb_of_heaps_stat = tasks_stat.heap_count;
const size_t nb_of_allocs_stat = tasks_stat.alloc_count;
heap_caps_free_all_task_stat_arrays(&tasks_stat);
// Create a task that will allocate memory
TaskHandle_t test_task_handle;
const char *task_name = "test_task_b";
xTaskCreate(&test_task_b, task_name, 3072, (void *)xTaskGetCurrentTaskHandle(), 5, &test_task_handle);
// wait for the task to give the hand to the test and call heap_caps_alloc_all_task_stat_arrays.
// Compare the number of tasks, heaps and allocs statistics available to make sure they contain the stats
// related to the newly created task.
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
ret_val = heap_caps_alloc_all_task_stat_arrays(&tasks_stat);
TEST_ASSERT_EQUAL(ret_val, ESP_OK);
ret_val = heap_caps_get_all_task_stat(&tasks_stat);
TEST_ASSERT_EQUAL(ret_val, ESP_OK);
TEST_ASSERT(nb_of_tasks_stat < tasks_stat.task_count);
TEST_ASSERT(nb_of_heaps_stat < tasks_stat.heap_count);
TEST_ASSERT(nb_of_allocs_stat < tasks_stat.alloc_count);
// free the arrays of stat in tasks_stat and reset the counters
heap_caps_free_all_task_stat_arrays(&tasks_stat);
// unlock task to delete allocated memory
xTaskNotifyGive(test_task_handle);
// wait for the task to free the memory
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// delete the task.
vTaskDelete(test_task_handle);
}
static void task_self_check(void *args)
{
const size_t alloc_size = 100;
const uint32_t caps = MALLOC_CAP_32BIT | MALLOC_CAP_DMA;
// call heap_caps_alloc_single_task_stat_arrays on the current task. Since no alloc was made, the
// function should return ESP_OK but the heap_count and alloc_count should be 0, the pointer to the
// allocated arrays should be NULL.
heap_single_task_stat_t task_stat;
esp_err_t ret_val = heap_caps_alloc_single_task_stat_arrays(&task_stat, NULL);
TEST_ASSERT_EQUAL(ret_val, ESP_OK);
TEST_ASSERT_EQUAL(task_stat.heap_count, 0);
TEST_ASSERT_EQUAL(task_stat.alloc_count, 0);
TEST_ASSERT_NULL(task_stat.heap_stat_start);
TEST_ASSERT_NULL(task_stat.alloc_stat_start);
// allocate memory
void *ptr = heap_caps_malloc(alloc_size, caps);
// allocate arrays for the statistics of the task. This time, it should succeed as we just
// allocated memory. This information should be stored in the task info list.
ret_val = heap_caps_alloc_single_task_stat_arrays(&task_stat, NULL);
TEST_ASSERT_EQUAL(ret_val, ESP_OK);
// The number of heap info should be one and the number of alloc should be one too
TEST_ASSERT_EQUAL(1, task_stat.heap_count);
TEST_ASSERT_EQUAL(1, task_stat.alloc_count);
ret_val = heap_caps_get_single_task_stat(&task_stat, NULL);
TEST_ASSERT_EQUAL(ret_val, ESP_OK);
// the caps of the heap info should contain the caps used to allocate the memory
TEST_ASSERT((task_stat.stat.heap_stat[0].caps & caps) == caps);
// The size of the alloc found in the stat should be not null and the address
// of the alloc should match too
TEST_ASSERT(task_stat.stat.heap_stat[0].alloc_stat[0].size > 0);
TEST_ASSERT(task_stat.stat.heap_stat[0].alloc_stat[0].address == ptr);
// free the memory and get the updated statistics on the task
heap_caps_free(ptr);
heap_caps_free_single_task_stat_arrays(&task_stat);
ret_val = heap_caps_alloc_single_task_stat_arrays(&task_stat, NULL);
TEST_ASSERT_EQUAL(ret_val, ESP_OK);
// The number of heap info should be one and the number of alloc should be zero
// since the allocated memory was just freed
TEST_ASSERT_EQUAL(1, task_stat.heap_count);
TEST_ASSERT_EQUAL(0, task_stat.alloc_count);
ret_val = heap_caps_get_single_task_stat(&task_stat, NULL);
TEST_ASSERT_EQUAL(ret_val, ESP_OK);
TEST_ASSERT((task_stat.stat.heap_stat[0].caps & caps) == caps);
TEST_ASSERT(task_stat.stat.heap_stat[0].alloc_stat == NULL);
// unlock main to check task tracking feature
xTaskNotifyGive((TaskHandle_t)args);
// wait for main to give back the hand to the task to delete the pointer
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
}
/* The test case calls heap_caps_alloc_single_task_stat_arrays and heap_caps_get_single_task_stat
* after creating new task and allocating in new heaps to check that the number of heaps and
* allocation statistics provided by heap_caps_get_single_task_stat is updated accordingly.
*/
TEST_CASE("heap task tracking check alloc arrays and get info on specific task", "[heap]")
{
TaskHandle_t test_task_handle;
const char *task_name = "task_self_check";
xTaskCreate(&task_self_check, task_name, 3072, (void *)xTaskGetCurrentTaskHandle(), 5, &test_task_handle);
// wait for the task to free the memory
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// delete the task.
vTaskDelete(test_task_handle);

View File

@@ -3,3 +3,4 @@ CONFIG_HEAP_POISONING_LIGHT=n
CONFIG_HEAP_POISONING_COMPREHENSIVE=n
CONFIG_HEAP_TASK_TRACKING=y # to make sure the config doesn't induce unexpected behavior
CONFIG_HEAP_TRACK_DELETED_TASKS=y # to make sure the config doesn't induce unexpected behavior

View File

@@ -202,7 +202,7 @@ Heap Task Tracking
Heap Task Tracking can be used to get per-task info for heap memory allocation. The application has to specify the heap capabilities for which the heap allocation is to be tracked.
Example code is provided in :example:`system/heap_task_tracking`.
Example applications are provided in :example:`system/heap_task_tracking/basic` and :example:`system/heap_task_tracking/advanced`.
.. _heap-tracing:
@@ -629,7 +629,8 @@ One way to differentiate between "real" and "false positive" memory leaks is to
Application Examples
--------------------
- :example:`system/heap_task_tracking` demonstrates the use of the heap task tracking feature to track heap memory allocated on a per-task basis.
- :example:`system/heap_task_tracking/basic` demonstrates the use of the overview feature of the heap task tracking, dumping per-task summary statistics on heap memory usage.
- :example:`system/heap_task_tracking/advanced` demonstrates the use of the statistics getter functions of the heap task tracking, accessing per-task complete statistic on the heap memory usage.
API Reference - Heap Tracing
----------------------------

View File

@@ -1,39 +0,0 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- |
# Heap Task Tracking Example
## Overview
The example creates a task which allocates random amount of memory in each iteration and demonstrates use of internal API to get heap info on per task basis running in a system.
Heap task tracking feature has dependency on some of the internal heap debugging features (e.g. heap poisoning) which allows to store task control block in metadata of each heap block.
This adds small memory overhead on per heap block and hence this feature should be used for debugging purpose only.
### Configure the project
To change the `Heap Corruption Detection level`, open the project configuration menu (`idf.py menuconfig`).
Navigate to `Component config -> Heap memory debugging` menu. In `Heap corruption detection` menu select either "Light Impact" or "Comprehensive".
**Note:** Enabling “Comprehensive” detection has a substantial runtime performance impact.
### Build and Flash
Run `idf.py -p PORT flash monitor` to build and flash the project..
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
```
Task: Pre-Scheduler allocs -> CAP_8BIT: 5360 CAP_32BIT: 0
Task: esp_timer -> CAP_8BIT: 1724 CAP_32BIT: 0
Task: ipc0 -> CAP_8BIT: 8316 CAP_32BIT: 0
Task: main -> CAP_8BIT: 3480 CAP_32BIT: 0
Task: ipc1 -> CAP_8BIT: 12 CAP_32BIT: 0
Task: example_task -> CAP_8BIT: 696 CAP_32BIT: 0
```

View File

@@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(advanced)

View File

@@ -0,0 +1,141 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- |
# Heap Task Tracking Basic Example
## Overview
The example creates a task which allocates random amount of memory and frees it and another task that allocates random amount of memory but never frees it.
The main then goes into a loop calling functions retrieving statistics for the "no leak" task, the "leaking" tasks and all tasks and printing them.
For each tasks, the following information is retrieved and printed:
- the task name
- the task status (running or deleted)
- the overall peak memory usage of the task
- the overall current memory usage of the task
For each heap used by a given task, the following information is printed:
- the heap name
- the heap caps
- the heap size
- the heap current memory usage by the task
- the heap peak memory usage by the task
- the number of blocks currently allocated in the heap by the task
For each block of memory allocated in a given heap by a given task, the following information is printed:
- the allocation address
- the allocation size
Because the heap task tracking feature requires additional metadata to be allocated for each memory allocations, the overall heap usage of the application is
greater than when the feature is disabled. For this reason, it is highly recommended to use the task tracking for debugging purpose only.
### Configure the project
- Enable thee option `Enable heap task tracking` by opening the project configuration menu (`idf.py menuconfig`) and navigate to `Component config -> Heap memory debugging` menu.
- (optional) Enable the option `Keep information about the memory usage on deleted tasks` if you wish to keep track of the information of a task after it has been deleted.
### Build and Flash
Run `idf.py -p PORT flash monitor` to build and flash the project..
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
```
--------------------------------------------------------------------------------
PRINTING SINGLE TASK INFO
--------------------------------------------------------------------------------
no_leak_task: ALIVE : Peak Usage 10128, Current Usage 0
RAM: Caps: 1071118. Size 22308, Current Usage 0, Peak Usage 10128, alloc count 0
--------------------------------------------------------------------------------
PRINTING SINGLE TASK INFO
--------------------------------------------------------------------------------
leaking_task: ALIVE : Peak Usage 7232, Current Usage 6656
RAM: Caps: 1071118. Size 22308, Current Usage 6656, Peak Usage 7232, alloc count 1
0x3fceb878: Size: 6656
--------------------------------------------------------------------------------
PRINTING SINGLE TASK INFO
--------------------------------------------------------------------------------
no_leak_task: ALIVE : Peak Usage 10128, Current Usage 0
RAM: Caps: 1071118. Size 22308, Current Usage 0, Peak Usage 10128, alloc count 0
RAM: Caps: 1071118. Size 14832, Current Usage 0, Peak Usage 8960, alloc count 0
--------------------------------------------------------------------------------
PRINTING SINGLE TASK INFO
--------------------------------------------------------------------------------
leaking_task: ALIVE : Peak Usage 15040, Current Usage 9664
RAM: Caps: 1071118. Size 22308, Current Usage 6656, Peak Usage 12032, alloc count 1
0x3fceb878: Size: 6656
RAM: Caps: 1071118. Size 14832, Current Usage 3008, Peak Usage 3008, alloc count 1
0x3fc9a0e4: Size: 3008
[...]
--------------------------------------------------------------------------------
PRINTING ALL TASKS INFO
--------------------------------------------------------------------------------
leaking_task: DELETED: Peak Usage 19248, Current Usage 13616
RAM: Caps: 1071118. Size 22308, Current Usage 10608, Peak Usage 13296, alloc count 3
0x3fceb878: Size: 6656
0x3fceb634: Size: 368
0x3fcedd00: Size: 3584
RAM: Caps: 1071118. Size 14832, Current Usage 3008, Peak Usage 12224, alloc count 1
0x3fc9a0e4: Size: 3008
no_leak_task: DELETED: Peak Usage 10128, Current Usage 0
RAM: Caps: 1071118. Size 22308, Current Usage 0, Peak Usage 10128, alloc count 0
RAM: Caps: 1071118. Size 14832, Current Usage 0, Peak Usage 9728, alloc count 0
main: ALIVE : Peak Usage 7456, Current Usage 352
RAM: Caps: 1071118. Size 14832, Current Usage 264, Peak Usage 264, alloc count 3
0x3fc99cf4: Size: 88
0x3fc99e1c: Size: 88
0x3fc99e78: Size: 88
RAM: Caps: 1071118. Size 22308, Current Usage 88, Peak Usage 7192, alloc count 5
0x3fce99f8: Size: 20
0x3fce9a10: Size: 12
0x3fce9a20: Size: 16
0x3fce9a34: Size: 20
0x3fce9a4c: Size: 20
ipc1: ALIVE : Peak Usage 44, Current Usage 32
RAM: Caps: 1071118. Size 14832, Current Usage 32, Peak Usage 44, alloc count 2
0x3fc99dcc: Size: 16
0x3fc99df4: Size: 16
ipc0: ALIVE : Peak Usage 10092, Current Usage 10080
RAM: Caps: 1071118. Size 14832, Current Usage 10080, Peak Usage 10092, alloc count 10
0x3fc973b0: Size: 1312
0x3fc97950: Size: 344
0x3fc97ae4: Size: 16
0x3fc97b0c: Size: 4224
0x3fc98b90: Size: 344
0x3fc98d00: Size: 1568
0x3fc99338: Size: 344
0x3fc994a8: Size: 1568
0x3fc99ae0: Size: 344
0x3fc99c64: Size: 16
Pre-scheduler: ALIVE : Peak Usage 3364, Current Usage 3364
RAM: Caps: 1071118. Size 14832, Current Usage 3364, Peak Usage 3364, alloc count 22
0x3fc96410: Size: 164
0x3fc96538: Size: 12
0x3fc9655c: Size: 12
0x3fc96580: Size: 16
0x3fc965a8: Size: 24
0x3fc965d8: Size: 36
0x3fc96614: Size: 40
0x3fc96654: Size: 36
0x3fc96690: Size: 40
0x3fc966d0: Size: 88
0x3fc96740: Size: 88
0x3fc967b0: Size: 88
0x3fc96820: Size: 432
0x3fc969e8: Size: 88
0x3fc96a58: Size: 88
0x3fc96ac8: Size: 88
0x3fc96b38: Size: 132
0x3fc96bd4: Size: 132
0x3fc96c70: Size: 88
0x3fc96ce0: Size: 16
0x3fc96d08: Size: 1312
0x3fc97240: Size: 344
I (4504) main_task: Returned from app_main()
```

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "heap_task_tracking_advanced_main.c"
INCLUDE_DIRS "")

View File

@@ -0,0 +1,180 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* Heap Task Tracking Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_heap_task_info.h"
#include "esp_heap_caps.h"
#include "esp_random.h"
#include "esp_log.h"
static void print_single_task_info(TaskHandle_t task_hdl)
{
heap_single_task_stat_t task_stat;
/* call API to dynamically allocate the memory necessary to store the
* information collected while calling heap_caps_get_single_task_stat */
const esp_err_t ret_val = heap_caps_alloc_single_task_stat_arrays(&task_stat, task_hdl);
assert(ret_val == ESP_OK);
/* collect the information */
heap_caps_get_single_task_stat(&task_stat, task_hdl);
/* process the information retrieved */
printf("\n--------------------------------------------------------------------------------\n");
printf("PRINTING SINGLE TASK INFO\n");
printf("--------------------------------------------------------------------------------\n");
printf("%s: %s: Peak Usage %"PRIu16", Current Usage %"PRIu16"\n", task_stat.stat.name,
task_stat.stat.is_alive ? "ALIVE " : "DELETED",
task_stat.stat.overall_peak_usage,
task_stat.stat.overall_current_usage);
for (size_t heap_idx = 0; heap_idx < task_stat.heap_count; heap_idx++) {
heap_stat_t heap_stat = task_stat.heap_stat_start[heap_idx];
printf(" %s: Caps: %"PRIu32". Size %"PRIu16", Current Usage %"PRIu16", Peak Usage %"PRIu16", alloc count %"PRIu16"\n", heap_stat.name,
heap_stat.caps,
heap_stat.size,
heap_stat.current_usage,
heap_stat.peak_usage,
heap_stat.alloc_count);
for (size_t alloc_idx = 0; alloc_idx < heap_stat.alloc_count; alloc_idx++) {
heap_task_block_t alloc_stat = heap_stat.alloc_stat[alloc_idx];
printf(" %p: Size: %"PRIu32"\n", alloc_stat.address, alloc_stat.size);
}
}
/* delete the memory dynamically allocated while calling heap_caps_alloc_all_task_stat_arrays */
heap_caps_free_single_task_stat_arrays(&task_stat);
}
static void print_all_tasks_info(void)
{
heap_all_tasks_stat_t tasks_stat;
/* call API to dynamically allocate the memory necessary to store the
* information collected while calling heap_caps_get_all_task_stat */
const esp_err_t ret_val = heap_caps_alloc_all_task_stat_arrays(&tasks_stat);
assert(ret_val == ESP_OK);
/* collect the information */
heap_caps_get_all_task_stat(&tasks_stat);
/* process the information retrieved */
printf("\n--------------------------------------------------------------------------------\n");
printf("PRINTING ALL TASKS INFO\n");
printf("--------------------------------------------------------------------------------\n");
for (size_t task_idx = 0; task_idx < tasks_stat.task_count; task_idx++) {
task_stat_t task_stat = tasks_stat.stat_arr[task_idx];
printf("%s: %s: Peak Usage %"PRIu16", Current Usage %"PRIu16"\n", task_stat.name,
task_stat.is_alive ? "ALIVE " : "DELETED",
task_stat.overall_peak_usage,
task_stat.overall_current_usage);
for (size_t heap_idx = 0; heap_idx < task_stat.heap_count; heap_idx++) {
heap_stat_t heap_stat = task_stat.heap_stat[heap_idx];
printf(" %s: Caps: %"PRIu32". Size %"PRIu16", Current Usage %"PRIu16", Peak Usage %"PRIu16", alloc count %"PRIu16"\n", heap_stat.name,
heap_stat.caps,
heap_stat.size,
heap_stat.current_usage,
heap_stat.peak_usage,
heap_stat.alloc_count);
for (size_t alloc_idx = 0; alloc_idx < heap_stat.alloc_count; alloc_idx++) {
heap_task_block_t alloc_stat = heap_stat.alloc_stat[alloc_idx];
printf(" %p: Size: %"PRIu32"\n", alloc_stat.address, alloc_stat.size);
}
}
}
/* delete the memory dynamically allocated while calling heap_caps_alloc_all_task_stat_arrays */
heap_caps_free_all_task_stat_arrays(&tasks_stat);
}
static void no_leak_task(void *args)
{
size_t size_a = 0;
size_t size_b = 0;
char *task_name = pcTaskGetName(*((TaskHandle_t*)args));
while(1) {
/* Allocate random amount of memory for demonstration */
size_a = (esp_random() % 10000) + 1;
size_b = (esp_random() % (10000 - size_a)) + 1;
void *ptr_a = heap_caps_malloc(size_a, MALLOC_CAP_DEFAULT);
void *ptr_b = heap_caps_malloc(size_b, MALLOC_CAP_DEFAULT);
if (ptr_a == NULL || ptr_b == NULL) {
ESP_LOGE(task_name, "Could not allocate heap memory");
abort();
}
heap_caps_free(ptr_a);
heap_caps_free(ptr_b);
// print the task statistics (passing NULL will print info for
// the currently running task)
print_single_task_info(NULL);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
static void leaking_task(void *args)
{
size_t size_a = 0;
size_t size_b = 0;
char *task_name = pcTaskGetName(*((TaskHandle_t*)args));
while(1) {
/* Allocate random amount of memory for demonstration */
size_a = (esp_random() % 10000) + 1;
size_b = (esp_random() % (10000 - size_a)) + 1;
void *ptr_a = heap_caps_malloc(size_a, MALLOC_CAP_DEFAULT);
void *ptr_b = heap_caps_malloc(size_b, MALLOC_CAP_DEFAULT);
if (ptr_a == NULL || ptr_b == NULL) {
ESP_LOGE(task_name, "Could not allocate heap memory");
abort();
}
heap_caps_free(ptr_a);
// don't free ptr_b on purpose to create unfreed memory for the task info to print
// heap_caps_free(ptr_b);
// print the task statistics (passing NULL will print info for
// the currently running task)
print_single_task_info(NULL);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void app_main(void)
{
TaskHandle_t no_leak_task_hdl, leaking_task_hdl;
/* Create example task to demonstrate heap_task_tracking */
xTaskCreate(&no_leak_task, "no_leak_task", 3072, &no_leak_task_hdl, 0, &no_leak_task_hdl);
xTaskCreate(&leaking_task, "leaking_task", 3072, &leaking_task_hdl, 0, &leaking_task_hdl);
size_t counter = 4;
while(counter != 0) {
vTaskDelay(pdMS_TO_TICKS(1000));
counter--;
}
vTaskDelete(leaking_task_hdl);
vTaskDelete(no_leak_task_hdl);
print_all_tasks_info();
}

View File

@@ -0,0 +1,5 @@
# enable the task tracking feature
CONFIG_HEAP_TASK_TRACKING=y
# keep task tracking information after the task is deleted
CONFIG_HEAP_TRACK_DELETED_TASKS=y

View File

@@ -5,4 +5,4 @@ cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
idf_build_set_property(MINIMAL_BUILD ON)
project(heap_task_tracking)
project(basic)

View File

@@ -0,0 +1,156 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- |
# Heap Task Tracking Basic Example
## Overview
The example creates a task which allocates random amount of memory and frees it and another task that allocates random amount of memory but never frees it.
The main then goes into a loop printing the overview information of each task that allocated memory dynamically.
The information include:
- The task name
- The task status
- The current memory usage
- The peak memory usage
- The number of heaps currently used by the task
Because the heap task tracking feature requires additional metadata to be allocated for each memory allocations, the overall heap usage of the application is
greater than when the feature is disabled. For this reason, it is highly recommended to use the task tracking for debugging purpose only.
### Configure the project
- Enable thee option `Enable heap task tracking` by opening the project configuration menu (`idf.py menuconfig`) and navigate to `Component config -> Heap memory debugging` menu.
- (optional) Enable the option `Keep information about the memory usage on deleted tasks` if you wish to keep track of the information of a task after it has been deleted.
### Build and Flash
Run `idf.py -p PORT flash monitor` to build and flash the project..
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
```
Starting task: no_leak_task
Starting task: leaking_task
PRINTING OVERVIEW STATISTICS OF EACH TASK
┌────────────────────┬─────────┬──────────────────────┬───────────────────┬─────────────────┐
│ TASK │ STATUS │ CURRENT MEMORY USAGE │ PEAK MEMORY USAGE │ TOTAL HEAP USED │
├────────────────────┼─────────┼──────────────────────┼───────────────────┼─────────────────┤
│ leaking_task │ ALIVE │ 6656 │ 8064 │ 1 │
│ no_leak_task │ ALIVE │ 0 │ 7152 │ 1 │
│ main │ ALIVE │ 7412 │ 7412 │ 2 │
│ ipc1 │ ALIVE │ 32 │ 44 │ 1 │
│ ipc0 │ ALIVE │ 10080 │ 10092 │ 1 │
│ Pre-scheduler │ ALIVE │ 2236 │ 2236 │ 1 │
└────────────────────┴─────────┴──────────────────────┴───────────────────┴─────────────────┘
PRINTING OVERVIEW STATISTICS OF NO LEAK TASK
┌────────────────────┬─────────┬──────────────────────┬───────────────────┬─────────────────┐
│ TASK │ STATUS │ CURRENT MEMORY USAGE │ PEAK MEMORY USAGE │ TOTAL HEAP USED │
├────────────────────┼─────────┼──────────────────────┼───────────────────┼─────────────────┤
│ no_leak_task │ ALIVE │ 0 │ 7152 │ 1 │
└────────────────────┴─────────┴──────────────────────┴───────────────────┴─────────────────┘
PRINTING OVERVIEW STATISTICS OF LEAKING TASK
┌────────────────────┬─────────┬──────────────────────┬───────────────────┬─────────────────┐
│ TASK │ STATUS │ CURRENT MEMORY USAGE │ PEAK MEMORY USAGE │ TOTAL HEAP USED │
├────────────────────┼─────────┼──────────────────────┼───────────────────┼─────────────────┤
│ leaking_task │ ALIVE │ 6656 │ 8064 │ 1 │
└────────────────────┴─────────┴──────────────────────┴───────────────────┴─────────────────┘
[...]
Deleting task: leaking_task
PRINTING OVERVIEW STATISTICS OF EACH TASK
┌────────────────────┬─────────┬──────────────────────┬───────────────────┬─────────────────┐
│ TASK │ STATUS │ CURRENT MEMORY USAGE │ PEAK MEMORY USAGE │ TOTAL HEAP USED │
├────────────────────┼─────────┼──────────────────────┼───────────────────┼─────────────────┤
│ leaking_task │ DELETED │ 11392 │ 11616 │ 1 │
│ no_leak_task │ ALIVE │ 0 │ 9408 │ 2 │
│ main │ ALIVE │ 3860 │ 7412 │ 2 │
│ ipc1 │ ALIVE │ 32 │ 44 │ 1 │
│ ipc0 │ ALIVE │ 10080 │ 10092 │ 1 │
│ Pre-scheduler │ ALIVE │ 2236 │ 2236 │ 1 │
└────────────────────┴─────────┴──────────────────────┴───────────────────┴─────────────────┘
PRINTING OVERVIEW STATISTICS OF NO LEAK TASK
┌────────────────────┬─────────┬──────────────────────┬───────────────────┬─────────────────┐
│ TASK │ STATUS │ CURRENT MEMORY USAGE │ PEAK MEMORY USAGE │ TOTAL HEAP USED │
├────────────────────┼─────────┼──────────────────────┼───────────────────┼─────────────────┤
│ no_leak_task │ ALIVE │ 0 │ 9408 │ 2 │
└────────────────────┴─────────┴──────────────────────┴───────────────────┴─────────────────┘
PRINTING OVERVIEW STATISTICS OF LEAKING TASK
┌────────────────────┬─────────┬──────────────────────┬───────────────────┬─────────────────┐
│ TASK │ STATUS │ CURRENT MEMORY USAGE │ PEAK MEMORY USAGE │ TOTAL HEAP USED │
├────────────────────┼─────────┼──────────────────────┼───────────────────┼─────────────────┤
│ leaking_task │ DELETED │ 11392 │ 11616 │ 1 │
└────────────────────┴─────────┴──────────────────────┴───────────────────┴─────────────────┘
Deleting task: no_leak_task
PRINTING OVERVIEW STATISTICS OF EACH TASK
┌────────────────────┬─────────┬──────────────────────┬───────────────────┬─────────────────┐
│ TASK │ STATUS │ CURRENT MEMORY USAGE │ PEAK MEMORY USAGE │ TOTAL HEAP USED │
├────────────────────┼─────────┼──────────────────────┼───────────────────┼─────────────────┤
│ leaking_task │ DELETED │ 11392 │ 11616 │ 1 │
│ no_leak_task │ DELETED │ 0 │ 9408 │ 2 │
│ main │ ALIVE │ 308 │ 7412 │ 2 │
│ ipc1 │ ALIVE │ 32 │ 44 │ 1 │
│ ipc0 │ ALIVE │ 10080 │ 10092 │ 1 │
│ Pre-scheduler │ ALIVE │ 2236 │ 2236 │ 1 │
└────────────────────┴─────────┴──────────────────────┴───────────────────┴─────────────────┘
PRINTING DETAILED STATISTICS OF EACH TASK
├ DELETED: leaking_task, CURRENT MEMORY USAGE 11392, PEAK MEMORY USAGE 11616, TOTAL HEAP USED 1:
│ └ HEAP: RAM, CAPS: 0x0010580e, SIZE: 22308, USAGE: CURRENT 11392 (51%), PEAK 11616 (52%), ALLOC COUNT: 3
│ ├ ALLOC 0x3fcebbb8, SIZE 6656
│ ├ ALLOC 0x3fced5bc, SIZE 3584
│ ├ ALLOC 0x3fceb718, SIZE 1152
├ DELETED: no_leak_task, CURRENT MEMORY USAGE 0, PEAK MEMORY USAGE 9408, TOTAL HEAP USED 2:
│ ├ HEAP: RAM, CAPS: 0x0010580e, SIZE: 22308, USAGE: CURRENT 0 (0%), PEAK 7152 (32%), ALLOC COUNT: 0
│ └ HEAP: RAM, CAPS: 0x0010580e, SIZE: 344400, USAGE: CURRENT 0 (0%), PEAK 9216 (2%), ALLOC COUNT: 0
├ ALIVE: main, CURRENT MEMORY USAGE 308, PEAK MEMORY USAGE 7412, TOTAL HEAP USED 2:
│ ├ HEAP: RAM, CAPS: 0x0010580e, SIZE: 344400, USAGE: CURRENT 220 (0%), PEAK 220 (0%), ALLOC COUNT: 2
│ │ ├ ALLOC 0x3fc99024, SIZE 88
│ │ ├ ALLOC 0x3fc99124, SIZE 132
│ └ HEAP: RAM, CAPS: 0x0010580e, SIZE: 22308, USAGE: CURRENT 88 (0%), PEAK 7192 (32%), ALLOC COUNT: 5
│ ├ ALLOC 0x3fce99f8, SIZE 20
│ ├ ALLOC 0x3fce9a10, SIZE 12
│ ├ ALLOC 0x3fce9a20, SIZE 16
│ ├ ALLOC 0x3fce9a34, SIZE 20
│ ├ ALLOC 0x3fce9a4c, SIZE 20
├ ALIVE: ipc1, CURRENT MEMORY USAGE 32, PEAK MEMORY USAGE 44, TOTAL HEAP USED 1:
│ └ HEAP: RAM, CAPS: 0x0010580e, SIZE: 344400, USAGE: CURRENT 32 (0%), PEAK 44 (0%), ALLOC COUNT: 2
│ ├ ALLOC 0x3fc990fc, SIZE 16
│ ├ ALLOC 0x3fc991c0, SIZE 16
├ ALIVE: ipc0, CURRENT MEMORY USAGE 10080, PEAK MEMORY USAGE 10092, TOTAL HEAP USED 1:
│ └ HEAP: RAM, CAPS: 0x0010580e, SIZE: 344400, USAGE: CURRENT 10080 (2%), PEAK 10092 (2%), ALLOC COUNT: 10
│ ├ ALLOC 0x3fc966e0, SIZE 1312
│ ├ ALLOC 0x3fc96c80, SIZE 344
│ ├ ALLOC 0x3fc96e14, SIZE 16
│ ├ ALLOC 0x3fc96e3c, SIZE 4224
│ ├ ALLOC 0x3fc97ec0, SIZE 344
│ ├ ALLOC 0x3fc98030, SIZE 1568
│ ├ ALLOC 0x3fc98668, SIZE 344
│ ├ ALLOC 0x3fc987d8, SIZE 1568
│ ├ ALLOC 0x3fc98e10, SIZE 344
│ ├ ALLOC 0x3fc98f94, SIZE 16
└ ALIVE: Pre-scheduler, CURRENT MEMORY USAGE 2236, PEAK MEMORY USAGE 2236, TOTAL HEAP USED 1:
└ HEAP: RAM, CAPS: 0x0010580e, SIZE: 344400, USAGE: CURRENT 2236 (0%), PEAK 2236 (0%), ALLOC COUNT: 11
├ ALLOC 0x3fc95cb0, SIZE 164
├ ALLOC 0x3fc95dd8, SIZE 12
├ ALLOC 0x3fc95dfc, SIZE 12
├ ALLOC 0x3fc95e20, SIZE 16
├ ALLOC 0x3fc95e48, SIZE 24
├ ALLOC 0x3fc95e78, SIZE 88
├ ALLOC 0x3fc95ee8, SIZE 88
├ ALLOC 0x3fc95f58, SIZE 88
├ ALLOC 0x3fc95fc8, SIZE 88
├ ALLOC 0x3fc96038, SIZE 1312
├ ALLOC 0x3fc96570, SIZE 344
I (5949) main_task: Returned from app_main()
```

View File

@@ -0,0 +1,117 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* Heap Task Tracking Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_heap_task_info.h"
#include "esp_heap_caps.h"
#include "esp_random.h"
#include "esp_log.h"
static void no_leak_task(void *args)
{
size_t size_a = 0;
size_t size_b = 0;
char *task_name = pcTaskGetName(*((TaskHandle_t*)args));
printf("Starting task: %s\n", task_name);
while(1) {
/* Allocate random amount of memory for demonstration */
size_a = (esp_random() % 10000) + 1;
size_b = (esp_random() % (10000 - size_a)) + 1;
void *ptr_a = heap_caps_malloc(size_a, MALLOC_CAP_DEFAULT);
void *ptr_b = heap_caps_malloc(size_b, MALLOC_CAP_DEFAULT);
if (ptr_a == NULL || ptr_b == NULL) {
ESP_LOGE(task_name, "Could not allocate heap memory");
abort();
}
heap_caps_free(ptr_a);
heap_caps_free(ptr_b);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
static void leaking_task(void *args)
{
size_t size_a = 0;
size_t size_b = 0;
char *task_name = pcTaskGetName(*((TaskHandle_t*)args));
printf("Starting task: %s\n", task_name);
while(1) {
/* Allocate random amount of memory for demonstration */
size_a = (esp_random() % 10000) + 1;
size_b = (esp_random() % (10000 - size_a)) + 1;
void *ptr_a = heap_caps_malloc(size_a, MALLOC_CAP_DEFAULT);
void *ptr_b = heap_caps_malloc(size_b, MALLOC_CAP_DEFAULT);
if (ptr_a == NULL || ptr_b == NULL) {
ESP_LOGE(task_name, "Could not allocate heap memory");
abort();
}
heap_caps_free(ptr_a);
// heap_caps_free(ptr_b);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void app_main(void)
{
TaskHandle_t no_leak_task_hdl, leaking_task_hdl;
/* Create example task to demonstrate heap_task_tracking */
xTaskCreate(&no_leak_task, "no_leak_task", 3072, &no_leak_task_hdl, 5, &no_leak_task_hdl);
xTaskCreate(&leaking_task, "leaking_task", 3072, &leaking_task_hdl, 5, &leaking_task_hdl);
/* print task statistic periodically */
for(size_t counter = 0; counter < 4; counter++) {
/* print the overview stats of every task */
printf("\n PRINTING OVERVIEW STATISTICS OF EACH TASK\n");
heap_caps_print_all_task_stat_overview(stdout);
/* print the overview statistics of the no leak task */
printf("\n PRINTING OVERVIEW STATISTICS OF NO LEAK TASK\n");
heap_caps_print_single_task_stat_overview(stdout, no_leak_task_hdl);
/* print the overview statistics of the leaking task */
printf("\n PRINTING OVERVIEW STATISTICS OF LEAKING TASK\n");
heap_caps_print_single_task_stat_overview(stdout, leaking_task_hdl);
if (counter == 2) {
/* delete the leaking task and let the no leak task run
* for some more time */
printf("Deleting task: %s\n", pcTaskGetName(leaking_task_hdl));
vTaskDelete(leaking_task_hdl);
}
/* wait for a second before running the loop again*/
vTaskDelay(pdMS_TO_TICKS(1000));
}
/* Delete the no leak task */
printf("Deleting task: %s\n", pcTaskGetName(no_leak_task_hdl));
vTaskDelete(no_leak_task_hdl);
/* print overview information of every task */
printf("\n PRINTING OVERVIEW STATISTICS OF EACH TASK\n");
heap_caps_print_all_task_stat_overview(stdout);
/* print detailed statistics for every task */
printf("\n PRINTING DETAILED STATISTICS OF EACH TASK\n");
heap_caps_print_all_task_stat(stdout);
}

View File

@@ -0,0 +1,5 @@
# enable the task tracking feature
CONFIG_HEAP_TASK_TRACKING=y
# keep task tracking information after the task is deleted
CONFIG_HEAP_TRACK_DELETED_TASKS=y

View File

@@ -1,78 +0,0 @@
/* Heap Task Tracking Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_heap_task_info.h"
#include "esp_log.h"
#include "esp_random.h"
#define MAX_TASK_NUM 20 // Max number of per tasks info that it can store
#define MAX_BLOCK_NUM 20 // Max number of per block info that it can store
static size_t s_prepopulated_num = 0;
static heap_task_totals_t s_totals_arr[MAX_TASK_NUM];
static heap_task_block_t s_block_arr[MAX_BLOCK_NUM];
static void esp_dump_per_task_heap_info(void)
{
heap_task_info_params_t heap_info = {0};
heap_info.caps[0] = MALLOC_CAP_8BIT; // Gets heap with CAP_8BIT capabilities
heap_info.mask[0] = MALLOC_CAP_8BIT;
heap_info.caps[1] = MALLOC_CAP_32BIT; // Gets heap info with CAP_32BIT capabilities
heap_info.mask[1] = MALLOC_CAP_32BIT;
heap_info.tasks = NULL; // Passing NULL captures heap info for all tasks
heap_info.num_tasks = 0;
heap_info.totals = s_totals_arr; // Gets task wise allocation details
heap_info.num_totals = &s_prepopulated_num;
heap_info.max_totals = MAX_TASK_NUM; // Maximum length of "s_totals_arr"
heap_info.blocks = s_block_arr; // Gets block wise allocation details. For each block, gets owner task, address and size
heap_info.max_blocks = MAX_BLOCK_NUM; // Maximum length of "s_block_arr"
heap_caps_get_per_task_info(&heap_info);
for (int i = 0 ; i < *heap_info.num_totals; i++) {
printf("Task: %s -> CAP_8BIT: %d CAP_32BIT: %d\n",
heap_info.totals[i].task ? pcTaskGetName(heap_info.totals[i].task) : "Pre-Scheduler allocs" ,
heap_info.totals[i].size[0], // Heap size with CAP_8BIT capabilities
heap_info.totals[i].size[1]); // Heap size with CAP32_BIT capabilities
}
printf("\n\n");
}
static void example_task(void *args)
{
uint32_t size = 0;
const char *TAG = "example_task";
while (1) {
/*
* Allocate random amount of memory for demonstration
*/
size = (esp_random() % 1000);
void *ptr = malloc(size);
if (ptr == NULL) {
ESP_LOGE(TAG, "Could not allocate heap memory");
abort();
}
esp_dump_per_task_heap_info();
free(ptr);
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
void app_main(void)
{
/*
* Create example task to demonstrate heap_task_tracking
*/
xTaskCreate(&example_task, "example_task", 3072, NULL, 5, NULL);
}

View File

@@ -1 +0,0 @@
CONFIG_HEAP_TASK_TRACKING=y

View File

@@ -941,7 +941,6 @@ examples/system/gcov/components/sample/some_funcs.c
examples/system/gcov/main/gcov_example_func.c
examples/system/gcov/main/gcov_example_main.c
examples/system/gdbstub/main/gdbstub_main.c
examples/system/heap_task_tracking/main/heap_task_tracking_main.c
examples/system/himem/main/himem_example_main.c
examples/system/ota/advanced_https_ota/main/advanced_https_ota_example.c
examples/system/ota/native_ota_example/main/native_ota_example.c