Add FreeRTOS per-task mem usage reporting

This commit is contained in:
robert committed 2026-10-26 17:10:25 +02:00
1 parent 76a9853b12
commit bbdd24d7f8
7 files changed
+201 -10

No files matched your search

+87 -5
View File
@@ -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) {
+11
View File
@@ -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
+86
View File
@@ -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;
+11
View File
@@ -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);
+1 -5
View File
@@ -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) {
@@ -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 */
@@ -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