1 /*
2 * Copyright (c) 2022 Intel Corporation
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/ztest.h>
8
9 #define HELPER_STACK_SIZE 500
10
11 /**
12 * @brief Verify @a va1 and @a val2 are within @a pcnt % of each other
13 */
14 #define TEST_WITHIN_X_PERCENT(val1, val2, pcnt) \
15 ((((val1) * 100) < ((val2) * (100 + (pcnt)))) && \
16 (((val1) * 100) > ((val2) * (100 - (pcnt))))) ? true : false
17
18 #if defined(CONFIG_RISCV)
19 #define IDLE_EVENT_STATS_PRECISION 7
20 #elif defined(CONFIG_QEMU_TARGET)
21 #define IDLE_EVENT_STATS_PRECISION 3
22 #else
23 #define IDLE_EVENT_STATS_PRECISION 1
24 #endif
25
26 static struct k_thread helper_thread;
27 static K_THREAD_STACK_DEFINE(helper_stack, HELPER_STACK_SIZE);
28
29 static struct k_thread *main_thread;
30
31 /**
32 * @brief Helper thread to test_thread_runtime_stats_get()
33 */
helper1(void * p1,void * p2,void * p3)34 void helper1(void *p1, void *p2, void *p3)
35 {
36 /* Using volatile condition to prevent compilers from optimizing while(true) */
37 volatile bool condition = true;
38
39 while (condition) {
40 }
41 }
42
43 /**
44 * @brief Busy wait for specified number of ticks
45 */
busy_loop(uint32_t ticks)46 void busy_loop(uint32_t ticks)
47 {
48 uint32_t tick = sys_clock_tick_get_32();
49
50 while (sys_clock_tick_get_32() < (tick + ticks)) {
51 }
52 }
53
54 /**
55 * @brief Test the k_threads_runtime_stats_all_get() API
56 *
57 * 1. Create a helper thread.
58 * 2. Busy loop for 2 ticks.
59 * - Idle time should not increase.
60 * 3. Sleep for two ticks. Helper executes and busy loops.
61 * - Idle time should not increase
62 * 4. Kill helper thread, and sleep for 2 ticks
63 * - Idle time should increase
64 * 5. Busy loop for 3 ticks
65 * - Idle time should not increase
66 * - current, peak and average cycles should be different
67 */
ZTEST(usage_api,test_all_stats_usage)68 ZTEST(usage_api, test_all_stats_usage)
69 {
70 int priority;
71 k_tid_t tid;
72 k_thread_runtime_stats_t stats1;
73 k_thread_runtime_stats_t stats2;
74 k_thread_runtime_stats_t stats3;
75 k_thread_runtime_stats_t stats4;
76 k_thread_runtime_stats_t stats5;
77
78 priority = k_thread_priority_get(_current);
79 tid = k_thread_create(&helper_thread, helper_stack,
80 K_THREAD_STACK_SIZEOF(helper_stack),
81 helper1, NULL, NULL, NULL,
82 priority + 2, 0, K_NO_WAIT);
83
84 k_thread_runtime_stats_all_get(&stats1);
85
86 busy_loop(2); /* Busy wait 2 ticks */
87
88 k_thread_runtime_stats_all_get(&stats2);
89
90 #if defined(CONFIG_RISCV)
91 k_sleep(K_TICKS(3)); /* Helper runs for 3 ticks - on slower platforms */
92 #else
93 k_sleep(K_TICKS(2)); /* Helper runs for 2 ticks */
94 #endif
95
96 k_thread_runtime_stats_all_get(&stats3);
97
98 k_thread_abort(tid);
99
100 k_sleep(K_TICKS(2)); /* Idle for 2 ticks */
101
102 k_thread_runtime_stats_all_get(&stats4);
103
104 busy_loop(3); /* Busy wait for 3 ticks */
105
106 k_thread_runtime_stats_all_get(&stats5);
107
108 /*
109 * Verify that before the system idles for 2 ticks that
110 * [execution_cycles] is increasing, [total_cycles + idle_cycles] matches
111 * [execution_cycles] and [idle_cycles] is not changing (as the
112 * system is not going to idle during that test).
113 */
114
115 zassert_true(stats2.execution_cycles > stats1.execution_cycles);
116 zassert_true(stats3.execution_cycles > stats2.execution_cycles);
117 zassert_true(stats1.execution_cycles == (stats1.total_cycles + stats1.idle_cycles));
118 zassert_true(stats2.execution_cycles == (stats2.total_cycles + stats2.idle_cycles));
119 zassert_true(stats3.execution_cycles == (stats3.total_cycles + stats3.idle_cycles));
120 #ifdef CONFIG_SCHED_THREAD_USAGE_ALL
121 zassert_true(stats1.idle_cycles == stats2.idle_cycles);
122 zassert_true(stats1.idle_cycles == stats3.idle_cycles);
123 #endif
124
125 #ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS
126
127 /*
128 * The analysis fields should behave as follows prior to the system
129 * going idle.
130 * 1. [current_cycles] increases.
131 * 2. [peak_cycles] matches [current_cycles].
132 * 3. [average_cycles] is 0 if system has not gone idle yet
133 * 4. [current_cycles] matches [execution_cycles] if system has not gone idle yet
134 */
135
136 zassert_true(stats2.current_cycles > stats1.current_cycles);
137 zassert_true(stats3.current_cycles > stats2.current_cycles);
138
139 zassert_true(stats1.peak_cycles == stats1.current_cycles);
140 zassert_true(stats2.peak_cycles == stats2.current_cycles);
141 zassert_true(stats3.peak_cycles == stats3.current_cycles);
142
143 if (stats1.idle_cycles == 0) {
144 zassert_true(stats1.average_cycles == 0);
145 zassert_true(stats2.average_cycles == 0);
146 zassert_true(stats3.average_cycles == 0);
147
148 zassert_true(stats1.current_cycles == stats1.execution_cycles);
149 zassert_true(stats2.current_cycles == stats2.execution_cycles);
150 zassert_true(stats3.current_cycles == stats3.execution_cycles);
151 } else {
152 zassert_true(stats1.current_cycles < stats1.execution_cycles);
153 zassert_true(stats2.current_cycles < stats2.execution_cycles);
154 zassert_true(stats3.current_cycles < stats3.execution_cycles);
155 }
156 #endif
157
158 /*
159 * Now process the statistics after the idle event.
160 *
161 * 1. [execution_cycles] continues to increase
162 * 2. [total_cycles] increases
163 * 3. [current_cycles] had a reset event but still increases
164 * 4. [peak_cycles] does not change
165 * 5. [average_cycles] increases
166 * 6. [idle_cycles] increased once.
167 */
168
169 zassert_true(stats4.execution_cycles > stats3.execution_cycles);
170 zassert_true(stats5.execution_cycles > stats4.execution_cycles);
171
172 /*
173 * If the frequency is low enough, the [total_cycles] might not
174 * increase between sample points 3 and 4. Count this as acceptable.
175 */
176
177 zassert_true(stats4.total_cycles >= stats3.total_cycles);
178 zassert_true(stats5.total_cycles > stats4.total_cycles);
179
180 #ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS
181 zassert_true(stats4.current_cycles <= stats1.current_cycles);
182 zassert_true(stats5.current_cycles > stats4.current_cycles);
183
184 zassert_true(TEST_WITHIN_X_PERCENT(stats4.peak_cycles,
185 stats3.peak_cycles, IDLE_EVENT_STATS_PRECISION), NULL);
186 zassert_true(stats4.peak_cycles == stats5.peak_cycles);
187
188 zassert_true(stats4.average_cycles > 0);
189 zassert_true(stats5.average_cycles > stats4.average_cycles);
190 #endif
191
192 #ifdef CONFIG_SCHED_THREAD_USAGE_ALL
193 zassert_true(stats4.idle_cycles > stats3.idle_cycles);
194 zassert_true(stats4.idle_cycles == stats5.idle_cycles);
195 #endif
196 }
197
198 #ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS
199 /**
200 * @brief Test the k_thread_runtime_stats_enable/disable APIs
201 */
ZTEST(usage_api,test_thread_stats_enable_disable)202 ZTEST(usage_api, test_thread_stats_enable_disable)
203 {
204 k_tid_t tid;
205 k_thread_runtime_stats_t stats1;
206 k_thread_runtime_stats_t stats2;
207 k_thread_runtime_stats_t helper_stats1;
208 k_thread_runtime_stats_t helper_stats2;
209 k_thread_runtime_stats_t helper_stats3;
210 int priority;
211
212 priority = k_thread_priority_get(_current);
213 tid = k_thread_create(&helper_thread, helper_stack,
214 K_THREAD_STACK_SIZEOF(helper_stack),
215 helper1, NULL, NULL, NULL,
216 priority + 2, 0, K_NO_WAIT);
217
218 /*
219 * Sleep to let the helper thread execute for some time before
220 * disabling the runtime stats on the helper thread.
221 */
222
223 k_sleep(K_TICKS(5));
224
225 k_thread_runtime_stats_get(_current, &stats1);
226 k_thread_runtime_stats_get(tid, &helper_stats1);
227 k_thread_runtime_stats_disable(tid);
228
229 /*
230 * Busy wait for the remaining tick before re-enabling the thread
231 * runtime stats on the helper thread.
232 */
233
234 busy_loop(1);
235
236 /* Sleep for two ticks to let the helper thread execute. */
237
238 k_sleep(K_TICKS(2));
239
240 k_thread_runtime_stats_enable(tid);
241 k_thread_runtime_stats_get(_current, &stats2);
242 k_thread_runtime_stats_get(tid, &helper_stats2);
243
244 /* Sleep for two ticks to let the helper thread execute again. */
245
246 k_sleep(K_TICKS(2));
247
248 k_thread_runtime_stats_get(tid, &helper_stats3);
249
250 /*
251 * Verify that the between sample sets 1 and 2 that additional stats
252 * were not gathered for the helper thread, but were gathered for the
253 * main current thread.
254 */
255
256 zassert_true(helper_stats1.execution_cycles ==
257 helper_stats2.execution_cycles, NULL);
258 zassert_true(stats1.execution_cycles < stats2.execution_cycles);
259
260 /*
261 * Verify that between sample sets 2 and 3 that additional stats were
262 * gathered for the helper thread.
263 */
264
265 zassert_true(helper_stats2.execution_cycles <
266 helper_stats3.execution_cycles, NULL);
267
268 k_thread_abort(tid);
269 }
270 #else
ZTEST(usage_api,test_thread_stats_enable_disable)271 ZTEST(usage_api, test_thread_stats_enable_disable)
272 {
273 }
274 #endif
275
276 #ifdef CONFIG_SCHED_THREAD_USAGE_ALL
277 /**
278 * @brief Test the k_sys_runtime_stats_enable/disable APIs
279 */
ZTEST(usage_api,test_sys_stats_enable_disable)280 ZTEST(usage_api, test_sys_stats_enable_disable)
281 {
282 k_thread_runtime_stats_t sys_stats1;
283 k_thread_runtime_stats_t sys_stats2;
284 k_thread_runtime_stats_t sys_stats3;
285 k_thread_runtime_stats_t thread_stats1;
286 k_thread_runtime_stats_t thread_stats2;
287 k_thread_runtime_stats_t thread_stats3;
288
289 /*
290 * Disable system runtime stats gathering.
291 * This should not impact thread runtime stats gathering.
292 */
293
294 k_sys_runtime_stats_disable();
295
296 k_thread_runtime_stats_get(_current, &thread_stats1);
297 k_thread_runtime_stats_all_get(&sys_stats1);
298
299 busy_loop(2);
300
301 k_thread_runtime_stats_get(_current, &thread_stats2);
302 k_thread_runtime_stats_all_get(&sys_stats2);
303
304 /*
305 * Enable system runtime stats gathering.
306 * This should not impact thread runtime stats gathering.
307 */
308
309 k_sys_runtime_stats_enable();
310
311 busy_loop(2);
312
313 k_thread_runtime_stats_get(_current, &thread_stats3);
314 k_thread_runtime_stats_all_get(&sys_stats3);
315
316 /*
317 * There ought to be no differences between sys_stat1 and sys_stat2.
318 * Although a memory compare of the two structures would be sufficient,
319 * each individual field is being tested in case to more easily
320 * isolate the cause of any error.
321 */
322
323 zassert_true(sys_stats1.execution_cycles == sys_stats2.execution_cycles,
324 NULL);
325 zassert_true(sys_stats1.total_cycles == sys_stats2.total_cycles);
326
327 #ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS
328 zassert_true(sys_stats1.current_cycles == sys_stats2.current_cycles,
329 NULL);
330 zassert_true(sys_stats1.peak_cycles == sys_stats2.peak_cycles);
331 zassert_true(sys_stats1.average_cycles == sys_stats2.average_cycles,
332 NULL);
333 #endif
334
335 #ifdef CONFIG_SCHED_THREAD_USAGE_ALL
336 zassert_true(sys_stats1.idle_cycles == sys_stats2.idle_cycles);
337 #endif
338
339 /*
340 * As only system stats have been disabled, thread stats should be
341 * unaffected. To simplify things, just check [execution_cycles] and
342 * [current_cycles] (if enabled).
343 */
344
345 zassert_true(thread_stats1.execution_cycles <
346 thread_stats2.execution_cycles, NULL);
347
348 #ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS
349 zassert_true(thread_stats1.current_cycles <
350 thread_stats2.current_cycles, NULL);
351 #endif
352
353 /*
354 * Now verify that the enabling of system runtime stats gathering
355 * has resulted in the gathering of system runtime stats. Again,
356 * thread runtime stats gathering should be unaffected.
357 */
358
359 zassert_true(sys_stats2.execution_cycles < sys_stats3.execution_cycles,
360 NULL);
361 zassert_true(sys_stats2.total_cycles < sys_stats3.total_cycles);
362
363 #ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS
364
365 /*
366 * As enabling reset [current_cycles], it is not easy to predict
367 * what its value should be. For now, settle for ensuring that it
368 * is different and not zero.
369 */
370
371 zassert_true(sys_stats2.current_cycles != sys_stats3.current_cycles,
372 NULL);
373 zassert_true(sys_stats2.current_cycles != 0);
374 zassert_true(sys_stats2.peak_cycles == sys_stats3.peak_cycles);
375 zassert_true(sys_stats2.average_cycles > sys_stats3.average_cycles,
376 NULL);
377 #endif
378
379 #ifdef CONFIG_SCHED_THREAD_USAGE_ALL
380 zassert_true(sys_stats2.idle_cycles == sys_stats3.idle_cycles);
381 #endif
382 }
383 #else
ZTEST(usage_api,test_sys_stats_enable_disable)384 ZTEST(usage_api, test_sys_stats_enable_disable)
385 {
386 }
387 #endif
388
389 /**
390 * @brief Timer handler to resume the main thread
391 */
resume_main(struct k_timer * timer)392 void resume_main(struct k_timer *timer)
393 {
394 k_thread_resume(main_thread);
395 }
396
397 /**
398 * @brief Test the k_thread_runtime_stats_get() API
399 *
400 * This routine tests the k_thread_runtime_stats_get() routine. It verifies
401 * that the contents of the fields guarded by CONFIG_SCHED_THREAD_USAGE
402 * are correct.
403 */
ZTEST(usage_api,test_thread_stats_usage)404 ZTEST(usage_api, test_thread_stats_usage)
405 {
406 k_tid_t tid;
407 int priority;
408 int status;
409 struct k_timer timer;
410 k_thread_runtime_stats_t stats1;
411 k_thread_runtime_stats_t stats2;
412 k_thread_runtime_stats_t stats3;
413
414 priority = k_thread_priority_get(_current);
415
416 /*
417 * Verify that k_thread_runtime_stats_get() returns the expected
418 * values for error cases.
419 */
420
421 status = k_thread_runtime_stats_get(NULL, &stats1);
422 zassert_true(status == -EINVAL);
423
424 status = k_thread_runtime_stats_get(_current, NULL);
425 zassert_true(status == -EINVAL);
426
427 /* Align to the next tick */
428
429 k_sleep(K_TICKS(1));
430
431 /* Create a low priority helper thread to start in 1 tick. */
432
433 tid = k_thread_create(&helper_thread, helper_stack,
434 K_THREAD_STACK_SIZEOF(helper_stack),
435 helper1, NULL, NULL, NULL,
436 priority + 2, 0, K_TICKS(1));
437
438 main_thread = _current;
439 k_timer_init(&timer, resume_main, NULL);
440 k_timer_start(&timer, K_TICKS(1), K_TICKS(10));
441
442 /* Verify thread creation succeeded */
443
444 zassert_true(tid == &helper_thread);
445
446 /* Get a valid set of thread runtime stats */
447
448 status = k_thread_runtime_stats_get(tid, &stats1);
449 zassert_true(status == 0);
450
451 /*
452 * Suspend main thread. Timer will wake it 1 tick to sample
453 * the helper threads runtime stats.
454 */
455
456 k_thread_suspend(_current);
457
458 /*
459 * T = 1.
460 * Timer woke the main thread. Sample runtime stats for helper thread
461 * before suspending.
462 */
463
464 k_thread_runtime_stats_get(tid, &stats1);
465 k_thread_suspend(_current);
466
467 /*
468 * T = 11.
469 * Timer woke the main thread. Suspend main thread again.
470 */
471
472 k_thread_suspend(_current);
473
474 /*
475 * T = 21.
476 * Timer woke the main thread. Sample runtime stats for helper thread
477 * before suspending.
478 */
479
480 k_thread_runtime_stats_get(tid, &stats2);
481 k_thread_suspend(_current);
482
483 /*
484 * T = 31.
485 * Timer woke the main thread. Sample runtime stats for helper thread
486 * and stop the timer.
487 */
488
489 k_thread_runtime_stats_get(tid, &stats3);
490 k_timer_stop(&timer);
491
492 #ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS
493 k_thread_runtime_stats_t stats4;
494 k_thread_runtime_stats_t stats5;
495
496 /*
497 * Sleep for 20 ticks, and then 1 tick. This will allow the helper
498 * thread to have two different scheduled execution windows.
499 */
500
501 k_sleep(K_TICKS(20));
502 k_thread_runtime_stats_get(tid, &stats4);
503
504 k_sleep(K_TICKS(1));
505 k_thread_runtime_stats_get(tid, &stats5);
506 #endif
507
508 /* Verify execution_cycles are identical to total_cycles */
509
510 zassert_true(stats1.execution_cycles == stats1.total_cycles);
511 zassert_true(stats2.execution_cycles == stats2.total_cycles);
512
513 #ifdef CONFIG_SCHED_THREAD_USAGE_ALL
514 zassert_true(stats3.idle_cycles == 0);
515 #endif
516
517 /*
518 * Verify that the time for which the helper thread executed between
519 * the first and second samplings is more than that between the
520 * second and third.
521 */
522
523 uint64_t diff12;
524 uint64_t diff23;
525
526 diff12 = stats2.execution_cycles - stats1.execution_cycles;
527 diff23 = stats3.execution_cycles - stats2.execution_cycles;
528
529 zassert_true(diff12 > diff23);
530
531 #ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS
532
533 /* Verify that [current_cycles] change as expected. */
534
535 zassert_true(stats4.current_cycles >= stats5.current_cycles);
536 zassert_true(stats4.current_cycles > stats3.current_cycles);
537 zassert_true(stats5.current_cycles < stats3.current_cycles);
538
539 /* Verify that [peak_cycles] change as expected */
540
541 zassert_true(stats4.peak_cycles > stats2.peak_cycles);
542 zassert_true(stats4.peak_cycles == stats5.peak_cycles);
543 zassert_true(stats4.peak_cycles == stats4.current_cycles);
544
545 /* Verify that [average_cycles] change as expected */
546
547 zassert_true(stats4.average_cycles > stats3.average_cycles);
548 zassert_true(stats4.average_cycles > stats5.average_cycles);
549 zassert_true(stats5.average_cycles >= stats3.average_cycles);
550 #endif
551
552 /* Abort the helper thread */
553
554 k_thread_abort(tid);
555 }
556
557 ZTEST_SUITE(usage_api, NULL, NULL,
558 ztest_simple_1cpu_before, ztest_simple_1cpu_after, NULL);
559