/* * Copyright (c) 2022 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include #define HELPER_STACK_SIZE 500 /** * @brief Verify @a va1 and @a val2 are within @a pcnt % of each other */ #define TEST_WITHIN_X_PERCENT(val1, val2, pcnt) \ ((((val1) * 100) < ((val2) * (100 + (pcnt)))) && \ (((val1) * 100) > ((val2) * (100 - (pcnt))))) ? true : false #if defined(CONFIG_RISCV) #define IDLE_EVENT_STATS_PRECISION 7 #elif defined(CONFIG_QEMU_TARGET) #define IDLE_EVENT_STATS_PRECISION 3 #else #define IDLE_EVENT_STATS_PRECISION 1 #endif static struct k_thread helper_thread; static K_THREAD_STACK_DEFINE(helper_stack, HELPER_STACK_SIZE); static struct k_thread *main_thread; /** * @brief Helper thread to test_thread_runtime_stats_get() */ void helper1(void *p1, void *p2, void *p3) { while (1) { } } /** * @brief Busy wait for specified number of ticks */ void busy_loop(uint32_t ticks) { uint32_t tick = sys_clock_tick_get_32(); while (sys_clock_tick_get_32() < (tick + ticks)) { } } /** * @brief Test the k_threads_runtime_stats_all_get() API * * 1. Create a helper thread. * 2. Busy loop for 2 ticks. * - Idle time should not increase. * 3. Sleep for two ticks. Helper executes and busy loops. * - Idle time should not increase * 4. Kill helper thread, and sleep for 2 ticks * - Idle time should increase * 5. Busy loop for 3 ticks * - Idle time should not increase * - current, peak and average cycles should be different */ ZTEST(usage_api, test_all_stats_usage) { int priority; k_tid_t tid; k_thread_runtime_stats_t stats1; k_thread_runtime_stats_t stats2; k_thread_runtime_stats_t stats3; k_thread_runtime_stats_t stats4; k_thread_runtime_stats_t stats5; priority = k_thread_priority_get(arch_current_thread()); tid = k_thread_create(&helper_thread, helper_stack, K_THREAD_STACK_SIZEOF(helper_stack), helper1, NULL, NULL, NULL, priority + 2, 0, K_NO_WAIT); k_thread_runtime_stats_all_get(&stats1); busy_loop(2); /* Busy wait 2 ticks */ k_thread_runtime_stats_all_get(&stats2); k_sleep(K_TICKS(2)); /* Helper runs for 2 ticks */ k_thread_runtime_stats_all_get(&stats3); k_thread_abort(tid); k_sleep(K_TICKS(2)); /* Idle for 2 ticks */ k_thread_runtime_stats_all_get(&stats4); busy_loop(3); /* Busy wait for 3 ticks */ k_thread_runtime_stats_all_get(&stats5); /* * Verify that before the system idles for 2 ticks that * [execution_cycles] is increasing, [total_cycles] matches * [execution_cycles] and [idle_cycles] is not changing (as the * system has been idle yet. */ zassert_true(stats2.execution_cycles > stats1.execution_cycles); zassert_true(stats3.execution_cycles > stats2.execution_cycles); zassert_true(stats1.execution_cycles == stats1.total_cycles); zassert_true(stats2.execution_cycles == stats2.total_cycles); zassert_true(stats3.execution_cycles == stats3.total_cycles); #ifdef CONFIG_SCHED_THREAD_USAGE_ALL zassert_true(stats1.idle_cycles == stats2.idle_cycles); zassert_true(stats1.idle_cycles == stats3.idle_cycles); #endif #ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS /* * The analysis fields should behave as follows prior to the system * going idle. * 1. [current_cycles] increases. * 2. [peak_cycles] matches [current_cycles]. * 3. [average_cycles] is 0 (because system has not gone idle yet) * 4. [current_cycles] matches [execution_cycles]. */ zassert_true(stats2.current_cycles > stats1.current_cycles); zassert_true(stats3.current_cycles > stats2.current_cycles); zassert_true(stats1.peak_cycles == stats1.current_cycles); zassert_true(stats2.peak_cycles == stats2.current_cycles); zassert_true(stats3.peak_cycles == stats3.current_cycles); zassert_true(stats1.average_cycles == 0); zassert_true(stats2.average_cycles == 0); zassert_true(stats3.average_cycles == 0); zassert_true(stats1.current_cycles == stats1.execution_cycles); zassert_true(stats2.current_cycles == stats2.execution_cycles); zassert_true(stats3.current_cycles == stats3.execution_cycles); #endif /* * Now process the statistics after the idle event. * * 1. [execution_cycles] continues to increase * 2. [total_cycles] increases * 3. [current_cycles] had a reset event but still increases * 4. [peak_cycles] does not change * 5. [average_cycles] increases * 6. [idle_cycles] increased once. */ zassert_true(stats4.execution_cycles > stats3.execution_cycles); zassert_true(stats5.execution_cycles > stats4.execution_cycles); /* * If the frequency is low enough, the [total_cycles] might not * increase between sample points 3 and 4. Count this as acceptable. */ zassert_true(stats4.total_cycles >= stats3.total_cycles); zassert_true(stats5.total_cycles > stats4.total_cycles); #ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS zassert_true(stats4.current_cycles <= stats1.current_cycles); zassert_true(stats5.current_cycles > stats4.current_cycles); zassert_true(TEST_WITHIN_X_PERCENT(stats4.peak_cycles, stats3.peak_cycles, IDLE_EVENT_STATS_PRECISION), NULL); zassert_true(stats4.peak_cycles == stats5.peak_cycles); zassert_true(stats4.average_cycles > stats3.average_cycles); zassert_true(stats5.average_cycles > stats4.average_cycles); #endif #ifdef CONFIG_SCHED_THREAD_USAGE_ALL zassert_true(stats4.idle_cycles > stats3.idle_cycles); zassert_true(stats4.idle_cycles == stats5.idle_cycles); #endif } #ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS /** * @brief Test the k_thread_runtime_stats_enable/disable APIs */ ZTEST(usage_api, test_thread_stats_enable_disable) { k_tid_t tid; k_thread_runtime_stats_t stats1; k_thread_runtime_stats_t stats2; k_thread_runtime_stats_t helper_stats1; k_thread_runtime_stats_t helper_stats2; k_thread_runtime_stats_t helper_stats3; int priority; priority = k_thread_priority_get(arch_current_thread()); tid = k_thread_create(&helper_thread, helper_stack, K_THREAD_STACK_SIZEOF(helper_stack), helper1, NULL, NULL, NULL, priority + 2, 0, K_NO_WAIT); /* * Sleep to let the helper thread execute for some time before * disabling the runtime stats on the helper thread. */ k_sleep(K_TICKS(5)); k_thread_runtime_stats_get(arch_current_thread(), &stats1); k_thread_runtime_stats_get(tid, &helper_stats1); k_thread_runtime_stats_disable(tid); /* * Busy wait for the remaining tick before re-enabling the thread * runtime stats on the helper thread. */ busy_loop(1); /* Sleep for two ticks to let the helper thread execute. */ k_sleep(K_TICKS(2)); k_thread_runtime_stats_enable(tid); k_thread_runtime_stats_get(arch_current_thread(), &stats2); k_thread_runtime_stats_get(tid, &helper_stats2); /* Sleep for two ticks to let the helper thread execute again. */ k_sleep(K_TICKS(2)); k_thread_runtime_stats_get(tid, &helper_stats3); /* * Verify that the between sample sets 1 and 2 that additional stats * were not gathered for the helper thread, but were gathered for the * main current thread. */ zassert_true(helper_stats1.execution_cycles == helper_stats2.execution_cycles, NULL); zassert_true(stats1.execution_cycles < stats2.execution_cycles); /* * Verify that between sample sets 2 and 3 that additional stats were * gathered for the helper thread. */ zassert_true(helper_stats2.execution_cycles < helper_stats3.execution_cycles, NULL); k_thread_abort(tid); } #else ZTEST(usage_api, test_thread_stats_enable_disable) { } #endif #ifdef CONFIG_SCHED_THREAD_USAGE_ALL /** * @brief Test the k_sys_runtime_stats_enable/disable APIs */ ZTEST(usage_api, test_sys_stats_enable_disable) { k_thread_runtime_stats_t sys_stats1; k_thread_runtime_stats_t sys_stats2; k_thread_runtime_stats_t sys_stats3; k_thread_runtime_stats_t thread_stats1; k_thread_runtime_stats_t thread_stats2; k_thread_runtime_stats_t thread_stats3; /* * Disable system runtime stats gathering. * This should not impact thread runtime stats gathering. */ k_sys_runtime_stats_disable(); k_thread_runtime_stats_get(arch_current_thread(), &thread_stats1); k_thread_runtime_stats_all_get(&sys_stats1); busy_loop(2); k_thread_runtime_stats_get(arch_current_thread(), &thread_stats2); k_thread_runtime_stats_all_get(&sys_stats2); /* * Enable system runtime stats gathering. * This should not impact thread runtime stats gathering. */ k_sys_runtime_stats_enable(); busy_loop(2); k_thread_runtime_stats_get(arch_current_thread(), &thread_stats3); k_thread_runtime_stats_all_get(&sys_stats3); /* * There ought to be no differences between sys_stat1 and sys_stat2. * Although a memory compare of the two structures would be sufficient, * each individual field is being tested in case to more easily * isolate the cause of any error. */ zassert_true(sys_stats1.execution_cycles == sys_stats2.execution_cycles, NULL); zassert_true(sys_stats1.total_cycles == sys_stats2.total_cycles); #ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS zassert_true(sys_stats1.current_cycles == sys_stats2.current_cycles, NULL); zassert_true(sys_stats1.peak_cycles == sys_stats2.peak_cycles); zassert_true(sys_stats1.average_cycles == sys_stats2.average_cycles, NULL); #endif #ifdef CONFIG_SCHED_THREAD_USAGE_ALL zassert_true(sys_stats1.idle_cycles == sys_stats2.idle_cycles); #endif /* * As only system stats have been disabled, thread stats should be * unaffected. To simplify things, just check [execution_cycles] and * [current_cycles] (if enabled). */ zassert_true(thread_stats1.execution_cycles < thread_stats2.execution_cycles, NULL); #ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS zassert_true(thread_stats1.current_cycles < thread_stats2.current_cycles, NULL); #endif /* * Now verify that the enabling of system runtime stats gathering * has resulted in the gathering of system runtime stats. Again, * thread runtime stats gathering should be unaffected. */ zassert_true(sys_stats2.execution_cycles < sys_stats3.execution_cycles, NULL); zassert_true(sys_stats2.total_cycles < sys_stats3.total_cycles); #ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS /* * As enabling reset [current_cycles], it is not easy to predict * what its value should be. For now, settle for ensuring that it * is different and not zero. */ zassert_true(sys_stats2.current_cycles != sys_stats3.current_cycles, NULL); zassert_true(sys_stats2.current_cycles != 0); zassert_true(sys_stats2.peak_cycles == sys_stats3.peak_cycles); zassert_true(sys_stats2.average_cycles > sys_stats3.average_cycles, NULL); #endif #ifdef CONFIG_SCHED_THREAD_USAGE_ALL zassert_true(sys_stats2.idle_cycles == sys_stats3.idle_cycles); #endif } #else ZTEST(usage_api, test_sys_stats_enable_disable) { } #endif /** * @brief Timer handler to resume the main thread */ void resume_main(struct k_timer *timer) { k_thread_resume(main_thread); } /** * @brief Test the k_thread_runtime_stats_get() API * * This routine tests the k_thread_runtime_stats_get() routine. It verifies * that the contents of the fields guarded by CONFIG_SCHED_THREAD_USAGE * are correct. */ ZTEST(usage_api, test_thread_stats_usage) { k_tid_t tid; int priority; int status; struct k_timer timer; k_thread_runtime_stats_t stats1; k_thread_runtime_stats_t stats2; k_thread_runtime_stats_t stats3; priority = k_thread_priority_get(arch_current_thread()); /* * Verify that k_thread_runtime_stats_get() returns the expected * values for error cases. */ status = k_thread_runtime_stats_get(NULL, &stats1); zassert_true(status == -EINVAL); status = k_thread_runtime_stats_get(arch_current_thread(), NULL); zassert_true(status == -EINVAL); /* Align to the next tick */ k_sleep(K_TICKS(1)); /* Create a low priority helper thread to start in 1 tick. */ tid = k_thread_create(&helper_thread, helper_stack, K_THREAD_STACK_SIZEOF(helper_stack), helper1, NULL, NULL, NULL, priority + 2, 0, K_TICKS(1)); main_thread = arch_current_thread(); k_timer_init(&timer, resume_main, NULL); k_timer_start(&timer, K_TICKS(1), K_TICKS(10)); /* Verify thread creation succeeded */ zassert_true(tid == &helper_thread); /* Get a valid set of thread runtime stats */ status = k_thread_runtime_stats_get(tid, &stats1); zassert_true(status == 0); /* * Suspend main thread. Timer will wake it 1 tick to sample * the helper threads runtime stats. */ k_thread_suspend(arch_current_thread()); /* * T = 1. * Timer woke the main thread. Sample runtime stats for helper thread * before suspending. */ k_thread_runtime_stats_get(tid, &stats1); k_thread_suspend(arch_current_thread()); /* * T = 11. * Timer woke the main thread. Suspend main thread again. */ k_thread_suspend(arch_current_thread()); /* * T = 21. * Timer woke the main thread. Sample runtime stats for helper thread * before suspending. */ k_thread_runtime_stats_get(tid, &stats2); k_thread_suspend(arch_current_thread()); /* * T = 31. * Timer woke the main thread. Sample runtime stats for helper thread * and stop the timer. */ k_thread_runtime_stats_get(tid, &stats3); k_timer_stop(&timer); #ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS k_thread_runtime_stats_t stats4; k_thread_runtime_stats_t stats5; /* * Sleep for 20 ticks, and then 1 tick. This will allow the helper * thread to have two different scheduled execution windows. */ k_sleep(K_TICKS(20)); k_thread_runtime_stats_get(tid, &stats4); k_sleep(K_TICKS(1)); k_thread_runtime_stats_get(tid, &stats5); #endif /* Verify execution_cycles are identical to total_cycles */ zassert_true(stats1.execution_cycles == stats1.total_cycles); zassert_true(stats2.execution_cycles == stats2.total_cycles); #ifdef CONFIG_SCHED_THREAD_USAGE_ALL zassert_true(stats3.idle_cycles == 0); #endif /* * Verify that the time for which the helper thread executed between * the first and second samplings is more than that between the * second and third. */ uint64_t diff12; uint64_t diff23; diff12 = stats2.execution_cycles - stats1.execution_cycles; diff23 = stats3.execution_cycles - stats2.execution_cycles; zassert_true(diff12 > diff23); #ifdef CONFIG_SCHED_THREAD_USAGE_ANALYSIS /* Verify that [current_cycles] change as expected. */ zassert_true(stats4.current_cycles >= stats5.current_cycles); zassert_true(stats4.current_cycles > stats3.current_cycles); zassert_true(stats5.current_cycles < stats3.current_cycles); /* Verify that [peak_cycles] change as expected */ zassert_true(stats4.peak_cycles > stats2.peak_cycles); zassert_true(stats4.peak_cycles == stats5.peak_cycles); zassert_true(stats4.peak_cycles == stats4.current_cycles); /* Verify that [average_cycles] change as expected */ zassert_true(stats4.average_cycles > stats3.average_cycles); zassert_true(stats4.average_cycles > stats5.average_cycles); zassert_true(stats5.average_cycles >= stats3.average_cycles); #endif /* Abort the helper thread */ k_thread_abort(tid); } ZTEST_SUITE(usage_api, NULL, NULL, ztest_simple_1cpu_before, ztest_simple_1cpu_after, NULL);