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