| 1 | |
| 2 | |
| 3 | * Resctrl tests
|
| 4 | *
|
| 5 | * Copyright (C) 2018 Intel Corporation
|
| 6 | *
|
| 7 | * Authors:
|
| 8 | * Sai Praneeth Prakhya <[email protected]>,
|
| 9 | * Fenghua Yu <[email protected]>
|
| 10 |
|
| 11 | #include "resctrl.h"
|
| 12 |
|
| 13 |
|
| 14 | static volatile int sink_target;
|
| 15 | volatile int *value_sink = &sink_target;
|
| 16 |
|
| 17 | static struct resctrl_test *resctrl_tests[] = {
|
| 18 | &mbm_test,
|
| 19 | &mba_test,
|
| 20 | &cmt_test,
|
| 21 | &l3_cat_test,
|
| 22 | &l3_noncont_cat_test,
|
| 23 | &l2_noncont_cat_test,
|
| 24 | };
|
| 25 |
|
| 26 | static unsigned int detect_vendor(void)
|
| 27 | {
|
| 28 | static unsigned int vendor_id;
|
| 29 | static bool initialized;
|
| 30 | char *s = NULL;
|
| 31 | FILE *inf;
|
| 32 | char *res;
|
| 33 |
|
| 34 | if (initialized)
|
| 35 | return vendor_id;
|
| 36 |
|
| 37 | inf = fopen("/proc/cpuinfo", "r");
|
| 38 | if (!inf) {
|
| 39 | vendor_id = 0;
|
| 40 | initialized = true;
|
| 41 | return vendor_id;
|
| 42 | }
|
| 43 |
|
| 44 | res = fgrep(inf, "vendor_id");
|
| 45 |
|
| 46 | if (res)
|
| 47 | s = strchr(res, ':');
|
| 48 |
|
| 49 | if (s && !strcmp(s, ": GenuineIntel\n"))
|
| 50 | vendor_id = ARCH_INTEL;
|
| 51 | else if (s && !strcmp(s, ": AuthenticAMD\n"))
|
| 52 | vendor_id = ARCH_AMD;
|
| 53 | else if (s && !strcmp(s, ": HygonGenuine\n"))
|
| 54 | vendor_id = ARCH_HYGON;
|
| 55 |
|
| 56 | fclose(inf);
|
| 57 | free(res);
|
| 58 |
|
| 59 | initialized = true;
|
| 60 | return vendor_id;
|
| 61 | }
|
| 62 |
|
| 63 | unsigned int get_vendor(void)
|
| 64 | {
|
| 65 | unsigned int vendor;
|
| 66 |
|
| 67 | vendor = detect_vendor();
|
| 68 |
|
| 69 | if (vendor == 0)
|
| 70 | ksft_print_msg("Can not get vendor info...\n");
|
| 71 |
|
| 72 | return vendor;
|
| 73 | }
|
| 74 |
|
| 75 | static void cmd_help(void)
|
| 76 | {
|
| 77 | int i;
|
| 78 |
|
| 79 | printf("usage: resctrl_tests [-h] [-t test list] [-n no_of_bits] [-b benchmark_cmd [option]...]\n");
|
| 80 | printf("\t-b benchmark_cmd [option]...: run specified benchmark for MBM, MBA and CMT\n");
|
| 81 | printf("\t default benchmark is builtin fill_buf\n");
|
| 82 | printf("\t-t test list: run tests/groups specified by the list, ");
|
| 83 | printf("e.g. -t mbm,mba,cmt,cat\n");
|
| 84 | printf("\t\tSupported tests (group):\n");
|
| 85 | for (i = 0; i < ARRAY_SIZE(resctrl_tests); i++) {
|
| 86 | if (resctrl_tests[i]->group)
|
| 87 | printf("\t\t\t%s (%s)\n", resctrl_tests[i]->name, resctrl_tests[i]->group);
|
| 88 | else
|
| 89 | printf("\t\t\t%s\n", resctrl_tests[i]->name);
|
| 90 | }
|
| 91 | printf("\t-n no_of_bits: run cache tests using specified no of bits in cache bit mask\n");
|
| 92 | printf("\t-p cpu_no: specify CPU number to run the test. 1 is default\n");
|
| 93 | printf("\t-h: help\n");
|
| 94 | }
|
| 95 |
|
| 96 | static int test_prepare(const struct resctrl_test *test)
|
| 97 | {
|
| 98 | int res;
|
| 99 |
|
| 100 | res = signal_handler_register(test);
|
| 101 | if (res) {
|
| 102 | ksft_print_msg("Failed to register signal handler\n");
|
| 103 | return res;
|
| 104 | }
|
| 105 |
|
| 106 | res = mount_resctrlfs();
|
| 107 | if (res) {
|
| 108 | signal_handler_unregister();
|
| 109 | ksft_print_msg("Failed to mount resctrl FS\n");
|
| 110 | return res;
|
| 111 | }
|
| 112 | return 0;
|
| 113 | }
|
| 114 |
|
| 115 | static void test_cleanup(const struct resctrl_test *test)
|
| 116 | {
|
| 117 | if (test->cleanup)
|
| 118 | test->cleanup();
|
| 119 | umount_resctrlfs();
|
| 120 | signal_handler_unregister();
|
| 121 | }
|
| 122 |
|
| 123 | static bool test_vendor_specific_check(const struct resctrl_test *test)
|
| 124 | {
|
| 125 | if (!test->vendor_specific)
|
| 126 | return true;
|
| 127 |
|
| 128 | return get_vendor() & test->vendor_specific;
|
| 129 | }
|
| 130 |
|
| 131 | static void run_single_test(const struct resctrl_test *test, const struct user_params *uparams)
|
| 132 | {
|
| 133 | int ret, snc_mode;
|
| 134 |
|
| 135 | if (test->disabled)
|
| 136 | return;
|
| 137 |
|
| 138 | if (!test_vendor_specific_check(test)) {
|
| 139 | ksft_test_result_skip("Hardware does not support %s\n", test->name);
|
| 140 | return;
|
| 141 | }
|
| 142 |
|
| 143 | snc_mode = snc_nodes_per_l3_cache();
|
| 144 |
|
| 145 | ksft_print_msg("Starting %s test ...\n", test->name);
|
| 146 |
|
| 147 | if (snc_mode == 1 && snc_unreliable && get_vendor() == ARCH_INTEL) {
|
| 148 | ksft_test_result_skip("SNC detection unreliable due to offline CPUs. Test results may not be accurate if SNC enabled.\n");
|
| 149 | return;
|
| 150 | }
|
| 151 |
|
| 152 | if (test_prepare(test)) {
|
| 153 | ksft_exit_fail_msg("Abnormal failure when preparing for the test\n");
|
| 154 | return;
|
| 155 | }
|
| 156 |
|
| 157 | if (!test->feature_check(test)) {
|
| 158 | ksft_test_result_skip("Hardware does not support %s or %s is disabled\n",
|
| 159 | test->name, test->name);
|
| 160 | goto cleanup;
|
| 161 | }
|
| 162 |
|
| 163 | ret = test->run_test(test, uparams);
|
| 164 | ksft_test_result(!ret, "%s: test\n", test->name);
|
| 165 |
|
| 166 | cleanup:
|
| 167 | test_cleanup(test);
|
| 168 | }
|
| 169 |
|
| 170 | |
| 171 | * Allocate and initialize a struct fill_buf_param with user provided
|
| 172 | * (via "-b fill_buf <fill_buf parameters>") parameters.
|
| 173 | *
|
| 174 | * Use defaults (that may not be appropriate for all tests) for any
|
| 175 | * fill_buf parameters omitted by the user.
|
| 176 | *
|
| 177 | * Historically it may have been possible for user space to provide
|
| 178 | * additional parameters, "operation" ("read" vs "write") in
|
| 179 | * benchmark_cmd[3] and "once" (run "once" or until terminated) in
|
| 180 | * benchmark_cmd[4]. Changing these parameters have never been
|
| 181 | * supported with the default of "read" operation and running until
|
| 182 | * terminated built into the tests. Any unsupported values for
|
| 183 | * (original) "fill_buf" parameters are treated as failure.
|
| 184 | *
|
| 185 | * Return: On failure, forcibly exits the test on any parsing failure,
|
| 186 | * returns NULL if no parsing needed (user did not actually provide
|
| 187 | * "-b fill_buf").
|
| 188 | * On success, returns pointer to newly allocated and fully
|
| 189 | * initialized struct fill_buf_param that caller must free.
|
| 190 |
|
| 191 | static struct fill_buf_param *alloc_fill_buf_param(struct user_params *uparams)
|
| 192 | {
|
| 193 | struct fill_buf_param *fill_param = NULL;
|
| 194 | char *endptr = NULL;
|
| 195 |
|
| 196 | if (!uparams->benchmark_cmd[0] || strcmp(uparams->benchmark_cmd[0], "fill_buf"))
|
| 197 | return NULL;
|
| 198 |
|
| 199 | fill_param = malloc(sizeof(*fill_param));
|
| 200 | if (!fill_param)
|
| 201 | ksft_exit_skip("Unable to allocate memory for fill_buf parameters.\n");
|
| 202 |
|
| 203 | if (uparams->benchmark_cmd[1] && *uparams->benchmark_cmd[1] != '\0') {
|
| 204 | errno = 0;
|
| 205 | fill_param->buf_size = strtoul(uparams->benchmark_cmd[1], &endptr, 10);
|
| 206 | if (errno || *endptr != '\0') {
|
| 207 | free(fill_param);
|
| 208 | ksft_exit_skip("Unable to parse benchmark buffer size.\n");
|
| 209 | }
|
| 210 | } else {
|
| 211 | fill_param->buf_size = MINIMUM_SPAN;
|
| 212 | }
|
| 213 |
|
| 214 | if (uparams->benchmark_cmd[2] && *uparams->benchmark_cmd[2] != '\0') {
|
| 215 | errno = 0;
|
| 216 | fill_param->memflush = strtol(uparams->benchmark_cmd[2], &endptr, 10) != 0;
|
| 217 | if (errno || *endptr != '\0') {
|
| 218 | free(fill_param);
|
| 219 | ksft_exit_skip("Unable to parse benchmark memflush parameter.\n");
|
| 220 | }
|
| 221 | } else {
|
| 222 | fill_param->memflush = true;
|
| 223 | }
|
| 224 |
|
| 225 | if (uparams->benchmark_cmd[3] && *uparams->benchmark_cmd[3] != '\0') {
|
| 226 | if (strcmp(uparams->benchmark_cmd[3], "0")) {
|
| 227 | free(fill_param);
|
| 228 | ksft_exit_skip("Only read operations supported.\n");
|
| 229 | }
|
| 230 | }
|
| 231 |
|
| 232 | if (uparams->benchmark_cmd[4] && *uparams->benchmark_cmd[4] != '\0') {
|
| 233 | if (strcmp(uparams->benchmark_cmd[4], "false")) {
|
| 234 | free(fill_param);
|
| 235 | ksft_exit_skip("fill_buf is required to run until termination.\n");
|
| 236 | }
|
| 237 | }
|
| 238 |
|
| 239 | return fill_param;
|
| 240 | }
|
| 241 |
|
| 242 | static void init_user_params(struct user_params *uparams)
|
| 243 | {
|
| 244 | memset(uparams, 0, sizeof(*uparams));
|
| 245 |
|
| 246 | uparams->cpu = 1;
|
| 247 | uparams->bits = 0;
|
| 248 | }
|
| 249 |
|
| 250 | int main(int argc, char **argv)
|
| 251 | {
|
| 252 | struct fill_buf_param *fill_param = NULL;
|
| 253 | int tests = ARRAY_SIZE(resctrl_tests);
|
| 254 | bool test_param_seen = false;
|
| 255 | struct user_params uparams;
|
| 256 | int c, i;
|
| 257 |
|
| 258 | init_user_params(&uparams);
|
| 259 |
|
| 260 | while ((c = getopt(argc, argv, "ht:b:n:p:")) != -1) {
|
| 261 | char *token;
|
| 262 |
|
| 263 | switch (c) {
|
| 264 | case 'b':
|
| 265 | |
| 266 | * First move optind back to the (first) optarg and
|
| 267 | * then build the benchmark command using the
|
| 268 | * remaining arguments.
|
| 269 |
|
| 270 | optind--;
|
| 271 | if (argc - optind >= BENCHMARK_ARGS)
|
| 272 | ksft_exit_fail_msg("Too long benchmark command");
|
| 273 |
|
| 274 |
|
| 275 | for (i = 0; i < argc - optind; i++)
|
| 276 | uparams.benchmark_cmd[i] = argv[i + optind];
|
| 277 | uparams.benchmark_cmd[i] = NULL;
|
| 278 |
|
| 279 | goto last_arg;
|
| 280 | case 't':
|
| 281 | token = strtok(optarg, ",");
|
| 282 |
|
| 283 | if (!test_param_seen) {
|
| 284 | for (i = 0; i < ARRAY_SIZE(resctrl_tests); i++)
|
| 285 | resctrl_tests[i]->disabled = true;
|
| 286 | tests = 0;
|
| 287 | test_param_seen = true;
|
| 288 | }
|
| 289 | while (token) {
|
| 290 | bool found = false;
|
| 291 |
|
| 292 | for (i = 0; i < ARRAY_SIZE(resctrl_tests); i++) {
|
| 293 | if (!strcasecmp(token, resctrl_tests[i]->name) ||
|
| 294 | (resctrl_tests[i]->group &&
|
| 295 | !strcasecmp(token, resctrl_tests[i]->group))) {
|
| 296 | if (resctrl_tests[i]->disabled)
|
| 297 | tests++;
|
| 298 | resctrl_tests[i]->disabled = false;
|
| 299 | found = true;
|
| 300 | }
|
| 301 | }
|
| 302 |
|
| 303 | if (!found) {
|
| 304 | printf("invalid test: %s\n", token);
|
| 305 |
|
| 306 | return -1;
|
| 307 | }
|
| 308 | token = strtok(NULL, ",");
|
| 309 | }
|
| 310 | break;
|
| 311 | case 'p':
|
| 312 | uparams.cpu = atoi(optarg);
|
| 313 | break;
|
| 314 | case 'n':
|
| 315 | uparams.bits = atoi(optarg);
|
| 316 | if (uparams.bits <= 0) {
|
| 317 | printf("Bail out! invalid argument for no_of_bits\n");
|
| 318 | return -1;
|
| 319 | }
|
| 320 | break;
|
| 321 | case 'h':
|
| 322 | cmd_help();
|
| 323 |
|
| 324 | return 0;
|
| 325 | default:
|
| 326 | printf("invalid argument\n");
|
| 327 |
|
| 328 | return -1;
|
| 329 | }
|
| 330 | }
|
| 331 | last_arg:
|
| 332 |
|
| 333 | fill_param = alloc_fill_buf_param(&uparams);
|
| 334 | if (fill_param)
|
| 335 | uparams.fill_buf = fill_param;
|
| 336 |
|
| 337 | ksft_print_header();
|
| 338 |
|
| 339 | |
| 340 | * Typically we need root privileges, because:
|
| 341 | * 1. We write to resctrl FS
|
| 342 | * 2. We execute perf commands
|
| 343 |
|
| 344 | if (geteuid() != 0)
|
| 345 | ksft_exit_skip("Not running as root. Skipping...\n");
|
| 346 |
|
| 347 | if (!check_resctrlfs_support())
|
| 348 | ksft_exit_skip("resctrl FS does not exist. Enable X86_CPU_RESCTRL config option.\n");
|
| 349 |
|
| 350 | if (umount_resctrlfs())
|
| 351 | ksft_exit_skip("resctrl FS unmount failed.\n");
|
| 352 |
|
| 353 | filter_dmesg();
|
| 354 |
|
| 355 | ksft_set_plan(tests);
|
| 356 |
|
| 357 | for (i = 0; i < ARRAY_SIZE(resctrl_tests); i++)
|
| 358 | run_single_test(resctrl_tests[i], &uparams);
|
| 359 |
|
| 360 | free(fill_param);
|
| 361 | ksft_finished();
|
| 362 | }
|