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 } 531