forked from espressif/esp-idf
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:
@@ -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}
|
||||
|
@@ -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"
|
||||
|
@@ -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;
|
||||
|
@@ -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(®istered_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;
|
||||
|
@@ -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
|
||||
|
@@ -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, ®istered_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;
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
----------------------------
|
||||
|
@@ -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
|
||||
```
|
@@ -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)
|
141
examples/system/heap_task_tracking/advanced/README.md
Normal file
141
examples/system/heap_task_tracking/advanced/README.md
Normal 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()
|
||||
```
|
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "heap_task_tracking_advanced_main.c"
|
||||
INCLUDE_DIRS "")
|
@@ -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();
|
||||
}
|
@@ -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
|
@@ -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)
|
156
examples/system/heap_task_tracking/basic/README.md
Normal file
156
examples/system/heap_task_tracking/basic/README.md
Normal 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()
|
||||
```
|
@@ -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);
|
||||
}
|
@@ -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
|
@@ -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);
|
||||
}
|
@@ -1 +0,0 @@
|
||||
CONFIG_HEAP_TASK_TRACKING=y
|
@@ -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
|
||||
|
Reference in New Issue
Block a user