Added framework for concurrency testing. Framework setup multiple priority contexts and executes user handlers in that context. Test terminates after certain number of repetitions or preemptions or when timeout occurs. It can also be aborted by the user. Framework can be used for testing resiliency to preemptions. Signed-off-by: Krzysztof Chruscinski <krzysztof.chruscinski@nordicsemi.no>
232 lines
7.9 KiB
C
232 lines
7.9 KiB
C
/*
|
|
* Copyright (c) 2021 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
#ifndef TESTSUITE_ZTEST_INCLUDE_ZTRESS_H__
|
|
#define TESTSUITE_ZTEST_INCLUDE_ZTRESS_H__
|
|
|
|
#include <sys/util.h>
|
|
#include <kernel.h>
|
|
|
|
/** @internal Internal ID's to distinguish context type. */
|
|
#define ZTRESS_ID_THREAD 0
|
|
#define ZTRESS_ID_K_TIMER 1
|
|
|
|
/** @brief Descriptor of a k_timer handler execution context.
|
|
*
|
|
* The handler is executed in the k_timer handler context which typically means
|
|
* interrupt context. This context will preempt any other used in the set.
|
|
*
|
|
* @note There can only be up to one k_timer context in the set and it must be the
|
|
* first argument of @ref ZTRESS_EXECUTE.
|
|
*
|
|
* @param handler User handler of type @ref ztress_handler.
|
|
*
|
|
* @param user_data User data passed to the @p handler.
|
|
*
|
|
* @param exec_cnt Number of handler executions to complete the test. If 0 then
|
|
* this is not included in completion criteria.
|
|
*
|
|
* @param init_timeout Initial backoff time base (given in @ref k_timeout_t). It
|
|
* is adjusted during the test to optimize CPU load. The actual timeout used for
|
|
* the timer is randomized.
|
|
*/
|
|
#define ZTRESS_TIMER(handler, user_data, exec_cnt, init_timeout) \
|
|
(ZTRESS_ID_K_TIMER, handler, user_data, exec_cnt, 0, init_timeout)
|
|
|
|
/** @brief Descriptor of a thread execution context.
|
|
*
|
|
* The handler is executed in the thread context. The priority of the thread is
|
|
* determined based on the order in which contexts are listed in @ref ZTRESS_EXECUTE.
|
|
*
|
|
* @note thread sleeps for random amount of time. Additionally, the thread busy-waits
|
|
* for a random length of time to further increase randomization in the test.
|
|
*
|
|
* @param handler User handler of type @ref ztress_handler.
|
|
*
|
|
* @param user_data User data passed to the @p handler.
|
|
*
|
|
* @param exec_cnt Number of handler executions to complete the test. If 0 then
|
|
* this is not included in completion criteria.
|
|
*
|
|
* @param preempt_cnt Number of preemptions of that context to complete the test.
|
|
* If 0 then this is not included in completion criteria.
|
|
*
|
|
* @param init_timeout Initial backoff time base (given in @ref k_timeout_t). It
|
|
* is adjusted during the test to optimize CPU load. The actual timeout used for
|
|
* sleeping is randomized.
|
|
*/
|
|
#define ZTRESS_THREAD(handler, user_data, exec_cnt, preempt_cnt, init_timeout) \
|
|
(ZTRESS_ID_THREAD, handler, user_data, exec_cnt, preempt_cnt, init_timeout)
|
|
|
|
/** @brief User handler called in one of the configured contexts.
|
|
*
|
|
* @param user_data User data provided in the context descriptor.
|
|
*
|
|
* @param cnt Current execution counter. Counted from 0.
|
|
*
|
|
* @param last Flag set to true indicates that it is the last execution because
|
|
* completion criteria are met, test timed out or was aborted.
|
|
*
|
|
* @param prio Context priority counting from 0 which indicates the highest priority.
|
|
*
|
|
* @retval true continue test.
|
|
* @retval false stop executing the current context.
|
|
*/
|
|
typedef bool (*ztress_handler)(void *user_data, uint32_t cnt, bool last, int prio);
|
|
|
|
/** @internal Context structure. */
|
|
struct ztress_context_data {
|
|
/* Handler. */
|
|
ztress_handler handler;
|
|
|
|
/* User data */
|
|
void *user_data;
|
|
|
|
/* Minimum number of executions to complete the test. */
|
|
uint32_t exec_cnt;
|
|
|
|
/* Minimum number of preemptions to complete the test. Valid only for
|
|
* thread context.
|
|
*/
|
|
uint32_t preempt_cnt;
|
|
|
|
/* Initial timeout. */
|
|
k_timeout_t t;
|
|
};
|
|
|
|
/** @brief Initialize context structure.
|
|
*
|
|
* For argument types see @ref ztress_context_data. For more details see
|
|
* @ref ZTRESS_THREAD.
|
|
*
|
|
* @param _handler Handler.
|
|
* @param _user_data User data passed to the handler.
|
|
* @param _exec_cnt Execution count limit.
|
|
* @param _preempt_cnt Preemption count limit.
|
|
* @param _t Initial timeout.
|
|
*/
|
|
#define ZTRESS_CONTEXT_INITIALIZER(_handler, _user_data, _exec_cnt, _preempt_cnt, _t) \
|
|
{ \
|
|
.handler = (_handler), \
|
|
.user_data = (_user_data), \
|
|
.exec_cnt = (_exec_cnt), \
|
|
.preempt_cnt = (_preempt_cnt), \
|
|
.t = (_t) \
|
|
}
|
|
|
|
/** @internal Strip first argument (context type) and call struct initializer. */
|
|
#define Z_ZTRESS_GET_HANDLER_DATA2(_, ...) \
|
|
ZTRESS_CONTEXT_INITIALIZER(__VA_ARGS__)
|
|
|
|
/** @internal Macro for initializing context data. */
|
|
#define Z_ZTRESS_GET_HANDLER_DATA(data) \
|
|
Z_ZTRESS_GET_HANDLER_DATA2 data
|
|
|
|
/** @internal Macro for checking if provided context is a timer context. */
|
|
#define Z_ZTRESS_HAS_TIMER(data, ...) \
|
|
GET_ARG_N(1, __DEBRACKET data)
|
|
|
|
/** @internal If context descriptor is @ref ZTRESS_TIMER, it returns index of that
|
|
* descriptor in the list of arguments.
|
|
*/
|
|
#define Z_ZTRESS_TIMER_IDX(idx, data) \
|
|
((GET_ARG_N(1, __DEBRACKET data)) == ZTRESS_ID_K_TIMER ? idx : 0)
|
|
|
|
/** @intenal Macro validates that @ref ZTRESS_TIMER context is not used except for
|
|
* the first item in the list of contexts.
|
|
*/
|
|
#define Z_ZTRESS_TIMER_CONTEXT_VALIDATE(...) \
|
|
BUILD_ASSERT((FOR_EACH_IDX(Z_ZTRESS_TIMER_IDX, (+), __VA_ARGS__)) == 0, \
|
|
"There can only be up to one ZTRESS_TIMER context and it must " \
|
|
"be the first in the list")
|
|
|
|
/** @brief Setup and run stress test.
|
|
*
|
|
* It initialises all contexts and calls @ref ztress_execute.
|
|
*
|
|
* @param ... List of contexts. Contexts are configured using @ref ZTRESS_TIMER
|
|
* and @ref ZTRESS_THREAD macros. @ref ZTRESS_TIMER must be the first argument if
|
|
* used. Each thread context has an assigned priority. The priority is assigned in
|
|
* a descending order (first listed thread context has the highest priority). The
|
|
* number of supported thread contexts is configurable in Kconfig.
|
|
*/
|
|
#define ZTRESS_EXECUTE(...) do { \
|
|
Z_ZTRESS_TIMER_CONTEXT_VALIDATE(__VA_ARGS__); \
|
|
int has_timer = Z_ZTRESS_HAS_TIMER(__VA_ARGS__); \
|
|
struct ztress_context_data data[] = { \
|
|
FOR_EACH(Z_ZTRESS_GET_HANDLER_DATA, (,), __VA_ARGS__) \
|
|
}; \
|
|
size_t cnt = ARRAY_SIZE(data) - has_timer; \
|
|
int err = ztress_execute(has_timer ? &data[0] : NULL, &data[has_timer], cnt); \
|
|
\
|
|
zassert_equal(err, 0, "ztress_execute failed (err: %d)", err); \
|
|
} while (0)
|
|
|
|
/** Execute contexts.
|
|
*
|
|
* The test runs until all completion requirements are met or until the test times out
|
|
* (use @ref ztress_set_timeout to configure timeout) or until the test is aborted
|
|
* (@ref ztress_abort).
|
|
*
|
|
* on test completion a report is printed (@ref ztress_report is called internally).
|
|
*
|
|
* @param timer_data Timer context. NULL if timer context is not used.
|
|
* @param thread_data List of thread contexts descriptors in priority descending order.
|
|
* @param cnt Number of thread contexts.
|
|
*
|
|
* @retval -EINVAL If configuration is invalid.
|
|
* @retval 0 if test is successfully performed.
|
|
*/
|
|
int ztress_execute(struct ztress_context_data *timer_data,
|
|
struct ztress_context_data *thread_data,
|
|
size_t cnt);
|
|
|
|
/** @brief Abort ongoing stress test. */
|
|
void ztress_abort(void);
|
|
|
|
/** @brief Set test timeout.
|
|
*
|
|
* Test is terminated after timeout disregarding completion criteria. Setting
|
|
* is persistent between executions.
|
|
*
|
|
* @param t Timeout.
|
|
*/
|
|
void ztress_set_timeout(k_timeout_t t);
|
|
|
|
/** @brief Print last test report.
|
|
*
|
|
* Report contains number of executions and preemptions for each context, initial
|
|
* and adjusted timeouts and CPU load during the test.
|
|
*/
|
|
void ztress_report(void);
|
|
|
|
/** @brief Get number of executions of a given context in the last test.
|
|
*
|
|
* @param id Context id. 0 means the highest priority.
|
|
*
|
|
* @return Number of executions.
|
|
*/
|
|
int ztress_exec_count(uint32_t id);
|
|
|
|
/** @brief Get number of preemptions of a given context in the last test.
|
|
*
|
|
* @param id Context id. 0 means the highest priority.
|
|
*
|
|
* @return Number of preemptions.
|
|
*/
|
|
int ztress_preempt_count(uint32_t id);
|
|
|
|
/** @brief Get optimized timeout base of a given context in the last test.
|
|
*
|
|
* Optimized value can be used to update initial value. It will improve the test
|
|
* since optimal CPU load will be reach immediately.
|
|
*
|
|
* @param id Context id. 0 means the highest priority.
|
|
*
|
|
* @return Optimized timeout base.
|
|
*/
|
|
uint32_t ztress_optimized_ticks(uint32_t id);
|
|
|
|
#endif /* TESTSUITE_ZTEST_INCLUDE_ZTRESS_H__ */
|