blob: 211cc11004ca8c4ddf4d8303c0f571eb58ec77f4 [file] [log] [blame]
/*
* Copyright (c) 2022 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/ztest.h>
#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
#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(_current);
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(_current);
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(_current, &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(_current, &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(_current, &thread_stats1);
k_thread_runtime_stats_all_get(&sys_stats1);
busy_loop(2);
k_thread_runtime_stats_get(_current, &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(_current, &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(_current);
/*
* 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(_current, 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 = _current;
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(_current);
/*
* 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(_current);
/*
* T = 11.
* Timer woke the main thread. Suspend main thread again.
*/
k_thread_suspend(_current);
/*
* 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(_current);
/*
* 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);