Home | History | Annotate | Download | only in eas
      1 /*
      2  * Copyright (c) 2018 Google, Inc.
      3  *
      4  * SPDX-License-Identifier: GPL-2.0-or-later
      5  *
      6  * Some CFS tasks are started with different priorities. The tasks are CPU hogs
      7  * and affined to the same CPU. Their runtime is checked to see that it
      8  * corresponds to that which is expected given the task priorities.
      9  */
     10 
     11 #define _GNU_SOURCE
     12 #include <errno.h>
     13 #include <pthread.h>
     14 #include <sched.h>
     15 #include <semaphore.h>
     16 #include <stdlib.h>
     17 #include <time.h>
     18 
     19 #include "tst_test.h"
     20 #include "tst_safe_file_ops.h"
     21 #include "tst_safe_pthread.h"
     22 
     23 #include "trace_parse.h"
     24 #include "util.h"
     25 
     26 #define TRACE_EVENTS "sched_switch"
     27 
     28 static int cfs_task_tids[4];
     29 /* If testing a nice value of -1, task_fn's use of nice() must be amended to
     30  * check for an error properly. */
     31 static int nice_vals[] = { -15, -5, 5, 15 };
     32 /* These come from sched_prio_to_weight in kernel/sched/core.c. */
     33 static int prio_to_weight[] = { 29154, 3121, 335, 36 };
     34 
     35 #define TEST_TASK_SECONDS 5
     36 static void *task_fn(void *arg)
     37 {
     38 	int idx = (int *)arg - cfs_task_tids;
     39 
     40 	cfs_task_tids[idx] = gettid();
     41 
     42 	affine(0);
     43 	if (nice(nice_vals[idx]) != nice_vals[idx]) {
     44 		printf("Error calling nice(%d)\n", nice_vals[idx]);
     45 		return NULL;
     46 	}
     47 	burn(TEST_TASK_SECONDS * USEC_PER_SEC, 0);
     48 	return NULL;
     49 }
     50 
     51 #define LOWER_BOUND_PCT 80
     52 #define UPPER_BOUND_PCT 105
     53 #define LOWER_BOUND_US 20000
     54 #define UPPER_BOUND_US 30000
     55 
     56 int check_bounds(long long expected_us, long long runtime_us)
     57 {
     58 	int rv = 0;
     59 	long long lower_bound, lower_bound_pct, lower_bound_us;
     60 	long long upper_bound, upper_bound_pct, upper_bound_us;
     61 
     62 	lower_bound_pct = (LOWER_BOUND_PCT / (100 * expected_us));
     63 	lower_bound_us = (expected_us - LOWER_BOUND_US);
     64 	if (lower_bound_us > lower_bound_pct)
     65 		lower_bound = lower_bound_us;
     66 	else
     67 		lower_bound = lower_bound_pct;
     68 	upper_bound_pct = (UPPER_BOUND_PCT / (100 * expected_us));
     69 	upper_bound_us = (expected_us + UPPER_BOUND_US);
     70 	if (upper_bound_us > upper_bound_pct)
     71 		upper_bound = upper_bound_us;
     72 	else
     73 		upper_bound = upper_bound_pct;
     74 
     75 	if (runtime_us < lower_bound) {
     76 		printf("  lower bound of %lld ms not met\n",
     77 		       lower_bound/1000);
     78 		rv = 1;
     79 	}
     80 	if (runtime_us > upper_bound) {
     81 		printf("  upper bound of %lld ms exceeded\n",
     82 		       upper_bound/1000);
     83 		rv = 1;
     84 	}
     85 	return rv;
     86 }
     87 
     88 static int parse_results(void)
     89 {
     90 	int i, j, weight_sum;
     91 	int rv = 0;
     92 	unsigned long long start_ts_us[4] = { 0, 0, 0, 0 };
     93 	long long runtime_us[4] = { 0, 0, 0, 0 };
     94 	long long expected_us[4];
     95 
     96 	for (i = 0; i < num_trace_records; i++) {
     97 		struct trace_sched_switch *t = trace[i].event_data;
     98 
     99 		if (trace[i].event_type != TRACE_RECORD_SCHED_SWITCH)
    100 			continue;
    101 
    102 		for (j = 0; j < 4; j++) {
    103 			if (t->prev_pid == cfs_task_tids[j]) {
    104 				if (!start_ts_us[j]) {
    105 					printf("Trace parse error, start_ts_us "
    106 					       "unset at segment end!\n");
    107 					return -1;
    108 				}
    109 				runtime_us[j] += TS_TO_USEC(trace[i].ts) -
    110 					start_ts_us[j];
    111 				start_ts_us[j] = 0;
    112 			}
    113 			if (t->next_pid == cfs_task_tids[j]) {
    114 				if (start_ts_us[j]) {
    115 					printf("Trace parse error, start_ts_us "
    116 					       "already set at segment "
    117 					       "start!\n");
    118 					return -1;
    119 				}
    120 				start_ts_us[j] = TS_TO_USEC(trace[i].ts);
    121 			}
    122 		}
    123 	}
    124 
    125 	/*
    126 	 * Task prio to weight values are defined in the kernel at the end of
    127 	 * kernel/sched/core.c (as of 4.19).
    128 	 * -15: 29154
    129 	 *  -5: 3121
    130 	 *   5: 335
    131 	 *  15: 36
    132 	 *  Sum of weights: 29154 + 3121 + 335 + 36 = 32646
    133 	 *  Expected task runtime % (time with 5 second test):
    134 	 *  29154/32646 = 89.3%, 4465ms
    135 	 *  3121/32646 = 9.56%, 478ms
    136 	 *  335/32646 = 1.02%, 51ms
    137 	 *  36/32646 = 0.11%, 5.5ms
    138 	 */
    139 
    140 	weight_sum = 0;
    141 	for (i = 0; i < 4; i++)
    142 		weight_sum += prio_to_weight[i];
    143 	for (i = 0; i < 4; i++) {
    144 		/*
    145 		 * Expected task runtime:
    146 		 * (prio_to_weight[i] / weight_sum) * TEST_TASK_SECONDS
    147 		 */
    148 		expected_us[i] = TEST_TASK_SECONDS * USEC_PER_SEC;
    149 		expected_us[i] *= prio_to_weight[i];
    150 		expected_us[i] /= weight_sum;
    151 	}
    152 
    153 	printf("Task runtimes:\n");
    154 
    155 	printf("Task a (nice -15): %8lld ms (expected %8lld ms)\n",
    156 	       runtime_us[0] / 1000, expected_us[0] / 1000);
    157 	rv |= check_bounds(expected_us[0], runtime_us[0]);
    158 
    159 	printf("Task b (nice -5) : %8lld ms (expected %8lld ms)\n",
    160 	       runtime_us[1] / 1000, expected_us[1] / 1000);
    161 	rv |= check_bounds(expected_us[1], runtime_us[1]);
    162 
    163 	printf("Task c (nice 5)  : %8lld ms (expected %8lld ms)\n",
    164 	       runtime_us[2] / 1000, expected_us[2] / 1000);
    165 	rv |= check_bounds(expected_us[2], runtime_us[2]);
    166 
    167 	printf("Task d (nice 15) : %8lld ms (expected %8lld ms)\n",
    168 	       runtime_us[3] / 1000, expected_us[3] / 1000);
    169 	rv |= check_bounds(expected_us[3], runtime_us[3]);
    170 
    171 	return rv;
    172 }
    173 
    174 #define NUM_TASKS 4
    175 static void run(void)
    176 {
    177 	pthread_t tasks[NUM_TASKS];
    178 	int i;
    179 
    180 	printf("Running %d CFS tasks concurrently for %d sec\n",
    181 	       NUM_TASKS, TEST_TASK_SECONDS);
    182 
    183 	/* configure and enable tracing */
    184 	SAFE_FILE_PRINTF(TRACING_DIR "tracing_on", "0");
    185 	SAFE_FILE_PRINTF(TRACING_DIR "buffer_size_kb", "16384");
    186 	SAFE_FILE_PRINTF(TRACING_DIR "set_event", TRACE_EVENTS);
    187 	SAFE_FILE_PRINTF(TRACING_DIR "trace", "\n");
    188 	SAFE_FILE_PRINTF(TRACING_DIR "tracing_on", "1");
    189 
    190 	for (i = 0; i < NUM_TASKS; i++)
    191 		SAFE_PTHREAD_CREATE(&tasks[i], NULL, task_fn,
    192 				    &cfs_task_tids[i]);
    193 	for (i = 0; i < NUM_TASKS; i++)
    194 		SAFE_PTHREAD_JOIN(tasks[i], NULL);
    195 
    196 	/* disable tracing */
    197 	SAFE_FILE_PRINTF(TRACING_DIR "tracing_on", "0");
    198 	LOAD_TRACE();
    199 
    200 	if (parse_results())
    201 		tst_res(TFAIL, "Task runtimes not within allowed margins "
    202 			"of expected values.\n");
    203 	else
    204 		tst_res(TPASS, "Task runtimes within allowed margins "
    205 			"of expected values.\n");
    206 }
    207 
    208 static struct tst_test test = {
    209 	.test_all = run,
    210 	.cleanup = trace_cleanup,
    211 };
    212