1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
20 #define MAX_MSG_LENGTH 1024
24 * err_msg - print an error message to the stderr
26 void err_msg(const char *fmt, ...)
28 char message[MAX_MSG_LENGTH];
32 vsnprintf(message, sizeof(message), fmt, ap);
35 fprintf(stderr, "%s", message);
39 * debug_msg - print a debug message to stderr if debug is set
41 void debug_msg(const char *fmt, ...)
43 char message[MAX_MSG_LENGTH];
50 vsnprintf(message, sizeof(message), fmt, ap);
53 fprintf(stderr, "%s", message);
57 * get_llong_from_str - get a long long int from a string
59 long long get_llong_from_str(char *start)
65 value = strtoll(start, &end, 10);
66 if (errno || start == end)
73 * get_duration - fill output with a human readable duration since start_time
75 void get_duration(time_t start_time, char *output, int output_size)
77 time_t now = time(NULL);
81 duration = difftime(now, start_time);
82 tm_info = gmtime(&duration);
84 snprintf(output, output_size, "%3d %02d:%02d:%02d",
92 * parse_cpu_set - parse a cpu_list filling cpu_set_t argument
94 * Receives a cpu list, like 1-3,5 (cpus 1, 2, 3, 5), and then set
95 * filling cpu_set_t argument.
97 * Returns 1 on success, 0 otherwise.
99 int parse_cpu_set(char *cpu_list, cpu_set_t *set)
109 nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
111 for (p = cpu_list; *p; ) {
113 if (cpu < 0 || (!cpu && *p != '0') || cpu >= nr_cpus)
121 if (end_cpu < cpu || (!end_cpu && *p != '0') || end_cpu >= nr_cpus)
128 if (cpu == end_cpu) {
129 debug_msg("cpu_set: adding cpu %d\n", cpu);
132 for (i = cpu; i <= end_cpu; i++) {
133 debug_msg("cpu_set: adding cpu %d\n", i);
144 debug_msg("Error parsing the cpu set %s\n", cpu_list);
149 * parse_duration - parse duration with s/m/h/d suffix converting it to seconds
151 long parse_seconds_duration(char *val)
156 t = strtol(val, &end, 10);
183 * parse_ns_duration - parse duration with ns/us/ms/s converting it to nanoseconds
185 long parse_ns_duration(char *val)
190 t = strtol(val, &end, 10);
193 if (!strncmp(end, "ns", 2)) {
195 } else if (!strncmp(end, "us", 2)) {
198 } else if (!strncmp(end, "ms", 2)) {
201 } else if (!strncmp(end, "s", 1)) {
202 t *= 1000 * 1000 * 1000;
212 * This is a set of helper functions to use SCHED_DEADLINE.
215 # define __NR_sched_setattr 314
216 # define __NR_sched_getattr 315
218 # define __NR_sched_setattr 351
219 # define __NR_sched_getattr 352
221 # define __NR_sched_setattr 380
222 # define __NR_sched_getattr 381
223 #elif __aarch64__ || __riscv
224 # define __NR_sched_setattr 274
225 # define __NR_sched_getattr 275
227 # define __NR_sched_setattr 355
228 # define __NR_sched_getattr 356
230 # define __NR_sched_setattr 345
231 # define __NR_sched_getattr 346
234 #define SCHED_DEADLINE 6
236 static inline int sched_setattr(pid_t pid, const struct sched_attr *attr,
237 unsigned int flags) {
238 return syscall(__NR_sched_setattr, pid, attr, flags);
241 int __set_sched_attr(int pid, struct sched_attr *attr)
246 retval = sched_setattr(pid, attr, flags);
248 err_msg("Failed to set sched attributes to the pid %d: %s\n",
249 pid, strerror(errno));
257 * procfs_is_workload_pid - check if a procfs entry contains a comm_prefix* comm
259 * Check if the procfs entry is a directory of a process, and then check if the
260 * process has a comm with the prefix set in char *comm_prefix. As the
261 * current users of this function only check for kernel threads, there is no
262 * need to check for the threads for the process.
264 * Return: True if the proc_entry contains a comm file with comm_prefix*.
265 * Otherwise returns false.
267 static int procfs_is_workload_pid(const char *comm_prefix, struct dirent *proc_entry)
269 char buffer[MAX_PATH];
273 if (proc_entry->d_type != DT_DIR)
276 if (*proc_entry->d_name == '.')
279 /* check if the string is a pid */
280 for (t_name = proc_entry->d_name; t_name; t_name++) {
281 if (!isdigit(*t_name))
288 snprintf(buffer, MAX_PATH, "/proc/%s/comm", proc_entry->d_name);
289 comm_fd = open(buffer, O_RDONLY);
293 memset(buffer, 0, MAX_PATH);
294 retval = read(comm_fd, buffer, MAX_PATH);
301 retval = strncmp(comm_prefix, buffer, strlen(comm_prefix));
305 /* comm already have \n */
306 debug_msg("Found workload pid:%s comm:%s", proc_entry->d_name, buffer);
312 * set_comm_sched_attr - set sched params to threads starting with char *comm_prefix
314 * This function uses procfs to list the currently running threads and then set the
315 * sched_attr *attr to the threads that start with char *comm_prefix. It is
316 * mainly used to set the priority to the kernel threads created by the
319 int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr)
321 struct dirent *proc_entry;
325 if (strlen(comm_prefix) >= MAX_PATH) {
326 err_msg("Command prefix is too long: %d < strlen(%s)\n",
327 MAX_PATH, comm_prefix);
331 procfs = opendir("/proc");
333 err_msg("Could not open procfs\n");
337 while ((proc_entry = readdir(procfs))) {
339 retval = procfs_is_workload_pid(comm_prefix, proc_entry);
343 /* procfs_is_workload_pid confirmed it is a pid */
344 retval = __set_sched_attr(atoi(proc_entry->d_name), attr);
346 err_msg("Error setting sched attributes for pid:%s\n", proc_entry->d_name);
350 debug_msg("Set sched attributes for pid:%s\n", proc_entry->d_name);
359 #define INVALID_VAL (~0L)
360 static long get_long_ns_after_colon(char *start)
362 long val = INVALID_VAL;
365 start = strstr(start, ":");
371 val = parse_ns_duration(start);
376 static long get_long_after_colon(char *start)
378 long val = INVALID_VAL;
381 start = strstr(start, ":");
387 val = get_llong_from_str(start);
393 * parse priority in the format:
407 int parse_prio(char *arg, struct sched_attr *sched_param)
413 memset(sched_param, 0, sizeof(*sched_param));
414 sched_param->size = sizeof(*sched_param);
419 /* d:runtime:period */
423 runtime = get_long_ns_after_colon(arg);
424 if (runtime == INVALID_VAL)
427 period = get_long_ns_after_colon(&arg[2]);
428 if (period == INVALID_VAL)
431 if (runtime > period)
434 sched_param->sched_policy = SCHED_DEADLINE;
435 sched_param->sched_runtime = runtime;
436 sched_param->sched_deadline = period;
437 sched_param->sched_period = period;
442 prio = get_long_after_colon(arg);
443 if (prio == INVALID_VAL)
446 if (prio < sched_get_priority_min(SCHED_FIFO))
448 if (prio > sched_get_priority_max(SCHED_FIFO))
451 sched_param->sched_policy = SCHED_FIFO;
452 sched_param->sched_priority = prio;
457 prio = get_long_after_colon(arg);
458 if (prio == INVALID_VAL)
461 if (prio < sched_get_priority_min(SCHED_RR))
463 if (prio > sched_get_priority_max(SCHED_RR))
466 sched_param->sched_policy = SCHED_RR;
467 sched_param->sched_priority = prio;
472 prio = get_long_after_colon(arg);
473 if (prio == INVALID_VAL)
481 sched_param->sched_policy = SCHED_OTHER;
482 sched_param->sched_nice = prio;
491 * set_cpu_dma_latency - set the /dev/cpu_dma_latecy
493 * This is used to reduce the exit from idle latency. The value
494 * will be reset once the file descriptor of /dev/cpu_dma_latecy
497 * Return: the /dev/cpu_dma_latecy file descriptor
499 int set_cpu_dma_latency(int32_t latency)
504 fd = open("/dev/cpu_dma_latency", O_RDWR);
506 err_msg("Error opening /dev/cpu_dma_latency\n");
510 retval = write(fd, &latency, 4);
512 err_msg("Error setting /dev/cpu_dma_latency\n");
517 debug_msg("Set /dev/cpu_dma_latency to %d\n", latency);
523 #define STR(x) _STR(x)
526 * find_mount - find a the mount point of a given fs
528 * Returns 0 if mount is not found, otherwise return 1 and fill mp
529 * with the mount point.
531 static const int find_mount(const char *fs, char *mp, int sizeof_mp)
533 char mount_point[MAX_PATH+1];
538 fp = fopen("/proc/mounts", "r");
542 while (fscanf(fp, "%*s %" STR(MAX_PATH) "s %99s %*s %*d %*d\n", mount_point, type) == 2) {
543 if (strcmp(type, fs) == 0) {
553 memset(mp, 0, sizeof_mp);
554 strncpy(mp, mount_point, sizeof_mp - 1);
556 debug_msg("Fs %s found at %s\n", fs, mp);
561 * get_self_cgroup - get the current thread cgroup path
563 * Parse /proc/$$/cgroup file to get the thread's cgroup. As an example of line to parse:
565 * 0::/user.slice/user-0.slice/session-3.scope'\n'
567 * This function is interested in the content after the second : and before the '\n'.
569 * Returns 1 if a string was found, 0 otherwise.
571 static int get_self_cgroup(char *self_cg, int sizeof_self_cg)
573 char path[MAX_PATH], *start;
576 snprintf(path, MAX_PATH, "/proc/%d/cgroup", getpid());
578 fd = open(path, O_RDONLY);
582 retval = read(fd, path, MAX_PATH);
591 start = strstr(start, ":");
598 start = strstr(start, ":");
605 if (strlen(start) >= sizeof_self_cg)
608 snprintf(self_cg, sizeof_self_cg, "%s", start);
610 /* Swap '\n' with '\0' */
611 start = strstr(self_cg, "\n");
613 /* there must be '\n' */
617 /* ok, it found a string after the second : and before the \n */
624 * set_comm_cgroup - Set cgroup to pid_t pid
626 * If cgroup argument is not NULL, the threads will move to the given cgroup.
627 * Otherwise, the cgroup of the calling, i.e., rtla, thread will be used.
629 * Supports cgroup v2.
631 * Returns 1 on success, 0 otherwise.
633 int set_pid_cgroup(pid_t pid, const char *cgroup)
635 char cgroup_path[MAX_PATH - strlen("/cgroup.procs")];
636 char cgroup_procs[MAX_PATH];
641 retval = find_mount("cgroup2", cgroup_path, sizeof(cgroup_path));
643 err_msg("Did not find cgroupv2 mount point\n");
648 retval = get_self_cgroup(&cgroup_path[strlen(cgroup_path)],
649 sizeof(cgroup_path) - strlen(cgroup_path));
651 err_msg("Did not find self cgroup\n");
655 snprintf(&cgroup_path[strlen(cgroup_path)],
656 sizeof(cgroup_path) - strlen(cgroup_path), "%s/", cgroup);
659 snprintf(cgroup_procs, MAX_PATH, "%s/cgroup.procs", cgroup_path);
661 debug_msg("Using cgroup path at: %s\n", cgroup_procs);
663 cg_fd = open(cgroup_procs, O_RDWR);
667 snprintf(pid_str, sizeof(pid_str), "%d\n", pid);
669 retval = write(cg_fd, pid_str, strlen(pid_str));
671 err_msg("Error setting cgroup attributes for pid:%s - %s\n",
672 pid_str, strerror(errno));
674 debug_msg("Set cgroup attributes for pid:%s\n", pid_str);
678 return (retval >= 0);
682 * set_comm_cgroup - Set cgroup to threads starting with char *comm_prefix
684 * If cgroup argument is not NULL, the threads will move to the given cgroup.
685 * Otherwise, the cgroup of the calling, i.e., rtla, thread will be used.
687 * Supports cgroup v2.
689 * Returns 1 on success, 0 otherwise.
691 int set_comm_cgroup(const char *comm_prefix, const char *cgroup)
693 char cgroup_path[MAX_PATH - strlen("/cgroup.procs")];
694 char cgroup_procs[MAX_PATH];
695 struct dirent *proc_entry;
700 if (strlen(comm_prefix) >= MAX_PATH) {
701 err_msg("Command prefix is too long: %d < strlen(%s)\n",
702 MAX_PATH, comm_prefix);
706 retval = find_mount("cgroup2", cgroup_path, sizeof(cgroup_path));
708 err_msg("Did not find cgroupv2 mount point\n");
713 retval = get_self_cgroup(&cgroup_path[strlen(cgroup_path)],
714 sizeof(cgroup_path) - strlen(cgroup_path));
716 err_msg("Did not find self cgroup\n");
720 snprintf(&cgroup_path[strlen(cgroup_path)],
721 sizeof(cgroup_path) - strlen(cgroup_path), "%s/", cgroup);
724 snprintf(cgroup_procs, MAX_PATH, "%s/cgroup.procs", cgroup_path);
726 debug_msg("Using cgroup path at: %s\n", cgroup_procs);
728 cg_fd = open(cgroup_procs, O_RDWR);
732 procfs = opendir("/proc");
734 err_msg("Could not open procfs\n");
738 while ((proc_entry = readdir(procfs))) {
740 retval = procfs_is_workload_pid(comm_prefix, proc_entry);
744 retval = write(cg_fd, proc_entry->d_name, strlen(proc_entry->d_name));
746 err_msg("Error setting cgroup attributes for pid:%s - %s\n",
747 proc_entry->d_name, strerror(errno));
751 debug_msg("Set cgroup attributes for pid:%s\n", proc_entry->d_name);
766 * auto_house_keeping - Automatically move rtla out of measurement threads
768 * Try to move rtla away from the tracer, if possible.
770 * Returns 1 on success, 0 otherwise.
772 int auto_house_keeping(cpu_set_t *monitored_cpus)
774 cpu_set_t rtla_cpus, house_keeping_cpus;
777 /* first get the CPUs in which rtla can actually run. */
778 retval = sched_getaffinity(getpid(), sizeof(rtla_cpus), &rtla_cpus);
780 debug_msg("Could not get rtla affinity, rtla might run with the threads!\n");
784 /* then check if the existing setup is already good. */
785 CPU_AND(&house_keeping_cpus, &rtla_cpus, monitored_cpus);
786 if (!CPU_COUNT(&house_keeping_cpus)) {
787 debug_msg("rtla and the monitored CPUs do not share CPUs.");
788 debug_msg("Skipping auto house-keeping\n");
792 /* remove the intersection */
793 CPU_XOR(&house_keeping_cpus, &rtla_cpus, monitored_cpus);
795 /* get only those that rtla can run */
796 CPU_AND(&house_keeping_cpus, &house_keeping_cpus, &rtla_cpus);
798 /* is there any cpu left? */
799 if (!CPU_COUNT(&house_keeping_cpus)) {
800 debug_msg("Could not find any CPU for auto house-keeping\n");
804 retval = sched_setaffinity(getpid(), sizeof(house_keeping_cpus), &house_keeping_cpus);
806 debug_msg("Could not set affinity for auto house-keeping\n");
810 debug_msg("rtla automatically moved to an auto house-keeping cpu set\n");