Hi Richard, On 6/8/26 4:06 AM, Richard Cheng wrote: > L3_CAT needs a CPU-exclusive cache portion, so it's skipped when MPAM > reports every CBM bit as shareable, leaving L3 allocation untested. CMT > only checks occupancy accuracy, not that a CBM actually limits it.
hmmm ... CMT test ensures that the LLC occupancy is within a % of the size of the cache allocation. To me this implies that it indeed tests that the CBM limits the allocation, no? > > L3_CAT_OCCUP gives a group a small CBM, run a workload spanning the > whole cache, and check every occupancy sample stays within the > allocation. An unenforced CBM would instead let occupancy grow to the > full cache. > > Move CON_MON_LCC_OCCUP_PATH to resctrl.h to share it with CMT. > > Signed-off-by: Richard Cheng <[email protected]> > --- > tools/testing/selftests/resctrl/cat_test.c | 201 ++++++++++++++++++ > tools/testing/selftests/resctrl/cmt_test.c | 3 - > tools/testing/selftests/resctrl/resctrl.h | 4 + > .../testing/selftests/resctrl/resctrl_tests.c | 1 + > 4 files changed, 206 insertions(+), 3 deletions(-) > > diff --git a/tools/testing/selftests/resctrl/cat_test.c > b/tools/testing/selftests/resctrl/cat_test.c > index f00b622c1460..16a947f1ed16 100644 > --- a/tools/testing/selftests/resctrl/cat_test.c > +++ b/tools/testing/selftests/resctrl/cat_test.c > @@ -402,3 +402,204 @@ struct resctrl_test l2_noncont_cat_test = { > .feature_check = noncont_cat_feature_check, > .run_test = noncont_cat_run_test, > }; > + > +/* > + * L3_CAT_OCCUP - Verify that a CAT allocation bounds cache occupancy. > + * > + * Unlike L3_CAT (which measures interference between groups and needs an > + * exclusive cache portion), this test gives a control group a strict subset Please let comment just refer to what this test does. These comments are unlikely to be updated if L3_CAT ever changes. > + * of the CBM, then runs a benchmark whose buffer spans the *whole* cache - > + * i.e. much larger than the allocation. With CAT enforced, the group can > + * only keep its allocated portion resident, so llc_occupancy settles near > + * the allocation size. Without enforcement occupancy would instead climb > + * towards the full cache. This works even when all CBM bits are shareable > + * (where L3_CAT is skipped). hmmm ... these statements state as fact what really depends on system load and interference that this test make no attempt to avoid. > + */ > +#define CAT_OCCUP_RESULT_FILE "result_cat_occup" > +#define CAT_OCCUP_NUM_OF_RUNS 5 > + > +static int cat_occup_cpu; > + > +static int cat_occup_init(const struct resctrl_val_param *param, int > domain_id) Please take a look at recent resctrl selftest changes that provides more data to the init() that will avoid the cat_occup_cpu global. Even so, why not just use cmt_init() that reduces interference from rest of system to improve chances of workload's cache occupancy to match its cache allocation? > +{ > + char schemata[64]; > + > + sprintf(llc_occup_path, CON_MON_LCC_OCCUP_PATH, RESCTRL_PATH, > + param->ctrlgrp, domain_id); > + > + /* > + * Confine the benchmark to the allocated portion *before* it starts > + * filling (resctrl_val() calls init() before forking the benchmark), > + * so occupancy reflects the restricted CBM from the first sample. > + */ > + snprintf(schemata, sizeof(schemata), "%lx", param->mask); > + > + return write_schemata(param->ctrlgrp, schemata, cat_occup_cpu, "L3"); > +} > + > +static int cat_occup_setup(const struct resctrl_test *test, > + const struct user_params *uparams, > + struct resctrl_val_param *p) > +{ > + if (p->num_of_runs >= CAT_OCCUP_NUM_OF_RUNS) > + return END_OF_TESTS; > + > + p->num_of_runs++; > + > + return 0; > +} cmt_setup()? > + > +static int cat_occup_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); > +} cmt_measure()? This test has a lot in common with the existing CMT test since it duplicates cmt_setup(), cmt_measure(), and cmt_feature_check(). From what I can tell it can use cmt_init() also. Could the implementation be simplified by instead considering it a CMT test, move code to cmt_test.c, and avoid all this duplication? > + > +static int cat_occup_check_results(struct resctrl_val_param *param, > + size_t alloc_span, size_t cache_size, > + int no_of_bits) > +{ > + char *token_array[8], temp[512]; > + unsigned long occu, max_occu = 0, ceiling, floor; Please follow kernel coding style (throughout this series) by, in this example, using reverse-fir tree ordering. > + int runs = 0; > + int fail = 0; > + FILE *fp; > + > + /* > + * Check every sample, not an average: CAT is a hard limit, so a single hmmm ... "is a hard limit" does not match what the code does > + * sample above the allocation is a real violation that an average > + * could mask. > + */ > + ceiling = alloc_span + (cache_size - alloc_span) / 2; > + floor = alloc_span / 2; > + > + 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 the resctrl-reported llc_occupancy value. */ > + occu = strtoul(token_array[3], NULL, 0); > + runs++; > + > + if (occu > max_occu) > + max_occu = occu; > + > + if (occu > ceiling) { > + ksft_print_msg("Fail: run %d occupancy %lu exceeds > ceiling %lu\n", > + runs, occu, ceiling); > + fail = 1; KSFT_FAIL is available to avoid using magic numbers. > + } > + } > + fclose(fp); > + > + if (!runs) { > + ksft_print_msg("No occupancy samples collected\n"); > + return -1; > + } > + > + if (max_occu < floor) { > + ksft_print_msg("Fail: peak occupancy %lu never reached floor > %lu\n", > + max_occu, floor); I think this test should be dropped. We cannot control the environments in which the tests are run and legitimate interference may cause this test to fail without it meaning that there is a bug in resctrl. > + fail = 1; > + } > + > + ksft_print_msg("%s CAT confines occupancy to the allocated %d-bit > portion\n", > + fail ? "Fail:" : "Pass:", no_of_bits); > + ksft_print_msg("occupancy=%lu alloc=%zu full=%zu ceiling=%lu > floor=%lu\n", > + max_occu, alloc_span, cache_size, ceiling, floor); > + > + return fail; > +} > + > +static void cat_occup_test_cleanup(void) > +{ > + remove(CAT_OCCUP_RESULT_FILE); > +} > + > +static int cat_occup_run_test(const struct resctrl_test *test, > + const struct user_params *uparams) > +{ > + struct fill_buf_param fill_buf = {}; > + unsigned long cache_total_size = 0; > + unsigned long full_mask; > + int count_of_bits; > + size_t alloc_span; > + int n, ret; > + > + ret = get_full_cbm(test->resource, &full_mask); > + if (ret) > + return ret; > + > + ret = get_cache_size(uparams->cpu, test->resource, &cache_total_size); > + if (ret) > + return ret; > + ksft_print_msg("Cache size :%lu\n", cache_total_size); > + > + count_of_bits = count_bits(full_mask); > + > + /* > + * Allocate a strict subset of the cache so the benchmark buffer > + * is larger than the allocation and CAT has something to enforce. > + */ > + n = uparams->bits ? : count_of_bits / 2; > + if (n < 1 || n >= count_of_bits) { > + ksft_print_msg("Invalid number of CBM bits %d, expected 1 to > %d\n", > + n, count_of_bits - 1); > + return -1; > + } > + > + struct resctrl_val_param param = { > + .ctrlgrp = "c1", > + .filename = CAT_OCCUP_RESULT_FILE, > + .mask = ~(full_mask << n) & full_mask, > + .num_of_runs = 0, > + .init = cat_occup_init, > + .setup = cat_occup_setup, > + .measure = cat_occup_measure, > + }; > + > + alloc_span = cache_portion_size(cache_total_size, param.mask, > full_mask); > + > + /* Benchmark buffer spans the full cache: larger than the allocation. */ > + fill_buf.buf_size = cache_total_size; > + fill_buf.memflush = uparams->fill_buf ? uparams->fill_buf->memflush : > true; > + param.fill_buf = &fill_buf; This prevents usage of user provided benchmark. Please compare with cmt_run_test() initialization. You can find more details about how the workload and parameters are communicated in e958c21e2ede ("selftests/resctrl: Make benchmark parameter passing robust") > + cat_occup_cpu = uparams->cpu; > + > + remove(param.filename); > + > + ret = resctrl_val(test, uparams, ¶m); > + if (ret) > + return ret; > + > + return cat_occup_check_results(¶m, alloc_span, cache_total_size, n); > +} Fundamentally this looks like a duplicate of cmt_run_test()? Only differences I see is the size of the buffer and how the test results are checked for pass/fail. Looking at the pass/fail I do not see a big difference with what the CMT test tests. I do not see what value this tests add beyond what the CMT test already provides. > + > +static bool cat_occup_feature_check(const struct resctrl_test *test) > +{ > + return test_resource_feature_check(test) && > + resctrl_mon_feature_exists("L3_MON", "llc_occupancy"); > +} > + > +struct resctrl_test l3_cat_occup_test = { > + .name = "L3_CAT_OCCUP", > + .group = "CAT", > + .resource = "L3", > + .feature_check = cat_occup_feature_check, > + .run_test = cat_occup_run_test, > + .cleanup = cat_occup_test_cleanup, > +}; > diff --git a/tools/testing/selftests/resctrl/cmt_test.c > b/tools/testing/selftests/resctrl/cmt_test.c > index d09e693dc739..ef51daa8061a 100644 > --- a/tools/testing/selftests/resctrl/cmt_test.c > +++ b/tools/testing/selftests/resctrl/cmt_test.c > @@ -16,9 +16,6 @@ > #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, > diff --git a/tools/testing/selftests/resctrl/resctrl.h > b/tools/testing/selftests/resctrl/resctrl.h > index afe635b6e48d..ce3abf0bdac2 100644 > --- a/tools/testing/selftests/resctrl/resctrl.h > +++ b/tools/testing/selftests/resctrl/resctrl.h > @@ -31,6 +31,9 @@ > #define PHYS_ID_PATH "/sys/devices/system/cpu/cpu" > #define INFO_PATH "/sys/fs/resctrl/info" > > +#define CON_MON_LCC_OCCUP_PATH \ > + "%s/%s/mon_data/mon_L3_%02d/llc_occupancy" > + > /* > * CPU vendor IDs > * > @@ -244,6 +247,7 @@ 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_cat_occup_test; > extern struct resctrl_test l3_noncont_cat_test; > extern struct resctrl_test l2_noncont_cat_test; > > diff --git a/tools/testing/selftests/resctrl/resctrl_tests.c > b/tools/testing/selftests/resctrl/resctrl_tests.c > index dbcd5eea9fbc..324a60818aa1 100644 > --- a/tools/testing/selftests/resctrl/resctrl_tests.c > +++ b/tools/testing/selftests/resctrl/resctrl_tests.c > @@ -19,6 +19,7 @@ static struct resctrl_test *resctrl_tests[] = { > &mba_test, > &cmt_test, > &l3_cat_test, > + &l3_cat_occup_test, > &l3_noncont_cat_test, > &l2_noncont_cat_test, > }; Reinette

