From bbdd24d7f8c80d1abca7493d64b3baba20408d02 Mon Sep 17 00:00:00 2001 From: robert Date: Mon, 26 Oct 2026 17:10:25 +0200 Subject: [PATCH] Add FreeRTOS per-task mem usage reporting --- mongoose.c | 92 ++++++++++++++++++- mongoose.h | 11 +++ src/health.c | 86 +++++++++++++++++ src/health.h | 11 +++ src/mdash.c | 6 +- .../cubemx-freertos/Core/Inc/FreeRTOSConfig.h | 1 + .../mongoose/mongoose_config.h | 4 + 7 files changed, 201 insertions(+), 10 deletions(-) diff --git a/mongoose.c b/mongoose.c index 16380ac7..d32765bc 100644 --- a/mongoose.c +++ b/mongoose.c @@ -3641,6 +3641,92 @@ struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, p_read, +#if MG_ENABLE_FREERTOS +static size_t print_freertos_stacks(mg_pfn_t fn, void *arg, va_list *ap) { + size_t len = 0; +#if configUSE_TRACE_FACILITY == 1 + struct stack_stat { + char name[configMAX_TASK_NAME_LEN]; + size_t total, free_min; + }; + struct stack_stat *stacks = NULL; + TaskStatus_t *tasks = NULL; + size_t count; + bool ok = false; + + // Keep task handles and names valid until the snapshot has been copied + vTaskSuspendAll(); + count = (size_t) uxTaskGetNumberOfTasks(); + if (count == 0) { + ok = true; + } else if (count <= SIZE_MAX / sizeof(*tasks) && + count <= SIZE_MAX / sizeof(*stacks)) { + tasks = mg_calloc(count, sizeof(*tasks)); + stacks = mg_calloc(count, sizeof(*stacks)); + if (tasks != NULL && stacks != NULL && + (size_t) uxTaskGetSystemState(tasks, (UBaseType_t) count, NULL) == count) { + for (size_t i = 0; i < count; i++) { + mg_snprintf(stacks[i].name, sizeof(stacks[i].name), "%s", + tasks[i].pcTaskName); + stacks[i].total = (size_t) MG_HEALTH_TASK_STACK_TOTAL(tasks[i].xHandle); + stacks[i].free_min = + (size_t) tasks[i].usStackHighWaterMark * sizeof(StackType_t); + } + ok = true; + } + } + (void) xTaskResumeAll(); + mg_free(tasks); + + // Format after resuming the scheduler. No task-owned pointers are retained. + if (ok) { + len += mg_xprintf(fn, arg, "["); + for (size_t i = 0; i < count; i++) { + char used[32] = "null", total[32] = "null"; + if (stacks[i].total > 0) { + mg_snprintf(total, sizeof(total), "%zu", stacks[i].total); + if (stacks[i].total >= stacks[i].free_min) { + mg_snprintf(used, sizeof(used), "%zu", + stacks[i].total - stacks[i].free_min); + } + } + len += mg_xprintf(fn, arg, "%s{%m:%m,%m:%s,%m:%s}", + i == 0 ? "" : ",", MG_ESC("name"), + MG_ESC(stacks[i].name), MG_ESC("used"), used, + MG_ESC("total"), total); + } + len += mg_xprintf(fn, arg, "]"); + } else { + len += mg_xprintf(fn, arg, "null"); + } + mg_free(stacks); +#else + len += mg_xprintf(fn, arg, "null"); +#endif + (void) ap; + return len; +} +#endif + +size_t mg_print_mem_stats(mg_pfn_t fn, void *arg, va_list *ap) { + size_t len; +#if MG_ENABLE_FREERTOS + size_t heap_used = (size_t) MG_HEALTH_HEAP_USED; + size_t heap_total = (size_t) MG_HEALTH_HEAP_TOTAL; + len = mg_xprintf(fn, arg, "{%m:%zu,%m:%zu,%m:%M}", + MG_ESC("heap_used"), heap_used, MG_ESC("heap_total"), + heap_total, MG_ESC("stacks"), print_freertos_stacks); +#else + len = mg_xprintf(fn, arg, "{%m:%zu,%m:%zu,%m:%zu,%m:%zu}", + MG_ESC("stack_used"), (size_t) MG_HEALTH_STACK_USED, + MG_ESC("stack_total"), (size_t) MG_HEALTH_STACK_TOTAL, + MG_ESC("heap_used"), (size_t) MG_HEALTH_HEAP_USED, + MG_ESC("heap_total"), (size_t) MG_HEALTH_HEAP_TOTAL); +#endif + (void) ap; + return len; +} + // The one health record. Lives in RAM that survives a warm reset, see // MG_HEALTH_RAM and the .mg_health region in the linker script struct mg_health mg_health_record MG_HEALTH_RAM; @@ -7291,11 +7377,7 @@ static void mg_mdash_rpc_get_info(struct mg_rpc_req *r) { } static void mg_mdash_rpc_mem_stats(struct mg_rpc_req *r) { - mg_rpc_ok(r, "{%m:%zu,%m:%zu,%m:%zu,%m:%zu}", // - MG_ESC("stack_used"), (size_t) MG_HEALTH_STACK_USED, // - MG_ESC("stack_total"), (size_t) MG_HEALTH_STACK_TOTAL, // - MG_ESC("heap_used"), (size_t) MG_HEALTH_HEAP_USED, // - MG_ESC("heap_total"), (size_t) MG_HEALTH_HEAP_TOTAL); + mg_rpc_ok(r, "%M", mg_print_mem_stats); } static void mg_mdash_rpc_ota_begin(struct mg_rpc_req *r) { diff --git a/mongoose.h b/mongoose.h index 41756fb3..fc9825c9 100644 --- a/mongoose.h +++ b/mongoose.h @@ -4431,6 +4431,12 @@ void mg_ota_poll(struct mg_mgr *); #define MG_HEALTH_HEAP_TOTAL 0 #endif +// FreeRTOS: return the task's configured stack size in bytes, or 0 if unknown +// Override with an application lookup, e.g. app_task_stack_total(task) +#ifndef MG_HEALTH_TASK_STACK_TOTAL +#define MG_HEALTH_TASK_STACK_TOTAL(task) 0 +#endif + #ifndef MG_HEALTH_MAGIC #define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version #endif @@ -4504,6 +4510,11 @@ static inline const char *mg_health_reason_str(int reason) { #define MG_HEALTH_INIT() mg_health_init() extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *); +// JSON memory statistics, suitable for %M. Non-FreeRTOS builds retain scalar +// stack_used/stack_total. FreeRTOS builds report a stacks array with name, used, +// and total. Used represents peak usage, requires +// configUSE_TRACE_FACILITY=1, stacks is null if unavailable or allocation fails. +extern size_t mg_print_mem_stats(mg_pfn_t, void *, va_list *); extern void mg_health_init(void); // Cloud connector API to https://mdash.net - device management cloud diff --git a/src/health.c b/src/health.c index d1b7fbd2..d539b0c7 100644 --- a/src/health.c +++ b/src/health.c @@ -1,6 +1,92 @@ #include "config.h" #include "health.h" +#if MG_ENABLE_FREERTOS +static size_t print_freertos_stacks(mg_pfn_t fn, void *arg, va_list *ap) { + size_t len = 0; +#if configUSE_TRACE_FACILITY == 1 + struct stack_stat { + char name[configMAX_TASK_NAME_LEN]; + size_t total, free_min; + }; + struct stack_stat *stacks = NULL; + TaskStatus_t *tasks = NULL; + size_t count; + bool ok = false; + + // Keep task handles and names valid until the snapshot has been copied + vTaskSuspendAll(); + count = (size_t) uxTaskGetNumberOfTasks(); + if (count == 0) { + ok = true; + } else if (count <= SIZE_MAX / sizeof(*tasks) && + count <= SIZE_MAX / sizeof(*stacks)) { + tasks = mg_calloc(count, sizeof(*tasks)); + stacks = mg_calloc(count, sizeof(*stacks)); + if (tasks != NULL && stacks != NULL && + (size_t) uxTaskGetSystemState(tasks, (UBaseType_t) count, NULL) == count) { + for (size_t i = 0; i < count; i++) { + mg_snprintf(stacks[i].name, sizeof(stacks[i].name), "%s", + tasks[i].pcTaskName); + stacks[i].total = (size_t) MG_HEALTH_TASK_STACK_TOTAL(tasks[i].xHandle); + stacks[i].free_min = + (size_t) tasks[i].usStackHighWaterMark * sizeof(StackType_t); + } + ok = true; + } + } + (void) xTaskResumeAll(); + mg_free(tasks); + + // Format after resuming the scheduler. No task-owned pointers are retained. + if (ok) { + len += mg_xprintf(fn, arg, "["); + for (size_t i = 0; i < count; i++) { + char used[32] = "null", total[32] = "null"; + if (stacks[i].total > 0) { + mg_snprintf(total, sizeof(total), "%zu", stacks[i].total); + if (stacks[i].total >= stacks[i].free_min) { + mg_snprintf(used, sizeof(used), "%zu", + stacks[i].total - stacks[i].free_min); + } + } + len += mg_xprintf(fn, arg, "%s{%m:%m,%m:%s,%m:%s}", + i == 0 ? "" : ",", MG_ESC("name"), + MG_ESC(stacks[i].name), MG_ESC("used"), used, + MG_ESC("total"), total); + } + len += mg_xprintf(fn, arg, "]"); + } else { + len += mg_xprintf(fn, arg, "null"); + } + mg_free(stacks); +#else + len += mg_xprintf(fn, arg, "null"); +#endif + (void) ap; + return len; +} +#endif + +size_t mg_print_mem_stats(mg_pfn_t fn, void *arg, va_list *ap) { + size_t len; +#if MG_ENABLE_FREERTOS + size_t heap_used = (size_t) MG_HEALTH_HEAP_USED; + size_t heap_total = (size_t) MG_HEALTH_HEAP_TOTAL; + len = mg_xprintf(fn, arg, "{%m:%zu,%m:%zu,%m:%M}", + MG_ESC("heap_used"), heap_used, MG_ESC("heap_total"), + heap_total, MG_ESC("stacks"), print_freertos_stacks); +#else + len = mg_xprintf(fn, arg, "{%m:%zu,%m:%zu,%m:%zu,%m:%zu}", + MG_ESC("stack_used"), (size_t) MG_HEALTH_STACK_USED, + MG_ESC("stack_total"), (size_t) MG_HEALTH_STACK_TOTAL, + MG_ESC("heap_used"), (size_t) MG_HEALTH_HEAP_USED, + MG_ESC("heap_total"), (size_t) MG_HEALTH_HEAP_TOTAL); +#endif + (void) ap; + return len; +} + // The one health record. Lives in RAM that survives a warm reset, see // MG_HEALTH_RAM and the .mg_health region in the linker script struct mg_health mg_health_record MG_HEALTH_RAM; diff --git a/src/health.h b/src/health.h index 541d7b34..cb51369b 100644 --- a/src/health.h +++ b/src/health.h @@ -22,6 +22,12 @@ #define MG_HEALTH_HEAP_TOTAL 0 #endif +// FreeRTOS: return the task's configured stack size in bytes, or 0 if unknown +// Override with an application lookup, e.g. app_task_stack_total(task) +#ifndef MG_HEALTH_TASK_STACK_TOTAL +#define MG_HEALTH_TASK_STACK_TOTAL(task) 0 +#endif + #ifndef MG_HEALTH_MAGIC #define MG_HEALTH_MAGIC {'M', 'G', 'H', '3'} // '3' is the layout version #endif @@ -95,4 +101,9 @@ static inline const char *mg_health_reason_str(int reason) { #define MG_HEALTH_INIT() mg_health_init() extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *); +// JSON memory statistics, suitable for %M. Non-FreeRTOS builds retain scalar +// stack_used/stack_total. FreeRTOS builds report a stacks array with name, used, +// and total. Used represents peak usage, requires +// configUSE_TRACE_FACILITY=1, stacks is null if unavailable or allocation fails. +extern size_t mg_print_mem_stats(mg_pfn_t, void *, va_list *); extern void mg_health_init(void); diff --git a/src/mdash.c b/src/mdash.c index f206d523..bb826bad 100644 --- a/src/mdash.c +++ b/src/mdash.c @@ -106,11 +106,7 @@ static void mg_mdash_rpc_get_info(struct mg_rpc_req *r) { } static void mg_mdash_rpc_mem_stats(struct mg_rpc_req *r) { - mg_rpc_ok(r, "{%m:%zu,%m:%zu,%m:%zu,%m:%zu}", // - MG_ESC("stack_used"), (size_t) MG_HEALTH_STACK_USED, // - MG_ESC("stack_total"), (size_t) MG_HEALTH_STACK_TOTAL, // - MG_ESC("heap_used"), (size_t) MG_HEALTH_HEAP_USED, // - MG_ESC("heap_total"), (size_t) MG_HEALTH_HEAP_TOTAL); + mg_rpc_ok(r, "%M", mg_print_mem_stats); } static void mg_mdash_rpc_ota_begin(struct mg_rpc_req *r) { diff --git a/tutorials/stm32/nucleo-h723zg/cubemx-freertos/Core/Inc/FreeRTOSConfig.h b/tutorials/stm32/nucleo-h723zg/cubemx-freertos/Core/Inc/FreeRTOSConfig.h index 6d592ec1..e9f3b55e 100644 --- a/tutorials/stm32/nucleo-h723zg/cubemx-freertos/Core/Inc/FreeRTOSConfig.h +++ b/tutorials/stm32/nucleo-h723zg/cubemx-freertos/Core/Inc/FreeRTOSConfig.h @@ -165,6 +165,7 @@ standard names. */ #define USE_CUSTOM_SYSTICK_HANDLER_IMPLEMENTATION 0 /* USER CODE BEGIN Defines */ +#define INCLUDE_xTaskGetIdleTaskHandle 1 /* Section where parameter definitions can be added (for instance, to override default ones in FreeRTOS.h) */ /* USER CODE END Defines */ diff --git a/tutorials/stm32/nucleo-h723zg/cubemx-freertos/mongoose/mongoose_config.h b/tutorials/stm32/nucleo-h723zg/cubemx-freertos/mongoose/mongoose_config.h index 927b69ee..2bb42b2b 100644 --- a/tutorials/stm32/nucleo-h723zg/cubemx-freertos/mongoose/mongoose_config.h +++ b/tutorials/stm32/nucleo-h723zg/cubemx-freertos/mongoose/mongoose_config.h @@ -13,6 +13,10 @@ #define MG_HEALTH_HEAP_TOTAL ((size_t) configTOTAL_HEAP_SIZE) #define MG_HEALTH_HEAP_USED (MG_HEALTH_HEAP_TOTAL - xPortGetFreeHeapSize()) +// Per-task totals in bytes +size_t app_task_stack_total(TaskHandle_t task); +#define MG_HEALTH_TASK_STACK_TOTAL(task) app_task_stack_total(task) + // Ethernet driver tunables: // #define MG_DRIVER_MDC_CR 4 // RMII MDC clock divider, from 0 to 5 // #define MG_TCPIP_PHY_ADDR 0 // PHY address