Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
0004  */
0005 
0006 #include <dirent.h>
0007 #include <stdarg.h>
0008 #include <stdlib.h>
0009 #include <string.h>
0010 #include <unistd.h>
0011 #include <ctype.h>
0012 #include <errno.h>
0013 #include <fcntl.h>
0014 #include <sched.h>
0015 #include <stdio.h>
0016 
0017 #include "utils.h"
0018 
0019 #define MAX_MSG_LENGTH  1024
0020 int config_debug;
0021 
0022 /*
0023  * err_msg - print an error message to the stderr
0024  */
0025 void err_msg(const char *fmt, ...)
0026 {
0027     char message[MAX_MSG_LENGTH];
0028     va_list ap;
0029 
0030     va_start(ap, fmt);
0031     vsnprintf(message, sizeof(message), fmt, ap);
0032     va_end(ap);
0033 
0034     fprintf(stderr, "%s", message);
0035 }
0036 
0037 /*
0038  * debug_msg - print a debug message to stderr if debug is set
0039  */
0040 void debug_msg(const char *fmt, ...)
0041 {
0042     char message[MAX_MSG_LENGTH];
0043     va_list ap;
0044 
0045     if (!config_debug)
0046         return;
0047 
0048     va_start(ap, fmt);
0049     vsnprintf(message, sizeof(message), fmt, ap);
0050     va_end(ap);
0051 
0052     fprintf(stderr, "%s", message);
0053 }
0054 
0055 /*
0056  * get_llong_from_str - get a long long int from a string
0057  */
0058 long long get_llong_from_str(char *start)
0059 {
0060     long long value;
0061     char *end;
0062 
0063     errno = 0;
0064     value = strtoll(start, &end, 10);
0065     if (errno || start == end)
0066         return -1;
0067 
0068     return value;
0069 }
0070 
0071 /*
0072  * get_duration - fill output with a human readable duration since start_time
0073  */
0074 void get_duration(time_t start_time, char *output, int output_size)
0075 {
0076     time_t now = time(NULL);
0077     struct tm *tm_info;
0078     time_t duration;
0079 
0080     duration = difftime(now, start_time);
0081     tm_info = gmtime(&duration);
0082 
0083     snprintf(output, output_size, "%3d %02d:%02d:%02d",
0084             tm_info->tm_yday,
0085             tm_info->tm_hour,
0086             tm_info->tm_min,
0087             tm_info->tm_sec);
0088 }
0089 
0090 /*
0091  * parse_cpu_list - parse a cpu_list filling a char vector with cpus set
0092  *
0093  * Receives a cpu list, like 1-3,5 (cpus 1, 2, 3, 5), and then set the char
0094  * in the monitored_cpus.
0095  *
0096  * XXX: convert to a bitmask.
0097  */
0098 int parse_cpu_list(char *cpu_list, char **monitored_cpus)
0099 {
0100     char *mon_cpus;
0101     const char *p;
0102     int end_cpu;
0103     int nr_cpus;
0104     int cpu;
0105     int i;
0106 
0107     nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
0108 
0109     mon_cpus = calloc(nr_cpus, sizeof(char));
0110     if (!mon_cpus)
0111         goto err;
0112 
0113     for (p = cpu_list; *p; ) {
0114         cpu = atoi(p);
0115         if (cpu < 0 || (!cpu && *p != '0') || cpu >= nr_cpus)
0116             goto err;
0117 
0118         while (isdigit(*p))
0119             p++;
0120         if (*p == '-') {
0121             p++;
0122             end_cpu = atoi(p);
0123             if (end_cpu < cpu || (!end_cpu && *p != '0') || end_cpu >= nr_cpus)
0124                 goto err;
0125             while (isdigit(*p))
0126                 p++;
0127         } else
0128             end_cpu = cpu;
0129 
0130         if (cpu == end_cpu) {
0131             debug_msg("cpu_list: adding cpu %d\n", cpu);
0132             mon_cpus[cpu] = 1;
0133         } else {
0134             for (i = cpu; i <= end_cpu; i++) {
0135                 debug_msg("cpu_list: adding cpu %d\n", i);
0136                 mon_cpus[i] = 1;
0137             }
0138         }
0139 
0140         if (*p == ',')
0141             p++;
0142     }
0143 
0144     *monitored_cpus = mon_cpus;
0145 
0146     return 0;
0147 
0148 err:
0149     debug_msg("Error parsing the cpu list %s", cpu_list);
0150     return 1;
0151 }
0152 
0153 /*
0154  * parse_duration - parse duration with s/m/h/d suffix converting it to seconds
0155  */
0156 long parse_seconds_duration(char *val)
0157 {
0158     char *end;
0159     long t;
0160 
0161     t = strtol(val, &end, 10);
0162 
0163     if (end) {
0164         switch (*end) {
0165         case 's':
0166         case 'S':
0167             break;
0168         case 'm':
0169         case 'M':
0170             t *= 60;
0171             break;
0172         case 'h':
0173         case 'H':
0174             t *= 60 * 60;
0175             break;
0176 
0177         case 'd':
0178         case 'D':
0179             t *= 24 * 60 * 60;
0180             break;
0181         }
0182     }
0183 
0184     return t;
0185 }
0186 
0187 /*
0188  * parse_ns_duration - parse duration with ns/us/ms/s converting it to nanoseconds
0189  */
0190 long parse_ns_duration(char *val)
0191 {
0192     char *end;
0193     long t;
0194 
0195     t = strtol(val, &end, 10);
0196 
0197     if (end) {
0198         if (!strncmp(end, "ns", 2)) {
0199             return t;
0200         } else if (!strncmp(end, "us", 2)) {
0201             t *= 1000;
0202             return t;
0203         } else if (!strncmp(end, "ms", 2)) {
0204             t *= 1000 * 1000;
0205             return t;
0206         } else if (!strncmp(end, "s", 1)) {
0207             t *= 1000 * 1000 * 1000;
0208             return t;
0209         }
0210         return -1;
0211     }
0212 
0213     return t;
0214 }
0215 
0216 /*
0217  * This is a set of helper functions to use SCHED_DEADLINE.
0218  */
0219 #ifdef __x86_64__
0220 # define __NR_sched_setattr 314
0221 # define __NR_sched_getattr 315
0222 #elif __i386__
0223 # define __NR_sched_setattr 351
0224 # define __NR_sched_getattr 352
0225 #elif __arm__
0226 # define __NR_sched_setattr 380
0227 # define __NR_sched_getattr 381
0228 #elif __aarch64__ || __riscv
0229 # define __NR_sched_setattr 274
0230 # define __NR_sched_getattr 275
0231 #elif __powerpc__
0232 # define __NR_sched_setattr 355
0233 # define __NR_sched_getattr 356
0234 #elif __s390x__
0235 # define __NR_sched_setattr 345
0236 # define __NR_sched_getattr 346
0237 #endif
0238 
0239 #define SCHED_DEADLINE      6
0240 
0241 static inline int sched_setattr(pid_t pid, const struct sched_attr *attr,
0242                 unsigned int flags) {
0243     return syscall(__NR_sched_setattr, pid, attr, flags);
0244 }
0245 
0246 static inline int sched_getattr(pid_t pid, struct sched_attr *attr,
0247                 unsigned int size, unsigned int flags)
0248 {
0249     return syscall(__NR_sched_getattr, pid, attr, size, flags);
0250 }
0251 
0252 int __set_sched_attr(int pid, struct sched_attr *attr)
0253 {
0254     int flags = 0;
0255     int retval;
0256 
0257     retval = sched_setattr(pid, attr, flags);
0258     if (retval < 0) {
0259         err_msg("Failed to set sched attributes to the pid %d: %s\n",
0260             pid, strerror(errno));
0261         return 1;
0262     }
0263 
0264     return 0;
0265 }
0266 
0267 /*
0268  * procfs_is_workload_pid - check if a procfs entry contains a comm_prefix* comm
0269  *
0270  * Check if the procfs entry is a directory of a process, and then check if the
0271  * process has a comm with the prefix set in char *comm_prefix. As the
0272  * current users of this function only check for kernel threads, there is no
0273  * need to check for the threads for the process.
0274  *
0275  * Return: True if the proc_entry contains a comm file with comm_prefix*.
0276  * Otherwise returns false.
0277  */
0278 static int procfs_is_workload_pid(const char *comm_prefix, struct dirent *proc_entry)
0279 {
0280     char buffer[MAX_PATH];
0281     int comm_fd, retval;
0282     char *t_name;
0283 
0284     if (proc_entry->d_type != DT_DIR)
0285         return 0;
0286 
0287     if (*proc_entry->d_name == '.')
0288         return 0;
0289 
0290     /* check if the string is a pid */
0291     for (t_name = proc_entry->d_name; t_name; t_name++) {
0292         if (!isdigit(*t_name))
0293             break;
0294     }
0295 
0296     if (*t_name != '\0')
0297         return 0;
0298 
0299     snprintf(buffer, MAX_PATH, "/proc/%s/comm", proc_entry->d_name);
0300     comm_fd = open(buffer, O_RDONLY);
0301     if (comm_fd < 0)
0302         return 0;
0303 
0304     memset(buffer, 0, MAX_PATH);
0305     retval = read(comm_fd, buffer, MAX_PATH);
0306 
0307     close(comm_fd);
0308 
0309     if (retval <= 0)
0310         return 0;
0311 
0312     retval = strncmp(comm_prefix, buffer, strlen(comm_prefix));
0313     if (retval)
0314         return 0;
0315 
0316     /* comm already have \n */
0317     debug_msg("Found workload pid:%s comm:%s", proc_entry->d_name, buffer);
0318 
0319     return 1;
0320 }
0321 
0322 /*
0323  * set_comm_sched_attr - set sched params to threads starting with char *comm_prefix
0324  *
0325  * This function uses procfs to list the currently running threads and then set the
0326  * sched_attr *attr to the threads that start with char *comm_prefix. It is
0327  * mainly used to set the priority to the kernel threads created by the
0328  * tracers.
0329  */
0330 int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr)
0331 {
0332     struct dirent *proc_entry;
0333     DIR *procfs;
0334     int retval;
0335 
0336     if (strlen(comm_prefix) >= MAX_PATH) {
0337         err_msg("Command prefix is too long: %d < strlen(%s)\n",
0338             MAX_PATH, comm_prefix);
0339         return 1;
0340     }
0341 
0342     procfs = opendir("/proc");
0343     if (!procfs) {
0344         err_msg("Could not open procfs\n");
0345         return 1;
0346     }
0347 
0348     while ((proc_entry = readdir(procfs))) {
0349 
0350         retval = procfs_is_workload_pid(comm_prefix, proc_entry);
0351         if (!retval)
0352             continue;
0353 
0354         /* procfs_is_workload_pid confirmed it is a pid */
0355         retval = __set_sched_attr(atoi(proc_entry->d_name), attr);
0356         if (retval) {
0357             err_msg("Error setting sched attributes for pid:%s\n", proc_entry->d_name);
0358             goto out_err;
0359         }
0360 
0361         debug_msg("Set sched attributes for pid:%s\n", proc_entry->d_name);
0362     }
0363     return 0;
0364 
0365 out_err:
0366     closedir(procfs);
0367     return 1;
0368 }
0369 
0370 #define INVALID_VAL (~0L)
0371 static long get_long_ns_after_colon(char *start)
0372 {
0373     long val = INVALID_VAL;
0374 
0375     /* find the ":" */
0376     start = strstr(start, ":");
0377     if (!start)
0378         return -1;
0379 
0380     /* skip ":" */
0381     start++;
0382     val = parse_ns_duration(start);
0383 
0384     return val;
0385 }
0386 
0387 static long get_long_after_colon(char *start)
0388 {
0389     long val = INVALID_VAL;
0390 
0391     /* find the ":" */
0392     start = strstr(start, ":");
0393     if (!start)
0394         return -1;
0395 
0396     /* skip ":" */
0397     start++;
0398     val = get_llong_from_str(start);
0399 
0400     return val;
0401 }
0402 
0403 /*
0404  * parse priority in the format:
0405  * SCHED_OTHER:
0406  *      o:<prio>
0407  *      O:<prio>
0408  * SCHED_RR:
0409  *      r:<prio>
0410  *      R:<prio>
0411  * SCHED_FIFO:
0412  *      f:<prio>
0413  *      F:<prio>
0414  * SCHED_DEADLINE:
0415  *      d:runtime:period
0416  *      D:runtime:period
0417  */
0418 int parse_prio(char *arg, struct sched_attr *sched_param)
0419 {
0420     long prio;
0421     long runtime;
0422     long period;
0423 
0424     memset(sched_param, 0, sizeof(*sched_param));
0425     sched_param->size = sizeof(*sched_param);
0426 
0427     switch (arg[0]) {
0428     case 'd':
0429     case 'D':
0430         /* d:runtime:period */
0431         if (strlen(arg) < 4)
0432             return -1;
0433 
0434         runtime = get_long_ns_after_colon(arg);
0435         if (runtime == INVALID_VAL)
0436             return -1;
0437 
0438         period = get_long_ns_after_colon(&arg[2]);
0439         if (period == INVALID_VAL)
0440             return -1;
0441 
0442         if (runtime > period)
0443             return -1;
0444 
0445         sched_param->sched_policy   = SCHED_DEADLINE;
0446         sched_param->sched_runtime  = runtime;
0447         sched_param->sched_deadline = period;
0448         sched_param->sched_period   = period;
0449         break;
0450     case 'f':
0451     case 'F':
0452         /* f:prio */
0453         prio = get_long_after_colon(arg);
0454         if (prio == INVALID_VAL)
0455             return -1;
0456 
0457         if (prio < sched_get_priority_min(SCHED_FIFO))
0458             return -1;
0459         if (prio > sched_get_priority_max(SCHED_FIFO))
0460             return -1;
0461 
0462         sched_param->sched_policy   = SCHED_FIFO;
0463         sched_param->sched_priority = prio;
0464         break;
0465     case 'r':
0466     case 'R':
0467         /* r:prio */
0468         prio = get_long_after_colon(arg);
0469         if (prio == INVALID_VAL)
0470             return -1;
0471 
0472         if (prio < sched_get_priority_min(SCHED_RR))
0473             return -1;
0474         if (prio > sched_get_priority_max(SCHED_RR))
0475             return -1;
0476 
0477         sched_param->sched_policy   = SCHED_RR;
0478         sched_param->sched_priority = prio;
0479         break;
0480     case 'o':
0481     case 'O':
0482         /* o:prio */
0483         prio = get_long_after_colon(arg);
0484         if (prio == INVALID_VAL)
0485             return -1;
0486 
0487         if (prio < sched_get_priority_min(SCHED_OTHER))
0488             return -1;
0489         if (prio > sched_get_priority_max(SCHED_OTHER))
0490             return -1;
0491 
0492         sched_param->sched_policy   = SCHED_OTHER;
0493         sched_param->sched_priority = prio;
0494         break;
0495     default:
0496         return -1;
0497     }
0498     return 0;
0499 }
0500 
0501 /*
0502  * set_cpu_dma_latency - set the /dev/cpu_dma_latecy
0503  *
0504  * This is used to reduce the exit from idle latency. The value
0505  * will be reset once the file descriptor of /dev/cpu_dma_latecy
0506  * is closed.
0507  *
0508  * Return: the /dev/cpu_dma_latecy file descriptor
0509  */
0510 int set_cpu_dma_latency(int32_t latency)
0511 {
0512     int retval;
0513     int fd;
0514 
0515     fd = open("/dev/cpu_dma_latency", O_RDWR);
0516     if (fd < 0) {
0517         err_msg("Error opening /dev/cpu_dma_latency\n");
0518         return -1;
0519     }
0520 
0521     retval = write(fd, &latency, 4);
0522     if (retval < 1) {
0523         err_msg("Error setting /dev/cpu_dma_latency\n");
0524         close(fd);
0525         return -1;
0526     }
0527 
0528     debug_msg("Set /dev/cpu_dma_latency to %d\n", latency);
0529 
0530     return fd;
0531 }