1 /*
2  * Copyright (c) 2019 - 2020 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /** @file
8  *  @brief Thread analyzer implementation
9  */
10 
11 #include <kernel.h>
12 #include <debug/thread_analyzer.h>
13 #include <debug/stack.h>
14 #include <kernel.h>
15 #include <logging/log.h>
16 #include <stdio.h>
17 
18 LOG_MODULE_REGISTER(thread_analyzer, CONFIG_THREAD_ANALYZER_LOG_LEVEL);
19 
20 #if IS_ENABLED(CONFIG_THREAD_ANALYZER_USE_PRINTK)
21 #define THREAD_ANALYZER_PRINT(...) printk(__VA_ARGS__)
22 #define THREAD_ANALYZER_FMT(str)   str "\n"
23 #define THREAD_ANALYZER_VSTR(str)  (str)
24 #else
25 #define THREAD_ANALYZER_PRINT(...) LOG_INF(__VA_ARGS__)
26 #define THREAD_ANALYZER_FMT(str)   str
27 #define THREAD_ANALYZER_VSTR(str)  log_strdup(str)
28 #endif
29 
30 /* @brief Maximum length of the pointer when converted to string
31  *
32  * Pointer is converted to string in hexadecimal form.
33  * It would use 2 hex digits for every single byte of the pointer
34  * but some implementations adds 0x prefix when used with %p format option.
35  */
36 #define PTR_STR_MAXLEN (sizeof(void *) * 2 + 2)
37 
thread_print_cb(struct thread_analyzer_info * info)38 static void thread_print_cb(struct thread_analyzer_info *info)
39 {
40 	size_t pcnt = (info->stack_used * 100U) / info->stack_size;
41 #ifdef CONFIG_THREAD_RUNTIME_STATS
42 	THREAD_ANALYZER_PRINT(
43 		THREAD_ANALYZER_FMT(
44 			" %-20s: STACK: unused %zu usage %zu / %zu (%zu %%); CPU: %u %%"),
45 		THREAD_ANALYZER_VSTR(info->name),
46 		info->stack_size - info->stack_used, info->stack_used,
47 		info->stack_size, pcnt,
48 		info->utilization);
49 #else
50 	THREAD_ANALYZER_PRINT(
51 		THREAD_ANALYZER_FMT(
52 			" %-20s: unused %zu usage %zu / %zu (%zu %%)"),
53 		THREAD_ANALYZER_VSTR(info->name),
54 		info->stack_size - info->stack_used, info->stack_used,
55 		info->stack_size, pcnt);
56 #endif
57 }
58 
thread_analyze_cb(const struct k_thread * cthread,void * user_data)59 static void thread_analyze_cb(const struct k_thread *cthread, void *user_data)
60 {
61 	struct k_thread *thread = (struct k_thread *)cthread;
62 #ifdef CONFIG_THREAD_RUNTIME_STATS
63 	k_thread_runtime_stats_t rt_stats_all;
64 	k_thread_runtime_stats_t rt_stats_thread;
65 	int ret;
66 #endif
67 	size_t size = thread->stack_info.size;
68 	thread_analyzer_cb cb = user_data;
69 	struct thread_analyzer_info info;
70 	char hexname[PTR_STR_MAXLEN + 1];
71 	const char *name;
72 	size_t unused;
73 	int err;
74 
75 
76 
77 	name = k_thread_name_get((k_tid_t)thread);
78 	if (!name || name[0] == '\0') {
79 		name = hexname;
80 		snprintk(hexname, sizeof(hexname), "%p", (void *)thread);
81 	}
82 
83 	err = k_thread_stack_space_get(thread, &unused);
84 	if (err) {
85 		THREAD_ANALYZER_PRINT(
86 			THREAD_ANALYZER_FMT(
87 				" %-20s: unable to get stack space (%d)"),
88 			name, err);
89 
90 		unused = 0;
91 	}
92 
93 	info.name = name;
94 	info.stack_size = size;
95 	info.stack_used = size - unused;
96 
97 #ifdef CONFIG_THREAD_RUNTIME_STATS
98 	ret = 0;
99 
100 	if (k_thread_runtime_stats_get(thread, &rt_stats_thread) != 0) {
101 		ret++;
102 	}
103 
104 	if (k_thread_runtime_stats_all_get(&rt_stats_all) != 0) {
105 		ret++;
106 	}
107 	if (ret == 0) {
108 		info.utilization = (rt_stats_thread.execution_cycles * 100U) /
109 			rt_stats_all.execution_cycles;
110 	}
111 #endif
112 	cb(&info);
113 }
114 
thread_analyzer_run(thread_analyzer_cb cb)115 void thread_analyzer_run(thread_analyzer_cb cb)
116 {
117 	if (IS_ENABLED(CONFIG_THREAD_ANALYZER_RUN_UNLOCKED)) {
118 		k_thread_foreach_unlocked(thread_analyze_cb, cb);
119 	} else {
120 		k_thread_foreach(thread_analyze_cb, cb);
121 	}
122 }
123 
thread_analyzer_print(void)124 void thread_analyzer_print(void)
125 {
126 	THREAD_ANALYZER_PRINT(THREAD_ANALYZER_FMT("Thread analyze:"));
127 	thread_analyzer_run(thread_print_cb);
128 }
129 
130 #if IS_ENABLED(CONFIG_THREAD_ANALYZER_AUTO)
131 
thread_analyzer_auto(void)132 void thread_analyzer_auto(void)
133 {
134 	for (;;) {
135 		thread_analyzer_print();
136 		k_sleep(K_SECONDS(CONFIG_THREAD_ANALYZER_AUTO_INTERVAL));
137 	}
138 }
139 
140 K_THREAD_DEFINE(thread_analyzer,
141 		CONFIG_THREAD_ANALYZER_AUTO_STACK_SIZE,
142 		thread_analyzer_auto,
143 		NULL, NULL, NULL,
144 		K_LOWEST_APPLICATION_THREAD_PRIO,
145 		0, 0);
146 
147 #endif
148