1 /*
2 * Copyright (c) 2020, 2023 Intel Corporation
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 /*
8 * @file measure time for various thread operations
9 *
10 * This file contains the tests that measures the times for the following
11 * thread operations from both kernel threads and user threads:
12 * 1. Creating a thread
13 * 2. Starting a thread
14 * 3. Suspending a thread
15 * 4. Resuming a thread
16 * 5. Aborting a thread
17 *
18 * It is worthwhile to note that there is no measurement for creating a kernel
19 * thread from a user thread as that is an invalid operation.
20 */
21
22 #include <zephyr/kernel.h>
23 #include <zephyr/timing/timing.h>
24 #include "utils.h"
25 #include "timing_sc.h"
26
27 #define STACK_SIZE (512 + CONFIG_TEST_EXTRA_STACK_SIZE)
28
29 #define START_ALT 0x01
30 #define ALT_USER 0x02
31
alt_thread_entry(void * p1,void * p2,void * p3)32 static void alt_thread_entry(void *p1, void *p2, void *p3)
33 {
34 int priority;
35
36 ARG_UNUSED(p1);
37 ARG_UNUSED(p2);
38 ARG_UNUSED(p3);
39
40 /* 3. Finish measuring time to start <alt_thread> */
41
42 timestamp.sample = timing_timestamp_get();
43
44 /* 4. Let <start_thread> process the time measurement. */
45
46 k_sem_take(&pause_sem, K_FOREVER); /* Let 'start_thread' execute */
47
48 /* 7. Begin measuring time to suspend active thread (self/alt_thread) */
49
50 timestamp.sample = timing_timestamp_get();
51 k_thread_suspend(&alt_thread);
52
53 /* 10. Finish measuring time to resume <alt_thread> (self) */
54
55 timestamp.sample = timing_timestamp_get();
56
57 /* 11. Lower the priority so <start_thread> can terminate us. */
58
59 priority = k_thread_priority_get(&alt_thread);
60 k_thread_priority_set(&alt_thread, priority + 2);
61 }
62
start_thread_entry(void * p1,void * p2,void * p3)63 static void start_thread_entry(void *p1, void *p2, void *p3)
64 {
65 uint32_t num_iterations = (uint32_t)(uintptr_t)p1;
66 uint32_t bit_options = (uint32_t)(uintptr_t)p2;
67 timing_t start;
68 timing_t finish;
69 uint64_t thread_create_sum = 0ull;
70 uint64_t thread_start_sum = 0ull;
71 uint64_t thread_suspend_sum = 0ull;
72 uint64_t thread_resume_sum = 0ull;
73 uint64_t thread_abort_sum = 0ull;
74 int priority;
75
76 ARG_UNUSED(p3);
77
78 priority = k_thread_priority_get(&start_thread);
79
80 for (uint32_t i = 0; i < num_iterations; i++) {
81
82 /* 1. Measure time to create, but not start <alt_thread> */
83
84 if ((bit_options & START_ALT) == START_ALT) {
85 start = timing_timestamp_get();
86 k_thread_create(&alt_thread, alt_stack,
87 K_THREAD_STACK_SIZEOF(alt_stack),
88 alt_thread_entry, NULL, NULL, NULL,
89 priority,
90 (bit_options & ALT_USER) == ALT_USER ?
91 K_USER : 0, K_FOREVER);
92 finish = timing_timestamp_get();
93
94 thread_create_sum += timing_cycles_get(&start, &finish);
95 } else {
96
97 /*
98 * Wait for the "main" thread to create <alt_thread>
99 * as this thread can not do it.
100 */
101
102 k_sem_take(&pause_sem, K_FOREVER);
103 }
104
105 if ((bit_options & ALT_USER) == ALT_USER) {
106 k_thread_access_grant(&alt_thread, &pause_sem);
107 }
108
109 /*
110 * Let the main thread change the priority of <alt_thread>
111 * to a higher priority level as user threads may not create
112 * a thread of higher priority than itself.
113 */
114
115 k_sem_take(&pause_sem, K_FOREVER);
116
117
118 /* 2. Begin measuring time to start <alt_thread> */
119
120 start = timing_timestamp_get();
121 k_thread_start(&alt_thread);
122
123 /* 5. Process the time to start <alt_thread> */
124
125 finish = timestamp.sample;
126 thread_start_sum += timing_cycles_get(&start, &finish);
127
128 /* 6. Allow <alt_thread> to continue */
129
130 k_sem_give(&pause_sem);
131
132 /* 8. Finish measuring time to suspend <alt_thread> */
133
134 start = timestamp.sample;
135 finish = timing_timestamp_get();
136 thread_suspend_sum += timing_cycles_get(&start, &finish);
137
138 /* 9. Being measuring time to resume <alt_thread> */
139
140 start = timing_timestamp_get();
141 k_thread_resume(&alt_thread);
142
143 /* 12. Process the time it took to resume <alt_thread> */
144
145 finish = timestamp.sample;
146 thread_resume_sum += timing_cycles_get(&start, &finish);
147
148 /* 13. Process the time to terminate <alt_thread> */
149
150 start = timing_timestamp_get();
151 k_thread_abort(&alt_thread);
152 finish = timing_timestamp_get();
153 thread_abort_sum += timing_cycles_get(&start, &finish);
154 }
155
156 timestamp.cycles = thread_create_sum;
157 k_sem_take(&pause_sem, K_FOREVER);
158
159 timestamp.cycles = thread_start_sum;
160 k_sem_take(&pause_sem, K_FOREVER);
161
162 timestamp.cycles = thread_suspend_sum;
163 k_sem_take(&pause_sem, K_FOREVER);
164
165 timestamp.cycles = thread_resume_sum;
166 k_sem_take(&pause_sem, K_FOREVER);
167
168 timestamp.cycles = thread_abort_sum;
169 k_sem_take(&pause_sem, K_FOREVER);
170 }
171
thread_ops(uint32_t num_iterations,uint32_t start_options,uint32_t alt_options)172 int thread_ops(uint32_t num_iterations, uint32_t start_options, uint32_t alt_options)
173 {
174 int priority;
175 uint64_t cycles;
176 uint32_t bit_options = START_ALT;
177 char tag[50];
178 char description[120];
179
180 priority = k_thread_priority_get(k_current_get());
181
182 timing_start();
183
184 /*
185 * Determine if <start_thread> is allowed to start <alt_thread>.
186 * If it can not, then <alt_thread) must be created by the current
187 * thread.
188 */
189
190 if (((start_options & K_USER) == K_USER) &&
191 ((alt_options & K_USER) == 0)) {
192 bit_options = 0;
193 }
194
195 if ((alt_options & K_USER) == K_USER) {
196 bit_options |= ALT_USER;
197 }
198
199 k_thread_create(&start_thread, start_stack,
200 K_THREAD_STACK_SIZEOF(start_stack),
201 start_thread_entry,
202 (void *)(uintptr_t)num_iterations,
203 (void *)(uintptr_t)bit_options, NULL,
204 priority - 1, start_options, K_FOREVER);
205
206 if ((start_options & K_USER) == K_USER) {
207 k_thread_access_grant(&start_thread, &alt_thread, &alt_stack,
208 &pause_sem);
209 }
210
211 k_thread_start(&start_thread);
212
213 for (uint32_t i = 0; i < num_iterations; i++) {
214 if ((bit_options & START_ALT) == 0) {
215
216 /*
217 * <start_thread> can not create <alt_thread> as it
218 * would be a user thread trying to create a kernel
219 * thread. Instead, create <alt_thread> here.
220 */
221
222 k_thread_create(&alt_thread, alt_stack,
223 K_THREAD_STACK_SIZEOF(alt_stack),
224 alt_thread_entry,
225 NULL, NULL, NULL,
226 priority - 1, alt_options, K_FOREVER);
227
228 /* Give <pause_sem> sends us back to <start_thread> */
229
230 k_sem_give(&pause_sem);
231 }
232
233 /*
234 * <alt_thread> needs to be of higher priority than
235 * <start_thread>, which can not always be done in
236 * <start_thread> as sometimes it is a user thread.
237 */
238
239 k_thread_priority_set(&alt_thread, priority - 2);
240 k_sem_give(&pause_sem);
241 }
242
243 cycles = timestamp.cycles;
244 cycles -= timestamp_overhead_adjustment(start_options, alt_options);
245 k_sem_give(&pause_sem);
246
247 if ((bit_options & START_ALT) == START_ALT) {
248
249 /* Only report stats if <start_thread> created <alt_thread> */
250
251 snprintf(tag, sizeof(tag),
252 "thread.create.%s.from.%s",
253 (alt_options & K_USER) != 0 ? "user" : "kernel",
254 (start_options & K_USER) != 0 ? "user" : "kernel");
255 snprintf(description, sizeof(description),
256 "%-40s - Create thread", tag);
257
258 PRINT_STATS_AVG(description, (uint32_t)cycles,
259 num_iterations, false, "");
260 }
261
262 cycles = timestamp.cycles;
263 cycles -= timestamp_overhead_adjustment(start_options, alt_options);
264 k_sem_give(&pause_sem);
265
266 snprintf(tag, sizeof(tag),
267 "thread.start.%s.from.%s",
268 (alt_options & K_USER) != 0 ? "user" : "kernel",
269 (start_options & K_USER) != 0 ? "user" : "kernel");
270 snprintf(description, sizeof(description),
271 "%-40s - Start thread", tag);
272
273 PRINT_STATS_AVG(description, (uint32_t)cycles,
274 num_iterations, false, "");
275
276 cycles = timestamp.cycles;
277 cycles -= timestamp_overhead_adjustment(start_options, alt_options);
278 k_sem_give(&pause_sem);
279
280 snprintf(tag, sizeof(tag),
281 "thread.suspend.%s.from.%s",
282 (alt_options & K_USER) != 0 ? "user" : "kernel",
283 (start_options & K_USER) != 0 ? "user" : "kernel");
284 snprintf(description, sizeof(description),
285 "%-40s - Suspend thread", tag);
286
287 PRINT_STATS_AVG(description, (uint32_t)cycles,
288 num_iterations, false, "");
289
290 cycles = timestamp.cycles;
291 cycles -= timestamp_overhead_adjustment(start_options, alt_options);
292 k_sem_give(&pause_sem);
293
294 snprintf(tag, sizeof(tag),
295 "thread.resume.%s.from.%s",
296 (alt_options & K_USER) != 0 ? "user" : "kernel",
297 (start_options & K_USER) != 0 ? "user" : "kernel");
298 snprintf(description, sizeof(description),
299 "%-40s - Resume thread", tag);
300
301 PRINT_STATS_AVG(description, (uint32_t)cycles,
302 num_iterations, false, "");
303
304 cycles = timestamp.cycles;
305 cycles -= timestamp_overhead_adjustment(start_options, alt_options);
306 k_sem_give(&pause_sem);
307
308 snprintf(tag, sizeof(tag),
309 "thread.abort.%s.from.%s",
310 (alt_options & K_USER) != 0 ? "user" : "kernel",
311 (start_options & K_USER) != 0 ? "user" : "kernel");
312 snprintf(description, sizeof(description),
313 "%-40s - Abort thread", tag);
314
315 PRINT_STATS_AVG(description, (uint32_t)cycles,
316 num_iterations, false, "");
317
318 timing_stop();
319 return 0;
320 }
321