Merge tag 'linux_kselftest-next-7.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest

Pull kselftest updates from Shuah Khan:
 "Several fixes and improvements to resctrl tests and a change to
  kselftest document to clarify the use of FORCE_TARGETS build variable"

* tag 'linux_kselftest-next-7.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest:
  kselftest: fix doc for ksft_test_result_report()
  selftests/resctrl: Reduce L2 impact on CAT test
  selftests/resctrl: Simplify perf usage in CAT test
  selftests/resctrl: Remove requirement on cache miss rate
  selftests/resctrl: Raise threshold at which MBM and PMU values are compared
  selftests/resctrl: Increase size of buffer used in MBM and MBA tests
  selftests/resctrl: Support multiple events associated with iMC
  selftests/resctrl: Prepare for parsing multiple events per iMC
  selftests/resctrl: Do not store iMC counter value in counter config structure
  selftests/resctrl: Reduce interference from L2 occupancy during cache occupancy test
  selftests/resctrl: Improve accuracy of cache occupancy test
  docs: kselftest: Document the FORCE_TARGETS build variable
This commit is contained in:
Linus Torvalds
2026-06-16 16:49:07 +05:30
10 changed files with 196 additions and 104 deletions

View File

@@ -126,6 +126,18 @@ dedicated skiplist::
See the top-level tools/testing/selftests/Makefile for the list of all
possible targets.
Requiring all targets to build successfully
===========================================
By default, the build succeeds as long as at least one target builds
without error. Set ``FORCE_TARGETS=1`` to instead require every target to
build successfully; make will abort as soon as any target fails::
$ make -C tools/testing/selftests FORCE_TARGETS=1
This applies to both the ``all`` and ``install`` targets and is useful in
CI environments where a silent partial build would be misleading.
Running the full range hotplug selftests
========================================

View File

@@ -346,9 +346,9 @@ void ksft_test_result_code(int exit_code, const char *test_name,
}
/**
* ksft_test_result() - Report test success based on truth of condition
* ksft_test_result_report() - Report test result based on a kselftest exit code
*
* @condition: if true, report test success, otherwise failure.
* @result: a kselftest exit code
*/
#define ksft_test_result_report(result, fmt, ...) do { \
switch (result) { \

View File

@@ -10,7 +10,6 @@ void perf_event_attr_initialize(struct perf_event_attr *pea, __u64 config)
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;
@@ -37,19 +36,13 @@ int perf_event_reset_enable(int pe_fd)
return 0;
}
void perf_event_initialize_read_format(struct perf_event_read *pe_read)
{
memset(pe_read, 0, sizeof(*pe_read));
pe_read->nr = 1;
}
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");
ksft_perror("Unable to set up performance monitoring");
return -1;
}
@@ -132,9 +125,9 @@ static int print_results_cache(const char *filename, pid_t bm_pid, __u64 llc_val
*
* 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)
int perf_event_measure(int pe_fd, const char *filename, pid_t bm_pid)
{
__u64 value;
int ret;
/* Stop counters after one span to get miss rate */
@@ -142,13 +135,13 @@ int perf_event_measure(int pe_fd, struct perf_event_read *pe_read,
if (ret < 0)
return ret;
ret = read(pe_fd, pe_read, sizeof(*pe_read));
ret = read(pe_fd, &value, sizeof(value));
if (ret == -1) {
ksft_perror("Could not get perf value");
return -1;
}
return print_results_cache(filename, bm_pid, pe_read->values[0].value);
return print_results_cache(filename, bm_pid, value);
}
/*
@@ -173,6 +166,19 @@ int measure_llc_resctrl(const char *filename, pid_t bm_pid)
return print_results_cache(filename, bm_pid, llc_occu_resc);
}
/*
* Reduce L2 allocation to minimum when testing L3 cache allocation.
*/
int minimize_l2_occupancy(const struct resctrl_test *test,
const struct user_params *uparams,
const struct resctrl_val_param *param)
{
if (!strcmp(test->resource, "L3") && resctrl_resource_exists("L2"))
return write_schemata(param->ctrlgrp, "0x1", uparams->cpu, "L2");
return 0;
}
/*
* show_cache_info - Show generic cache test information
* @no_of_bits: Number of bits

View File

@@ -14,42 +14,20 @@
#define RESULT_FILE_NAME "result_cat"
#define NUM_OF_RUNS 5
/*
* 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.
*/
#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)
{
__u64 avg_llc_val = 0;
float avg_diff;
int ret = 0;
avg_llc_val = sum_llc_val / num_of_runs;
if (*prev_avg_llc_val) {
float delta = (__s64)(avg_llc_val - *prev_avg_llc_val);
ret = platform && (avg_llc_val < *prev_avg_llc_val);
avg_diff = delta / *prev_avg_llc_val;
ret = platform && (avg_diff * 100) < (float)min_diff_percent;
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);
ksft_print_msg("%s Check cache miss rate increased\n",
ret ? "Fail:" : "Pass:");
}
*prev_avg_llc_val = avg_llc_val;
@@ -58,10 +36,10 @@ static int show_results_info(__u64 sum_llc_val, int no_of_bits,
return ret;
}
/* Remove the highest bit from CBM */
/* Remove the highest bits from CBM */
static unsigned long next_mask(unsigned long current_mask)
{
return current_mask & (current_mask >> 1);
return current_mask & (current_mask >> 2);
}
static int check_results(struct resctrl_val_param *param, const char *cache_type,
@@ -112,7 +90,6 @@ static int check_results(struct resctrl_val_param *param, const char *cache_type
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)
@@ -158,7 +135,6 @@ static int cat_test(const struct resctrl_test *test,
struct resctrl_val_param *param,
size_t span, unsigned long current_mask)
{
struct perf_event_read pe_read;
struct perf_event_attr pea;
cpu_set_t old_affinity;
unsigned char *buf;
@@ -181,8 +157,11 @@ static int cat_test(const struct resctrl_test *test,
if (ret)
goto reset_affinity;
ret = minimize_l2_occupancy(test, uparams, param);
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;
@@ -215,7 +194,7 @@ static int cat_test(const struct resctrl_test *test,
fill_cache_read(buf, span, true);
ret = perf_event_measure(pe_fd, &pe_read, param->filename, bm_pid);
ret = perf_event_measure(pe_fd, param->filename, bm_pid);
if (ret)
goto free_buf;
}

View File

@@ -19,12 +19,40 @@
#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)
/*
* Initialize capacity bitmasks (CBMs) of:
* - control group being tested per test parameters,
* - default resource group as inverse of control group being tested to prevent
* other tasks from interfering with test,
* - L2 resource of control group being tested to minimize allocations into
* L2 if possible to better predict L3 occupancy.
*/
static int cmt_init(const struct resctrl_test *test,
const struct user_params *uparams,
const struct resctrl_val_param *param, int domain_id)
{
unsigned long full_mask;
char schemata[64];
int ret;
sprintf(llc_occup_path, CON_MON_LCC_OCCUP_PATH, RESCTRL_PATH,
param->ctrlgrp, domain_id);
return 0;
ret = get_full_cbm(test->resource, &full_mask);
if (ret)
return ret;
snprintf(schemata, sizeof(schemata), "%lx", ~param->mask & full_mask);
ret = write_schemata("", schemata, uparams->cpu, test->resource);
if (ret)
return ret;
snprintf(schemata, sizeof(schemata), "%lx", param->mask);
ret = write_schemata(param->ctrlgrp, schemata, uparams->cpu, test->resource);
if (ret)
return ret;
return minimize_l2_occupancy(test, uparams, param);
}
static int cmt_setup(const struct resctrl_test *test,
@@ -153,11 +181,11 @@ static int cmt_run_test(const struct resctrl_test *test, const struct user_param
span = cache_portion_size(cache_total_size, param.mask, long_mask);
if (uparams->fill_buf) {
fill_buf.buf_size = span;
fill_buf.buf_size = span * 2;
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.buf_size = span * 2;
fill_buf.memflush = true;
param.fill_buf = &fill_buf;
}

View File

@@ -139,6 +139,6 @@ ssize_t get_fill_buf_size(int cpu_no, const char *cache_type)
if (ret)
return ret;
return cache_total_size * 2 > MINIMUM_SPAN ?
cache_total_size * 2 : MINIMUM_SPAN;
return cache_total_size * 4 > MINIMUM_SPAN ?
cache_total_size * 4 : MINIMUM_SPAN;
}

View File

@@ -12,12 +12,14 @@
#define RESULT_FILE_NAME "result_mba"
#define NUM_OF_RUNS 5
#define MAX_DIFF_PERCENT 8
#define MAX_DIFF_PERCENT 15
#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)
static int mba_init(const struct resctrl_test *test,
const struct user_params *uparams,
const struct resctrl_val_param *param, int domain_id)
{
int ret;

View File

@@ -11,7 +11,7 @@
#include "resctrl.h"
#define RESULT_FILE_NAME "result_mbm"
#define MAX_DIFF_PERCENT 8
#define MAX_DIFF_PERCENT 15
#define NUM_OF_RUNS 5
static int
@@ -83,7 +83,9 @@ static int check_results(size_t span)
return ret;
}
static int mbm_init(const struct resctrl_val_param *param, int domain_id)
static int mbm_init(const struct resctrl_test *test,
const struct user_params *uparams,
const struct resctrl_val_param *param, int domain_id)
{
int ret;

View File

@@ -55,7 +55,7 @@
* and MBM respectively, for instance generating "overhead" traffic which
* is not counted against any specific RMID.
*/
#define THROTTLE_THRESHOLD 750
#define THROTTLE_THRESHOLD 2500
/*
* fill_buf_param: "fill_buf" benchmark parameters
@@ -135,7 +135,9 @@ struct resctrl_val_param {
char filename[64];
unsigned long mask;
int num_of_runs;
int (*init)(const struct resctrl_val_param *param,
int (*init)(const struct resctrl_test *test,
const struct user_params *uparams,
const struct resctrl_val_param *param,
int domain_id);
int (*setup)(const struct resctrl_test *test,
const struct user_params *uparams,
@@ -146,13 +148,6 @@ struct resctrl_val_param {
struct fill_buf_param *fill_buf;
};
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
@@ -208,12 +203,13 @@ unsigned int count_bits(unsigned long n);
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 perf_event_measure(int pe_fd, const char *filename, pid_t bm_pid);
int measure_llc_resctrl(const char *filename, pid_t bm_pid);
int minimize_l2_occupancy(const struct resctrl_test *test,
const struct user_params *uparams,
const struct resctrl_val_param *param);
void show_cache_info(int no_of_bits, __u64 avg_llc_val, size_t cache_span, bool lines);
/*

View File

@@ -11,10 +11,10 @@
#include "resctrl.h"
#define UNCORE_IMC "uncore_imc"
#define READ_FILE_NAME "events/cas_count_read"
#define READ_FILE_NAME "cas_count_read"
#define DYN_PMU_PATH "/sys/bus/event_source/devices"
#define SCALE 0.00006103515625
#define MAX_IMCS 20
#define MAX_IMCS 40
#define MAX_TOKENS 5
#define CON_MBM_LOCAL_BYTES_PATH \
@@ -32,7 +32,6 @@ struct imc_counter_config {
__u64 event;
__u64 umask;
struct perf_event_attr pe;
struct membw_read_format return_value;
int fd;
};
@@ -74,7 +73,7 @@ static void read_mem_bw_ioctl_perf_event_ioc_disable(int i)
* @cas_count_cfg: Config
* @count: iMC number
*/
static void get_read_event_and_umask(char *cas_count_cfg, int count)
static void get_read_event_and_umask(char *cas_count_cfg, unsigned int count)
{
char *token[MAX_TOKENS];
int i = 0;
@@ -110,45 +109,114 @@ static int open_perf_read_event(int i, int cpu_no)
return 0;
}
/* Get type and config of an iMC counter's read event. */
static int read_from_imc_dir(char *imc_dir, int count)
static int parse_imc_read_bw_events(char *imc_dir, unsigned int type,
unsigned int *count)
{
char cas_count_cfg[1024], imc_counter_cfg[1024], imc_counter_type[1024];
char imc_events_dir[PATH_MAX], imc_counter_cfg[PATH_MAX];
unsigned int orig_count = *count;
char cas_count_cfg[1024];
struct dirent *ep;
int path_len;
int ret = -1;
int num_cfg;
FILE *fp;
DIR *dp;
path_len = snprintf(imc_events_dir, sizeof(imc_events_dir), "%sevents",
imc_dir);
if (path_len >= sizeof(imc_events_dir)) {
ksft_print_msg("Unable to create path to %sevents\n", imc_dir);
return -1;
}
dp = opendir(imc_events_dir);
if (!dp) {
ksft_perror("Unable to open PMU events directory");
return -1;
}
while ((ep = readdir(dp))) {
/*
* Parse all event files with READ_FILE_NAME prefix that
* contain the event number and umask. Skip files containing
* "." that contain unused properties of event.
*/
if (!strstr(ep->d_name, READ_FILE_NAME) ||
strchr(ep->d_name, '.'))
continue;
path_len = snprintf(imc_counter_cfg, sizeof(imc_counter_cfg),
"%s/%s", imc_events_dir, ep->d_name);
if (path_len >= sizeof(imc_counter_cfg)) {
ksft_print_msg("Unable to create path to %s/%s\n",
imc_events_dir, ep->d_name);
goto out_close;
}
fp = fopen(imc_counter_cfg, "r");
if (!fp) {
ksft_perror("Failed to open iMC config file");
goto out_close;
}
num_cfg = fscanf(fp, "%1023s", cas_count_cfg);
fclose(fp);
if (num_cfg <= 0) {
ksft_perror("Could not get iMC cas count read");
goto out_close;
}
if (*count >= MAX_IMCS) {
ksft_print_msg("Maximum iMC count exceeded\n");
goto out_close;
}
imc_counters_config[*count].type = type;
get_read_event_and_umask(cas_count_cfg, *count);
/* Do not fail after incrementing *count. */
*count += 1;
}
if (*count == orig_count) {
ksft_print_msg("Unable to find events in %s\n", imc_events_dir);
goto out_close;
}
ret = 0;
out_close:
closedir(dp);
return ret;
}
/* Get type and config of an iMC counter's read event. */
static int read_from_imc_dir(char *imc_dir, unsigned int *count)
{
char imc_counter_type[PATH_MAX];
unsigned int type;
int path_len;
FILE *fp;
int ret;
/* Get type of iMC counter */
sprintf(imc_counter_type, "%s%s", imc_dir, "type");
path_len = snprintf(imc_counter_type, sizeof(imc_counter_type),
"%s%s", imc_dir, "type");
if (path_len >= sizeof(imc_counter_type)) {
ksft_print_msg("Unable to create path to %s%s\n",
imc_dir, "type");
return -1;
}
fp = fopen(imc_counter_type, "r");
if (!fp) {
ksft_perror("Failed to open iMC counter type file");
return -1;
}
if (fscanf(fp, "%u", &imc_counters_config[count].type) <= 0) {
ret = fscanf(fp, "%u", &type);
fclose(fp);
if (ret <= 0) {
ksft_perror("Could not get iMC type");
fclose(fp);
return -1;
}
fclose(fp);
/* Get read config */
sprintf(imc_counter_cfg, "%s%s", imc_dir, READ_FILE_NAME);
fp = fopen(imc_counter_cfg, "r");
if (!fp) {
ksft_perror("Failed to open iMC config file");
return -1;
ret = parse_imc_read_bw_events(imc_dir, type, count);
if (ret) {
ksft_print_msg("Unable to parse bandwidth event and umask\n");
return ret;
}
if (fscanf(fp, "%1023s", cas_count_cfg) <= 0) {
ksft_perror("Could not get iMC cas count read");
fclose(fp);
return -1;
}
fclose(fp);
get_read_event_and_umask(cas_count_cfg, count);
return 0;
}
@@ -197,13 +265,12 @@ static int num_of_imcs(void)
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);
ret = read_from_imc_dir(imc_dir, &count);
if (ret) {
closedir(dp);
return ret;
}
count++;
}
}
closedir(dp);
@@ -312,23 +379,23 @@ static int get_read_mem_bw_imc(float *bw_imc)
* Take overflow into consideration before calculating total bandwidth.
*/
for (imc = 0; imc < imcs; imc++) {
struct membw_read_format measurement;
struct imc_counter_config *r =
&imc_counters_config[imc];
if (read(r->fd, &r->return_value,
sizeof(struct membw_read_format)) == -1) {
if (read(r->fd, &measurement, sizeof(measurement)) == -1) {
ksft_perror("Couldn't get read bandwidth through iMC");
return -1;
}
__u64 r_time_enabled = r->return_value.time_enabled;
__u64 r_time_running = r->return_value.time_running;
__u64 r_time_enabled = measurement.time_enabled;
__u64 r_time_running = measurement.time_running;
if (r_time_enabled != r_time_running)
of_mul_read = (float)r_time_enabled /
(float)r_time_running;
reads += r->return_value.value * of_mul_read * SCALE;
reads += measurement.value * of_mul_read * SCALE;
}
*bw_imc = reads;
@@ -569,7 +636,7 @@ int resctrl_val(const struct resctrl_test *test,
goto reset_affinity;
if (param->init) {
ret = param->init(param, domain_id);
ret = param->init(test, uparams, param, domain_id);
if (ret)
goto reset_affinity;
}