]> Git Repo - linux.git/blob - tools/tracing/rtla/src/utils.c
Merge tag 'cxl-for-6.0' of git://git.kernel.org/pub/scm/linux/kernel/git/cxl/cxl
[linux.git] / tools / tracing / rtla / src / utils.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <[email protected]>
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  */
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  */
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  */
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  */
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  */
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  */
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  */
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
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
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
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  */
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  */
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)
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
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  */
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  */
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 }
This page took 0.066935 seconds and 4 git commands to generate.