blob: b4c12e5a8fab7b4f3f18c25ddd24ce64d608f584 [file] [log] [blame]
//! @file
//!
//! Copyright (c) Memfault, Inc.
//! See License.txt for details
//!
//! Example app main
// clang-format off
#include "memfault/ports/zephyr/include_compatibility.h"
#include MEMFAULT_ZEPHYR_INCLUDE(device.h)
#include MEMFAULT_ZEPHYR_INCLUDE(devicetree.h)
#include MEMFAULT_ZEPHYR_INCLUDE(drivers/gpio.h)
#include MEMFAULT_ZEPHYR_INCLUDE(kernel.h)
#include MEMFAULT_ZEPHYR_INCLUDE(logging/log.h)
#include MEMFAULT_ZEPHYR_INCLUDE(shell/shell.h)
#include "memfault/components.h"
#include "memfault/ports/zephyr/core.h"
// clang-format on
LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);
#if CONFIG_ZEPHYR_MEMFAULT_EXAMPLE_MEMORY_METRICS
//! Size of memory to allocate on main stack
//! This requires a larger allocation due to the method used to measure stack usage
#define STACK_ALLOCATION_SIZE (CONFIG_MAIN_STACK_SIZE >> 2)
//! Size of memory to allocate on system heap
#define HEAP_ALLOCATION_SIZE (CONFIG_HEAP_MEM_POOL_SIZE >> 3)
//! Value to sleep for observing metrics changes
#define METRICS_OBSERVE_PERIOD MEMFAULT_METRICS_HEARTBEAT_INTERVAL_SECS
//! Array of heap pointers
static void *heap_ptrs[4] = { NULL };
//! Keep a reference to the main thread for stack info
static struct k_thread *s_main_thread = NULL;
#endif // CONFIG_ZEPHYR_MEMFAULT_EXAMPLE_MEMORY_METRICS
// Blink code taken from the zephyr/samples/basic/blinky example.
static void blink_forever(void) {
#if DT_NODE_HAS_PROP(DT_ALIAS(led0), gpios)
/* 1000 msec = 1 sec */
#define SLEEP_TIME_MS 1000
/* The devicetree node identifier for the "led0" alias. */
#define LED0_NODE DT_ALIAS(led0)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
int ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return;
}
while (1) {
gpio_pin_toggle_dt(&led);
k_msleep(SLEEP_TIME_MS);
}
#else
// no led on this board, just sleep forever
k_sleep(K_FOREVER);
#endif
}
void memfault_platform_get_device_info(sMemfaultDeviceInfo *info) {
*info = (sMemfaultDeviceInfo){
.device_serial = "DEMOSERIAL",
.software_type = "zephyr-app",
.software_version =
CONFIG_ZEPHYR_MEMFAULT_EXAMPLE_SOFTWARE_VERSION "+" ZEPHYR_MEMFAULT_EXAMPLE_GIT_SHA1,
.hardware_version = CONFIG_BOARD,
};
}
#if CONFIG_ZEPHYR_MEMFAULT_EXAMPLE_THREAD_TOGGLE
K_THREAD_STACK_DEFINE(test_thread_stack_area, 1024);
static struct k_thread test_thread;
static void prv_test_thread_function(void *arg0, void *arg1, void *arg2) {
ARG_UNUSED(arg0);
ARG_UNUSED(arg1);
ARG_UNUSED(arg2);
k_sleep(K_FOREVER);
}
static void prv_test_thread_work_handler(struct k_work *work) {
ARG_UNUSED(work);
static bool started = false;
if (started) {
LOG_INF("ending test_thread ❌");
k_thread_abort(&test_thread);
started = false;
} else {
LOG_INF("starting test_thread ✅");
k_thread_create(&test_thread, test_thread_stack_area,
K_THREAD_STACK_SIZEOF(test_thread_stack_area), prv_test_thread_function, NULL,
NULL, NULL, 7, 0, K_FOREVER);
k_thread_name_set(&test_thread, "test_thread");
k_thread_start(&test_thread);
started = true;
}
}
K_WORK_DEFINE(s_test_thread_work, prv_test_thread_work_handler);
//! Timer handlers run from an ISR so we dispatch the heartbeat job to the
//! worker task
static void prv_test_thread_timer_expiry_handler(struct k_timer *dummy) {
k_work_submit(&s_test_thread_work);
}
K_TIMER_DEFINE(s_test_thread_timer, prv_test_thread_timer_expiry_handler, NULL);
static void prv_init_test_thread_timer(void) {
k_timer_start(&s_test_thread_timer, K_SECONDS(10), K_SECONDS(10));
// fire one time right away
k_work_submit(&s_test_thread_work);
}
#else
static void prv_init_test_thread_timer(void) { }
#endif // CONFIG_ZEPHYR_MEMFAULT_EXAMPLE_THREAD_TOGGLE
#if CONFIG_ZEPHYR_MEMFAULT_EXAMPLE_MEMORY_METRICS
//! Helper function to collect metric value on main thread stack usage.
static void prv_collect_main_thread_stack_free(void) {
if (s_main_thread == NULL) {
return;
}
size_t unused = 0;
int rc = k_thread_stack_space_get(s_main_thread, &unused);
if (rc == 0) {
rc = MEMFAULT_METRIC_SET_UNSIGNED(MainStack_MinBytesFree, unused);
if (rc) {
LOG_ERR("Error[%d] setting MainStack_MinBytesFree", rc);
}
} else {
LOG_ERR("Error getting thread stack usage[%d]", rc);
}
}
//! Shell function to exercise example memory metrics
//!
//! This function demonstrates the change in stack and heap memory metrics
//! as memory is allocated and deallocated from these regions.
//!
//! @warning This code uses `memfault_metrics_heartbeat_debug_trigger` which is not intended
//! to be used in production code. This functions use here is solely to demonstrate the metrics
//! values changing. Production applications should rely on the heartbeat timer to trigger
//! collection
static int prv_run_example_memory_metrics(const struct shell *shell, size_t argc, char **argv) {
ARG_UNUSED(shell);
ARG_UNUSED(argc);
ARG_UNUSED(argv);
if (s_main_thread == NULL) {
return 0;
}
// Next two loops demonstrate heap usage metric
for (size_t i = 0; i < ARRAY_SIZE(heap_ptrs); i++) {
heap_ptrs[i] = k_malloc(HEAP_ALLOCATION_SIZE);
}
// Collect data after allocation
memfault_metrics_heartbeat_debug_trigger();
for (size_t i = 0; i < ARRAY_SIZE(heap_ptrs); i++) {
k_free(heap_ptrs[i]);
heap_ptrs[i] = NULL;
}
// Collect data after deallocation
memfault_metrics_heartbeat_debug_trigger();
return 0;
}
SHELL_CMD_REGISTER(memory_metrics, NULL, "Collects runtime memory metrics from application",
prv_run_example_memory_metrics);
// Override function to collect the app metric MainStack_MinBytesFree
// and print current metric values
void memfault_metrics_heartbeat_collect_data(void) {
prv_collect_main_thread_stack_free();
memfault_metrics_heartbeat_debug_print();
}
//! Helper function to demonstrate changes in stack metrics
static void prv_run_stack_metrics_example(void) {
volatile uint8_t stack_array[STACK_ALLOCATION_SIZE];
memset((uint8_t *)stack_array, 0, STACK_ALLOCATION_SIZE);
}
#endif // CONFIG_ZEPHYR_MEMFAULT_EXAMPLE_MEMORY_METRICS
#if defined(CONFIG_MEMFAULT_FAULT_HANDLER_RETURN)
#include MEMFAULT_ZEPHYR_INCLUDE(fatal.h)
void k_sys_fatal_error_handler(unsigned int reason, const z_arch_esf_t *esf) {
printk("User fatal handler invoked with reason: %d. Rebooting!\n", reason);
memfault_platform_reboot();
}
#endif
int main(void) {
LOG_INF("👋 Memfault Demo App! Board %s\n", CONFIG_BOARD);
memfault_device_info_dump();
#if !defined(MEMFAULT_RECORD_REBOOT_ON_SYSTEM_INIT)
memfault_zephyr_collect_reset_info();
#endif
#if CONFIG_ZEPHYR_MEMFAULT_EXAMPLE_MEMORY_METRICS
s_main_thread = k_current_get();
// @warning This code uses `memfault_metrics_heartbeat_debug_trigger` which is not intended
// to be used in production code. This functions use here is solely to demonstrate the metrics
// values changing. Production applications should rely on the heartbeat timer to trigger
// collection
// Collect a round of metrics to show initial stack usage
memfault_metrics_heartbeat_debug_trigger();
prv_run_stack_metrics_example();
// Collect another round to show change in stack metrics
memfault_metrics_heartbeat_debug_trigger();
#endif
prv_init_test_thread_timer();
blink_forever();
return 0;
}