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_SCHED_THREAD_USAGE
52 	THREAD_ANALYZER_PRINT(
53 		THREAD_ANALYZER_FMT("      : Total CPU cycles used: %llu"),
54 		info->usage.total_cycles);
55 
56 #ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS
57 	THREAD_ANALYZER_PRINT(
58 		THREAD_ANALYZER_FMT(
59 			"         - Current Frame: %llu; Longest Frame: %llu; Average Frame: %llu"),
60 		info->usage.current_cycles, info->usage.peak_cycles,
61 		info->usage.average_cycles);
62 #endif
63 #endif
64 #else
65 	THREAD_ANALYZER_PRINT(
66 		THREAD_ANALYZER_FMT(
67 			" %-20s: unused %zu usage %zu / %zu (%zu %%)"),
68 		THREAD_ANALYZER_VSTR(info->name),
69 		info->stack_size - info->stack_used, info->stack_used,
70 		info->stack_size, pcnt);
71 #endif
72 }
73 
thread_analyze_cb(const struct k_thread * cthread,void * user_data)74 static void thread_analyze_cb(const struct k_thread *cthread, void *user_data)
75 {
76 	struct k_thread *thread = (struct k_thread *)cthread;
77 #ifdef CONFIG_THREAD_RUNTIME_STATS
78 	k_thread_runtime_stats_t rt_stats_all;
79 	int ret;
80 #endif
81 	size_t size = thread->stack_info.size;
82 	thread_analyzer_cb cb = user_data;
83 	struct thread_analyzer_info info;
84 	char hexname[PTR_STR_MAXLEN + 1];
85 	const char *name;
86 	size_t unused;
87 	int err;
88 
89 
90 
91 	name = k_thread_name_get((k_tid_t)thread);
92 	if (!name || name[0] == '\0') {
93 		name = hexname;
94 		snprintk(hexname, sizeof(hexname), "%p", (void *)thread);
95 	}
96 
97 	err = k_thread_stack_space_get(thread, &unused);
98 	if (err) {
99 		THREAD_ANALYZER_PRINT(
100 			THREAD_ANALYZER_FMT(
101 				" %-20s: unable to get stack space (%d)"),
102 			name, err);
103 
104 		unused = 0;
105 	}
106 
107 	info.name = name;
108 	info.stack_size = size;
109 	info.stack_used = size - unused;
110 
111 #ifdef CONFIG_THREAD_RUNTIME_STATS
112 	ret = 0;
113 
114 	if (k_thread_runtime_stats_get(thread, &info.usage) != 0) {
115 		ret++;
116 	}
117 
118 	if (k_thread_runtime_stats_all_get(&rt_stats_all) != 0) {
119 		ret++;
120 	}
121 	if (ret == 0) {
122 		info.utilization = (info.usage.execution_cycles * 100U) /
123 			rt_stats_all.execution_cycles;
124 	}
125 #endif
126 	cb(&info);
127 }
128 
129 K_KERNEL_STACK_ARRAY_DECLARE(z_interrupt_stacks, CONFIG_MP_MAX_NUM_CPUS,
130 			     CONFIG_ISR_STACK_SIZE);
131 
isr_stacks(void)132 static void isr_stacks(void)
133 {
134 	unsigned int num_cpus = arch_num_cpus();
135 
136 	for (int i = 0; i < num_cpus; i++) {
137 		const uint8_t *buf = Z_KERNEL_STACK_BUFFER(z_interrupt_stacks[i]);
138 		size_t size = K_KERNEL_STACK_SIZEOF(z_interrupt_stacks[i]);
139 		size_t unused;
140 		int err;
141 
142 		err = z_stack_space_get(buf, size, &unused);
143 		if (err == 0) {
144 			THREAD_ANALYZER_PRINT(
145 				THREAD_ANALYZER_FMT(
146 					" %s%-17d: STACK: unused %zu usage %zu / %zu (%zu %%)"),
147 					THREAD_ANALYZER_VSTR("ISR"), i, unused,
148 					size - unused, size, (100 * (size - unused)) / size);
149 		}
150 	}
151 }
152 
thread_analyzer_run(thread_analyzer_cb cb)153 void thread_analyzer_run(thread_analyzer_cb cb)
154 {
155 	if (IS_ENABLED(CONFIG_THREAD_ANALYZER_RUN_UNLOCKED)) {
156 		k_thread_foreach_unlocked(thread_analyze_cb, cb);
157 	} else {
158 		k_thread_foreach(thread_analyze_cb, cb);
159 	}
160 
161 	if (IS_ENABLED(CONFIG_THREAD_ANALYZER_ISR_STACK_USAGE)) {
162 		isr_stacks();
163 	}
164 }
165 
thread_analyzer_print(void)166 void thread_analyzer_print(void)
167 {
168 	THREAD_ANALYZER_PRINT(THREAD_ANALYZER_FMT("Thread analyze:"));
169 	thread_analyzer_run(thread_print_cb);
170 }
171 
172 #if defined(CONFIG_THREAD_ANALYZER_AUTO)
173 
thread_analyzer_auto(void)174 void thread_analyzer_auto(void)
175 {
176 	for (;;) {
177 		thread_analyzer_print();
178 		k_sleep(K_SECONDS(CONFIG_THREAD_ANALYZER_AUTO_INTERVAL));
179 	}
180 }
181 
182 K_THREAD_DEFINE(thread_analyzer,
183 		CONFIG_THREAD_ANALYZER_AUTO_STACK_SIZE,
184 		thread_analyzer_auto,
185 		NULL, NULL, NULL,
186 		K_LOWEST_APPLICATION_THREAD_PRIO,
187 		0, 0);
188 
189 #endif
190