linux/tools/perf/tests/pmu-events.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2#include "math.h"
   3#include "parse-events.h"
   4#include "pmu.h"
   5#include "tests.h"
   6#include <errno.h>
   7#include <stdio.h>
   8#include <linux/kernel.h>
   9#include <linux/zalloc.h>
  10#include "debug.h"
  11#include "../pmu-events/pmu-events.h"
  12#include "util/evlist.h"
  13#include "util/expr.h"
  14#include "util/parse-events.h"
  15#include "metricgroup.h"
  16
  17struct perf_pmu_test_event {
  18        /* used for matching against events from generated pmu-events.c */
  19        struct pmu_event event;
  20
  21        /* used for matching against event aliases */
  22        /* extra events for aliases */
  23        const char *alias_str;
  24
  25        /*
  26         * Note: For when PublicDescription does not exist in the JSON, we
  27         * will have no long_desc in pmu_event.long_desc, but long_desc may
  28         * be set in the alias.
  29         */
  30        const char *alias_long_desc;
  31};
  32
  33static struct perf_pmu_test_event test_cpu_events[] = {
  34        {
  35                .event = {
  36                        .name = "bp_l1_btb_correct",
  37                        .event = "event=0x8a",
  38                        .desc = "L1 BTB Correction",
  39                        .topic = "branch",
  40                },
  41                .alias_str = "event=0x8a",
  42                .alias_long_desc = "L1 BTB Correction",
  43        },
  44        {
  45                .event = {
  46                        .name = "bp_l2_btb_correct",
  47                        .event = "event=0x8b",
  48                        .desc = "L2 BTB Correction",
  49                        .topic = "branch",
  50                },
  51                .alias_str = "event=0x8b",
  52                .alias_long_desc = "L2 BTB Correction",
  53        },
  54        {
  55                .event = {
  56                        .name = "segment_reg_loads.any",
  57                        .event = "umask=0x80,period=200000,event=0x6",
  58                        .desc = "Number of segment register loads",
  59                        .topic = "other",
  60                },
  61                .alias_str = "umask=0x80,(null)=0x30d40,event=0x6",
  62                .alias_long_desc = "Number of segment register loads",
  63        },
  64        {
  65                .event = {
  66                        .name = "dispatch_blocked.any",
  67                        .event = "umask=0x20,period=200000,event=0x9",
  68                        .desc = "Memory cluster signals to block micro-op dispatch for any reason",
  69                        .topic = "other",
  70                },
  71                .alias_str = "umask=0x20,(null)=0x30d40,event=0x9",
  72                .alias_long_desc = "Memory cluster signals to block micro-op dispatch for any reason",
  73        },
  74        {
  75                .event = {
  76                        .name = "eist_trans",
  77                        .event = "umask=0x0,period=200000,event=0x3a",
  78                        .desc = "Number of Enhanced Intel SpeedStep(R) Technology (EIST) transitions",
  79                        .topic = "other",
  80                },
  81                .alias_str = "umask=0,(null)=0x30d40,event=0x3a",
  82                .alias_long_desc = "Number of Enhanced Intel SpeedStep(R) Technology (EIST) transitions",
  83        },
  84        {
  85                .event = {
  86                        .name = "l3_cache_rd",
  87                        .event = "event=0x40",
  88                        .desc = "L3 cache access, read",
  89                        .long_desc = "Attributable Level 3 cache access, read",
  90                        .topic = "cache",
  91                },
  92                .alias_str = "event=0x40",
  93                .alias_long_desc = "Attributable Level 3 cache access, read",
  94        },
  95        { /* sentinel */
  96                .event = {
  97                        .name = NULL,
  98                },
  99        },
 100};
 101
 102static struct perf_pmu_test_event test_uncore_events[] = {
 103        {
 104                .event = {
 105                        .name = "uncore_hisi_ddrc.flux_wcmd",
 106                        .event = "event=0x2",
 107                        .desc = "DDRC write commands. Unit: hisi_sccl,ddrc ",
 108                        .topic = "uncore",
 109                        .long_desc = "DDRC write commands",
 110                        .pmu = "hisi_sccl,ddrc",
 111                },
 112                .alias_str = "event=0x2",
 113                .alias_long_desc = "DDRC write commands",
 114        },
 115        {
 116                .event = {
 117                        .name = "unc_cbo_xsnp_response.miss_eviction",
 118                        .event = "umask=0x81,event=0x22",
 119                        .desc = "Unit: uncore_cbox A cross-core snoop resulted from L3 Eviction which misses in some processor core",
 120                        .topic = "uncore",
 121                        .long_desc = "A cross-core snoop resulted from L3 Eviction which misses in some processor core",
 122                        .pmu = "uncore_cbox",
 123                },
 124                .alias_str = "umask=0x81,event=0x22",
 125                .alias_long_desc = "A cross-core snoop resulted from L3 Eviction which misses in some processor core",
 126        },
 127        { /* sentinel */
 128                .event = {
 129                        .name = NULL,
 130                },
 131        }
 132};
 133
 134const int total_test_events_size = ARRAY_SIZE(test_uncore_events);
 135
 136static bool is_same(const char *reference, const char *test)
 137{
 138        if (!reference && !test)
 139                return true;
 140
 141        if (reference && !test)
 142                return false;
 143
 144        if (!reference && test)
 145                return false;
 146
 147        return !strcmp(reference, test);
 148}
 149
 150static struct pmu_events_map *__test_pmu_get_events_map(void)
 151{
 152        struct pmu_events_map *map;
 153
 154        for (map = &pmu_events_map[0]; map->cpuid; map++) {
 155                if (!strcmp(map->cpuid, "testcpu"))
 156                        return map;
 157        }
 158
 159        pr_err("could not find test events map\n");
 160
 161        return NULL;
 162}
 163
 164/* Verify generated events from pmu-events.c is as expected */
 165static int test_pmu_event_table(void)
 166{
 167        struct pmu_events_map *map = __test_pmu_get_events_map();
 168        struct pmu_event *table;
 169        int map_events = 0, expected_events;
 170
 171        /* ignore 2x sentinels */
 172        expected_events = ARRAY_SIZE(test_cpu_events) +
 173                          ARRAY_SIZE(test_uncore_events) - 2;
 174
 175        if (!map)
 176                return -1;
 177
 178        for (table = map->table; table->name; table++) {
 179                struct perf_pmu_test_event *test;
 180                struct pmu_event *te;
 181                bool found = false;
 182
 183                if (table->pmu)
 184                        test = &test_uncore_events[0];
 185                else
 186                        test = &test_cpu_events[0];
 187
 188                te = &test->event;
 189
 190                for (; te->name; test++, te = &test->event) {
 191                        if (strcmp(table->name, te->name))
 192                                continue;
 193                        found = true;
 194                        map_events++;
 195
 196                        if (!is_same(table->desc, te->desc)) {
 197                                pr_debug2("testing event table %s: mismatched desc, %s vs %s\n",
 198                                          table->name, table->desc, te->desc);
 199                                return -1;
 200                        }
 201
 202                        if (!is_same(table->topic, te->topic)) {
 203                                pr_debug2("testing event table %s: mismatched topic, %s vs %s\n",
 204                                          table->name, table->topic,
 205                                          te->topic);
 206                                return -1;
 207                        }
 208
 209                        if (!is_same(table->long_desc, te->long_desc)) {
 210                                pr_debug2("testing event table %s: mismatched long_desc, %s vs %s\n",
 211                                          table->name, table->long_desc,
 212                                          te->long_desc);
 213                                return -1;
 214                        }
 215
 216                        if (!is_same(table->unit, te->unit)) {
 217                                pr_debug2("testing event table %s: mismatched unit, %s vs %s\n",
 218                                          table->name, table->unit,
 219                                          te->unit);
 220                                return -1;
 221                        }
 222
 223                        if (!is_same(table->perpkg, te->perpkg)) {
 224                                pr_debug2("testing event table %s: mismatched perpkg, %s vs %s\n",
 225                                          table->name, table->perpkg,
 226                                          te->perpkg);
 227                                return -1;
 228                        }
 229
 230                        if (!is_same(table->metric_expr, te->metric_expr)) {
 231                                pr_debug2("testing event table %s: mismatched metric_expr, %s vs %s\n",
 232                                          table->name, table->metric_expr,
 233                                          te->metric_expr);
 234                                return -1;
 235                        }
 236
 237                        if (!is_same(table->metric_name, te->metric_name)) {
 238                                pr_debug2("testing event table %s: mismatched metric_name, %s vs %s\n",
 239                                          table->name,  table->metric_name,
 240                                          te->metric_name);
 241                                return -1;
 242                        }
 243
 244                        if (!is_same(table->deprecated, te->deprecated)) {
 245                                pr_debug2("testing event table %s: mismatched deprecated, %s vs %s\n",
 246                                          table->name, table->deprecated,
 247                                          te->deprecated);
 248                                return -1;
 249                        }
 250
 251                        pr_debug("testing event table %s: pass\n", table->name);
 252                }
 253
 254                if (!found) {
 255                        pr_err("testing event table: could not find event %s\n",
 256                               table->name);
 257                        return -1;
 258                }
 259        }
 260
 261        if (map_events != expected_events) {
 262                pr_err("testing event table: found %d, but expected %d\n",
 263                       map_events, expected_events);
 264                return -1;
 265        }
 266
 267        return 0;
 268}
 269
 270static struct perf_pmu_alias *find_alias(const char *test_event, struct list_head *aliases)
 271{
 272        struct perf_pmu_alias *alias;
 273
 274        list_for_each_entry(alias, aliases, list)
 275                if (!strcmp(test_event, alias->name))
 276                        return alias;
 277
 278        return NULL;
 279}
 280
 281/* Verify aliases are as expected */
 282static int __test__pmu_event_aliases(char *pmu_name, int *count)
 283{
 284        struct perf_pmu_test_event *test;
 285        struct pmu_event *te;
 286        struct perf_pmu *pmu;
 287        LIST_HEAD(aliases);
 288        int res = 0;
 289        bool use_uncore_table;
 290        struct pmu_events_map *map = __test_pmu_get_events_map();
 291        struct perf_pmu_alias *a, *tmp;
 292
 293        if (!map)
 294                return -1;
 295
 296        if (is_pmu_core(pmu_name)) {
 297                test = &test_cpu_events[0];
 298                use_uncore_table = false;
 299        } else {
 300                test = &test_uncore_events[0];
 301                use_uncore_table = true;
 302        }
 303
 304        pmu = zalloc(sizeof(*pmu));
 305        if (!pmu)
 306                return -1;
 307
 308        pmu->name = pmu_name;
 309
 310        pmu_add_cpu_aliases_map(&aliases, pmu, map);
 311
 312        for (te = &test->event; te->name; test++, te = &test->event) {
 313                struct perf_pmu_alias *alias = find_alias(te->name, &aliases);
 314
 315                if (!alias) {
 316                        bool uncore_match = pmu_uncore_alias_match(pmu_name,
 317                                                                   te->pmu);
 318
 319                        if (use_uncore_table && !uncore_match) {
 320                                pr_debug3("testing aliases PMU %s: skip matching alias %s\n",
 321                                          pmu_name, te->name);
 322                                continue;
 323                        }
 324
 325                        pr_debug2("testing aliases PMU %s: no alias, alias_table->name=%s\n",
 326                                  pmu_name, te->name);
 327                        res = -1;
 328                        break;
 329                }
 330
 331                if (!is_same(alias->desc, te->desc)) {
 332                        pr_debug2("testing aliases PMU %s: mismatched desc, %s vs %s\n",
 333                                  pmu_name, alias->desc, te->desc);
 334                        res = -1;
 335                        break;
 336                }
 337
 338                if (!is_same(alias->long_desc, test->alias_long_desc)) {
 339                        pr_debug2("testing aliases PMU %s: mismatched long_desc, %s vs %s\n",
 340                                  pmu_name, alias->long_desc,
 341                                  test->alias_long_desc);
 342                        res = -1;
 343                        break;
 344                }
 345
 346                if (!is_same(alias->str, test->alias_str)) {
 347                        pr_debug2("testing aliases PMU %s: mismatched str, %s vs %s\n",
 348                                  pmu_name, alias->str, test->alias_str);
 349                        res = -1;
 350                        break;
 351                }
 352
 353                if (!is_same(alias->topic, te->topic)) {
 354                        pr_debug2("testing aliases PMU %s: mismatched topic, %s vs %s\n",
 355                                  pmu_name, alias->topic, te->topic);
 356                        res = -1;
 357                        break;
 358                }
 359
 360                (*count)++;
 361                pr_debug2("testing aliases PMU %s: matched event %s\n",
 362                          pmu_name, alias->name);
 363        }
 364
 365        list_for_each_entry_safe(a, tmp, &aliases, list) {
 366                list_del(&a->list);
 367                perf_pmu_free_alias(a);
 368        }
 369        free(pmu);
 370        return res;
 371}
 372
 373
 374/* Test that aliases generated are as expected */
 375static int test_aliases(void)
 376{
 377        struct perf_pmu *pmu = NULL;
 378
 379        while ((pmu = perf_pmu__scan(pmu)) != NULL) {
 380                int count = 0;
 381
 382                if (list_empty(&pmu->format)) {
 383                        pr_debug2("skipping testing PMU %s\n", pmu->name);
 384                        continue;
 385                }
 386
 387                if (__test__pmu_event_aliases(pmu->name, &count)) {
 388                        pr_debug("testing PMU %s aliases: failed\n", pmu->name);
 389                        return -1;
 390                }
 391
 392                if (count == 0)
 393                        pr_debug3("testing PMU %s aliases: no events to match\n",
 394                                  pmu->name);
 395                else
 396                        pr_debug("testing PMU %s aliases: pass\n", pmu->name);
 397        }
 398
 399        return 0;
 400}
 401
 402static bool is_number(const char *str)
 403{
 404        char *end_ptr;
 405        double v;
 406
 407        errno = 0;
 408        v = strtod(str, &end_ptr);
 409        (void)v; // We're not interested in this value, only if it is valid
 410        return errno == 0 && end_ptr != str;
 411}
 412
 413static int check_parse_id(const char *id, struct parse_events_error *error,
 414                          struct perf_pmu *fake_pmu)
 415{
 416        struct evlist *evlist;
 417        int ret;
 418
 419        /* Numbers are always valid. */
 420        if (is_number(id))
 421                return 0;
 422
 423        evlist = evlist__new();
 424        if (!evlist)
 425                return -ENOMEM;
 426        ret = __parse_events(evlist, id, error, fake_pmu);
 427        evlist__delete(evlist);
 428        return ret;
 429}
 430
 431static int check_parse_cpu(const char *id, bool same_cpu, struct pmu_event *pe)
 432{
 433        struct parse_events_error error = { .idx = 0, };
 434
 435        int ret = check_parse_id(id, &error, NULL);
 436        if (ret && same_cpu) {
 437                pr_warning("Parse event failed metric '%s' id '%s' expr '%s'\n",
 438                        pe->metric_name, id, pe->metric_expr);
 439                pr_warning("Error string '%s' help '%s'\n", error.str,
 440                        error.help);
 441        } else if (ret) {
 442                pr_debug3("Parse event failed, but for an event that may not be supported by this CPU.\nid '%s' metric '%s' expr '%s'\n",
 443                          id, pe->metric_name, pe->metric_expr);
 444                ret = 0;
 445        }
 446        free(error.str);
 447        free(error.help);
 448        free(error.first_str);
 449        free(error.first_help);
 450        return ret;
 451}
 452
 453static int check_parse_fake(const char *id)
 454{
 455        struct parse_events_error error = { .idx = 0, };
 456        int ret = check_parse_id(id, &error, &perf_pmu__fake);
 457
 458        free(error.str);
 459        free(error.help);
 460        free(error.first_str);
 461        free(error.first_help);
 462        return ret;
 463}
 464
 465static void expr_failure(const char *msg,
 466                         const struct pmu_events_map *map,
 467                         const struct pmu_event *pe)
 468{
 469        pr_debug("%s for map %s %s %s\n",
 470                msg, map->cpuid, map->version, map->type);
 471        pr_debug("On metric %s\n", pe->metric_name);
 472        pr_debug("On expression %s\n", pe->metric_expr);
 473}
 474
 475struct metric {
 476        struct list_head list;
 477        struct metric_ref metric_ref;
 478};
 479
 480static int resolve_metric_simple(struct expr_parse_ctx *pctx,
 481                                 struct list_head *compound_list,
 482                                 struct pmu_events_map *map,
 483                                 const char *metric_name)
 484{
 485        struct hashmap_entry *cur, *cur_tmp;
 486        struct metric *metric, *tmp;
 487        size_t bkt;
 488        bool all;
 489        int rc;
 490
 491        do {
 492                all = true;
 493                hashmap__for_each_entry_safe((&pctx->ids), cur, cur_tmp, bkt) {
 494                        struct metric_ref *ref;
 495                        struct pmu_event *pe;
 496
 497                        pe = metricgroup__find_metric(cur->key, map);
 498                        if (!pe)
 499                                continue;
 500
 501                        if (!strcmp(metric_name, (char *)cur->key)) {
 502                                pr_warning("Recursion detected for metric %s\n", metric_name);
 503                                rc = -1;
 504                                goto out_err;
 505                        }
 506
 507                        all = false;
 508
 509                        /* The metric key itself needs to go out.. */
 510                        expr__del_id(pctx, cur->key);
 511
 512                        metric = malloc(sizeof(*metric));
 513                        if (!metric) {
 514                                rc = -ENOMEM;
 515                                goto out_err;
 516                        }
 517
 518                        ref = &metric->metric_ref;
 519                        ref->metric_name = pe->metric_name;
 520                        ref->metric_expr = pe->metric_expr;
 521                        list_add_tail(&metric->list, compound_list);
 522
 523                        rc = expr__find_other(pe->metric_expr, NULL, pctx, 0);
 524                        if (rc)
 525                                goto out_err;
 526                        break; /* The hashmap has been modified, so restart */
 527                }
 528        } while (!all);
 529
 530        return 0;
 531
 532out_err:
 533        list_for_each_entry_safe(metric, tmp, compound_list, list)
 534                free(metric);
 535
 536        return rc;
 537
 538}
 539
 540static int test_parsing(void)
 541{
 542        struct pmu_events_map *cpus_map = pmu_events_map__find();
 543        struct pmu_events_map *map;
 544        struct pmu_event *pe;
 545        int i, j, k;
 546        int ret = 0;
 547        struct expr_parse_ctx ctx;
 548        double result;
 549
 550        i = 0;
 551        for (;;) {
 552                map = &pmu_events_map[i++];
 553                if (!map->table)
 554                        break;
 555                j = 0;
 556                for (;;) {
 557                        struct metric *metric, *tmp;
 558                        struct hashmap_entry *cur;
 559                        LIST_HEAD(compound_list);
 560                        size_t bkt;
 561
 562                        pe = &map->table[j++];
 563                        if (!pe->name && !pe->metric_group && !pe->metric_name)
 564                                break;
 565                        if (!pe->metric_expr)
 566                                continue;
 567                        expr__ctx_init(&ctx);
 568                        if (expr__find_other(pe->metric_expr, NULL, &ctx, 0)
 569                                  < 0) {
 570                                expr_failure("Parse other failed", map, pe);
 571                                ret++;
 572                                continue;
 573                        }
 574
 575                        if (resolve_metric_simple(&ctx, &compound_list, map,
 576                                                  pe->metric_name)) {
 577                                expr_failure("Could not resolve metrics", map, pe);
 578                                ret++;
 579                                goto exit; /* Don't tolerate errors due to severity */
 580                        }
 581
 582                        /*
 583                         * Add all ids with a made up value. The value may
 584                         * trigger divide by zero when subtracted and so try to
 585                         * make them unique.
 586                         */
 587                        k = 1;
 588                        hashmap__for_each_entry((&ctx.ids), cur, bkt)
 589                                expr__add_id_val(&ctx, strdup(cur->key), k++);
 590
 591                        hashmap__for_each_entry((&ctx.ids), cur, bkt) {
 592                                if (check_parse_cpu(cur->key, map == cpus_map,
 593                                                   pe))
 594                                        ret++;
 595                        }
 596
 597                        list_for_each_entry_safe(metric, tmp, &compound_list, list) {
 598                                expr__add_ref(&ctx, &metric->metric_ref);
 599                                free(metric);
 600                        }
 601
 602                        if (expr__parse(&result, &ctx, pe->metric_expr, 0)) {
 603                                expr_failure("Parse failed", map, pe);
 604                                ret++;
 605                        }
 606                        expr__ctx_clear(&ctx);
 607                }
 608        }
 609        /* TODO: fail when not ok */
 610exit:
 611        return ret == 0 ? TEST_OK : TEST_SKIP;
 612}
 613
 614struct test_metric {
 615        const char *str;
 616};
 617
 618static struct test_metric metrics[] = {
 619        { "(unc_p_power_state_occupancy.cores_c0 / unc_p_clockticks) * 100." },
 620        { "imx8_ddr0@read\\-cycles@ * 4 * 4", },
 621        { "imx8_ddr0@axid\\-read\\,axi_mask\\=0xffff\\,axi_id\\=0x0000@ * 4", },
 622        { "(cstate_pkg@c2\\-residency@ / msr@tsc@) * 100", },
 623        { "(imx8_ddr0@read\\-cycles@ + imx8_ddr0@write\\-cycles@)", },
 624};
 625
 626static int metric_parse_fake(const char *str)
 627{
 628        struct expr_parse_ctx ctx;
 629        struct hashmap_entry *cur;
 630        double result;
 631        int ret = -1;
 632        size_t bkt;
 633        int i;
 634
 635        pr_debug("parsing '%s'\n", str);
 636
 637        expr__ctx_init(&ctx);
 638        if (expr__find_other(str, NULL, &ctx, 0) < 0) {
 639                pr_err("expr__find_other failed\n");
 640                return -1;
 641        }
 642
 643        /*
 644         * Add all ids with a made up value. The value may
 645         * trigger divide by zero when subtracted and so try to
 646         * make them unique.
 647         */
 648        i = 1;
 649        hashmap__for_each_entry((&ctx.ids), cur, bkt)
 650                expr__add_id_val(&ctx, strdup(cur->key), i++);
 651
 652        hashmap__for_each_entry((&ctx.ids), cur, bkt) {
 653                if (check_parse_fake(cur->key)) {
 654                        pr_err("check_parse_fake failed\n");
 655                        goto out;
 656                }
 657        }
 658
 659        if (expr__parse(&result, &ctx, str, 0))
 660                pr_err("expr__parse failed\n");
 661        else
 662                ret = 0;
 663
 664out:
 665        expr__ctx_clear(&ctx);
 666        return ret;
 667}
 668
 669/*
 670 * Parse all the metrics for current architecture,
 671 * or all defined cpus via the 'fake_pmu'
 672 * in parse_events.
 673 */
 674static int test_parsing_fake(void)
 675{
 676        struct pmu_events_map *map;
 677        struct pmu_event *pe;
 678        unsigned int i, j;
 679        int err = 0;
 680
 681        for (i = 0; i < ARRAY_SIZE(metrics); i++) {
 682                err = metric_parse_fake(metrics[i].str);
 683                if (err)
 684                        return err;
 685        }
 686
 687        i = 0;
 688        for (;;) {
 689                map = &pmu_events_map[i++];
 690                if (!map->table)
 691                        break;
 692                j = 0;
 693                for (;;) {
 694                        pe = &map->table[j++];
 695                        if (!pe->name && !pe->metric_group && !pe->metric_name)
 696                                break;
 697                        if (!pe->metric_expr)
 698                                continue;
 699                        err = metric_parse_fake(pe->metric_expr);
 700                        if (err)
 701                                return err;
 702                }
 703        }
 704
 705        return 0;
 706}
 707
 708static const struct {
 709        int (*func)(void);
 710        const char *desc;
 711} pmu_events_testcase_table[] = {
 712        {
 713                .func = test_pmu_event_table,
 714                .desc = "PMU event table sanity",
 715        },
 716        {
 717                .func = test_aliases,
 718                .desc = "PMU event map aliases",
 719        },
 720        {
 721                .func = test_parsing,
 722                .desc = "Parsing of PMU event table metrics",
 723        },
 724        {
 725                .func = test_parsing_fake,
 726                .desc = "Parsing of PMU event table metrics with fake PMUs",
 727        },
 728};
 729
 730const char *test__pmu_events_subtest_get_desc(int subtest)
 731{
 732        if (subtest < 0 ||
 733            subtest >= (int)ARRAY_SIZE(pmu_events_testcase_table))
 734                return NULL;
 735        return pmu_events_testcase_table[subtest].desc;
 736}
 737
 738const char *test__pmu_events_subtest_skip_reason(int subtest)
 739{
 740        if (subtest < 0 ||
 741            subtest >= (int)ARRAY_SIZE(pmu_events_testcase_table))
 742                return NULL;
 743        if (pmu_events_testcase_table[subtest].func != test_parsing)
 744                return NULL;
 745        return "some metrics failed";
 746}
 747
 748int test__pmu_events_subtest_get_nr(void)
 749{
 750        return (int)ARRAY_SIZE(pmu_events_testcase_table);
 751}
 752
 753int test__pmu_events(struct test *test __maybe_unused, int subtest)
 754{
 755        if (subtest < 0 ||
 756            subtest >= (int)ARRAY_SIZE(pmu_events_testcase_table))
 757                return TEST_FAIL;
 758        return pmu_events_testcase_table[subtest].func();
 759}
 760