xref: /linux/tools/tracing/rtla/src/utils.c (revision 34f7c6e7d4396090692a09789db231e12cb4762b)
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 <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 <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("boost_with_deadline failed to boost pid %d: %s\n",
259 			pid, strerror(errno));
260 		return 1;
261 	}
262 
263 	return 0;
264 }
265 /*
266  * set_comm_sched_attr - set sched params to threads starting with char *comm
267  *
268  * This function uses procps to list the currently running threads and then
269  * set the sched_attr *attr to the threads that start with char *comm. It is
270  * mainly used to set the priority to the kernel threads created by the
271  * tracers.
272  */
273 int set_comm_sched_attr(const char *comm, struct sched_attr *attr)
274 {
275 	int flags = PROC_FILLCOM | PROC_FILLSTAT;
276 	PROCTAB *ptp;
277 	proc_t task;
278 	int retval;
279 
280 	ptp = openproc(flags);
281 	if (!ptp) {
282 		err_msg("error openproc()\n");
283 		return -ENOENT;
284 	}
285 
286 	memset(&task, 0, sizeof(task));
287 
288 	while (readproc(ptp, &task)) {
289 		retval = strncmp(comm, task.cmd, strlen(comm));
290 		if (retval)
291 			continue;
292 		retval = __set_sched_attr(task.tid, attr);
293 		if (retval)
294 			goto out_err;
295 	}
296 
297 	closeproc(ptp);
298 	return 0;
299 
300 out_err:
301 	closeproc(ptp);
302 	return 1;
303 }
304 
305 #define INVALID_VAL	(~0L)
306 static long get_long_ns_after_colon(char *start)
307 {
308 	long val = INVALID_VAL;
309 
310 	/* find the ":" */
311 	start = strstr(start, ":");
312 	if (!start)
313 		return -1;
314 
315 	/* skip ":" */
316 	start++;
317 	val = parse_ns_duration(start);
318 
319 	return val;
320 }
321 
322 static long get_long_after_colon(char *start)
323 {
324 	long val = INVALID_VAL;
325 
326 	/* find the ":" */
327 	start = strstr(start, ":");
328 	if (!start)
329 		return -1;
330 
331 	/* skip ":" */
332 	start++;
333 	val = get_llong_from_str(start);
334 
335 	return val;
336 }
337 
338 /*
339  * parse priority in the format:
340  * SCHED_OTHER:
341  *		o:<prio>
342  *		O:<prio>
343  * SCHED_RR:
344  *		r:<prio>
345  *		R:<prio>
346  * SCHED_FIFO:
347  *		f:<prio>
348  *		F:<prio>
349  * SCHED_DEADLINE:
350  *		d:runtime:period
351  *		D:runtime:period
352  */
353 int parse_prio(char *arg, struct sched_attr *sched_param)
354 {
355 	long prio;
356 	long runtime;
357 	long period;
358 
359 	memset(sched_param, 0, sizeof(*sched_param));
360 	sched_param->size = sizeof(*sched_param);
361 
362 	switch (arg[0]) {
363 	case 'd':
364 	case 'D':
365 		/* d:runtime:period */
366 		if (strlen(arg) < 4)
367 			return -1;
368 
369 		runtime = get_long_ns_after_colon(arg);
370 		if (runtime == INVALID_VAL)
371 			return -1;
372 
373 		period = get_long_ns_after_colon(&arg[2]);
374 		if (period == INVALID_VAL)
375 			return -1;
376 
377 		if (runtime > period)
378 			return -1;
379 
380 		sched_param->sched_policy   = SCHED_DEADLINE;
381 		sched_param->sched_runtime  = runtime;
382 		sched_param->sched_deadline = period;
383 		sched_param->sched_period   = period;
384 		break;
385 	case 'f':
386 	case 'F':
387 		/* f:prio */
388 		prio = get_long_after_colon(arg);
389 		if (prio == INVALID_VAL)
390 			return -1;
391 
392 		if (prio < sched_get_priority_min(SCHED_FIFO))
393 			return -1;
394 		if (prio > sched_get_priority_max(SCHED_FIFO))
395 			return -1;
396 
397 		sched_param->sched_policy   = SCHED_FIFO;
398 		sched_param->sched_priority = prio;
399 		break;
400 	case 'r':
401 	case 'R':
402 		/* r:prio */
403 		prio = get_long_after_colon(arg);
404 		if (prio == INVALID_VAL)
405 			return -1;
406 
407 		if (prio < sched_get_priority_min(SCHED_RR))
408 			return -1;
409 		if (prio > sched_get_priority_max(SCHED_RR))
410 			return -1;
411 
412 		sched_param->sched_policy   = SCHED_RR;
413 		sched_param->sched_priority = prio;
414 		break;
415 	case 'o':
416 	case 'O':
417 		/* o:prio */
418 		prio = get_long_after_colon(arg);
419 		if (prio == INVALID_VAL)
420 			return -1;
421 
422 		if (prio < sched_get_priority_min(SCHED_OTHER))
423 			return -1;
424 		if (prio > sched_get_priority_max(SCHED_OTHER))
425 			return -1;
426 
427 		sched_param->sched_policy   = SCHED_OTHER;
428 		sched_param->sched_priority = prio;
429 		break;
430 	default:
431 		return -1;
432 	}
433 	return 0;
434 }
435 
436 /*
437  * set_cpu_dma_latency - set the /dev/cpu_dma_latecy
438  *
439  * This is used to reduce the exit from idle latency. The value
440  * will be reset once the file descriptor of /dev/cpu_dma_latecy
441  * is closed.
442  *
443  * Return: the /dev/cpu_dma_latecy file descriptor
444  */
445 int set_cpu_dma_latency(int32_t latency)
446 {
447 	int retval;
448 	int fd;
449 
450 	fd = open("/dev/cpu_dma_latency", O_RDWR);
451 	if (fd < 0) {
452 		err_msg("Error opening /dev/cpu_dma_latency\n");
453 		return -1;
454 	}
455 
456 	retval = write(fd, &latency, 4);
457 	if (retval < 1) {
458 		err_msg("Error setting /dev/cpu_dma_latency\n");
459 		close(fd);
460 		return -1;
461 	}
462 
463 	debug_msg("Set /dev/cpu_dma_latency to %d\n", latency);
464 
465 	return fd;
466 }
467