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 <zephyr/kernel.h>
12 #include <kernel_internal.h>
13 #include <zephyr/debug/thread_analyzer.h>
14 #include <zephyr/debug/stack.h>
15 #include <zephyr/kernel.h>
16 #include <zephyr/logging/log.h>
17 #include <stdio.h>
18
19 LOG_MODULE_REGISTER(thread_analyzer, CONFIG_THREAD_ANALYZER_LOG_LEVEL);
20
21 #if defined(CONFIG_THREAD_ANALYZER_USE_PRINTK)
22 #define THREAD_ANALYZER_PRINT(...) printk(__VA_ARGS__)
23 #define THREAD_ANALYZER_FMT(str) str "\n"
24 #define THREAD_ANALYZER_VSTR(str) (str)
25 #else
26 #define THREAD_ANALYZER_PRINT(...) LOG_INF(__VA_ARGS__)
27 #define THREAD_ANALYZER_FMT(str) str
28 #define THREAD_ANALYZER_VSTR(str) str
29 #endif
30
31 /* @brief Maximum length of the pointer when converted to string
32 *
33 * Pointer is converted to string in hexadecimal form.
34 * It would use 2 hex digits for every single byte of the pointer
35 * but some implementations adds 0x prefix when used with %p format option.
36 */
37 #define PTR_STR_MAXLEN (sizeof(void *) * 2 + 2)
38
thread_print_cb(struct thread_analyzer_info * info)39 static void thread_print_cb(struct thread_analyzer_info *info)
40 {
41 size_t pcnt = (info->stack_used * 100U) / info->stack_size;
42 #ifdef CONFIG_THREAD_RUNTIME_STATS
43 THREAD_ANALYZER_PRINT(
44 THREAD_ANALYZER_FMT(
45 " %-20s: STACK: unused %zu usage %zu / %zu (%zu %%); CPU: %u %%"),
46 THREAD_ANALYZER_VSTR(info->name),
47 info->stack_size - info->stack_used, info->stack_used,
48 info->stack_size, pcnt,
49 info->utilization);
50
51 #ifdef CONFIG_THREAD_ANALYZER_PRIV_STACK_USAGE
52 if (info->priv_stack_size > 0) {
53 pcnt = (info->priv_stack_used * 100U) / info->priv_stack_size;
54
55 THREAD_ANALYZER_PRINT(
56 THREAD_ANALYZER_FMT(
57 " %-20s: PRIV_STACK: unused %zu usage %zu / %zu (%zu %%)"),
58 " ", info->priv_stack_size - info->priv_stack_used, info->priv_stack_used,
59 info->priv_stack_size, pcnt);
60 }
61 #endif
62
63 #ifdef CONFIG_SCHED_THREAD_USAGE
64 THREAD_ANALYZER_PRINT(
65 THREAD_ANALYZER_FMT(" %-20s: Total CPU cycles used: %llu"),
66 " ", info->usage.total_cycles);
67
68 #ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS
69 THREAD_ANALYZER_PRINT(
70 THREAD_ANALYZER_FMT(
71 " %-20s: Current Frame: %llu;"
72 " Longest Frame: %llu; Average Frame: %llu"),
73 " ", info->usage.current_cycles, info->usage.peak_cycles,
74 info->usage.average_cycles);
75 #endif
76 #endif
77 #else
78 THREAD_ANALYZER_PRINT(
79 THREAD_ANALYZER_FMT(
80 " %-20s: unused %zu usage %zu / %zu (%zu %%)"),
81 THREAD_ANALYZER_VSTR(info->name),
82 info->stack_size - info->stack_used, info->stack_used,
83 info->stack_size, pcnt);
84 #endif
85 }
86
87 struct ta_cb_user_data {
88 thread_analyzer_cb cb;
89 unsigned int cpu;
90 };
91
thread_analyze_cb(const struct k_thread * cthread,void * user_data)92 static void thread_analyze_cb(const struct k_thread *cthread, void *user_data)
93 {
94 struct k_thread *thread = (struct k_thread *)cthread;
95 #ifdef CONFIG_THREAD_RUNTIME_STATS
96 k_thread_runtime_stats_t rt_stats_all;
97 #endif
98 size_t size = thread->stack_info.size;
99 struct ta_cb_user_data *ud = user_data;
100 thread_analyzer_cb cb = ud->cb;
101 unsigned int cpu = ud->cpu;
102 struct thread_analyzer_info info;
103 char hexname[PTR_STR_MAXLEN + 1];
104 const char *name;
105 size_t unused;
106 int err;
107 int ret;
108
109
110
111 name = k_thread_name_get((k_tid_t)thread);
112 if (!name || name[0] == '\0') {
113 name = hexname;
114 snprintk(hexname, sizeof(hexname), "%p", (void *)thread);
115 }
116
117 err = k_thread_stack_space_get(thread, &unused);
118 if (err) {
119 THREAD_ANALYZER_PRINT(
120 THREAD_ANALYZER_FMT(
121 " %-20s: unable to get stack space (%d)"),
122 name, err);
123
124 unused = 0;
125 }
126
127 info.name = name;
128 info.stack_size = size;
129 info.stack_used = size - unused;
130
131 #ifdef CONFIG_THREAD_ANALYZER_PRIV_STACK_USAGE
132 ret = arch_thread_priv_stack_space_get(cthread, &size, &unused);
133 if (ret == 0) {
134 info.priv_stack_size = size;
135 info.priv_stack_used = size - unused;
136 } else {
137 info.priv_stack_size = 0;
138 }
139 #endif
140
141 #ifdef CONFIG_THREAD_RUNTIME_STATS
142 ret = 0;
143
144 if (k_thread_runtime_stats_get(thread, &info.usage) != 0) {
145 ret++;
146 }
147
148 if (IS_ENABLED(CONFIG_THREAD_ANALYZER_AUTO_SEPARATE_CORES)) {
149 if (k_thread_runtime_stats_cpu_get(cpu, &rt_stats_all) != 0) {
150 ret++;
151 }
152 } else {
153 if (k_thread_runtime_stats_all_get(&rt_stats_all) != 0) {
154 ret++;
155 }
156 }
157
158 if (ret == 0) {
159 info.utilization = (info.usage.execution_cycles * 100U) /
160 rt_stats_all.execution_cycles;
161 }
162 #endif
163
164 ARG_UNUSED(ret);
165
166 cb(&info);
167 }
168
169 K_KERNEL_STACK_ARRAY_DECLARE(z_interrupt_stacks, CONFIG_MP_MAX_NUM_CPUS,
170 CONFIG_ISR_STACK_SIZE);
171
isr_stack(int core)172 static void isr_stack(int core)
173 {
174 const uint8_t *buf = K_KERNEL_STACK_BUFFER(z_interrupt_stacks[core]);
175 size_t size = K_KERNEL_STACK_SIZEOF(z_interrupt_stacks[core]);
176 size_t unused;
177 int err;
178
179 err = z_stack_space_get(buf, size, &unused);
180 if (err == 0) {
181 THREAD_ANALYZER_PRINT(
182 THREAD_ANALYZER_FMT(
183 " %s%-17d: STACK: unused %zu usage %zu / %zu (%zu %%)"),
184 THREAD_ANALYZER_VSTR("ISR"), core, unused,
185 size - unused, size, (100 * (size - unused)) / size);
186 }
187 }
188
isr_stacks(void)189 static void isr_stacks(void)
190 {
191 unsigned int num_cpus = arch_num_cpus();
192
193 for (int i = 0; i < num_cpus; i++) {
194 isr_stack(i);
195 }
196 }
197
thread_analyzer_run(thread_analyzer_cb cb,unsigned int cpu)198 void thread_analyzer_run(thread_analyzer_cb cb, unsigned int cpu)
199 {
200 struct ta_cb_user_data ud = { .cb = cb, .cpu = cpu };
201
202 if (IS_ENABLED(CONFIG_THREAD_ANALYZER_RUN_UNLOCKED)) {
203 if (IS_ENABLED(CONFIG_THREAD_ANALYZER_AUTO_SEPARATE_CORES)) {
204 k_thread_foreach_unlocked_filter_by_cpu(cpu, thread_analyze_cb, &ud);
205 } else {
206 k_thread_foreach_unlocked(thread_analyze_cb, &ud);
207 }
208 } else {
209 if (IS_ENABLED(CONFIG_THREAD_ANALYZER_AUTO_SEPARATE_CORES)) {
210 k_thread_foreach_filter_by_cpu(cpu, thread_analyze_cb, &ud);
211 } else {
212 k_thread_foreach(thread_analyze_cb, &ud);
213 }
214 }
215
216 if (IS_ENABLED(CONFIG_THREAD_ANALYZER_ISR_STACK_USAGE)) {
217 if (IS_ENABLED(CONFIG_THREAD_ANALYZER_AUTO_SEPARATE_CORES)) {
218 isr_stack(cpu);
219 } else {
220 isr_stacks();
221 }
222 }
223 }
224
thread_analyzer_print(unsigned int cpu)225 void thread_analyzer_print(unsigned int cpu)
226 {
227 #if IS_ENABLED(CONFIG_THREAD_ANALYZER_AUTO_SEPARATE_CORES)
228 THREAD_ANALYZER_PRINT(THREAD_ANALYZER_FMT("Thread analyze core %u:"),
229 cpu);
230 #else
231 THREAD_ANALYZER_PRINT(THREAD_ANALYZER_FMT("Thread analyze:"));
232 #endif
233 thread_analyzer_run(thread_print_cb, cpu);
234 }
235
236 #if defined(CONFIG_THREAD_ANALYZER_AUTO)
237
thread_analyzer_auto(void * a,void * b,void * c)238 void thread_analyzer_auto(void *a, void *b, void *c)
239 {
240 unsigned int cpu = IS_ENABLED(CONFIG_THREAD_ANALYZER_AUTO_SEPARATE_CORES) ?
241 (unsigned int)(uintptr_t) a : 0;
242
243 for (;;) {
244 thread_analyzer_print(cpu);
245 k_sleep(K_SECONDS(CONFIG_THREAD_ANALYZER_AUTO_INTERVAL));
246 }
247 }
248
249 #if IS_ENABLED(CONFIG_THREAD_ANALYZER_AUTO_SEPARATE_CORES)
250
251 static K_THREAD_STACK_ARRAY_DEFINE(analyzer_thread_stacks, CONFIG_MP_MAX_NUM_CPUS,
252 CONFIG_THREAD_ANALYZER_AUTO_STACK_SIZE);
253 static struct k_thread analyzer_thread[CONFIG_MP_MAX_NUM_CPUS];
254
thread_analyzer_init(void)255 static int thread_analyzer_init(void)
256 {
257 uint16_t i;
258
259 for (i = 0; i < ARRAY_SIZE(analyzer_thread); i++) {
260 char name[24];
261 k_tid_t tid = NULL;
262 int ret;
263
264 tid = k_thread_create(&analyzer_thread[i], analyzer_thread_stacks[i],
265 CONFIG_THREAD_ANALYZER_AUTO_STACK_SIZE,
266 thread_analyzer_auto,
267 (void *) (uint32_t) i, NULL, NULL,
268 K_LOWEST_APPLICATION_THREAD_PRIO, 0, K_FOREVER);
269 if (!tid) {
270 LOG_ERR("k_thread_create() failed for core %u", i);
271 continue;
272 }
273 ret = k_thread_cpu_pin(tid, i);
274 if (ret < 0) {
275 LOG_ERR("Pinning thread to code core %u", i);
276 k_thread_abort(tid);
277 continue;
278 }
279 snprintf(name, sizeof(name), "core %u thread analyzer", i);
280 ret = k_thread_name_set(tid, name);
281 if (ret < 0) {
282 LOG_INF("k_thread_name_set failed: %d for %u", ret, i);
283 }
284
285 k_thread_start(tid);
286 LOG_DBG("Thread %p for core %u started", tid, i);
287 }
288
289 return 0;
290 }
291
292 SYS_INIT(thread_analyzer_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
293
294 #else
295
296 K_THREAD_DEFINE(thread_analyzer,
297 CONFIG_THREAD_ANALYZER_AUTO_STACK_SIZE,
298 thread_analyzer_auto,
299 NULL, NULL, NULL,
300 K_LOWEST_APPLICATION_THREAD_PRIO,
301 0, 0);
302
303 #endif /* CONFIG_THREAD_ANALYZER_AUTO_SEPARATE_CORES */
304
305 #endif /* CONFIG_THREAD_ANALYZER_AUTO */
306