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:
parent
13ef99831a
commit
27ef15b8f5
6 changed files with 291 additions and 0 deletions
|
@ -8,3 +8,4 @@ Debugging
|
|||
|
||||
host-tools.rst
|
||||
probes.rst
|
||||
thread-analyzer.rst
|
||||
|
|
31
doc/guides/debugging/thread-analyzer.rst
Normal file
31
doc/guides/debugging/thread-analyzer.rst
Normal 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
|
64
include/debug/thread_analyzer.h
Normal file
64
include/debug/thread_analyzer.h
Normal 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 */
|
|
@ -9,3 +9,8 @@ zephyr_sources_ifdef(
|
|||
CONFIG_ASAN
|
||||
asan_hacks.c
|
||||
)
|
||||
|
||||
zephyr_sources_ifdef(
|
||||
CONFIG_THREAD_ANALYZER
|
||||
thread_analyzer.c
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
115
subsys/debug/thread_analyzer.c
Normal file
115
subsys/debug/thread_analyzer.c
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue