1 // 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 <dirent.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 <fcntl.h>
14 #include <sched.h>
15 #include <stdio.h>
16
17 #include "utils.h"
18
19 #define MAX_MSG_LENGTH 1024
20 int config_debug;
21
22 /*
23 * err_msg - print an error message to the stderr
24 */
err_msg(const char * fmt,...)25 void err_msg(const char *fmt, ...)
26 {
27 char message[MAX_MSG_LENGTH];
28 va_list ap;
29
30 va_start(ap, fmt);
31 vsnprintf(message, sizeof(message), fmt, ap);
32 va_end(ap);
33
34 fprintf(stderr, "%s", message);
35 }
36
37 /*
38 * debug_msg - print a debug message to stderr if debug is set
39 */
debug_msg(const char * fmt,...)40 void debug_msg(const char *fmt, ...)
41 {
42 char message[MAX_MSG_LENGTH];
43 va_list ap;
44
45 if (!config_debug)
46 return;
47
48 va_start(ap, fmt);
49 vsnprintf(message, sizeof(message), fmt, ap);
50 va_end(ap);
51
52 fprintf(stderr, "%s", message);
53 }
54
55 /*
56 * get_llong_from_str - get a long long int from a string
57 */
get_llong_from_str(char * start)58 long long get_llong_from_str(char *start)
59 {
60 long long value;
61 char *end;
62
63 errno = 0;
64 value = strtoll(start, &end, 10);
65 if (errno || start == end)
66 return -1;
67
68 return value;
69 }
70
71 /*
72 * get_duration - fill output with a human readable duration since start_time
73 */
get_duration(time_t start_time,char * output,int output_size)74 void get_duration(time_t start_time, char *output, int output_size)
75 {
76 time_t now = time(NULL);
77 struct tm *tm_info;
78 time_t duration;
79
80 duration = difftime(now, start_time);
81 tm_info = gmtime(&duration);
82
83 snprintf(output, output_size, "%3d %02d:%02d:%02d",
84 tm_info->tm_yday,
85 tm_info->tm_hour,
86 tm_info->tm_min,
87 tm_info->tm_sec);
88 }
89
90 /*
91 * parse_cpu_list - parse a cpu_list filling a char vector with cpus set
92 *
93 * Receives a cpu list, like 1-3,5 (cpus 1, 2, 3, 5), and then set the char
94 * in the monitored_cpus.
95 *
96 * XXX: convert to a bitmask.
97 */
parse_cpu_list(char * cpu_list,char ** monitored_cpus)98 int parse_cpu_list(char *cpu_list, char **monitored_cpus)
99 {
100 char *mon_cpus;
101 const char *p;
102 int end_cpu;
103 int nr_cpus;
104 int cpu;
105 int i;
106
107 nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
108
109 mon_cpus = calloc(nr_cpus, sizeof(char));
110 if (!mon_cpus)
111 goto err;
112
113 for (p = cpu_list; *p; ) {
114 cpu = atoi(p);
115 if (cpu < 0 || (!cpu && *p != '0') || cpu >= nr_cpus)
116 goto err;
117
118 while (isdigit(*p))
119 p++;
120 if (*p == '-') {
121 p++;
122 end_cpu = atoi(p);
123 if (end_cpu < cpu || (!end_cpu && *p != '0') || end_cpu >= nr_cpus)
124 goto err;
125 while (isdigit(*p))
126 p++;
127 } else
128 end_cpu = cpu;
129
130 if (cpu == end_cpu) {
131 debug_msg("cpu_list: adding cpu %d\n", cpu);
132 mon_cpus[cpu] = 1;
133 } else {
134 for (i = cpu; i <= end_cpu; i++) {
135 debug_msg("cpu_list: adding cpu %d\n", i);
136 mon_cpus[i] = 1;
137 }
138 }
139
140 if (*p == ',')
141 p++;
142 }
143
144 *monitored_cpus = mon_cpus;
145
146 return 0;
147
148 err:
149 debug_msg("Error parsing the cpu list %s", cpu_list);
150 return 1;
151 }
152
153 /*
154 * parse_duration - parse duration with s/m/h/d suffix converting it to seconds
155 */
parse_seconds_duration(char * val)156 long parse_seconds_duration(char *val)
157 {
158 char *end;
159 long t;
160
161 t = strtol(val, &end, 10);
162
163 if (end) {
164 switch (*end) {
165 case 's':
166 case 'S':
167 break;
168 case 'm':
169 case 'M':
170 t *= 60;
171 break;
172 case 'h':
173 case 'H':
174 t *= 60 * 60;
175 break;
176
177 case 'd':
178 case 'D':
179 t *= 24 * 60 * 60;
180 break;
181 }
182 }
183
184 return t;
185 }
186
187 /*
188 * parse_ns_duration - parse duration with ns/us/ms/s converting it to nanoseconds
189 */
parse_ns_duration(char * val)190 long parse_ns_duration(char *val)
191 {
192 char *end;
193 long t;
194
195 t = strtol(val, &end, 10);
196
197 if (end) {
198 if (!strncmp(end, "ns", 2)) {
199 return t;
200 } else if (!strncmp(end, "us", 2)) {
201 t *= 1000;
202 return t;
203 } else if (!strncmp(end, "ms", 2)) {
204 t *= 1000 * 1000;
205 return t;
206 } else if (!strncmp(end, "s", 1)) {
207 t *= 1000 * 1000 * 1000;
208 return t;
209 }
210 return -1;
211 }
212
213 return t;
214 }
215
216 /*
217 * This is a set of helper functions to use SCHED_DEADLINE.
218 */
219 #ifdef __x86_64__
220 # define __NR_sched_setattr 314
221 # define __NR_sched_getattr 315
222 #elif __i386__
223 # define __NR_sched_setattr 351
224 # define __NR_sched_getattr 352
225 #elif __arm__
226 # define __NR_sched_setattr 380
227 # define __NR_sched_getattr 381
228 #elif __aarch64__ || __riscv
229 # define __NR_sched_setattr 274
230 # define __NR_sched_getattr 275
231 #elif __powerpc__
232 # define __NR_sched_setattr 355
233 # define __NR_sched_getattr 356
234 #elif __s390x__
235 # define __NR_sched_setattr 345
236 # define __NR_sched_getattr 346
237 #endif
238
239 #define SCHED_DEADLINE 6
240
sched_setattr(pid_t pid,const struct sched_attr * attr,unsigned int flags)241 static inline int sched_setattr(pid_t pid, const struct sched_attr *attr,
242 unsigned int flags) {
243 return syscall(__NR_sched_setattr, pid, attr, flags);
244 }
245
sched_getattr(pid_t pid,struct sched_attr * attr,unsigned int size,unsigned int flags)246 static inline int sched_getattr(pid_t pid, struct sched_attr *attr,
247 unsigned int size, unsigned int flags)
248 {
249 return syscall(__NR_sched_getattr, pid, attr, size, flags);
250 }
251
__set_sched_attr(int pid,struct sched_attr * attr)252 int __set_sched_attr(int pid, struct sched_attr *attr)
253 {
254 int flags = 0;
255 int retval;
256
257 retval = sched_setattr(pid, attr, flags);
258 if (retval < 0) {
259 err_msg("Failed to set sched attributes to the pid %d: %s\n",
260 pid, strerror(errno));
261 return 1;
262 }
263
264 return 0;
265 }
266
267 /*
268 * procfs_is_workload_pid - check if a procfs entry contains a comm_prefix* comm
269 *
270 * Check if the procfs entry is a directory of a process, and then check if the
271 * process has a comm with the prefix set in char *comm_prefix. As the
272 * current users of this function only check for kernel threads, there is no
273 * need to check for the threads for the process.
274 *
275 * Return: True if the proc_entry contains a comm file with comm_prefix*.
276 * Otherwise returns false.
277 */
procfs_is_workload_pid(const char * comm_prefix,struct dirent * proc_entry)278 static int procfs_is_workload_pid(const char *comm_prefix, struct dirent *proc_entry)
279 {
280 char buffer[MAX_PATH];
281 int comm_fd, retval;
282 char *t_name;
283
284 if (proc_entry->d_type != DT_DIR)
285 return 0;
286
287 if (*proc_entry->d_name == '.')
288 return 0;
289
290 /* check if the string is a pid */
291 for (t_name = proc_entry->d_name; t_name; t_name++) {
292 if (!isdigit(*t_name))
293 break;
294 }
295
296 if (*t_name != '\0')
297 return 0;
298
299 snprintf(buffer, MAX_PATH, "/proc/%s/comm", proc_entry->d_name);
300 comm_fd = open(buffer, O_RDONLY);
301 if (comm_fd < 0)
302 return 0;
303
304 memset(buffer, 0, MAX_PATH);
305 retval = read(comm_fd, buffer, MAX_PATH);
306
307 close(comm_fd);
308
309 if (retval <= 0)
310 return 0;
311
312 retval = strncmp(comm_prefix, buffer, strlen(comm_prefix));
313 if (retval)
314 return 0;
315
316 /* comm already have \n */
317 debug_msg("Found workload pid:%s comm:%s", proc_entry->d_name, buffer);
318
319 return 1;
320 }
321
322 /*
323 * set_comm_sched_attr - set sched params to threads starting with char *comm_prefix
324 *
325 * This function uses procfs to list the currently running threads and then set the
326 * sched_attr *attr to the threads that start with char *comm_prefix. It is
327 * mainly used to set the priority to the kernel threads created by the
328 * tracers.
329 */
set_comm_sched_attr(const char * comm_prefix,struct sched_attr * attr)330 int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr)
331 {
332 struct dirent *proc_entry;
333 DIR *procfs;
334 int retval;
335
336 if (strlen(comm_prefix) >= MAX_PATH) {
337 err_msg("Command prefix is too long: %d < strlen(%s)\n",
338 MAX_PATH, comm_prefix);
339 return 1;
340 }
341
342 procfs = opendir("/proc");
343 if (!procfs) {
344 err_msg("Could not open procfs\n");
345 return 1;
346 }
347
348 while ((proc_entry = readdir(procfs))) {
349
350 retval = procfs_is_workload_pid(comm_prefix, proc_entry);
351 if (!retval)
352 continue;
353
354 /* procfs_is_workload_pid confirmed it is a pid */
355 retval = __set_sched_attr(atoi(proc_entry->d_name), attr);
356 if (retval) {
357 err_msg("Error setting sched attributes for pid:%s\n", proc_entry->d_name);
358 goto out_err;
359 }
360
361 debug_msg("Set sched attributes for pid:%s\n", proc_entry->d_name);
362 }
363 return 0;
364
365 out_err:
366 closedir(procfs);
367 return 1;
368 }
369
370 #define INVALID_VAL (~0L)
get_long_ns_after_colon(char * start)371 static long get_long_ns_after_colon(char *start)
372 {
373 long val = INVALID_VAL;
374
375 /* find the ":" */
376 start = strstr(start, ":");
377 if (!start)
378 return -1;
379
380 /* skip ":" */
381 start++;
382 val = parse_ns_duration(start);
383
384 return val;
385 }
386
get_long_after_colon(char * start)387 static long get_long_after_colon(char *start)
388 {
389 long val = INVALID_VAL;
390
391 /* find the ":" */
392 start = strstr(start, ":");
393 if (!start)
394 return -1;
395
396 /* skip ":" */
397 start++;
398 val = get_llong_from_str(start);
399
400 return val;
401 }
402
403 /*
404 * parse priority in the format:
405 * SCHED_OTHER:
406 * o:<prio>
407 * O:<prio>
408 * SCHED_RR:
409 * r:<prio>
410 * R:<prio>
411 * SCHED_FIFO:
412 * f:<prio>
413 * F:<prio>
414 * SCHED_DEADLINE:
415 * d:runtime:period
416 * D:runtime:period
417 */
parse_prio(char * arg,struct sched_attr * sched_param)418 int parse_prio(char *arg, struct sched_attr *sched_param)
419 {
420 long prio;
421 long runtime;
422 long period;
423
424 memset(sched_param, 0, sizeof(*sched_param));
425 sched_param->size = sizeof(*sched_param);
426
427 switch (arg[0]) {
428 case 'd':
429 case 'D':
430 /* d:runtime:period */
431 if (strlen(arg) < 4)
432 return -1;
433
434 runtime = get_long_ns_after_colon(arg);
435 if (runtime == INVALID_VAL)
436 return -1;
437
438 period = get_long_ns_after_colon(&arg[2]);
439 if (period == INVALID_VAL)
440 return -1;
441
442 if (runtime > period)
443 return -1;
444
445 sched_param->sched_policy = SCHED_DEADLINE;
446 sched_param->sched_runtime = runtime;
447 sched_param->sched_deadline = period;
448 sched_param->sched_period = period;
449 break;
450 case 'f':
451 case 'F':
452 /* f:prio */
453 prio = get_long_after_colon(arg);
454 if (prio == INVALID_VAL)
455 return -1;
456
457 if (prio < sched_get_priority_min(SCHED_FIFO))
458 return -1;
459 if (prio > sched_get_priority_max(SCHED_FIFO))
460 return -1;
461
462 sched_param->sched_policy = SCHED_FIFO;
463 sched_param->sched_priority = prio;
464 break;
465 case 'r':
466 case 'R':
467 /* r:prio */
468 prio = get_long_after_colon(arg);
469 if (prio == INVALID_VAL)
470 return -1;
471
472 if (prio < sched_get_priority_min(SCHED_RR))
473 return -1;
474 if (prio > sched_get_priority_max(SCHED_RR))
475 return -1;
476
477 sched_param->sched_policy = SCHED_RR;
478 sched_param->sched_priority = prio;
479 break;
480 case 'o':
481 case 'O':
482 /* o:prio */
483 prio = get_long_after_colon(arg);
484 if (prio == INVALID_VAL)
485 return -1;
486
487 if (prio < sched_get_priority_min(SCHED_OTHER))
488 return -1;
489 if (prio > sched_get_priority_max(SCHED_OTHER))
490 return -1;
491
492 sched_param->sched_policy = SCHED_OTHER;
493 sched_param->sched_priority = prio;
494 break;
495 default:
496 return -1;
497 }
498 return 0;
499 }
500
501 /*
502 * set_cpu_dma_latency - set the /dev/cpu_dma_latecy
503 *
504 * This is used to reduce the exit from idle latency. The value
505 * will be reset once the file descriptor of /dev/cpu_dma_latecy
506 * is closed.
507 *
508 * Return: the /dev/cpu_dma_latecy file descriptor
509 */
set_cpu_dma_latency(int32_t latency)510 int set_cpu_dma_latency(int32_t latency)
511 {
512 int retval;
513 int fd;
514
515 fd = open("/dev/cpu_dma_latency", O_RDWR);
516 if (fd < 0) {
517 err_msg("Error opening /dev/cpu_dma_latency\n");
518 return -1;
519 }
520
521 retval = write(fd, &latency, 4);
522 if (retval < 1) {
523 err_msg("Error setting /dev/cpu_dma_latency\n");
524 close(fd);
525 return -1;
526 }
527
528 debug_msg("Set /dev/cpu_dma_latency to %d\n", latency);
529
530 return fd;
531 }
532