blob: 1c9f0eea616699efd026785cc56867a7ea66d183 [file] [log] [blame]
Daniel Bristot de Oliveirab1696372021-12-10 19:11:21 +01001// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
4 */
5
6#include <proc/readproc.h>
7#include <stdarg.h>
8#include <stdlib.h>
9#include <string.h>
10#include <unistd.h>
11#include <ctype.h>
12#include <errno.h>
13#include <sched.h>
14#include <stdio.h>
15
16#include "utils.h"
17
18#define MAX_MSG_LENGTH 1024
19int config_debug;
20
21/*
22 * err_msg - print an error message to the stderr
23 */
24void err_msg(const char *fmt, ...)
25{
26 char message[MAX_MSG_LENGTH];
27 va_list ap;
28
29 va_start(ap, fmt);
30 vsnprintf(message, sizeof(message), fmt, ap);
31 va_end(ap);
32
33 fprintf(stderr, "%s", message);
34}
35
36/*
37 * debug_msg - print a debug message to stderr if debug is set
38 */
39void debug_msg(const char *fmt, ...)
40{
41 char message[MAX_MSG_LENGTH];
42 va_list ap;
43
44 if (!config_debug)
45 return;
46
47 va_start(ap, fmt);
48 vsnprintf(message, sizeof(message), fmt, ap);
49 va_end(ap);
50
51 fprintf(stderr, "%s", message);
52}
53
54/*
55 * get_llong_from_str - get a long long int from a string
56 */
57long long get_llong_from_str(char *start)
58{
59 long long value;
60 char *end;
61
62 errno = 0;
63 value = strtoll(start, &end, 10);
64 if (errno || start == end)
65 return -1;
66
67 return value;
68}
69
70/*
71 * get_duration - fill output with a human readable duration since start_time
72 */
73void get_duration(time_t start_time, char *output, int output_size)
74{
75 time_t now = time(NULL);
76 struct tm *tm_info;
77 time_t duration;
78
79 duration = difftime(now, start_time);
80 tm_info = localtime(&duration);
81
82 snprintf(output, output_size, "%3d %02d:%02d:%02d",
83 tm_info->tm_yday,
84 tm_info->tm_hour - 1,
85 tm_info->tm_min,
86 tm_info->tm_sec);
87}
88
89/*
90 * parse_cpu_list - parse a cpu_list filling a char vector with cpus set
91 *
92 * Receives a cpu list, like 1-3,5 (cpus 1, 2, 3, 5), and then set the char
93 * in the monitored_cpus.
94 *
95 * XXX: convert to a bitmask.
96 */
97int parse_cpu_list(char *cpu_list, char **monitored_cpus)
98{
99 char *mon_cpus;
100 const char *p;
101 int end_cpu;
102 int nr_cpus;
103 int cpu;
104 int i;
105
106 nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
107
108 mon_cpus = malloc(nr_cpus * sizeof(char));
109 memset(mon_cpus, 0, (nr_cpus * sizeof(char)));
110
111 for (p = cpu_list; *p; ) {
112 cpu = atoi(p);
113 if (cpu < 0 || (!cpu && *p != '0') || cpu >= nr_cpus)
114 goto err;
115
116 while (isdigit(*p))
117 p++;
118 if (*p == '-') {
119 p++;
120 end_cpu = atoi(p);
121 if (end_cpu < cpu || (!end_cpu && *p != '0') || end_cpu >= nr_cpus)
122 goto err;
123 while (isdigit(*p))
124 p++;
125 } else
126 end_cpu = cpu;
127
128 if (cpu == end_cpu) {
129 debug_msg("cpu_list: adding cpu %d\n", cpu);
130 mon_cpus[cpu] = 1;
131 } else {
132 for (i = cpu; i <= end_cpu; i++) {
133 debug_msg("cpu_list: adding cpu %d\n", i);
134 mon_cpus[i] = 1;
135 }
136 }
137
138 if (*p == ',')
139 p++;
140 }
141
142 *monitored_cpus = mon_cpus;
143
144 return 0;
145
146err:
147 debug_msg("Error parsing the cpu list %s", cpu_list);
148 return 1;
149}
150
151/*
152 * parse_duration - parse duration with s/m/h/d suffix converting it to seconds
153 */
154long parse_seconds_duration(char *val)
155{
156 char *end;
157 long t;
158
159 t = strtol(val, &end, 10);
160
161 if (end) {
162 switch (*end) {
163 case 's':
164 case 'S':
165 break;
166 case 'm':
167 case 'M':
168 t *= 60;
169 break;
170 case 'h':
171 case 'H':
172 t *= 60 * 60;
173 break;
174
175 case 'd':
176 case 'D':
177 t *= 24 * 60 * 60;
178 break;
179 }
180 }
181
182 return t;
183}
184
185/*
186 * parse_ns_duration - parse duration with ns/us/ms/s converting it to nanoseconds
187 */
188long parse_ns_duration(char *val)
189{
190 char *end;
191 long t;
192
193 t = strtol(val, &end, 10);
194
195 if (end) {
196 if (!strncmp(end, "ns", 2)) {
197 return t;
198 } else if (!strncmp(end, "us", 2)) {
199 t *= 1000;
200 return t;
201 } else if (!strncmp(end, "ms", 2)) {
202 t *= 1000 * 1000;
203 return t;
204 } else if (!strncmp(end, "s", 1)) {
205 t *= 1000 * 1000 * 1000;
206 return t;
207 }
208 return -1;
209 }
210
211 return t;
212}
213
214/*
215 * This is a set of helper functions to use SCHED_DEADLINE.
216 */
217#ifdef __x86_64__
218# define __NR_sched_setattr 314
219# define __NR_sched_getattr 315
220#elif __i386__
221# define __NR_sched_setattr 351
222# define __NR_sched_getattr 352
223#elif __arm__
224# define __NR_sched_setattr 380
225# define __NR_sched_getattr 381
226#elif __aarch64__
227# define __NR_sched_setattr 274
228# define __NR_sched_getattr 275
229#elif __powerpc__
230# define __NR_sched_setattr 355
231# define __NR_sched_getattr 356
232#elif __s390x__
233# define __NR_sched_setattr 345
234# define __NR_sched_getattr 346
235#endif
236
237#define SCHED_DEADLINE 6
238
239static inline int sched_setattr(pid_t pid, const struct sched_attr *attr,
240 unsigned int flags) {
241 return syscall(__NR_sched_setattr, pid, attr, flags);
242}
243
244static inline int sched_getattr(pid_t pid, struct sched_attr *attr,
245 unsigned int size, unsigned int flags)
246{
247 return syscall(__NR_sched_getattr, pid, attr, size, flags);
248}
249
250int __set_sched_attr(int pid, struct sched_attr *attr)
251{
252 int flags = 0;
253 int retval;
254
255 retval = sched_setattr(pid, attr, flags);
256 if (retval < 0) {
257 err_msg("boost_with_deadline failed to boost pid %d: %s\n",
258 pid, strerror(errno));
259 return 1;
260 }
261
262 return 0;
263}
264/*
265 * set_comm_sched_attr - set sched params to threads starting with char *comm
266 *
267 * This function uses procps to list the currently running threads and then
268 * set the sched_attr *attr to the threads that start with char *comm. It is
269 * mainly used to set the priority to the kernel threads created by the
270 * tracers.
271 */
272int set_comm_sched_attr(const char *comm, struct sched_attr *attr)
273{
274 int flags = PROC_FILLCOM | PROC_FILLSTAT;
275 PROCTAB *ptp;
276 proc_t task;
277 int retval;
278
279 ptp = openproc(flags);
280 if (!ptp) {
281 err_msg("error openproc()\n");
282 return -ENOENT;
283 }
284
285 memset(&task, 0, sizeof(task));
286
287 while (readproc(ptp, &task)) {
288 retval = strncmp(comm, task.cmd, strlen(comm));
289 if (retval)
290 continue;
291 retval = __set_sched_attr(task.tid, attr);
292 if (retval)
293 goto out_err;
294 }
295
296 closeproc(ptp);
297 return 0;
298
299out_err:
300 closeproc(ptp);
301 return 1;
302}
303
304#define INVALID_VAL (~0L)
305static long get_long_ns_after_colon(char *start)
306{
307 long val = INVALID_VAL;
308
309 /* find the ":" */
310 start = strstr(start, ":");
311 if (!start)
312 return -1;
313
314 /* skip ":" */
315 start++;
316 val = parse_ns_duration(start);
317
318 return val;
319}
320
321static long get_long_after_colon(char *start)
322{
323 long val = INVALID_VAL;
324
325 /* find the ":" */
326 start = strstr(start, ":");
327 if (!start)
328 return -1;
329
330 /* skip ":" */
331 start++;
332 val = get_llong_from_str(start);
333
334 return val;
335}
336
337/*
338 * parse priority in the format:
339 * SCHED_OTHER:
340 * o:<prio>
341 * O:<prio>
342 * SCHED_RR:
343 * r:<prio>
344 * R:<prio>
345 * SCHED_FIFO:
346 * f:<prio>
347 * F:<prio>
348 * SCHED_DEADLINE:
349 * d:runtime:period
350 * D:runtime:period
351 */
352int parse_prio(char *arg, struct sched_attr *sched_param)
353{
354 long prio;
355 long runtime;
356 long period;
357
358 memset(sched_param, 0, sizeof(*sched_param));
359 sched_param->size = sizeof(*sched_param);
360
361 switch (arg[0]) {
362 case 'd':
363 case 'D':
364 /* d:runtime:period */
365 if (strlen(arg) < 4)
366 return -1;
367
368 runtime = get_long_ns_after_colon(arg);
369 if (runtime == INVALID_VAL)
370 return -1;
371
372 period = get_long_ns_after_colon(&arg[2]);
373 if (period == INVALID_VAL)
374 return -1;
375
376 if (runtime > period)
377 return -1;
378
379 sched_param->sched_policy = SCHED_DEADLINE;
380 sched_param->sched_runtime = runtime;
381 sched_param->sched_deadline = period;
382 sched_param->sched_period = period;
383 break;
384 case 'f':
385 case 'F':
386 /* f:prio */
387 prio = get_long_after_colon(arg);
388 if (prio == INVALID_VAL)
389 return -1;
390
391 if (prio < sched_get_priority_min(SCHED_FIFO))
392 return -1;
393 if (prio > sched_get_priority_max(SCHED_FIFO))
394 return -1;
395
396 sched_param->sched_policy = SCHED_FIFO;
397 sched_param->sched_priority = prio;
398 break;
399 case 'r':
400 case 'R':
401 /* r:prio */
402 prio = get_long_after_colon(arg);
403 if (prio == INVALID_VAL)
404 return -1;
405
406 if (prio < sched_get_priority_min(SCHED_RR))
407 return -1;
408 if (prio > sched_get_priority_max(SCHED_RR))
409 return -1;
410
411 sched_param->sched_policy = SCHED_RR;
412 sched_param->sched_priority = prio;
413 break;
414 case 'o':
415 case 'O':
416 /* o:prio */
417 prio = get_long_after_colon(arg);
418 if (prio == INVALID_VAL)
419 return -1;
420
421 if (prio < sched_get_priority_min(SCHED_OTHER))
422 return -1;
423 if (prio > sched_get_priority_max(SCHED_OTHER))
424 return -1;
425
426 sched_param->sched_policy = SCHED_OTHER;
427 sched_param->sched_priority = prio;
428 break;
429 default:
430 return -1;
431 }
432 return 0;
433}