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
task_fn(void * arg)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
check_bounds(long long expected_us,long long runtime_us)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
parse_results(void)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
run(void)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