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 = 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 */ 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 */ 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 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 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 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 */ 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 */ 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) 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 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 */ 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 */ 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