xref: /openbmc/linux/tools/tracing/rtla/src/utils.c (revision 94d964e5)
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 <sched.h>
14 #include <stdio.h>
15 
16 #include "utils.h"
17 
18 #define MAX_MSG_LENGTH	1024
19 int config_debug;
20 
21 /*
22  * err_msg - print an error message to the stderr
23  */
24 void err_msg(const char *fmt, ...)
25 {
26 	char message[MAX_MSG_LENGTH];
27 	va_list ap;
28 
29 	va_start(ap, fmt);
30 	vsnprintf(message, sizeof(message), fmt, ap);
31 	va_end(ap);
32 
33 	fprintf(stderr, "%s", message);
34 }
35 
36 /*
37  * debug_msg - print a debug message to stderr if debug is set
38  */
39 void debug_msg(const char *fmt, ...)
40 {
41 	char message[MAX_MSG_LENGTH];
42 	va_list ap;
43 
44 	if (!config_debug)
45 		return;
46 
47 	va_start(ap, fmt);
48 	vsnprintf(message, sizeof(message), fmt, ap);
49 	va_end(ap);
50 
51 	fprintf(stderr, "%s", message);
52 }
53 
54 /*
55  * get_llong_from_str - get a long long int from a string
56  */
57 long long get_llong_from_str(char *start)
58 {
59 	long long value;
60 	char *end;
61 
62 	errno = 0;
63 	value = strtoll(start, &end, 10);
64 	if (errno || start == end)
65 		return -1;
66 
67 	return value;
68 }
69 
70 /*
71  * get_duration - fill output with a human readable duration since start_time
72  */
73 void get_duration(time_t start_time, char *output, int output_size)
74 {
75 	time_t now = time(NULL);
76 	struct tm *tm_info;
77 	time_t duration;
78 
79 	duration = difftime(now, start_time);
80 	tm_info = localtime(&duration);
81 
82 	snprintf(output, output_size, "%3d %02d:%02d:%02d",
83 			tm_info->tm_yday,
84 			tm_info->tm_hour - 1,
85 			tm_info->tm_min,
86 			tm_info->tm_sec);
87 }
88 
89 /*
90  * parse_cpu_list - parse a cpu_list filling a char vector with cpus set
91  *
92  * Receives a cpu list, like 1-3,5 (cpus 1, 2, 3, 5), and then set the char
93  * in the monitored_cpus.
94  *
95  * XXX: convert to a bitmask.
96  */
97 int parse_cpu_list(char *cpu_list, char **monitored_cpus)
98 {
99 	char *mon_cpus;
100 	const char *p;
101 	int end_cpu;
102 	int nr_cpus;
103 	int cpu;
104 	int i;
105 
106 	nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
107 
108 	mon_cpus = malloc(nr_cpus * sizeof(char));
109 	memset(mon_cpus, 0, (nr_cpus * sizeof(char)));
110 
111 	for (p = cpu_list; *p; ) {
112 		cpu = atoi(p);
113 		if (cpu < 0 || (!cpu && *p != '0') || cpu >= nr_cpus)
114 			goto err;
115 
116 		while (isdigit(*p))
117 			p++;
118 		if (*p == '-') {
119 			p++;
120 			end_cpu = atoi(p);
121 			if (end_cpu < cpu || (!end_cpu && *p != '0') || end_cpu >= nr_cpus)
122 				goto err;
123 			while (isdigit(*p))
124 				p++;
125 		} else
126 			end_cpu = cpu;
127 
128 		if (cpu == end_cpu) {
129 			debug_msg("cpu_list: adding cpu %d\n", cpu);
130 			mon_cpus[cpu] = 1;
131 		} else {
132 			for (i = cpu; i <= end_cpu; i++) {
133 				debug_msg("cpu_list: adding cpu %d\n", i);
134 				mon_cpus[i] = 1;
135 			}
136 		}
137 
138 		if (*p == ',')
139 			p++;
140 	}
141 
142 	*monitored_cpus = mon_cpus;
143 
144 	return 0;
145 
146 err:
147 	debug_msg("Error parsing the cpu list %s", cpu_list);
148 	return 1;
149 }
150 
151 /*
152  * parse_duration - parse duration with s/m/h/d suffix converting it to seconds
153  */
154 long parse_seconds_duration(char *val)
155 {
156 	char *end;
157 	long t;
158 
159 	t = strtol(val, &end, 10);
160 
161 	if (end) {
162 		switch (*end) {
163 		case 's':
164 		case 'S':
165 			break;
166 		case 'm':
167 		case 'M':
168 			t *= 60;
169 			break;
170 		case 'h':
171 		case 'H':
172 			t *= 60 * 60;
173 			break;
174 
175 		case 'd':
176 		case 'D':
177 			t *= 24 * 60 * 60;
178 			break;
179 		}
180 	}
181 
182 	return t;
183 }
184 
185 /*
186  * parse_ns_duration - parse duration with ns/us/ms/s converting it to nanoseconds
187  */
188 long parse_ns_duration(char *val)
189 {
190 	char *end;
191 	long t;
192 
193 	t = strtol(val, &end, 10);
194 
195 	if (end) {
196 		if (!strncmp(end, "ns", 2)) {
197 			return t;
198 		} else if (!strncmp(end, "us", 2)) {
199 			t *= 1000;
200 			return t;
201 		} else if (!strncmp(end, "ms", 2)) {
202 			t *= 1000 * 1000;
203 			return t;
204 		} else if (!strncmp(end, "s", 1)) {
205 			t *= 1000 * 1000 * 1000;
206 			return t;
207 		}
208 		return -1;
209 	}
210 
211 	return t;
212 }
213 
214 /*
215  * This is a set of helper functions to use SCHED_DEADLINE.
216  */
217 #ifdef __x86_64__
218 # define __NR_sched_setattr	314
219 # define __NR_sched_getattr	315
220 #elif __i386__
221 # define __NR_sched_setattr	351
222 # define __NR_sched_getattr	352
223 #elif __arm__
224 # define __NR_sched_setattr	380
225 # define __NR_sched_getattr	381
226 #elif __aarch64__
227 # define __NR_sched_setattr	274
228 # define __NR_sched_getattr	275
229 #elif __powerpc__
230 # define __NR_sched_setattr	355
231 # define __NR_sched_getattr	356
232 #elif __s390x__
233 # define __NR_sched_setattr	345
234 # define __NR_sched_getattr	346
235 #endif
236 
237 #define SCHED_DEADLINE		6
238 
239 static inline int sched_setattr(pid_t pid, const struct sched_attr *attr,
240 				unsigned int flags) {
241 	return syscall(__NR_sched_setattr, pid, attr, flags);
242 }
243 
244 static inline int sched_getattr(pid_t pid, struct sched_attr *attr,
245 				unsigned int size, unsigned int flags)
246 {
247 	return syscall(__NR_sched_getattr, pid, attr, size, flags);
248 }
249 
250 int __set_sched_attr(int pid, struct sched_attr *attr)
251 {
252 	int flags = 0;
253 	int retval;
254 
255 	retval = sched_setattr(pid, attr, flags);
256 	if (retval < 0) {
257 		err_msg("boost_with_deadline failed to boost pid %d: %s\n",
258 			pid, strerror(errno));
259 		return 1;
260 	}
261 
262 	return 0;
263 }
264 /*
265  * set_comm_sched_attr - set sched params to threads starting with char *comm
266  *
267  * This function uses procps to list the currently running threads and then
268  * set the sched_attr *attr to the threads that start with char *comm. It is
269  * mainly used to set the priority to the kernel threads created by the
270  * tracers.
271  */
272 int set_comm_sched_attr(const char *comm, struct sched_attr *attr)
273 {
274 	int flags = PROC_FILLCOM | PROC_FILLSTAT;
275 	PROCTAB *ptp;
276 	proc_t task;
277 	int retval;
278 
279 	ptp = openproc(flags);
280 	if (!ptp) {
281 		err_msg("error openproc()\n");
282 		return -ENOENT;
283 	}
284 
285 	memset(&task, 0, sizeof(task));
286 
287 	while (readproc(ptp, &task)) {
288 		retval = strncmp(comm, task.cmd, strlen(comm));
289 		if (retval)
290 			continue;
291 		retval = __set_sched_attr(task.tid, attr);
292 		if (retval)
293 			goto out_err;
294 	}
295 
296 	closeproc(ptp);
297 	return 0;
298 
299 out_err:
300 	closeproc(ptp);
301 	return 1;
302 }
303 
304 #define INVALID_VAL	(~0L)
305 static long get_long_ns_after_colon(char *start)
306 {
307 	long val = INVALID_VAL;
308 
309 	/* find the ":" */
310 	start = strstr(start, ":");
311 	if (!start)
312 		return -1;
313 
314 	/* skip ":" */
315 	start++;
316 	val = parse_ns_duration(start);
317 
318 	return val;
319 }
320 
321 static long get_long_after_colon(char *start)
322 {
323 	long val = INVALID_VAL;
324 
325 	/* find the ":" */
326 	start = strstr(start, ":");
327 	if (!start)
328 		return -1;
329 
330 	/* skip ":" */
331 	start++;
332 	val = get_llong_from_str(start);
333 
334 	return val;
335 }
336 
337 /*
338  * parse priority in the format:
339  * SCHED_OTHER:
340  *		o:<prio>
341  *		O:<prio>
342  * SCHED_RR:
343  *		r:<prio>
344  *		R:<prio>
345  * SCHED_FIFO:
346  *		f:<prio>
347  *		F:<prio>
348  * SCHED_DEADLINE:
349  *		d:runtime:period
350  *		D:runtime:period
351  */
352 int parse_prio(char *arg, struct sched_attr *sched_param)
353 {
354 	long prio;
355 	long runtime;
356 	long period;
357 
358 	memset(sched_param, 0, sizeof(*sched_param));
359 	sched_param->size = sizeof(*sched_param);
360 
361 	switch (arg[0]) {
362 	case 'd':
363 	case 'D':
364 		/* d:runtime:period */
365 		if (strlen(arg) < 4)
366 			return -1;
367 
368 		runtime = get_long_ns_after_colon(arg);
369 		if (runtime == INVALID_VAL)
370 			return -1;
371 
372 		period = get_long_ns_after_colon(&arg[2]);
373 		if (period == INVALID_VAL)
374 			return -1;
375 
376 		if (runtime > period)
377 			return -1;
378 
379 		sched_param->sched_policy   = SCHED_DEADLINE;
380 		sched_param->sched_runtime  = runtime;
381 		sched_param->sched_deadline = period;
382 		sched_param->sched_period   = period;
383 		break;
384 	case 'f':
385 	case 'F':
386 		/* f:prio */
387 		prio = get_long_after_colon(arg);
388 		if (prio == INVALID_VAL)
389 			return -1;
390 
391 		if (prio < sched_get_priority_min(SCHED_FIFO))
392 			return -1;
393 		if (prio > sched_get_priority_max(SCHED_FIFO))
394 			return -1;
395 
396 		sched_param->sched_policy   = SCHED_FIFO;
397 		sched_param->sched_priority = prio;
398 		break;
399 	case 'r':
400 	case 'R':
401 		/* r:prio */
402 		prio = get_long_after_colon(arg);
403 		if (prio == INVALID_VAL)
404 			return -1;
405 
406 		if (prio < sched_get_priority_min(SCHED_RR))
407 			return -1;
408 		if (prio > sched_get_priority_max(SCHED_RR))
409 			return -1;
410 
411 		sched_param->sched_policy   = SCHED_RR;
412 		sched_param->sched_priority = prio;
413 		break;
414 	case 'o':
415 	case 'O':
416 		/* o:prio */
417 		prio = get_long_after_colon(arg);
418 		if (prio == INVALID_VAL)
419 			return -1;
420 
421 		if (prio < sched_get_priority_min(SCHED_OTHER))
422 			return -1;
423 		if (prio > sched_get_priority_max(SCHED_OTHER))
424 			return -1;
425 
426 		sched_param->sched_policy   = SCHED_OTHER;
427 		sched_param->sched_priority = prio;
428 		break;
429 	default:
430 		return -1;
431 	}
432 	return 0;
433 }
434