1069 lines · 22.0 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org> */ #define _GNU_SOURCE#ifdef HAVE_LIBCPUPOWER_SUPPORT#include <cpuidle.h>#endif /* HAVE_LIBCPUPOWER_SUPPORT */#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 <limits.h> #include "common.h" #define MAX_MSG_LENGTH	1024bool 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);} /* * fatal - print an error message and EOL to stderr and exit with ERROR */void fatal(const char *fmt, ...){	va_list ap; 	va_start(ap, fmt);	vfprintf(stderr, fmt, ap);	va_end(ap);	fprintf(stderr, "\n"); 	exit(ERROR);} /* * 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 0 on success, 1 otherwise. */int parse_cpu_set(char *cpu_list, cpu_set_t *set){	const char *p;	int end_cpu;	int cpu;	int i; 	CPU_ZERO(set); 	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_stack_format - parse the stack format * * Return: the stack format on success, -1 otherwise. */int parse_stack_format(char *arg){	if (!strcmp(arg, "truncate"))		return STACK_FORMAT_TRUNCATE;	if (!strcmp(arg, "skip"))		return STACK_FORMAT_SKIP;	if (!strcmp(arg, "full"))		return STACK_FORMAT_FULL; 	debug_msg("Error parsing the stack format %s\n", arg);	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;} /* * match_time_unit - check if str starts with unit followed by end-of-string or ':' * * This allows the time unit parser to work both in standalone duration strings * like "100ms" and in colon-delimited SCHED_DEADLINE specifications like * "d:10ms:100ms", while still rejecting malformed input like "100msx". */static bool match_time_unit(const char *str, const char *unit){	size_t len = strlen(unit); 	return strncmp(str, unit, len) == 0 &&	       (str[len] == '\0' || str[len] == ':');} /* * 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 (match_time_unit(end, "ns")) {			return t;		} else if (match_time_unit(end, "us")) {			t *= 1000;			return t;		} else if (match_time_unit(end, "ms")) {			t *= 1000 * 1000;			return t;		} else if (match_time_unit(end, "s")) {			t *= 1000 * 1000 * 1000;			return t;		}		return -1;	} 	return t;} /* * This is a set of helper functions to use SCHED_DEADLINE. */#ifndef __NR_sched_setattr# ifdef __x86_64__#  define __NR_sched_setattr	314# elif __i386__#  define __NR_sched_setattr	351# elif __arm__#  define __NR_sched_setattr	380# elif __aarch64__ || __riscv#  define __NR_sched_setattr	274# elif __powerpc__#  define __NR_sched_setattr	355# elif __s390x__#  define __NR_sched_setattr	345# elif __loongarch__#  define __NR_sched_setattr	274# endif#endif #define SCHED_DEADLINE		6 static inline int syscall_sched_setattr(pid_t pid, const struct sched_attr *attr,				unsigned int flags) {	return syscall(__NR_sched_setattr, pid, attr, flags);} int __set_sched_attr(int pid, struct sched_attr *attr){	int flags = 0;	int retval; 	retval = syscall_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; 	buffer[MAX_PATH-1] = '\0';	if (!str_has_prefix(buffer, comm_prefix))		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;	int pid; 	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; 		if (strtoi(proc_entry->d_name, &pid)) {			err_msg("'%s' is not a valid pid", proc_entry->d_name);			retval = 1;			goto out;		}		/* procfs_is_workload_pid confirmed it is a pid */		retval = __set_sched_attr(pid, attr);		if (retval) {			err_msg("Error setting sched attributes for pid:%s\n", proc_entry->d_name);			goto out;		} 		debug_msg("Set sched attributes for pid:%s\n", proc_entry->d_name);	} 	retval = 0;out:	closedir(procfs);	return retval;} #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;

Lines 1–500 of 1069.