mirror of
https://github.com/espressif/esp-idf.git
synced 2025-09-25 17:52:36 +00:00
feat(heap): Add feature to get peak heap usage
This feature keeps track of the per task peak memory usage. - Update the heap_task_tracking example to make use of the new feature Cleanup the implementation: - multi_heap_get_free_size() is never used, remove it. - Minor update in heap_caps_update_per_task_info_xx() funcitons. - Update settting on block owner in heap_caps.c to work with the get peak usage feature. - Update heap_caps_update_per_task_info_free() to detect when it is called to delete the memory allocated for a task TCB. Mark the corresponding task in the statistic list as deleted. - Add a Kconfig option dependant on HEAP_TASK_TRACKING being enabled that force the deletion of the statistics related to deleted task when set to true. - In task tracking feature, add a current and peak memory usage to the heap_stat_t structure to keep track of the current and peak memory usage of the given task across all heaps. - Fix missing block owner when allocating memory for heaps_array in heap_caps_init. - Keep the original implementation of the task tracking for backward compatibility reasons.
This commit is contained in:
@@ -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
|
||||
|
Reference in New Issue
Block a user