diff --git a/doc/guides/debugging/index.rst b/doc/guides/debugging/index.rst index 9abbe344c2e..fae592c51ae 100644 --- a/doc/guides/debugging/index.rst +++ b/doc/guides/debugging/index.rst @@ -8,3 +8,4 @@ Debugging host-tools.rst probes.rst + thread-analyzer.rst diff --git a/doc/guides/debugging/thread-analyzer.rst b/doc/guides/debugging/thread-analyzer.rst new file mode 100644 index 00000000000..5df3d49b315 --- /dev/null +++ b/doc/guides/debugging/thread-analyzer.rst @@ -0,0 +1,31 @@ +.. _thread_analyzer: + +Thread analyzer +################### + +The thread analyzer module enables all the Zephyr options required to track +the thread information, e.g. thread stack size usage. +The analysis is performed on demand when the application calls +:cpp:func:`thread_analyzer_run` or :cpp:func:`thread_analyzer_print`. + +Configuration +************* +Configure this module using the following options. + +* ``THREAD_ANALYZER``: enable the module. +* ``THREAD_ANALYZER_USE_PRINTK``: use printk for thread statistics. +* ``THREAD_ANALYZER_USE_LOG``: use the logger for thread statistics. +* ``THREAD_ANALYZER_AUTO``: run the thread analyzer automatically. + You do not need to add any code to the application when using this option. +* ``THREAD_ANALYZER_AUTO_INTERVAL``: the time for which the module sleeps + between consecutive printing of thread analysis in automatic mode. +* ``THREAD_ANALYZER_AUTO_STACK_SIZE``: the stack for thread analyzer + automatic thread. +* ``THREAD_NAME``: enable this option in the kernel to print the name of the + thread instead of its ID. + +API documentation +***************** + +.. doxygengroup:: thread_analyzer + :project: Zephyr diff --git a/include/debug/thread_analyzer.h b/include/debug/thread_analyzer.h new file mode 100644 index 00000000000..55f905591bd --- /dev/null +++ b/include/debug/thread_analyzer.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019 - 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __STACK_SIZE_ANALYZER_H +#define __STACK_SIZE_ANALYZER_H +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @defgroup thread_analyzer Thread analyzer + * @brief Module for analyzing threads + * + * This module implements functions and the configuration that simplifies + * thread analysis. + * @{ + */ + +struct thread_analyzer_info { + /** The name of the thread or stringified address of the thread handle + * if name is not set. + */ + const char *name; + /** The total size of the stack*/ + size_t stack_size; + /** Stack size in used */ + size_t stack_used; +}; + +/** @brief Thread analyzer stack size callback function + * + * Callback function with thread analysis information. + * + * @param info Thread analysis information. + */ +typedef void (*thread_analyzer_cb)(struct thread_analyzer_info *info); + +/** @brief Run the thread analyzer and provide information to the callback + * + * This function analyzes the current state for all threads and calls + * a given callback on every thread found. + * + * @param cb The callback function handler + */ +void thread_analyzer_run(thread_analyzer_cb cb); + +/** @brief Run the thread analyzer and print stack size statistics. + * + * This function runs the thread analyzer and prints the output in standard + * form. + */ +void thread_analyzer_print(void); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __STACK_SIZE_ANALYZER_H */ diff --git a/subsys/debug/CMakeLists.txt b/subsys/debug/CMakeLists.txt index 0387382575f..957b4338290 100644 --- a/subsys/debug/CMakeLists.txt +++ b/subsys/debug/CMakeLists.txt @@ -9,3 +9,8 @@ zephyr_sources_ifdef( CONFIG_ASAN asan_hacks.c ) + +zephyr_sources_ifdef( + CONFIG_THREAD_ANALYZER + thread_analyzer.c + ) diff --git a/subsys/debug/Kconfig b/subsys/debug/Kconfig index f0b440cf7c3..b75381df722 100644 --- a/subsys/debug/Kconfig +++ b/subsys/debug/Kconfig @@ -281,3 +281,78 @@ config OPENOCD_SUPPORT selects CONFIG_THREAD_MONITOR, so all of its caveats are implied.) endmenu + +menuconfig THREAD_ANALYZER + bool "Enable Thread analyzer" + select INIT_STACKS + select THREAD_MONITOR + select THREAD_STACK_INFO + help + Enable thread analyzer functionality and all the required modules. + This module may be used to debug thread configuration issues, e.g. + stack size configuration to find stack overflow or to find stacks + which may be optimized. + +if THREAD_ANALYZER +module = THREAD_ANALYZER +module-str = thread analyzer +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" + +choice + prompt "Thread analysis print mode" + +config THREAD_ANALYZER_USE_LOG + bool "Use logger output" + select LOG + help + Use logger output to print thread information. + +config THREAD_ANALYZER_USE_PRINTK + bool "Use printk function" + help + Use kernel printk function to print thread information. + +endchoice + +config THREAD_ANALYZER_RUN_UNLOCKED + bool "Run analysis with interrupts unlocked" + default y + help + The thread analysis takes quite a long time. + Every thread it finds is analyzed word by word to find any that + does not match the magic number. + Normally while thread are analyzed the k_thread_foreach function + is used. + While this is a safe run from the thread list perspective it may lock + the interrupts for a long time - long enough to disconnect when + Bluetooth communication is used. + Setting this flag will force thread analyzer to use + the k_thread_foreach_unlocked function. + This will allow the interrupts to be processed while the thread is + analyzed. + For the limitation of such configuration see the k_thread_foreach + documentation. + +config THREAD_ANALYZER_AUTO + bool "Run periodic thread analysis in a thread" + help + Run the thread analyzer automatically, without the need to add + any code to the application. + Thread analysis would be called periodically. + +if THREAD_ANALYZER_AUTO + +config THREAD_ANALYZER_AUTO_INTERVAL + int "Thread analysis interval" + default 60 + range 5 3600 + help + The time in seconds to call thread analyzer periodic printing function. + +config THREAD_ANALYZER_AUTO_STACK_SIZE + int "Stack size for the periodic thread analysis thread" + default 512 + +endif # THREAD_ANALYZER_AUTO + +endif # THREAD_ANALYZER diff --git a/subsys/debug/thread_analyzer.c b/subsys/debug/thread_analyzer.c new file mode 100644 index 00000000000..a7d1449c465 --- /dev/null +++ b/subsys/debug/thread_analyzer.c @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2019 - 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file + * @brief Thread analyzer implementation + */ + +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(thread_analyzer, CONFIG_THREAD_ANALYZER_LOG_LEVEL); + +#if IS_ENABLED(CONFIG_THREAD_ANALYZER_USE_PRINTK) +#define THREAD_ANALYZER_PRINT(...) printk(__VA_ARGS__) +#define THREAD_ANALYZER_FMT(str) str "\n" +#define THREAD_ANALYZER_VSTR(str) (str) +#else +#define THREAD_ANALYZER_PRINT(...) LOG_INF(__VA_ARGS__) +#define THREAD_ANALYZER_FMT(str) str +#define THREAD_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) + +static void thread_print_cb(struct thread_analyzer_info *info) +{ + unsigned int pcnt = (info->stack_used * 100U) / info->stack_size; + + THREAD_ANALYZER_PRINT( + THREAD_ANALYZER_FMT( + " %-20s: unused %zu usage %zu / %zu (%zu %%)"), + THREAD_ANALYZER_VSTR(info->name), + info->stack_size - info->stack_used, info->stack_used, + info->stack_size, pcnt); +} + +static void thread_analyze_cb(const struct k_thread *cthread, void *user_data) +{ + struct k_thread *thread = (struct k_thread *)cthread; + size_t size = thread->stack_info.size; + thread_analyzer_cb cb = user_data; + struct thread_analyzer_info info; + char hexname[PTR_STR_MAXLEN + 1]; + const char *name; + size_t unused; + int err; + + name = k_thread_name_get((k_tid_t)thread); + if (!name || name[0] == '\0') { + name = hexname; + sprintf(hexname, "%p", (void *)thread); + } + + err = k_thread_stack_space_get(thread, &unused); + if (err) { + THREAD_ANALYZER_PRINT( + THREAD_ANALYZER_FMT( + " %-20s: unable to get stack space (%d)"), + name, err); + + unused = 0; + } + + info.name = name; + info.stack_size = size; + info.stack_used = size - unused; + cb(&info); +} + +void thread_analyzer_run(thread_analyzer_cb cb) +{ + if (IS_ENABLED(CONFIG_THREAD_ANALYZER_RUN_UNLOCKED)) { + k_thread_foreach_unlocked(thread_analyze_cb, cb); + } else { + k_thread_foreach(thread_analyze_cb, cb); + } +} + +void thread_analyzer_print(void) +{ + THREAD_ANALYZER_PRINT(THREAD_ANALYZER_FMT("Thread analyze:")); + thread_analyzer_run(thread_print_cb); +} + +#if IS_ENABLED(CONFIG_THREAD_ANALYZER_AUTO) + +void thread_analyzer_auto(void) +{ + for (;;) { + thread_analyzer_print(); + k_sleep(K_SECONDS(CONFIG_THREAD_ANALYZER_AUTO_INTERVAL)); + } +} + +K_THREAD_DEFINE(thread_analyzer, + CONFIG_THREAD_ANALYZER_AUTO_STACK_SIZE, + thread_analyzer_auto, + NULL, NULL, NULL, + K_LOWEST_APPLICATION_THREAD_PRIO, + 0, 0); + +#endif