diff options
Diffstat (limited to 'tools/testing/selftests/resctrl/resctrlfs.c')
| -rw-r--r-- | tools/testing/selftests/resctrl/resctrlfs.c | 722 | 
1 files changed, 722 insertions, 0 deletions
diff --git a/tools/testing/selftests/resctrl/resctrlfs.c b/tools/testing/selftests/resctrl/resctrlfs.c new file mode 100644 index 000000000000..19c0ec4045a4 --- /dev/null +++ b/tools/testing/selftests/resctrl/resctrlfs.c @@ -0,0 +1,722 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Basic resctrl file system operations + * + * Copyright (C) 2018 Intel Corporation + * + * Authors: + *    Sai Praneeth Prakhya <[email protected]>, + *    Fenghua Yu <[email protected]> + */ +#include "resctrl.h" + +int tests_run; + +static int find_resctrl_mount(char *buffer) +{ +	FILE *mounts; +	char line[256], *fs, *mntpoint; + +	mounts = fopen("/proc/mounts", "r"); +	if (!mounts) { +		perror("/proc/mounts"); +		return -ENXIO; +	} +	while (!feof(mounts)) { +		if (!fgets(line, 256, mounts)) +			break; +		fs = strtok(line, " \t"); +		if (!fs) +			continue; +		mntpoint = strtok(NULL, " \t"); +		if (!mntpoint) +			continue; +		fs = strtok(NULL, " \t"); +		if (!fs) +			continue; +		if (strcmp(fs, "resctrl")) +			continue; + +		fclose(mounts); +		if (buffer) +			strncpy(buffer, mntpoint, 256); + +		return 0; +	} + +	fclose(mounts); + +	return -ENOENT; +} + +char cbm_mask[256]; + +/* + * remount_resctrlfs - Remount resctrl FS at /sys/fs/resctrl + * @mum_resctrlfs:	Should the resctrl FS be remounted? + * + * If not mounted, mount it. + * If mounted and mum_resctrlfs then remount resctrl FS. + * If mounted and !mum_resctrlfs then noop + * + * Return: 0 on success, non-zero on failure + */ +int remount_resctrlfs(bool mum_resctrlfs) +{ +	char mountpoint[256]; +	int ret; + +	ret = find_resctrl_mount(mountpoint); +	if (ret) +		strcpy(mountpoint, RESCTRL_PATH); + +	if (!ret && mum_resctrlfs && umount(mountpoint)) { +		printf("not ok unmounting \"%s\"\n", mountpoint); +		perror("# umount"); +		tests_run++; +	} + +	if (!ret && !mum_resctrlfs) +		return 0; + +	ret = mount("resctrl", RESCTRL_PATH, "resctrl", 0, NULL); +	printf("%sok mounting resctrl to \"%s\"\n", ret ? "not " : "", +	       RESCTRL_PATH); +	if (ret) +		perror("# mount"); + +	tests_run++; + +	return ret; +} + +int umount_resctrlfs(void) +{ +	if (umount(RESCTRL_PATH)) { +		perror("# Unable to umount resctrl"); + +		return errno; +	} + +	return 0; +} + +/* + * get_resource_id - Get socket number/l3 id for a specified CPU + * @cpu_no:	CPU number + * @resource_id: Socket number or l3_id + * + * Return: >= 0 on success, < 0 on failure. + */ +int get_resource_id(int cpu_no, int *resource_id) +{ +	char phys_pkg_path[1024]; +	FILE *fp; + +	if (is_amd) +		sprintf(phys_pkg_path, "%s%d/cache/index3/id", +			PHYS_ID_PATH, cpu_no); +	else +		sprintf(phys_pkg_path, "%s%d/topology/physical_package_id", +			PHYS_ID_PATH, cpu_no); + +	fp = fopen(phys_pkg_path, "r"); +	if (!fp) { +		perror("Failed to open physical_package_id"); + +		return -1; +	} +	if (fscanf(fp, "%d", resource_id) <= 0) { +		perror("Could not get socket number or l3 id"); +		fclose(fp); + +		return -1; +	} +	fclose(fp); + +	return 0; +} + +/* + * get_cache_size - Get cache size for a specified CPU + * @cpu_no:	CPU number + * @cache_type:	Cache level L2/L3 + * @cache_size:	pointer to cache_size + * + * Return: = 0 on success, < 0 on failure. + */ +int get_cache_size(int cpu_no, char *cache_type, unsigned long *cache_size) +{ +	char cache_path[1024], cache_str[64]; +	int length, i, cache_num; +	FILE *fp; + +	if (!strcmp(cache_type, "L3")) { +		cache_num = 3; +	} else if (!strcmp(cache_type, "L2")) { +		cache_num = 2; +	} else { +		perror("Invalid cache level"); +		return -1; +	} + +	sprintf(cache_path, "/sys/bus/cpu/devices/cpu%d/cache/index%d/size", +		cpu_no, cache_num); +	fp = fopen(cache_path, "r"); +	if (!fp) { +		perror("Failed to open cache size"); + +		return -1; +	} +	if (fscanf(fp, "%s", cache_str) <= 0) { +		perror("Could not get cache_size"); +		fclose(fp); + +		return -1; +	} +	fclose(fp); + +	length = (int)strlen(cache_str); + +	*cache_size = 0; + +	for (i = 0; i < length; i++) { +		if ((cache_str[i] >= '0') && (cache_str[i] <= '9')) + +			*cache_size = *cache_size * 10 + (cache_str[i] - '0'); + +		else if (cache_str[i] == 'K') + +			*cache_size = *cache_size * 1024; + +		else if (cache_str[i] == 'M') + +			*cache_size = *cache_size * 1024 * 1024; + +		else +			break; +	} + +	return 0; +} + +#define CORE_SIBLINGS_PATH	"/sys/bus/cpu/devices/cpu" + +/* + * get_cbm_mask - Get cbm mask for given cache + * @cache_type:	Cache level L2/L3 + * + * Mask is stored in cbm_mask which is global variable. + * + * Return: = 0 on success, < 0 on failure. + */ +int get_cbm_mask(char *cache_type) +{ +	char cbm_mask_path[1024]; +	FILE *fp; + +	sprintf(cbm_mask_path, "%s/%s/cbm_mask", CBM_MASK_PATH, cache_type); + +	fp = fopen(cbm_mask_path, "r"); +	if (!fp) { +		perror("Failed to open cache level"); + +		return -1; +	} +	if (fscanf(fp, "%s", cbm_mask) <= 0) { +		perror("Could not get max cbm_mask"); +		fclose(fp); + +		return -1; +	} +	fclose(fp); + +	return 0; +} + +/* + * get_core_sibling - Get sibling core id from the same socket for given CPU + * @cpu_no:	CPU number + * + * Return:	> 0 on success, < 0 on failure. + */ +int get_core_sibling(int cpu_no) +{ +	char core_siblings_path[1024], cpu_list_str[64]; +	int sibling_cpu_no = -1; +	FILE *fp; + +	sprintf(core_siblings_path, "%s%d/topology/core_siblings_list", +		CORE_SIBLINGS_PATH, cpu_no); + +	fp = fopen(core_siblings_path, "r"); +	if (!fp) { +		perror("Failed to open core siblings path"); + +		return -1; +	} +	if (fscanf(fp, "%s", cpu_list_str) <= 0) { +		perror("Could not get core_siblings list"); +		fclose(fp); + +		return -1; +	} +	fclose(fp); + +	char *token = strtok(cpu_list_str, "-,"); + +	while (token) { +		sibling_cpu_no = atoi(token); +		/* Skipping core 0 as we don't want to run test on core 0 */ +		if (sibling_cpu_no != 0) +			break; +		token = strtok(NULL, "-,"); +	} + +	return sibling_cpu_no; +} + +/* + * taskset_benchmark - Taskset PID (i.e. benchmark) to a specified cpu + * @bm_pid:	PID that should be binded + * @cpu_no:	CPU number at which the PID would be binded + * + * Return: 0 on success, non-zero on failure + */ +int taskset_benchmark(pid_t bm_pid, int cpu_no) +{ +	cpu_set_t my_set; + +	CPU_ZERO(&my_set); +	CPU_SET(cpu_no, &my_set); + +	if (sched_setaffinity(bm_pid, sizeof(cpu_set_t), &my_set)) { +		perror("Unable to taskset benchmark"); + +		return -1; +	} + +	return 0; +} + +/* + * run_benchmark - Run a specified benchmark or fill_buf (default benchmark) + *		   in specified signal. Direct benchmark stdio to /dev/null. + * @signum:	signal number + * @info:	signal info + * @ucontext:	user context in signal handling + * + * Return: void + */ +void run_benchmark(int signum, siginfo_t *info, void *ucontext) +{ +	int operation, ret, malloc_and_init_memory, memflush; +	unsigned long span, buffer_span; +	char **benchmark_cmd; +	char resctrl_val[64]; +	FILE *fp; + +	benchmark_cmd = info->si_ptr; + +	/* +	 * Direct stdio of child to /dev/null, so that only parent writes to +	 * stdio (console) +	 */ +	fp = freopen("/dev/null", "w", stdout); +	if (!fp) +		PARENT_EXIT("Unable to direct benchmark status to /dev/null"); + +	if (strcmp(benchmark_cmd[0], "fill_buf") == 0) { +		/* Execute default fill_buf benchmark */ +		span = strtoul(benchmark_cmd[1], NULL, 10); +		malloc_and_init_memory = atoi(benchmark_cmd[2]); +		memflush =  atoi(benchmark_cmd[3]); +		operation = atoi(benchmark_cmd[4]); +		sprintf(resctrl_val, "%s", benchmark_cmd[5]); + +		if (strcmp(resctrl_val, "cqm") != 0) +			buffer_span = span * MB; +		else +			buffer_span = span; + +		if (run_fill_buf(buffer_span, malloc_and_init_memory, memflush, +				 operation, resctrl_val)) +			fprintf(stderr, "Error in running fill buffer\n"); +	} else { +		/* Execute specified benchmark */ +		ret = execvp(benchmark_cmd[0], benchmark_cmd); +		if (ret) +			perror("wrong\n"); +	} + +	fclose(stdout); +	PARENT_EXIT("Unable to run specified benchmark"); +} + +/* + * create_grp - Create a group only if one doesn't exist + * @grp_name:	Name of the group + * @grp:	Full path and name of the group + * @parent_grp:	Full path and name of the parent group + * + * Return: 0 on success, non-zero on failure + */ +static int create_grp(const char *grp_name, char *grp, const char *parent_grp) +{ +	int found_grp = 0; +	struct dirent *ep; +	DIR *dp; + +	/* +	 * At this point, we are guaranteed to have resctrl FS mounted and if +	 * length of grp_name == 0, it means, user wants to use root con_mon +	 * grp, so do nothing +	 */ +	if (strlen(grp_name) == 0) +		return 0; + +	/* Check if requested grp exists or not */ +	dp = opendir(parent_grp); +	if (dp) { +		while ((ep = readdir(dp)) != NULL) { +			if (strcmp(ep->d_name, grp_name) == 0) +				found_grp = 1; +		} +		closedir(dp); +	} else { +		perror("Unable to open resctrl for group"); + +		return -1; +	} + +	/* Requested grp doesn't exist, hence create it */ +	if (found_grp == 0) { +		if (mkdir(grp, 0) == -1) { +			perror("Unable to create group"); + +			return -1; +		} +	} + +	return 0; +} + +static int write_pid_to_tasks(char *tasks, pid_t pid) +{ +	FILE *fp; + +	fp = fopen(tasks, "w"); +	if (!fp) { +		perror("Failed to open tasks file"); + +		return -1; +	} +	if (fprintf(fp, "%d\n", pid) < 0) { +		perror("Failed to wr pid to tasks file"); +		fclose(fp); + +		return -1; +	} +	fclose(fp); + +	return 0; +} + +/* + * write_bm_pid_to_resctrl - Write a PID (i.e. benchmark) to resctrl FS + * @bm_pid:		PID that should be written + * @ctrlgrp:		Name of the control monitor group (con_mon grp) + * @mongrp:		Name of the monitor group (mon grp) + * @resctrl_val:	Resctrl feature (Eg: mbm, mba.. etc) + * + * If a con_mon grp is requested, create it and write pid to it, otherwise + * write pid to root con_mon grp. + * If a mon grp is requested, create it and write pid to it, otherwise + * pid is not written, this means that pid is in con_mon grp and hence + * should consult con_mon grp's mon_data directory for results. + * + * Return: 0 on success, non-zero on failure + */ +int write_bm_pid_to_resctrl(pid_t bm_pid, char *ctrlgrp, char *mongrp, +			    char *resctrl_val) +{ +	char controlgroup[128], monitorgroup[512], monitorgroup_p[256]; +	char tasks[1024]; +	int ret = 0; + +	if (strlen(ctrlgrp)) +		sprintf(controlgroup, "%s/%s", RESCTRL_PATH, ctrlgrp); +	else +		sprintf(controlgroup, "%s", RESCTRL_PATH); + +	/* Create control and monitoring group and write pid into it */ +	ret = create_grp(ctrlgrp, controlgroup, RESCTRL_PATH); +	if (ret) +		goto out; +	sprintf(tasks, "%s/tasks", controlgroup); +	ret = write_pid_to_tasks(tasks, bm_pid); +	if (ret) +		goto out; + +	/* Create mon grp and write pid into it for "mbm" and "cqm" test */ +	if ((strcmp(resctrl_val, "cqm") == 0) || +	    (strcmp(resctrl_val, "mbm") == 0)) { +		if (strlen(mongrp)) { +			sprintf(monitorgroup_p, "%s/mon_groups", controlgroup); +			sprintf(monitorgroup, "%s/%s", monitorgroup_p, mongrp); +			ret = create_grp(mongrp, monitorgroup, monitorgroup_p); +			if (ret) +				goto out; + +			sprintf(tasks, "%s/mon_groups/%s/tasks", +				controlgroup, mongrp); +			ret = write_pid_to_tasks(tasks, bm_pid); +			if (ret) +				goto out; +		} +	} + +out: +	printf("%sok writing benchmark parameters to resctrl FS\n", +	       ret ? "not " : ""); +	if (ret) +		perror("# writing to resctrlfs"); + +	tests_run++; + +	return ret; +} + +/* + * write_schemata - Update schemata of a con_mon grp + * @ctrlgrp:		Name of the con_mon grp + * @schemata:		Schemata that should be updated to + * @cpu_no:		CPU number that the benchmark PID is binded to + * @resctrl_val:	Resctrl feature (Eg: mbm, mba.. etc) + * + * Update schemata of a con_mon grp *only* if requested resctrl feature is + * allocation type + * + * Return: 0 on success, non-zero on failure + */ +int write_schemata(char *ctrlgrp, char *schemata, int cpu_no, char *resctrl_val) +{ +	char controlgroup[1024], schema[1024], reason[64]; +	int resource_id, ret = 0; +	FILE *fp; + +	if ((strcmp(resctrl_val, "mba") != 0) && +	    (strcmp(resctrl_val, "cat") != 0) && +	    (strcmp(resctrl_val, "cqm") != 0)) +		return -ENOENT; + +	if (!schemata) { +		printf("# Skipping empty schemata update\n"); + +		return -1; +	} + +	if (get_resource_id(cpu_no, &resource_id) < 0) { +		sprintf(reason, "Failed to get resource id"); +		ret = -1; + +		goto out; +	} + +	if (strlen(ctrlgrp) != 0) +		sprintf(controlgroup, "%s/%s/schemata", RESCTRL_PATH, ctrlgrp); +	else +		sprintf(controlgroup, "%s/schemata", RESCTRL_PATH); + +	if (!strcmp(resctrl_val, "cat") || !strcmp(resctrl_val, "cqm")) +		sprintf(schema, "%s%d%c%s", "L3:", resource_id, '=', schemata); +	if (strcmp(resctrl_val, "mba") == 0) +		sprintf(schema, "%s%d%c%s", "MB:", resource_id, '=', schemata); + +	fp = fopen(controlgroup, "w"); +	if (!fp) { +		sprintf(reason, "Failed to open control group"); +		ret = -1; + +		goto out; +	} + +	if (fprintf(fp, "%s\n", schema) < 0) { +		sprintf(reason, "Failed to write schemata in control group"); +		fclose(fp); +		ret = -1; + +		goto out; +	} +	fclose(fp); + +out: +	printf("%sok Write schema \"%s\" to resctrl FS%s%s\n", +	       ret ? "not " : "", schema, ret ? " # " : "", +	       ret ? reason : ""); +	tests_run++; + +	return ret; +} + +bool check_resctrlfs_support(void) +{ +	FILE *inf = fopen("/proc/filesystems", "r"); +	DIR *dp; +	char *res; +	bool ret = false; + +	if (!inf) +		return false; + +	res = fgrep(inf, "nodev\tresctrl\n"); + +	if (res) { +		ret = true; +		free(res); +	} + +	fclose(inf); + +	printf("%sok kernel supports resctrl filesystem\n", ret ? "" : "not "); +	tests_run++; + +	dp = opendir(RESCTRL_PATH); +	printf("%sok resctrl mountpoint \"%s\" exists\n", +	       dp ? "" : "not ", RESCTRL_PATH); +	if (dp) +		closedir(dp); +	tests_run++; + +	printf("# resctrl filesystem %s mounted\n", +	       find_resctrl_mount(NULL) ? "not" : "is"); + +	return ret; +} + +char *fgrep(FILE *inf, const char *str) +{ +	char line[256]; +	int slen = strlen(str); + +	while (!feof(inf)) { +		if (!fgets(line, 256, inf)) +			break; +		if (strncmp(line, str, slen)) +			continue; + +		return strdup(line); +	} + +	return NULL; +} + +/* + * validate_resctrl_feature_request - Check if requested feature is valid. + * @resctrl_val:	Requested feature + * + * Return: 0 on success, non-zero on failure + */ +bool validate_resctrl_feature_request(char *resctrl_val) +{ +	FILE *inf = fopen("/proc/cpuinfo", "r"); +	bool found = false; +	char *res; + +	if (!inf) +		return false; + +	res = fgrep(inf, "flags"); + +	if (res) { +		char *s = strchr(res, ':'); + +		found = s && !strstr(s, resctrl_val); +		free(res); +	} +	fclose(inf); + +	return found; +} + +int filter_dmesg(void) +{ +	char line[1024]; +	FILE *fp; +	int pipefds[2]; +	pid_t pid; +	int ret; + +	ret = pipe(pipefds); +	if (ret) { +		perror("pipe"); +		return ret; +	} +	pid = fork(); +	if (pid == 0) { +		close(pipefds[0]); +		dup2(pipefds[1], STDOUT_FILENO); +		execlp("dmesg", "dmesg", NULL); +		perror("executing dmesg"); +		exit(1); +	} +	close(pipefds[1]); +	fp = fdopen(pipefds[0], "r"); +	if (!fp) { +		perror("fdopen(pipe)"); +		kill(pid, SIGTERM); + +		return -1; +	} + +	while (fgets(line, 1024, fp)) { +		if (strstr(line, "intel_rdt:")) +			printf("# dmesg: %s", line); +		if (strstr(line, "resctrl:")) +			printf("# dmesg: %s", line); +	} +	fclose(fp); +	waitpid(pid, NULL, 0); + +	return 0; +} + +int validate_bw_report_request(char *bw_report) +{ +	if (strcmp(bw_report, "reads") == 0) +		return 0; +	if (strcmp(bw_report, "writes") == 0) +		return 0; +	if (strcmp(bw_report, "nt-writes") == 0) { +		strcpy(bw_report, "writes"); +		return 0; +	} +	if (strcmp(bw_report, "total") == 0) +		return 0; + +	fprintf(stderr, "Requested iMC B/W report type unavailable\n"); + +	return -1; +} + +int perf_event_open(struct perf_event_attr *hw_event, pid_t pid, int cpu, +		    int group_fd, unsigned long flags) +{ +	int ret; + +	ret = syscall(__NR_perf_event_open, hw_event, pid, cpu, +		      group_fd, flags); +	return ret; +} + +unsigned int count_bits(unsigned long n) +{ +	unsigned int count = 0; + +	while (n) { +		count += n & 1; +		n >>= 1; +	} + +	return count; +}  |