aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/tools/testing/selftests/resctrl
diff options
context:
space:
mode:
Diffstat (limited to 'tools/testing/selftests/resctrl')
-rw-r--r--tools/testing/selftests/resctrl/.gitignore2
-rw-r--r--tools/testing/selftests/resctrl/Makefile20
-rw-r--r--tools/testing/selftests/resctrl/README43
-rw-r--r--tools/testing/selftests/resctrl/cache.c265
-rw-r--r--tools/testing/selftests/resctrl/cat_test.c466
-rw-r--r--tools/testing/selftests/resctrl/cmt_test.c190
-rw-r--r--tools/testing/selftests/resctrl/config2
-rw-r--r--tools/testing/selftests/resctrl/cqm_test.c176
-rw-r--r--tools/testing/selftests/resctrl/fill_buf.c189
-rw-r--r--tools/testing/selftests/resctrl/mba_test.c176
-rw-r--r--tools/testing/selftests/resctrl/mbm_test.c155
-rw-r--r--tools/testing/selftests/resctrl/resctrl.h255
-rw-r--r--tools/testing/selftests/resctrl/resctrl_tests.c388
-rw-r--r--tools/testing/selftests/resctrl/resctrl_val.c765
-rw-r--r--tools/testing/selftests/resctrl/resctrlfs.c783
-rw-r--r--tools/testing/selftests/resctrl/settings3
16 files changed, 2232 insertions, 1646 deletions
diff --git a/tools/testing/selftests/resctrl/.gitignore b/tools/testing/selftests/resctrl/.gitignore
new file mode 100644
index 000000000000..ab68442b6bc8
--- /dev/null
+++ b/tools/testing/selftests/resctrl/.gitignore
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+resctrl_tests
diff --git a/tools/testing/selftests/resctrl/Makefile b/tools/testing/selftests/resctrl/Makefile
index d585cc1948cc..984534cfbf1b 100644
--- a/tools/testing/selftests/resctrl/Makefile
+++ b/tools/testing/selftests/resctrl/Makefile
@@ -1,17 +1,13 @@
-CC = $(CROSS_COMPILE)gcc
-CFLAGS = -g -Wall
-SRCS=$(wildcard *.c)
-OBJS=$(SRCS:.c=.o)
+# SPDX-License-Identifier: GPL-2.0
-all: resctrl_tests
+CFLAGS = -g -Wall -O2 -D_FORTIFY_SOURCE=2
+CFLAGS += $(KHDR_INCLUDES)
-$(OBJS): $(SRCS)
- $(CC) $(CFLAGS) -c $(SRCS)
+TEST_GEN_PROGS := resctrl_tests
-resctrl_tests: $(OBJS)
- $(CC) $(CFLAGS) -o $@ $^
+LOCAL_HDRS += $(wildcard *.h)
-.PHONY: clean
+include ../lib.mk
+CFLAGS += -I$(top_srcdir)/tools/include
-clean:
- $(RM) $(OBJS) resctrl_tests
+$(OUTPUT)/resctrl_tests: $(wildcard *.c)
diff --git a/tools/testing/selftests/resctrl/README b/tools/testing/selftests/resctrl/README
index 6e5a0ffa18e8..8d11ce7c2ee5 100644
--- a/tools/testing/selftests/resctrl/README
+++ b/tools/testing/selftests/resctrl/README
@@ -12,24 +12,49 @@ Allocation test on Intel RDT hardware. More tests will be added in the future.
And the test suit can be extended to cover AMD QoS and ARM MPAM hardware
as well.
+resctrl_tests can be run with or without kselftest framework.
+
+WITH KSELFTEST FRAMEWORK
+=======================
+
BUILD
-----
-Run "make" to build executable file "resctrl_tests".
+Build executable file "resctrl_tests" from top level directory of the kernel source:
+ $ make -C tools/testing/selftests TARGETS=resctrl
RUN
---
-To use resctrl_tests, root or sudoer privileges are required. This is because
-the test needs to mount resctrl file system and change contents in the file
-system.
+Run resctrl_tests as sudo or root since the test needs to mount resctrl file
+system and change contents in the file system.
+Using kselftest framework will run all supported tests within resctrl_tests:
+
+ $ sudo make -C tools/testing/selftests TARGETS=resctrl run_tests
+
+More details about kselftest framework can be found in
+Documentation/dev-tools/kselftest.rst.
+
+WITHOUT KSELFTEST FRAMEWORK
+===========================
+
+BUILD
+-----
+
+Build executable file "resctrl_tests" from this directory(tools/testing/selftests/resctrl/):
+ $ make
+
+RUN
+---
+Run resctrl_tests as sudo or root since the test needs to mount resctrl file
+system and change contents in the file system.
Executing the test without any parameter will run all supported tests:
- sudo ./resctrl_tests
+ $ sudo ./resctrl_tests
OVERVIEW OF EXECUTION
----------------------
+=====================
A test case has four stages:
@@ -41,13 +66,13 @@ A test case has four stages:
- teardown: umount resctrl and clear temporary files.
ARGUMENTS
----------
+=========
Parameter '-h' shows usage information.
usage: resctrl_tests [-h] [-b "benchmark_cmd [options]"] [-t test list] [-n no_of_bits]
- -b benchmark_cmd [options]: run specified benchmark for MBM, MBA and CQM default benchmark is builtin fill_buf
- -t test list: run tests specified in the test list, e.g. -t mbm, mba, cqm, cat
+ -b benchmark_cmd [options]: run specified benchmark for MBM, MBA and CMT default benchmark is builtin fill_buf
+ -t test list: run tests specified in the test list, e.g. -t mbm,mba,cmt,cat
-n no_of_bits: run cache tests using specified no of bits in cache bit mask
-p cpu_no: specify CPU number to run the test. 1 is default
-h: help
diff --git a/tools/testing/selftests/resctrl/cache.c b/tools/testing/selftests/resctrl/cache.c
index 38dbf4962e33..1ff1104e6575 100644
--- a/tools/testing/selftests/resctrl/cache.c
+++ b/tools/testing/selftests/resctrl/cache.c
@@ -3,115 +3,64 @@
#include <stdint.h>
#include "resctrl.h"
-struct read_format {
- __u64 nr; /* The number of events */
- struct {
- __u64 value; /* The value of the event */
- } values[2];
-};
-
-static struct perf_event_attr pea_llc_miss;
-static struct read_format rf_cqm;
-static int fd_lm;
char llc_occup_path[1024];
-static void initialize_perf_event_attr(void)
-{
- pea_llc_miss.type = PERF_TYPE_HARDWARE;
- pea_llc_miss.size = sizeof(struct perf_event_attr);
- pea_llc_miss.read_format = PERF_FORMAT_GROUP;
- pea_llc_miss.exclude_kernel = 1;
- pea_llc_miss.exclude_hv = 1;
- pea_llc_miss.exclude_idle = 1;
- pea_llc_miss.exclude_callchain_kernel = 1;
- pea_llc_miss.inherit = 1;
- pea_llc_miss.exclude_guest = 1;
- pea_llc_miss.disabled = 1;
-}
-
-static void ioctl_perf_event_ioc_reset_enable(void)
-{
- ioctl(fd_lm, PERF_EVENT_IOC_RESET, 0);
- ioctl(fd_lm, PERF_EVENT_IOC_ENABLE, 0);
-}
-
-static int perf_event_open_llc_miss(pid_t pid, int cpu_no)
-{
- fd_lm = perf_event_open(&pea_llc_miss, pid, cpu_no, -1,
- PERF_FLAG_FD_CLOEXEC);
- if (fd_lm == -1) {
- perror("Error opening leader");
- ctrlc_handler(0, NULL, NULL);
- return -1;
- }
-
- return 0;
-}
-
-static int initialize_llc_perf(void)
+void perf_event_attr_initialize(struct perf_event_attr *pea, __u64 config)
{
- memset(&pea_llc_miss, 0, sizeof(struct perf_event_attr));
- memset(&rf_cqm, 0, sizeof(struct read_format));
-
- /* Initialize perf_event_attr structures for HW_CACHE_MISSES */
- initialize_perf_event_attr();
-
- pea_llc_miss.config = PERF_COUNT_HW_CACHE_MISSES;
-
- rf_cqm.nr = 1;
-
- return 0;
+ memset(pea, 0, sizeof(*pea));
+ pea->type = PERF_TYPE_HARDWARE;
+ pea->size = sizeof(*pea);
+ pea->read_format = PERF_FORMAT_GROUP;
+ pea->exclude_kernel = 1;
+ pea->exclude_hv = 1;
+ pea->exclude_idle = 1;
+ pea->exclude_callchain_kernel = 1;
+ pea->inherit = 1;
+ pea->exclude_guest = 1;
+ pea->disabled = 1;
+ pea->config = config;
}
-static int reset_enable_llc_perf(pid_t pid, int cpu_no)
+/* Start counters to log values */
+int perf_event_reset_enable(int pe_fd)
{
- int ret = 0;
+ int ret;
- ret = perf_event_open_llc_miss(pid, cpu_no);
+ ret = ioctl(pe_fd, PERF_EVENT_IOC_RESET, 0);
if (ret < 0)
return ret;
- /* Start counters to log values */
- ioctl_perf_event_ioc_reset_enable();
+ ret = ioctl(pe_fd, PERF_EVENT_IOC_ENABLE, 0);
+ if (ret < 0)
+ return ret;
return 0;
}
-/*
- * get_llc_perf: llc cache miss through perf events
- * @cpu_no: CPU number that the benchmark PID is binded to
- *
- * Perf events like HW_CACHE_MISSES could be used to validate number of
- * cache lines allocated.
- *
- * Return: =0 on success. <0 on failure.
- */
-static int get_llc_perf(unsigned long *llc_perf_miss)
+void perf_event_initialize_read_format(struct perf_event_read *pe_read)
{
- __u64 total_misses;
-
- /* Stop counters after one span to get miss rate */
-
- ioctl(fd_lm, PERF_EVENT_IOC_DISABLE, 0);
+ memset(pe_read, 0, sizeof(*pe_read));
+ pe_read->nr = 1;
+}
- if (read(fd_lm, &rf_cqm, sizeof(struct read_format)) == -1) {
- perror("Could not get llc misses through perf");
+int perf_open(struct perf_event_attr *pea, pid_t pid, int cpu_no)
+{
+ int pe_fd;
+ pe_fd = perf_event_open(pea, pid, cpu_no, -1, PERF_FLAG_FD_CLOEXEC);
+ if (pe_fd == -1) {
+ ksft_perror("Error opening leader");
return -1;
}
- total_misses = rf_cqm.values[0].value;
+ perf_event_reset_enable(pe_fd);
- close(fd_lm);
-
- *llc_perf_miss = total_misses;
-
- return 0;
+ return pe_fd;
}
/*
* Get LLC Occupancy as reported by RESCTRL FS
- * For CQM,
+ * For CMT,
* 1. If con_mon grp and mon grp given, then read from mon grp in
* con_mon grp
* 2. If only con_mon grp given, then read from con_mon grp
@@ -128,12 +77,12 @@ static int get_llc_occu_resctrl(unsigned long *llc_occupancy)
fp = fopen(llc_occup_path, "r");
if (!fp) {
- perror("Failed to open results file");
+ ksft_perror("Failed to open results file");
- return errno;
+ return -1;
}
if (fscanf(fp, "%lu", llc_occupancy) <= 0) {
- perror("Could not get llc occupancy");
+ ksft_perror("Could not get llc occupancy");
fclose(fp);
return -1;
@@ -150,123 +99,91 @@ static int get_llc_occu_resctrl(unsigned long *llc_occupancy)
* @llc_value: perf miss value /
* llc occupancy value reported by resctrl FS
*
- * Return: 0 on success. non-zero on failure.
+ * Return: 0 on success, < 0 on error.
*/
-static int print_results_cache(char *filename, int bm_pid,
- unsigned long llc_value)
+static int print_results_cache(const char *filename, pid_t bm_pid, __u64 llc_value)
{
FILE *fp;
if (strcmp(filename, "stdio") == 0 || strcmp(filename, "stderr") == 0) {
- printf("Pid: %d \t LLC_value: %lu\n", bm_pid,
- llc_value);
+ printf("Pid: %d \t LLC_value: %llu\n", (int)bm_pid, llc_value);
} else {
fp = fopen(filename, "a");
if (!fp) {
- perror("Cannot open results file");
+ ksft_perror("Cannot open results file");
- return errno;
+ return -1;
}
- fprintf(fp, "Pid: %d \t llc_value: %lu\n", bm_pid, llc_value);
+ fprintf(fp, "Pid: %d \t llc_value: %llu\n", (int)bm_pid, llc_value);
fclose(fp);
}
return 0;
}
-int measure_cache_vals(struct resctrl_val_param *param, int bm_pid)
+/*
+ * perf_event_measure - Measure perf events
+ * @filename: Filename for writing the results
+ * @bm_pid: PID that runs the benchmark
+ *
+ * Measures perf events (e.g., cache misses) and writes the results into
+ * @filename. @bm_pid is written to the results file along with the measured
+ * value.
+ *
+ * Return: =0 on success. <0 on failure.
+ */
+int perf_event_measure(int pe_fd, struct perf_event_read *pe_read,
+ const char *filename, pid_t bm_pid)
{
- unsigned long llc_perf_miss = 0, llc_occu_resc = 0, llc_value = 0;
int ret;
- /*
- * Measure cache miss from perf.
- */
- if (!strcmp(param->resctrl_val, "cat")) {
- ret = get_llc_perf(&llc_perf_miss);
- if (ret < 0)
- return ret;
- llc_value = llc_perf_miss;
- }
+ /* Stop counters after one span to get miss rate */
+ ret = ioctl(pe_fd, PERF_EVENT_IOC_DISABLE, 0);
+ if (ret < 0)
+ return ret;
- /*
- * Measure llc occupancy from resctrl.
- */
- if (!strcmp(param->resctrl_val, "cqm")) {
- ret = get_llc_occu_resctrl(&llc_occu_resc);
- if (ret < 0)
- return ret;
- llc_value = llc_occu_resc;
+ ret = read(pe_fd, pe_read, sizeof(*pe_read));
+ if (ret == -1) {
+ ksft_perror("Could not get perf value");
+ return -1;
}
- ret = print_results_cache(param->filename, bm_pid, llc_value);
- if (ret)
- return ret;
- return 0;
+ return print_results_cache(filename, bm_pid, pe_read->values[0].value);
}
/*
- * cache_val: execute benchmark and measure LLC occupancy resctrl
- * and perf cache miss for the benchmark
- * @param: parameters passed to cache_val()
+ * measure_llc_resctrl - Measure resctrl LLC value from resctrl
+ * @filename: Filename for writing the results
+ * @bm_pid: PID that runs the benchmark
+ *
+ * Measures LLC occupancy from resctrl and writes the results into @filename.
+ * @bm_pid is written to the results file along with the measured value.
*
- * Return: 0 on success. non-zero on failure.
+ * Return: =0 on success. <0 on failure.
*/
-int cat_val(struct resctrl_val_param *param)
+int measure_llc_resctrl(const char *filename, pid_t bm_pid)
{
- int malloc_and_init_memory = 1, memflush = 1, operation = 0, ret = 0;
- char *resctrl_val = param->resctrl_val;
- pid_t bm_pid;
-
- if (strcmp(param->filename, "") == 0)
- sprintf(param->filename, "stdio");
-
- bm_pid = getpid();
-
- /* Taskset benchmark to specified cpu */
- ret = taskset_benchmark(bm_pid, param->cpu_no);
- if (ret)
- return ret;
+ unsigned long llc_occu_resc = 0;
+ int ret;
- /* Write benchmark to specified con_mon grp, mon_grp in resctrl FS*/
- ret = write_bm_pid_to_resctrl(bm_pid, param->ctrlgrp, param->mongrp,
- resctrl_val);
- if (ret)
+ ret = get_llc_occu_resctrl(&llc_occu_resc);
+ if (ret < 0)
return ret;
- if ((strcmp(resctrl_val, "cat") == 0)) {
- ret = initialize_llc_perf();
- if (ret)
- return ret;
- }
-
- /* Test runs until the callback setup() tells the test to stop. */
- while (1) {
- if (strcmp(resctrl_val, "cat") == 0) {
- ret = param->setup(1, param);
- if (ret) {
- ret = 0;
- break;
- }
- ret = reset_enable_llc_perf(bm_pid, param->cpu_no);
- if (ret)
- break;
-
- if (run_fill_buf(param->span, malloc_and_init_memory,
- memflush, operation, resctrl_val)) {
- fprintf(stderr, "Error-running fill buffer\n");
- ret = -1;
- break;
- }
-
- sleep(1);
- ret = measure_cache_vals(param, bm_pid);
- if (ret)
- break;
- } else {
- break;
- }
- }
+ return print_results_cache(filename, bm_pid, llc_occu_resc);
+}
- return ret;
+/*
+ * show_cache_info - Show generic cache test information
+ * @no_of_bits: Number of bits
+ * @avg_llc_val: Average of LLC cache result data
+ * @cache_span: Cache span
+ * @lines: @cache_span in lines or bytes
+ */
+void show_cache_info(int no_of_bits, __u64 avg_llc_val, size_t cache_span, bool lines)
+{
+ ksft_print_msg("Number of bits: %d\n", no_of_bits);
+ ksft_print_msg("Average LLC val: %llu\n", avg_llc_val);
+ ksft_print_msg("Cache span (%s): %zu\n", lines ? "lines" : "bytes",
+ cache_span);
}
diff --git a/tools/testing/selftests/resctrl/cat_test.c b/tools/testing/selftests/resctrl/cat_test.c
index 5da43767b973..94cfdba5308d 100644
--- a/tools/testing/selftests/resctrl/cat_test.c
+++ b/tools/testing/selftests/resctrl/cat_test.c
@@ -11,240 +11,392 @@
#include "resctrl.h"
#include <unistd.h>
-#define RESULT_FILE_NAME1 "result_cat1"
-#define RESULT_FILE_NAME2 "result_cat2"
+#define RESULT_FILE_NAME "result_cat"
#define NUM_OF_RUNS 5
-#define MAX_DIFF_PERCENT 4
-#define MAX_DIFF 1000000
-
-int count_of_bits;
-char cbm_mask[256];
-unsigned long long_mask;
-unsigned long cache_size;
/*
- * Change schemata. Write schemata to specified
- * con_mon grp, mon_grp in resctrl FS.
- * Run 5 times in order to get average values.
+ * Minimum difference in LLC misses between a test with n+1 bits CBM to the
+ * test with n bits is MIN_DIFF_PERCENT_PER_BIT * (n - 1). With e.g. 5 vs 4
+ * bits in the CBM mask, the minimum difference must be at least
+ * MIN_DIFF_PERCENT_PER_BIT * (4 - 1) = 3 percent.
+ *
+ * The relationship between number of used CBM bits and difference in LLC
+ * misses is not expected to be linear. With a small number of bits, the
+ * margin is smaller than with larger number of bits. For selftest purposes,
+ * however, linear approach is enough because ultimately only pass/fail
+ * decision has to be made and distinction between strong and stronger
+ * signal is irrelevant.
*/
-static int cat_setup(int num, ...)
+#define MIN_DIFF_PERCENT_PER_BIT 1UL
+
+static int show_results_info(__u64 sum_llc_val, int no_of_bits,
+ unsigned long cache_span,
+ unsigned long min_diff_percent,
+ unsigned long num_of_runs, bool platform,
+ __s64 *prev_avg_llc_val)
{
- struct resctrl_val_param *p;
- char schemata[64];
- va_list param;
+ __u64 avg_llc_val = 0;
+ float avg_diff;
int ret = 0;
- va_start(param, num);
- p = va_arg(param, struct resctrl_val_param *);
- va_end(param);
+ avg_llc_val = sum_llc_val / num_of_runs;
+ if (*prev_avg_llc_val) {
+ float delta = (__s64)(avg_llc_val - *prev_avg_llc_val);
- /* Run NUM_OF_RUNS times */
- if (p->num_of_runs >= NUM_OF_RUNS)
- return -1;
+ avg_diff = delta / *prev_avg_llc_val;
+ ret = platform && (avg_diff * 100) < (float)min_diff_percent;
- if (p->num_of_runs == 0) {
- sprintf(schemata, "%lx", p->mask);
- ret = write_schemata(p->ctrlgrp, schemata, p->cpu_no,
- p->resctrl_val);
+ ksft_print_msg("%s Check cache miss rate changed more than %.1f%%\n",
+ ret ? "Fail:" : "Pass:", (float)min_diff_percent);
+
+ ksft_print_msg("Percent diff=%.1f\n", avg_diff * 100);
}
- p->num_of_runs++;
+ *prev_avg_llc_val = avg_llc_val;
+
+ show_cache_info(no_of_bits, avg_llc_val, cache_span, true);
return ret;
}
-static void show_cache_info(unsigned long sum_llc_perf_miss, int no_of_bits,
- unsigned long span)
+/* Remove the highest bit from CBM */
+static unsigned long next_mask(unsigned long current_mask)
{
- unsigned long allocated_cache_lines = span / 64;
- unsigned long avg_llc_perf_miss = 0;
- float diff_percent;
-
- avg_llc_perf_miss = sum_llc_perf_miss / (NUM_OF_RUNS - 1);
- diff_percent = ((float)allocated_cache_lines - avg_llc_perf_miss) /
- allocated_cache_lines * 100;
-
- printf("%sok CAT: cache miss rate within %d%%\n",
- !is_amd && abs((int)diff_percent) > MAX_DIFF_PERCENT ?
- "not " : "", MAX_DIFF_PERCENT);
- tests_run++;
- printf("# Percent diff=%d\n", abs((int)diff_percent));
- printf("# Number of bits: %d\n", no_of_bits);
- printf("# Avg_llc_perf_miss: %lu\n", avg_llc_perf_miss);
- printf("# Allocated cache lines: %lu\n", allocated_cache_lines);
+ return current_mask & (current_mask >> 1);
}
-static int check_results(struct resctrl_val_param *param)
+static int check_results(struct resctrl_val_param *param, const char *cache_type,
+ unsigned long cache_total_size, unsigned long full_cache_mask,
+ unsigned long current_mask)
{
char *token_array[8], temp[512];
- unsigned long sum_llc_perf_miss = 0;
- int runs = 0, no_of_bits = 0;
+ __u64 sum_llc_perf_miss = 0;
+ __s64 prev_avg_llc_val = 0;
+ unsigned long alloc_size;
+ int runs = 0;
+ int fail = 0;
+ int ret;
FILE *fp;
- printf("# Checking for pass/fail\n");
+ ksft_print_msg("Checking for pass/fail\n");
fp = fopen(param->filename, "r");
if (!fp) {
- perror("# Cannot open file");
+ ksft_perror("Cannot open file");
- return errno;
+ return -1;
}
while (fgets(temp, sizeof(temp), fp)) {
char *token = strtok(temp, ":\t");
int fields = 0;
+ int bits;
while (token) {
token_array[fields++] = token;
token = strtok(NULL, ":\t");
}
- /*
- * Discard the first value which is inaccurate due to monitoring
- * setup transition phase.
- */
- if (runs > 0)
- sum_llc_perf_miss += strtoul(token_array[3], NULL, 0);
+
+ sum_llc_perf_miss += strtoull(token_array[3], NULL, 0);
runs++;
+
+ if (runs < NUM_OF_RUNS)
+ continue;
+
+ if (!current_mask) {
+ ksft_print_msg("Unexpected empty cache mask\n");
+ break;
+ }
+
+ alloc_size = cache_portion_size(cache_total_size, current_mask, full_cache_mask);
+
+ bits = count_bits(current_mask);
+
+ ret = show_results_info(sum_llc_perf_miss, bits,
+ alloc_size / 64,
+ MIN_DIFF_PERCENT_PER_BIT * (bits - 1),
+ runs, get_vendor() == ARCH_INTEL,
+ &prev_avg_llc_val);
+ if (ret)
+ fail = 1;
+
+ runs = 0;
+ sum_llc_perf_miss = 0;
+ current_mask = next_mask(current_mask);
}
fclose(fp);
- no_of_bits = count_bits(param->mask);
- show_cache_info(sum_llc_perf_miss, no_of_bits, param->span);
-
- return 0;
+ return fail;
}
-void cat_test_cleanup(void)
+static void cat_test_cleanup(void)
{
- remove(RESULT_FILE_NAME1);
- remove(RESULT_FILE_NAME2);
+ remove(RESULT_FILE_NAME);
}
-int cat_perf_miss_val(int cpu_no, int n, char *cache_type)
+/*
+ * cat_test - Execute CAT benchmark and measure cache misses
+ * @test: Test information structure
+ * @uparams: User supplied parameters
+ * @param: Parameters passed to cat_test()
+ * @span: Buffer size for the benchmark
+ * @current_mask Start mask for the first iteration
+ *
+ * Run CAT selftest by varying the allocated cache portion and comparing the
+ * impact on cache misses (the result analysis is done in check_results()
+ * and show_results_info(), not in this function).
+ *
+ * One bit is removed from the CAT allocation bit mask (in current_mask) for
+ * each subsequent test which keeps reducing the size of the allocated cache
+ * portion. A single test flushes the buffer, reads it to warm up the cache,
+ * and reads the buffer again. The cache misses are measured during the last
+ * read pass.
+ *
+ * Return: 0 when the test was run, < 0 on error.
+ */
+static int cat_test(const struct resctrl_test *test,
+ const struct user_params *uparams,
+ struct resctrl_val_param *param,
+ size_t span, unsigned long current_mask)
{
- unsigned long l_mask, l_mask_1;
- int ret, pipefd[2], sibling_cpu_no;
- char pipe_message;
+ struct perf_event_read pe_read;
+ struct perf_event_attr pea;
+ cpu_set_t old_affinity;
+ unsigned char *buf;
+ char schemata[64];
+ int ret, i, pe_fd;
pid_t bm_pid;
- cache_size = 0;
+ if (strcmp(param->filename, "") == 0)
+ sprintf(param->filename, "stdio");
- ret = remount_resctrlfs(true);
+ bm_pid = getpid();
+
+ /* Taskset benchmark to specified cpu */
+ ret = taskset_benchmark(bm_pid, uparams->cpu, &old_affinity);
if (ret)
return ret;
- if (!validate_resctrl_feature_request("cat"))
- return -1;
+ /* Write benchmark to specified con_mon grp, mon_grp in resctrl FS*/
+ ret = write_bm_pid_to_resctrl(bm_pid, param->ctrlgrp, param->mongrp);
+ if (ret)
+ goto reset_affinity;
+
+ perf_event_attr_initialize(&pea, PERF_COUNT_HW_CACHE_MISSES);
+ perf_event_initialize_read_format(&pe_read);
+ pe_fd = perf_open(&pea, bm_pid, uparams->cpu);
+ if (pe_fd < 0) {
+ ret = -1;
+ goto reset_affinity;
+ }
- /* Get default cbm mask for L3/L2 cache */
- ret = get_cbm_mask(cache_type);
+ buf = alloc_buffer(span, 1);
+ if (!buf) {
+ ret = -1;
+ goto pe_close;
+ }
+
+ while (current_mask) {
+ snprintf(schemata, sizeof(schemata), "%lx", param->mask & ~current_mask);
+ ret = write_schemata("", schemata, uparams->cpu, test->resource);
+ if (ret)
+ goto free_buf;
+ snprintf(schemata, sizeof(schemata), "%lx", current_mask);
+ ret = write_schemata(param->ctrlgrp, schemata, uparams->cpu, test->resource);
+ if (ret)
+ goto free_buf;
+
+ for (i = 0; i < NUM_OF_RUNS; i++) {
+ mem_flush(buf, span);
+ fill_cache_read(buf, span, true);
+
+ ret = perf_event_reset_enable(pe_fd);
+ if (ret)
+ goto free_buf;
+
+ fill_cache_read(buf, span, true);
+
+ ret = perf_event_measure(pe_fd, &pe_read, param->filename, bm_pid);
+ if (ret)
+ goto free_buf;
+ }
+ current_mask = next_mask(current_mask);
+ }
+
+free_buf:
+ free(buf);
+pe_close:
+ close(pe_fd);
+reset_affinity:
+ taskset_restore(bm_pid, &old_affinity);
+
+ return ret;
+}
+
+static int cat_run_test(const struct resctrl_test *test, const struct user_params *uparams)
+{
+ unsigned long long_mask, start_mask, full_cache_mask;
+ unsigned long cache_total_size = 0;
+ int n = uparams->bits;
+ unsigned int start;
+ int count_of_bits;
+ size_t span;
+ int ret;
+
+ ret = get_full_cbm(test->resource, &full_cache_mask);
+ if (ret)
+ return ret;
+ /* Get the largest contiguous exclusive portion of the cache */
+ ret = get_mask_no_shareable(test->resource, &long_mask);
if (ret)
return ret;
-
- long_mask = strtoul(cbm_mask, NULL, 16);
/* Get L3/L2 cache size */
- ret = get_cache_size(cpu_no, cache_type, &cache_size);
+ ret = get_cache_size(uparams->cpu, test->resource, &cache_total_size);
if (ret)
return ret;
- printf("cache size :%lu\n", cache_size);
+ ksft_print_msg("Cache size :%lu\n", cache_total_size);
- /* Get max number of bits from default-cabm mask */
- count_of_bits = count_bits(long_mask);
+ count_of_bits = count_contiguous_bits(long_mask, &start);
- if (n < 1 || n > count_of_bits - 1) {
- printf("Invalid input value for no_of_bits n!\n");
- printf("Please Enter value in range 1 to %d\n",
- count_of_bits - 1);
- return -1;
- }
+ if (!n)
+ n = count_of_bits / 2;
- /* Get core id from same socket for running another thread */
- sibling_cpu_no = get_core_sibling(cpu_no);
- if (sibling_cpu_no < 0)
+ if (n > count_of_bits - 1) {
+ ksft_print_msg("Invalid input value for no_of_bits n!\n");
+ ksft_print_msg("Please enter value in range 1 to %d\n",
+ count_of_bits - 1);
return -1;
+ }
+ start_mask = create_bit_mask(start, n);
struct resctrl_val_param param = {
- .resctrl_val = "cat",
- .cpu_no = cpu_no,
- .mum_resctrlfs = 0,
- .setup = cat_setup,
+ .ctrlgrp = "c1",
+ .filename = RESULT_FILE_NAME,
+ .num_of_runs = 0,
};
+ param.mask = long_mask;
+ span = cache_portion_size(cache_total_size, start_mask, full_cache_mask);
- l_mask = long_mask >> n;
- l_mask_1 = ~l_mask & long_mask;
+ remove(param.filename);
- /* Set param values for parent thread which will be allocated bitmask
- * with (max_bits - n) bits
- */
- param.span = cache_size * (count_of_bits - n) / count_of_bits;
- strcpy(param.ctrlgrp, "c2");
- strcpy(param.mongrp, "m2");
- strcpy(param.filename, RESULT_FILE_NAME2);
- param.mask = l_mask;
- param.num_of_runs = 0;
-
- if (pipe(pipefd)) {
- perror("# Unable to create pipe");
- return errno;
- }
+ ret = cat_test(test, uparams, &param, span, start_mask);
+ if (ret)
+ return ret;
- bm_pid = fork();
+ ret = check_results(&param, test->resource,
+ cache_total_size, full_cache_mask, start_mask);
+ return ret;
+}
- /* Set param values for child thread which will be allocated bitmask
- * with n bits
- */
- if (bm_pid == 0) {
- param.mask = l_mask_1;
- strcpy(param.ctrlgrp, "c1");
- strcpy(param.mongrp, "m1");
- param.span = cache_size * n / count_of_bits;
- strcpy(param.filename, RESULT_FILE_NAME1);
- param.num_of_runs = 0;
- param.cpu_no = sibling_cpu_no;
- }
+static bool arch_supports_noncont_cat(const struct resctrl_test *test)
+{
+ /* AMD always supports non-contiguous CBM. */
+ if (get_vendor() == ARCH_AMD)
+ return true;
+
+#if defined(__i386__) || defined(__x86_64__) /* arch */
+ unsigned int eax, ebx, ecx, edx;
+ /* Intel support for non-contiguous CBM needs to be discovered. */
+ if (!strcmp(test->resource, "L3"))
+ __cpuid_count(0x10, 1, eax, ebx, ecx, edx);
+ else if (!strcmp(test->resource, "L2"))
+ __cpuid_count(0x10, 2, eax, ebx, ecx, edx);
+ else
+ return false;
+
+ return ((ecx >> 3) & 1);
+#endif /* end arch */
+
+ return false;
+}
- remove(param.filename);
+static int noncont_cat_run_test(const struct resctrl_test *test,
+ const struct user_params *uparams)
+{
+ unsigned long full_cache_mask, cont_mask, noncont_mask;
+ unsigned int sparse_masks;
+ int bit_center, ret;
+ char schemata[64];
- ret = cat_val(&param);
+ /* Check to compare sparse_masks content to CPUID output. */
+ ret = resource_info_unsigned_get(test->resource, "sparse_masks", &sparse_masks);
if (ret)
return ret;
- ret = check_results(&param);
- if (ret)
- return ret;
+ if (arch_supports_noncont_cat(test) != sparse_masks) {
+ ksft_print_msg("Hardware and kernel differ on non-contiguous CBM support!\n");
+ return 1;
+ }
- if (bm_pid == 0) {
- /* Tell parent that child is ready */
- close(pipefd[0]);
- pipe_message = 1;
- if (write(pipefd[1], &pipe_message, sizeof(pipe_message)) <
- sizeof(pipe_message)) {
- close(pipefd[1]);
- perror("# failed signaling parent process");
- return errno;
- }
+ /* Write checks initialization. */
+ ret = get_full_cbm(test->resource, &full_cache_mask);
+ if (ret < 0)
+ return ret;
+ bit_center = count_bits(full_cache_mask) / 2;
- close(pipefd[1]);
- while (1)
- ;
- } else {
- /* Parent waits for child to be ready. */
- close(pipefd[1]);
- pipe_message = 0;
- while (pipe_message != 1) {
- if (read(pipefd[0], &pipe_message,
- sizeof(pipe_message)) < sizeof(pipe_message)) {
- perror("# failed reading from child process");
- break;
- }
- }
- close(pipefd[0]);
- kill(bm_pid, SIGKILL);
+ /*
+ * The bit_center needs to be at least 3 to properly calculate the CBM
+ * hole in the noncont_mask. If it's smaller return an error since the
+ * cache mask is too short and that shouldn't happen.
+ */
+ if (bit_center < 3)
+ return -EINVAL;
+ cont_mask = full_cache_mask >> bit_center;
+
+ /* Contiguous mask write check. */
+ snprintf(schemata, sizeof(schemata), "%lx", cont_mask);
+ ret = write_schemata("", schemata, uparams->cpu, test->resource);
+ if (ret) {
+ ksft_print_msg("Write of contiguous CBM failed\n");
+ return 1;
}
- cat_test_cleanup();
- if (bm_pid)
- umount_resctrlfs();
+ /*
+ * Non-contiguous mask write check. CBM has a 0xf hole approximately in the middle.
+ * Output is compared with support information to catch any edge case errors.
+ */
+ noncont_mask = ~(0xfUL << (bit_center - 2)) & full_cache_mask;
+ snprintf(schemata, sizeof(schemata), "%lx", noncont_mask);
+ ret = write_schemata("", schemata, uparams->cpu, test->resource);
+ if (ret && sparse_masks)
+ ksft_print_msg("Non-contiguous CBMs supported but write of non-contiguous CBM failed\n");
+ else if (ret && !sparse_masks)
+ ksft_print_msg("Non-contiguous CBMs not supported and write of non-contiguous CBM failed as expected\n");
+ else if (!ret && !sparse_masks)
+ ksft_print_msg("Non-contiguous CBMs not supported but write of non-contiguous CBM succeeded\n");
+
+ return !ret == !sparse_masks;
+}
+
+static bool noncont_cat_feature_check(const struct resctrl_test *test)
+{
+ if (!resctrl_resource_exists(test->resource))
+ return false;
- return 0;
+ return resource_info_file_exists(test->resource, "sparse_masks");
}
+
+struct resctrl_test l3_cat_test = {
+ .name = "L3_CAT",
+ .group = "CAT",
+ .resource = "L3",
+ .feature_check = test_resource_feature_check,
+ .run_test = cat_run_test,
+ .cleanup = cat_test_cleanup,
+};
+
+struct resctrl_test l3_noncont_cat_test = {
+ .name = "L3_NONCONT_CAT",
+ .group = "CAT",
+ .resource = "L3",
+ .feature_check = noncont_cat_feature_check,
+ .run_test = noncont_cat_run_test,
+};
+
+struct resctrl_test l2_noncont_cat_test = {
+ .name = "L2_NONCONT_CAT",
+ .group = "CAT",
+ .resource = "L2",
+ .feature_check = noncont_cat_feature_check,
+ .run_test = noncont_cat_run_test,
+};
diff --git a/tools/testing/selftests/resctrl/cmt_test.c b/tools/testing/selftests/resctrl/cmt_test.c
new file mode 100644
index 000000000000..d09e693dc739
--- /dev/null
+++ b/tools/testing/selftests/resctrl/cmt_test.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Cache Monitoring Technology (CMT) test
+ *
+ * Copyright (C) 2018 Intel Corporation
+ *
+ * Authors:
+ * Sai Praneeth Prakhya <sai.praneeth.prakhya@intel.com>,
+ * Fenghua Yu <fenghua.yu@intel.com>
+ */
+#include "resctrl.h"
+#include <unistd.h>
+
+#define RESULT_FILE_NAME "result_cmt"
+#define NUM_OF_RUNS 5
+#define MAX_DIFF 2000000
+#define MAX_DIFF_PERCENT 15
+
+#define CON_MON_LCC_OCCUP_PATH \
+ "%s/%s/mon_data/mon_L3_%02d/llc_occupancy"
+
+static int cmt_init(const struct resctrl_val_param *param, int domain_id)
+{
+ sprintf(llc_occup_path, CON_MON_LCC_OCCUP_PATH, RESCTRL_PATH,
+ param->ctrlgrp, domain_id);
+
+ return 0;
+}
+
+static int cmt_setup(const struct resctrl_test *test,
+ const struct user_params *uparams,
+ struct resctrl_val_param *p)
+{
+ /* Run NUM_OF_RUNS times */
+ if (p->num_of_runs >= NUM_OF_RUNS)
+ return END_OF_TESTS;
+
+ p->num_of_runs++;
+
+ return 0;
+}
+
+static int cmt_measure(const struct user_params *uparams,
+ struct resctrl_val_param *param, pid_t bm_pid)
+{
+ sleep(1);
+ return measure_llc_resctrl(param->filename, bm_pid);
+}
+
+static int show_results_info(unsigned long sum_llc_val, int no_of_bits,
+ unsigned long cache_span, unsigned long max_diff,
+ unsigned long max_diff_percent, unsigned long num_of_runs,
+ bool platform)
+{
+ unsigned long avg_llc_val = 0;
+ float diff_percent;
+ long avg_diff = 0;
+ int ret;
+
+ avg_llc_val = sum_llc_val / num_of_runs;
+ avg_diff = (long)(cache_span - avg_llc_val);
+ diff_percent = ((float)cache_span - avg_llc_val) / cache_span * 100;
+
+ ret = platform && abs((int)diff_percent) > max_diff_percent &&
+ labs(avg_diff) > max_diff;
+
+ ksft_print_msg("%s Check cache miss rate within %lu%%\n",
+ ret ? "Fail:" : "Pass:", max_diff_percent);
+
+ ksft_print_msg("Percent diff=%d\n", abs((int)diff_percent));
+
+ show_cache_info(no_of_bits, avg_llc_val, cache_span, false);
+
+ return ret;
+}
+
+static int check_results(struct resctrl_val_param *param, size_t span, int no_of_bits)
+{
+ char *token_array[8], temp[512];
+ unsigned long sum_llc_occu_resc = 0;
+ int runs = 0;
+ FILE *fp;
+
+ ksft_print_msg("Checking for pass/fail\n");
+ fp = fopen(param->filename, "r");
+ if (!fp) {
+ ksft_perror("Error in opening file");
+
+ return -1;
+ }
+
+ while (fgets(temp, sizeof(temp), fp)) {
+ char *token = strtok(temp, ":\t");
+ int fields = 0;
+
+ while (token) {
+ token_array[fields++] = token;
+ token = strtok(NULL, ":\t");
+ }
+
+ /* Field 3 is llc occ resc value */
+ sum_llc_occu_resc += strtoul(token_array[3], NULL, 0);
+ runs++;
+ }
+ fclose(fp);
+
+ return show_results_info(sum_llc_occu_resc, no_of_bits, span,
+ MAX_DIFF, MAX_DIFF_PERCENT, runs, true);
+}
+
+static void cmt_test_cleanup(void)
+{
+ remove(RESULT_FILE_NAME);
+}
+
+static int cmt_run_test(const struct resctrl_test *test, const struct user_params *uparams)
+{
+ struct fill_buf_param fill_buf = {};
+ unsigned long cache_total_size = 0;
+ int n = uparams->bits ? : 5;
+ unsigned long long_mask;
+ int count_of_bits;
+ size_t span;
+ int ret;
+
+ ret = get_full_cbm("L3", &long_mask);
+ if (ret)
+ return ret;
+
+ ret = get_cache_size(uparams->cpu, "L3", &cache_total_size);
+ if (ret)
+ return ret;
+ ksft_print_msg("Cache size :%lu\n", cache_total_size);
+
+ count_of_bits = count_bits(long_mask);
+
+ if (n < 1 || n > count_of_bits) {
+ ksft_print_msg("Invalid input value for numbr_of_bits n!\n");
+ ksft_print_msg("Please enter value in range 1 to %d\n", count_of_bits);
+ return -1;
+ }
+
+ struct resctrl_val_param param = {
+ .ctrlgrp = "c1",
+ .filename = RESULT_FILE_NAME,
+ .mask = ~(long_mask << n) & long_mask,
+ .num_of_runs = 0,
+ .init = cmt_init,
+ .setup = cmt_setup,
+ .measure = cmt_measure,
+ };
+
+ span = cache_portion_size(cache_total_size, param.mask, long_mask);
+
+ if (uparams->fill_buf) {
+ fill_buf.buf_size = span;
+ fill_buf.memflush = uparams->fill_buf->memflush;
+ param.fill_buf = &fill_buf;
+ } else if (!uparams->benchmark_cmd[0]) {
+ fill_buf.buf_size = span;
+ fill_buf.memflush = true;
+ param.fill_buf = &fill_buf;
+ }
+
+ remove(RESULT_FILE_NAME);
+
+ ret = resctrl_val(test, uparams, &param);
+ if (ret)
+ return ret;
+
+ ret = check_results(&param, span, n);
+ if (ret && (get_vendor() == ARCH_INTEL) && !snc_kernel_support())
+ ksft_print_msg("Kernel doesn't support Sub-NUMA Clustering but it is enabled on the system.\n");
+
+ return ret;
+}
+
+static bool cmt_feature_check(const struct resctrl_test *test)
+{
+ return test_resource_feature_check(test) &&
+ resctrl_mon_feature_exists("L3_MON", "llc_occupancy");
+}
+
+struct resctrl_test cmt_test = {
+ .name = "CMT",
+ .resource = "L3",
+ .feature_check = cmt_feature_check,
+ .run_test = cmt_run_test,
+ .cleanup = cmt_test_cleanup,
+};
diff --git a/tools/testing/selftests/resctrl/config b/tools/testing/selftests/resctrl/config
new file mode 100644
index 000000000000..8d9f2deb56ed
--- /dev/null
+++ b/tools/testing/selftests/resctrl/config
@@ -0,0 +1,2 @@
+CONFIG_X86_CPU_RESCTRL=y
+CONFIG_PROC_CPU_RESCTRL=y
diff --git a/tools/testing/selftests/resctrl/cqm_test.c b/tools/testing/selftests/resctrl/cqm_test.c
deleted file mode 100644
index c8756152bd61..000000000000
--- a/tools/testing/selftests/resctrl/cqm_test.c
+++ /dev/null
@@ -1,176 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Cache Monitoring Technology (CQM) test
- *
- * Copyright (C) 2018 Intel Corporation
- *
- * Authors:
- * Sai Praneeth Prakhya <sai.praneeth.prakhya@intel.com>,
- * Fenghua Yu <fenghua.yu@intel.com>
- */
-#include "resctrl.h"
-#include <unistd.h>
-
-#define RESULT_FILE_NAME "result_cqm"
-#define NUM_OF_RUNS 5
-#define MAX_DIFF 2000000
-#define MAX_DIFF_PERCENT 15
-
-int count_of_bits;
-char cbm_mask[256];
-unsigned long long_mask;
-unsigned long cache_size;
-
-static int cqm_setup(int num, ...)
-{
- struct resctrl_val_param *p;
- va_list param;
-
- va_start(param, num);
- p = va_arg(param, struct resctrl_val_param *);
- va_end(param);
-
- /* Run NUM_OF_RUNS times */
- if (p->num_of_runs >= NUM_OF_RUNS)
- return -1;
-
- p->num_of_runs++;
-
- return 0;
-}
-
-static void show_cache_info(unsigned long sum_llc_occu_resc, int no_of_bits,
- unsigned long span)
-{
- unsigned long avg_llc_occu_resc = 0;
- float diff_percent;
- long avg_diff = 0;
- bool res;
-
- avg_llc_occu_resc = sum_llc_occu_resc / (NUM_OF_RUNS - 1);
- avg_diff = (long)abs(span - avg_llc_occu_resc);
-
- diff_percent = (((float)span - avg_llc_occu_resc) / span) * 100;
-
- if ((abs((int)diff_percent) <= MAX_DIFF_PERCENT) ||
- (abs(avg_diff) <= MAX_DIFF))
- res = true;
- else
- res = false;
-
- printf("%sok CQM: diff within %d, %d\%%\n", res ? "" : "not",
- MAX_DIFF, (int)MAX_DIFF_PERCENT);
-
- printf("# diff: %ld\n", avg_diff);
- printf("# percent diff=%d\n", abs((int)diff_percent));
- printf("# Results are displayed in (Bytes)\n");
- printf("# Number of bits: %d\n", no_of_bits);
- printf("# Avg_llc_occu_resc: %lu\n", avg_llc_occu_resc);
- printf("# llc_occu_exp (span): %lu\n", span);
-
- tests_run++;
-}
-
-static int check_results(struct resctrl_val_param *param, int no_of_bits)
-{
- char *token_array[8], temp[512];
- unsigned long sum_llc_occu_resc = 0;
- int runs = 0;
- FILE *fp;
-
- printf("# checking for pass/fail\n");
- fp = fopen(param->filename, "r");
- if (!fp) {
- perror("# Error in opening file\n");
-
- return errno;
- }
-
- while (fgets(temp, 1024, fp)) {
- char *token = strtok(temp, ":\t");
- int fields = 0;
-
- while (token) {
- token_array[fields++] = token;
- token = strtok(NULL, ":\t");
- }
-
- /* Field 3 is llc occ resc value */
- if (runs > 0)
- sum_llc_occu_resc += strtoul(token_array[3], NULL, 0);
- runs++;
- }
- fclose(fp);
- show_cache_info(sum_llc_occu_resc, no_of_bits, param->span);
-
- return 0;
-}
-
-void cqm_test_cleanup(void)
-{
- remove(RESULT_FILE_NAME);
-}
-
-int cqm_resctrl_val(int cpu_no, int n, char **benchmark_cmd)
-{
- int ret, mum_resctrlfs;
-
- cache_size = 0;
- mum_resctrlfs = 1;
-
- ret = remount_resctrlfs(mum_resctrlfs);
- if (ret)
- return ret;
-
- if (!validate_resctrl_feature_request("cqm"))
- return -1;
-
- ret = get_cbm_mask("L3");
- if (ret)
- return ret;
-
- long_mask = strtoul(cbm_mask, NULL, 16);
-
- ret = get_cache_size(cpu_no, "L3", &cache_size);
- if (ret)
- return ret;
- printf("cache size :%lu\n", cache_size);
-
- count_of_bits = count_bits(long_mask);
-
- if (n < 1 || n > count_of_bits) {
- printf("Invalid input value for numbr_of_bits n!\n");
- printf("Please Enter value in range 1 to %d\n", count_of_bits);
- return -1;
- }
-
- struct resctrl_val_param param = {
- .resctrl_val = "cqm",
- .ctrlgrp = "c1",
- .mongrp = "m1",
- .cpu_no = cpu_no,
- .mum_resctrlfs = 0,
- .filename = RESULT_FILE_NAME,
- .mask = ~(long_mask << n) & long_mask,
- .span = cache_size * n / count_of_bits,
- .num_of_runs = 0,
- .setup = cqm_setup,
- };
-
- if (strcmp(benchmark_cmd[0], "fill_buf") == 0)
- sprintf(benchmark_cmd[1], "%lu", param.span);
-
- remove(RESULT_FILE_NAME);
-
- ret = resctrl_val(benchmark_cmd, &param);
- if (ret)
- return ret;
-
- ret = check_results(&param, n);
- if (ret)
- return ret;
-
- cqm_test_cleanup();
-
- return 0;
-}
diff --git a/tools/testing/selftests/resctrl/fill_buf.c b/tools/testing/selftests/resctrl/fill_buf.c
index 79c611c99a3d..19a01a52dc1a 100644
--- a/tools/testing/selftests/resctrl/fill_buf.c
+++ b/tools/testing/selftests/resctrl/fill_buf.c
@@ -14,7 +14,6 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <inttypes.h>
-#include <malloc.h>
#include <string.h>
#include "resctrl.h"
@@ -23,8 +22,6 @@
#define PAGE_SIZE (4 * 1024)
#define MB (1024 * 1024)
-static unsigned char *startptr;
-
static void sb(void)
{
#if defined(__i386) || defined(__x86_64)
@@ -33,14 +30,6 @@ static void sb(void)
#endif
}
-static void ctrl_handler(int signo)
-{
- free(startptr);
- printf("\nEnding\n");
- sb();
- exit(EXIT_SUCCESS);
-}
-
static void cl_flush(void *p)
{
#if defined(__i386) || defined(__x86_64)
@@ -49,165 +38,107 @@ static void cl_flush(void *p)
#endif
}
-static void mem_flush(void *p, size_t s)
+void mem_flush(unsigned char *buf, size_t buf_size)
{
- char *cp = (char *)p;
+ unsigned char *cp = buf;
size_t i = 0;
- s = s / CL_SIZE; /* mem size in cache llines */
+ buf_size = buf_size / CL_SIZE; /* mem size in cache lines */
- for (i = 0; i < s; i++)
+ for (i = 0; i < buf_size; i++)
cl_flush(&cp[i * CL_SIZE]);
sb();
}
-static void *malloc_and_init_memory(size_t s)
-{
- uint64_t *p64;
- size_t s64;
-
- void *p = memalign(PAGE_SIZE, s);
-
- p64 = (uint64_t *)p;
- s64 = s / sizeof(uint64_t);
-
- while (s64 > 0) {
- *p64 = (uint64_t)rand();
- p64 += (CL_SIZE / sizeof(uint64_t));
- s64 -= (CL_SIZE / sizeof(uint64_t));
- }
-
- return p;
-}
+/*
+ * Buffer index step advance to workaround HW prefetching interfering with
+ * the measurements.
+ *
+ * Must be a prime to step through all indexes of the buffer.
+ *
+ * Some primes work better than others on some architectures (from MBA/MBM
+ * result stability point of view).
+ */
+#define FILL_IDX_MULT 23
-static int fill_one_span_read(unsigned char *start_ptr, unsigned char *end_ptr)
+static int fill_one_span_read(unsigned char *buf, size_t buf_size)
{
- unsigned char sum, *p;
+ unsigned int size = buf_size / (CL_SIZE / 2);
+ unsigned int i, idx = 0;
+ unsigned char sum = 0;
+
+ /*
+ * Read the buffer in an order that is unexpected by HW prefetching
+ * optimizations to prevent them interfering with the caching pattern.
+ *
+ * The read order is (in terms of halves of cachelines):
+ * i * FILL_IDX_MULT % size
+ * The formula is open-coded below to avoiding modulo inside the loop
+ * as it improves MBA/MBM result stability on some architectures.
+ */
+ for (i = 0; i < size; i++) {
+ sum += buf[idx * (CL_SIZE / 2)];
- sum = 0;
- p = start_ptr;
- while (p < end_ptr) {
- sum += *p;
- p += (CL_SIZE / 2);
+ idx += FILL_IDX_MULT;
+ while (idx >= size)
+ idx -= size;
}
return sum;
}
-static
-void fill_one_span_write(unsigned char *start_ptr, unsigned char *end_ptr)
-{
- unsigned char *p;
-
- p = start_ptr;
- while (p < end_ptr) {
- *p = '1';
- p += (CL_SIZE / 2);
- }
-}
-
-static int fill_cache_read(unsigned char *start_ptr, unsigned char *end_ptr,
- char *resctrl_val)
+void fill_cache_read(unsigned char *buf, size_t buf_size, bool once)
{
int ret = 0;
- FILE *fp;
while (1) {
- ret = fill_one_span_read(start_ptr, end_ptr);
- if (!strcmp(resctrl_val, "cat"))
+ ret = fill_one_span_read(buf, buf_size);
+ if (once)
break;
}
/* Consume read result so that reading memory is not optimized out. */
- fp = fopen("/dev/null", "w");
- if (!fp)
- perror("Unable to write to /dev/null");
- fprintf(fp, "Sum: %d ", ret);
- fclose(fp);
-
- return 0;
+ *value_sink = ret;
}
-static int fill_cache_write(unsigned char *start_ptr, unsigned char *end_ptr,
- char *resctrl_val)
+unsigned char *alloc_buffer(size_t buf_size, bool memflush)
{
- while (1) {
- fill_one_span_write(start_ptr, end_ptr);
- if (!strcmp(resctrl_val, "cat"))
- break;
- }
-
- return 0;
-}
-
-static int
-fill_cache(unsigned long long buf_size, int malloc_and_init, int memflush,
- int op, char *resctrl_val)
-{
- unsigned char *start_ptr, *end_ptr;
- unsigned long long i;
+ void *buf = NULL;
+ uint64_t *p64;
+ ssize_t s64;
int ret;
- if (malloc_and_init)
- start_ptr = malloc_and_init_memory(buf_size);
- else
- start_ptr = malloc(buf_size);
-
- if (!start_ptr)
- return -1;
+ ret = posix_memalign(&buf, PAGE_SIZE, buf_size);
+ if (ret < 0)
+ return NULL;
- startptr = start_ptr;
- end_ptr = start_ptr + buf_size;
+ /* Initialize the buffer */
+ p64 = buf;
+ s64 = buf_size / sizeof(uint64_t);
- /*
- * It's better to touch the memory once to avoid any compiler
- * optimizations
- */
- if (!malloc_and_init) {
- for (i = 0; i < buf_size; i++)
- *start_ptr++ = (unsigned char)rand();
+ while (s64 > 0) {
+ *p64 = (uint64_t)rand();
+ p64 += (CL_SIZE / sizeof(uint64_t));
+ s64 -= (CL_SIZE / sizeof(uint64_t));
}
- start_ptr = startptr;
-
/* Flush the memory before using to avoid "cache hot pages" effect */
if (memflush)
- mem_flush(start_ptr, buf_size);
-
- if (op == 0)
- ret = fill_cache_read(start_ptr, end_ptr, resctrl_val);
- else
- ret = fill_cache_write(start_ptr, end_ptr, resctrl_val);
-
- if (ret) {
- printf("\n Error in fill cache read/write...\n");
- return -1;
- }
-
- free(startptr);
+ mem_flush(buf, buf_size);
- return 0;
+ return buf;
}
-int run_fill_buf(unsigned long span, int malloc_and_init_memory,
- int memflush, int op, char *resctrl_val)
+ssize_t get_fill_buf_size(int cpu_no, const char *cache_type)
{
- unsigned long long cache_size = span;
+ unsigned long cache_total_size = 0;
int ret;
- /* set up ctrl-c handler */
- if (signal(SIGINT, ctrl_handler) == SIG_ERR)
- printf("Failed to catch SIGINT!\n");
- if (signal(SIGHUP, ctrl_handler) == SIG_ERR)
- printf("Failed to catch SIGHUP!\n");
-
- ret = fill_cache(cache_size, malloc_and_init_memory, memflush, op,
- resctrl_val);
- if (ret) {
- printf("\n Error in fill cache\n");
- return -1;
- }
+ ret = get_cache_size(cpu_no, cache_type, &cache_total_size);
+ if (ret)
+ return ret;
- return 0;
+ return cache_total_size * 2 > MINIMUM_SPAN ?
+ cache_total_size * 2 : MINIMUM_SPAN;
}
diff --git a/tools/testing/selftests/resctrl/mba_test.c b/tools/testing/selftests/resctrl/mba_test.c
index 7bf8eaa6204b..c7e9adc0368f 100644
--- a/tools/testing/selftests/resctrl/mba_test.c
+++ b/tools/testing/selftests/resctrl/mba_test.c
@@ -12,26 +12,37 @@
#define RESULT_FILE_NAME "result_mba"
#define NUM_OF_RUNS 5
-#define MAX_DIFF 300
+#define MAX_DIFF_PERCENT 8
#define ALLOCATION_MAX 100
#define ALLOCATION_MIN 10
#define ALLOCATION_STEP 10
+static int mba_init(const struct resctrl_val_param *param, int domain_id)
+{
+ int ret;
+
+ ret = initialize_read_mem_bw_imc();
+ if (ret)
+ return ret;
+
+ initialize_mem_bw_resctrl(param, domain_id);
+
+ return 0;
+}
+
/*
* Change schemata percentage from 100 to 10%. Write schemata to specified
* con_mon grp, mon_grp in resctrl FS.
* For each allocation, run 5 times in order to get average values.
*/
-static int mba_setup(int num, ...)
+static int mba_setup(const struct resctrl_test *test,
+ const struct user_params *uparams,
+ struct resctrl_val_param *p)
{
- static int runs_per_allocation, allocation = 100;
- struct resctrl_val_param *p;
+ static unsigned int allocation = ALLOCATION_MIN;
+ static int runs_per_allocation;
char allocation_str[64];
- va_list param;
-
- va_start(param, num);
- p = va_arg(param, struct resctrl_val_param *);
- va_end(param);
+ int ret;
if (runs_per_allocation >= NUM_OF_RUNS)
runs_per_allocation = 0;
@@ -40,73 +51,93 @@ static int mba_setup(int num, ...)
if (runs_per_allocation++ != 0)
return 0;
- if (allocation < ALLOCATION_MIN || allocation > ALLOCATION_MAX)
- return -1;
+ if (allocation > ALLOCATION_MAX)
+ return END_OF_TESTS;
sprintf(allocation_str, "%d", allocation);
- write_schemata(p->ctrlgrp, allocation_str, p->cpu_no, p->resctrl_val);
- allocation -= ALLOCATION_STEP;
+ ret = write_schemata(p->ctrlgrp, allocation_str, uparams->cpu, test->resource);
+ if (ret < 0)
+ return ret;
+
+ allocation += ALLOCATION_STEP;
return 0;
}
-static void show_mba_info(unsigned long *bw_imc, unsigned long *bw_resc)
+static int mba_measure(const struct user_params *uparams,
+ struct resctrl_val_param *param, pid_t bm_pid)
+{
+ return measure_read_mem_bw(uparams, param, bm_pid);
+}
+
+static bool show_mba_info(unsigned long *bw_imc, unsigned long *bw_resc)
{
- int allocation, runs;
- bool failed = false;
+ unsigned int allocation;
+ bool ret = false;
+ int runs;
- printf("# Results are displayed in (MB)\n");
+ ksft_print_msg("Results are displayed in (MB)\n");
/* Memory bandwidth from 100% down to 10% */
for (allocation = 0; allocation < ALLOCATION_MAX / ALLOCATION_STEP;
allocation++) {
- unsigned long avg_bw_imc, avg_bw_resc;
unsigned long sum_bw_imc = 0, sum_bw_resc = 0;
- unsigned long avg_diff;
+ long avg_bw_imc, avg_bw_resc;
+ int avg_diff_per;
+ float avg_diff;
- /*
- * The first run is discarded due to inaccurate value from
- * phase transition.
- */
- for (runs = NUM_OF_RUNS * allocation + 1;
+ for (runs = NUM_OF_RUNS * allocation;
runs < NUM_OF_RUNS * allocation + NUM_OF_RUNS ; runs++) {
sum_bw_imc += bw_imc[runs];
sum_bw_resc += bw_resc[runs];
}
- avg_bw_imc = sum_bw_imc / (NUM_OF_RUNS - 1);
- avg_bw_resc = sum_bw_resc / (NUM_OF_RUNS - 1);
- avg_diff = labs((long)(avg_bw_resc - avg_bw_imc));
-
- printf("%sok MBA schemata percentage %u smaller than %d %%\n",
- avg_diff > MAX_DIFF ? "not " : "",
- ALLOCATION_MAX - ALLOCATION_STEP * allocation,
- MAX_DIFF);
- tests_run++;
- printf("# avg_diff: %lu\n", avg_diff);
- printf("# avg_bw_imc: %lu\n", avg_bw_imc);
- printf("# avg_bw_resc: %lu\n", avg_bw_resc);
- if (avg_diff > MAX_DIFF)
- failed = true;
+ avg_bw_imc = sum_bw_imc / NUM_OF_RUNS;
+ avg_bw_resc = sum_bw_resc / NUM_OF_RUNS;
+ if (avg_bw_imc < THROTTLE_THRESHOLD || avg_bw_resc < THROTTLE_THRESHOLD) {
+ ksft_print_msg("Bandwidth below threshold (%d MiB). Dropping results from MBA schemata %u.\n",
+ THROTTLE_THRESHOLD,
+ ALLOCATION_MIN + ALLOCATION_STEP * allocation);
+ continue;
+ }
+
+ avg_diff = (float)labs(avg_bw_resc - avg_bw_imc) / avg_bw_imc;
+ avg_diff_per = (int)(avg_diff * 100);
+
+ ksft_print_msg("%s Check MBA diff within %d%% for schemata %u\n",
+ avg_diff_per > MAX_DIFF_PERCENT ?
+ "Fail:" : "Pass:",
+ MAX_DIFF_PERCENT,
+ ALLOCATION_MIN + ALLOCATION_STEP * allocation);
+
+ ksft_print_msg("avg_diff_per: %d%%\n", avg_diff_per);
+ ksft_print_msg("avg_bw_imc: %lu\n", avg_bw_imc);
+ ksft_print_msg("avg_bw_resc: %lu\n", avg_bw_resc);
+ if (avg_diff_per > MAX_DIFF_PERCENT)
+ ret = true;
}
- printf("%sok schemata change using MBA%s\n", failed ? "not " : "",
- failed ? " # at least one test failed" : "");
- tests_run++;
+ ksft_print_msg("%s Check schemata change using MBA\n",
+ ret ? "Fail:" : "Pass:");
+ if (ret)
+ ksft_print_msg("At least one test failed\n");
+
+ return ret;
}
static int check_results(void)
{
+ unsigned long bw_resc[NUM_OF_RUNS * ALLOCATION_MAX / ALLOCATION_STEP];
+ unsigned long bw_imc[NUM_OF_RUNS * ALLOCATION_MAX / ALLOCATION_STEP];
char *token_array[8], output[] = RESULT_FILE_NAME, temp[512];
- unsigned long bw_imc[1024], bw_resc[1024];
int runs;
FILE *fp;
fp = fopen(output, "r");
if (!fp) {
- perror(output);
+ ksft_perror(output);
- return errno;
+ return -1;
}
runs = 0;
@@ -128,44 +159,65 @@ static int check_results(void)
fclose(fp);
- show_mba_info(bw_imc, bw_resc);
-
- return 0;
+ return show_mba_info(bw_imc, bw_resc);
}
-void mba_test_cleanup(void)
+static void mba_test_cleanup(void)
{
remove(RESULT_FILE_NAME);
}
-int mba_schemata_change(int cpu_no, char *bw_report, char **benchmark_cmd)
+static int mba_run_test(const struct resctrl_test *test, const struct user_params *uparams)
{
struct resctrl_val_param param = {
- .resctrl_val = "mba",
.ctrlgrp = "c1",
- .mongrp = "m1",
- .cpu_no = cpu_no,
- .mum_resctrlfs = 1,
.filename = RESULT_FILE_NAME,
- .bw_report = bw_report,
- .setup = mba_setup
+ .init = mba_init,
+ .setup = mba_setup,
+ .measure = mba_measure,
};
+ struct fill_buf_param fill_buf = {};
int ret;
remove(RESULT_FILE_NAME);
- if (!validate_resctrl_feature_request("mba"))
- return -1;
+ if (uparams->fill_buf) {
+ fill_buf.buf_size = uparams->fill_buf->buf_size;
+ fill_buf.memflush = uparams->fill_buf->memflush;
+ param.fill_buf = &fill_buf;
+ } else if (!uparams->benchmark_cmd[0]) {
+ ssize_t buf_size;
+
+ buf_size = get_fill_buf_size(uparams->cpu, "L3");
+ if (buf_size < 0)
+ return buf_size;
+ fill_buf.buf_size = buf_size;
+ fill_buf.memflush = true;
+ param.fill_buf = &fill_buf;
+ }
- ret = resctrl_val(benchmark_cmd, &param);
+ ret = resctrl_val(test, uparams, &param);
if (ret)
return ret;
ret = check_results();
- if (ret)
- return ret;
+ if (ret && (get_vendor() == ARCH_INTEL) && !snc_kernel_support())
+ ksft_print_msg("Kernel doesn't support Sub-NUMA Clustering but it is enabled on the system.\n");
- mba_test_cleanup();
+ return ret;
+}
- return 0;
+static bool mba_feature_check(const struct resctrl_test *test)
+{
+ return test_resource_feature_check(test) &&
+ resctrl_mon_feature_exists("L3_MON", "mbm_local_bytes");
}
+
+struct resctrl_test mba_test = {
+ .name = "MBA",
+ .resource = "MB",
+ .vendor_specific = ARCH_INTEL,
+ .feature_check = mba_feature_check,
+ .run_test = mba_run_test,
+ .cleanup = mba_test_cleanup,
+};
diff --git a/tools/testing/selftests/resctrl/mbm_test.c b/tools/testing/selftests/resctrl/mbm_test.c
index 4700f7453f81..84d8bc250539 100644
--- a/tools/testing/selftests/resctrl/mbm_test.c
+++ b/tools/testing/selftests/resctrl/mbm_test.c
@@ -11,54 +11,54 @@
#include "resctrl.h"
#define RESULT_FILE_NAME "result_mbm"
-#define MAX_DIFF 300
+#define MAX_DIFF_PERCENT 8
#define NUM_OF_RUNS 5
-static void
-show_bw_info(unsigned long *bw_imc, unsigned long *bw_resc, int span)
+static int
+show_bw_info(unsigned long *bw_imc, unsigned long *bw_resc, size_t span)
{
- unsigned long avg_bw_imc = 0, avg_bw_resc = 0;
unsigned long sum_bw_imc = 0, sum_bw_resc = 0;
- long avg_diff = 0;
- int runs;
-
- /*
- * Discard the first value which is inaccurate due to monitoring setup
- * transition phase.
- */
- for (runs = 1; runs < NUM_OF_RUNS ; runs++) {
+ long avg_bw_imc = 0, avg_bw_resc = 0;
+ int runs, ret, avg_diff_per;
+ float avg_diff = 0;
+
+ for (runs = 0; runs < NUM_OF_RUNS; runs++) {
sum_bw_imc += bw_imc[runs];
sum_bw_resc += bw_resc[runs];
}
- avg_bw_imc = sum_bw_imc / 4;
- avg_bw_resc = sum_bw_resc / 4;
- avg_diff = avg_bw_resc - avg_bw_imc;
-
- printf("%sok MBM: diff within %d%%\n",
- labs(avg_diff) > MAX_DIFF ? "not " : "", MAX_DIFF);
- tests_run++;
- printf("# avg_diff: %lu\n", labs(avg_diff));
- printf("# Span (MB): %d\n", span);
- printf("# avg_bw_imc: %lu\n", avg_bw_imc);
- printf("# avg_bw_resc: %lu\n", avg_bw_resc);
+ avg_bw_imc = sum_bw_imc / NUM_OF_RUNS;
+ avg_bw_resc = sum_bw_resc / NUM_OF_RUNS;
+ avg_diff = (float)labs(avg_bw_resc - avg_bw_imc) / avg_bw_imc;
+ avg_diff_per = (int)(avg_diff * 100);
+
+ ret = avg_diff_per > MAX_DIFF_PERCENT;
+ ksft_print_msg("%s Check MBM diff within %d%%\n",
+ ret ? "Fail:" : "Pass:", MAX_DIFF_PERCENT);
+ ksft_print_msg("avg_diff_per: %d%%\n", avg_diff_per);
+ if (span)
+ ksft_print_msg("Span (MB): %zu\n", span / MB);
+ ksft_print_msg("avg_bw_imc: %lu\n", avg_bw_imc);
+ ksft_print_msg("avg_bw_resc: %lu\n", avg_bw_resc);
+
+ return ret;
}
-static int check_results(int span)
+static int check_results(size_t span)
{
unsigned long bw_imc[NUM_OF_RUNS], bw_resc[NUM_OF_RUNS];
char temp[1024], *token_array[8];
char output[] = RESULT_FILE_NAME;
- int runs;
+ int runs, ret;
FILE *fp;
- printf("# Checking for pass/fail\n");
+ ksft_print_msg("Checking for pass/fail\n");
fp = fopen(output, "r");
if (!fp) {
- perror(output);
+ ksft_perror(output);
- return errno;
+ return -1;
}
runs = 0;
@@ -76,70 +76,107 @@ static int check_results(int span)
runs++;
}
- show_bw_info(bw_imc, bw_resc, span);
+ ret = show_bw_info(bw_imc, bw_resc, span);
fclose(fp);
+ return ret;
+}
+
+static int mbm_init(const struct resctrl_val_param *param, int domain_id)
+{
+ int ret;
+
+ ret = initialize_read_mem_bw_imc();
+ if (ret)
+ return ret;
+
+ initialize_mem_bw_resctrl(param, domain_id);
+
return 0;
}
-static int mbm_setup(int num, ...)
+static int mbm_setup(const struct resctrl_test *test,
+ const struct user_params *uparams,
+ struct resctrl_val_param *p)
{
- struct resctrl_val_param *p;
- static int num_of_runs;
- va_list param;
int ret = 0;
/* Run NUM_OF_RUNS times */
- if (num_of_runs++ >= NUM_OF_RUNS)
- return -1;
-
- va_start(param, num);
- p = va_arg(param, struct resctrl_val_param *);
- va_end(param);
+ if (p->num_of_runs >= NUM_OF_RUNS)
+ return END_OF_TESTS;
/* Set up shemata with 100% allocation on the first run. */
- if (num_of_runs == 0)
- ret = write_schemata(p->ctrlgrp, "100", p->cpu_no,
- p->resctrl_val);
+ if (p->num_of_runs == 0 && resctrl_resource_exists("MB"))
+ ret = write_schemata(p->ctrlgrp, "100", uparams->cpu, test->resource);
+
+ p->num_of_runs++;
return ret;
}
-void mbm_test_cleanup(void)
+static int mbm_measure(const struct user_params *uparams,
+ struct resctrl_val_param *param, pid_t bm_pid)
+{
+ return measure_read_mem_bw(uparams, param, bm_pid);
+}
+
+static void mbm_test_cleanup(void)
{
remove(RESULT_FILE_NAME);
}
-int mbm_bw_change(int span, int cpu_no, char *bw_report, char **benchmark_cmd)
+static int mbm_run_test(const struct resctrl_test *test, const struct user_params *uparams)
{
struct resctrl_val_param param = {
- .resctrl_val = "mbm",
.ctrlgrp = "c1",
- .mongrp = "m1",
- .span = span,
- .cpu_no = cpu_no,
- .mum_resctrlfs = 1,
.filename = RESULT_FILE_NAME,
- .bw_report = bw_report,
- .setup = mbm_setup
+ .init = mbm_init,
+ .setup = mbm_setup,
+ .measure = mbm_measure,
};
+ struct fill_buf_param fill_buf = {};
int ret;
remove(RESULT_FILE_NAME);
- if (!validate_resctrl_feature_request("mbm"))
- return -1;
+ if (uparams->fill_buf) {
+ fill_buf.buf_size = uparams->fill_buf->buf_size;
+ fill_buf.memflush = uparams->fill_buf->memflush;
+ param.fill_buf = &fill_buf;
+ } else if (!uparams->benchmark_cmd[0]) {
+ ssize_t buf_size;
+
+ buf_size = get_fill_buf_size(uparams->cpu, "L3");
+ if (buf_size < 0)
+ return buf_size;
+ fill_buf.buf_size = buf_size;
+ fill_buf.memflush = true;
+ param.fill_buf = &fill_buf;
+ }
- ret = resctrl_val(benchmark_cmd, &param);
+ ret = resctrl_val(test, uparams, &param);
if (ret)
return ret;
- ret = check_results(span);
- if (ret)
- return ret;
+ ret = check_results(param.fill_buf ? param.fill_buf->buf_size : 0);
+ if (ret && (get_vendor() == ARCH_INTEL) && !snc_kernel_support())
+ ksft_print_msg("Kernel doesn't support Sub-NUMA Clustering but it is enabled on the system.\n");
- mbm_test_cleanup();
+ return ret;
+}
- return 0;
+static bool mbm_feature_check(const struct resctrl_test *test)
+{
+ return resctrl_mon_feature_exists("L3_MON", "mbm_total_bytes") &&
+ resctrl_mon_feature_exists("L3_MON", "mbm_local_bytes");
}
+
+struct resctrl_test mbm_test = {
+ .name = "MBM",
+ .resource = "MB",
+ .vendor_specific = ARCH_INTEL,
+ .feature_check = mbm_feature_check,
+ .run_test = mbm_run_test,
+ .cleanup = mbm_test_cleanup,
+};
diff --git a/tools/testing/selftests/resctrl/resctrl.h b/tools/testing/selftests/resctrl/resctrl.h
index 39bf59c6b9c5..cd3adfc14969 100644
--- a/tools/testing/selftests/resctrl/resctrl.h
+++ b/tools/testing/selftests/resctrl/resctrl.h
@@ -1,9 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
-#define _GNU_SOURCE
#ifndef RESCTRL_H
#define RESCTRL_H
#include <stdio.h>
-#include <stdarg.h>
#include <math.h>
#include <errno.h>
#include <sched.h>
@@ -13,6 +11,7 @@
#include <signal.h>
#include <dirent.h>
#include <stdbool.h>
+#include <ctype.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
@@ -23,85 +22,227 @@
#include <sys/eventfd.h>
#include <asm/unistd.h>
#include <linux/perf_event.h>
+#include <linux/compiler.h>
+#include "../kselftest.h"
#define MB (1024 * 1024)
#define RESCTRL_PATH "/sys/fs/resctrl"
#define PHYS_ID_PATH "/sys/devices/system/cpu/cpu"
-#define CBM_MASK_PATH "/sys/fs/resctrl/info"
+#define INFO_PATH "/sys/fs/resctrl/info"
-#define PARENT_EXIT(err_msg) \
- do { \
- perror(err_msg); \
- kill(ppid, SIGKILL); \
- exit(EXIT_FAILURE); \
- } while (0)
+/*
+ * CPU vendor IDs
+ *
+ * Define as bits because they're used for vendor_specific bitmask in
+ * the struct resctrl_test.
+ */
+#define ARCH_INTEL 1
+#define ARCH_AMD 2
+
+#define END_OF_TESTS 1
+
+#define BENCHMARK_ARGS 64
+
+#define MINIMUM_SPAN (250 * MB)
+
+/*
+ * Memory bandwidth (in MiB) below which the bandwidth comparisons
+ * between iMC and resctrl are considered unreliable. For example RAS
+ * features or memory performance features that generate memory traffic
+ * may drive accesses that are counted differently by performance counters
+ * and MBM respectively, for instance generating "overhead" traffic which
+ * is not counted against any specific RMID.
+ */
+#define THROTTLE_THRESHOLD 750
+
+/*
+ * fill_buf_param: "fill_buf" benchmark parameters
+ * @buf_size: Size (in bytes) of buffer used in benchmark.
+ * "fill_buf" allocates and initializes buffer of
+ * @buf_size. User can change value via command line.
+ * @memflush: If false the buffer will not be flushed after
+ * allocation and initialization, otherwise the
+ * buffer will be flushed. User can change value via
+ * command line (via integers with 0 interpreted as
+ * false and anything else as true).
+ */
+struct fill_buf_param {
+ size_t buf_size;
+ bool memflush;
+};
+
+/*
+ * user_params: User supplied parameters
+ * @cpu: CPU number to which the benchmark will be bound to
+ * @bits: Number of bits used for cache allocation size
+ * @benchmark_cmd: Benchmark command to run during (some of the) tests
+ * @fill_buf: Pointer to user provided parameters for "fill_buf",
+ * NULL if user did not provide parameters and test
+ * specific defaults should be used.
+ */
+struct user_params {
+ int cpu;
+ int bits;
+ const char *benchmark_cmd[BENCHMARK_ARGS];
+ const struct fill_buf_param *fill_buf;
+};
+
+/*
+ * resctrl_test: resctrl test definition
+ * @name: Test name
+ * @group: Test group - a common name for tests that share some characteristic
+ * (e.g., L3 CAT test belongs to the CAT group). Can be NULL
+ * @resource: Resource to test (e.g., MB, L3, L2, etc.)
+ * @vendor_specific: Bitmask for vendor-specific tests (can be 0 for universal tests)
+ * @disabled: Test is disabled
+ * @feature_check: Callback to check required resctrl features
+ * @run_test: Callback to run the test
+ * @cleanup: Callback to cleanup after the test
+ */
+struct resctrl_test {
+ const char *name;
+ const char *group;
+ const char *resource;
+ unsigned int vendor_specific;
+ bool disabled;
+ bool (*feature_check)(const struct resctrl_test *test);
+ int (*run_test)(const struct resctrl_test *test,
+ const struct user_params *uparams);
+ void (*cleanup)(void);
+};
/*
* resctrl_val_param: resctrl test parameters
- * @resctrl_val: Resctrl feature (Eg: mbm, mba.. etc)
* @ctrlgrp: Name of the control monitor group (con_mon grp)
* @mongrp: Name of the monitor group (mon grp)
- * @cpu_no: CPU number to which the benchmark would be binded
- * @span: Memory bytes accessed in each benchmark iteration
- * @mum_resctrlfs: Should the resctrl FS be remounted?
* @filename: Name of file to which the o/p should be written
- * @bw_report: Bandwidth report type (reads vs writes)
- * @setup: Call back function to setup test environment
+ * @init: Callback function to initialize test environment
+ * @setup: Callback function to setup per test run environment
+ * @measure: Callback that performs the measurement (a single test)
+ * @fill_buf: Parameters for default "fill_buf" benchmark.
+ * Initialized with user provided parameters, possibly
+ * adapted to be relevant to the test. If user does
+ * not provide parameters for "fill_buf" nor a
+ * replacement benchmark then initialized with defaults
+ * appropriate for test. NULL if user provided
+ * benchmark.
*/
struct resctrl_val_param {
- char *resctrl_val;
- char ctrlgrp[64];
- char mongrp[64];
- int cpu_no;
- unsigned long span;
- int mum_resctrlfs;
- char filename[64];
- char *bw_report;
- unsigned long mask;
- int num_of_runs;
- int (*setup)(int num, ...);
+ const char *ctrlgrp;
+ const char *mongrp;
+ char filename[64];
+ unsigned long mask;
+ int num_of_runs;
+ int (*init)(const struct resctrl_val_param *param,
+ int domain_id);
+ int (*setup)(const struct resctrl_test *test,
+ const struct user_params *uparams,
+ struct resctrl_val_param *param);
+ int (*measure)(const struct user_params *uparams,
+ struct resctrl_val_param *param,
+ pid_t bm_pid);
+ struct fill_buf_param *fill_buf;
};
-pid_t bm_pid, ppid;
-int tests_run;
+struct perf_event_read {
+ __u64 nr; /* The number of events */
+ struct {
+ __u64 value; /* The value of the event */
+ } values[2];
+};
+
+/*
+ * Memory location that consumes values compiler must not optimize away.
+ * Volatile ensures writes to this location cannot be optimized away by
+ * compiler.
+ */
+extern volatile int *value_sink;
+
+extern int snc_unreliable;
-char llc_occup_path[1024];
-bool is_amd;
+extern char llc_occup_path[1024];
+int snc_nodes_per_l3_cache(void);
+int get_vendor(void);
bool check_resctrlfs_support(void);
int filter_dmesg(void);
-int remount_resctrlfs(bool mum_resctrlfs);
-int get_resource_id(int cpu_no, int *resource_id);
+int get_domain_id(const char *resource, int cpu_no, int *domain_id);
+int mount_resctrlfs(void);
int umount_resctrlfs(void);
-int validate_bw_report_request(char *bw_report);
-bool validate_resctrl_feature_request(char *resctrl_val);
+bool resctrl_resource_exists(const char *resource);
+bool resctrl_mon_feature_exists(const char *resource, const char *feature);
+bool resource_info_file_exists(const char *resource, const char *file);
+bool test_resource_feature_check(const struct resctrl_test *test);
char *fgrep(FILE *inf, const char *str);
-int taskset_benchmark(pid_t bm_pid, int cpu_no);
-void run_benchmark(int signum, siginfo_t *info, void *ucontext);
-int write_schemata(char *ctrlgrp, char *schemata, int cpu_no,
- char *resctrl_val);
-int write_bm_pid_to_resctrl(pid_t bm_pid, char *ctrlgrp, char *mongrp,
- char *resctrl_val);
+int taskset_benchmark(pid_t bm_pid, int cpu_no, cpu_set_t *old_affinity);
+int taskset_restore(pid_t bm_pid, cpu_set_t *old_affinity);
+int write_schemata(const char *ctrlgrp, char *schemata, int cpu_no,
+ const char *resource);
+int write_bm_pid_to_resctrl(pid_t bm_pid, const char *ctrlgrp, const char *mongrp);
int perf_event_open(struct perf_event_attr *hw_event, pid_t pid, int cpu,
int group_fd, unsigned long flags);
-int run_fill_buf(unsigned long span, int malloc_and_init_memory, int memflush,
- int op, char *resctrl_va);
-int resctrl_val(char **benchmark_cmd, struct resctrl_val_param *param);
-int mbm_bw_change(int span, int cpu_no, char *bw_report, char **benchmark_cmd);
-void tests_cleanup(void);
-void mbm_test_cleanup(void);
-int mba_schemata_change(int cpu_no, char *bw_report, char **benchmark_cmd);
-void mba_test_cleanup(void);
-int get_cbm_mask(char *cache_type);
-int get_cache_size(int cpu_no, char *cache_type, unsigned long *cache_size);
+unsigned char *alloc_buffer(size_t buf_size, bool memflush);
+void mem_flush(unsigned char *buf, size_t buf_size);
+void fill_cache_read(unsigned char *buf, size_t buf_size, bool once);
+ssize_t get_fill_buf_size(int cpu_no, const char *cache_type);
+int initialize_read_mem_bw_imc(void);
+int measure_read_mem_bw(const struct user_params *uparams,
+ struct resctrl_val_param *param, pid_t bm_pid);
+void initialize_mem_bw_resctrl(const struct resctrl_val_param *param,
+ int domain_id);
+int resctrl_val(const struct resctrl_test *test,
+ const struct user_params *uparams,
+ struct resctrl_val_param *param);
+unsigned long create_bit_mask(unsigned int start, unsigned int len);
+unsigned int count_contiguous_bits(unsigned long val, unsigned int *start);
+int get_full_cbm(const char *cache_type, unsigned long *mask);
+int get_mask_no_shareable(const char *cache_type, unsigned long *mask);
+int get_cache_size(int cpu_no, const char *cache_type, unsigned long *cache_size);
+int resource_info_unsigned_get(const char *resource, const char *filename, unsigned int *val);
void ctrlc_handler(int signum, siginfo_t *info, void *ptr);
-int cat_val(struct resctrl_val_param *param);
-void cat_test_cleanup(void);
-int cat_perf_miss_val(int cpu_no, int no_of_bits, char *cache_type);
-int cqm_resctrl_val(int cpu_no, int n, char **benchmark_cmd);
+int signal_handler_register(const struct resctrl_test *test);
+void signal_handler_unregister(void);
unsigned int count_bits(unsigned long n);
-void cqm_test_cleanup(void);
-int get_core_sibling(int cpu_no);
-int measure_cache_vals(struct resctrl_val_param *param, int bm_pid);
+int snc_kernel_support(void);
+
+void perf_event_attr_initialize(struct perf_event_attr *pea, __u64 config);
+void perf_event_initialize_read_format(struct perf_event_read *pe_read);
+int perf_open(struct perf_event_attr *pea, pid_t pid, int cpu_no);
+int perf_event_reset_enable(int pe_fd);
+int perf_event_measure(int pe_fd, struct perf_event_read *pe_read,
+ const char *filename, pid_t bm_pid);
+int measure_llc_resctrl(const char *filename, pid_t bm_pid);
+void show_cache_info(int no_of_bits, __u64 avg_llc_val, size_t cache_span, bool lines);
+
+/*
+ * cache_portion_size - Calculate the size of a cache portion
+ * @cache_size: Total cache size in bytes
+ * @portion_mask: Cache portion mask
+ * @full_cache_mask: Full Cache Bit Mask (CBM) for the cache
+ *
+ * Return: The size of the cache portion in bytes.
+ */
+static inline unsigned long cache_portion_size(unsigned long cache_size,
+ unsigned long portion_mask,
+ unsigned long full_cache_mask)
+{
+ unsigned int bits = count_bits(full_cache_mask);
+
+ /*
+ * With no bits the full CBM, assume cache cannot be split into
+ * smaller portions. To avoid divide by zero, return cache_size.
+ */
+ if (!bits)
+ return cache_size;
+
+ return cache_size * count_bits(portion_mask) / bits;
+}
+
+extern struct resctrl_test mbm_test;
+extern struct resctrl_test mba_test;
+extern struct resctrl_test cmt_test;
+extern struct resctrl_test l3_cat_test;
+extern struct resctrl_test l3_noncont_cat_test;
+extern struct resctrl_test l2_noncont_cat_test;
#endif /* RESCTRL_H */
diff --git a/tools/testing/selftests/resctrl/resctrl_tests.c b/tools/testing/selftests/resctrl/resctrl_tests.c
index 425cc85ac883..5154ffd821c4 100644
--- a/tools/testing/selftests/resctrl/resctrl_tests.c
+++ b/tools/testing/selftests/resctrl/resctrl_tests.c
@@ -10,102 +10,301 @@
*/
#include "resctrl.h"
-#define BENCHMARK_ARGS 64
-#define BENCHMARK_ARG_SIZE 64
+/* Volatile memory sink to prevent compiler optimizations */
+static volatile int sink_target;
+volatile int *value_sink = &sink_target;
-bool is_amd;
+static struct resctrl_test *resctrl_tests[] = {
+ &mbm_test,
+ &mba_test,
+ &cmt_test,
+ &l3_cat_test,
+ &l3_noncont_cat_test,
+ &l2_noncont_cat_test,
+};
-void detect_amd(void)
+static int detect_vendor(void)
{
FILE *inf = fopen("/proc/cpuinfo", "r");
+ int vendor_id = 0;
+ char *s = NULL;
char *res;
if (!inf)
- return;
+ return vendor_id;
res = fgrep(inf, "vendor_id");
- if (res) {
- char *s = strchr(res, ':');
+ if (res)
+ s = strchr(res, ':');
+
+ if (s && !strcmp(s, ": GenuineIntel\n"))
+ vendor_id = ARCH_INTEL;
+ else if (s && !strcmp(s, ": AuthenticAMD\n"))
+ vendor_id = ARCH_AMD;
- is_amd = s && !strcmp(s, ": AuthenticAMD\n");
- free(res);
- }
fclose(inf);
+ free(res);
+ return vendor_id;
+}
+
+int get_vendor(void)
+{
+ static int vendor = -1;
+
+ if (vendor == -1)
+ vendor = detect_vendor();
+ if (vendor == 0)
+ ksft_print_msg("Can not get vendor info...\n");
+
+ return vendor;
}
static void cmd_help(void)
{
- printf("usage: resctrl_tests [-h] [-b \"benchmark_cmd [options]\"] [-t test list] [-n no_of_bits]\n");
- printf("\t-b benchmark_cmd [options]: run specified benchmark for MBM, MBA and CQM");
- printf("\t default benchmark is builtin fill_buf\n");
- printf("\t-t test list: run tests specified in the test list, ");
- printf("e.g. -t mbm, mba, cqm, cat\n");
+ int i;
+
+ printf("usage: resctrl_tests [-h] [-t test list] [-n no_of_bits] [-b benchmark_cmd [option]...]\n");
+ printf("\t-b benchmark_cmd [option]...: run specified benchmark for MBM, MBA and CMT\n");
+ printf("\t default benchmark is builtin fill_buf\n");
+ printf("\t-t test list: run tests/groups specified by the list, ");
+ printf("e.g. -t mbm,mba,cmt,cat\n");
+ printf("\t\tSupported tests (group):\n");
+ for (i = 0; i < ARRAY_SIZE(resctrl_tests); i++) {
+ if (resctrl_tests[i]->group)
+ printf("\t\t\t%s (%s)\n", resctrl_tests[i]->name, resctrl_tests[i]->group);
+ else
+ printf("\t\t\t%s\n", resctrl_tests[i]->name);
+ }
printf("\t-n no_of_bits: run cache tests using specified no of bits in cache bit mask\n");
printf("\t-p cpu_no: specify CPU number to run the test. 1 is default\n");
printf("\t-h: help\n");
}
-void tests_cleanup(void)
+static int test_prepare(const struct resctrl_test *test)
+{
+ int res;
+
+ res = signal_handler_register(test);
+ if (res) {
+ ksft_print_msg("Failed to register signal handler\n");
+ return res;
+ }
+
+ res = mount_resctrlfs();
+ if (res) {
+ signal_handler_unregister();
+ ksft_print_msg("Failed to mount resctrl FS\n");
+ return res;
+ }
+ return 0;
+}
+
+static void test_cleanup(const struct resctrl_test *test)
{
- mbm_test_cleanup();
- mba_test_cleanup();
- cqm_test_cleanup();
- cat_test_cleanup();
+ if (test->cleanup)
+ test->cleanup();
+ umount_resctrlfs();
+ signal_handler_unregister();
}
-int main(int argc, char **argv)
+static bool test_vendor_specific_check(const struct resctrl_test *test)
{
- bool has_ben = false, mbm_test = true, mba_test = true, cqm_test = true;
- int res, c, cpu_no = 1, span = 250, argc_new = argc, i, no_of_bits = 5;
- char *benchmark_cmd[BENCHMARK_ARGS], bw_report[64], bm_type[64];
- char benchmark_cmd_area[BENCHMARK_ARGS][BENCHMARK_ARG_SIZE];
- int ben_ind, ben_count;
- bool cat_test = true;
-
- for (i = 0; i < argc; i++) {
- if (strcmp(argv[i], "-b") == 0) {
- ben_ind = i + 1;
- ben_count = argc - ben_ind;
- argc_new = ben_ind - 1;
- has_ben = true;
- break;
+ if (!test->vendor_specific)
+ return true;
+
+ return get_vendor() & test->vendor_specific;
+}
+
+static void run_single_test(const struct resctrl_test *test, const struct user_params *uparams)
+{
+ int ret, snc_mode;
+
+ if (test->disabled)
+ return;
+
+ if (!test_vendor_specific_check(test)) {
+ ksft_test_result_skip("Hardware does not support %s\n", test->name);
+ return;
+ }
+
+ snc_mode = snc_nodes_per_l3_cache();
+
+ ksft_print_msg("Starting %s test ...\n", test->name);
+
+ if (snc_mode == 1 && snc_unreliable && get_vendor() == ARCH_INTEL) {
+ ksft_test_result_skip("SNC detection unreliable due to offline CPUs. Test results may not be accurate if SNC enabled.\n");
+ return;
+ }
+
+ if (test_prepare(test)) {
+ ksft_exit_fail_msg("Abnormal failure when preparing for the test\n");
+ return;
+ }
+
+ if (!test->feature_check(test)) {
+ ksft_test_result_skip("Hardware does not support %s or %s is disabled\n",
+ test->name, test->name);
+ goto cleanup;
+ }
+
+ ret = test->run_test(test, uparams);
+ ksft_test_result(!ret, "%s: test\n", test->name);
+
+cleanup:
+ test_cleanup(test);
+}
+
+/*
+ * Allocate and initialize a struct fill_buf_param with user provided
+ * (via "-b fill_buf <fill_buf parameters>") parameters.
+ *
+ * Use defaults (that may not be appropriate for all tests) for any
+ * fill_buf parameters omitted by the user.
+ *
+ * Historically it may have been possible for user space to provide
+ * additional parameters, "operation" ("read" vs "write") in
+ * benchmark_cmd[3] and "once" (run "once" or until terminated) in
+ * benchmark_cmd[4]. Changing these parameters have never been
+ * supported with the default of "read" operation and running until
+ * terminated built into the tests. Any unsupported values for
+ * (original) "fill_buf" parameters are treated as failure.
+ *
+ * Return: On failure, forcibly exits the test on any parsing failure,
+ * returns NULL if no parsing needed (user did not actually provide
+ * "-b fill_buf").
+ * On success, returns pointer to newly allocated and fully
+ * initialized struct fill_buf_param that caller must free.
+ */
+static struct fill_buf_param *alloc_fill_buf_param(struct user_params *uparams)
+{
+ struct fill_buf_param *fill_param = NULL;
+ char *endptr = NULL;
+
+ if (!uparams->benchmark_cmd[0] || strcmp(uparams->benchmark_cmd[0], "fill_buf"))
+ return NULL;
+
+ fill_param = malloc(sizeof(*fill_param));
+ if (!fill_param)
+ ksft_exit_skip("Unable to allocate memory for fill_buf parameters.\n");
+
+ if (uparams->benchmark_cmd[1] && *uparams->benchmark_cmd[1] != '\0') {
+ errno = 0;
+ fill_param->buf_size = strtoul(uparams->benchmark_cmd[1], &endptr, 10);
+ if (errno || *endptr != '\0') {
+ free(fill_param);
+ ksft_exit_skip("Unable to parse benchmark buffer size.\n");
+ }
+ } else {
+ fill_param->buf_size = MINIMUM_SPAN;
+ }
+
+ if (uparams->benchmark_cmd[2] && *uparams->benchmark_cmd[2] != '\0') {
+ errno = 0;
+ fill_param->memflush = strtol(uparams->benchmark_cmd[2], &endptr, 10) != 0;
+ if (errno || *endptr != '\0') {
+ free(fill_param);
+ ksft_exit_skip("Unable to parse benchmark memflush parameter.\n");
+ }
+ } else {
+ fill_param->memflush = true;
+ }
+
+ if (uparams->benchmark_cmd[3] && *uparams->benchmark_cmd[3] != '\0') {
+ if (strcmp(uparams->benchmark_cmd[3], "0")) {
+ free(fill_param);
+ ksft_exit_skip("Only read operations supported.\n");
+ }
+ }
+
+ if (uparams->benchmark_cmd[4] && *uparams->benchmark_cmd[4] != '\0') {
+ if (strcmp(uparams->benchmark_cmd[4], "false")) {
+ free(fill_param);
+ ksft_exit_skip("fill_buf is required to run until termination.\n");
}
}
- while ((c = getopt(argc_new, argv, "ht:b:")) != -1) {
+ return fill_param;
+}
+
+static void init_user_params(struct user_params *uparams)
+{
+ memset(uparams, 0, sizeof(*uparams));
+
+ uparams->cpu = 1;
+ uparams->bits = 0;
+}
+
+int main(int argc, char **argv)
+{
+ struct fill_buf_param *fill_param = NULL;
+ int tests = ARRAY_SIZE(resctrl_tests);
+ bool test_param_seen = false;
+ struct user_params uparams;
+ int c, i;
+
+ init_user_params(&uparams);
+
+ while ((c = getopt(argc, argv, "ht:b:n:p:")) != -1) {
char *token;
switch (c) {
+ case 'b':
+ /*
+ * First move optind back to the (first) optarg and
+ * then build the benchmark command using the
+ * remaining arguments.
+ */
+ optind--;
+ if (argc - optind >= BENCHMARK_ARGS)
+ ksft_exit_fail_msg("Too long benchmark command");
+
+ /* Extract benchmark command from command line. */
+ for (i = 0; i < argc - optind; i++)
+ uparams.benchmark_cmd[i] = argv[i + optind];
+ uparams.benchmark_cmd[i] = NULL;
+
+ goto last_arg;
case 't':
token = strtok(optarg, ",");
- mbm_test = false;
- mba_test = false;
- cqm_test = false;
- cat_test = false;
+ if (!test_param_seen) {
+ for (i = 0; i < ARRAY_SIZE(resctrl_tests); i++)
+ resctrl_tests[i]->disabled = true;
+ tests = 0;
+ test_param_seen = true;
+ }
while (token) {
- if (!strcmp(token, "mbm")) {
- mbm_test = true;
- } else if (!strcmp(token, "mba")) {
- mba_test = true;
- } else if (!strcmp(token, "cqm")) {
- cqm_test = true;
- } else if (!strcmp(token, "cat")) {
- cat_test = true;
- } else {
- printf("invalid argument\n");
+ bool found = false;
+
+ for (i = 0; i < ARRAY_SIZE(resctrl_tests); i++) {
+ if (!strcasecmp(token, resctrl_tests[i]->name) ||
+ (resctrl_tests[i]->group &&
+ !strcasecmp(token, resctrl_tests[i]->group))) {
+ if (resctrl_tests[i]->disabled)
+ tests++;
+ resctrl_tests[i]->disabled = false;
+ found = true;
+ }
+ }
+
+ if (!found) {
+ printf("invalid test: %s\n", token);
return -1;
}
- token = strtok(NULL, ":\t");
+ token = strtok(NULL, ",");
}
break;
case 'p':
- cpu_no = atoi(optarg);
+ uparams.cpu = atoi(optarg);
break;
case 'n':
- no_of_bits = atoi(optarg);
+ uparams.bits = atoi(optarg);
+ if (uparams.bits <= 0) {
+ printf("Bail out! invalid argument for no_of_bits\n");
+ return -1;
+ }
break;
case 'h':
cmd_help();
@@ -117,8 +316,13 @@ int main(int argc, char **argv)
return -1;
}
}
+last_arg:
+
+ fill_param = alloc_fill_buf_param(&uparams);
+ if (fill_param)
+ uparams.fill_buf = fill_param;
- printf("TAP version 13\n");
+ ksft_print_header();
/*
* Typically we need root privileges, because:
@@ -126,77 +330,21 @@ int main(int argc, char **argv)
* 2. We execute perf commands
*/
if (geteuid() != 0)
- printf("# WARNING: not running as root, tests may fail.\n");
+ ksft_exit_skip("Not running as root. Skipping...\n");
- /* Detect AMD vendor */
- detect_amd();
+ if (!check_resctrlfs_support())
+ ksft_exit_skip("resctrl FS does not exist. Enable X86_CPU_RESCTRL config option.\n");
- if (has_ben) {
- /* Extract benchmark command from command line. */
- for (i = ben_ind; i < argc; i++) {
- benchmark_cmd[i - ben_ind] = benchmark_cmd_area[i];
- sprintf(benchmark_cmd[i - ben_ind], "%s", argv[i]);
- }
- benchmark_cmd[ben_count] = NULL;
- } else {
- /* If no benchmark is given by "-b" argument, use fill_buf. */
- for (i = 0; i < 6; i++)
- benchmark_cmd[i] = benchmark_cmd_area[i];
-
- strcpy(benchmark_cmd[0], "fill_buf");
- sprintf(benchmark_cmd[1], "%d", span);
- strcpy(benchmark_cmd[2], "1");
- strcpy(benchmark_cmd[3], "1");
- strcpy(benchmark_cmd[4], "0");
- strcpy(benchmark_cmd[5], "");
- benchmark_cmd[6] = NULL;
- }
+ if (umount_resctrlfs())
+ ksft_exit_skip("resctrl FS unmount failed.\n");
- sprintf(bw_report, "reads");
- sprintf(bm_type, "fill_buf");
-
- check_resctrlfs_support();
filter_dmesg();
- if (!is_amd && mbm_test) {
- printf("# Starting MBM BW change ...\n");
- if (!has_ben)
- sprintf(benchmark_cmd[5], "%s", "mba");
- res = mbm_bw_change(span, cpu_no, bw_report, benchmark_cmd);
- printf("%sok MBM: bw change\n", res ? "not " : "");
- mbm_test_cleanup();
- tests_run++;
- }
-
- if (!is_amd && mba_test) {
- printf("# Starting MBA Schemata change ...\n");
- if (!has_ben)
- sprintf(benchmark_cmd[1], "%d", span);
- res = mba_schemata_change(cpu_no, bw_report, benchmark_cmd);
- printf("%sok MBA: schemata change\n", res ? "not " : "");
- mba_test_cleanup();
- tests_run++;
- }
-
- if (cqm_test) {
- printf("# Starting CQM test ...\n");
- if (!has_ben)
- sprintf(benchmark_cmd[5], "%s", "cqm");
- res = cqm_resctrl_val(cpu_no, no_of_bits, benchmark_cmd);
- printf("%sok CQM: test\n", res ? "not " : "");
- cqm_test_cleanup();
- tests_run++;
- }
+ ksft_set_plan(tests);
- if (cat_test) {
- printf("# Starting CAT test ...\n");
- res = cat_perf_miss_val(cpu_no, no_of_bits, "L3");
- printf("%sok CAT: test\n", res ? "not " : "");
- tests_run++;
- cat_test_cleanup();
- }
-
- printf("1..%d\n", tests_run);
+ for (i = 0; i < ARRAY_SIZE(resctrl_tests); i++)
+ run_single_test(resctrl_tests[i], &uparams);
- return 0;
+ free(fill_param);
+ ksft_finished();
}
diff --git a/tools/testing/selftests/resctrl/resctrl_val.c b/tools/testing/selftests/resctrl/resctrl_val.c
index 520fea3606d1..7c08e936572d 100644
--- a/tools/testing/selftests/resctrl/resctrl_val.c
+++ b/tools/testing/selftests/resctrl/resctrl_val.c
@@ -12,37 +12,14 @@
#define UNCORE_IMC "uncore_imc"
#define READ_FILE_NAME "events/cas_count_read"
-#define WRITE_FILE_NAME "events/cas_count_write"
#define DYN_PMU_PATH "/sys/bus/event_source/devices"
#define SCALE 0.00006103515625
#define MAX_IMCS 20
#define MAX_TOKENS 5
-#define READ 0
-#define WRITE 1
-#define CON_MON_MBM_LOCAL_BYTES_PATH \
- "%s/%s/mon_groups/%s/mon_data/mon_L3_%02d/mbm_local_bytes"
#define CON_MBM_LOCAL_BYTES_PATH \
"%s/%s/mon_data/mon_L3_%02d/mbm_local_bytes"
-#define MON_MBM_LOCAL_BYTES_PATH \
- "%s/mon_groups/%s/mon_data/mon_L3_%02d/mbm_local_bytes"
-
-#define MBM_LOCAL_BYTES_PATH \
- "%s/mon_data/mon_L3_%02d/mbm_local_bytes"
-
-#define CON_MON_LCC_OCCUP_PATH \
- "%s/%s/mon_groups/%s/mon_data/mon_L3_%02d/llc_occupancy"
-
-#define CON_LCC_OCCUP_PATH \
- "%s/%s/mon_data/mon_L3_%02d/llc_occupancy"
-
-#define MON_LCC_OCCUP_PATH \
- "%s/mon_groups/%s/mon_data/mon_L3_%02d/llc_occupancy"
-
-#define LCC_OCCUP_PATH \
- "%s/mon_data/mon_L3_%02d/llc_occupancy"
-
struct membw_read_format {
__u64 value; /* The value of the event */
__u64 time_enabled; /* if PERF_FORMAT_TOTAL_TIME_ENABLED */
@@ -61,84 +38,71 @@ struct imc_counter_config {
static char mbm_total_path[1024];
static int imcs;
-static struct imc_counter_config imc_counters_config[MAX_IMCS][2];
+static struct imc_counter_config imc_counters_config[MAX_IMCS];
+static const struct resctrl_test *current_test;
-void membw_initialize_perf_event_attr(int i, int j)
+static void read_mem_bw_initialize_perf_event_attr(int i)
{
- memset(&imc_counters_config[i][j].pe, 0,
+ memset(&imc_counters_config[i].pe, 0,
sizeof(struct perf_event_attr));
- imc_counters_config[i][j].pe.type = imc_counters_config[i][j].type;
- imc_counters_config[i][j].pe.size = sizeof(struct perf_event_attr);
- imc_counters_config[i][j].pe.disabled = 1;
- imc_counters_config[i][j].pe.inherit = 1;
- imc_counters_config[i][j].pe.exclude_guest = 0;
- imc_counters_config[i][j].pe.config =
- imc_counters_config[i][j].umask << 8 |
- imc_counters_config[i][j].event;
- imc_counters_config[i][j].pe.sample_type = PERF_SAMPLE_IDENTIFIER;
- imc_counters_config[i][j].pe.read_format =
+ imc_counters_config[i].pe.type = imc_counters_config[i].type;
+ imc_counters_config[i].pe.size = sizeof(struct perf_event_attr);
+ imc_counters_config[i].pe.disabled = 1;
+ imc_counters_config[i].pe.inherit = 1;
+ imc_counters_config[i].pe.exclude_guest = 0;
+ imc_counters_config[i].pe.config =
+ imc_counters_config[i].umask << 8 |
+ imc_counters_config[i].event;
+ imc_counters_config[i].pe.sample_type = PERF_SAMPLE_IDENTIFIER;
+ imc_counters_config[i].pe.read_format =
PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING;
}
-void membw_ioctl_perf_event_ioc_reset_enable(int i, int j)
+static void read_mem_bw_ioctl_perf_event_ioc_reset_enable(int i)
{
- ioctl(imc_counters_config[i][j].fd, PERF_EVENT_IOC_RESET, 0);
- ioctl(imc_counters_config[i][j].fd, PERF_EVENT_IOC_ENABLE, 0);
+ ioctl(imc_counters_config[i].fd, PERF_EVENT_IOC_RESET, 0);
+ ioctl(imc_counters_config[i].fd, PERF_EVENT_IOC_ENABLE, 0);
}
-void membw_ioctl_perf_event_ioc_disable(int i, int j)
+static void read_mem_bw_ioctl_perf_event_ioc_disable(int i)
{
- ioctl(imc_counters_config[i][j].fd, PERF_EVENT_IOC_DISABLE, 0);
+ ioctl(imc_counters_config[i].fd, PERF_EVENT_IOC_DISABLE, 0);
}
/*
- * get_event_and_umask: Parse config into event and umask
+ * get_read_event_and_umask: Parse config into event and umask
* @cas_count_cfg: Config
* @count: iMC number
- * @op: Operation (read/write)
*/
-void get_event_and_umask(char *cas_count_cfg, int count, bool op)
+static void get_read_event_and_umask(char *cas_count_cfg, int count)
{
char *token[MAX_TOKENS];
int i = 0;
- strcat(cas_count_cfg, ",");
token[0] = strtok(cas_count_cfg, "=,");
for (i = 1; i < MAX_TOKENS; i++)
token[i] = strtok(NULL, "=,");
- for (i = 0; i < MAX_TOKENS; i++) {
+ for (i = 0; i < MAX_TOKENS - 1; i++) {
if (!token[i])
break;
- if (strcmp(token[i], "event") == 0) {
- if (op == READ)
- imc_counters_config[count][READ].event =
- strtol(token[i + 1], NULL, 16);
- else
- imc_counters_config[count][WRITE].event =
- strtol(token[i + 1], NULL, 16);
- }
- if (strcmp(token[i], "umask") == 0) {
- if (op == READ)
- imc_counters_config[count][READ].umask =
- strtol(token[i + 1], NULL, 16);
- else
- imc_counters_config[count][WRITE].umask =
- strtol(token[i + 1], NULL, 16);
- }
+ if (strcmp(token[i], "event") == 0)
+ imc_counters_config[count].event = strtol(token[i + 1], NULL, 16);
+ if (strcmp(token[i], "umask") == 0)
+ imc_counters_config[count].umask = strtol(token[i + 1], NULL, 16);
}
}
-static int open_perf_event(int i, int cpu_no, int j)
+static int open_perf_read_event(int i, int cpu_no)
{
- imc_counters_config[i][j].fd =
- perf_event_open(&imc_counters_config[i][j].pe, -1, cpu_no, -1,
+ imc_counters_config[i].fd =
+ perf_event_open(&imc_counters_config[i].pe, -1, cpu_no, -1,
PERF_FLAG_FD_CLOEXEC);
- if (imc_counters_config[i][j].fd == -1) {
+ if (imc_counters_config[i].fd == -1) {
fprintf(stderr, "Error opening leader %llx\n",
- imc_counters_config[i][j].pe.config);
+ imc_counters_config[i].pe.config);
return -1;
}
@@ -146,7 +110,7 @@ static int open_perf_event(int i, int cpu_no, int j)
return 0;
}
-/* Get type and config (read and write) of an iMC counter */
+/* Get type and config of an iMC counter's read event. */
static int read_from_imc_dir(char *imc_dir, int count)
{
char cas_count_cfg[1024], imc_counter_cfg[1024], imc_counter_type[1024];
@@ -156,73 +120,55 @@ static int read_from_imc_dir(char *imc_dir, int count)
sprintf(imc_counter_type, "%s%s", imc_dir, "type");
fp = fopen(imc_counter_type, "r");
if (!fp) {
- perror("Failed to open imc counter type file");
+ ksft_perror("Failed to open iMC counter type file");
return -1;
}
- if (fscanf(fp, "%u", &imc_counters_config[count][READ].type) <= 0) {
- perror("Could not get imc type");
+ if (fscanf(fp, "%u", &imc_counters_config[count].type) <= 0) {
+ ksft_perror("Could not get iMC type");
fclose(fp);
return -1;
}
fclose(fp);
- imc_counters_config[count][WRITE].type =
- imc_counters_config[count][READ].type;
-
/* Get read config */
sprintf(imc_counter_cfg, "%s%s", imc_dir, READ_FILE_NAME);
fp = fopen(imc_counter_cfg, "r");
if (!fp) {
- perror("Failed to open imc config file");
+ ksft_perror("Failed to open iMC config file");
return -1;
}
- if (fscanf(fp, "%s", cas_count_cfg) <= 0) {
- perror("Could not get imc cas count read");
+ if (fscanf(fp, "%1023s", cas_count_cfg) <= 0) {
+ ksft_perror("Could not get iMC cas count read");
fclose(fp);
return -1;
}
fclose(fp);
- get_event_and_umask(cas_count_cfg, count, READ);
-
- /* Get write config */
- sprintf(imc_counter_cfg, "%s%s", imc_dir, WRITE_FILE_NAME);
- fp = fopen(imc_counter_cfg, "r");
- if (!fp) {
- perror("Failed to open imc config file");
-
- return -1;
- }
- if (fscanf(fp, "%s", cas_count_cfg) <= 0) {
- perror("Could not get imc cas count write");
- fclose(fp);
-
- return -1;
- }
- fclose(fp);
-
- get_event_and_umask(cas_count_cfg, count, WRITE);
+ get_read_event_and_umask(cas_count_cfg, count);
return 0;
}
/*
* A system can have 'n' number of iMC (Integrated Memory Controller)
- * counters, get that 'n'. For each iMC counter get it's type and config.
- * Also, each counter has two configs, one for read and the other for write.
- * A config again has two parts, event and umask.
+ * counters, get that 'n'. Discover the properties of the available
+ * counters in support of needed performance measurement via perf.
+ * For each iMC counter get it's type and config. Also obtain each
+ * counter's event and umask for the memory read events that will be
+ * measured.
+ *
* Enumerate all these details into an array of structures.
*
* Return: >= 0 on success. < 0 on failure.
*/
static int num_of_imcs(void)
{
+ char imc_dir[512], *temp;
unsigned int count = 0;
- char imc_dir[512];
struct dirent *ep;
int ret;
DIR *dp;
@@ -230,7 +176,25 @@ static int num_of_imcs(void)
dp = opendir(DYN_PMU_PATH);
if (dp) {
while ((ep = readdir(dp))) {
- if (strstr(ep->d_name, UNCORE_IMC)) {
+ temp = strstr(ep->d_name, UNCORE_IMC);
+ if (!temp)
+ continue;
+
+ /*
+ * imc counters are named as "uncore_imc_<n>", hence
+ * increment the pointer to point to <n>. Note that
+ * sizeof(UNCORE_IMC) would count for null character as
+ * well and hence the last underscore character in
+ * uncore_imc'_' need not be counted.
+ */
+ temp = temp + sizeof(UNCORE_IMC);
+
+ /*
+ * Some directories under "DYN_PMU_PATH" could have
+ * names like "uncore_imc_free_running", hence, check if
+ * first character is a numerical digit or not.
+ */
+ if (temp[0] >= '0' && temp[0] <= '9') {
sprintf(imc_dir, "%s/%s/", DYN_PMU_PATH,
ep->d_name);
ret = read_from_imc_dir(imc_dir, count);
@@ -244,12 +208,12 @@ static int num_of_imcs(void)
}
closedir(dp);
if (count == 0) {
- perror("Unable find iMC counters!\n");
+ ksft_print_msg("Unable to find iMC counters\n");
return -1;
}
} else {
- perror("Unable to open PMU directory!\n");
+ ksft_perror("Unable to open PMU directory");
return -1;
}
@@ -257,79 +221,103 @@ static int num_of_imcs(void)
return count;
}
-static int initialize_mem_bw_imc(void)
+int initialize_read_mem_bw_imc(void)
{
- int imc, j;
+ int imc;
imcs = num_of_imcs();
if (imcs <= 0)
return imcs;
/* Initialize perf_event_attr structures for all iMC's */
- for (imc = 0; imc < imcs; imc++) {
- for (j = 0; j < 2; j++)
- membw_initialize_perf_event_attr(imc, j);
- }
+ for (imc = 0; imc < imcs; imc++)
+ read_mem_bw_initialize_perf_event_attr(imc);
return 0;
}
+static void perf_close_imc_read_mem_bw(void)
+{
+ int mc;
+
+ for (mc = 0; mc < imcs; mc++) {
+ if (imc_counters_config[mc].fd != -1)
+ close(imc_counters_config[mc].fd);
+ }
+}
+
/*
- * get_mem_bw_imc: Memory band width as reported by iMC counters
- * @cpu_no: CPU number that the benchmark PID is binded to
- * @bw_report: Bandwidth report type (reads, writes)
+ * perf_open_imc_read_mem_bw - Open perf fds for IMCs
+ * @cpu_no: CPU number that the benchmark PID is bound to
*
- * Memory B/W utilized by a process on a socket can be calculated using
- * iMC counters. Perf events are used to read these counters.
- *
- * Return: >= 0 on success. < 0 on failure.
+ * Return: = 0 on success. < 0 on failure.
*/
-static float get_mem_bw_imc(int cpu_no, char *bw_report)
+static int perf_open_imc_read_mem_bw(int cpu_no)
{
- float reads, writes, of_mul_read, of_mul_write;
- int imc, j, ret;
+ int imc, ret;
+
+ for (imc = 0; imc < imcs; imc++)
+ imc_counters_config[imc].fd = -1;
- /* Start all iMC counters to log values (both read and write) */
- reads = 0, writes = 0, of_mul_read = 1, of_mul_write = 1;
for (imc = 0; imc < imcs; imc++) {
- for (j = 0; j < 2; j++) {
- ret = open_perf_event(imc, cpu_no, j);
- if (ret)
- return -1;
- }
- for (j = 0; j < 2; j++)
- membw_ioctl_perf_event_ioc_reset_enable(imc, j);
+ ret = open_perf_read_event(imc, cpu_no);
+ if (ret)
+ goto close_fds;
}
+ return 0;
+
+close_fds:
+ perf_close_imc_read_mem_bw();
+ return -1;
+}
+
+/*
+ * do_imc_read_mem_bw_test - Perform memory bandwidth test
+ *
+ * Runs memory bandwidth test over one second period. Also, handles starting
+ * and stopping of the IMC perf counters around the test.
+ */
+static void do_imc_read_mem_bw_test(void)
+{
+ int imc;
+
+ for (imc = 0; imc < imcs; imc++)
+ read_mem_bw_ioctl_perf_event_ioc_reset_enable(imc);
+
sleep(1);
- /* Stop counters after a second to get results (both read and write) */
- for (imc = 0; imc < imcs; imc++) {
- for (j = 0; j < 2; j++)
- membw_ioctl_perf_event_ioc_disable(imc, j);
- }
+ /* Stop counters after a second to get results. */
+ for (imc = 0; imc < imcs; imc++)
+ read_mem_bw_ioctl_perf_event_ioc_disable(imc);
+}
+
+/*
+ * get_read_mem_bw_imc - Memory read bandwidth as reported by iMC counters
+ *
+ * Memory read bandwidth utilized by a process on a socket can be calculated
+ * using iMC counters' read events. Perf events are used to read these
+ * counters.
+ *
+ * Return: = 0 on success. < 0 on failure.
+ */
+static int get_read_mem_bw_imc(float *bw_imc)
+{
+ float reads = 0, of_mul_read = 1;
+ int imc;
/*
- * Get results which are stored in struct type imc_counter_config
- * Take over flow into consideration before calculating total b/w
+ * Log read event values from all iMC counters into
+ * struct imc_counter_config.
+ * Take overflow into consideration before calculating total bandwidth.
*/
for (imc = 0; imc < imcs; imc++) {
struct imc_counter_config *r =
- &imc_counters_config[imc][READ];
- struct imc_counter_config *w =
- &imc_counters_config[imc][WRITE];
+ &imc_counters_config[imc];
if (read(r->fd, &r->return_value,
sizeof(struct membw_read_format)) == -1) {
- perror("Couldn't get read b/w through iMC");
-
- return -1;
- }
-
- if (read(w->fd, &w->return_value,
- sizeof(struct membw_read_format)) == -1) {
- perror("Couldn't get write bw through iMC");
-
+ ksft_perror("Couldn't get read bandwidth through iMC");
return -1;
}
@@ -340,151 +328,140 @@ static float get_mem_bw_imc(int cpu_no, char *bw_report)
of_mul_read = (float)r_time_enabled /
(float)r_time_running;
- __u64 w_time_enabled = w->return_value.time_enabled;
- __u64 w_time_running = w->return_value.time_running;
-
- if (w_time_enabled != w_time_running)
- of_mul_write = (float)w_time_enabled /
- (float)w_time_running;
reads += r->return_value.value * of_mul_read * SCALE;
- writes += w->return_value.value * of_mul_write * SCALE;
- }
-
- for (imc = 0; imc < imcs; imc++) {
- close(imc_counters_config[imc][READ].fd);
- close(imc_counters_config[imc][WRITE].fd);
}
- if (strcmp(bw_report, "reads") == 0)
- return reads;
-
- if (strcmp(bw_report, "writes") == 0)
- return writes;
-
- return (reads + writes);
+ *bw_imc = reads;
+ return 0;
}
-void set_mbm_path(const char *ctrlgrp, const char *mongrp, int resource_id)
+/*
+ * initialize_mem_bw_resctrl: Appropriately populate "mbm_total_path"
+ * @param: Parameters passed to resctrl_val()
+ * @domain_id: Domain ID (cache ID; for MB, L3 cache ID)
+ */
+void initialize_mem_bw_resctrl(const struct resctrl_val_param *param,
+ int domain_id)
{
- if (ctrlgrp && mongrp)
- sprintf(mbm_total_path, CON_MON_MBM_LOCAL_BYTES_PATH,
- RESCTRL_PATH, ctrlgrp, mongrp, resource_id);
- else if (!ctrlgrp && mongrp)
- sprintf(mbm_total_path, MON_MBM_LOCAL_BYTES_PATH, RESCTRL_PATH,
- mongrp, resource_id);
- else if (ctrlgrp && !mongrp)
- sprintf(mbm_total_path, CON_MBM_LOCAL_BYTES_PATH, RESCTRL_PATH,
- ctrlgrp, resource_id);
- else if (!ctrlgrp && !mongrp)
- sprintf(mbm_total_path, MBM_LOCAL_BYTES_PATH, RESCTRL_PATH,
- resource_id);
+ sprintf(mbm_total_path, CON_MBM_LOCAL_BYTES_PATH, RESCTRL_PATH,
+ param->ctrlgrp, domain_id);
}
/*
- * initialize_mem_bw_resctrl: Appropriately populate "mbm_total_path"
- * @ctrlgrp: Name of the control monitor group (con_mon grp)
- * @mongrp: Name of the monitor group (mon grp)
- * @cpu_no: CPU number that the benchmark PID is binded to
- * @resctrl_val: Resctrl feature (Eg: mbm, mba.. etc)
+ * Open file to read MBM local bytes from resctrl FS
*/
-static void initialize_mem_bw_resctrl(const char *ctrlgrp, const char *mongrp,
- int cpu_no, char *resctrl_val)
+static FILE *open_mem_bw_resctrl(const char *mbm_bw_file)
{
- int resource_id;
-
- if (get_resource_id(cpu_no, &resource_id) < 0) {
- perror("Could not get resource_id");
- return;
- }
+ FILE *fp;
- if (strcmp(resctrl_val, "mbm") == 0)
- set_mbm_path(ctrlgrp, mongrp, resource_id);
+ fp = fopen(mbm_bw_file, "r");
+ if (!fp)
+ ksft_perror("Failed to open total memory bandwidth file");
- if ((strcmp(resctrl_val, "mba") == 0)) {
- if (ctrlgrp)
- sprintf(mbm_total_path, CON_MBM_LOCAL_BYTES_PATH,
- RESCTRL_PATH, ctrlgrp, resource_id);
- else
- sprintf(mbm_total_path, MBM_LOCAL_BYTES_PATH,
- RESCTRL_PATH, resource_id);
- }
+ return fp;
}
/*
* Get MBM Local bytes as reported by resctrl FS
- * For MBM,
- * 1. If con_mon grp and mon grp are given, then read from con_mon grp's mon grp
- * 2. If only con_mon grp is given, then read from con_mon grp
- * 3. If both are not given, then read from root con_mon grp
- * For MBA,
- * 1. If con_mon grp is given, then read from it
- * 2. If con_mon grp is not given, then read from root con_mon grp
*/
-static unsigned long get_mem_bw_resctrl(void)
+static int get_mem_bw_resctrl(FILE *fp, unsigned long *mbm_total)
{
- unsigned long mbm_total = 0;
- FILE *fp;
-
- fp = fopen(mbm_total_path, "r");
- if (!fp) {
- perror("Failed to open total bw file");
-
- return -1;
- }
- if (fscanf(fp, "%lu", &mbm_total) <= 0) {
- perror("Could not get mbm local bytes");
- fclose(fp);
-
+ if (fscanf(fp, "%lu\n", mbm_total) <= 0) {
+ ksft_perror("Could not get MBM local bytes");
return -1;
}
- fclose(fp);
-
- return mbm_total;
+ return 0;
}
-pid_t bm_pid, ppid;
+static pid_t bm_pid;
void ctrlc_handler(int signum, siginfo_t *info, void *ptr)
{
- kill(bm_pid, SIGKILL);
+ /* Only kill child after bm_pid is set after fork() */
+ if (bm_pid)
+ kill(bm_pid, SIGKILL);
umount_resctrlfs();
- tests_cleanup();
- printf("Ending\n\n");
+ if (current_test && current_test->cleanup)
+ current_test->cleanup();
+ ksft_print_msg("Ending\n\n");
exit(EXIT_SUCCESS);
}
/*
+ * Register CTRL-C handler for parent, as it has to kill
+ * child process before exiting.
+ */
+int signal_handler_register(const struct resctrl_test *test)
+{
+ struct sigaction sigact = {};
+ int ret = 0;
+
+ bm_pid = 0;
+
+ current_test = test;
+ sigact.sa_sigaction = ctrlc_handler;
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGINT, &sigact, NULL) ||
+ sigaction(SIGTERM, &sigact, NULL) ||
+ sigaction(SIGHUP, &sigact, NULL)) {
+ ksft_perror("sigaction");
+ ret = -1;
+ }
+ return ret;
+}
+
+/*
+ * Reset signal handler to SIG_DFL.
+ * Non-Value return because the caller should keep
+ * the error code of other path even if sigaction fails.
+ */
+void signal_handler_unregister(void)
+{
+ struct sigaction sigact = {};
+
+ current_test = NULL;
+ sigact.sa_handler = SIG_DFL;
+ sigemptyset(&sigact.sa_mask);
+ if (sigaction(SIGINT, &sigact, NULL) ||
+ sigaction(SIGTERM, &sigact, NULL) ||
+ sigaction(SIGHUP, &sigact, NULL)) {
+ ksft_perror("sigaction");
+ }
+}
+
+/*
* print_results_bw: the memory bandwidth results are stored in a file
* @filename: file that stores the results
* @bm_pid: child pid that runs benchmark
* @bw_imc: perf imc counter value
* @bw_resc: memory bandwidth value
*
- * Return: 0 on success. non-zero on failure.
+ * Return: 0 on success, < 0 on error.
*/
-static int print_results_bw(char *filename, int bm_pid, float bw_imc,
+static int print_results_bw(char *filename, pid_t bm_pid, float bw_imc,
unsigned long bw_resc)
{
unsigned long diff = fabs(bw_imc - bw_resc);
FILE *fp;
if (strcmp(filename, "stdio") == 0 || strcmp(filename, "stderr") == 0) {
- printf("Pid: %d \t Mem_BW_iMC: %f \t ", bm_pid, bw_imc);
+ printf("Pid: %d \t Mem_BW_iMC: %f \t ", (int)bm_pid, bw_imc);
printf("Mem_BW_resc: %lu \t Difference: %lu\n", bw_resc, diff);
} else {
fp = fopen(filename, "a");
if (!fp) {
- perror("Cannot open results file");
+ ksft_perror("Cannot open results file");
- return errno;
+ return -1;
}
if (fprintf(fp, "Pid: %d \t Mem_BW_iMC: %f \t Mem_BW_resc: %lu \t Difference: %lu\n",
- bm_pid, bw_imc, bw_resc, diff) <= 0) {
+ (int)bm_pid, bw_imc, bw_resc, diff) <= 0) {
+ ksft_print_msg("Could not log results\n");
fclose(fp);
- perror("Could not log results.");
- return errno;
+ return -1;
}
fclose(fp);
}
@@ -492,253 +469,173 @@ static int print_results_bw(char *filename, int bm_pid, float bw_imc,
return 0;
}
-static void set_cqm_path(const char *ctrlgrp, const char *mongrp, char sock_num)
-{
- if (strlen(ctrlgrp) && strlen(mongrp))
- sprintf(llc_occup_path, CON_MON_LCC_OCCUP_PATH, RESCTRL_PATH,
- ctrlgrp, mongrp, sock_num);
- else if (!strlen(ctrlgrp) && strlen(mongrp))
- sprintf(llc_occup_path, MON_LCC_OCCUP_PATH, RESCTRL_PATH,
- mongrp, sock_num);
- else if (strlen(ctrlgrp) && !strlen(mongrp))
- sprintf(llc_occup_path, CON_LCC_OCCUP_PATH, RESCTRL_PATH,
- ctrlgrp, sock_num);
- else if (!strlen(ctrlgrp) && !strlen(mongrp))
- sprintf(llc_occup_path, LCC_OCCUP_PATH, RESCTRL_PATH, sock_num);
-}
-
/*
- * initialize_llc_occu_resctrl: Appropriately populate "llc_occup_path"
- * @ctrlgrp: Name of the control monitor group (con_mon grp)
- * @mongrp: Name of the monitor group (mon grp)
- * @cpu_no: CPU number that the benchmark PID is binded to
- * @resctrl_val: Resctrl feature (Eg: cat, cqm.. etc)
+ * measure_read_mem_bw - Measures read memory bandwidth numbers while benchmark runs
+ * @uparams: User supplied parameters
+ * @param: Parameters passed to resctrl_val()
+ * @bm_pid: PID that runs the benchmark
+ *
+ * Measure memory bandwidth from resctrl and from another source which is
+ * perf imc value or could be something else if perf imc event is not
+ * available. Compare the two values to validate resctrl value. It takes
+ * 1 sec to measure the data.
+ * resctrl does not distinguish between read and write operations so
+ * its data includes all memory operations.
*/
-static void initialize_llc_occu_resctrl(const char *ctrlgrp, const char *mongrp,
- int cpu_no, char *resctrl_val)
+int measure_read_mem_bw(const struct user_params *uparams,
+ struct resctrl_val_param *param, pid_t bm_pid)
{
- int resource_id;
+ unsigned long bw_resc, bw_resc_start, bw_resc_end;
+ FILE *mem_bw_fp;
+ float bw_imc;
+ int ret;
- if (get_resource_id(cpu_no, &resource_id) < 0) {
- perror("# Unable to resource_id");
- return;
- }
+ mem_bw_fp = open_mem_bw_resctrl(mbm_total_path);
+ if (!mem_bw_fp)
+ return -1;
- if (strcmp(resctrl_val, "cqm") == 0)
- set_cqm_path(ctrlgrp, mongrp, resource_id);
-}
+ ret = perf_open_imc_read_mem_bw(uparams->cpu);
+ if (ret < 0)
+ goto close_fp;
-static int
-measure_vals(struct resctrl_val_param *param, unsigned long *bw_resc_start)
-{
- unsigned long bw_imc, bw_resc, bw_resc_end;
- int ret;
+ ret = get_mem_bw_resctrl(mem_bw_fp, &bw_resc_start);
+ if (ret < 0)
+ goto close_imc;
- /*
- * Measure memory bandwidth from resctrl and from
- * another source which is perf imc value or could
- * be something else if perf imc event is not available.
- * Compare the two values to validate resctrl value.
- * It takes 1sec to measure the data.
- */
- bw_imc = get_mem_bw_imc(param->cpu_no, param->bw_report);
- if (bw_imc <= 0)
- return bw_imc;
+ rewind(mem_bw_fp);
- bw_resc_end = get_mem_bw_resctrl();
- if (bw_resc_end <= 0)
- return bw_resc_end;
+ do_imc_read_mem_bw_test();
- bw_resc = (bw_resc_end - *bw_resc_start) / MB;
- ret = print_results_bw(param->filename, bm_pid, bw_imc, bw_resc);
- if (ret)
- return ret;
+ ret = get_mem_bw_resctrl(mem_bw_fp, &bw_resc_end);
+ if (ret < 0)
+ goto close_imc;
- *bw_resc_start = bw_resc_end;
+ ret = get_read_mem_bw_imc(&bw_imc);
+ if (ret < 0)
+ goto close_imc;
- return 0;
+ perf_close_imc_read_mem_bw();
+ fclose(mem_bw_fp);
+
+ bw_resc = (bw_resc_end - bw_resc_start) / MB;
+
+ return print_results_bw(param->filename, bm_pid, bw_imc, bw_resc);
+
+close_imc:
+ perf_close_imc_read_mem_bw();
+close_fp:
+ fclose(mem_bw_fp);
+ return ret;
}
/*
* resctrl_val: execute benchmark and measure memory bandwidth on
* the benchmark
- * @benchmark_cmd: benchmark command and its arguments
+ * @test: test information structure
+ * @uparams: user supplied parameters
* @param: parameters passed to resctrl_val()
*
- * Return: 0 on success. non-zero on failure.
+ * Return: 0 when the test was run, < 0 on error.
*/
-int resctrl_val(char **benchmark_cmd, struct resctrl_val_param *param)
+int resctrl_val(const struct resctrl_test *test,
+ const struct user_params *uparams,
+ struct resctrl_val_param *param)
{
- char *resctrl_val = param->resctrl_val;
- unsigned long bw_resc_start = 0;
- struct sigaction sigact;
- int ret = 0, pipefd[2];
- char pipe_message = 0;
- union sigval value;
+ unsigned char *buf = NULL;
+ cpu_set_t old_affinity;
+ int domain_id;
+ int ret = 0;
+ pid_t ppid;
if (strcmp(param->filename, "") == 0)
sprintf(param->filename, "stdio");
- if ((strcmp(resctrl_val, "mba")) == 0 ||
- (strcmp(resctrl_val, "mbm")) == 0) {
- ret = validate_bw_report_request(param->bw_report);
- if (ret)
- return ret;
+ ret = get_domain_id(test->resource, uparams->cpu, &domain_id);
+ if (ret < 0) {
+ ksft_print_msg("Could not get domain ID\n");
+ return ret;
}
- ret = remount_resctrlfs(param->mum_resctrlfs);
+ ppid = getpid();
+
+ /* Taskset test to specified CPU. */
+ ret = taskset_benchmark(ppid, uparams->cpu, &old_affinity);
if (ret)
return ret;
- /*
- * If benchmark wasn't successfully started by child, then child should
- * kill parent, so save parent's pid
- */
- ppid = getpid();
-
- if (pipe(pipefd)) {
- perror("# Unable to create pipe");
+ /* Write test to specified control & monitoring group in resctrl FS. */
+ ret = write_bm_pid_to_resctrl(ppid, param->ctrlgrp, param->mongrp);
+ if (ret)
+ goto reset_affinity;
- return -1;
+ if (param->init) {
+ ret = param->init(param, domain_id);
+ if (ret)
+ goto reset_affinity;
}
/*
- * Fork to start benchmark, save child's pid so that it can be killed
- * when needed
+ * If not running user provided benchmark, run the default
+ * "fill_buf". First phase of "fill_buf" is to prepare the
+ * buffer that the benchmark will operate on. No measurements
+ * are needed during this phase and prepared memory will be
+ * passed to next part of benchmark via copy-on-write thus
+ * no impact on the benchmark that relies on reading from
+ * memory only.
*/
- bm_pid = fork();
- if (bm_pid == -1) {
- perror("# Unable to fork");
-
- return -1;
- }
-
- if (bm_pid == 0) {
- /*
- * Mask all signals except SIGUSR1, parent uses SIGUSR1 to
- * start benchmark
- */
- sigfillset(&sigact.sa_mask);
- sigdelset(&sigact.sa_mask, SIGUSR1);
-
- sigact.sa_sigaction = run_benchmark;
- sigact.sa_flags = SA_SIGINFO;
-
- /* Register for "SIGUSR1" signal from parent */
- if (sigaction(SIGUSR1, &sigact, NULL))
- PARENT_EXIT("Can't register child for signal");
-
- /* Tell parent that child is ready */
- close(pipefd[0]);
- pipe_message = 1;
- if (write(pipefd[1], &pipe_message, sizeof(pipe_message)) <
- sizeof(pipe_message)) {
- perror("# failed signaling parent process");
- close(pipefd[1]);
- return -1;
+ if (param->fill_buf) {
+ buf = alloc_buffer(param->fill_buf->buf_size,
+ param->fill_buf->memflush);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto reset_affinity;
}
- close(pipefd[1]);
-
- /* Suspend child until delivery of "SIGUSR1" from parent */
- sigsuspend(&sigact.sa_mask);
-
- PARENT_EXIT("Child is done");
}
- printf("# benchmark PID: %d\n", bm_pid);
+ fflush(stdout);
+ bm_pid = fork();
+ if (bm_pid == -1) {
+ ret = -errno;
+ ksft_perror("Unable to fork");
+ goto free_buf;
+ }
/*
- * Register CTRL-C handler for parent, as it has to kill benchmark
- * before exiting
+ * What needs to be measured runs in separate process until
+ * terminated.
*/
- sigact.sa_sigaction = ctrlc_handler;
- sigemptyset(&sigact.sa_mask);
- sigact.sa_flags = SA_SIGINFO;
- if (sigaction(SIGINT, &sigact, NULL) ||
- sigaction(SIGHUP, &sigact, NULL)) {
- perror("# sigaction");
- ret = errno;
- goto out;
- }
-
- value.sival_ptr = benchmark_cmd;
-
- /* Taskset benchmark to specified cpu */
- ret = taskset_benchmark(bm_pid, param->cpu_no);
- if (ret)
- goto out;
-
- /* Write benchmark to specified control&monitoring grp in resctrl FS */
- ret = write_bm_pid_to_resctrl(bm_pid, param->ctrlgrp, param->mongrp,
- resctrl_val);
- if (ret)
- goto out;
-
- if ((strcmp(resctrl_val, "mbm") == 0) ||
- (strcmp(resctrl_val, "mba") == 0)) {
- ret = initialize_mem_bw_imc();
- if (ret)
- goto out;
-
- initialize_mem_bw_resctrl(param->ctrlgrp, param->mongrp,
- param->cpu_no, resctrl_val);
- } else if (strcmp(resctrl_val, "cqm") == 0)
- initialize_llc_occu_resctrl(param->ctrlgrp, param->mongrp,
- param->cpu_no, resctrl_val);
-
- /* Parent waits for child to be ready. */
- close(pipefd[1]);
- while (pipe_message != 1) {
- if (read(pipefd[0], &pipe_message, sizeof(pipe_message)) <
- sizeof(pipe_message)) {
- perror("# failed reading message from child process");
- close(pipefd[0]);
- goto out;
- }
+ if (bm_pid == 0) {
+ if (param->fill_buf)
+ fill_cache_read(buf, param->fill_buf->buf_size, false);
+ else if (uparams->benchmark_cmd[0])
+ execvp(uparams->benchmark_cmd[0], (char **)uparams->benchmark_cmd);
+ exit(EXIT_SUCCESS);
}
- close(pipefd[0]);
- /* Signal child to start benchmark */
- if (sigqueue(bm_pid, SIGUSR1, value) == -1) {
- perror("# sigqueue SIGUSR1 to child");
- ret = errno;
- goto out;
- }
+ ksft_print_msg("Benchmark PID: %d\n", (int)bm_pid);
- /* Give benchmark enough time to fully run */
+ /* Give benchmark enough time to fully run. */
sleep(1);
/* Test runs until the callback setup() tells the test to stop. */
while (1) {
- if ((strcmp(resctrl_val, "mbm") == 0) ||
- (strcmp(resctrl_val, "mba") == 0)) {
- ret = param->setup(1, param);
- if (ret) {
- ret = 0;
- break;
- }
-
- ret = measure_vals(param, &bw_resc_start);
- if (ret)
- break;
- } else if (strcmp(resctrl_val, "cqm") == 0) {
- ret = param->setup(1, param);
- if (ret) {
- ret = 0;
- break;
- }
- sleep(1);
- ret = measure_cache_vals(param, bm_pid);
- if (ret)
- break;
- } else {
+ ret = param->setup(test, uparams, param);
+ if (ret == END_OF_TESTS) {
+ ret = 0;
break;
}
+ if (ret < 0)
+ break;
+
+ ret = param->measure(uparams, param, bm_pid);
+ if (ret)
+ break;
}
-out:
kill(bm_pid, SIGKILL);
- umount_resctrlfs();
-
+free_buf:
+ free(buf);
+reset_affinity:
+ taskset_restore(ppid, &old_affinity);
return ret;
}
diff --git a/tools/testing/selftests/resctrl/resctrlfs.c b/tools/testing/selftests/resctrl/resctrlfs.c
index 19c0ec4045a4..195f04c4d158 100644
--- a/tools/testing/selftests/resctrl/resctrlfs.c
+++ b/tools/testing/selftests/resctrl/resctrlfs.c
@@ -8,9 +8,12 @@
* Sai Praneeth Prakhya <sai.praneeth.prakhya@intel.com>,
* Fenghua Yu <fenghua.yu@intel.com>
*/
+#include <fcntl.h>
+#include <limits.h>
+
#include "resctrl.h"
-int tests_run;
+int snc_unreliable;
static int find_resctrl_mount(char *buffer)
{
@@ -19,7 +22,7 @@ static int find_resctrl_mount(char *buffer)
mounts = fopen("/proc/mounts", "r");
if (!mounts) {
- perror("/proc/mounts");
+ ksft_perror("/proc/mounts");
return -ENXIO;
}
while (!feof(mounts)) {
@@ -49,85 +52,103 @@ static int find_resctrl_mount(char *buffer)
return -ENOENT;
}
-char cbm_mask[256];
-
/*
- * remount_resctrlfs - Remount resctrl FS at /sys/fs/resctrl
- * @mum_resctrlfs: Should the resctrl FS be remounted?
+ * mount_resctrlfs - Mount resctrl FS at /sys/fs/resctrl
*
- * If not mounted, mount it.
- * If mounted and mum_resctrlfs then remount resctrl FS.
- * If mounted and !mum_resctrlfs then noop
+ * Mounts resctrl FS. Fails if resctrl FS is already mounted to avoid
+ * pre-existing settings interfering with the test results.
*
- * Return: 0 on success, non-zero on failure
+ * Return: 0 on success, < 0 on error.
*/
-int remount_resctrlfs(bool mum_resctrlfs)
+int mount_resctrlfs(void)
{
- 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 = find_resctrl_mount(NULL);
+ if (ret != -ENOENT)
+ return -1;
+ ksft_print_msg("Mounting resctrl to \"%s\"\n", RESCTRL_PATH);
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++;
+ ksft_perror("mount");
return ret;
}
int umount_resctrlfs(void)
{
- if (umount(RESCTRL_PATH)) {
- perror("# Unable to umount resctrl");
+ char mountpoint[256];
+ int ret;
- return errno;
+ ret = find_resctrl_mount(mountpoint);
+ if (ret == -ENOENT)
+ return 0;
+ if (ret)
+ return ret;
+
+ if (umount(mountpoint)) {
+ ksft_perror("Unable to umount resctrl");
+
+ return -1;
}
return 0;
}
/*
- * get_resource_id - Get socket number/l3 id for a specified CPU
+ * get_cache_level - Convert cache level from string to integer
+ * @cache_type: Cache level as string
+ *
+ * Return: cache level as integer or -1 if @cache_type is invalid.
+ */
+static int get_cache_level(const char *cache_type)
+{
+ if (!strcmp(cache_type, "L3"))
+ return 3;
+ if (!strcmp(cache_type, "L2"))
+ return 2;
+
+ ksft_print_msg("Invalid cache level\n");
+ return -1;
+}
+
+static int get_resource_cache_level(const char *resource)
+{
+ /* "MB" use L3 (LLC) as resource */
+ if (!strcmp(resource, "MB"))
+ return 3;
+ return get_cache_level(resource);
+}
+
+/*
+ * get_domain_id - Get resctrl domain ID for a specified CPU
+ * @resource: resource name
* @cpu_no: CPU number
- * @resource_id: Socket number or l3_id
+ * @domain_id: domain ID (cache ID; for MB, L3 cache ID)
*
* Return: >= 0 on success, < 0 on failure.
*/
-int get_resource_id(int cpu_no, int *resource_id)
+int get_domain_id(const char *resource, int cpu_no, int *domain_id)
{
char phys_pkg_path[1024];
+ int cache_num;
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);
+ cache_num = get_resource_cache_level(resource);
+ if (cache_num < 0)
+ return cache_num;
+
+ sprintf(phys_pkg_path, "%s%d/cache/index%d/id", PHYS_ID_PATH, cpu_no, cache_num);
fp = fopen(phys_pkg_path, "r");
if (!fp) {
- perror("Failed to open physical_package_id");
+ ksft_perror("Failed to open cache id file");
return -1;
}
- if (fscanf(fp, "%d", resource_id) <= 0) {
- perror("Could not get socket number or l3 id");
+ if (fscanf(fp, "%d", domain_id) <= 0) {
+ ksft_perror("Could not get domain ID");
fclose(fp);
return -1;
@@ -138,6 +159,98 @@ int get_resource_id(int cpu_no, int *resource_id)
}
/*
+ * Count number of CPUs in a /sys bitmap
+ */
+static unsigned int count_sys_bitmap_bits(char *name)
+{
+ FILE *fp = fopen(name, "r");
+ int count = 0, c;
+
+ if (!fp)
+ return 0;
+
+ while ((c = fgetc(fp)) != EOF) {
+ if (!isxdigit(c))
+ continue;
+ switch (c) {
+ case 'f':
+ count++;
+ fallthrough;
+ case '7': case 'b': case 'd': case 'e':
+ count++;
+ fallthrough;
+ case '3': case '5': case '6': case '9': case 'a': case 'c':
+ count++;
+ fallthrough;
+ case '1': case '2': case '4': case '8':
+ count++;
+ break;
+ }
+ }
+ fclose(fp);
+
+ return count;
+}
+
+static bool cpus_offline_empty(void)
+{
+ char offline_cpus_str[64];
+ FILE *fp;
+
+ fp = fopen("/sys/devices/system/cpu/offline", "r");
+ if (!fp) {
+ ksft_perror("Could not open /sys/devices/system/cpu/offline");
+ return 0;
+ }
+
+ if (fscanf(fp, "%63s", offline_cpus_str) < 0) {
+ if (!errno) {
+ fclose(fp);
+ return 1;
+ }
+ ksft_perror("Could not read /sys/devices/system/cpu/offline");
+ }
+
+ fclose(fp);
+
+ return 0;
+}
+
+/*
+ * Detect SNC by comparing #CPUs in node0 with #CPUs sharing LLC with CPU0.
+ * If any CPUs are offline declare the detection as unreliable.
+ */
+int snc_nodes_per_l3_cache(void)
+{
+ int node_cpus, cache_cpus;
+ static int snc_mode;
+
+ if (!snc_mode) {
+ snc_mode = 1;
+ if (!cpus_offline_empty()) {
+ ksft_print_msg("Runtime SNC detection unreliable due to offline CPUs.\n");
+ ksft_print_msg("Setting SNC mode to disabled.\n");
+ snc_unreliable = 1;
+ return snc_mode;
+ }
+ node_cpus = count_sys_bitmap_bits("/sys/devices/system/node/node0/cpumap");
+ cache_cpus = count_sys_bitmap_bits("/sys/devices/system/cpu/cpu0/cache/index3/shared_cpu_map");
+
+ if (!node_cpus || !cache_cpus) {
+ ksft_print_msg("Could not determine Sub-NUMA Cluster mode.\n");
+ snc_unreliable = 1;
+ return snc_mode;
+ }
+ snc_mode = cache_cpus / node_cpus;
+
+ if (snc_mode > 1)
+ ksft_print_msg("SNC-%d mode discovered.\n", snc_mode);
+ }
+
+ return snc_mode;
+}
+
+/*
* get_cache_size - Get cache size for a specified CPU
* @cpu_no: CPU number
* @cache_type: Cache level L2/L3
@@ -145,31 +258,26 @@ int get_resource_id(int cpu_no, int *resource_id)
*
* Return: = 0 on success, < 0 on failure.
*/
-int get_cache_size(int cpu_no, char *cache_type, unsigned long *cache_size)
+int get_cache_size(int cpu_no, const 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;
- }
+ cache_num = get_cache_level(cache_type);
+ if (cache_num < 0)
+ return cache_num;
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");
+ ksft_perror("Failed to open cache size");
return -1;
}
- if (fscanf(fp, "%s", cache_str) <= 0) {
- perror("Could not get cache_size");
+ if (fscanf(fp, "%63s", cache_str) <= 0) {
+ ksft_perror("Could not get cache_size");
fclose(fp);
return -1;
@@ -197,34 +305,46 @@ int get_cache_size(int cpu_no, char *cache_type, unsigned long *cache_size)
break;
}
+ /*
+ * The amount of cache represented by each bit in the masks
+ * in the schemata file is reduced by a factor equal to SNC
+ * nodes per L3 cache.
+ * E.g. on a SNC-2 system with a 100MB L3 cache a test that
+ * allocates memory from its local SNC node (default behavior
+ * without using libnuma) will only see 50 MB llc_occupancy
+ * with a fully populated L3 mask in the schemata file.
+ */
+ if (cache_num == 3)
+ *cache_size /= snc_nodes_per_l3_cache();
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.
+ * get_bit_mask - Get bit mask from given file
+ * @filename: File containing the mask
+ * @mask: The bit mask returned as unsigned long
*
* Return: = 0 on success, < 0 on failure.
*/
-int get_cbm_mask(char *cache_type)
+static int get_bit_mask(const char *filename, unsigned long *mask)
{
- char cbm_mask_path[1024];
FILE *fp;
- sprintf(cbm_mask_path, "%s/%s/cbm_mask", CBM_MASK_PATH, cache_type);
+ if (!filename || !mask)
+ return -1;
- fp = fopen(cbm_mask_path, "r");
+ fp = fopen(filename, "r");
if (!fp) {
- perror("Failed to open cache level");
-
+ ksft_print_msg("Failed to open bit mask file '%s': %s\n",
+ filename, strerror(errno));
return -1;
}
- if (fscanf(fp, "%s", cbm_mask) <= 0) {
- perror("Could not get max cbm_mask");
+
+ if (fscanf(fp, "%lx", mask) <= 0) {
+ ksft_print_msg("Could not read bit mask file '%s': %s\n",
+ filename, strerror(errno));
fclose(fp);
return -1;
@@ -235,63 +355,182 @@ int get_cbm_mask(char *cache_type)
}
/*
- * get_core_sibling - Get sibling core id from the same socket for given CPU
- * @cpu_no: CPU number
+ * resource_info_unsigned_get - Read an unsigned value from
+ * /sys/fs/resctrl/info/@resource/@filename
+ * @resource: Resource name that matches directory name in
+ * /sys/fs/resctrl/info
+ * @filename: File in /sys/fs/resctrl/info/@resource
+ * @val: Contains read value on success.
*
- * Return: > 0 on success, < 0 on failure.
+ * Return: = 0 on success, < 0 on failure. On success the read
+ * value is saved into @val.
*/
-int get_core_sibling(int cpu_no)
+int resource_info_unsigned_get(const char *resource, const char *filename,
+ unsigned int *val)
{
- char core_siblings_path[1024], cpu_list_str[64];
- int sibling_cpu_no = -1;
+ char file_path[PATH_MAX];
FILE *fp;
- sprintf(core_siblings_path, "%s%d/topology/core_siblings_list",
- CORE_SIBLINGS_PATH, cpu_no);
+ snprintf(file_path, sizeof(file_path), "%s/%s/%s", INFO_PATH, resource,
+ filename);
- fp = fopen(core_siblings_path, "r");
+ fp = fopen(file_path, "r");
if (!fp) {
- perror("Failed to open core siblings path");
-
+ ksft_print_msg("Error opening %s: %m\n", file_path);
return -1;
}
- if (fscanf(fp, "%s", cpu_list_str) <= 0) {
- perror("Could not get core_siblings list");
- fclose(fp);
+ if (fscanf(fp, "%u", val) <= 0) {
+ ksft_print_msg("Could not get contents of %s: %m\n", file_path);
+ fclose(fp);
return -1;
}
+
fclose(fp);
+ return 0;
+}
+
+/*
+ * create_bit_mask- Create bit mask from start, len pair
+ * @start: LSB of the mask
+ * @len Number of bits in the mask
+ */
+unsigned long create_bit_mask(unsigned int start, unsigned int len)
+{
+ return ((1UL << len) - 1UL) << start;
+}
+
+/*
+ * count_contiguous_bits - Returns the longest train of bits in a bit mask
+ * @val A bit mask
+ * @start The location of the least-significant bit of the longest train
+ *
+ * Return: The length of the contiguous bits in the longest train of bits
+ */
+unsigned int count_contiguous_bits(unsigned long val, unsigned int *start)
+{
+ unsigned long last_val;
+ unsigned int count = 0;
- char *token = strtok(cpu_list_str, "-,");
+ while (val) {
+ last_val = val;
+ val &= (val >> 1);
+ count++;
+ }
- 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, "-,");
+ if (start) {
+ if (count)
+ *start = ffsl(last_val) - 1;
+ else
+ *start = 0;
}
- return sibling_cpu_no;
+ return count;
+}
+
+/*
+ * get_full_cbm - Get full Cache Bit Mask (CBM)
+ * @cache_type: Cache type as "L2" or "L3"
+ * @mask: Full cache bit mask representing the maximal portion of cache
+ * available for allocation, returned as unsigned long.
+ *
+ * Return: = 0 on success, < 0 on failure.
+ */
+int get_full_cbm(const char *cache_type, unsigned long *mask)
+{
+ char cbm_path[PATH_MAX];
+ int ret;
+
+ if (!cache_type)
+ return -1;
+
+ snprintf(cbm_path, sizeof(cbm_path), "%s/%s/cbm_mask",
+ INFO_PATH, cache_type);
+
+ ret = get_bit_mask(cbm_path, mask);
+ if (ret || !*mask)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * get_shareable_mask - Get shareable mask from shareable_bits
+ * @cache_type: Cache type as "L2" or "L3"
+ * @shareable_mask: Shareable mask returned as unsigned long
+ *
+ * Return: = 0 on success, < 0 on failure.
+ */
+static int get_shareable_mask(const char *cache_type, unsigned long *shareable_mask)
+{
+ char mask_path[PATH_MAX];
+
+ if (!cache_type)
+ return -1;
+
+ snprintf(mask_path, sizeof(mask_path), "%s/%s/shareable_bits",
+ INFO_PATH, cache_type);
+
+ return get_bit_mask(mask_path, shareable_mask);
+}
+
+/*
+ * get_mask_no_shareable - Get Cache Bit Mask (CBM) without shareable bits
+ * @cache_type: Cache type as "L2" or "L3"
+ * @mask: The largest exclusive portion of the cache out of the
+ * full CBM, returned as unsigned long
+ *
+ * Parts of a cache may be shared with other devices such as GPU. This function
+ * calculates the largest exclusive portion of the cache where no other devices
+ * besides CPU have access to the cache portion.
+ *
+ * Return: = 0 on success, < 0 on failure.
+ */
+int get_mask_no_shareable(const char *cache_type, unsigned long *mask)
+{
+ unsigned long full_mask, shareable_mask;
+ unsigned int start, len;
+
+ if (get_full_cbm(cache_type, &full_mask) < 0)
+ return -1;
+ if (get_shareable_mask(cache_type, &shareable_mask) < 0)
+ return -1;
+
+ len = count_contiguous_bits(full_mask & ~shareable_mask, &start);
+ if (!len)
+ return -1;
+
+ *mask = create_bit_mask(start, len);
+
+ return 0;
}
/*
* 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
+ * @bm_pid: PID that should be binded
+ * @cpu_no: CPU number at which the PID would be binded
+ * @old_affinity: When not NULL, set to old CPU affinity
*
- * Return: 0 on success, non-zero on failure
+ * Return: 0 on success, < 0 on error.
*/
-int taskset_benchmark(pid_t bm_pid, int cpu_no)
+int taskset_benchmark(pid_t bm_pid, int cpu_no, cpu_set_t *old_affinity)
{
cpu_set_t my_set;
+ if (old_affinity) {
+ CPU_ZERO(old_affinity);
+ if (sched_getaffinity(bm_pid, sizeof(*old_affinity),
+ old_affinity)) {
+ ksft_perror("Unable to read CPU affinity");
+ return -1;
+ }
+ }
+
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");
+ ksft_perror("Unable to taskset benchmark");
return -1;
}
@@ -300,57 +539,20 @@ int taskset_benchmark(pid_t bm_pid, int cpu_no)
}
/*
- * 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
+ * taskset_restore - Taskset PID to the earlier CPU affinity
+ * @bm_pid: PID that should be reset
+ * @old_affinity: The old CPU affinity to restore
*
- * Return: void
+ * Return: 0 on success, < 0 on error.
*/
-void run_benchmark(int signum, siginfo_t *info, void *ucontext)
+int taskset_restore(pid_t bm_pid, cpu_set_t *old_affinity)
{
- 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");
+ if (sched_setaffinity(bm_pid, sizeof(*old_affinity), old_affinity)) {
+ ksft_perror("Unable to restore CPU affinity");
+ return -1;
}
- fclose(stdout);
- PARENT_EXIT("Unable to run specified benchmark");
+ return 0;
}
/*
@@ -359,7 +561,10 @@ void run_benchmark(int signum, siginfo_t *info, void *ucontext)
* @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
+ * Creates a group @grp_name if it does not exist yet. If @grp_name is NULL,
+ * it is interpreted as the root group which always results in success.
+ *
+ * Return: 0 on success, < 0 on error.
*/
static int create_grp(const char *grp_name, char *grp, const char *parent_grp)
{
@@ -367,12 +572,7 @@ static int create_grp(const char *grp_name, char *grp, const char *parent_grp)
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)
+ if (!grp_name)
return 0;
/* Check if requested grp exists or not */
@@ -384,7 +584,7 @@ static int create_grp(const char *grp_name, char *grp, const char *parent_grp)
}
closedir(dp);
} else {
- perror("Unable to open resctrl for group");
+ ksft_perror("Unable to open resctrl for group");
return -1;
}
@@ -392,7 +592,7 @@ static int create_grp(const char *grp_name, char *grp, const char *parent_grp)
/* Requested grp doesn't exist, hence create it */
if (found_grp == 0) {
if (mkdir(grp, 0) == -1) {
- perror("Unable to create group");
+ ksft_perror("Unable to create group");
return -1;
}
@@ -407,12 +607,12 @@ static int write_pid_to_tasks(char *tasks, pid_t pid)
fp = fopen(tasks, "w");
if (!fp) {
- perror("Failed to open tasks file");
+ ksft_perror("Failed to open tasks file");
return -1;
}
- if (fprintf(fp, "%d\n", pid) < 0) {
- perror("Failed to wr pid to tasks file");
+ if (fprintf(fp, "%d\n", (int)pid) < 0) {
+ ksft_print_msg("Failed to write pid to tasks file\n");
fclose(fp);
return -1;
@@ -427,7 +627,6 @@ static int write_pid_to_tasks(char *tasks, pid_t pid)
* @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.
@@ -435,16 +634,15 @@ static int write_pid_to_tasks(char *tasks, pid_t pid)
* 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
+ * Return: 0 on success, < 0 on error.
*/
-int write_bm_pid_to_resctrl(pid_t bm_pid, char *ctrlgrp, char *mongrp,
- char *resctrl_val)
+int write_bm_pid_to_resctrl(pid_t bm_pid, const char *ctrlgrp, const char *mongrp)
{
char controlgroup[128], monitorgroup[512], monitorgroup_p[256];
char tasks[1024];
int ret = 0;
- if (strlen(ctrlgrp))
+ if (ctrlgrp)
sprintf(controlgroup, "%s/%s", RESCTRL_PATH, ctrlgrp);
else
sprintf(controlgroup, "%s", RESCTRL_PATH);
@@ -458,31 +656,25 @@ int write_bm_pid_to_resctrl(pid_t bm_pid, char *ctrlgrp, char *mongrp,
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;
- }
+ /* Create monitor group and write pid into if it is used */
+ if (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 " : "");
+ ksft_print_msg("Writing benchmark parameters to resctrl FS\n");
if (ret)
- perror("# writing to resctrlfs");
-
- tests_run++;
+ ksft_print_msg("Failed writing to resctrlfs\n");
return ret;
}
@@ -492,69 +684,70 @@ out:
* @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)
+ * @resource: Resctrl resource (Eg: MB, L3, L2, etc.)
*
- * Update schemata of a con_mon grp *only* if requested resctrl feature is
+ * Update schemata of a con_mon grp *only* if requested resctrl resource is
* allocation type
*
- * Return: 0 on success, non-zero on failure
+ * Return: 0 on success, < 0 on error.
*/
-int write_schemata(char *ctrlgrp, char *schemata, int cpu_no, char *resctrl_val)
+int write_schemata(const char *ctrlgrp, char *schemata, int cpu_no,
+ const char *resource)
{
- 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;
+ char controlgroup[1024], reason[128], schema[1024] = {};
+ int domain_id, fd, schema_len, ret = 0;
if (!schemata) {
- printf("# Skipping empty schemata update\n");
+ ksft_print_msg("Skipping empty schemata update\n");
return -1;
}
- if (get_resource_id(cpu_no, &resource_id) < 0) {
- sprintf(reason, "Failed to get resource id");
+ if (get_domain_id(resource, cpu_no, &domain_id) < 0) {
+ sprintf(reason, "Failed to get domain ID");
ret = -1;
goto out;
}
- if (strlen(ctrlgrp) != 0)
+ if (ctrlgrp)
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");
+ schema_len = snprintf(schema, sizeof(schema), "%s:%d=%s\n",
+ resource, domain_id, schemata);
+ if (schema_len < 0 || schema_len >= sizeof(schema)) {
+ snprintf(reason, sizeof(reason),
+ "snprintf() failed with return value : %d", schema_len);
ret = -1;
-
goto out;
}
- if (fprintf(fp, "%s\n", schema) < 0) {
- sprintf(reason, "Failed to write schemata in control group");
- fclose(fp);
+ fd = open(controlgroup, O_WRONLY);
+ if (fd < 0) {
+ snprintf(reason, sizeof(reason),
+ "open() failed : %s", strerror(errno));
ret = -1;
- goto out;
+ goto err_schema_not_empty;
}
- fclose(fp);
+ if (write(fd, schema, schema_len) < 0) {
+ snprintf(reason, sizeof(reason),
+ "write() failed : %s", strerror(errno));
+ close(fd);
+ ret = -1;
+ goto err_schema_not_empty;
+ }
+ close(fd);
+
+err_schema_not_empty:
+ schema[schema_len - 1] = 0;
out:
- printf("%sok Write schema \"%s\" to resctrl FS%s%s\n",
- ret ? "not " : "", schema, ret ? " # " : "",
- ret ? reason : "");
- tests_run++;
+ ksft_print_msg("Write schema \"%s\" to resctrl FS%s%s\n",
+ schema, ret ? " # " : "",
+ ret ? reason : "");
return ret;
}
@@ -578,18 +771,20 @@ bool check_resctrlfs_support(void)
fclose(inf);
- printf("%sok kernel supports resctrl filesystem\n", ret ? "" : "not ");
- tests_run++;
+ ksft_print_msg("%s Check kernel supports resctrl filesystem\n",
+ ret ? "Pass:" : "Fail:");
+
+ if (!ret)
+ return ret;
dp = opendir(RESCTRL_PATH);
- printf("%sok resctrl mountpoint \"%s\" exists\n",
- dp ? "" : "not ", RESCTRL_PATH);
+ ksft_print_msg("%s Check resctrl mountpoint \"%s\" exists\n",
+ dp ? "Pass:" : "Fail:", RESCTRL_PATH);
if (dp)
closedir(dp);
- tests_run++;
- printf("# resctrl filesystem %s mounted\n",
- find_resctrl_mount(NULL) ? "not" : "is");
+ ksft_print_msg("resctrl filesystem %s mounted\n",
+ find_resctrl_mount(NULL) ? "not" : "is");
return ret;
}
@@ -612,31 +807,90 @@ char *fgrep(FILE *inf, const char *str)
}
/*
- * validate_resctrl_feature_request - Check if requested feature is valid.
- * @resctrl_val: Requested feature
+ * resctrl_resource_exists - Check if a resource is supported.
+ * @resource: Resctrl resource (e.g., MB, L3, L2, L3_MON, etc.)
*
- * Return: 0 on success, non-zero on failure
+ * Return: True if the resource is supported, else false. False is
+ * also returned if resctrl FS is not mounted.
*/
-bool validate_resctrl_feature_request(char *resctrl_val)
+bool resctrl_resource_exists(const char *resource)
{
- FILE *inf = fopen("/proc/cpuinfo", "r");
- bool found = false;
+ char res_path[PATH_MAX];
+ struct stat statbuf;
+ int ret;
+
+ if (!resource)
+ return false;
+
+ ret = find_resctrl_mount(NULL);
+ if (ret)
+ return false;
+
+ snprintf(res_path, sizeof(res_path), "%s/%s", INFO_PATH, resource);
+
+ if (stat(res_path, &statbuf))
+ return false;
+
+ return true;
+}
+
+/*
+ * resctrl_mon_feature_exists - Check if requested monitoring feature is valid.
+ * @resource: Resource that uses the mon_features file. Currently only L3_MON
+ * is valid.
+ * @feature: Required monitor feature (in mon_features file).
+ *
+ * Return: True if the feature is supported, else false.
+ */
+bool resctrl_mon_feature_exists(const char *resource, const char *feature)
+{
+ char res_path[PATH_MAX];
char *res;
+ FILE *inf;
+ if (!feature || !resource)
+ return false;
+
+ snprintf(res_path, sizeof(res_path), "%s/%s/mon_features", INFO_PATH, resource);
+ inf = fopen(res_path, "r");
if (!inf)
return false;
- res = fgrep(inf, "flags");
+ res = fgrep(inf, feature);
+ free(res);
+ fclose(inf);
- if (res) {
- char *s = strchr(res, ':');
+ return !!res;
+}
- found = s && !strstr(s, resctrl_val);
- free(res);
- }
- fclose(inf);
+/*
+ * resource_info_file_exists - Check if a file is present inside
+ * /sys/fs/resctrl/info/@resource.
+ * @resource: Required resource (Eg: MB, L3, L2, etc.)
+ * @file: Required file.
+ *
+ * Return: True if the /sys/fs/resctrl/info/@resource/@file exists, else false.
+ */
+bool resource_info_file_exists(const char *resource, const char *file)
+{
+ char res_path[PATH_MAX];
+ struct stat statbuf;
+
+ if (!file || !resource)
+ return false;
+
+ snprintf(res_path, sizeof(res_path), "%s/%s/%s", INFO_PATH, resource,
+ file);
- return found;
+ if (stat(res_path, &statbuf))
+ return false;
+
+ return true;
+}
+
+bool test_resource_feature_check(const struct resctrl_test *test)
+{
+ return resctrl_resource_exists(test->resource);
}
int filter_dmesg(void)
@@ -649,21 +903,22 @@ int filter_dmesg(void)
ret = pipe(pipefds);
if (ret) {
- perror("pipe");
+ ksft_perror("pipe");
return ret;
}
+ fflush(stdout);
pid = fork();
if (pid == 0) {
close(pipefds[0]);
dup2(pipefds[1], STDOUT_FILENO);
execlp("dmesg", "dmesg", NULL);
- perror("executing dmesg");
+ ksft_perror("Executing dmesg");
exit(1);
}
close(pipefds[1]);
fp = fdopen(pipefds[0], "r");
if (!fp) {
- perror("fdopen(pipe)");
+ ksft_perror("fdopen(pipe)");
kill(pid, SIGTERM);
return -1;
@@ -671,9 +926,9 @@ int filter_dmesg(void)
while (fgets(line, 1024, fp)) {
if (strstr(line, "intel_rdt:"))
- printf("# dmesg: %s", line);
+ ksft_print_msg("dmesg: %s", line);
if (strstr(line, "resctrl:"))
- printf("# dmesg: %s", line);
+ ksft_print_msg("dmesg: %s", line);
}
fclose(fp);
waitpid(pid, NULL, 0);
@@ -681,24 +936,6 @@ int filter_dmesg(void)
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)
{
@@ -720,3 +957,35 @@ unsigned int count_bits(unsigned long n)
return count;
}
+
+/**
+ * snc_kernel_support - Check for existence of mon_sub_L3_00 file that indicates
+ * SNC resctrl support on the kernel side.
+ *
+ * Return: 0 if not supported, 1 if SNC is disabled or SNC discovery is
+ * unreliable or SNC is both enabled and supported.
+ */
+int snc_kernel_support(void)
+{
+ char node_path[PATH_MAX];
+ struct stat statbuf;
+ int ret;
+
+ ret = snc_nodes_per_l3_cache();
+ /*
+ * If SNC is disabled then its kernel support isn't important. If SNC
+ * got disabled because the discovery process was unreliable the
+ * snc_unreliable variable was set. It can be used to verify the SNC
+ * discovery reliability elsewhere in the selftest.
+ */
+ if (ret == 1)
+ return ret;
+
+ snprintf(node_path, sizeof(node_path), "%s/%s", RESCTRL_PATH,
+ "mon_data/mon_L3_00/mon_sub_L3_00");
+
+ if (!stat(node_path, &statbuf))
+ return 1;
+
+ return 0;
+}
diff --git a/tools/testing/selftests/resctrl/settings b/tools/testing/selftests/resctrl/settings
new file mode 100644
index 000000000000..a383f3d4565b
--- /dev/null
+++ b/tools/testing/selftests/resctrl/settings
@@ -0,0 +1,3 @@
+# If running time is longer than 120 seconds when new tests are added in
+# the future, increase timeout here.
+timeout=120