brintos

brintos / linux-shallow public Read only

0
0
Text · 16.5 KiB · 9ac71a6 Raw
814 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright (C) 2021 Red Hat Inc, Daniel Bristot de Oliveira <bristot@kernel.org>4 */5 6#define _GNU_SOURCE7#include <dirent.h>8#include <stdarg.h>9#include <stdlib.h>10#include <string.h>11#include <unistd.h>12#include <ctype.h>13#include <errno.h>14#include <fcntl.h>15#include <sched.h>16#include <stdio.h>17 18#include "utils.h"19 20#define MAX_MSG_LENGTH	102421int config_debug;22 23/*24 * err_msg - print an error message to the stderr25 */26void err_msg(const char *fmt, ...)27{28	char message[MAX_MSG_LENGTH];29	va_list ap;30 31	va_start(ap, fmt);32	vsnprintf(message, sizeof(message), fmt, ap);33	va_end(ap);34 35	fprintf(stderr, "%s", message);36}37 38/*39 * debug_msg - print a debug message to stderr if debug is set40 */41void debug_msg(const char *fmt, ...)42{43	char message[MAX_MSG_LENGTH];44	va_list ap;45 46	if (!config_debug)47		return;48 49	va_start(ap, fmt);50	vsnprintf(message, sizeof(message), fmt, ap);51	va_end(ap);52 53	fprintf(stderr, "%s", message);54}55 56/*57 * get_llong_from_str - get a long long int from a string58 */59long long get_llong_from_str(char *start)60{61	long long value;62	char *end;63 64	errno = 0;65	value = strtoll(start, &end, 10);66	if (errno || start == end)67		return -1;68 69	return value;70}71 72/*73 * get_duration - fill output with a human readable duration since start_time74 */75void get_duration(time_t start_time, char *output, int output_size)76{77	time_t now = time(NULL);78	struct tm *tm_info;79	time_t duration;80 81	duration = difftime(now, start_time);82	tm_info = gmtime(&duration);83 84	snprintf(output, output_size, "%3d %02d:%02d:%02d",85			tm_info->tm_yday,86			tm_info->tm_hour,87			tm_info->tm_min,88			tm_info->tm_sec);89}90 91/*92 * parse_cpu_set - parse a cpu_list filling cpu_set_t argument93 *94 * Receives a cpu list, like 1-3,5 (cpus 1, 2, 3, 5), and then set95 * filling cpu_set_t argument.96 *97 * Returns 1 on success, 0 otherwise.98 */99int parse_cpu_set(char *cpu_list, cpu_set_t *set)100{101	const char *p;102	int end_cpu;103	int nr_cpus;104	int cpu;105	int i;106 107	CPU_ZERO(set);108 109	nr_cpus = sysconf(_SC_NPROCESSORS_CONF);110 111	for (p = cpu_list; *p; ) {112		cpu = atoi(p);113		if (cpu < 0 || (!cpu && *p != '0') || cpu >= nr_cpus)114			goto err;115 116		while (isdigit(*p))117			p++;118		if (*p == '-') {119			p++;120			end_cpu = atoi(p);121			if (end_cpu < cpu || (!end_cpu && *p != '0') || end_cpu >= nr_cpus)122				goto err;123			while (isdigit(*p))124				p++;125		} else126			end_cpu = cpu;127 128		if (cpu == end_cpu) {129			debug_msg("cpu_set: adding cpu %d\n", cpu);130			CPU_SET(cpu, set);131		} else {132			for (i = cpu; i <= end_cpu; i++) {133				debug_msg("cpu_set: adding cpu %d\n", i);134				CPU_SET(i, set);135			}136		}137 138		if (*p == ',')139			p++;140	}141 142	return 0;143err:144	debug_msg("Error parsing the cpu set %s\n", cpu_list);145	return 1;146}147 148/*149 * parse_duration - parse duration with s/m/h/d suffix converting it to seconds150 */151long parse_seconds_duration(char *val)152{153	char *end;154	long t;155 156	t = strtol(val, &end, 10);157 158	if (end) {159		switch (*end) {160		case 's':161		case 'S':162			break;163		case 'm':164		case 'M':165			t *= 60;166			break;167		case 'h':168		case 'H':169			t *= 60 * 60;170			break;171 172		case 'd':173		case 'D':174			t *= 24 * 60 * 60;175			break;176		}177	}178 179	return t;180}181 182/*183 * parse_ns_duration - parse duration with ns/us/ms/s converting it to nanoseconds184 */185long parse_ns_duration(char *val)186{187	char *end;188	long t;189 190	t = strtol(val, &end, 10);191 192	if (end) {193		if (!strncmp(end, "ns", 2)) {194			return t;195		} else if (!strncmp(end, "us", 2)) {196			t *= 1000;197			return t;198		} else if (!strncmp(end, "ms", 2)) {199			t *= 1000 * 1000;200			return t;201		} else if (!strncmp(end, "s", 1)) {202			t *= 1000 * 1000 * 1000;203			return t;204		}205		return -1;206	}207 208	return t;209}210 211/*212 * This is a set of helper functions to use SCHED_DEADLINE.213 */214#ifdef __x86_64__215# define __NR_sched_setattr	314216# define __NR_sched_getattr	315217#elif __i386__218# define __NR_sched_setattr	351219# define __NR_sched_getattr	352220#elif __arm__221# define __NR_sched_setattr	380222# define __NR_sched_getattr	381223#elif __aarch64__ || __riscv224# define __NR_sched_setattr	274225# define __NR_sched_getattr	275226#elif __powerpc__227# define __NR_sched_setattr	355228# define __NR_sched_getattr	356229#elif __s390x__230# define __NR_sched_setattr	345231# define __NR_sched_getattr	346232#endif233 234#define SCHED_DEADLINE		6235 236static 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);239}240 241int __set_sched_attr(int pid, struct sched_attr *attr)242{243	int flags = 0;244	int retval;245 246	retval = sched_setattr(pid, attr, flags);247	if (retval < 0) {248		err_msg("Failed to set sched attributes to the pid %d: %s\n",249			pid, strerror(errno));250		return 1;251	}252 253	return 0;254}255 256/*257 * procfs_is_workload_pid - check if a procfs entry contains a comm_prefix* comm258 *259 * Check if the procfs entry is a directory of a process, and then check if the260 * process has a comm with the prefix set in char *comm_prefix. As the261 * current users of this function only check for kernel threads, there is no262 * need to check for the threads for the process.263 *264 * Return: True if the proc_entry contains a comm file with comm_prefix*.265 * Otherwise returns false.266 */267static int procfs_is_workload_pid(const char *comm_prefix, struct dirent *proc_entry)268{269	char buffer[MAX_PATH];270	int comm_fd, retval;271	char *t_name;272 273	if (proc_entry->d_type != DT_DIR)274		return 0;275 276	if (*proc_entry->d_name == '.')277		return 0;278 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))282			break;283	}284 285	if (*t_name != '\0')286		return 0;287 288	snprintf(buffer, MAX_PATH, "/proc/%s/comm", proc_entry->d_name);289	comm_fd = open(buffer, O_RDONLY);290	if (comm_fd < 0)291		return 0;292 293	memset(buffer, 0, MAX_PATH);294	retval = read(comm_fd, buffer, MAX_PATH);295 296	close(comm_fd);297 298	if (retval <= 0)299		return 0;300 301	retval = strncmp(comm_prefix, buffer, strlen(comm_prefix));302	if (retval)303		return 0;304 305	/* comm already have \n */306	debug_msg("Found workload pid:%s comm:%s", proc_entry->d_name, buffer);307 308	return 1;309}310 311/*312 * set_comm_sched_attr - set sched params to threads starting with char *comm_prefix313 *314 * This function uses procfs to list the currently running threads and then set the315 * sched_attr *attr to the threads that start with char *comm_prefix. It is316 * mainly used to set the priority to the kernel threads created by the317 * tracers.318 */319int set_comm_sched_attr(const char *comm_prefix, struct sched_attr *attr)320{321	struct dirent *proc_entry;322	DIR *procfs;323	int retval;324 325	if (strlen(comm_prefix) >= MAX_PATH) {326		err_msg("Command prefix is too long: %d < strlen(%s)\n",327			MAX_PATH, comm_prefix);328		return 1;329	}330 331	procfs = opendir("/proc");332	if (!procfs) {333		err_msg("Could not open procfs\n");334		return 1;335	}336 337	while ((proc_entry = readdir(procfs))) {338 339		retval = procfs_is_workload_pid(comm_prefix, proc_entry);340		if (!retval)341			continue;342 343		/* procfs_is_workload_pid confirmed it is a pid */344		retval = __set_sched_attr(atoi(proc_entry->d_name), attr);345		if (retval) {346			err_msg("Error setting sched attributes for pid:%s\n", proc_entry->d_name);347			goto out_err;348		}349 350		debug_msg("Set sched attributes for pid:%s\n", proc_entry->d_name);351	}352	return 0;353 354out_err:355	closedir(procfs);356	return 1;357}358 359#define INVALID_VAL	(~0L)360static long get_long_ns_after_colon(char *start)361{362	long val = INVALID_VAL;363 364	/* find the ":" */365	start = strstr(start, ":");366	if (!start)367		return -1;368 369	/* skip ":" */370	start++;371	val = parse_ns_duration(start);372 373	return val;374}375 376static long get_long_after_colon(char *start)377{378	long val = INVALID_VAL;379 380	/* find the ":" */381	start = strstr(start, ":");382	if (!start)383		return -1;384 385	/* skip ":" */386	start++;387	val = get_llong_from_str(start);388 389	return val;390}391 392/*393 * parse priority in the format:394 * SCHED_OTHER:395 *		o:<prio>396 *		O:<prio>397 * SCHED_RR:398 *		r:<prio>399 *		R:<prio>400 * SCHED_FIFO:401 *		f:<prio>402 *		F:<prio>403 * SCHED_DEADLINE:404 *		d:runtime:period405 *		D:runtime:period406 */407int parse_prio(char *arg, struct sched_attr *sched_param)408{409	long prio;410	long runtime;411	long period;412 413	memset(sched_param, 0, sizeof(*sched_param));414	sched_param->size = sizeof(*sched_param);415 416	switch (arg[0]) {417	case 'd':418	case 'D':419		/* d:runtime:period */420		if (strlen(arg) < 4)421			return -1;422 423		runtime = get_long_ns_after_colon(arg);424		if (runtime == INVALID_VAL)425			return -1;426 427		period = get_long_ns_after_colon(&arg[2]);428		if (period == INVALID_VAL)429			return -1;430 431		if (runtime > period)432			return -1;433 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;438		break;439	case 'f':440	case 'F':441		/* f:prio */442		prio = get_long_after_colon(arg);443		if (prio == INVALID_VAL)444			return -1;445 446		if (prio < sched_get_priority_min(SCHED_FIFO))447			return -1;448		if (prio > sched_get_priority_max(SCHED_FIFO))449			return -1;450 451		sched_param->sched_policy   = SCHED_FIFO;452		sched_param->sched_priority = prio;453		break;454	case 'r':455	case 'R':456		/* r:prio */457		prio = get_long_after_colon(arg);458		if (prio == INVALID_VAL)459			return -1;460 461		if (prio < sched_get_priority_min(SCHED_RR))462			return -1;463		if (prio > sched_get_priority_max(SCHED_RR))464			return -1;465 466		sched_param->sched_policy   = SCHED_RR;467		sched_param->sched_priority = prio;468		break;469	case 'o':470	case 'O':471		/* o:prio */472		prio = get_long_after_colon(arg);473		if (prio == INVALID_VAL)474			return -1;475 476		if (prio < MIN_NICE)477			return -1;478		if (prio > MAX_NICE)479			return -1;480 481		sched_param->sched_policy   = SCHED_OTHER;482		sched_param->sched_nice = prio;483		break;484	default:485		return -1;486	}487	return 0;488}489 490/*491 * set_cpu_dma_latency - set the /dev/cpu_dma_latecy492 *493 * This is used to reduce the exit from idle latency. The value494 * will be reset once the file descriptor of /dev/cpu_dma_latecy495 * is closed.496 *497 * Return: the /dev/cpu_dma_latecy file descriptor498 */499int set_cpu_dma_latency(int32_t latency)500{501	int retval;502	int fd;503 504	fd = open("/dev/cpu_dma_latency", O_RDWR);505	if (fd < 0) {506		err_msg("Error opening /dev/cpu_dma_latency\n");507		return -1;508	}509 510	retval = write(fd, &latency, 4);511	if (retval < 1) {512		err_msg("Error setting /dev/cpu_dma_latency\n");513		close(fd);514		return -1;515	}516 517	debug_msg("Set /dev/cpu_dma_latency to %d\n", latency);518 519	return fd;520}521 522#define _STR(x) #x523#define STR(x) _STR(x)524 525/*526 * find_mount - find a the mount point of a given fs527 *528 * Returns 0 if mount is not found, otherwise return 1 and fill mp529 * with the mount point.530 */531static const int find_mount(const char *fs, char *mp, int sizeof_mp)532{533	char mount_point[MAX_PATH+1];534	char type[100];535	int found = 0;536	FILE *fp;537 538	fp = fopen("/proc/mounts", "r");539	if (!fp)540		return 0;541 542	while (fscanf(fp, "%*s %" STR(MAX_PATH) "s %99s %*s %*d %*d\n",	mount_point, type) == 2) {543		if (strcmp(type, fs) == 0) {544			found = 1;545			break;546		}547	}548	fclose(fp);549 550	if (!found)551		return 0;552 553	memset(mp, 0, sizeof_mp);554	strncpy(mp, mount_point, sizeof_mp - 1);555 556	debug_msg("Fs %s found at %s\n", fs, mp);557	return 1;558}559 560/*561 * get_self_cgroup - get the current thread cgroup path562 *563 * Parse /proc/$$/cgroup file to get the thread's cgroup. As an example of line to parse:564 *565 * 0::/user.slice/user-0.slice/session-3.scope'\n'566 *567 * This function is interested in the content after the second : and before the '\n'.568 *569 * Returns 1 if a string was found, 0 otherwise.570 */571static int get_self_cgroup(char *self_cg, int sizeof_self_cg)572{573	char path[MAX_PATH], *start;574	int fd, retval;575 576	snprintf(path, MAX_PATH, "/proc/%d/cgroup", getpid());577 578	fd = open(path, O_RDONLY);579	if (fd < 0)580		return 0;581 582	retval = read(fd, path, MAX_PATH);583 584	close(fd);585 586	if (retval <= 0)587		return 0;588 589	start = path;590 591	start = strstr(start, ":");592	if (!start)593		return 0;594 595	/* skip ":" */596	start++;597 598	start = strstr(start, ":");599	if (!start)600		return 0;601 602	/* skip ":" */603	start++;604 605	if (strlen(start) >= sizeof_self_cg)606		return 0;607 608	snprintf(self_cg, sizeof_self_cg, "%s", start);609 610	/* Swap '\n' with '\0' */611	start = strstr(self_cg, "\n");612 613	/* there must be '\n' */614	if (!start)615		return 0;616 617	/* ok, it found a string after the second : and before the \n */618	*start = '\0';619 620	return 1;621}622 623/*624 * set_comm_cgroup - Set cgroup to pid_t pid625 *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.628 *629 * Supports cgroup v2.630 *631 * Returns 1 on success, 0 otherwise.632 */633int set_pid_cgroup(pid_t pid, const char *cgroup)634{635	char cgroup_path[MAX_PATH - strlen("/cgroup.procs")];636	char cgroup_procs[MAX_PATH];637	char pid_str[24];638	int retval;639	int cg_fd;640 641	retval = find_mount("cgroup2", cgroup_path, sizeof(cgroup_path));642	if (!retval) {643		err_msg("Did not find cgroupv2 mount point\n");644		return 0;645	}646 647	if (!cgroup) {648		retval = get_self_cgroup(&cgroup_path[strlen(cgroup_path)],649				sizeof(cgroup_path) - strlen(cgroup_path));650		if (!retval) {651			err_msg("Did not find self cgroup\n");652			return 0;653		}654	} else {655		snprintf(&cgroup_path[strlen(cgroup_path)],656				sizeof(cgroup_path) - strlen(cgroup_path), "%s/", cgroup);657	}658 659	snprintf(cgroup_procs, MAX_PATH, "%s/cgroup.procs", cgroup_path);660 661	debug_msg("Using cgroup path at: %s\n", cgroup_procs);662 663	cg_fd = open(cgroup_procs, O_RDWR);664	if (cg_fd < 0)665		return 0;666 667	snprintf(pid_str, sizeof(pid_str), "%d\n", pid);668 669	retval = write(cg_fd, pid_str, strlen(pid_str));670	if (retval < 0)671		err_msg("Error setting cgroup attributes for pid:%s - %s\n",672				pid_str, strerror(errno));673	else674		debug_msg("Set cgroup attributes for pid:%s\n", pid_str);675 676	close(cg_fd);677 678	return (retval >= 0);679}680 681/**682 * set_comm_cgroup - Set cgroup to threads starting with char *comm_prefix683 *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.686 *687 * Supports cgroup v2.688 *689 * Returns 1 on success, 0 otherwise.690 */691int set_comm_cgroup(const char *comm_prefix, const char *cgroup)692{693	char cgroup_path[MAX_PATH - strlen("/cgroup.procs")];694	char cgroup_procs[MAX_PATH];695	struct dirent *proc_entry;696	DIR *procfs;697	int retval;698	int cg_fd;699 700	if (strlen(comm_prefix) >= MAX_PATH) {701		err_msg("Command prefix is too long: %d < strlen(%s)\n",702			MAX_PATH, comm_prefix);703		return 0;704	}705 706	retval = find_mount("cgroup2", cgroup_path, sizeof(cgroup_path));707	if (!retval) {708		err_msg("Did not find cgroupv2 mount point\n");709		return 0;710	}711 712	if (!cgroup) {713		retval = get_self_cgroup(&cgroup_path[strlen(cgroup_path)],714				sizeof(cgroup_path) - strlen(cgroup_path));715		if (!retval) {716			err_msg("Did not find self cgroup\n");717			return 0;718		}719	} else {720		snprintf(&cgroup_path[strlen(cgroup_path)],721				sizeof(cgroup_path) - strlen(cgroup_path), "%s/", cgroup);722	}723 724	snprintf(cgroup_procs, MAX_PATH, "%s/cgroup.procs", cgroup_path);725 726	debug_msg("Using cgroup path at: %s\n", cgroup_procs);727 728	cg_fd = open(cgroup_procs, O_RDWR);729	if (cg_fd < 0)730		return 0;731 732	procfs = opendir("/proc");733	if (!procfs) {734		err_msg("Could not open procfs\n");735		goto out_cg;736	}737 738	while ((proc_entry = readdir(procfs))) {739 740		retval = procfs_is_workload_pid(comm_prefix, proc_entry);741		if (!retval)742			continue;743 744		retval = write(cg_fd, proc_entry->d_name, strlen(proc_entry->d_name));745		if (retval < 0) {746			err_msg("Error setting cgroup attributes for pid:%s - %s\n",747				proc_entry->d_name, strerror(errno));748			goto out_procfs;749		}750 751		debug_msg("Set cgroup attributes for pid:%s\n", proc_entry->d_name);752	}753 754	closedir(procfs);755	close(cg_fd);756	return 1;757 758out_procfs:759	closedir(procfs);760out_cg:761	close(cg_fd);762	return 0;763}764 765/**766 * auto_house_keeping - Automatically move rtla out of measurement threads767 *768 * Try to move rtla away from the tracer, if possible.769 *770 * Returns 1 on success, 0 otherwise.771 */772int auto_house_keeping(cpu_set_t *monitored_cpus)773{774	cpu_set_t rtla_cpus, house_keeping_cpus;775	int retval;776 777	/* first get the CPUs in which rtla can actually run. */778	retval = sched_getaffinity(getpid(), sizeof(rtla_cpus), &rtla_cpus);779	if (retval == -1) {780		debug_msg("Could not get rtla affinity, rtla might run with the threads!\n");781		return 0;782	}783 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");789		return 1;790	}791 792	/* remove the intersection */793	CPU_XOR(&house_keeping_cpus, &rtla_cpus, monitored_cpus);794 795	/* get only those that rtla can run */796	CPU_AND(&house_keeping_cpus, &house_keeping_cpus, &rtla_cpus);797 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");801		return 0;802	}803 804	retval = sched_setaffinity(getpid(), sizeof(house_keeping_cpus), &house_keeping_cpus);805	if (retval == -1) {806		debug_msg("Could not set affinity for auto house-keeping\n");807		return 0;808	}809 810	debug_msg("rtla automatically moved to an auto house-keeping cpu set\n");811 812	return 1;813}814