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