| /* |
| * Copyright (c) 2018 Google, Inc. |
| * |
| * SPDX-License-Identifier: GPL-2.0-or-later |
| * |
| * Three RT RR tasks are created and affined to the same CPU. They execute as |
| * CPU hogs. Their runtime is checked to see that they share the CPU as |
| * expected. |
| */ |
| |
| #define _GNU_SOURCE |
| #include <errno.h> |
| #include <pthread.h> |
| #include <sched.h> |
| #include <semaphore.h> |
| #include <time.h> |
| |
| #include "tst_test.h" |
| #include "tst_safe_file_ops.h" |
| #include "tst_safe_pthread.h" |
| |
| #include "trace_parse.h" |
| #include "util.h" |
| |
| #define TRACE_EVENTS "sched_wakeup sched_switch sched_process_exit" |
| |
| #define EXEC_MIN_PCT 33 |
| #define EXEC_MAX_PCT 34 |
| |
| static sem_t sem; |
| |
| static int rt_a_tid; |
| static int rt_b_tid; |
| static int rt_c_tid; |
| |
| #define BUSY_WAIT_USECS 10000000 |
| static void *rt_b_fn(void *arg LTP_ATTRIBUTE_UNUSED) |
| { |
| rt_b_tid = gettid(); |
| affine(0); |
| sem_wait(&sem); |
| burn(BUSY_WAIT_USECS, 0); |
| return NULL; |
| } |
| |
| static void *rt_c_fn(void *arg LTP_ATTRIBUTE_UNUSED) |
| { |
| rt_c_tid = gettid(); |
| affine(0); |
| sem_wait(&sem); |
| burn(BUSY_WAIT_USECS, 0); |
| return NULL; |
| } |
| |
| static void *rt_a_fn(void *arg LTP_ATTRIBUTE_UNUSED) |
| { |
| rt_a_tid = gettid(); |
| affine(0); |
| /* Give all other tasks a chance to affine and block. */ |
| usleep(3000); |
| SAFE_FILE_PRINTF(TRACING_DIR "trace_marker", "TEST START"); |
| sem_post(&sem); |
| sem_post(&sem); |
| burn(BUSY_WAIT_USECS, 0); |
| return NULL; |
| } |
| |
| static int parse_results(void) |
| { |
| int i, pct, rv; |
| int test_start = 0; |
| unsigned long long exec_start_us = 0; |
| unsigned long a_exec_us = 0; |
| unsigned long b_exec_us = 0; |
| unsigned long c_exec_us = 0; |
| unsigned long total; |
| |
| for (i = 0; i < num_trace_records; i++) { |
| struct trace_sched_switch *t = trace[i].event_data; |
| unsigned long long segment_us; |
| |
| if (trace[i].event_type == TRACE_RECORD_TRACING_MARK_WRITE && |
| !strcmp(trace[i].event_data, "TEST START")) { |
| /* We need to include this segment in A's exec time. */ |
| exec_start_us = TS_TO_USEC(trace[i].ts); |
| test_start = 1; |
| } |
| |
| if (!test_start) |
| continue; |
| |
| if (trace[i].event_type != TRACE_RECORD_SCHED_SWITCH) |
| continue; |
| |
| segment_us = TS_TO_USEC(trace[i].ts) - exec_start_us; |
| if (t->prev_pid == rt_a_tid) |
| a_exec_us += segment_us; |
| else if (t->prev_pid == rt_b_tid) |
| b_exec_us += segment_us; |
| else if (t->prev_pid == rt_c_tid) |
| c_exec_us += segment_us; |
| if (t->next_pid == rt_a_tid || |
| t->next_pid == rt_b_tid || |
| t->next_pid == rt_c_tid) |
| exec_start_us = TS_TO_USEC(trace[i].ts); |
| } |
| |
| rv = 0; |
| total = a_exec_us + b_exec_us + c_exec_us; |
| pct = (a_exec_us * 100) / total; |
| rv |= (pct < EXEC_MIN_PCT || pct > EXEC_MAX_PCT); |
| printf("a exec time: %ld usec (%d%%)\n", a_exec_us, pct); |
| pct = (b_exec_us * 100) / total; |
| rv |= (pct < EXEC_MIN_PCT || pct > EXEC_MAX_PCT); |
| printf("b exec time: %ld usec (%d%%)\n", b_exec_us, pct); |
| pct = (c_exec_us * 100) / total; |
| rv |= (pct < EXEC_MIN_PCT || pct > EXEC_MAX_PCT); |
| printf("c exec time: %ld usec (%d%%)\n", c_exec_us, pct); |
| |
| return rv; |
| } |
| |
| static void create_rt_thread(int prio, void *fn, pthread_t *rt_thread) |
| { |
| pthread_attr_t rt_thread_attrs; |
| struct sched_param rt_thread_sched_params; |
| |
| ERROR_CHECK(pthread_attr_init(&rt_thread_attrs)); |
| ERROR_CHECK(pthread_attr_setinheritsched(&rt_thread_attrs, |
| PTHREAD_EXPLICIT_SCHED)); |
| ERROR_CHECK(pthread_attr_setschedpolicy(&rt_thread_attrs, |
| SCHED_RR)); |
| rt_thread_sched_params.sched_priority = prio; |
| ERROR_CHECK(pthread_attr_setschedparam(&rt_thread_attrs, |
| &rt_thread_sched_params)); |
| |
| SAFE_PTHREAD_CREATE(rt_thread, &rt_thread_attrs, fn, NULL); |
| } |
| |
| #define NUM_TASKS 3 |
| static void run(void) |
| { |
| pthread_t rt_a, rt_b, rt_c; |
| |
| sem_init(&sem, 0, 0); |
| |
| printf("Running %d RT RR tasks for 10 seconds...\n", NUM_TASKS); |
| |
| /* configure and enable tracing */ |
| SAFE_FILE_PRINTF(TRACING_DIR "tracing_on", "0"); |
| SAFE_FILE_PRINTF(TRACING_DIR "buffer_size_kb", "16384"); |
| SAFE_FILE_PRINTF(TRACING_DIR "set_event", TRACE_EVENTS); |
| SAFE_FILE_PRINTF(TRACING_DIR "trace", "\n"); |
| SAFE_FILE_PRINTF(TRACING_DIR "tracing_on", "1"); |
| |
| create_rt_thread(70, rt_a_fn, &rt_a); |
| create_rt_thread(70, rt_b_fn, &rt_b); |
| create_rt_thread(70, rt_c_fn, &rt_c); |
| |
| SAFE_PTHREAD_JOIN(rt_a, NULL); |
| SAFE_PTHREAD_JOIN(rt_b, NULL); |
| SAFE_PTHREAD_JOIN(rt_c, NULL); |
| |
| /* disable tracing */ |
| SAFE_FILE_PRINTF(TRACING_DIR "tracing_on", "0"); |
| LOAD_TRACE(); |
| |
| if (parse_results()) |
| tst_res(TFAIL, "RT RR tasks did not receive the expected CPU " |
| "time (all between %d-%d %% CPU).\n", EXEC_MIN_PCT, |
| EXEC_MAX_PCT); |
| else |
| tst_res(TPASS, "RT RR tasks received the expected CPU time.\n"); |
| } |
| |
| static struct tst_test test = { |
| .test_all = run, |
| .cleanup = trace_cleanup, |
| }; |