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.
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 + * 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). + */ +#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) +{ + 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; +} + +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); +} + +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; + int runs = 0; + int fail = 0; + FILE *fp; + + /* + * Check every sample, not an average: CAT is a hard limit, so a single + * 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; + } + } + 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); + 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; + 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); +} + +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, }; -- 2.43.0

