diff --git a/components/heap/CMakeLists.txt b/components/heap/CMakeLists.txt index 3fb425f23b..8fa40265ed 100644 --- a/components/heap/CMakeLists.txt +++ b/components/heap/CMakeLists.txt @@ -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} diff --git a/components/heap/Kconfig b/components/heap/Kconfig index d40b64fcee..e86261910f 100644 --- a/components/heap/Kconfig +++ b/components/heap/Kconfig @@ -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" diff --git a/components/heap/heap_caps_base.c b/components/heap/heap_caps_base.c index b7da26f179..e1606c5c0a 100644 --- a/components/heap/heap_caps_base.c +++ b/components/heap/heap_caps_base.c @@ -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,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, ...) { \ @@ -72,6 +77,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); @@ -145,6 +155,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 @@ -156,6 +173,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); @@ -240,9 +264,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; diff --git a/components/heap/heap_caps_init.c b/components/heap/heap_caps_init.c index 67c6d911bb..9408f7490b 100644 --- a/components/heap/heap_caps_init.c +++ b/components/heap/heap_caps_init.c @@ -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; diff --git a/components/heap/heap_private.h b/components/heap/heap_private.h index 1f83c9daea..7a959bde6a 100644 --- a/components/heap/heap_private.h +++ b/components/heap/heap_private.h @@ -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 diff --git a/components/heap/heap_task_info.c b/components/heap/heap_task_info.c index 83368e5db4..8349ad441d 100644 --- a/components/heap/heap_task_info.c +++ b/components/heap/heap_task_info.c @@ -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 */ @@ -7,12 +7,878 @@ #include #include #include +#include #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(task_info_t *task_info, bool is_last_task_info) +{ + const char *task_info_visual = is_last_task_info ? " " : "│"; + const char *task_info_visual_start = is_last_task_info ? "└" : "├"; + esp_rom_printf("%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) ? "└" : "├"; + esp_rom_printf("%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) { + esp_rom_printf("%s %s ├ ALLOC %p, SIZE %d\n", task_info_visual, + next_heap_visual, + alloc_stats->alloc_stat.address, + alloc_stats->alloc_stat.size); + } + } +} + +static void heap_caps_print_task_overview(task_info_t *task_info, bool is_first_task_info, bool is_last_task_info) +{ + if (is_first_task_info) { + esp_rom_printf("┌────────────────────┬─────────┬──────────────────────┬───────────────────┬─────────────────┐\n"); + esp_rom_printf("│ TASK │ STATUS │ CURRENT MEMORY USAGE │ PEAK MEMORY USAGE │ TOTAL HEAP USED │\n"); + esp_rom_printf("├────────────────────┼─────────┼──────────────────────┼───────────────────┼─────────────────┤\n"); + } + + task_stat_t task_stat = task_info->task_stat; + esp_rom_printf("│ %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) { + esp_rom_printf("└────────────────────┴─────────┴──────────────────────┴───────────────────┴─────────────────┘\n"); + } +} + +void heap_caps_print_single_task_stat(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(void) +{ + 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(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(void) +{ + 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 +946,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; diff --git a/components/heap/include/esp_heap_task_info.h b/components/heap/include/esp_heap_task_info.h index 6ee4131833..1aee89b55d 100644 --- a/components/heap/include/esp_heap_task_info.h +++ b/components/heap/include/esp_heap_task_info.h @@ -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 */ @@ -74,6 +74,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 +136,115 @@ 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. + */ +void heap_caps_print_all_task_stat(void); + +/** + * @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) + */ +void heap_caps_print_all_task_stat_overview(void); + +/** + * @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 task_handle The task handle of the task to get memory usage and associated allocation information from. + */ +void heap_caps_print_single_task_stat(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 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(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 diff --git a/components/heap/include/multi_heap.h b/components/heap/include/multi_heap.h index 20e1ba977e..7aa348f72b 100644 --- a/components/heap/include/multi_heap.h +++ b/components/heap/include/multi_heap.h @@ -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 /* 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 diff --git a/components/heap/linker.lf b/components/heap/linker.lf index 7a65d269e7..105d116b7f 100644 --- a/components/heap/linker.lf +++ b/components/heap/linker.lf @@ -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) diff --git a/components/heap/multi_heap.c b/components/heap/multi_heap.c index d47a6c0a3a..a342252720 100644 --- a/components/heap/multi_heap.c +++ b/components/heap/multi_heap.c @@ -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; diff --git a/components/heap/multi_heap_poisoning.c b/components/heap/multi_heap_poisoning.c index faa8567986..469f0684ae 100644 --- a/components/heap/multi_heap_poisoning.c +++ b/components/heap/multi_heap_poisoning.c @@ -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 diff --git a/components/heap/private_include/esp_heap_task_info_internal.h b/components/heap/private_include/esp_heap_task_info_internal.h new file mode 100644 index 0000000000..ba112dc7d0 --- /dev/null +++ b/components/heap/private_include/esp_heap_task_info_internal.h @@ -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 diff --git a/components/heap/test_apps/heap_tests/main/test_malloc_caps.c b/components/heap/test_apps/heap_tests/main/test_malloc_caps.c index 7e57a69de8..d47f5a328a 100644 --- a/components/heap/test_apps/heap_tests/main/test_malloc_caps.c +++ b/components/heap/test_apps/heap_tests/main/test_malloc_caps.c @@ -18,7 +18,7 @@ #include #include -#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); diff --git a/components/heap/test_apps/heap_tests/main/test_task_tracking.c b/components/heap/test_apps/heap_tests/main/test_task_tracking.c index 96a370528d..a0ff197476 100644 --- a/components/heap/test_apps/heap_tests/main/test_task_tracking.c +++ b/components/heap/test_apps/heap_tests/main/test_task_tracking.c @@ -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 #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); diff --git a/components/heap/test_apps/heap_tests/sdkconfig.ci.no_poisoning b/components/heap/test_apps/heap_tests/sdkconfig.ci.no_poisoning index 58c6da62c4..9ea37891c5 100644 --- a/components/heap/test_apps/heap_tests/sdkconfig.ci.no_poisoning +++ b/components/heap/test_apps/heap_tests/sdkconfig.ci.no_poisoning @@ -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 diff --git a/tools/ci/check_copyright_ignore.txt b/tools/ci/check_copyright_ignore.txt index d509047b3c..52a63891c4 100644 --- a/tools/ci/check_copyright_ignore.txt +++ b/tools/ci/check_copyright_ignore.txt @@ -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