Files
linux/tools/tracing/rtla/src/utils.c
Colin Ian King 696444a544 rtla: Fix uninitialized variable found
Variable found is not being initialized, in the case where the desired
mount is not found the variable contains garbage. Fix this by initializing
it to zero.

Link: https://lore.kernel.org/all/20230727150117.627730-1-colin.i.king@gmail.com/

Fixes: a957cbc025 ("rtla: Add -C cgroup support")
Signed-off-by: Colin Ian King <colin.i.king@gmail.com>
Signed-off-by: Daniel Bristot de Oliveira <bristot@kernel.org>
2023-10-30 19:00:12 +01:00

820 lines
17 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>
*/
#define _GNU_SOURCE
#include <dirent.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include "utils.h"
#define MAX_MSG_LENGTH 1024
int config_debug;
/*
* err_msg - print an error message to the stderr
*/
void err_msg(const char *fmt, ...)
{
char message[MAX_MSG_LENGTH];
va_list ap;
va_start(ap, fmt);
vsnprintf(message, sizeof(message), fmt, ap);
va_end(ap);
fprintf(stderr, "%s", message);
}
/*
* debug_msg - print a debug message to stderr if debug is set
*/
void debug_msg(const char *fmt, ...)
{
char message[MAX_MSG_LENGTH];
va_list ap;
if (!config_debug)
return;
va_start(ap, fmt);
vsnprintf(message, sizeof(message), fmt, ap);
va_end(ap);
fprintf(stderr, "%s", message);
}
/*
* get_llong_from_str - get a long long int from a string
*/
long long get_llong_from_str(char *start)
{
long long value;
char *end;
errno = 0;
value = strtoll(start, &end, 10);
if (errno || start == end)
return -1;
return value;
}
/*
* get_duration - fill output with a human readable duration since start_time
*/
void get_duration(time_t start_time, char *output, int output_size)
{
time_t now = time(NULL);
struct tm *tm_info;
time_t duration;
duration = difftime(now, start_time);
tm_info = gmtime(&duration);
snprintf(output, output_size, "%3d %02d:%02d:%02d",
tm_info->tm_yday,
tm_info->tm_hour,
tm_info->tm_min,
tm_info->tm_sec);
}
/*
* parse_cpu_set - parse a cpu_list filling cpu_set_t argument
*
* Receives a cpu list, like 1-3,5 (cpus 1, 2, 3, 5), and then set
* filling cpu_set_t argument.
*
* Returns 1 on success, 0 otherwise.
*/
int parse_cpu_set(char *cpu_list, cpu_set_t *set)
{
const char *p;
int end_cpu;
int nr_cpus;
int cpu;
int i;
CPU_ZERO(set);
nr_cpus = sysconf(_SC_NPROCESSORS_CONF);
for (p = cpu_list; *p; ) {
cpu = atoi(p);
if (cpu < 0 || (!cpu && *p != '0') || cpu >= nr_cpus)
goto err;
while (isdigit(*p))
p++;
if (*p == '-') {
p++;
end_cpu = atoi(p);
if (end_cpu < cpu || (!end_cpu && *p != '0') || end_cpu >= nr_cpus)
goto err;
while (isdigit(*p))
p++;
} else
end_cpu = cpu;
if (cpu == end_cpu) {
debug_msg("cpu_set: adding cpu %d\n", cpu);
CPU_SET(cpu, set);
} else {
for (i = cpu; i <= end_cpu; i++) {
debug_msg("cpu_set: adding cpu %d\n", i);
CPU_SET(i, set);
}
}
if (*p == ',')
p++;
}
return 0;
err:
debug_msg("Error parsing the cpu set %s\n", cpu_list);
return 1;
}
/*
* parse_duration - parse duration with s/m/h/d suffix converting it to seconds
*/
long parse_seconds_duration(char *val)
{
char *end;
long t;
t = strtol(val, &end, 10);
if (end) {
switch (*end) {
case 's':
case 'S':
break;
case 'm':
case 'M':
t *= 60;
break;
case 'h':
case 'H':
t *= 60 * 60;
break;
case 'd':
case 'D':
t *= 24 * 60 * 60;
break;
}
}
return t;
}
/*
* parse_ns_duration - parse duration with ns/us/ms/s converting it to nanoseconds
*/
long parse_ns_duration(char *val)
{
char *end;
long t;
t = strtol(val, &end, 10);
if (end) {
if (!strncmp(end, "ns", 2)) {
return t;
} else if (!strncmp(end, "us", 2)) {
t *= 1000;
return t;
} else if (!strncmp(end, "ms", 2)) {
t *= 1000 * 1000;
return t;
} else if (!strncmp(end, "s", 1)) {
t *= 1000 * 1000 * 1000;
return t;
}
return -1;
}
return t;
}
/*
* This is a set of helper functions to use SCHED_DEADLINE.
*/
#ifdef __x86_64__
# define __NR_sched_setattr 314
# define __NR_sched_getattr 315
#elif __i386__
# define __NR_sched_setattr 351
# define __NR_sched_getattr 352
#elif __arm__
# define __NR_sched_setattr 380
# define __NR_sched_getattr 381
#elif __aarch64__ || __riscv
# define __NR_sched_setattr 274
# define __NR_sched_getattr 275
#elif __powerpc__
# define __NR_sched_setattr 355
# define __NR_sched_getattr 356
#elif __s390x__
# define __NR_sched_setattr 345
# define __NR_sched_getattr 346
#endif
#define SCHED_DEADLINE 6
static inline int sched_setattr(pid_t pid, const struct sched_attr *attr,
unsigned int flags) {
return syscall(__NR_sched_setattr, pid, attr, flags);
}
static inline int sched_getattr(pid_t pid, struct sched_attr *attr,
unsigned int size, unsigned int flags)
{
return syscall(__NR_sched_getattr, pid, attr, size, flags);
}
int __set_sched_attr(int pid, struct sched_attr *attr)
{
int flags = 0;
int retval;
retval = sched_setattr(pid, attr, flags);
if (retval < 0) {
err_msg("Failed to set sched attributes to the pid %d: %s\n",
pid, strerror(errno));
return 1;
}
return 0;
}
/*
* procfs_is_workload_pid - check if a procfs entry contains a comm_prefix* comm
*
* Check if the procfs entry is a directory of a process, and then check if the
* process has a comm with the prefix set in char *comm_prefix. As the
* current users of this function only check for kernel threads, there is no
* need to check for the threads for the process.
*
* Return: True if the proc_entry contains a comm file with comm_prefix*.
* Otherwise returns false.
*/
static int procfs_is_workload_pid(const char *comm_prefix, struct dirent *proc_entry)
{
char buffer[MAX_PATH];
int comm_fd, retval;
char *t_name;
if (proc_entry->d_type != DT_DIR)
return 0;
if (*proc_entry->d_name == '.')
return 0;
/* check if the string is a pid */
for (t_name = proc_entry->d_name; t_name; t_name++) {
if (!isdigit(*t_name))
break;
}
if (*t_name != '\0')
return 0;
snprintf(buffer, MAX_PATH, "/proc/%s/comm", proc_entry->d_name);
comm_fd = open(buffer, O_RDONLY);
if (comm_fd < 0)
return 0;
memset(buffer, 0, MAX_PATH);
retval = read(comm_fd, buffer, MAX_PATH);
close(comm_fd);
if (retval <= 0)
return 0;
retval = strncmp(comm_prefix, buffer, strlen(comm_prefix));
if (retval)
return 0;
/* comm already have \n */
debug_msg("Found workload pid:%s comm:%s", proc_entry->d_name, buffer);
return 1;
}
/*
* set_comm_sched_attr - set sched params to threads starting with char *comm_prefix
*
* This function uses procfs to list the currently running threads and then set the
* sched_attr *attr to the threads that start with char *comm_prefix. It is
* mainly used to set the priority to the kernel threads created by the
* tracers.
*/
int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr)
{
struct dirent *proc_entry;
DIR *procfs;
int retval;
if (strlen(comm_prefix) >= MAX_PATH) {
err_msg("Command prefix is too long: %d < strlen(%s)\n",
MAX_PATH, comm_prefix);
return 1;
}
procfs = opendir("/proc");
if (!procfs) {
err_msg("Could not open procfs\n");
return 1;
}
while ((proc_entry = readdir(procfs))) {
retval = procfs_is_workload_pid(comm_prefix, proc_entry);
if (!retval)
continue;
/* procfs_is_workload_pid confirmed it is a pid */
retval = __set_sched_attr(atoi(proc_entry->d_name), attr);
if (retval) {
err_msg("Error setting sched attributes for pid:%s\n", proc_entry->d_name);
goto out_err;
}
debug_msg("Set sched attributes for pid:%s\n", proc_entry->d_name);
}
return 0;
out_err:
closedir(procfs);
return 1;
}
#define INVALID_VAL (~0L)
static long get_long_ns_after_colon(char *start)
{
long val = INVALID_VAL;
/* find the ":" */
start = strstr(start, ":");
if (!start)
return -1;
/* skip ":" */
start++;
val = parse_ns_duration(start);
return val;
}
static long get_long_after_colon(char *start)
{
long val = INVALID_VAL;
/* find the ":" */
start = strstr(start, ":");
if (!start)
return -1;
/* skip ":" */
start++;
val = get_llong_from_str(start);
return val;
}
/*
* parse priority in the format:
* SCHED_OTHER:
* o:<prio>
* O:<prio>
* SCHED_RR:
* r:<prio>
* R:<prio>
* SCHED_FIFO:
* f:<prio>
* F:<prio>
* SCHED_DEADLINE:
* d:runtime:period
* D:runtime:period
*/
int parse_prio(char *arg, struct sched_attr *sched_param)
{
long prio;
long runtime;
long period;
memset(sched_param, 0, sizeof(*sched_param));
sched_param->size = sizeof(*sched_param);
switch (arg[0]) {
case 'd':
case 'D':
/* d:runtime:period */
if (strlen(arg) < 4)
return -1;
runtime = get_long_ns_after_colon(arg);
if (runtime == INVALID_VAL)
return -1;
period = get_long_ns_after_colon(&arg[2]);
if (period == INVALID_VAL)
return -1;
if (runtime > period)
return -1;
sched_param->sched_policy = SCHED_DEADLINE;
sched_param->sched_runtime = runtime;
sched_param->sched_deadline = period;
sched_param->sched_period = period;
break;
case 'f':
case 'F':
/* f:prio */
prio = get_long_after_colon(arg);
if (prio == INVALID_VAL)
return -1;
if (prio < sched_get_priority_min(SCHED_FIFO))
return -1;
if (prio > sched_get_priority_max(SCHED_FIFO))
return -1;
sched_param->sched_policy = SCHED_FIFO;
sched_param->sched_priority = prio;
break;
case 'r':
case 'R':
/* r:prio */
prio = get_long_after_colon(arg);
if (prio == INVALID_VAL)
return -1;
if (prio < sched_get_priority_min(SCHED_RR))
return -1;
if (prio > sched_get_priority_max(SCHED_RR))
return -1;
sched_param->sched_policy = SCHED_RR;
sched_param->sched_priority = prio;
break;
case 'o':
case 'O':
/* o:prio */
prio = get_long_after_colon(arg);
if (prio == INVALID_VAL)
return -1;
if (prio < sched_get_priority_min(SCHED_OTHER))
return -1;
if (prio > sched_get_priority_max(SCHED_OTHER))
return -1;
sched_param->sched_policy = SCHED_OTHER;
sched_param->sched_priority = prio;
break;
default:
return -1;
}
return 0;
}
/*
* set_cpu_dma_latency - set the /dev/cpu_dma_latecy
*
* This is used to reduce the exit from idle latency. The value
* will be reset once the file descriptor of /dev/cpu_dma_latecy
* is closed.
*
* Return: the /dev/cpu_dma_latecy file descriptor
*/
int set_cpu_dma_latency(int32_t latency)
{
int retval;
int fd;
fd = open("/dev/cpu_dma_latency", O_RDWR);
if (fd < 0) {
err_msg("Error opening /dev/cpu_dma_latency\n");
return -1;
}
retval = write(fd, &latency, 4);
if (retval < 1) {
err_msg("Error setting /dev/cpu_dma_latency\n");
close(fd);
return -1;
}
debug_msg("Set /dev/cpu_dma_latency to %d\n", latency);
return fd;
}
#define _STR(x) #x
#define STR(x) _STR(x)
/*
* find_mount - find a the mount point of a given fs
*
* Returns 0 if mount is not found, otherwise return 1 and fill mp
* with the mount point.
*/
static const int find_mount(const char *fs, char *mp, int sizeof_mp)
{
char mount_point[MAX_PATH];
char type[100];
int found = 0;
FILE *fp;
fp = fopen("/proc/mounts", "r");
if (!fp)
return 0;
while (fscanf(fp, "%*s %" STR(MAX_PATH) "s %99s %*s %*d %*d\n", mount_point, type) == 2) {
if (strcmp(type, fs) == 0) {
found = 1;
break;
}
}
fclose(fp);
if (!found)
return 0;
memset(mp, 0, sizeof_mp);
strncpy(mp, mount_point, sizeof_mp - 1);
debug_msg("Fs %s found at %s\n", fs, mp);
return 1;
}
/*
* get_self_cgroup - get the current thread cgroup path
*
* Parse /proc/$$/cgroup file to get the thread's cgroup. As an example of line to parse:
*
* 0::/user.slice/user-0.slice/session-3.scope'\n'
*
* This function is interested in the content after the second : and before the '\n'.
*
* Returns 1 if a string was found, 0 otherwise.
*/
static int get_self_cgroup(char *self_cg, int sizeof_self_cg)
{
char path[MAX_PATH], *start;
int fd, retval;
snprintf(path, MAX_PATH, "/proc/%d/cgroup", getpid());
fd = open(path, O_RDONLY);
if (fd < 0)
return 0;
retval = read(fd, path, MAX_PATH);
close(fd);
if (retval <= 0)
return 0;
start = path;
start = strstr(start, ":");
if (!start)
return 0;
/* skip ":" */
start++;
start = strstr(start, ":");
if (!start)
return 0;
/* skip ":" */
start++;
if (strlen(start) >= sizeof_self_cg)
return 0;
snprintf(self_cg, sizeof_self_cg, "%s", start);
/* Swap '\n' with '\0' */
start = strstr(self_cg, "\n");
/* there must be '\n' */
if (!start)
return 0;
/* ok, it found a string after the second : and before the \n */
*start = '\0';
return 1;
}
/*
* set_comm_cgroup - Set cgroup to pid_t pid
*
* If cgroup argument is not NULL, the threads will move to the given cgroup.
* Otherwise, the cgroup of the calling, i.e., rtla, thread will be used.
*
* Supports cgroup v2.
*
* Returns 1 on success, 0 otherwise.
*/
int set_pid_cgroup(pid_t pid, const char *cgroup)
{
char cgroup_path[MAX_PATH - strlen("/cgroup.procs")];
char cgroup_procs[MAX_PATH];
char pid_str[24];
int retval;
int cg_fd;
retval = find_mount("cgroup2", cgroup_path, sizeof(cgroup_path));
if (!retval) {
err_msg("Did not find cgroupv2 mount point\n");
return 0;
}
if (!cgroup) {
retval = get_self_cgroup(&cgroup_path[strlen(cgroup_path)],
sizeof(cgroup_path) - strlen(cgroup_path));
if (!retval) {
err_msg("Did not find self cgroup\n");
return 0;
}
} else {
snprintf(&cgroup_path[strlen(cgroup_path)],
sizeof(cgroup_path) - strlen(cgroup_path), "%s/", cgroup);
}
snprintf(cgroup_procs, MAX_PATH, "%s/cgroup.procs", cgroup_path);
debug_msg("Using cgroup path at: %s\n", cgroup_procs);
cg_fd = open(cgroup_procs, O_RDWR);
if (cg_fd < 0)
return 0;
snprintf(pid_str, sizeof(pid_str), "%d\n", pid);
retval = write(cg_fd, pid_str, strlen(pid_str));
if (retval < 0)
err_msg("Error setting cgroup attributes for pid:%s - %s\n",
pid_str, strerror(errno));
else
debug_msg("Set cgroup attributes for pid:%s\n", pid_str);
close(cg_fd);
return (retval >= 0);
}
/**
* set_comm_cgroup - Set cgroup to threads starting with char *comm_prefix
*
* If cgroup argument is not NULL, the threads will move to the given cgroup.
* Otherwise, the cgroup of the calling, i.e., rtla, thread will be used.
*
* Supports cgroup v2.
*
* Returns 1 on success, 0 otherwise.
*/
int set_comm_cgroup(const char *comm_prefix, const char *cgroup)
{
char cgroup_path[MAX_PATH - strlen("/cgroup.procs")];
char cgroup_procs[MAX_PATH];
struct dirent *proc_entry;
DIR *procfs;
int retval;
int cg_fd;
if (strlen(comm_prefix) >= MAX_PATH) {
err_msg("Command prefix is too long: %d < strlen(%s)\n",
MAX_PATH, comm_prefix);
return 0;
}
retval = find_mount("cgroup2", cgroup_path, sizeof(cgroup_path));
if (!retval) {
err_msg("Did not find cgroupv2 mount point\n");
return 0;
}
if (!cgroup) {
retval = get_self_cgroup(&cgroup_path[strlen(cgroup_path)],
sizeof(cgroup_path) - strlen(cgroup_path));
if (!retval) {
err_msg("Did not find self cgroup\n");
return 0;
}
} else {
snprintf(&cgroup_path[strlen(cgroup_path)],
sizeof(cgroup_path) - strlen(cgroup_path), "%s/", cgroup);
}
snprintf(cgroup_procs, MAX_PATH, "%s/cgroup.procs", cgroup_path);
debug_msg("Using cgroup path at: %s\n", cgroup_procs);
cg_fd = open(cgroup_procs, O_RDWR);
if (cg_fd < 0)
return 0;
procfs = opendir("/proc");
if (!procfs) {
err_msg("Could not open procfs\n");
goto out_cg;
}
while ((proc_entry = readdir(procfs))) {
retval = procfs_is_workload_pid(comm_prefix, proc_entry);
if (!retval)
continue;
retval = write(cg_fd, proc_entry->d_name, strlen(proc_entry->d_name));
if (retval < 0) {
err_msg("Error setting cgroup attributes for pid:%s - %s\n",
proc_entry->d_name, strerror(errno));
goto out_procfs;
}
debug_msg("Set cgroup attributes for pid:%s\n", proc_entry->d_name);
}
closedir(procfs);
close(cg_fd);
return 1;
out_procfs:
closedir(procfs);
out_cg:
close(cg_fd);
return 0;
}
/**
* auto_house_keeping - Automatically move rtla out of measurement threads
*
* Try to move rtla away from the tracer, if possible.
*
* Returns 1 on success, 0 otherwise.
*/
int auto_house_keeping(cpu_set_t *monitored_cpus)
{
cpu_set_t rtla_cpus, house_keeping_cpus;
int retval;
/* first get the CPUs in which rtla can actually run. */
retval = sched_getaffinity(getpid(), sizeof(rtla_cpus), &rtla_cpus);
if (retval == -1) {
debug_msg("Could not get rtla affinity, rtla might run with the threads!\n");
return 0;
}
/* then check if the existing setup is already good. */
CPU_AND(&house_keeping_cpus, &rtla_cpus, monitored_cpus);
if (!CPU_COUNT(&house_keeping_cpus)) {
debug_msg("rtla and the monitored CPUs do not share CPUs.");
debug_msg("Skipping auto house-keeping\n");
return 1;
}
/* remove the intersection */
CPU_XOR(&house_keeping_cpus, &rtla_cpus, monitored_cpus);
/* get only those that rtla can run */
CPU_AND(&house_keeping_cpus, &house_keeping_cpus, &rtla_cpus);
/* is there any cpu left? */
if (!CPU_COUNT(&house_keeping_cpus)) {
debug_msg("Could not find any CPU for auto house-keeping\n");
return 0;
}
retval = sched_setaffinity(getpid(), sizeof(house_keeping_cpus), &house_keeping_cpus);
if (retval == -1) {
debug_msg("Could not set affinity for auto house-keeping\n");
return 0;
}
debug_msg("rtla automatically moved to an auto house-keeping cpu set\n");
return 1;
}