• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
__set_sched_attr(int pid,struct sched_attr * attr)246 int __set_sched_attr(int pid, struct sched_attr *attr)
247 {
248 	int flags = 0;
249 	int retval;
250 
251 	retval = sched_setattr(pid, attr, flags);
252 	if (retval < 0) {
253 		err_msg("Failed to set sched attributes to the pid %d: %s\n",
254 			pid, strerror(errno));
255 		return 1;
256 	}
257 
258 	return 0;
259 }
260 
261 /*
262  * procfs_is_workload_pid - check if a procfs entry contains a comm_prefix* comm
263  *
264  * Check if the procfs entry is a directory of a process, and then check if the
265  * process has a comm with the prefix set in char *comm_prefix. As the
266  * current users of this function only check for kernel threads, there is no
267  * need to check for the threads for the process.
268  *
269  * Return: True if the proc_entry contains a comm file with comm_prefix*.
270  * Otherwise returns false.
271  */
procfs_is_workload_pid(const char * comm_prefix,struct dirent * proc_entry)272 static int procfs_is_workload_pid(const char *comm_prefix, struct dirent *proc_entry)
273 {
274 	char buffer[MAX_PATH];
275 	int comm_fd, retval;
276 	char *t_name;
277 
278 	if (proc_entry->d_type != DT_DIR)
279 		return 0;
280 
281 	if (*proc_entry->d_name == '.')
282 		return 0;
283 
284 	/* check if the string is a pid */
285 	for (t_name = proc_entry->d_name; t_name; t_name++) {
286 		if (!isdigit(*t_name))
287 			break;
288 	}
289 
290 	if (*t_name != '\0')
291 		return 0;
292 
293 	snprintf(buffer, MAX_PATH, "/proc/%s/comm", proc_entry->d_name);
294 	comm_fd = open(buffer, O_RDONLY);
295 	if (comm_fd < 0)
296 		return 0;
297 
298 	memset(buffer, 0, MAX_PATH);
299 	retval = read(comm_fd, buffer, MAX_PATH);
300 
301 	close(comm_fd);
302 
303 	if (retval <= 0)
304 		return 0;
305 
306 	retval = strncmp(comm_prefix, buffer, strlen(comm_prefix));
307 	if (retval)
308 		return 0;
309 
310 	/* comm already have \n */
311 	debug_msg("Found workload pid:%s comm:%s", proc_entry->d_name, buffer);
312 
313 	return 1;
314 }
315 
316 /*
317  * set_comm_sched_attr - set sched params to threads starting with char *comm_prefix
318  *
319  * This function uses procfs to list the currently running threads and then set the
320  * sched_attr *attr to the threads that start with char *comm_prefix. It is
321  * mainly used to set the priority to the kernel threads created by the
322  * tracers.
323  */
set_comm_sched_attr(const char * comm_prefix,struct sched_attr * attr)324 int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr)
325 {
326 	struct dirent *proc_entry;
327 	DIR *procfs;
328 	int retval;
329 
330 	if (strlen(comm_prefix) >= MAX_PATH) {
331 		err_msg("Command prefix is too long: %d < strlen(%s)\n",
332 			MAX_PATH, comm_prefix);
333 		return 1;
334 	}
335 
336 	procfs = opendir("/proc");
337 	if (!procfs) {
338 		err_msg("Could not open procfs\n");
339 		return 1;
340 	}
341 
342 	while ((proc_entry = readdir(procfs))) {
343 
344 		retval = procfs_is_workload_pid(comm_prefix, proc_entry);
345 		if (!retval)
346 			continue;
347 
348 		/* procfs_is_workload_pid confirmed it is a pid */
349 		retval = __set_sched_attr(atoi(proc_entry->d_name), attr);
350 		if (retval) {
351 			err_msg("Error setting sched attributes for pid:%s\n", proc_entry->d_name);
352 			goto out_err;
353 		}
354 
355 		debug_msg("Set sched attributes for pid:%s\n", proc_entry->d_name);
356 	}
357 	return 0;
358 
359 out_err:
360 	closedir(procfs);
361 	return 1;
362 }
363 
364 #define INVALID_VAL	(~0L)
get_long_ns_after_colon(char * start)365 static long get_long_ns_after_colon(char *start)
366 {
367 	long val = INVALID_VAL;
368 
369 	/* find the ":" */
370 	start = strstr(start, ":");
371 	if (!start)
372 		return -1;
373 
374 	/* skip ":" */
375 	start++;
376 	val = parse_ns_duration(start);
377 
378 	return val;
379 }
380 
get_long_after_colon(char * start)381 static long get_long_after_colon(char *start)
382 {
383 	long val = INVALID_VAL;
384 
385 	/* find the ":" */
386 	start = strstr(start, ":");
387 	if (!start)
388 		return -1;
389 
390 	/* skip ":" */
391 	start++;
392 	val = get_llong_from_str(start);
393 
394 	return val;
395 }
396 
397 /*
398  * parse priority in the format:
399  * SCHED_OTHER:
400  *		o:<prio>
401  *		O:<prio>
402  * SCHED_RR:
403  *		r:<prio>
404  *		R:<prio>
405  * SCHED_FIFO:
406  *		f:<prio>
407  *		F:<prio>
408  * SCHED_DEADLINE:
409  *		d:runtime:period
410  *		D:runtime:period
411  */
parse_prio(char * arg,struct sched_attr * sched_param)412 int parse_prio(char *arg, struct sched_attr *sched_param)
413 {
414 	long prio;
415 	long runtime;
416 	long period;
417 
418 	memset(sched_param, 0, sizeof(*sched_param));
419 	sched_param->size = sizeof(*sched_param);
420 
421 	switch (arg[0]) {
422 	case 'd':
423 	case 'D':
424 		/* d:runtime:period */
425 		if (strlen(arg) < 4)
426 			return -1;
427 
428 		runtime = get_long_ns_after_colon(arg);
429 		if (runtime == INVALID_VAL)
430 			return -1;
431 
432 		period = get_long_ns_after_colon(&arg[2]);
433 		if (period == INVALID_VAL)
434 			return -1;
435 
436 		if (runtime > period)
437 			return -1;
438 
439 		sched_param->sched_policy   = SCHED_DEADLINE;
440 		sched_param->sched_runtime  = runtime;
441 		sched_param->sched_deadline = period;
442 		sched_param->sched_period   = period;
443 		break;
444 	case 'f':
445 	case 'F':
446 		/* f:prio */
447 		prio = get_long_after_colon(arg);
448 		if (prio == INVALID_VAL)
449 			return -1;
450 
451 		if (prio < sched_get_priority_min(SCHED_FIFO))
452 			return -1;
453 		if (prio > sched_get_priority_max(SCHED_FIFO))
454 			return -1;
455 
456 		sched_param->sched_policy   = SCHED_FIFO;
457 		sched_param->sched_priority = prio;
458 		break;
459 	case 'r':
460 	case 'R':
461 		/* r:prio */
462 		prio = get_long_after_colon(arg);
463 		if (prio == INVALID_VAL)
464 			return -1;
465 
466 		if (prio < sched_get_priority_min(SCHED_RR))
467 			return -1;
468 		if (prio > sched_get_priority_max(SCHED_RR))
469 			return -1;
470 
471 		sched_param->sched_policy   = SCHED_RR;
472 		sched_param->sched_priority = prio;
473 		break;
474 	case 'o':
475 	case 'O':
476 		/* o:prio */
477 		prio = get_long_after_colon(arg);
478 		if (prio == INVALID_VAL)
479 			return -1;
480 
481 		if (prio < MIN_NICE)
482 			return -1;
483 		if (prio > MAX_NICE)
484 			return -1;
485 
486 		sched_param->sched_policy   = SCHED_OTHER;
487 		sched_param->sched_nice = prio;
488 		break;
489 	default:
490 		return -1;
491 	}
492 	return 0;
493 }
494 
495 /*
496  * set_cpu_dma_latency - set the /dev/cpu_dma_latecy
497  *
498  * This is used to reduce the exit from idle latency. The value
499  * will be reset once the file descriptor of /dev/cpu_dma_latecy
500  * is closed.
501  *
502  * Return: the /dev/cpu_dma_latecy file descriptor
503  */
set_cpu_dma_latency(int32_t latency)504 int set_cpu_dma_latency(int32_t latency)
505 {
506 	int retval;
507 	int fd;
508 
509 	fd = open("/dev/cpu_dma_latency", O_RDWR);
510 	if (fd < 0) {
511 		err_msg("Error opening /dev/cpu_dma_latency\n");
512 		return -1;
513 	}
514 
515 	retval = write(fd, &latency, 4);
516 	if (retval < 1) {
517 		err_msg("Error setting /dev/cpu_dma_latency\n");
518 		close(fd);
519 		return -1;
520 	}
521 
522 	debug_msg("Set /dev/cpu_dma_latency to %d\n", latency);
523 
524 	return fd;
525 }
526