| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Basic resctrl file system operations | |
| * | |
| * Copyright (C) 2018 Intel Corporation | |
| * | |
| * Authors: | |
| * Sai Praneeth Prakhya <sai.praneeth.prakhya@intel.com>, | |
| * Fenghua Yu <fenghua.yu@intel.com> | |
| */ | |
| #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; | |
| } |