diff --git a/CODEOWNERS b/CODEOWNERS index c6e1e64473b5..ea2efd865b4b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -97,6 +97,7 @@ Kconfig* @SebastianBoe /subsys/bluetooth/controller/ @joerchan @rugeGerritsen /subsys/bootloader/ @hakonfam @ioannisg /subsys/debug/ @nordic-krch @anangl +/subsys/debug/stack_size_analyzer/ @rakons /subsys/dfu/ @hakonfam @sigvartmh /subsys/esb/ @Raane @lemrey /subsys/event_manager/ @pdunaj diff --git a/include/debug/stack_size_analyzer.h b/include/debug/stack_size_analyzer.h new file mode 100644 index 000000000000..ec45a5b84ac9 --- /dev/null +++ b/include/debug/stack_size_analyzer.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-BSD-5-Clause-Nordic + */ +#ifndef __STACK_SIZE_ANALYZER_H +#define __STACK_SIZE_ANALYZER_H +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup stack_size_analyzer Stack analyzer + * @brief Module for analyzing stack usage in samples + * + * This module implements functions and the configuration that simplifies + * stack size analysis. + * @{ + */ + +/** + * @brief Stack size analyzer callback function + * + * Callback function with stack size statistics. + * + * @param name The name of the thread or stringified address + * of the thread handle if name is not set. + * @param size The total size of the stack + * @param used Stack size in use + */ +typedef void (*stack_size_analyzer_cb)(const char *name, + size_t size, size_t used); + +/** + * @brief Run the stack size analyzer and return the results to the callback + * + * This function analyzes stacks for all threads and calls + * a given callback on every thread found. + * + * @param cb The callback function handler + */ +void stack_size_analyzer_run(stack_size_analyzer_cb cb); + +/** + * @brief Run the stack size analyzer and print the result + * + * This function runs the stack size analyzer and prints the output in standard + * form. + */ +void stack_size_analyzer_print(void); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __STACK_SIZE_ANALYZER_H */ diff --git a/include/debug/stack_size_analyzer.rst b/include/debug/stack_size_analyzer.rst new file mode 100644 index 000000000000..7adcc6f97b29 --- /dev/null +++ b/include/debug/stack_size_analyzer.rst @@ -0,0 +1,35 @@ +.. _stack_size_analyzer: + +Stack size analyzer +################### + +The stack size analyzer module enables all the Zephyr options required to track +the stack usage. +The analysis is performed on demand when the application calls +:cpp:func:`stack_size_analyzer_run` or :cpp:func:`stack_size_analyzer_print`. + +Configuration +************* +Configure this module using the following options. + +* ``STACK_SIZE_ANALYZER``: enable the module. +* ``STACK_SIZE_ANALYZER_USE_PRINTK``: use printk for stack statistics. +* ``STACK_SIZE_ANALYZER_USE_LOG``: use the logger for stack statistics. +* ``STACK_SIZE_ANALYZER_AUTO``: run the stack analyzer automatically. + You do not need to add any code to the application when using this option. +* ``STACK_SIZE_ANALYZER_AUTO_PERIOD``: the time for which the module sleeps + between consecutive printing of stack size analysis in automatic mode. +* ``STACK_SIZE_ANALYZER_AUTO_STACK_SIZE``: the stack size for stack size analyzer + automatic thread. +* ``THREAD_NAME``: enable this option in the kernel to print the name of the thread + instead of its ID. + +API documentation +***************** + +| Header file: :file:`include/debug/stack_size_analyzer.h` +| Source files: :file:`subsys/debug/stack_size_analyzer/` + +.. doxygengroup:: stack_size_analyzer + :project: nrf + :members: diff --git a/subsys/debug/CMakeLists.txt b/subsys/debug/CMakeLists.txt index 105acd3f8b86..4116e4c188ab 100644 --- a/subsys/debug/CMakeLists.txt +++ b/subsys/debug/CMakeLists.txt @@ -5,4 +5,5 @@ # -add_subdirectory_ifdef(CONFIG_PPI_TRACE ppi_trace) +add_subdirectory_ifdef(CONFIG_PPI_TRACE ppi_trace) +add_subdirectory_ifdef(CONFIG_STACK_SIZE_ANALYZER stack_size_analyzer) diff --git a/subsys/debug/Kconfig b/subsys/debug/Kconfig index 44286480c195..50eaa20711c1 100644 --- a/subsys/debug/Kconfig +++ b/subsys/debug/Kconfig @@ -5,3 +5,4 @@ # rsource "ppi_trace/Kconfig" +rsource "stack_size_analyzer/Kconfig" diff --git a/subsys/debug/stack_size_analyzer/CMakeLists.txt b/subsys/debug/stack_size_analyzer/CMakeLists.txt new file mode 100644 index 000000000000..7480fe8277ee --- /dev/null +++ b/subsys/debug/stack_size_analyzer/CMakeLists.txt @@ -0,0 +1,7 @@ +# +# Copyright (c) 2019 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-BSD-5-Clause-Nordic +# + +zephyr_sources(stack_size_analyzer.c) diff --git a/subsys/debug/stack_size_analyzer/Kconfig b/subsys/debug/stack_size_analyzer/Kconfig new file mode 100644 index 000000000000..b06fac4ed387 --- /dev/null +++ b/subsys/debug/stack_size_analyzer/Kconfig @@ -0,0 +1,82 @@ +# +# Copyright (c) 2019 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-BSD-5-Clause-Nordic +# + +menuconfig STACK_SIZE_ANALYZER + bool "Enable Stack size analyzer functionality" + select INIT_STACKS + select THREAD_MONITOR + select THREAD_STACK_INFO + help + Enable stack size analyzer functionality and all the required modules. + This module may be used to debug stack overflow or to find stacks + which size may be optimized down. + The module uses less resources than shell implementation and does not + require a valid input to type the commands. + +if STACK_SIZE_ANALYZER +module = STACK_SIZE_ANALYZER +module-str = stack size analyzer +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" + +choice + prompt "Print mode" + default STACK_SIZE_ANALYZER_USE_LOG + +config STACK_SIZE_ANALYZER_USE_PRINTK + bool "Use printk function" + help + Use kernel printk function to print stack size information. + +config STACK_SIZE_ANALYZER_USE_LOG + bool "Use logger output" + select LOG + help + Use logger output to print stack size information. + +endchoice + +config STACK_SIZE_ANALYZER_RUN_UNLOCKED + bool "Run analysis with interrupts unlocked" + default y + help + The stack analisys takes quite a long time. + Every stack it founds is analyzed word by word to find any that + does not match the magic number. + Normally while stacks are analyzed the k_thread_foreach function + is used. + It makes safe run from the thread list perspective but may lock + the interrupts for a long time - long enough to disconnect when + bluetooth communication is used. + Setting this flag would force stack analyzer to use + the k_thread_foreach_unlocked function. + This would allow the interrupts to be processed while the stack size + is analyzed. + For the limitation of such configuration see the k_thread_foreach + documentation. + +config STACK_SIZE_ANALYZER_AUTO + bool "Enable automatic printing" + default y + help + Run the stack size analyzer automatically, without the need to add + any code to the application. + Stack size analysis would be called periodically. + +if STACK_SIZE_ANALYZER_AUTO + +config STACK_SIZE_ANALYZER_AUTO_PERIOD + int "Automatic printing period" + default 5000 + help + The time in milliseconds to call stack analyzer periodic printing function. + +config STACK_SIZE_ANALYZER_AUTO_STACK_SIZE + int "The stack size for the thread to periodically run stack analisys" + default 512 + +endif # STACK_SIZE_ANALYZER_AUTO + +endif # STACK_SIZE_ANALYZER diff --git a/subsys/debug/stack_size_analyzer/stack_size_analyzer.c b/subsys/debug/stack_size_analyzer/stack_size_analyzer.c new file mode 100644 index 000000000000..28ad3c2270b3 --- /dev/null +++ b/subsys/debug/stack_size_analyzer/stack_size_analyzer.c @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-BSD-5-Clause-Nordic + */ + +/** @file + * @brief Stack analyzer implementation + */ + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(stack_size_analyzer, CONFIG_STACK_SIZE_ANALYZER_LOG_LEVEL); + +#if IS_ENABLED(CONFIG_STACK_SIZE_ANALYZER_USE_PRINTK) +#define STACK_SIZE_ANALYZER_PRINT(...) printk(__VA_ARGS__) +#define STACK_SIZE_ANALYZER_FMT(str) str "\n" +#define STASK_SIZE_ANALYZER_VSTR(str) (str) +#else +#define STACK_SIZE_ANALYZER_PRINT(...) LOG_INF(__VA_ARGS__) +#define STACK_SIZE_ANALYZER_FMT(str) str +#define STASK_SIZE_ANALYZER_VSTR(str) log_strdup(str) +#endif + +/** + * @brief Maximum length of the pointer when converted to string + * + * Pointer is converted to string in hexadecimal form. + * It would use 2 hex digits for every single byte of the pointer + * but some implementations adds 0x prefix when used with %p format option. + */ +#define PTR_STR_MAXLEN (sizeof(void *) * 2 + 2) + + +/** + * @brief Internal callback for default print + * + * @param name The name of the thread + * @param size The total size of the stack + * @param used The used size of the stack + */ +static void stack_print_cb(const char *name, + size_t size, size_t used) +{ + unsigned int pcnt = (used * 100U) / size; + + STACK_SIZE_ANALYZER_PRINT( + STACK_SIZE_ANALYZER_FMT( + " %-20s: unused %u usage %u / %u (%u %%)"), + STASK_SIZE_ANALYZER_VSTR(name), + size - used, used, size, pcnt); +} + +static void stack_thread_cb(const struct k_thread *thread, void *user_data) +{ + stack_size_analyzer_cb cb = user_data; + const char *stack = (const char *)thread->stack_info.start; + unsigned int size = thread->stack_info.size; + unsigned int unused = stack_unused_space_get(stack, size); + const char *name; + char hexname[PTR_STR_MAXLEN + 1]; + + name = k_thread_name_get((k_tid_t)thread); + if (!name || name[0] == '\0') { + name = hexname; + sprintf(hexname, "%p", (void *)thread); + } + + cb(name, size, size-unused); +} + +void stack_size_analyzer_run(stack_size_analyzer_cb cb) +{ + if (IS_ENABLED(CONFIG_STACK_SIZE_ANALYZER_RUN_UNLOCKED)) { + k_thread_foreach_unlocked(stack_thread_cb, cb); + } else { + k_thread_foreach(stack_thread_cb, cb); + } +} + +void stack_size_analyzer_print(void) +{ + STACK_SIZE_ANALYZER_PRINT(STACK_SIZE_ANALYZER_FMT("Stack analyze:")); + stack_size_analyzer_run(stack_print_cb); +} + +#if IS_ENABLED(CONFIG_STACK_SIZE_ANALYZER_AUTO) + +void stack_size_analyzer_auto(void) +{ + for (;;) { + stack_size_analyzer_print(); + k_sleep(CONFIG_STACK_SIZE_ANALYZER_AUTO_PERIOD); + } +} + +K_THREAD_DEFINE(stack_size_analyzer, + CONFIG_STACK_SIZE_ANALYZER_AUTO_STACK_SIZE, + stack_size_analyzer_auto, + NULL, NULL, NULL, + CONFIG_NUM_PREEMPT_PRIORITIES - 1, + 0, + K_NO_WAIT); + +#endif