From 2e3d5e54a803718331a998884cfff29174dffce0 Mon Sep 17 00:00:00 2001 From: Memfault Inc Date: Thu, 9 Nov 2023 18:01:37 +0000 Subject: [PATCH] Memfault Firmware SDK 1.4.3 (Build 4437) --- CHANGES.md | 74 ++++++++++++++++++- VERSION | 6 +- components/core/src/memfault_log.c | 11 +++ .../demo/src/memfault_demo_shell_commands.c | 7 ++ .../demo/src/panics/memfault_demo_panics.c | 19 +++++ .../include/memfault/core/platform/core.h | 7 +- components/include/memfault/demo/cli.h | 6 ++ components/include/memfault/metrics/metrics.h | 14 ++++ components/include/memfault/version.h | 4 +- components/metrics/src/memfault_metrics.c | 1 + .../panics/src/memfault_fault_handling_arm.c | 15 +++- .../apps/memfault_demo_app/CMakeLists.txt | 2 + .../apps/memfault_demo_app/main/cmd_app.c | 4 + examples/freertos/FreeRTOSConfig.h | 1 + .../qemu_mps2_an385/memfault_platform_impl.c | 1 + .../memfault/memfault_platform_config.h | 2 + .../memfault/components/metrics | 1 + .../memfault_platform_config.h | 2 + .../memfault_platform_core.c | 9 ++- ports/esp_idf/memfault/CMakeLists.txt | 7 +- .../common/memfault_platform_coredump.c | 8 +- .../common/memfault_platform_metrics.c | 21 ++++++ .../memfault_esp_metrics_heartbeat_config.def | 1 + .../src/memfault_freertos_ram_regions.c | 64 ++++++++++++++++ ports/zephyr/Kconfig | 66 +++++++++-------- ports/zephyr/common/memfault_demo_cli.c | 2 +- scripts/memfault_gdb.py | 22 +++--- scripts/tests_embedded_scripts/gdb_fake.py | 6 +- .../test_memfault_gdb.py | 2 +- tasks/macos_ftdi.py | 2 +- tests/src/test_memfault_heartbeat_metrics.cpp | 65 ++++++++++++++++ 31 files changed, 388 insertions(+), 64 deletions(-) create mode 120000 examples/mbed/apps/memfault_demo_app/memfault/components/metrics diff --git a/CHANGES.md b/CHANGES.md index 85b98a034..96a2c950e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,77 @@ # Memfault Firmware SDK Changelog +## [1.4.3] - 2023-11-08 + +### :rocket: New Features + +- General: + + - Add a new streamlined Metrics setter API: + + - `MEMFAULT_HEARTBEAT_SET_SIGNED(key_name, signed_value)` + - `MEMFAULT_HEARTBEAT_SET_UNSIGNED(key_name, unsigned_value)` + - `MEMFAULT_HEARTBEAT_SET_STRING(key_name, value)` + - `MEMFAULT_HEARTBEAT_TIMER_START(key_name)` + - `MEMFAULT_HEARTBEAT_TIMER_STOP(key_name)` + - `MEMFAULT_HEARTBEAT_ADD(key_name, amount)` + + These APIs can be used in place of the original APIs: + + - `memfault_metrics_heartbeat_set_signed(MEMFAULT_METRICS_KEY(key_name), signed_value)` + - `memfault_metrics_heartbeat_set_unsigned(MEMFAULT_METRICS_KEY(key_name), unsigned_value)` + - `memfault_metrics_heartbeat_set_string(MEMFAULT_METRICS_KEY(key_name), value)` + - `memfault_metrics_heartbeat_timer_start(MEMFAULT_METRICS_KEY(key_name))` + - `memfault_metrics_heartbeat_timer_stop(MEMFAULT_METRICS_KEY(key_name))` + - `memfault_metrics_heartbeat_add(MEMFAULT_METRICS_KEY(key_name), amount)` + + Saving some typing! + + - Add the ability to compute FreeRTOS task stack high watermarks when storing + coredumps. This is useful only if the entire RAM (`.data` + `.bss`) cannot + be saved in the coredump. The feature is opt-in with the config flag + `#define MEMFAULT_COREDUMP_COMPUTE_THREAD_STACK_USAGE 1`. + + - Add a `heartbeat` command to the [core demo cli](components/demo). This + behaves the same as the commands of the same name already present in the + Zephyr + ESP-IDF port. + + - Add a `test_cassert` command to the core demo cli. This command executes a C + stdlib ```assert(0)` call. For platforms that do not implement a + `assert()` handler, a config flag `MEMFAULT_DEMO_DISABLE_CASSERT` can be + defined to `0` to disable the command. + +- ESP-IDF: + + - Add a new out-of-box metric, `wifi_ap_oui`, which will record the associated + AP's Organizationally Unique Identifier (OUI) in the Memfault heartbeat. + +### :chart_with_upwards_trend: Improvements + +- General: + + - Disable a warning emitted by the ARM C Compiler v5 + (`#188-D: enumerated type mixed with another type`) when initializing a + structure in + [`components/core/src/memfault_log.c`:314](https://github.com/memfault/memfault-firmware-sdk/blob/1.4.3/components/core/src/memfault_log.c#L313). + + - Improve the quality of Assert backtraces when using the ARM C Compiler v5. + Certain frames in the assert call stack were missing link register + inforation, due to compiler optimizations based on the `noreturn` and + unreachable compiler hints. These hints have been removed for `armcc`, which + should permit full stack unwinding for Assert coredumps generated from + builds on that toolchain. + + - Perform an update of the timer when calling the + `memfault_metrics_heartbeat_timer_read()` debug function. Fixes + [#65](https://github.com/memfault/memfault-firmware-sdk/pull/65). Thanks to + @LuskeyNoah for providing this fix! + +- ESP-IDF: + + - Fix a missing piece enabling the "zero-config" integration (originally added + in `1.4.0`)- the `memfault_platform_port.h` file was still incorrectly + required. This is now fixed. + ## [1.4.2] - 2023-11-02 ### :chart_with_upwards_trend: Improvements @@ -80,7 +152,7 @@ - Add a new Kconfig flag, `MEMFAULT_COREDUMP_STORAGE_MAX_SIZE`, which can be used to set the Memfault SDK's built-in - [ESP-IDF coredump storage implementation](sdk/embedded/ports/esp_idf/memfault/common/memfault_platform_coredump.c) + [ESP-IDF coredump storage implementation](https://github.com/memfault/memfault-firmware-sdk/blob/master/ports/esp_idf/memfault/common/memfault_platform_coredump.c) to artificially limit the maximum coredump storage size. This is useful for situations where the default `memfault_platform_coredump_get_regions()` function is still desirable, but the coredump maximum size needs to be diff --git a/VERSION b/VERSION index 920e865e5..4c04cfd40 100644 --- a/VERSION +++ b/VERSION @@ -1,3 +1,3 @@ -BUILD ID: 4292 -GIT COMMIT: 7f46de743 -VERSION: 1.4.2 +BUILD ID: 4437 +GIT COMMIT: 3453c1402 +VERSION: 1.4.3 diff --git a/components/core/src/memfault_log.c b/components/core/src/memfault_log.c index eccf8b50b..196b4acb0 100644 --- a/components/core/src/memfault_log.c +++ b/components/core/src/memfault_log.c @@ -314,10 +314,21 @@ void memfault_log_export_logs(void) { #pragma diag_push #pragma diag_remark 190 #endif + #if defined(__CC_ARM) + #pragma push + // This armcc diagnostic is technically violating the C standard, which + // _explicitly_ requires enums to be type-equivalent to ints. See ISO/IEC + // 9899:TC3 6.2.5.16, and specifically 6.4.4.3, which states: "An + // identifier declared as an enumeration constant has type int." + #pragma diag_suppress 188 // enumerated type mixed with another type + #endif sMemfaultLog log = {0}; #if defined(__TI_ARM__) #pragma diag_pop #endif + #if defined(__CC_ARM) + #pragma pop + #endif const bool log_found = memfault_log_read(&log); if (!log_found) { diff --git a/components/demo/src/memfault_demo_shell_commands.c b/components/demo/src/memfault_demo_shell_commands.c index e8d50d776..3871fb793 100644 --- a/components/demo/src/memfault_demo_shell_commands.c +++ b/components/demo/src/memfault_demo_shell_commands.c @@ -47,6 +47,11 @@ int memfault_demo_cli_cmd_heartbeat_dump(MEMFAULT_UNUSED int argc, MEMFAULT_UNUS return 0; } +int memfault_demo_cli_cmd_heartbeat(MEMFAULT_UNUSED int argc, MEMFAULT_UNUSED char *argv[]) { + memfault_metrics_heartbeat_debug_trigger(); + return 0; +} + static const sMemfaultShellCommand s_memfault_shell_commands[] = { {"clear_core", memfault_demo_cli_cmd_clear_core, "Clear an existing coredump"}, {"drain_chunks", memfault_demo_drain_chunk_data, @@ -58,11 +63,13 @@ static const sMemfaultShellCommand s_memfault_shell_commands[] = { {"coredump_size", memfault_demo_cli_cmd_coredump_size, "Print the coredump storage capacity"}, {"heartbeat_dump", memfault_demo_cli_cmd_heartbeat_dump, "Dump current Memfault metrics heartbeat state"}, + {"heartbeat", memfault_demo_cli_cmd_heartbeat, "Trigger a heartbeat"}, // // Test commands for validating SDK functionality: https://mflt.io/mcu-test-commands // {"test_assert", memfault_demo_cli_cmd_assert, "Trigger memfault assert"}, + {"test_cassert", memfault_demo_cli_cmd_cassert, "Trigger C assert"}, #if MEMFAULT_COMPILER_ARM_CORTEX_M {"test_busfault", memfault_demo_cli_cmd_busfault, "Trigger a busfault"}, diff --git a/components/demo/src/panics/memfault_demo_panics.c b/components/demo/src/panics/memfault_demo_panics.c index e93c9bb9e..7b598806e 100644 --- a/components/demo/src/panics/memfault_demo_panics.c +++ b/components/demo/src/panics/memfault_demo_panics.c @@ -22,6 +22,13 @@ #include "memfault/panics/platform/coredump.h" #include "memfault_demo_cli_aux_private.h" +// Allow opting out of the cassert demo. Some platforms may not have a libc +// assert implementation. +#if !defined(MEMFAULT_DEMO_DISABLE_CASSERT) + #include + #define MEMFAULT_DEMO_DISABLE_CASSERT 0 +#endif + MEMFAULT_NO_OPT static void do_some_work_base(char *argv[]) { // An assert that is guaranteed to fail. We perform @@ -131,6 +138,18 @@ int memfault_demo_cli_cmd_assert(int argc, char *argv[]) { return -1; } +int memfault_demo_cli_cmd_cassert(int argc, char *argv[]) { + (void)argc, (void)argv; + +#if MEMFAULT_DEMO_DISABLE_CASSERT + MEMFAULT_LOG_ERROR("C assert demo disabled"); +#else + assert(0); +#endif + + return -1; +} + #if MEMFAULT_COMPILER_ARM_CORTEX_M int memfault_demo_cli_cmd_hardfault(MEMFAULT_UNUSED int argc, MEMFAULT_UNUSED char *argv[]) { diff --git a/components/include/memfault/core/platform/core.h b/components/include/memfault/core/platform/core.h index f6f60a05b..b9079b7b7 100644 --- a/components/include/memfault/core/platform/core.h +++ b/components/include/memfault/core/platform/core.h @@ -33,9 +33,10 @@ int memfault_platform_boot(void); //! Invoked after memfault fault handling has run. //! //! The platform should do any final cleanup and reboot the system -#if defined(__ICCARM__) -//! IAR will optimize away link register stores from callsites which makes it -//! impossible for a reliable backtrace to be resolved so we don't use the NORETURN attribute +#if defined(__ICCARM__) || defined(__CC_ARM) +//! IAR and armcc will optimize away link register stores from callsites, which +//! makes it impossible for a reliable backtrace to be resolved, so for those +//! compilers don't apply the NORETURN attribute #else MEMFAULT_NORETURN #endif diff --git a/components/include/memfault/demo/cli.h b/components/include/memfault/demo/cli.h index 70e7d4dd7..413f71a42 100644 --- a/components/include/memfault/demo/cli.h +++ b/components/include/memfault/demo/cli.h @@ -57,6 +57,9 @@ int memfault_demo_cli_cmd_prefetchabort(int argc, char *argv[]); //! Command which will generate an assert int memfault_demo_cli_cmd_assert(int argc, char *argv[]); +//! Command which will generate a libc assert() +int memfault_demo_cli_cmd_cassert(int argc, char *argv[]); + //! Command to exercise the MEMFAULT_TRACE_EVENT API, capturing a //! Trace Event with the error reason set to "MemfaultDemoCli_Error". int memfault_demo_cli_cmd_trace_event_capture(int argc, char *argv[]); @@ -114,6 +117,9 @@ int memfault_demo_cli_cmd_export(int argc, char *argv[]); //! Print current heartbeat metrics int memfault_demo_cli_cmd_heartbeat_dump(int argc, char *argv[]); +//! Trigger a heartbeat +int memfault_demo_cli_cmd_heartbeat(int argc, char *argv[]); + #ifdef __cplusplus } #endif diff --git a/components/include/memfault/metrics/metrics.h b/components/include/memfault/metrics/metrics.h index 9b533e5a9..27d35df78 100644 --- a/components/include/memfault/metrics/metrics.h +++ b/components/include/memfault/metrics/metrics.h @@ -171,6 +171,20 @@ int memfault_metrics_heartbeat_timer_stop(MemfaultMetricId key); //! @note The metric must be of type kMemfaultMetricType_Unsigned or kMemfaultMetricType_Signed int memfault_metrics_heartbeat_add(MemfaultMetricId key, int32_t amount); +//! Alternate API that includes the 'MEMFAULT_METRICS_KEY()' expansion +#define MEMFAULT_HEARTBEAT_SET_SIGNED(key_name, signed_value) \ + memfault_metrics_heartbeat_set_signed(MEMFAULT_METRICS_KEY(key_name), signed_value) +#define MEMFAULT_HEARTBEAT_SET_UNSIGNED(key_name, unsigned_value) \ + memfault_metrics_heartbeat_set_unsigned(MEMFAULT_METRICS_KEY(key_name), unsigned_value) +#define MEMFAULT_HEARTBEAT_SET_STRING(key_name, value) \ + memfault_metrics_heartbeat_set_string(MEMFAULT_METRICS_KEY(key_name), value) +#define MEMFAULT_HEARTBEAT_TIMER_START(key_name) \ + memfault_metrics_heartbeat_timer_start(MEMFAULT_METRICS_KEY(key_name)) +#define MEMFAULT_HEARTBEAT_TIMER_STOP(key_name) \ + memfault_metrics_heartbeat_timer_stop(MEMFAULT_METRICS_KEY(key_name)) +#define MEMFAULT_HEARTBEAT_ADD(key_name, amount) \ + memfault_metrics_heartbeat_add(MEMFAULT_METRICS_KEY(key_name), amount) + //! For debugging purposes: prints the current heartbeat values using //! MEMFAULT_LOG_DEBUG(). Before printing, any active timer values are computed. //! Other metrics will print the current values. This can be called from the diff --git a/components/include/memfault/version.h b/components/include/memfault/version.h index 44dcc2647..6dd257dd2 100644 --- a/components/include/memfault/version.h +++ b/components/include/memfault/version.h @@ -19,8 +19,8 @@ typedef struct { uint8_t patch; } sMfltSdkVersion; -#define MEMFAULT_SDK_VERSION { .major = 1, .minor = 4, .patch = 2 } -#define MEMFAULT_SDK_VERSION_STR "1.4.2" +#define MEMFAULT_SDK_VERSION { .major = 1, .minor = 4, .patch = 3 } +#define MEMFAULT_SDK_VERSION_STR "1.4.3" #ifdef __cplusplus } diff --git a/components/metrics/src/memfault_metrics.c b/components/metrics/src/memfault_metrics.c index 1e78d12e6..d38e45c0d 100644 --- a/components/metrics/src/memfault_metrics.c +++ b/components/metrics/src/memfault_metrics.c @@ -728,6 +728,7 @@ int memfault_metrics_heartbeat_timer_read(MemfaultMetricId key, uint32_t *read_v memfault_lock(); { union MemfaultMetricValue *value; + prv_find_timer_metric_and_update(key, kMemfaultTimerOp_ForceValueUpdate); rv = prv_find_key_of_type(key, kMemfaultMetricType_Timer, &value); if (rv == 0) { *read_val = value->u32; diff --git a/components/panics/src/memfault_fault_handling_arm.c b/components/panics/src/memfault_fault_handling_arm.c index c72fd3058..2834bf8a2 100644 --- a/components/panics/src/memfault_fault_handling_arm.c +++ b/components/panics/src/memfault_fault_handling_arm.c @@ -561,6 +561,13 @@ static void prv_fault_handling_assert(void *pc, void *lr, eMemfaultRebootReason memfault_platform_reboot(); } + // ARM compiler 5 requires even more explicit no optimization directives to prevent our assert + // functions from optimizing away the callee saved registers. + #if defined(__CC_ARM) + #pragma push + #pragma optimize O0 + #endif + // Note: These functions are annotated as "noreturn" which can be useful for static analysis. // However, this can also lead to compiler optimizations that make recovering local variables // difficult (such as ignoring ABI requirements to preserve callee-saved registers) @@ -568,7 +575,7 @@ MEMFAULT_NO_OPT void memfault_fault_handling_assert(void *pc, void *lr) { prv_fault_handling_assert(pc, lr, kMfltRebootReason_Assert); -#if (defined(__clang__) && defined(__ti__)) +#if (defined(__clang__) && defined(__ti__)) || defined(__CC_ARM) //! tiarmclang does not respect the no optimization request and will //! strip the pushing callee saved registers making it impossible to recover //! an accurate backtrace so we skip over providing the unreachable hint. @@ -580,11 +587,15 @@ MEMFAULT_NO_OPT void memfault_fault_handling_assert_extra(void *pc, void *lr, sMemfaultAssertInfo *extra_info) { prv_fault_handling_assert(pc, lr, extra_info->assert_reason); -#if (defined(__clang__) && defined(__ti__)) +#if (defined(__clang__) && defined(__ti__)) || defined(__CC_ARM) //! See comment in memfault_fault_handling_assert for more context #else MEMFAULT_UNREACHABLE; #endif } + #if defined(__CC_ARM) + #pragma pop + #endif + #endif /* MEMFAULT_COMPILER_ARM_CORTEX_M */ diff --git a/examples/esp32/apps/memfault_demo_app/CMakeLists.txt b/examples/esp32/apps/memfault_demo_app/CMakeLists.txt index bbbdc4680..4b24fc920 100644 --- a/examples/esp32/apps/memfault_demo_app/CMakeLists.txt +++ b/examples/esp32/apps/memfault_demo_app/CMakeLists.txt @@ -59,6 +59,8 @@ add_custom_command(TARGET ${IDF_PROJECT_EXECUTABLE} COMMAND python ${memfault_firmare_sdk_dir}/scripts/fw_build_id.py ${IDF_PROJECT_EXECUTABLE} # Save a copy of the ELF that includes the 'log_fmt' section BYPRODUCTS ${IDF_PROJECT_EXECUTABLE}.memfault_log_fmt + # Compress debug sections; this reduces the elf file size from ~10MB -> ~4.8MB + COMMAND ${CMAKE_OBJCOPY} --compress-debug-sections ${IDF_PROJECT_EXECUTABLE} COMMAND ${CMAKE_COMMAND} -E copy ${IDF_PROJECT_EXECUTABLE} ${IDF_PROJECT_EXECUTABLE}.memfault_log_fmt COMMAND ${CMAKE_COMMAND} -E echo "*** NOTE: the symbol file to upload to app.memfault.com is ${IDF_PROJECT_EXECUTABLE}.memfault_log_fmt ***" # Remove the 'log_fmt' compact log section, which confuses elf2image diff --git a/examples/esp32/apps/memfault_demo_app/main/cmd_app.c b/examples/esp32/apps/memfault_demo_app/main/cmd_app.c index 60e7734f6..f93829aa3 100644 --- a/examples/esp32/apps/memfault_demo_app/main/cmd_app.c +++ b/examples/esp32/apps/memfault_demo_app/main/cmd_app.c @@ -16,6 +16,7 @@ #include "freertos/semphr.h" #include "sdkconfig.h" +#if MEMFAULT_TASK_WATCHDOG_ENABLE //! cause the task watchdog to fire by deadlocking the example task static int test_task_watchdog(int argc, char** argv) { (void)argc, (void)argv; @@ -25,8 +26,10 @@ static int test_task_watchdog(int argc, char** argv) { return 0; } +#endif void register_app(void) { +#if MEMFAULT_TASK_WATCHDOG_ENABLE const esp_console_cmd_t test_watchdog_cmd = { .command = "test_task_watchdog", .help = "Demonstrate the task watchdog tripping on a deadlock", @@ -34,4 +37,5 @@ void register_app(void) { .func = &test_task_watchdog, }; ESP_ERROR_CHECK(esp_console_cmd_register(&test_watchdog_cmd)); +#endif } diff --git a/examples/freertos/FreeRTOSConfig.h b/examples/freertos/FreeRTOSConfig.h index e053c6163..c573f806f 100644 --- a/examples/freertos/FreeRTOSConfig.h +++ b/examples/freertos/FreeRTOSConfig.h @@ -81,6 +81,7 @@ extern void vAssertCalled(const char* file, int line); #define configUSE_RECURSIVE_MUTEXES 1 #define configUSE_TIMERS 1 #define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE * 2) +#define configRECORD_STACK_HIGH_ADDRESS 1 #define configGENERATE_RUN_TIME_STATS 1 #define configUSE_TRACE_FACILITY 1 diff --git a/examples/freertos/boards/qemu_mps2_an385/memfault_platform_impl.c b/examples/freertos/boards/qemu_mps2_an385/memfault_platform_impl.c index e1fcb6558..dba7a84f4 100644 --- a/examples/freertos/boards/qemu_mps2_an385/memfault_platform_impl.c +++ b/examples/freertos/boards/qemu_mps2_an385/memfault_platform_impl.c @@ -58,6 +58,7 @@ static sMfltCoredumpRegion s_coredump_regions[MEMFAULT_COREDUMP_MAX_TASK_REGIONS 2 /* active stack(s) */ + 1 /* _kernel variable */ + 1 /* __memfault_capture_start */ + + 2 /* s_task_tcbs + s_task_watermarks */ ]; extern uint32_t __memfault_capture_bss_end; diff --git a/examples/freertos/memfault/memfault_platform_config.h b/examples/freertos/memfault/memfault_platform_config.h index acf039aba..2f99e70ed 100644 --- a/examples/freertos/memfault/memfault_platform_config.h +++ b/examples/freertos/memfault/memfault_platform_config.h @@ -15,3 +15,5 @@ #define MEMFAULT_COREDUMP_HEAP_STATS_LOCK_ENABLE 0 #define MEMFAULT_METRICS_HEARTBEAT_INTERVAL_SECS 60 #define MEMFAULT_COMPACT_LOG_ENABLE 1 + +#define MEMFAULT_COREDUMP_COMPUTE_THREAD_STACK_USAGE 1 diff --git a/examples/mbed/apps/memfault_demo_app/memfault/components/metrics b/examples/mbed/apps/memfault_demo_app/memfault/components/metrics new file mode 120000 index 000000000..b73d03ae6 --- /dev/null +++ b/examples/mbed/apps/memfault_demo_app/memfault/components/metrics @@ -0,0 +1 @@ +../../../../../../components/metrics/ \ No newline at end of file diff --git a/examples/mbed/apps/memfault_demo_app/memfault_platform_config.h b/examples/mbed/apps/memfault_demo_app/memfault_platform_config.h index fd8526453..8f40e076d 100644 --- a/examples/mbed/apps/memfault_demo_app/memfault_platform_config.h +++ b/examples/mbed/apps/memfault_demo_app/memfault_platform_config.h @@ -7,3 +7,5 @@ //! //! Platform overrides for the default configuration settings in the memfault-firmware-sdk. //! Default configuration settings can be found in "memfault/config.h" + +#define MEMFAULT_DEMO_DISABLE_CASSERT 1 diff --git a/examples/mbed/libraries/memfault/platform_reference_impl/memfault_platform_core.c b/examples/mbed/libraries/memfault/platform_reference_impl/memfault_platform_core.c index a954f2987..2ef70b236 100644 --- a/examples/mbed/libraries/memfault/platform_reference_impl/memfault_platform_core.c +++ b/examples/mbed/libraries/memfault/platform_reference_impl/memfault_platform_core.c @@ -4,8 +4,8 @@ //! See License.txt for details //! Reference implementation of the memfault platform header for Mbed #include "cmsis.h" - #include "memfault/core/compiler.h" +#include "os_tick.h" int memfault_platform_boot(void) { return 0; @@ -17,3 +17,10 @@ MEMFAULT_NORETURN void memfault_platform_reboot(void) { NVIC_SystemReset(); MEMFAULT_UNREACHABLE; } + +//! Example implementation. Re-define or verify this works for your platform. +uint64_t memfault_platform_get_time_since_boot_ms(void) { + const uint64_t tick_count = OS_Tick_GetCount(); + const uint32_t ticks_per_sec = OS_Tick_GetClock(); + return (1000 * tick_count) / ticks_per_sec; +} diff --git a/ports/esp_idf/memfault/CMakeLists.txt b/ports/esp_idf/memfault/CMakeLists.txt index 072a64048..0c7eecaaf 100644 --- a/ports/esp_idf/memfault/CMakeLists.txt +++ b/ports/esp_idf/memfault/CMakeLists.txt @@ -284,6 +284,11 @@ if(DEFINED ENV{IDF_VERSION}) # This method is an alternative to #include within FreeRTOSConfig.h which esp-idf # makes very difficult to do. get_filename_component(freertos_trace_header ${MEMFAULT_SDK_ROOT}/ports/include/memfault/ports/freertos_trace.h ABSOLUTE) - target_compile_options(${freertos_lib} INTERFACE -include ${freertos_trace_header}) + target_compile_options(${freertos_lib} INTERFACE + "-DMEMFAULT_METRICS_USER_HEARTBEAT_DEFS_FILE=\"memfault_esp_metrics_heartbeat_config.def\"" + "-DMEMFAULT_PLATFORM_CONFIG_FILE=\"memfault_esp_idf_port_config.h\"" + "-DMEMFAULT_TRACE_REASON_USER_DEFS_FILE=\"memfault_trace_reason_esp_idf_port_config.def\"" + -include ${freertos_trace_header} + ) endif() endif() diff --git a/ports/esp_idf/memfault/common/memfault_platform_coredump.c b/ports/esp_idf/memfault/common/memfault_platform_coredump.c index cb38593c5..497203708 100644 --- a/ports/esp_idf/memfault/common/memfault_platform_coredump.c +++ b/ports/esp_idf/memfault/common/memfault_platform_coredump.c @@ -67,9 +67,11 @@ #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 3) // Memory regions used for esp-idf >= 4.4.3 - // Active stack (1) + task/timer and bss/common regions (4) + - // freertos tasks (MEMFAULT_PLATFORM_MAX_TASK_REGIONS) + bss(1) + data(1) + heap(1) - #define MEMFAULT_ESP_PORT_NUM_REGIONS (1 + 4 + MEMFAULT_PLATFORM_MAX_TASK_REGIONS + 1 + 1 + 1) + //Active stack (1) + task/timer and bss/common regions (4) + freertos tasks + //(MEMFAULT_PLATFORM_MAX_TASK_REGIONS) + task_tcbs(1) + task_watermarks(1) + + //bss(1) + data(1) + heap(1) + #define MEMFAULT_ESP_PORT_NUM_REGIONS \ + (1 + 4 + MEMFAULT_PLATFORM_MAX_TASK_REGIONS + 1 + 1 + 1 + 1 + 1) #else // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 3) // Memory regions for esp-idf < 4.4.3 // Active stack (1) + bss(1) + data(1) + heap(1) diff --git a/ports/esp_idf/memfault/common/memfault_platform_metrics.c b/ports/esp_idf/memfault/common/memfault_platform_metrics.c index 38d6992bc..2b10113e5 100644 --- a/ports/esp_idf/memfault/common/memfault_platform_metrics.c +++ b/ports/esp_idf/memfault/common/memfault_platform_metrics.c @@ -9,6 +9,8 @@ //! by using the following CFLAG: //! -DMEMFAULT_METRICS_HEARTBEAT_INTERVAL_SECS=15 +#include + #include "esp_event.h" #include "esp_heap_caps.h" #include "esp_timer.h" @@ -107,6 +109,22 @@ static void prv_register_event_handler(void) { esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &metric_event_handler, NULL)); } +static void prv_collect_oui(void) { + wifi_ap_record_t ap_info; + static uint8_t s_memfault_ap_bssid[sizeof(ap_info.bssid)]; + esp_err_t err = esp_wifi_sta_get_ap_info(&ap_info); + if (err == ESP_OK) { + // only set the metric if the AP MAC changed + if (memcmp(s_memfault_ap_bssid, ap_info.bssid, sizeof(s_memfault_ap_bssid)) != 0) { + char oui[9]; + snprintf(oui, sizeof(oui), "%02x:%02x:%02x", ap_info.bssid[0], ap_info.bssid[1], + ap_info.bssid[2]); + memfault_metrics_heartbeat_set_string(MEMFAULT_METRICS_KEY(wifi_ap_oui), oui); + memcpy(s_memfault_ap_bssid, ap_info.bssid, sizeof(s_memfault_ap_bssid)); + } + } +} + static void prv_collect_wifi_metrics(void) { #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) if (s_min_rssi <= MAXIMUM_RSSI) { @@ -119,6 +137,9 @@ static void prv_collect_wifi_metrics(void) { s_min_rssi = 0; } #endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) + + // Collect AP OUI + prv_collect_oui(); } #endif // CONFIG_MEMFAULT_ESP_WIFI_METRICS diff --git a/ports/esp_idf/memfault/config/memfault_esp_metrics_heartbeat_config.def b/ports/esp_idf/memfault/config/memfault_esp_metrics_heartbeat_config.def index 8bdc1a097..76185ab6f 100644 --- a/ports/esp_idf/memfault/config/memfault_esp_metrics_heartbeat_config.def +++ b/ports/esp_idf/memfault/config/memfault_esp_metrics_heartbeat_config.def @@ -28,6 +28,7 @@ MEMFAULT_METRICS_KEY_DEFINE(wifi_connected_time_ms, kMemfaultMetricType_Timer) MEMFAULT_METRICS_KEY_DEFINE(wifi_sta_min_rssi, kMemfaultMetricType_Signed) #endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) MEMFAULT_METRICS_KEY_DEFINE(wifi_disconnect_count, kMemfaultMetricType_Unsigned) +MEMFAULT_METRICS_STRING_KEY_DEFINE(wifi_ap_oui, sizeof("00:00:00") - 1) #endif // CONFIG_MEMFAULT_ESP_WIFI_METRICS #if defined(CONFIG_MEMFAULT_USER_CONFIG_SILENT_FAIL) diff --git a/ports/freertos/src/memfault_freertos_ram_regions.c b/ports/freertos/src/memfault_freertos_ram_regions.c index 58b8b02fe..8d3a3cdfe 100644 --- a/ports/freertos/src/memfault_freertos_ram_regions.c +++ b/ports/freertos/src/memfault_freertos_ram_regions.c @@ -79,6 +79,8 @@ #include "memfault/ports/freertos_coredump.h" +#include + #include "memfault/config.h" #include "memfault/core/debug_log.h" #include "memfault/core/math.h" @@ -104,6 +106,28 @@ #error "'#include "memfault/ports/freertos_trace.h"' must be added to FreeRTOSConfig.h" #endif +// This feature is opt-in, since it can fail if the TCB data structures are +// corrupt, and results in a lost coredump. +#if !defined(MEMFAULT_COREDUMP_COMPUTE_THREAD_STACK_USAGE) + #define MEMFAULT_COREDUMP_COMPUTE_THREAD_STACK_USAGE 0 +#endif + +#if MEMFAULT_COREDUMP_COMPUTE_THREAD_STACK_USAGE && !INCLUDE_uxTaskGetStackHighWaterMark + #warning \ + MEMFAULT_COREDUMP_COMPUTE_THREAD_STACK_USAGE requires uxTaskGetStackHighWaterMark() API.\ + Add '#define INCLUDE_uxTaskGetStackHighWaterMark 1' to FreeRTOSConfig.h to enable it,\ + or set '#define MEMFAULT_COREDUMP_COMPUTE_THREAD_STACK_USAGE 0' in\ + memfault_platform_config.h to disable this warning. + #undef MEMFAULT_COREDUMP_COMPUTE_THREAD_STACK_USAGE + #define MEMFAULT_COREDUMP_COMPUTE_THREAD_STACK_USAGE 0 +#endif + +#if MEMFAULT_COREDUMP_COMPUTE_THREAD_STACK_USAGE +static struct MfltTaskWatermarks { + uint32_t high_watermark; +} s_memfault_task_watermarks[MEMFAULT_PLATFORM_MAX_TRACKED_TASKS]; +#endif + // If the MEMFAULT_PLATFORM_FREERTOS_TCB_SIZE value is set to 0, apply a default // to MEMFAULT_FREERTOS_TCB_SIZE here. This value can be overriden by setting // MEMFAULT_PLATFORM_FREERTOS_TCB_SIZE in memfault_platform_config.h. See @@ -182,6 +206,29 @@ void memfault_freertos_trace_task_delete(void *tcb) { s_task_tcbs[idx] = EMPTY_SLOT; } +#if MEMFAULT_COREDUMP_COMPUTE_THREAD_STACK_USAGE +static uint32_t prv_stack_bytes_high_watermark(void *tcb_address) { + // Note: ideally we would sanitize the pxTCB->pxStack value here (which is + // used when computing high watermark) to prevent a memory error. We could + // potentially scan in decrementing addresses from pxTopOfStack (full + // descending stack), looking for the first n run of 0xa5 bytes, and use that + // as the presumed "high watermark". We could then sanitize that access, so + // we'd be less likely to trip a memory error. For now, just rely on the + // earlier sanity check on pxTopOfStack being a valid address, and assume + // pxStack hasn't been corrupted in that case. + + // Note: uxTaskGetStackHighWaterMark is very simple, and just scans the + // stack for the first non 0xa5 byte. It's safe to call it from the fault + // handler context (no locks, no memory allocation, etc) + + // The value returned here is the high watermark, *NOT* the "bytes unused". + // Memfault's backend will convert it using the TCB_t stack size. We cannot + // access the TCB_t definition from this compilation unit as it is + // intentionally opaque in FreeRTOS, unfortunately. + return uxTaskGetStackHighWaterMark((TaskHandle_t)tcb_address); +} +#endif + size_t memfault_freertos_get_task_regions(sMfltCoredumpRegion *regions, size_t num_regions) { if (regions == NULL || num_regions == 0) { return 0; @@ -226,6 +273,10 @@ size_t memfault_freertos_get_task_regions(sMfltCoredumpRegion *regions, size_t n continue; } +#if MEMFAULT_COREDUMP_COMPUTE_THREAD_STACK_USAGE + s_memfault_task_watermarks[i].high_watermark = prv_stack_bytes_high_watermark(tcb_address); +#endif + regions[region_idx] = MEMFAULT_COREDUMP_MEMORY_REGION_INIT(top_of_stack, stack_size); region_idx++; if (region_idx == num_regions) { @@ -233,5 +284,18 @@ size_t memfault_freertos_get_task_regions(sMfltCoredumpRegion *regions, size_t n } } +#if MEMFAULT_COREDUMP_COMPUTE_THREAD_STACK_USAGE + // Store the task TCBs and watermarks, if there's free regions + if (region_idx < num_regions) { + regions[region_idx] = + MEMFAULT_COREDUMP_MEMORY_REGION_INIT(&s_task_tcbs[0], sizeof(s_task_tcbs)); + region_idx++; + + regions[region_idx] = + MEMFAULT_COREDUMP_MEMORY_REGION_INIT(&s_memfault_task_watermarks[0], sizeof(s_memfault_task_watermarks)); + region_idx++; + } +#endif + return region_idx; } diff --git a/ports/zephyr/Kconfig b/ports/zephyr/Kconfig index 7eafb4bb5..cead5d527 100644 --- a/ports/zephyr/Kconfig +++ b/ports/zephyr/Kconfig @@ -1,5 +1,5 @@ config MEMFAULT - bool "MEMFAULT Support" + bool "Memfault Support" default n depends on CPU_CORTEX_M select RUNTIME_NMI @@ -12,7 +12,7 @@ config MEMFAULT if MEMFAULT config MEMFAULT_CACHE_FAULT_REGS - bool "MEMFAULT Cache ARM fault registers" + bool "Memfault Cache ARM fault registers" default y help Save a copy of the ARMv7's fault registers before Zephyr @@ -40,6 +40,9 @@ config MEMFAULT_USER_CONFIG_SILENT_FAIL memfault_metrics_heartbeat_config.def memfault_trace_reason_user_config.def +# sub menu for coredump settings +menu "Memfault Coredump Settings" + config MEMFAULT_COREDUMP_STORAGE_CUSTOM bool default n @@ -48,14 +51,14 @@ config MEMFAULT_COREDUMP_STORAGE_CUSTOM coredump implementation using select. config MEMFAULT_RAM_BACKED_COREDUMP - bool "MEMFAULT Ram Backed Coredump" + bool "Memfault Ram Backed Coredump" default y if !MEMFAULT_COREDUMP_STORAGE_CUSTOM depends on !MEMFAULT_COREDUMP_STORAGE_CUSTOM help Save a minimal coredump in noinit RAM. config MEMFAULT_RAM_BACKED_COREDUMP_SIZE - int "MEMFAULT Ram Backed Coredump" + int "Memfault Ram Backed Coredump" default 8192 depends on MEMFAULT_RAM_BACKED_COREDUMP help @@ -107,6 +110,32 @@ config MEMFAULT_COREDUMP_COMPUTE_THREAD_STACK_USAGE depends on THREAD_STACK_INFO && !STACK_GROWS_UP && INIT_STACKS help Adds thread stack usage computed during fault handling into a coredump. +config MEMFAULT_COREDUMP_STACK_SIZE_TO_COLLECT + int "Maximum amount of bytes to collect for task" + default 256 + help + The larger the size, the more stack frames Memfault can recover for tasks. The + default setting typically allows for 4 or more frames to be recovered. + +config MEMFAULT_COREDUMP_FULL_THREAD_STACKS + bool "Collect full thread stacks in coredumps" + default n + depends on THREAD_STACK_INFO + help + When enabled, Memfault will collect the full thread stack in + coredumps. This will likely increase the size of coredumps, and there + will no longer be a strict ceiling on the coredump task region sizes. + This is provided as an alternative when capturing all of RAM is not + viable but full thread stack analysis (watermarking, stack overflow + tagging) is desired. + +config MEMFAULT_COREDUMP_MAX_TRACKED_TASKS + int "Maximum amount of tasks to collect in a coredump" + default 32 + help + The maximum amount of tasks Memfault will store state for in a coredump. + +endmenu # Memfault Coredump Settings config MEMFAULT_HEAP_STATS bool "Collect system heap stats with coredumps" @@ -119,7 +148,7 @@ config MEMFAULT_HEAP_STATS allocation/deallocation (k_malloc/k_free). config MEMFAULT_SHELL - bool "MEMFAULT Shell" + bool "Memfault Shell" default y if SHELL depends on SHELL help @@ -149,7 +178,7 @@ config MEMFAULT_COMPACT_LOG information here: https://mflt.io/compact-logs config MEMFAULT_LOGGING_ENABLE - bool "MEMFAULT Zephyr backend logging Enable [EXPERIMENTAL]" + bool "Memfault Zephyr backend logging Enable [EXPERIMENTAL]" default n select LOG select LOG_OUTPUT @@ -267,31 +296,6 @@ config MEMFAULT_EVENT_STORAGE_SIZE The storage area used to batch memfault events before they are flushed to persistent storage or the Memfault Cloud. -config MEMFAULT_COREDUMP_STACK_SIZE_TO_COLLECT - int "Maximum amount of bytes to collect for task" - default 256 - help - The larger the size, the more stack frames Memfault can recover for tasks. The - default setting typically allows for 4 or more frames to be recovered. - -config MEMFAULT_COREDUMP_FULL_THREAD_STACKS - bool "Collect full thread stacks in coredumps" - default n - depends on THREAD_STACK_INFO - help - When enabled, Memfault will collect the full thread stack in - coredumps. This will likely increase the size of coredumps, and there - will no longer be a strict ceiling on the coredump task region sizes. - This is provided as an alternative when capturing all of RAM is not - viable but full thread stack analysis (watermarking, stack overflow - tagging) is desired. - -config MEMFAULT_COREDUMP_MAX_TRACKED_TASKS - int "Maximum amount of tasks to collect in a coredump" - default 32 - help - The maximum amount of tasks Memfault will store state for in a coredump. - config MEMFAULT_REBOOT_REASON_GET_CUSTOM bool "Provide a custom implementation for recovering reboot information" default n diff --git a/ports/zephyr/common/memfault_demo_cli.c b/ports/zephyr/common/memfault_demo_cli.c index 19a191afe..7d026065e 100644 --- a/ports/zephyr/common/memfault_demo_cli.c +++ b/ports/zephyr/common/memfault_demo_cli.c @@ -224,7 +224,7 @@ static int prv_zephyr_assert_example(const struct shell *shell, size_t argc, cha // Fire off a last-ditch log message to show how logs are flushed prior to // crash, in the case of deferred logging mode MEMFAULT_LOG_ERROR("About to crash in %s!", __func__); - __ASSERT(0, "test assert"); + __ASSERT(0, "test __ASSERT"); return 0; } diff --git a/scripts/memfault_gdb.py b/scripts/memfault_gdb.py index 49f8a3a05..ade1a6752 100644 --- a/scripts/memfault_gdb.py +++ b/scripts/memfault_gdb.py @@ -529,7 +529,7 @@ def get_current_registers(self, gdb_thread, analytics_props): return (lookup_registers_from_list(self, info_reg_all_list, analytics_props),) -# FIXME: De-duplicate with code from rtos_register_stacking.py +# TODO: De-duplicate with code from rtos_register_stacking.py def concat_registers_dict_to_bytes(arch, regs): result = b"" for reg_name in arch.register_collection_list: @@ -639,7 +639,7 @@ def _search_list_for_alt_name(reg, found_registers): return register_list -# FIXME: De-duplicate with code from core_convert.py +# TODO: De-duplicate with code from core_convert.py MEMFAULT_COREDUMP_MAGIC = 0x45524F43 MEMFAULT_COREDUMP_VERSION = 1 MEMFAULT_COREDUMP_FILE_HEADER_FMT = "= 200 and status < 300: print("Coredump uploaded successfully!") print("Once it has been processed, it will appear here:") - # FIXME: Print direct link to trace + # TODO: Print direct link to trace # https://memfault.myjetbrains.com/youtrack/issue/MFLT-461 print(_infer_issues_html_url(parsed_args.ingress_uri, config)) else: @@ -1761,13 +1761,13 @@ def track(self, event_name, event_properties=None, user_id=None): # Put in queue to offload to background thread, to avoid slowing down the GDB commands self._queue.put((event_name, event_properties, user_id)) - def log(self, level, type, **kwargs): + def log(self, level, type, **kwargs): # noqa: A002 props = dict(**kwargs) props["type"] = type props["level"] = level self.track("Log", props) - def error(self, type, info=None): + def error(self, type, info=None): # noqa: A002 self.log("error", type, info=info) def _is_analytics_disabled(self): @@ -1839,7 +1839,7 @@ def _track_script_sourced(): ANALYTICS.track( "Script sourced", - # FIXME: MFLT-497 -- properly version this + # TODO: MFLT-497 -- properly version this { "version": "1", "python": platform.python_version(), diff --git a/scripts/tests_embedded_scripts/gdb_fake.py b/scripts/tests_embedded_scripts/gdb_fake.py index 0d8d72f92..2876df22e 100644 --- a/scripts/tests_embedded_scripts/gdb_fake.py +++ b/scripts/tests_embedded_scripts/gdb_fake.py @@ -34,7 +34,7 @@ class Type: @dataclasses.dataclass class Value: _value: Any - type: Type + type: Type # noqa: A003 def __int__(self) -> int: return int(self._value) @@ -78,7 +78,7 @@ def threads(self): return [] def read_memory(self, addr, size): - raise NotImplementedError() + raise NotImplementedError def inferiors(): @@ -94,7 +94,7 @@ def selected_thread(): def execute(cmd: str, to_string=False) -> Any: - raise NotImplementedError() + raise NotImplementedError class GdbError(Exception): diff --git a/scripts/tests_embedded_scripts/test_memfault_gdb.py b/scripts/tests_embedded_scripts/test_memfault_gdb.py index 84366014c..7456e465b 100644 --- a/scripts/tests_embedded_scripts/test_memfault_gdb.py +++ b/scripts/tests_embedded_scripts/test_memfault_gdb.py @@ -310,7 +310,7 @@ def _read_memory_raise_after_10k(_addr, _size): nonlocal total_size total_size += _size if total_size > _size * 10: - raise ValueError() + raise ValueError return b"A" * _size inferior.read_memory.side_effect = _read_memory_raise_after_10k diff --git a/tasks/macos_ftdi.py b/tasks/macos_ftdi.py index f4ce9281d..761f8bfa7 100644 --- a/tasks/macos_ftdi.py +++ b/tasks/macos_ftdi.py @@ -15,7 +15,7 @@ def is_macos(): def _unload_apple_ftdi_driver_if_needed(ctx): if not is_macos(): - return + return None result = ctx.run("kextstat -b {}".format(APPLE_FTDI_DRIVER_BUNDLE_ID), hide=True) loaded = APPLE_FTDI_DRIVER_BUNDLE_ID in result.stdout if loaded: diff --git a/tests/src/test_memfault_heartbeat_metrics.cpp b/tests/src/test_memfault_heartbeat_metrics.cpp index a9209fe24..09311ba4c 100644 --- a/tests/src/test_memfault_heartbeat_metrics.cpp +++ b/tests/src/test_memfault_heartbeat_metrics.cpp @@ -238,6 +238,29 @@ TEST(MemfaultHeartbeatMetrics, Test_TimerHeartBeatValueSimple) { LONGS_EQUAL(10, val); } +TEST(MemfaultHeartbeatMetrics, Test_TimerHeartBeatReadWhileRunning) +{ + MemfaultMetricId key = MEMFAULT_METRICS_KEY(test_key_timer); + + // start the timer + int rv = memfault_metrics_heartbeat_timer_start(key); + LONGS_EQUAL(0, rv); + + // read while running + uint32_t val; + prv_fake_time_incr(10); + memfault_metrics_heartbeat_timer_read(key, &val); + LONGS_EQUAL(10, val); + + // stop the timer + prv_fake_time_incr(9); + rv = memfault_metrics_heartbeat_timer_stop(key); + LONGS_EQUAL(0, rv); + + memfault_metrics_heartbeat_timer_read(key, &val); + LONGS_EQUAL(19, val); +} + TEST(MemfaultHeartbeatMetrics, Test_TimerHeartBeatValueRollover) { MemfaultMetricId key = MEMFAULT_METRICS_KEY(test_key_timer); @@ -479,3 +502,45 @@ TEST(MemfaultHeartbeatMetrics, Test_HeartbeatCollection) { rv = memfault_metrics_heartbeat_read_unsigned(keyu32, &valu32); CHECK(rv != 0); } + +// Sanity test the alternate setter API +TEST(MemfaultHeartbeatMetrics, Test_HeartbeatCollectionAltSetter) { + MemfaultMetricId keyi32 = MEMFAULT_METRICS_KEY(test_key_signed); + MemfaultMetricId keyu32 = MEMFAULT_METRICS_KEY(test_key_unsigned); + MemfaultMetricId keytimer = MEMFAULT_METRICS_KEY(test_key_timer); + MemfaultMetricId keystring = MEMFAULT_METRICS_KEY(test_key_string); + + // integers + MEMFAULT_HEARTBEAT_SET_SIGNED(test_key_signed, -200); + MEMFAULT_HEARTBEAT_SET_UNSIGNED(test_key_unsigned, 199); + MEMFAULT_HEARTBEAT_ADD(test_key_signed, 1); + MEMFAULT_HEARTBEAT_ADD(test_key_unsigned, 1); + + // string + MEMFAULT_HEARTBEAT_SET_STRING(test_key_string, "test"); + + // timer + MEMFAULT_HEARTBEAT_TIMER_START(test_key_timer); + prv_fake_time_incr(10); + MEMFAULT_HEARTBEAT_TIMER_STOP(test_key_timer); + + int32_t vali32; + uint32_t valu32; + uint32_t timer_valu32; + + int rv; + rv = memfault_metrics_heartbeat_read_signed(keyi32, &vali32); + LONGS_EQUAL(0, rv); + rv = memfault_metrics_heartbeat_read_unsigned(keyu32, &valu32); + LONGS_EQUAL(0, rv); + char buf[512]; + rv = memfault_metrics_heartbeat_read_string(keystring, buf, sizeof(buf)); + LONGS_EQUAL(0, rv); + rv = memfault_metrics_heartbeat_timer_read(keytimer, &timer_valu32); + LONGS_EQUAL(0, rv); + + LONGS_EQUAL(vali32, -199); + LONGS_EQUAL(valu32, 200); + STRCMP_EQUAL(buf, "test"); + LONGS_EQUAL(timer_valu32, 10); +}