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
__set_sched_attr(int pid,struct sched_attr * attr)246 int __set_sched_attr(int pid, struct sched_attr *attr)
247 {
248 int flags = 0;
249 int retval;
250
251 retval = sched_setattr(pid, attr, flags);
252 if (retval < 0) {
253 err_msg("Failed to set sched attributes to the pid %d: %s\n",
254 pid, strerror(errno));
255 return 1;
256 }
257
258 return 0;
259 }
260
261 /*
262 * procfs_is_workload_pid - check if a procfs entry contains a comm_prefix* comm
263 *
264 * Check if the procfs entry is a directory of a process, and then check if the
265 * process has a comm with the prefix set in char *comm_prefix. As the
266 * current users of this function only check for kernel threads, there is no
267 * need to check for the threads for the process.
268 *
269 * Return: True if the proc_entry contains a comm file with comm_prefix*.
270 * Otherwise returns false.
271 */
procfs_is_workload_pid(const char * comm_prefix,struct dirent * proc_entry)272 static int procfs_is_workload_pid(const char *comm_prefix, struct dirent *proc_entry)
273 {
274 char buffer[MAX_PATH];
275 int comm_fd, retval;
276 char *t_name;
277
278 if (proc_entry->d_type != DT_DIR)
279 return 0;
280
281 if (*proc_entry->d_name == '.')
282 return 0;
283
284 /* check if the string is a pid */
285 for (t_name = proc_entry->d_name; t_name; t_name++) {
286 if (!isdigit(*t_name))
287 break;
288 }
289
290 if (*t_name != '\0')
291 return 0;
292
293 snprintf(buffer, MAX_PATH, "/proc/%s/comm", proc_entry->d_name);
294 comm_fd = open(buffer, O_RDONLY);
295 if (comm_fd < 0)
296 return 0;
297
298 memset(buffer, 0, MAX_PATH);
299 retval = read(comm_fd, buffer, MAX_PATH);
300
301 close(comm_fd);
302
303 if (retval <= 0)
304 return 0;
305
306 retval = strncmp(comm_prefix, buffer, strlen(comm_prefix));
307 if (retval)
308 return 0;
309
310 /* comm already have \n */
311 debug_msg("Found workload pid:%s comm:%s", proc_entry->d_name, buffer);
312
313 return 1;
314 }
315
316 /*
317 * set_comm_sched_attr - set sched params to threads starting with char *comm_prefix
318 *
319 * This function uses procfs to list the currently running threads and then set the
320 * sched_attr *attr to the threads that start with char *comm_prefix. It is
321 * mainly used to set the priority to the kernel threads created by the
322 * tracers.
323 */
set_comm_sched_attr(const char * comm_prefix,struct sched_attr * attr)324 int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr)
325 {
326 struct dirent *proc_entry;
327 DIR *procfs;
328 int retval;
329
330 if (strlen(comm_prefix) >= MAX_PATH) {
331 err_msg("Command prefix is too long: %d < strlen(%s)\n",
332 MAX_PATH, comm_prefix);
333 return 1;
334 }
335
336 procfs = opendir("/proc");
337 if (!procfs) {
338 err_msg("Could not open procfs\n");
339 return 1;
340 }
341
342 while ((proc_entry = readdir(procfs))) {
343
344 retval = procfs_is_workload_pid(comm_prefix, proc_entry);
345 if (!retval)
346 continue;
347
348 /* procfs_is_workload_pid confirmed it is a pid */
349 retval = __set_sched_attr(atoi(proc_entry->d_name), attr);
350 if (retval) {
351 err_msg("Error setting sched attributes for pid:%s\n", proc_entry->d_name);
352 goto out_err;
353 }
354
355 debug_msg("Set sched attributes for pid:%s\n", proc_entry->d_name);
356 }
357 return 0;
358
359 out_err:
360 closedir(procfs);
361 return 1;
362 }
363
364 #define INVALID_VAL (~0L)
get_long_ns_after_colon(char * start)365 static long get_long_ns_after_colon(char *start)
366 {
367 long val = INVALID_VAL;
368
369 /* find the ":" */
370 start = strstr(start, ":");
371 if (!start)
372 return -1;
373
374 /* skip ":" */
375 start++;
376 val = parse_ns_duration(start);
377
378 return val;
379 }
380
get_long_after_colon(char * start)381 static long get_long_after_colon(char *start)
382 {
383 long val = INVALID_VAL;
384
385 /* find the ":" */
386 start = strstr(start, ":");
387 if (!start)
388 return -1;
389
390 /* skip ":" */
391 start++;
392 val = get_llong_from_str(start);
393
394 return val;
395 }
396
397 /*
398 * parse priority in the format:
399 * SCHED_OTHER:
400 * o:<prio>
401 * O:<prio>
402 * SCHED_RR:
403 * r:<prio>
404 * R:<prio>
405 * SCHED_FIFO:
406 * f:<prio>
407 * F:<prio>
408 * SCHED_DEADLINE:
409 * d:runtime:period
410 * D:runtime:period
411 */
parse_prio(char * arg,struct sched_attr * sched_param)412 int parse_prio(char *arg, struct sched_attr *sched_param)
413 {
414 long prio;
415 long runtime;
416 long period;
417
418 memset(sched_param, 0, sizeof(*sched_param));
419 sched_param->size = sizeof(*sched_param);
420
421 switch (arg[0]) {
422 case 'd':
423 case 'D':
424 /* d:runtime:period */
425 if (strlen(arg) < 4)
426 return -1;
427
428 runtime = get_long_ns_after_colon(arg);
429 if (runtime == INVALID_VAL)
430 return -1;
431
432 period = get_long_ns_after_colon(&arg[2]);
433 if (period == INVALID_VAL)
434 return -1;
435
436 if (runtime > period)
437 return -1;
438
439 sched_param->sched_policy = SCHED_DEADLINE;
440 sched_param->sched_runtime = runtime;
441 sched_param->sched_deadline = period;
442 sched_param->sched_period = period;
443 break;
444 case 'f':
445 case 'F':
446 /* f:prio */
447 prio = get_long_after_colon(arg);
448 if (prio == INVALID_VAL)
449 return -1;
450
451 if (prio < sched_get_priority_min(SCHED_FIFO))
452 return -1;
453 if (prio > sched_get_priority_max(SCHED_FIFO))
454 return -1;
455
456 sched_param->sched_policy = SCHED_FIFO;
457 sched_param->sched_priority = prio;
458 break;
459 case 'r':
460 case 'R':
461 /* r:prio */
462 prio = get_long_after_colon(arg);
463 if (prio == INVALID_VAL)
464 return -1;
465
466 if (prio < sched_get_priority_min(SCHED_RR))
467 return -1;
468 if (prio > sched_get_priority_max(SCHED_RR))
469 return -1;
470
471 sched_param->sched_policy = SCHED_RR;
472 sched_param->sched_priority = prio;
473 break;
474 case 'o':
475 case 'O':
476 /* o:prio */
477 prio = get_long_after_colon(arg);
478 if (prio == INVALID_VAL)
479 return -1;
480
481 if (prio < MIN_NICE)
482 return -1;
483 if (prio > MAX_NICE)
484 return -1;
485
486 sched_param->sched_policy = SCHED_OTHER;
487 sched_param->sched_nice = prio;
488 break;
489 default:
490 return -1;
491 }
492 return 0;
493 }
494
495 /*
496 * set_cpu_dma_latency - set the /dev/cpu_dma_latecy
497 *
498 * This is used to reduce the exit from idle latency. The value
499 * will be reset once the file descriptor of /dev/cpu_dma_latecy
500 * is closed.
501 *
502 * Return: the /dev/cpu_dma_latecy file descriptor
503 */
set_cpu_dma_latency(int32_t latency)504 int set_cpu_dma_latency(int32_t latency)
505 {
506 int retval;
507 int fd;
508
509 fd = open("/dev/cpu_dma_latency", O_RDWR);
510 if (fd < 0) {
511 err_msg("Error opening /dev/cpu_dma_latency\n");
512 return -1;
513 }
514
515 retval = write(fd, &latency, 4);
516 if (retval < 1) {
517 err_msg("Error setting /dev/cpu_dma_latency\n");
518 close(fd);
519 return -1;
520 }
521
522 debug_msg("Set /dev/cpu_dma_latency to %d\n", latency);
523
524 return fd;
525 }
526