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