mirror of
https://github.com/cesanta/mongoose.git
synced 2026-11-10 16:31:44 +00:00
17 files changed
+347
-260
No files matched your search
+126
-101
@@ -3859,87 +3859,117 @@ struct mg_fs mg_fs_posix = {p_stat, p_list, p_open, p_close, p_read,
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#include <limits.h>
|
|
||||||
|
|
||||||
#if MG_ENABLE_HEALTH
|
#if MG_ENABLE_HEALTH
|
||||||
#if !MG_ENABLE_CUSTOM_USAGE
|
#if !MG_ENABLE_CUSTOM_USAGE
|
||||||
|
#if MG_ARCH == MG_ARCH_ESP32
|
||||||
|
#include <esp_heap_caps.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
#elif MG_ENABLE_FREERTOS && defined(__GNUC__) && defined(__ELF__)
|
||||||
|
// Some FreeRTOS allocators provide current free bytes but no low watermark.
|
||||||
|
extern size_t xPortGetMinimumEverFreeHeapSize(void) __attribute__((weak));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if MG_ENABLE_FREERTOS && MG_ARCH == MG_ARCH_CUBE && \
|
||||||
|
configUSE_TRACE_FACILITY == 1 && configRECORD_STACK_HIGH_ADDRESS != 1
|
||||||
|
#warning \
|
||||||
|
"Set configRECORD_STACK_HIGH_ADDRESS to 1 in FreeRTOSConfig.h to report stack total and peak"
|
||||||
|
#endif
|
||||||
|
|
||||||
size_t mg_usage(int task, int metric) {
|
size_t mg_usage(int task, int metric) {
|
||||||
// FreeRTOS: call NUM_TASKS first to snapshot tasks and copy their names
|
#if MG_ARCH == MG_ARCH_ESP32 || \
|
||||||
// Indices and names stay valid until the next refresh, preventing duplicate
|
(MG_ENABLE_FREERTOS && defined(configTOTAL_HEAP_SIZE))
|
||||||
// or mismatched entries. STACK_FREE is minimum-ever free; STACK_TOTAL is 0
|
if (metric == MG_USAGE_HEAP_TOTAL || metric == MG_USAGE_HEAP_USED ||
|
||||||
#if MG_ENABLE_FREERTOS
|
metric == MG_USAGE_HEAP_PEAK) {
|
||||||
if (metric == MG_USAGE_HEAP_FREE) return xPortGetFreeHeapSize();
|
size_t total, available;
|
||||||
#ifdef configTOTAL_HEAP_SIZE
|
#if MG_ARCH == MG_ARCH_ESP32
|
||||||
if (metric == MG_USAGE_HEAP_TOTAL) return configTOTAL_HEAP_SIZE;
|
total = heap_caps_get_total_size(MALLOC_CAP_DEFAULT);
|
||||||
|
if (metric == MG_USAGE_HEAP_TOTAL) return total;
|
||||||
|
available = metric == MG_USAGE_HEAP_USED
|
||||||
|
? heap_caps_get_free_size(MALLOC_CAP_DEFAULT)
|
||||||
|
: heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT);
|
||||||
|
#else
|
||||||
|
total = configTOTAL_HEAP_SIZE;
|
||||||
|
if (metric == MG_USAGE_HEAP_TOTAL) return total;
|
||||||
|
if (metric == MG_USAGE_HEAP_USED) {
|
||||||
|
available = xPortGetFreeHeapSize();
|
||||||
|
} else {
|
||||||
|
#if defined(__GNUC__) && defined(__ELF__)
|
||||||
|
if (xPortGetMinimumEverFreeHeapSize == NULL) return 0;
|
||||||
|
available = xPortGetMinimumEverFreeHeapSize();
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return available <= total ? total - available : 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if MG_ENABLE_FREERTOS || MG_ARCH == MG_ARCH_ESP32
|
||||||
#if configUSE_TRACE_FACILITY == 1
|
#if configUSE_TRACE_FACILITY == 1
|
||||||
static TaskStatus_t *tasks;
|
UBaseType_t count = uxTaskGetNumberOfTasks();
|
||||||
static UBaseType_t count;
|
if (metric == MG_USAGE_NUM_TASKS) return (size_t) count;
|
||||||
if (metric == MG_USAGE_NUM_TASKS) {
|
if (task >= 0 && (size_t) task < count) {
|
||||||
size_t size = sizeof(*tasks) + configMAX_TASK_NAME_LEN;
|
TaskStatus_t tasks[count];
|
||||||
vTaskSuspendAll();
|
if ((size_t) task >= uxTaskGetSystemState(tasks, count, NULL)) return 0;
|
||||||
if (tasks != NULL) {
|
TaskStatus_t *t = &tasks[task];
|
||||||
mg_free(tasks);
|
if (metric == MG_USAGE_TASK_NAME) return (size_t) t->pcTaskName;
|
||||||
tasks = NULL;
|
#if (MG_ARCH == MG_ARCH_CUBE || MG_ARCH == MG_ARCH_ESP32) && \
|
||||||
|
configRECORD_STACK_HIGH_ADDRESS == 1
|
||||||
|
size_t total = 0;
|
||||||
|
uintptr_t tcb = (uintptr_t) t->xHandle;
|
||||||
|
uintptr_t base = (uintptr_t) t->pxStackBase;
|
||||||
|
if (tcb != 0 &&
|
||||||
|
t->pcTaskName == (const char *) (tcb + offsetof(StaticTask_t, ucDummy7))) {
|
||||||
|
StackType_t *end;
|
||||||
|
uintptr_t field = tcb + offsetof(StaticTask_t, pxDummy8);
|
||||||
|
memcpy(&end, (const void *) field, sizeof(end));
|
||||||
|
if (base != 0 && (uintptr_t) end >= base &&
|
||||||
|
(uintptr_t) end - base <= ((size_t) -1) - sizeof(*end))
|
||||||
|
total = (size_t) ((uintptr_t) end - base) + sizeof(*end);
|
||||||
}
|
}
|
||||||
count = 0;
|
if (metric == MG_USAGE_STACK_TOTAL) return total;
|
||||||
UBaseType_t n = uxTaskGetNumberOfTasks();
|
if (metric == MG_USAGE_STACK_PEAK) {
|
||||||
if (n > 0 && (size_t) n <= INT_MAX && (size_t) n <= SIZE_MAX / size) {
|
size_t available =
|
||||||
tasks = (TaskStatus_t *) mg_calloc((size_t) n, size);
|
(size_t) t->usStackHighWaterMark * sizeof(StackType_t);
|
||||||
if (tasks != NULL) {
|
return available <= total ? total - available : 0;
|
||||||
count = uxTaskGetSystemState(tasks, n, NULL);
|
|
||||||
if (count == n) {
|
|
||||||
for (UBaseType_t i = 0; i < count; i++) {
|
|
||||||
char *name = (char *) (tasks + n) + (size_t) i * configMAX_TASK_NAME_LEN;
|
|
||||||
mg_snprintf(name, configMAX_TASK_NAME_LEN, "%s", tasks[i].pcTaskName);
|
|
||||||
tasks[i].pcTaskName = name;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mg_free(tasks);
|
|
||||||
tasks = NULL;
|
|
||||||
count = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
(void) xTaskResumeAll();
|
#endif
|
||||||
return (size_t) count;
|
|
||||||
}
|
|
||||||
if (task >= 0 && (size_t) task < (size_t) count) {
|
|
||||||
if (metric == MG_USAGE_TASK_NAME) return (size_t) tasks[task].pcTaskName;
|
|
||||||
if (metric == MG_USAGE_STACK_FREE)
|
|
||||||
return (size_t) tasks[task].usStackHighWaterMark * sizeof(StackType_t);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
(void) task;
|
(void) task, (void) metric;
|
||||||
(void) metric;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static size_t print_stacks(mg_pfn_t fn, void *arg, va_list *ap) {
|
static size_t print_mem_stat(mg_pfn_t fn, void *arg, const char *name,
|
||||||
int i;
|
size_t total, size_t used, size_t peak) {
|
||||||
int count = (int) mg_usage(-1, MG_USAGE_NUM_TASKS);
|
return mg_xprintf(fn, arg, "{%m:%m,%m:%zu,%m:%zu,%m:%zu}", MG_ESC("name"),
|
||||||
size_t len = mg_xprintf(fn, arg, "[");
|
MG_ESC(name == NULL ? "" : name), MG_ESC("total"), total,
|
||||||
for (i = 0; i < count; i++) {
|
MG_ESC("used"), used, MG_ESC("peak"), peak);
|
||||||
const char *name = (const char *) mg_usage(i, MG_USAGE_TASK_NAME);
|
|
||||||
len += mg_xprintf(fn, arg, "%s{%m:%m,%m:%zu,%m:%zu}",
|
|
||||||
i == 0 ? "" : ",", MG_ESC("name"),
|
|
||||||
MG_ESC(name == NULL ? "" : name), MG_ESC("free"),
|
|
||||||
(size_t) mg_usage(i, MG_USAGE_STACK_FREE), MG_ESC("total"),
|
|
||||||
(size_t) mg_usage(i, MG_USAGE_STACK_TOTAL));
|
|
||||||
}
|
|
||||||
(void) ap;
|
|
||||||
return len + mg_xprintf(fn, arg, "]");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t mg_print_mem_stats(mg_pfn_t fn, void *arg, va_list *ap) {
|
size_t mg_print_mem_stats(mg_pfn_t fn, void *arg, va_list *ap) {
|
||||||
size_t heap_total = (size_t) mg_usage(0, MG_USAGE_HEAP_TOTAL);
|
int i, count;
|
||||||
size_t heap_free = (size_t) mg_usage(0, MG_USAGE_HEAP_FREE);
|
size_t heap_total = mg_usage(0, MG_USAGE_HEAP_TOTAL);
|
||||||
|
size_t heap_used = mg_usage(0, MG_USAGE_HEAP_USED);
|
||||||
|
size_t heap_peak = mg_usage(0, MG_USAGE_HEAP_PEAK);
|
||||||
|
size_t len = mg_xprintf(fn, arg, "[");
|
||||||
|
len += print_mem_stat(fn, arg, "heap", heap_total, heap_used, heap_peak);
|
||||||
|
count = (int) mg_usage(-1, MG_USAGE_NUM_TASKS);
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
const char *name = (const char *) mg_usage(i, MG_USAGE_TASK_NAME);
|
||||||
|
len += mg_xprintf(fn, arg, ",");
|
||||||
|
len += print_mem_stat(fn, arg, name, mg_usage(i, MG_USAGE_STACK_TOTAL),
|
||||||
|
mg_usage(i, MG_USAGE_STACK_USED),
|
||||||
|
mg_usage(i, MG_USAGE_STACK_PEAK));
|
||||||
|
}
|
||||||
(void) ap;
|
(void) ap;
|
||||||
return mg_xprintf(fn, arg, "{%m:%zu,%m:%zu,%m:%M}",
|
return len + mg_xprintf(fn, arg, "]");
|
||||||
MG_ESC("heap_total"), heap_total, MG_ESC("heap_free"),
|
|
||||||
heap_free, MG_ESC("stacks"), print_stacks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MG_ARCH == MG_ARCH_ESP32
|
#if MG_ARCH == MG_ARCH_ESP32
|
||||||
@@ -3975,10 +4005,10 @@ bool mg_health_coredump_begin(size_t *size) {
|
|||||||
bool mg_health_coredump_read(size_t offset, void *buf, size_t *len) {
|
bool mg_health_coredump_read(size_t offset, void *buf, size_t *len) {
|
||||||
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
||||||
const esp_partition_t *p = (const esp_partition_t *) s_coredump.ptr;
|
const esp_partition_t *p = (const esp_partition_t *) s_coredump.ptr;
|
||||||
if (p == NULL || offset > s_coredump.size)
|
if (p == NULL || offset > s_coredump.size) return false;
|
||||||
return false;
|
|
||||||
if (*len > s_coredump.size - offset) *len = s_coredump.size - offset;
|
if (*len > s_coredump.size - offset) *len = s_coredump.size - offset;
|
||||||
return esp_partition_read(p, s_coredump.poffset + offset, buf, *len) == ESP_OK;
|
return esp_partition_read(p, s_coredump.poffset + offset, buf, *len) ==
|
||||||
|
ESP_OK;
|
||||||
#else
|
#else
|
||||||
(void) offset, (void) buf, (void) len;
|
(void) offset, (void) buf, (void) len;
|
||||||
return false;
|
return false;
|
||||||
@@ -4020,6 +4050,7 @@ __attribute__((used, noinline)) static void fault_c(uint32_t *frame,
|
|||||||
r[3 + i] = stack + i < &_estack ? stack[i] : 0;
|
r[3 + i] = stack + i < &_estack ? stack[i] : 0;
|
||||||
}
|
}
|
||||||
mg_health_record.cpuid = SCB->CPUID;
|
mg_health_record.cpuid = SCB->CPUID;
|
||||||
|
mg_health_record.arch = MG_HEALTH_ARCH_ARM;
|
||||||
mg_health_record.reset_reason = MG_HEALTH_RESET_FAULT;
|
mg_health_record.reset_reason = MG_HEALTH_RESET_FAULT;
|
||||||
NVIC_SystemReset();
|
NVIC_SystemReset();
|
||||||
}
|
}
|
||||||
@@ -4051,46 +4082,43 @@ void mg_health_init(void) {
|
|||||||
}
|
}
|
||||||
#if MG_ARCH == MG_ARCH_ESP32
|
#if MG_ARCH == MG_ARCH_ESP32
|
||||||
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||||
mg_health_record.arch = (uint16_t) MG_HEALTH_ARCH_XTENSA;
|
mg_health_record.arch = MG_HEALTH_ARCH_XTENSA;
|
||||||
#elif CONFIG_IDF_TARGET_ARCH_RISCV
|
#elif CONFIG_IDF_TARGET_ARCH_RISCV
|
||||||
mg_health_record.arch = (uint16_t) MG_HEALTH_ARCH_RISCV;
|
mg_health_record.arch = MG_HEALTH_ARCH_RISCV;
|
||||||
#endif
|
#endif
|
||||||
esp_reset_reason_t rr = esp_reset_reason();
|
esp_reset_reason_t rr = esp_reset_reason();
|
||||||
mg_health_record.reset_reason = esp2mg(rr);
|
mg_health_record.reset_reason = esp2mg(rr);
|
||||||
#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH == 0
|
#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH == 0
|
||||||
MG_ERROR(("MG_HEALTH is enabled but CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=n"));
|
MG_ERROR(("MG_HEALTH is enabled but CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=n"));
|
||||||
#else
|
#else
|
||||||
// After a crash, copy the backtrace from the core dump in flash: the same
|
// After a crash, copy the backtrace from the core dump in flash: the same
|
||||||
// addresses the panic handler prints as "Backtrace: PC:SP PC:SP ...".
|
// addresses the panic handler prints as "Backtrace: PC:SP PC:SP ...".
|
||||||
// The core dump stays in flash, so check the reset reason, otherwise an
|
// The core dump stays in flash, so check the reset reason, otherwise an
|
||||||
// old crash is reported on every boot
|
// old crash is reported on every boot
|
||||||
esp_core_dump_summary_t *s = calloc(1, sizeof(*s));
|
esp_core_dump_summary_t s;
|
||||||
uint32_t *out = mg_health_record.data;
|
uint32_t *out = mg_health_record.data;
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
memset(out, 0, sizeof(mg_health_record.data));
|
memset(out, 0, sizeof(mg_health_record.data));
|
||||||
if (s == NULL) return;
|
if ((rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT &&
|
||||||
if ((rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT &&
|
rr != ESP_RST_TASK_WDT) ||
|
||||||
rr != ESP_RST_TASK_WDT) // Not a crash caught by the panic handler
|
esp_core_dump_get_summary(&s) != ESP_OK) {
|
||||||
|| esp_core_dump_get_summary(s) != ESP_OK) {
|
return; // If not panic, and if cannot get crash info, give up
|
||||||
free(s);
|
}
|
||||||
return;
|
out[n++] = s.exc_pc; // Crash PC
|
||||||
}
|
memcpy(&out[n], s.exc_task, 16), n += 4; // Crashed task name
|
||||||
out[n++] = s->exc_pc;
|
|
||||||
memcpy(&out[n], s->exc_task, 16), n += 4;
|
|
||||||
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||||
out[n++] = s->ex_info.exc_a[0];
|
out[n++] = s.ex_info.exc_a[0];
|
||||||
out[n++] = s->ex_info.exc_a[1];
|
out[n++] = s.ex_info.exc_a[1];
|
||||||
size_t len = s->exc_bt_info.depth;
|
size_t len = s.exc_bt_info.depth;
|
||||||
if (len > MG_HEALTH_DATA_SIZE - n) len = (MG_HEALTH_DATA_SIZE - n);
|
if (len > MG_HEALTH_DATA_SIZE - n) len = MG_HEALTH_DATA_SIZE - n;
|
||||||
memcpy(&out[n], s->exc_bt_info.bt, 4 * len); // sizeof(uint32_t)
|
memcpy(&out[n], s.exc_bt_info.bt, 4 * len);
|
||||||
#elif CONFIG_IDF_TARGET_ARCH_RISCV
|
#elif CONFIG_IDF_TARGET_ARCH_RISCV
|
||||||
out[n++] = s->ex_info.exc_ra;
|
out[n++] = s.ex_info.ra;
|
||||||
out[n++] = s->ex_info.exc_sp;
|
out[n++] = s.ex_info.sp;
|
||||||
size_t len = s->exc_bt_info.dump_size / 4 ; // sizeof(uint32_t)
|
size_t len = s.exc_bt_info.dump_size / 4;
|
||||||
if (len > MG_HEALTH_DATA_SIZE - n) len = (MG_HEALTH_DATA_SIZE - n);
|
if (len > MG_HEALTH_DATA_SIZE - n) len = MG_HEALTH_DATA_SIZE - n;
|
||||||
memcpy(&out[n], s->exc_bt_info.stackdump, 4 * len);
|
memcpy(&out[n], s.exc_bt_info.stackdump, 4 * len);
|
||||||
#endif
|
#endif
|
||||||
free(s);
|
|
||||||
#endif // CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
#endif // CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
||||||
#endif // MG_ARCH_ESP32
|
#endif // MG_ARCH_ESP32
|
||||||
}
|
}
|
||||||
@@ -4106,13 +4134,10 @@ static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) {
|
|||||||
size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
|
size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
if (mg_health_valid() &&
|
if (mg_health_valid() &&
|
||||||
(mg_health_record.reset_reason == (uint16_t) MG_HEALTH_RESET_FAULT
|
(mg_health_record.reset_reason == MG_HEALTH_RESET_FAULT ||
|
||||||
#if MG_ARCH == MG_ARCH_ESP32
|
mg_health_record.reset_reason == MG_HEALTH_RESET_WATCHDOG)) {
|
||||||
|| mg_health_record.reset_reason == (uint16_t) MG_HEALTH_RESET_WATCHDOG
|
|
||||||
#endif
|
|
||||||
)) {
|
|
||||||
size_t n = sizeof(mg_health_record.data) / sizeof(mg_health_record.data[0]);
|
size_t n = sizeof(mg_health_record.data) / sizeof(mg_health_record.data[0]);
|
||||||
len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%hu,%m:%hu,%m:%u,%m:[%M]}", //
|
len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%lu,%m:%lu,%m:%u,%m:[%M]}", //
|
||||||
MG_ESC("version"), mg_health_record.magic[3], //
|
MG_ESC("version"), mg_health_record.magic[3], //
|
||||||
MG_ESC("arch"), mg_health_record.arch, //
|
MG_ESC("arch"), mg_health_record.arch, //
|
||||||
MG_ESC("reason"), mg_health_record.reset_reason, //
|
MG_ESC("reason"), mg_health_record.reset_reason, //
|
||||||
|
|||||||
+16
-16
@@ -265,10 +265,6 @@ extern "C" {
|
|||||||
#define MG_ENABLE_POSIX_FS 1
|
#define MG_ENABLE_POSIX_FS 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef MG_HEALTH_DATA_SIZE
|
|
||||||
#define MG_HEALTH_DATA_SIZE 20
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@@ -4395,8 +4391,10 @@ void mg_ota_poll(struct mg_mgr *);
|
|||||||
#ifndef MG_HEALTH_DATA_SIZE
|
#ifndef MG_HEALTH_DATA_SIZE
|
||||||
#if (defined(STM32H7_DEV_ID) || defined(STM32H5)) || defined(STM32C5)
|
#if (defined(STM32H7_DEV_ID) || defined(STM32H5)) || defined(STM32C5)
|
||||||
#define MG_HEALTH_DATA_SIZE 250
|
#define MG_HEALTH_DATA_SIZE 250
|
||||||
#elif MG_ARCH == MG_ARCH_ESP32
|
#elif MG_ARCH == MG_ARCH_ESP32 && CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||||
#define MG_HEALTH_DATA_SIZE 23
|
#define MG_HEALTH_DATA_SIZE 23 // 7 header words + 16 backtrace PCs
|
||||||
|
#elif MG_ARCH == MG_ARCH_ESP32 && CONFIG_IDF_TARGET_ARCH_RISCV
|
||||||
|
#define MG_HEALTH_DATA_SIZE 7 // PC, task name, RA, SP
|
||||||
#else
|
#else
|
||||||
#define MG_HEALTH_DATA_SIZE 10
|
#define MG_HEALTH_DATA_SIZE 10
|
||||||
#endif
|
#endif
|
||||||
@@ -4429,10 +4427,10 @@ enum mg_health_reason {
|
|||||||
MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack
|
MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack
|
||||||
};
|
};
|
||||||
|
|
||||||
struct mg_health {
|
struct mg_health { // NOTE: use 32-bit fields to avoid write issues
|
||||||
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
|
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
|
||||||
uint16_t reset_reason; // enum mg_health_reason that started this boot
|
uint32_t reset_reason; // enum mg_health_reason that started this boot
|
||||||
uint16_t arch; // enum mg_health_arch, core architecture (ARM, etc)
|
uint32_t arch; // enum mg_health_arch, core architecture (ARM, etc)
|
||||||
uint32_t cpuid; // ARM CPU ID
|
uint32_t cpuid; // ARM CPU ID
|
||||||
uint32_t data[MG_HEALTH_DATA_SIZE];
|
uint32_t data[MG_HEALTH_DATA_SIZE];
|
||||||
};
|
};
|
||||||
@@ -4501,24 +4499,26 @@ enum mg_usage_metric {
|
|||||||
MG_USAGE_NUM_TASKS,
|
MG_USAGE_NUM_TASKS,
|
||||||
MG_USAGE_TASK_NAME,
|
MG_USAGE_TASK_NAME,
|
||||||
MG_USAGE_STACK_TOTAL,
|
MG_USAGE_STACK_TOTAL,
|
||||||
MG_USAGE_STACK_FREE,
|
MG_USAGE_STACK_USED,
|
||||||
|
MG_USAGE_STACK_PEAK,
|
||||||
MG_USAGE_HEAP_TOTAL,
|
MG_USAGE_HEAP_TOTAL,
|
||||||
MG_USAGE_HEAP_FREE
|
MG_USAGE_HEAP_USED,
|
||||||
|
MG_USAGE_HEAP_PEAK
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns a task or heap metric. task is a zero-based index; NUM_TASKS and heap
|
// Returns a task or heap metric. task is a zero-based index; NUM_TASKS and heap
|
||||||
// metrics ignore it. Sizes are in bytes, TASK_NAME returns const char * cast to
|
// metrics ignore it. Sizes are in bytes, TASK_NAME returns const char * cast to
|
||||||
// size_t
|
// size_t
|
||||||
// Unsupported metrics and unknown totals return 0
|
// Unsupported metrics return 0. PEAK is the maximum used since boot
|
||||||
// Set MG_ENABLE_CUSTOM_USAGE=1 and define mg_usage() in the application to
|
// Set MG_ENABLE_CUSTOM_USAGE=1 and define mg_usage() in the application to
|
||||||
// provide custom measurements
|
// provide custom measurements
|
||||||
size_t mg_usage(int task, int metric);
|
size_t mg_usage(int task, int metric);
|
||||||
|
|
||||||
#define MG_HEALTH_INIT() mg_health_init()
|
#define MG_HEALTH_INIT() mg_health_init()
|
||||||
extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *);
|
extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *);
|
||||||
// JSON memory statistics via mg_usage(), suitable for %M: heap_total, heap_free,
|
// JSON memory statistics via mg_usage(), suitable for %M: an array of
|
||||||
// and a stacks array of {name, free, total}
|
// {name, total, used, peak} entries, starting with "heap", then task stacks
|
||||||
// A zero total means unknown
|
// Unavailable metrics are reported as 0
|
||||||
extern size_t mg_print_mem_stats(mg_pfn_t, void *, va_list *);
|
extern size_t mg_print_mem_stats(mg_pfn_t, void *, va_list *);
|
||||||
extern void mg_health_init(void);
|
extern void mg_health_init(void);
|
||||||
extern bool mg_health_coredump_begin(size_t *size);
|
extern bool mg_health_coredump_begin(size_t *size);
|
||||||
@@ -4540,7 +4540,7 @@ static inline size_t mg_print_crash_record(mg_pfn_t fn, void *arg,
|
|||||||
}
|
}
|
||||||
static inline size_t mg_print_mem_stats(mg_pfn_t fn, void *arg, va_list *ap) {
|
static inline size_t mg_print_mem_stats(mg_pfn_t fn, void *arg, va_list *ap) {
|
||||||
(void) ap;
|
(void) ap;
|
||||||
return mg_xprintf(fn, arg, "{}");
|
return mg_xprintf(fn, arg, "[]");
|
||||||
}
|
}
|
||||||
static inline bool mg_health_coredump_begin(size_t *size) {
|
static inline bool mg_health_coredump_begin(size_t *size) {
|
||||||
(void) size;
|
(void) size;
|
||||||
|
|||||||
@@ -34,8 +34,4 @@
|
|||||||
#define MG_ENABLE_POSIX_FS 1
|
#define MG_ENABLE_POSIX_FS 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef MG_HEALTH_DATA_SIZE
|
|
||||||
#define MG_HEALTH_DATA_SIZE 20
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
+126
-101
@@ -1,86 +1,116 @@
|
|||||||
|
#include "arch.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "health.h"
|
#include "health.h"
|
||||||
#include <limits.h>
|
|
||||||
|
|
||||||
#if MG_ENABLE_HEALTH
|
#if MG_ENABLE_HEALTH
|
||||||
#if !MG_ENABLE_CUSTOM_USAGE
|
#if !MG_ENABLE_CUSTOM_USAGE
|
||||||
|
#if MG_ARCH == MG_ARCH_ESP32
|
||||||
|
#include <esp_heap_caps.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
#elif MG_ENABLE_FREERTOS && defined(__GNUC__) && defined(__ELF__)
|
||||||
|
// Some FreeRTOS allocators provide current free bytes but no low watermark.
|
||||||
|
extern size_t xPortGetMinimumEverFreeHeapSize(void) __attribute__((weak));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if MG_ENABLE_FREERTOS && MG_ARCH == MG_ARCH_CUBE && \
|
||||||
|
configUSE_TRACE_FACILITY == 1 && configRECORD_STACK_HIGH_ADDRESS != 1
|
||||||
|
#warning \
|
||||||
|
"Set configRECORD_STACK_HIGH_ADDRESS to 1 in FreeRTOSConfig.h to report stack total and peak"
|
||||||
|
#endif
|
||||||
|
|
||||||
size_t mg_usage(int task, int metric) {
|
size_t mg_usage(int task, int metric) {
|
||||||
// FreeRTOS: call NUM_TASKS first to snapshot tasks and copy their names
|
#if MG_ARCH == MG_ARCH_ESP32 || \
|
||||||
// Indices and names stay valid until the next refresh, preventing duplicate
|
(MG_ENABLE_FREERTOS && defined(configTOTAL_HEAP_SIZE))
|
||||||
// or mismatched entries. STACK_FREE is minimum-ever free; STACK_TOTAL is 0
|
if (metric == MG_USAGE_HEAP_TOTAL || metric == MG_USAGE_HEAP_USED ||
|
||||||
#if MG_ENABLE_FREERTOS
|
metric == MG_USAGE_HEAP_PEAK) {
|
||||||
if (metric == MG_USAGE_HEAP_FREE) return xPortGetFreeHeapSize();
|
size_t total, available;
|
||||||
#ifdef configTOTAL_HEAP_SIZE
|
#if MG_ARCH == MG_ARCH_ESP32
|
||||||
if (metric == MG_USAGE_HEAP_TOTAL) return configTOTAL_HEAP_SIZE;
|
total = heap_caps_get_total_size(MALLOC_CAP_DEFAULT);
|
||||||
|
if (metric == MG_USAGE_HEAP_TOTAL) return total;
|
||||||
|
available = metric == MG_USAGE_HEAP_USED
|
||||||
|
? heap_caps_get_free_size(MALLOC_CAP_DEFAULT)
|
||||||
|
: heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT);
|
||||||
|
#else
|
||||||
|
total = configTOTAL_HEAP_SIZE;
|
||||||
|
if (metric == MG_USAGE_HEAP_TOTAL) return total;
|
||||||
|
if (metric == MG_USAGE_HEAP_USED) {
|
||||||
|
available = xPortGetFreeHeapSize();
|
||||||
|
} else {
|
||||||
|
#if defined(__GNUC__) && defined(__ELF__)
|
||||||
|
if (xPortGetMinimumEverFreeHeapSize == NULL) return 0;
|
||||||
|
available = xPortGetMinimumEverFreeHeapSize();
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return available <= total ? total - available : 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if MG_ENABLE_FREERTOS || MG_ARCH == MG_ARCH_ESP32
|
||||||
#if configUSE_TRACE_FACILITY == 1
|
#if configUSE_TRACE_FACILITY == 1
|
||||||
static TaskStatus_t *tasks;
|
UBaseType_t count = uxTaskGetNumberOfTasks();
|
||||||
static UBaseType_t count;
|
if (metric == MG_USAGE_NUM_TASKS) return (size_t) count;
|
||||||
if (metric == MG_USAGE_NUM_TASKS) {
|
if (task >= 0 && (size_t) task < count) {
|
||||||
size_t size = sizeof(*tasks) + configMAX_TASK_NAME_LEN;
|
TaskStatus_t tasks[count];
|
||||||
vTaskSuspendAll();
|
if ((size_t) task >= uxTaskGetSystemState(tasks, count, NULL)) return 0;
|
||||||
if (tasks != NULL) {
|
TaskStatus_t *t = &tasks[task];
|
||||||
mg_free(tasks);
|
if (metric == MG_USAGE_TASK_NAME) return (size_t) t->pcTaskName;
|
||||||
tasks = NULL;
|
#if (MG_ARCH == MG_ARCH_CUBE || MG_ARCH == MG_ARCH_ESP32) && \
|
||||||
|
configRECORD_STACK_HIGH_ADDRESS == 1
|
||||||
|
size_t total = 0;
|
||||||
|
uintptr_t tcb = (uintptr_t) t->xHandle;
|
||||||
|
uintptr_t base = (uintptr_t) t->pxStackBase;
|
||||||
|
if (tcb != 0 &&
|
||||||
|
t->pcTaskName == (const char *) (tcb + offsetof(StaticTask_t, ucDummy7))) {
|
||||||
|
StackType_t *end;
|
||||||
|
uintptr_t field = tcb + offsetof(StaticTask_t, pxDummy8);
|
||||||
|
memcpy(&end, (const void *) field, sizeof(end));
|
||||||
|
if (base != 0 && (uintptr_t) end >= base &&
|
||||||
|
(uintptr_t) end - base <= ((size_t) -1) - sizeof(*end))
|
||||||
|
total = (size_t) ((uintptr_t) end - base) + sizeof(*end);
|
||||||
}
|
}
|
||||||
count = 0;
|
if (metric == MG_USAGE_STACK_TOTAL) return total;
|
||||||
UBaseType_t n = uxTaskGetNumberOfTasks();
|
if (metric == MG_USAGE_STACK_PEAK) {
|
||||||
if (n > 0 && (size_t) n <= INT_MAX && (size_t) n <= SIZE_MAX / size) {
|
size_t available =
|
||||||
tasks = (TaskStatus_t *) mg_calloc((size_t) n, size);
|
(size_t) t->usStackHighWaterMark * sizeof(StackType_t);
|
||||||
if (tasks != NULL) {
|
return available <= total ? total - available : 0;
|
||||||
count = uxTaskGetSystemState(tasks, n, NULL);
|
|
||||||
if (count == n) {
|
|
||||||
for (UBaseType_t i = 0; i < count; i++) {
|
|
||||||
char *name = (char *) (tasks + n) + (size_t) i * configMAX_TASK_NAME_LEN;
|
|
||||||
mg_snprintf(name, configMAX_TASK_NAME_LEN, "%s", tasks[i].pcTaskName);
|
|
||||||
tasks[i].pcTaskName = name;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mg_free(tasks);
|
|
||||||
tasks = NULL;
|
|
||||||
count = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
(void) xTaskResumeAll();
|
#endif
|
||||||
return (size_t) count;
|
|
||||||
}
|
|
||||||
if (task >= 0 && (size_t) task < (size_t) count) {
|
|
||||||
if (metric == MG_USAGE_TASK_NAME) return (size_t) tasks[task].pcTaskName;
|
|
||||||
if (metric == MG_USAGE_STACK_FREE)
|
|
||||||
return (size_t) tasks[task].usStackHighWaterMark * sizeof(StackType_t);
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
(void) task;
|
(void) task, (void) metric;
|
||||||
(void) metric;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static size_t print_stacks(mg_pfn_t fn, void *arg, va_list *ap) {
|
static size_t print_mem_stat(mg_pfn_t fn, void *arg, const char *name,
|
||||||
int i;
|
size_t total, size_t used, size_t peak) {
|
||||||
int count = (int) mg_usage(-1, MG_USAGE_NUM_TASKS);
|
return mg_xprintf(fn, arg, "{%m:%m,%m:%zu,%m:%zu,%m:%zu}", MG_ESC("name"),
|
||||||
size_t len = mg_xprintf(fn, arg, "[");
|
MG_ESC(name == NULL ? "" : name), MG_ESC("total"), total,
|
||||||
for (i = 0; i < count; i++) {
|
MG_ESC("used"), used, MG_ESC("peak"), peak);
|
||||||
const char *name = (const char *) mg_usage(i, MG_USAGE_TASK_NAME);
|
|
||||||
len += mg_xprintf(fn, arg, "%s{%m:%m,%m:%zu,%m:%zu}",
|
|
||||||
i == 0 ? "" : ",", MG_ESC("name"),
|
|
||||||
MG_ESC(name == NULL ? "" : name), MG_ESC("free"),
|
|
||||||
(size_t) mg_usage(i, MG_USAGE_STACK_FREE), MG_ESC("total"),
|
|
||||||
(size_t) mg_usage(i, MG_USAGE_STACK_TOTAL));
|
|
||||||
}
|
|
||||||
(void) ap;
|
|
||||||
return len + mg_xprintf(fn, arg, "]");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t mg_print_mem_stats(mg_pfn_t fn, void *arg, va_list *ap) {
|
size_t mg_print_mem_stats(mg_pfn_t fn, void *arg, va_list *ap) {
|
||||||
size_t heap_total = (size_t) mg_usage(0, MG_USAGE_HEAP_TOTAL);
|
int i, count;
|
||||||
size_t heap_free = (size_t) mg_usage(0, MG_USAGE_HEAP_FREE);
|
size_t heap_total = mg_usage(0, MG_USAGE_HEAP_TOTAL);
|
||||||
|
size_t heap_used = mg_usage(0, MG_USAGE_HEAP_USED);
|
||||||
|
size_t heap_peak = mg_usage(0, MG_USAGE_HEAP_PEAK);
|
||||||
|
size_t len = mg_xprintf(fn, arg, "[");
|
||||||
|
len += print_mem_stat(fn, arg, "heap", heap_total, heap_used, heap_peak);
|
||||||
|
count = (int) mg_usage(-1, MG_USAGE_NUM_TASKS);
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
const char *name = (const char *) mg_usage(i, MG_USAGE_TASK_NAME);
|
||||||
|
len += mg_xprintf(fn, arg, ",");
|
||||||
|
len += print_mem_stat(fn, arg, name, mg_usage(i, MG_USAGE_STACK_TOTAL),
|
||||||
|
mg_usage(i, MG_USAGE_STACK_USED),
|
||||||
|
mg_usage(i, MG_USAGE_STACK_PEAK));
|
||||||
|
}
|
||||||
(void) ap;
|
(void) ap;
|
||||||
return mg_xprintf(fn, arg, "{%m:%zu,%m:%zu,%m:%M}",
|
return len + mg_xprintf(fn, arg, "]");
|
||||||
MG_ESC("heap_total"), heap_total, MG_ESC("heap_free"),
|
|
||||||
heap_free, MG_ESC("stacks"), print_stacks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if MG_ARCH == MG_ARCH_ESP32
|
#if MG_ARCH == MG_ARCH_ESP32
|
||||||
@@ -116,10 +146,10 @@ bool mg_health_coredump_begin(size_t *size) {
|
|||||||
bool mg_health_coredump_read(size_t offset, void *buf, size_t *len) {
|
bool mg_health_coredump_read(size_t offset, void *buf, size_t *len) {
|
||||||
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
#if MG_ARCH == MG_ARCH_ESP32 && CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
||||||
const esp_partition_t *p = (const esp_partition_t *) s_coredump.ptr;
|
const esp_partition_t *p = (const esp_partition_t *) s_coredump.ptr;
|
||||||
if (p == NULL || offset > s_coredump.size)
|
if (p == NULL || offset > s_coredump.size) return false;
|
||||||
return false;
|
|
||||||
if (*len > s_coredump.size - offset) *len = s_coredump.size - offset;
|
if (*len > s_coredump.size - offset) *len = s_coredump.size - offset;
|
||||||
return esp_partition_read(p, s_coredump.poffset + offset, buf, *len) == ESP_OK;
|
return esp_partition_read(p, s_coredump.poffset + offset, buf, *len) ==
|
||||||
|
ESP_OK;
|
||||||
#else
|
#else
|
||||||
(void) offset, (void) buf, (void) len;
|
(void) offset, (void) buf, (void) len;
|
||||||
return false;
|
return false;
|
||||||
@@ -161,6 +191,7 @@ __attribute__((used, noinline)) static void fault_c(uint32_t *frame,
|
|||||||
r[3 + i] = stack + i < &_estack ? stack[i] : 0;
|
r[3 + i] = stack + i < &_estack ? stack[i] : 0;
|
||||||
}
|
}
|
||||||
mg_health_record.cpuid = SCB->CPUID;
|
mg_health_record.cpuid = SCB->CPUID;
|
||||||
|
mg_health_record.arch = MG_HEALTH_ARCH_ARM;
|
||||||
mg_health_record.reset_reason = MG_HEALTH_RESET_FAULT;
|
mg_health_record.reset_reason = MG_HEALTH_RESET_FAULT;
|
||||||
NVIC_SystemReset();
|
NVIC_SystemReset();
|
||||||
}
|
}
|
||||||
@@ -192,46 +223,43 @@ void mg_health_init(void) {
|
|||||||
}
|
}
|
||||||
#if MG_ARCH == MG_ARCH_ESP32
|
#if MG_ARCH == MG_ARCH_ESP32
|
||||||
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||||
mg_health_record.arch = (uint16_t) MG_HEALTH_ARCH_XTENSA;
|
mg_health_record.arch = MG_HEALTH_ARCH_XTENSA;
|
||||||
#elif CONFIG_IDF_TARGET_ARCH_RISCV
|
#elif CONFIG_IDF_TARGET_ARCH_RISCV
|
||||||
mg_health_record.arch = (uint16_t) MG_HEALTH_ARCH_RISCV;
|
mg_health_record.arch = MG_HEALTH_ARCH_RISCV;
|
||||||
#endif
|
#endif
|
||||||
esp_reset_reason_t rr = esp_reset_reason();
|
esp_reset_reason_t rr = esp_reset_reason();
|
||||||
mg_health_record.reset_reason = esp2mg(rr);
|
mg_health_record.reset_reason = esp2mg(rr);
|
||||||
#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH == 0
|
#if CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH == 0
|
||||||
MG_ERROR(("MG_HEALTH is enabled but CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=n"));
|
MG_ERROR(("MG_HEALTH is enabled but CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=n"));
|
||||||
#else
|
#else
|
||||||
// After a crash, copy the backtrace from the core dump in flash: the same
|
// After a crash, copy the backtrace from the core dump in flash: the same
|
||||||
// addresses the panic handler prints as "Backtrace: PC:SP PC:SP ...".
|
// addresses the panic handler prints as "Backtrace: PC:SP PC:SP ...".
|
||||||
// The core dump stays in flash, so check the reset reason, otherwise an
|
// The core dump stays in flash, so check the reset reason, otherwise an
|
||||||
// old crash is reported on every boot
|
// old crash is reported on every boot
|
||||||
esp_core_dump_summary_t *s = calloc(1, sizeof(*s));
|
esp_core_dump_summary_t s;
|
||||||
uint32_t *out = mg_health_record.data;
|
uint32_t *out = mg_health_record.data;
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
memset(out, 0, sizeof(mg_health_record.data));
|
memset(out, 0, sizeof(mg_health_record.data));
|
||||||
if (s == NULL) return;
|
if ((rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT &&
|
||||||
if ((rr != ESP_RST_PANIC && rr != ESP_RST_INT_WDT &&
|
rr != ESP_RST_TASK_WDT) ||
|
||||||
rr != ESP_RST_TASK_WDT) // Not a crash caught by the panic handler
|
esp_core_dump_get_summary(&s) != ESP_OK) {
|
||||||
|| esp_core_dump_get_summary(s) != ESP_OK) {
|
return; // If not panic, and if cannot get crash info, give up
|
||||||
free(s);
|
}
|
||||||
return;
|
out[n++] = s.exc_pc; // Crash PC
|
||||||
}
|
memcpy(&out[n], s.exc_task, 16), n += 4; // Crashed task name
|
||||||
out[n++] = s->exc_pc;
|
|
||||||
memcpy(&out[n], s->exc_task, 16), n += 4;
|
|
||||||
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
#if CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||||
out[n++] = s->ex_info.exc_a[0];
|
out[n++] = s.ex_info.exc_a[0];
|
||||||
out[n++] = s->ex_info.exc_a[1];
|
out[n++] = s.ex_info.exc_a[1];
|
||||||
size_t len = s->exc_bt_info.depth;
|
size_t len = s.exc_bt_info.depth;
|
||||||
if (len > MG_HEALTH_DATA_SIZE - n) len = (MG_HEALTH_DATA_SIZE - n);
|
if (len > MG_HEALTH_DATA_SIZE - n) len = MG_HEALTH_DATA_SIZE - n;
|
||||||
memcpy(&out[n], s->exc_bt_info.bt, 4 * len); // sizeof(uint32_t)
|
memcpy(&out[n], s.exc_bt_info.bt, 4 * len);
|
||||||
#elif CONFIG_IDF_TARGET_ARCH_RISCV
|
#elif CONFIG_IDF_TARGET_ARCH_RISCV
|
||||||
out[n++] = s->ex_info.exc_ra;
|
out[n++] = s.ex_info.ra;
|
||||||
out[n++] = s->ex_info.exc_sp;
|
out[n++] = s.ex_info.sp;
|
||||||
size_t len = s->exc_bt_info.dump_size / 4 ; // sizeof(uint32_t)
|
size_t len = s.exc_bt_info.dump_size / 4;
|
||||||
if (len > MG_HEALTH_DATA_SIZE - n) len = (MG_HEALTH_DATA_SIZE - n);
|
if (len > MG_HEALTH_DATA_SIZE - n) len = MG_HEALTH_DATA_SIZE - n;
|
||||||
memcpy(&out[n], s->exc_bt_info.stackdump, 4 * len);
|
memcpy(&out[n], s.exc_bt_info.stackdump, 4 * len);
|
||||||
#endif
|
#endif
|
||||||
free(s);
|
|
||||||
#endif // CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
#endif // CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH
|
||||||
#endif // MG_ARCH_ESP32
|
#endif // MG_ARCH_ESP32
|
||||||
}
|
}
|
||||||
@@ -247,13 +275,10 @@ static size_t print_uint32(void (*fn)(char, void *), void *arg, va_list *ap) {
|
|||||||
size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
|
size_t mg_print_crash_record(void (*fn)(char, void *), void *arg, va_list *ap) {
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
if (mg_health_valid() &&
|
if (mg_health_valid() &&
|
||||||
(mg_health_record.reset_reason == (uint16_t) MG_HEALTH_RESET_FAULT
|
(mg_health_record.reset_reason == MG_HEALTH_RESET_FAULT ||
|
||||||
#if MG_ARCH == MG_ARCH_ESP32
|
mg_health_record.reset_reason == MG_HEALTH_RESET_WATCHDOG)) {
|
||||||
|| mg_health_record.reset_reason == (uint16_t) MG_HEALTH_RESET_WATCHDOG
|
|
||||||
#endif
|
|
||||||
)) {
|
|
||||||
size_t n = sizeof(mg_health_record.data) / sizeof(mg_health_record.data[0]);
|
size_t n = sizeof(mg_health_record.data) / sizeof(mg_health_record.data[0]);
|
||||||
len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%hu,%m:%hu,%m:%u,%m:[%M]}", //
|
len += mg_xprintf(fn, arg, "{%m:%hhu,%m:%lu,%m:%lu,%m:%u,%m:[%M]}", //
|
||||||
MG_ESC("version"), mg_health_record.magic[3], //
|
MG_ESC("version"), mg_health_record.magic[3], //
|
||||||
MG_ESC("arch"), mg_health_record.arch, //
|
MG_ESC("arch"), mg_health_record.arch, //
|
||||||
MG_ESC("reason"), mg_health_record.reset_reason, //
|
MG_ESC("reason"), mg_health_record.reset_reason, //
|
||||||
|
|||||||
+16
-12
@@ -18,8 +18,10 @@
|
|||||||
#ifndef MG_HEALTH_DATA_SIZE
|
#ifndef MG_HEALTH_DATA_SIZE
|
||||||
#if (defined(STM32H7_DEV_ID) || defined(STM32H5)) || defined(STM32C5)
|
#if (defined(STM32H7_DEV_ID) || defined(STM32H5)) || defined(STM32C5)
|
||||||
#define MG_HEALTH_DATA_SIZE 250
|
#define MG_HEALTH_DATA_SIZE 250
|
||||||
#elif MG_ARCH == MG_ARCH_ESP32
|
#elif MG_ARCH == MG_ARCH_ESP32 && CONFIG_IDF_TARGET_ARCH_XTENSA
|
||||||
#define MG_HEALTH_DATA_SIZE 23
|
#define MG_HEALTH_DATA_SIZE 23 // 7 header words + 16 backtrace PCs
|
||||||
|
#elif MG_ARCH == MG_ARCH_ESP32 && CONFIG_IDF_TARGET_ARCH_RISCV
|
||||||
|
#define MG_HEALTH_DATA_SIZE 7 // PC, task name, RA, SP
|
||||||
#else
|
#else
|
||||||
#define MG_HEALTH_DATA_SIZE 10
|
#define MG_HEALTH_DATA_SIZE 10
|
||||||
#endif
|
#endif
|
||||||
@@ -52,10 +54,10 @@ enum mg_health_reason {
|
|||||||
MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack
|
MG_HEALTH_RESET_FAULT // Fault handler ran, see regs[] and saved_stack
|
||||||
};
|
};
|
||||||
|
|
||||||
struct mg_health {
|
struct mg_health { // NOTE: use 32-bit fields to avoid write issues
|
||||||
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
|
char magic[4]; // MG_HEALTH_MAGIC when the record holds valid data
|
||||||
uint16_t reset_reason; // enum mg_health_reason that started this boot
|
uint32_t reset_reason; // enum mg_health_reason that started this boot
|
||||||
uint16_t arch; // enum mg_health_arch, core architecture (ARM, etc)
|
uint32_t arch; // enum mg_health_arch, core architecture (ARM, etc)
|
||||||
uint32_t cpuid; // ARM CPU ID
|
uint32_t cpuid; // ARM CPU ID
|
||||||
uint32_t data[MG_HEALTH_DATA_SIZE];
|
uint32_t data[MG_HEALTH_DATA_SIZE];
|
||||||
};
|
};
|
||||||
@@ -124,24 +126,26 @@ enum mg_usage_metric {
|
|||||||
MG_USAGE_NUM_TASKS,
|
MG_USAGE_NUM_TASKS,
|
||||||
MG_USAGE_TASK_NAME,
|
MG_USAGE_TASK_NAME,
|
||||||
MG_USAGE_STACK_TOTAL,
|
MG_USAGE_STACK_TOTAL,
|
||||||
MG_USAGE_STACK_FREE,
|
MG_USAGE_STACK_USED,
|
||||||
|
MG_USAGE_STACK_PEAK,
|
||||||
MG_USAGE_HEAP_TOTAL,
|
MG_USAGE_HEAP_TOTAL,
|
||||||
MG_USAGE_HEAP_FREE
|
MG_USAGE_HEAP_USED,
|
||||||
|
MG_USAGE_HEAP_PEAK
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns a task or heap metric. task is a zero-based index; NUM_TASKS and heap
|
// Returns a task or heap metric. task is a zero-based index; NUM_TASKS and heap
|
||||||
// metrics ignore it. Sizes are in bytes, TASK_NAME returns const char * cast to
|
// metrics ignore it. Sizes are in bytes, TASK_NAME returns const char * cast to
|
||||||
// size_t
|
// size_t
|
||||||
// Unsupported metrics and unknown totals return 0
|
// Unsupported metrics return 0. PEAK is the maximum used since boot
|
||||||
// Set MG_ENABLE_CUSTOM_USAGE=1 and define mg_usage() in the application to
|
// Set MG_ENABLE_CUSTOM_USAGE=1 and define mg_usage() in the application to
|
||||||
// provide custom measurements
|
// provide custom measurements
|
||||||
size_t mg_usage(int task, int metric);
|
size_t mg_usage(int task, int metric);
|
||||||
|
|
||||||
#define MG_HEALTH_INIT() mg_health_init()
|
#define MG_HEALTH_INIT() mg_health_init()
|
||||||
extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *);
|
extern size_t mg_print_crash_record(mg_pfn_t, void *, va_list *);
|
||||||
// JSON memory statistics via mg_usage(), suitable for %M: heap_total, heap_free,
|
// JSON memory statistics via mg_usage(), suitable for %M: an array of
|
||||||
// and a stacks array of {name, free, total}
|
// {name, total, used, peak} entries, starting with "heap", then task stacks
|
||||||
// A zero total means unknown
|
// Unavailable metrics are reported as 0
|
||||||
extern size_t mg_print_mem_stats(mg_pfn_t, void *, va_list *);
|
extern size_t mg_print_mem_stats(mg_pfn_t, void *, va_list *);
|
||||||
extern void mg_health_init(void);
|
extern void mg_health_init(void);
|
||||||
extern bool mg_health_coredump_begin(size_t *size);
|
extern bool mg_health_coredump_begin(size_t *size);
|
||||||
@@ -163,7 +167,7 @@ static inline size_t mg_print_crash_record(mg_pfn_t fn, void *arg,
|
|||||||
}
|
}
|
||||||
static inline size_t mg_print_mem_stats(mg_pfn_t fn, void *arg, va_list *ap) {
|
static inline size_t mg_print_mem_stats(mg_pfn_t fn, void *arg, va_list *ap) {
|
||||||
(void) ap;
|
(void) ap;
|
||||||
return mg_xprintf(fn, arg, "{}");
|
return mg_xprintf(fn, arg, "[]");
|
||||||
}
|
}
|
||||||
static inline bool mg_health_coredump_begin(size_t *size) {
|
static inline bool mg_health_coredump_begin(size_t *size) {
|
||||||
(void) size;
|
(void) size;
|
||||||
|
|||||||
@@ -1,25 +1,22 @@
|
|||||||
THISDIR = $(realpath $(CURDIR))
|
THISDIR = $(realpath $(CURDIR))
|
||||||
ROOTDIR = $(realpath $(CURDIR)/../../..)
|
ROOTDIR = $(realpath $(CURDIR)/../../..)
|
||||||
DOCKER ?= docker run --rm $(DA) -v $(ROOTDIR):$(ROOTDIR) -w $(THISDIR) espressif/idf
|
DOCKER ?= docker run --rm $(DA) -v $(ROOTDIR):$(ROOTDIR) -w $(THISDIR) espressif/idf
|
||||||
CMD ?= idf.py -DCFLAGS_EXTRA="$(CFLAGS_EXTRA)" build
|
ARCH ?= esp32
|
||||||
PORT ?= /dev/ttyUSB0
|
PORT ?= /dev/ttyUSB0
|
||||||
|
|
||||||
CFLAGS_EXTRA ?= -DMG_TLS=MG_TLS_BUILTIN
|
|
||||||
|
|
||||||
all: example
|
all: example
|
||||||
|
|
||||||
example:
|
example:
|
||||||
true
|
true
|
||||||
|
|
||||||
build: Makefile $(wildcard main/*)
|
build: Makefile $(wildcard main/*)
|
||||||
$(DOCKER) $(CMD)
|
test -d build || $(DOCKER) idf.py set-target $(ARCH)
|
||||||
|
$(DOCKER) idf.py build
|
||||||
|
|
||||||
flash:
|
flash:
|
||||||
flash: CMD = idf.py flash
|
cd build && esptool --chip $(ARCH) --baud 460800 --port $(PORT) write-flash @flash_args
|
||||||
flash: DA = --device $(PORT)
|
|
||||||
flash: build
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
test -d build && $(DOCKER) rm -rf build sdkconfig || true
|
test -d build && (rm -rf build sdkconfig || true)
|
||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
@@ -1,3 +1,16 @@
|
|||||||
#define MG_ARCH MG_ARCH_ESP32
|
#define MG_ARCH MG_ARCH_ESP32
|
||||||
|
|
||||||
|
// Crash reports and mem usage stats
|
||||||
|
// Make sure to add to sdkconfig.defaults the following:
|
||||||
|
// CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y
|
||||||
|
// CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=y
|
||||||
|
// CONFIG_ESP_COREDUMP_MAX_TASKS_NUM=16
|
||||||
|
// CONFIG_ESP_COREDUMP_STACK_SIZE=2048
|
||||||
|
// CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||||
#define MG_ENABLE_HEALTH 1
|
#define MG_ENABLE_HEALTH 1
|
||||||
|
|
||||||
|
// mdash.net device management support, https://mongoose.ws/docs/guides/mdash/
|
||||||
|
#define MG_ENABLE_MDASH 0
|
||||||
|
#define MG_MDASH_KEY "..."
|
||||||
|
#define MG_FIRMWARE_VERSION "esp32_min.1.0.0"
|
||||||
|
#define MG_TLS MG_TLS_BUILTIN
|
||||||
@@ -5,3 +5,5 @@ CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y
|
|||||||
CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=y
|
CONFIG_ESP_COREDUMP_DATA_FORMAT_ELF=y
|
||||||
CONFIG_ESP_COREDUMP_MAX_TASKS_NUM=16
|
CONFIG_ESP_COREDUMP_MAX_TASKS_NUM=16
|
||||||
CONFIG_ESP_COREDUMP_STACK_SIZE=2048
|
CONFIG_ESP_COREDUMP_STACK_SIZE=2048
|
||||||
|
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||||
|
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <stm32h5xx_hal.h>
|
||||||
#define MG_ARCH MG_ARCH_CUBE
|
#define MG_ARCH MG_ARCH_CUBE
|
||||||
#define MG_ENABLE_LFS 1
|
#define MG_ENABLE_LFS 1
|
||||||
#define MG_LFS_SIZE (128 * 1024)
|
#define MG_LFS_SIZE (128 * 1024)
|
||||||
@@ -73,6 +73,7 @@ extern uint32_t SystemCoreClock;
|
|||||||
#define configMAX_TASK_NAME_LEN ( 16 )
|
#define configMAX_TASK_NAME_LEN ( 16 )
|
||||||
#define configHEAP_CLEAR_MEMORY_ON_FREE 0
|
#define configHEAP_CLEAR_MEMORY_ON_FREE 0
|
||||||
#define configUSE_TRACE_FACILITY 1
|
#define configUSE_TRACE_FACILITY 1
|
||||||
|
#define configRECORD_STACK_HIGH_ADDRESS 1
|
||||||
#define configUSE_16_BIT_TICKS 0
|
#define configUSE_16_BIT_TICKS 0
|
||||||
#define configUSE_MUTEXES 1
|
#define configUSE_MUTEXES 1
|
||||||
#define configQUEUE_REGISTRY_SIZE 8
|
#define configQUEUE_REGISTRY_SIZE 8
|
||||||
|
|||||||
@@ -104,8 +104,7 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||||
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
|
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
|
||||||
mg_http_reply(c, 200, "", "{%m:%lu}\n", MG_ESC("tick"), HAL_GetTick());
|
mg_http_reply(c, 200, "", "{%m:%lu}\n", MG_ESC("tick"), HAL_GetTick());
|
||||||
} else if (mg_match(hm->uri, mg_str("/api/kill"), NULL)) {
|
} else if (mg_match(hm->uri, mg_str("/api/crash"), NULL)) {
|
||||||
SCB->SHCSR &= ~SCB_SHCSR_USGFAULTENA_Msk;
|
|
||||||
__asm volatile("udf #0");
|
__asm volatile("udf #0");
|
||||||
} else {
|
} else {
|
||||||
mg_http_reply(c, 200, "Content-Type: text/plain\r\n",
|
mg_http_reply(c, 200, "Content-Type: text/plain\r\n",
|
||||||
@@ -446,6 +445,11 @@ void StartDefaultTask(void *argument)
|
|||||||
mg_mgr_init(&mgr);
|
mg_mgr_init(&mgr);
|
||||||
mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL);
|
mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL);
|
||||||
|
|
||||||
|
MG_INFO(("Initialised. CPU clock: %lu MHz", SystemCoreClock / 1000000));
|
||||||
|
MG_INFO(("Memory: %M", mg_print_mem_stats));
|
||||||
|
MG_INFO(("Crash record: %s, %M", mg_health_valid() ? "enabled" : "disabled",
|
||||||
|
mg_print_crash_record));
|
||||||
|
|
||||||
/* Infinite loop */
|
/* Infinite loop */
|
||||||
for(;;)
|
for(;;)
|
||||||
{
|
{
|
||||||
|
|||||||
Vendored
+7
@@ -328,6 +328,13 @@ typedef struct tskTaskControlBlock /* The old naming convention is used to
|
|||||||
* below to enable the use of older kernel aware debuggers. */
|
* below to enable the use of older kernel aware debuggers. */
|
||||||
typedef tskTCB TCB_t;
|
typedef tskTCB TCB_t;
|
||||||
|
|
||||||
|
#if ( configRECORD_STACK_HIGH_ADDRESS == 1 )
|
||||||
|
_Static_assert( offsetof( TCB_t, pcTaskName ) == offsetof( StaticTask_t, ucDummy7 ),
|
||||||
|
"StaticTask_t task-name offset must match TCB_t" );
|
||||||
|
_Static_assert( offsetof( TCB_t, pxEndOfStack ) == offsetof( StaticTask_t, pxDummy8 ),
|
||||||
|
"StaticTask_t stack-end offset must match TCB_t" );
|
||||||
|
#endif
|
||||||
|
|
||||||
/*lint -save -e956 A manual analysis and inspection has been used to determine
|
/*lint -save -e956 A manual analysis and inspection has been used to determine
|
||||||
* which static variables must be declared volatile. */
|
* which static variables must be declared volatile. */
|
||||||
portDONT_DISCARD PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB = NULL;
|
portDONT_DISCARD PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB = NULL;
|
||||||
|
|||||||
@@ -254,4 +254,6 @@ SECTIONS
|
|||||||
libgcc.a:* ( * )
|
libgcc.a:* ( * )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Retained health record. Startup code must neither copy nor clear it. */
|
||||||
|
.mg_health (NOLOAD) : { KEEP(*(.mg_health .mg_health*)) } > RAM_D3
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#define MG_ARCH MG_ARCH_CUBE
|
#define MG_ARCH MG_ARCH_CUBE
|
||||||
#define MG_ENABLE_HEALTH 1
|
#define MG_ENABLE_HEALTH 1 // Mem stats and crash reports
|
||||||
#define MG_ENABLE_FREERTOS 1
|
#define MG_ENABLE_FREERTOS 1 // Set configRECORD_STACK_HIGH_ADDRESS
|
||||||
|
|
||||||
|
#define MG_ENABLE_MDASH 0 // mdash.net device management service support
|
||||||
|
#define MG_MDASH_KEY "..." // See https://mongoose.ws/docs/guides/mdash/
|
||||||
|
#define MG_FIRMWARE_VERSION "h723_cube.1.0.0"
|
||||||
|
|
||||||
// Ethernet driver tunables:
|
// Ethernet driver tunables:
|
||||||
// #define MG_DRIVER_MDC_CR 4 // RMII MDC clock divider, from 0 to 5
|
// #define MG_DRIVER_MDC_CR 4 // RMII MDC clock divider, from 0 to 5
|
||||||
|
|||||||
@@ -86,22 +86,24 @@ static void MX_USART3_UART_Init(void);
|
|||||||
/* USER CODE BEGIN 0 */
|
/* USER CODE BEGIN 0 */
|
||||||
size_t mg_usage(int task, int metric) {
|
size_t mg_usage(int task, int metric) {
|
||||||
extern void *_sbrk(ptrdiff_t incr);
|
extern void *_sbrk(ptrdiff_t incr);
|
||||||
extern unsigned char _end, _sstack;
|
extern unsigned char _end, _sstack, _estack;
|
||||||
extern uint32_t _Min_Stack_Size;
|
extern uint32_t _Min_Stack_Size;
|
||||||
switch (metric) {
|
switch (metric) {
|
||||||
case MG_USAGE_NUM_TASKS: return 1;
|
case MG_USAGE_NUM_TASKS: return 1;
|
||||||
case MG_USAGE_TASK_NAME: return task == 0 ? (size_t) "main" : 0;
|
case MG_USAGE_TASK_NAME: return task == 0 ? (size_t) "main" : 0;
|
||||||
case MG_USAGE_STACK_TOTAL:
|
case MG_USAGE_STACK_TOTAL:
|
||||||
return task == 0 ? (size_t) &_Min_Stack_Size : 0;
|
return task == 0 ? (size_t) &_Min_Stack_Size : 0;
|
||||||
case MG_USAGE_STACK_FREE: {
|
case MG_USAGE_STACK_USED: {
|
||||||
size_t sp = (size_t) __get_MSP(), base = (size_t) &_sstack;
|
size_t sp = (size_t) __get_MSP();
|
||||||
return task == 0 && sp > base ? sp - base : 0;
|
size_t base = (size_t) &_sstack, top = (size_t) &_estack;
|
||||||
|
if (task != 0 || sp > top) return 0;
|
||||||
|
return top - (sp < base ? base : sp);
|
||||||
}
|
}
|
||||||
case MG_USAGE_HEAP_TOTAL:
|
case MG_USAGE_HEAP_TOTAL:
|
||||||
return (size_t) &_sstack - (size_t) &_end;
|
return (size_t) &_sstack - (size_t) &_end;
|
||||||
case MG_USAGE_HEAP_FREE: {
|
case MG_USAGE_HEAP_USED: {
|
||||||
size_t heap = (size_t) _sbrk(0), limit = (size_t) &_sstack;
|
size_t heap = (size_t) _sbrk(0), start = (size_t) &_end;
|
||||||
return limit > heap ? limit - heap : 0;
|
return heap >= start && heap <= (size_t) &_sstack ? heap - start : 0;
|
||||||
}
|
}
|
||||||
default: return 0;
|
default: return 0;
|
||||||
}
|
}
|
||||||
@@ -121,8 +123,7 @@ static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
|||||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||||
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
|
if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) {
|
||||||
mg_http_reply(c, 200, "", "{%m:%lu}\n", MG_ESC("tick"), HAL_GetTick());
|
mg_http_reply(c, 200, "", "{%m:%lu}\n", MG_ESC("tick"), HAL_GetTick());
|
||||||
} else if (mg_match(hm->uri, mg_str("/api/kill"), NULL)) {
|
} else if (mg_match(hm->uri, mg_str("/api/crash"), NULL)) {
|
||||||
SCB->SHCSR &= ~SCB_SHCSR_USGFAULTENA_Msk;
|
|
||||||
__asm volatile("udf #0");
|
__asm volatile("udf #0");
|
||||||
} else {
|
} else {
|
||||||
mg_http_reply(c, 200, "", "Hi from Mongoose, tick %lu\n%M\n",
|
mg_http_reply(c, 200, "", "Hi from Mongoose, tick %lu\n%M\n",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <stm32h7xx_hal.h>
|
||||||
#define MG_ARCH MG_ARCH_CUBE
|
#define MG_ARCH MG_ARCH_CUBE
|
||||||
#define MG_ENABLE_HEALTH 1
|
#define MG_ENABLE_HEALTH 1
|
||||||
#define MG_ENABLE_CUSTOM_USAGE 1
|
#define MG_ENABLE_CUSTOM_USAGE 1
|
||||||
|
|||||||
@@ -6,13 +6,17 @@
|
|||||||
|
|
||||||
size_t mg_usage(int task, int metric) {
|
size_t mg_usage(int task, int metric) {
|
||||||
extern void *_sbrk(int incr);
|
extern void *_sbrk(int incr);
|
||||||
|
extern unsigned char _end[], _estack[];
|
||||||
if (metric == MG_USAGE_NUM_TASKS) return 1;
|
if (metric == MG_USAGE_NUM_TASKS) return 1;
|
||||||
if (metric == MG_USAGE_TASK_NAME) return task == 0 ? (size_t) "main" : 0;
|
if (metric == MG_USAGE_TASK_NAME) return task == 0 ? (size_t) "main" : 0;
|
||||||
// Heap and stack share the free gap, neither has a fixed total
|
// Heap and stack share RAM, so neither has a fixed total.
|
||||||
if (metric == MG_USAGE_HEAP_FREE ||
|
if (metric == MG_USAGE_HEAP_USED) {
|
||||||
(metric == MG_USAGE_STACK_FREE && task == 0)) {
|
size_t heap = (size_t) _sbrk(0), start = (size_t) _end;
|
||||||
size_t sp = (size_t) __get_MSP(), heap = (size_t) _sbrk(0);
|
return heap >= start && heap <= (size_t) _estack ? heap - start : 0;
|
||||||
return sp > heap ? sp - heap : 0;
|
}
|
||||||
|
if (metric == MG_USAGE_STACK_USED && task == 0) {
|
||||||
|
size_t sp = (size_t) __get_MSP(), top = (size_t) _estack;
|
||||||
|
return sp <= top ? top - sp : 0;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user