959 lines · 20.2 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 "utils.h" #define MAX_MSG_LENGTH	1024int 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. */#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# 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; 	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 < MIN_NICE)			return -1;		if (prio > MAX_NICE)			return -1; 		sched_param->sched_policy   = SCHED_OTHER;		sched_param->sched_nice = 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;

Lines 1–500 of 959.