tests: latency_measure: Update context switch

Updates both the preemptive and cooperative thread context switch
benchmark tests so that not only will they share the same
infrastructure, but they can both get the context switch times
when switching from ...
  1. Kernel thread to kernel thread
  2. Kernel thread to user thread
  3. User thread to kernel thread
  4. User thread to user thread

Signed-off-by: Peter Mitsis <peter.mitsis@intel.com>
This commit is contained in:
Peter Mitsis 2023-09-01 14:27:33 -04:00 committed by Carles Cufí
commit 8c80bbb96e
4 changed files with 197 additions and 234 deletions

View file

@ -1,143 +0,0 @@
/*
* Copyright (c) 2012-2014 Wind River Systems, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
*
* @brief Measure context switch time between cooperative threads
*
* This file contains thread (coop) context switch time measurement.
* The thread starts two cooperative thread. One thread waits on a semaphore. The other,
* after starting, releases a semaphore which enable the first thread to run.
* Each thread increases a common global counter and context switch back and
* forth by yielding the cpu. When counter reaches the maximal value, threads
* stop and the average time of context switch is displayed.
*/
#include <zephyr/kernel.h>
#include <zephyr/timing/timing.h>
#include "utils.h"
/* number of context switches */
#define NCTXSWITCH 10000
#ifndef STACKSIZE
#define STACKSIZE (512 + CONFIG_TEST_EXTRA_STACK_SIZE)
#endif
/* stack used by the threads */
static K_THREAD_STACK_DEFINE(thread_one_stack, STACKSIZE);
static K_THREAD_STACK_DEFINE(thread_two_stack, STACKSIZE);
static struct k_thread thread_one_data;
static struct k_thread thread_two_data;
static timing_t timestamp_start;
static timing_t timestamp_end;
/* context switches counter */
static volatile uint32_t ctx_switch_counter;
/* context switch balancer. Incremented by one thread, decremented by another*/
static volatile int ctx_switch_balancer;
K_SEM_DEFINE(sync_sema, 0, 1);
/**
*
* thread_one
*
* Fiber makes all the test preparations: registers the interrupt handler,
* gets the first timestamp and invokes the software interrupt.
*
*/
static void thread_one(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
k_sem_take(&sync_sema, K_FOREVER);
timestamp_start = timing_counter_get();
while (ctx_switch_counter < NCTXSWITCH) {
k_yield();
ctx_switch_counter++;
ctx_switch_balancer--;
}
timestamp_end = timing_counter_get();
}
/**
*
* @brief Check the time when it gets executed after the semaphore
*
* Fiber starts, waits on semaphore. When the interrupt handler releases
* the semaphore, thread measures the time.
*
* @return 0 on success
*/
static void thread_two(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
k_sem_give(&sync_sema);
while (ctx_switch_counter < NCTXSWITCH) {
k_yield();
ctx_switch_counter++;
ctx_switch_balancer++;
}
}
/**
*
* @brief The test main function
*
* @return 0 on success
*/
int coop_ctx_switch(void)
{
ctx_switch_counter = 0U;
ctx_switch_balancer = 0;
char error_string[80];
const char *notes = "";
bool failed = false;
int end;
timing_start();
bench_test_start();
k_thread_create(&thread_one_data, thread_one_stack, STACKSIZE,
thread_one, NULL, NULL, NULL,
K_PRIO_COOP(6), 0, K_NO_WAIT);
k_thread_create(&thread_two_data, thread_two_stack, STACKSIZE,
thread_two, NULL, NULL, NULL,
K_PRIO_COOP(6), 0, K_NO_WAIT);
end = bench_test_end();
if (ctx_switch_balancer > 3 || ctx_switch_balancer < -3) {
error_count++;
snprintk(error_string, 78, " Balance is %d",
ctx_switch_balancer);
notes = error_string;
failed = true;
} else if (end != 0) {
error_count++;
notes = TICK_OCCURRENCE_ERROR;
}
uint32_t diff;
diff = timing_cycles_get(&timestamp_start, &timestamp_end);
PRINT_STATS_AVG("Average context switch time between threads (coop)",
diff, ctx_switch_counter, failed, notes);
timing_stop();
return 0;
}

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2012-2015 Wind River Systems, Inc.
* Copyright (c) 2023 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -9,30 +10,62 @@
* This file contains the main testing module that invokes all the tests.
*/
#include <zephyr/kernel.h>
#include <zephyr/timestamp.h>
#include <zephyr/app_memory/app_memdomain.h>
#include "utils.h"
#include <zephyr/tc_util.h>
#define NUM_ITERATIONS 10000
#define STACK_SIZE (1024 + CONFIG_TEST_EXTRA_STACK_SIZE)
uint32_t tm_off; /* time necessary to read the time */
#ifdef CONFIG_USERSPACE
#define BENCH_BMEM K_APP_BMEM(bench_mem_partition)
#else
#define BENCH_BMEM
#endif
uint32_t tm_off;
BENCH_BMEM struct timestamp_data timestamp;
#ifdef CONFIG_USERSPACE
K_APPMEM_PARTITION_DEFINE(bench_mem_partition);
#endif
K_THREAD_STACK_DEFINE(start_stack, START_STACK_SIZE);
K_THREAD_STACK_DEFINE(alt_stack, START_STACK_SIZE);
K_SEM_DEFINE(pause_sem, 0, 1);
struct k_thread start_thread;
struct k_thread alt_thread;
int error_count; /* track number of errors */
extern void thread_switch_yield(void);
extern void thread_switch_yield(uint32_t num_iterations, bool is_cooperative);
extern void int_to_thread(void);
extern void int_to_thread_evt(void);
extern void sema_test_signal(void);
extern void mutex_lock_unlock(void);
extern int coop_ctx_switch(void);
extern int sema_test(void);
extern int sema_context_switch(void);
extern int suspend_resume(void);
extern void heap_malloc_free(void);
void test_thread(void *arg1, void *arg2, void *arg3)
static void test_thread(void *arg1, void *arg2, void *arg3)
{
uint32_t freq;
ARG_UNUSED(arg1);
ARG_UNUSED(arg2);
ARG_UNUSED(arg3);
#ifdef CONFIG_USERSPACE
k_mem_domain_add_partition(&k_mem_domain_default,
&bench_mem_partition);
#endif
timing_init();
bench_test_init();
@ -42,9 +75,11 @@ void test_thread(void *arg1, void *arg2, void *arg3)
TC_START("Time Measurement");
TC_PRINT("Timing results: Clock frequency: %u MHz\n", freq);
thread_switch_yield();
/* Preemptive threads context switching */
thread_switch_yield(NUM_ITERATIONS, false);
coop_ctx_switch();
/* Cooperative threads context switching */
thread_switch_yield(NUM_ITERATIONS, true);
int_to_thread();
@ -63,7 +98,8 @@ void test_thread(void *arg1, void *arg2, void *arg3)
TC_END_REPORT(error_count);
}
K_THREAD_DEFINE(test_thread_id, STACK_SIZE, test_thread, NULL, NULL, NULL, K_PRIO_PREEMPT(10), 0, 0);
K_THREAD_DEFINE(test_thread_id, STACK_SIZE, test_thread, NULL, NULL, NULL,
K_PRIO_PREEMPT(10), 0, 0);
int main(void)
{

View file

@ -1,113 +1,168 @@
/*
* Copyright (c) 2012-2014 Wind River Systems, Inc.
* Copyright (c) 2023 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* This file contains the benchmark that measure the average time it takes to
* do context switches between threads using k_yield () to force
* context switch.
* This file contains the benchmarking code that measures the average time it
* takes to perform context switches between threads using k_yield().
*
* When user threads are supported, there are four cases to consider. These are
* 1. Kernel thread -> Kernel thread
* 2. User thread -> User thread
* 3. Kernel thread -> User thread
* 4. User thread -> Kernel thread
*/
#include <zephyr/kernel.h>
#include <zephyr/timing/timing.h>
#include <stdlib.h>
#include <zephyr/timestamp.h>
#include "utils.h" /* PRINT () and other macros */
/* context switch enough time so our measurement is precise */
#define NB_OF_YIELD 1000
#include "utils.h"
static uint32_t helper_thread_iterations;
#define Y_STACK_SIZE (512 + CONFIG_TEST_EXTRA_STACK_SIZE)
#define Y_PRIORITY K_PRIO_PREEMPT(10)
K_THREAD_STACK_DEFINE(y_stack_area, Y_STACK_SIZE);
static struct k_thread y_thread;
/**
* @brief Helper thread for measuring thread switch latency using yield
*/
void yielding_thread(void *arg1, void *arg2, void *arg3)
static void alt_thread_entry(void *p1, void *p2, void *p3)
{
while (helper_thread_iterations < NB_OF_YIELD) {
uint32_t num_iterations;
ARG_UNUSED(p2);
ARG_UNUSED(p2);
num_iterations = (uint32_t)(uintptr_t)p1;
for (uint32_t i = 0; i < num_iterations; i++) {
/* 3. Obtain the 'finish' timestamp */
timestamp.sample = timing_counter_get();
/* 4. Switch to <start_thread> */
k_yield();
helper_thread_iterations++;
}
}
/**
* @brief Entry point for thread context switch using yield test
*/
void thread_switch_yield(void)
static void start_thread_entry(void *p1, void *p2, void *p3)
{
uint32_t iterations = 0U;
int32_t delta;
timing_t timestamp_start;
timing_t timestamp_end;
uint32_t ts_diff;
const char *notes = "";
char error_string[80];
bool failed = false;
int end;
uint64_t sum = 0ull;
uint32_t num_iterations;
timing_t start;
timing_t finish;
timing_start();
bench_test_start();
ARG_UNUSED(p2);
ARG_UNUSED(p2);
/* launch helper thread of the same priority as the thread
* of routine
*/
k_thread_create(&y_thread, y_stack_area, Y_STACK_SIZE, yielding_thread,
NULL, NULL, NULL, Y_PRIORITY, 0, K_NO_WAIT);
num_iterations = (uint32_t)(uintptr_t)p1;
/* get initial timestamp */
timestamp_start = timing_counter_get();
k_thread_start(&alt_thread);
for (uint32_t i = 0; i < num_iterations; i++) {
/* 1. Get 'start' timestamp */
start = timing_counter_get();
/* 2. Switch to <alt_thread> */
/* loop until either helper or this routine reaches number of yields */
while (iterations < NB_OF_YIELD &&
helper_thread_iterations < NB_OF_YIELD) {
k_yield();
iterations++;
/* 5. Get the 'finish' timestamp obtained in <alt_thread> */
finish = timestamp.sample;
/* 6. Track the sum of elapsed times */
sum += timing_cycles_get(&start, &finish);
}
/* get the number of cycles it took to do the test */
timestamp_end = timing_counter_get();
end = bench_test_end();
/* Wait for <alt_thread> to complete */
/* Ensure both helper and this routine were context switching back &
* forth.
* For execution to reach the line below, either this routine or helper
* routine reached NB_OF_YIELD. The other loop must be at most one
* iteration away from reaching NB_OF_YIELD if execute was switching
* back and forth.
*/
delta = iterations - helper_thread_iterations;
if (abs(delta) > 1) {
/* expecting even alternating context switch, seems one routine
* called yield without the other having chance to execute
*/
error_count++;
snprintk(error_string, 78,
"Error: iteration:%u : helper iteration:%u",
iterations, helper_thread_iterations);
notes = error_string;
failed = true;
} else if (end != 0) {
error_count++;
notes = TICK_OCCURRENCE_ERROR;
}
k_thread_join(&alt_thread, K_FOREVER);
/*
* thread_yield is called (iterations + helper_thread_iterations)
* times in total.
*/
/* Record the number of cycles for use by the main thread */
ts_diff = timing_cycles_get(&timestamp_start, &timestamp_end);
PRINT_STATS_AVG("Average thread context switch using yield", ts_diff,
(iterations + helper_thread_iterations), failed, notes);
timing_stop();
timestamp.cycles = sum;
}
static void thread_switch_yield_common(const char *description,
uint32_t num_iterations,
uint32_t start_options,
uint32_t alt_options,
int priority)
{
uint64_t sum;
char summary[80];
/* Create the two threads */
k_thread_create(&start_thread, start_stack,
K_THREAD_STACK_SIZEOF(start_stack),
start_thread_entry,
(void *)(uintptr_t)num_iterations, NULL, NULL,
priority - 1, start_options, K_FOREVER);
k_thread_create(&alt_thread, alt_stack,
K_THREAD_STACK_SIZEOF(alt_stack),
alt_thread_entry,
(void *)(uintptr_t)num_iterations, NULL, NULL,
priority - 1, alt_options, K_FOREVER);
/* Grant access rights if necessary */
if ((start_options & K_USER) == K_USER) {
k_thread_access_grant(&start_thread, &alt_thread);
}
k_thread_start(&start_thread);
/* Wait until <start_thread> finishes */
k_thread_join(&start_thread, K_FOREVER);
/* Get the sum total of measured cycles */
sum = timestamp.cycles;
snprintf(summary, sizeof(summary),
"%s (%c -> %c)",
description,
(start_options & K_USER) == K_USER ? 'U' : 'K',
(alt_options & K_USER) == K_USER ? 'U' : 'K');
PRINT_STATS_AVG(summary, (uint32_t)sum, num_iterations, 0, "");
}
void thread_switch_yield(uint32_t num_iterations, bool is_cooperative)
{
int priority;
char description[60];
priority = is_cooperative ? K_PRIO_COOP(6)
: k_thread_priority_get(k_current_get()) - 1;
snprintf(description, sizeof(description),
"%s threads ctx switch via k_yield",
is_cooperative ? "Cooperative" : "Preemptive");
/* Kernel -> Kernel */
thread_switch_yield_common(description, num_iterations, 0, 0,
priority);
#if CONFIG_USERSPACE
/* User -> User */
thread_switch_yield_common(description, num_iterations, K_USER, K_USER,
priority);
/* Kernel -> User */
thread_switch_yield_common(description, num_iterations, 0, K_USER,
priority);
/* User -> Kernel */
thread_switch_yield_common(description, num_iterations, K_USER, 0,
priority);
#endif
}

View file

@ -7,7 +7,7 @@
#ifndef _LATENCY_MEASURE_UNIT_H
#define _LATENCY_MEASURE_UNIT_H
/*
* @brief This file contains function declarations, macroses and inline functions
* @brief This file contains function declarations, macros and inline functions
* used in latency measurement.
*/
@ -16,8 +16,23 @@
#include <stdio.h>
#include <zephyr/timestamp.h>
#define INT_IMM8_OFFSET 1
#define IRQ_PRIORITY 3
#define START_STACK_SIZE (512 + CONFIG_TEST_EXTRA_STACK_SIZE)
#define ALT_STACK_SIZE (512 + CONFIG_TEST_EXTRA_STACK_SIZE)
struct timestamp_data {
uint64_t cycles;
timing_t sample;
};
K_THREAD_STACK_DECLARE(start_stack, START_STACK_SIZE);
K_THREAD_STACK_DECLARE(alt_stack, ALT_STACK_SIZE);
extern struct k_thread start_thread;
extern struct k_thread alt_thread;
extern struct k_sem pause_sem;
extern struct timestamp_data timestamp;
extern int error_count;