debug: thread_analyzer: Implement thread analyzer

Thread analyzer is simple module that helps in printing thread
information, like stacks usage statistics.

Signed-off-by: Radoslaw Koppel <radoslaw.koppel@nordicsemi.no>
Signed-off-by: Bartosz Gentkowski <bartosz.gentkowski@nordicsemi.no>
Signed-off-by: Joakim Andersson <joakim.andersson@nordicsemi.no>
This commit is contained in:
Joakim Andersson 2020-03-04 15:08:44 +01:00 committed by Anas Nashif
commit 27ef15b8f5
6 changed files with 291 additions and 0 deletions

View file

@ -8,3 +8,4 @@ Debugging
host-tools.rst
probes.rst
thread-analyzer.rst

View file

@ -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

View file

@ -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 <stddef.h>
#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 */

View file

@ -9,3 +9,8 @@ zephyr_sources_ifdef(
CONFIG_ASAN
asan_hacks.c
)
zephyr_sources_ifdef(
CONFIG_THREAD_ANALYZER
thread_analyzer.c
)

View file

@ -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

View file

@ -0,0 +1,115 @@
/*
* Copyright (c) 2019 - 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/** @file
* @brief Thread analyzer implementation
*/
#include <kernel.h>
#include <debug/thread_analyzer.h>
#include <debug/stack.h>
#include <kernel.h>
#include <logging/log.h>
#include <stdio.h>
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