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