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 
sched_getattr(pid_t pid,struct sched_attr * attr,unsigned int size,unsigned int flags)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 
__set_sched_attr(int pid,struct sched_attr * attr)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  */
procfs_is_workload_pid(const char * comm_prefix,struct dirent * proc_entry)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  */
set_comm_sched_attr(const char * comm_prefix,struct sched_attr * attr)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)
get_long_ns_after_colon(char * start)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 
get_long_after_colon(char * start)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  */
parse_prio(char * arg,struct sched_attr * sched_param)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  */
set_cpu_dma_latency(int32_t latency)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 }
532