Files
linux-stable-mirror/tools/perf/tests/pmu-events.c
T
Ian RogersandArnaldo Carvalho de Melo 8ae26f6ae7 perf test pmu-events: A sub-test per metric table
Break apart the slow "Parsing of PMU event table metrics" tests into one
pair of tests (real and fake PMU) per metric table found, storing the
specific table pointer in priv data. Implement setup_pmu_events_suite()
to dynamically allocate and populate these test cases. Split static
parser tests out into a separate test__parsing_fake_static() test case.
Update test__parsing() and test__parsing_fake() to retrieve the specific
table from priv data and test only that table, maintaining fallback
compatibility if priv is NULL.

Running these individual tests in parallel significantly reduces overall
test execution time.

Assisted-by: Gemini-CLI:Google Gemini 3
Signed-off-by: Ian Rogers <irogers@google.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@linaro.org>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
2026-06-04 11:36:36 -03:00

1216 lines
29 KiB
C

// SPDX-License-Identifier: GPL-2.0
#include "math.h"
#include "parse-events.h"
#include "pmu.h"
#include "pmus.h"
#include "tests.h"
#include <errno.h>
#include <stdio.h>
#include <linux/kernel.h>
#include <linux/zalloc.h>
#include "debug.h"
#include "../pmu-events/pmu-events.h"
#include <perf/evlist.h>
#include "util/evlist.h"
#include "util/expr.h"
#include "util/hashmap.h"
#include "util/parse-events.h"
#include "util/tool_pmu.h"
#include "metricgroup.h"
#include "stat.h"
struct perf_pmu_test_event {
/* used for matching against events from generated pmu-events.c */
struct pmu_event event;
/*
* Note: For when PublicDescription does not exist in the JSON, we
* will have no long_desc in pmu_event.long_desc, but long_desc may
* be set in the alias.
*/
const char *alias_long_desc;
/* PMU which we should match against */
const char *matching_pmu;
};
struct perf_pmu_test_pmu {
const char *pmu_name;
bool pmu_is_uncore;
const char *pmu_id;
struct perf_pmu_test_event const *aliases[10];
};
static const struct perf_pmu_test_event bp_l1_btb_correct = {
.event = {
.pmu = "default_core",
.name = "bp_l1_btb_correct",
.event = "event=0x8a",
.desc = "L1 BTB Correction",
.topic = "branch",
},
};
static const struct perf_pmu_test_event bp_l2_btb_correct = {
.event = {
.pmu = "default_core",
.name = "bp_l2_btb_correct",
.event = "event=0x8b",
.desc = "L2 BTB Correction",
.topic = "branch",
},
};
static const struct perf_pmu_test_event segment_reg_loads_any = {
.event = {
.pmu = "default_core",
.name = "segment_reg_loads.any",
.event = "event=6,period=200000,umask=0x80",
.desc = "Number of segment register loads",
.topic = "other",
},
};
static const struct perf_pmu_test_event dispatch_blocked_any = {
.event = {
.pmu = "default_core",
.name = "dispatch_blocked.any",
.event = "event=9,period=200000,umask=0x20",
.desc = "Memory cluster signals to block micro-op dispatch for any reason",
.topic = "other",
},
};
static const struct perf_pmu_test_event eist_trans = {
.event = {
.pmu = "default_core",
.name = "eist_trans",
.event = "event=0x3a,period=200000",
.desc = "Number of Enhanced Intel SpeedStep(R) Technology (EIST) transitions",
.topic = "other",
},
};
static const struct perf_pmu_test_event l3_cache_rd = {
.event = {
.pmu = "default_core",
.name = "l3_cache_rd",
.event = "event=0x40",
.desc = "L3 cache access, read",
.long_desc = "Attributable Level 3 cache access, read",
.topic = "cache",
},
.alias_long_desc = "Attributable Level 3 cache access, read",
};
static const struct perf_pmu_test_event *core_events[] = {
&bp_l1_btb_correct,
&bp_l2_btb_correct,
&segment_reg_loads_any,
&dispatch_blocked_any,
&eist_trans,
&l3_cache_rd,
NULL
};
static const struct perf_pmu_test_event uncore_hisi_ddrc_flux_wcmd = {
.event = {
.name = "uncore_hisi_ddrc.flux_wcmd",
.event = "event=2",
.desc = "DDRC write commands",
.topic = "uncore",
.pmu = "hisi_sccl,ddrc",
},
.matching_pmu = "hisi_sccl1_ddrc2",
};
static const struct perf_pmu_test_event unc_cbo_xsnp_response_miss_eviction = {
.event = {
.name = "unc_cbo_xsnp_response.miss_eviction",
.event = "event=0x22,umask=0x81",
.desc = "A cross-core snoop resulted from L3 Eviction which misses in some processor core",
.topic = "uncore",
.pmu = "uncore_cbox",
},
.matching_pmu = "uncore_cbox_0",
};
static const struct perf_pmu_test_event uncore_hyphen = {
.event = {
.name = "event-hyphen",
.event = "event=0xe0",
.desc = "UNC_CBO_HYPHEN",
.topic = "uncore",
.pmu = "uncore_cbox",
},
.matching_pmu = "uncore_cbox_0",
};
static const struct perf_pmu_test_event uncore_two_hyph = {
.event = {
.name = "event-two-hyph",
.event = "event=0xc0",
.desc = "UNC_CBO_TWO_HYPH",
.topic = "uncore",
.pmu = "uncore_cbox",
},
.matching_pmu = "uncore_cbox_0",
};
static const struct perf_pmu_test_event uncore_hisi_l3c_rd_hit_cpipe = {
.event = {
.name = "uncore_hisi_l3c.rd_hit_cpipe",
.event = "event=7",
.desc = "Total read hits",
.topic = "uncore",
.pmu = "hisi_sccl,l3c",
},
.matching_pmu = "hisi_sccl3_l3c7",
};
static const struct perf_pmu_test_event uncore_imc_free_running_cache_miss = {
.event = {
.name = "uncore_imc_free_running.cache_miss",
.event = "event=0x12",
.desc = "Total cache misses",
.topic = "uncore",
.pmu = "uncore_imc_free_running",
},
.matching_pmu = "uncore_imc_free_running_0",
};
static const struct perf_pmu_test_event uncore_imc_cache_hits = {
.event = {
.name = "uncore_imc.cache_hits",
.event = "event=0x34",
.desc = "Total cache hits",
.topic = "uncore",
.pmu = "uncore_imc",
},
.matching_pmu = "uncore_imc_0",
};
static const struct perf_pmu_test_event *uncore_events[] = {
&uncore_hisi_ddrc_flux_wcmd,
&unc_cbo_xsnp_response_miss_eviction,
&uncore_hyphen,
&uncore_two_hyph,
&uncore_hisi_l3c_rd_hit_cpipe,
&uncore_imc_free_running_cache_miss,
&uncore_imc_cache_hits,
NULL
};
static const struct perf_pmu_test_event sys_ddr_pmu_write_cycles = {
.event = {
.name = "sys_ddr_pmu.write_cycles",
.event = "event=0x2b",
.desc = "ddr write-cycles event",
.topic = "uncore",
.pmu = "uncore_sys_ddr_pmu",
.compat = "v8",
},
.matching_pmu = "uncore_sys_ddr_pmu0",
};
static const struct perf_pmu_test_event sys_ccn_pmu_read_cycles = {
.event = {
.name = "sys_ccn_pmu.read_cycles",
.event = "config=0x2c",
.desc = "ccn read-cycles event",
.topic = "uncore",
.pmu = "uncore_sys_ccn_pmu",
.compat = "0x01",
},
.matching_pmu = "uncore_sys_ccn_pmu4",
};
static const struct perf_pmu_test_event sys_cmn_pmu_hnf_cache_miss = {
.event = {
.name = "sys_cmn_pmu.hnf_cache_miss",
.event = "eventid=1,type=5",
.desc = "Counts total cache misses in first lookup result (high priority)",
.topic = "uncore",
.pmu = "uncore_sys_cmn_pmu",
.compat = "(434|436|43c|43a).*",
},
.matching_pmu = "uncore_sys_cmn_pmu0",
};
static const struct perf_pmu_test_event *sys_events[] = {
&sys_ddr_pmu_write_cycles,
&sys_ccn_pmu_read_cycles,
&sys_cmn_pmu_hnf_cache_miss,
NULL
};
static bool is_same(const char *reference, const char *test)
{
if (!reference && !test)
return true;
if (reference && !test)
return false;
if (!reference && test)
return false;
return !strcmp(reference, test);
}
static int compare_pmu_events(const struct pmu_event *e1, const struct pmu_event *e2)
{
if (!is_same(e1->name, e2->name)) {
pr_debug2("testing event e1 %s: mismatched name string, %s vs %s\n",
e1->name, e1->name, e2->name);
return -1;
}
if (!is_same(e1->compat, e2->compat)) {
pr_debug2("testing event e1 %s: mismatched compat string, %s vs %s\n",
e1->name, e1->compat, e2->compat);
return -1;
}
if (!is_same(e1->event, e2->event)) {
pr_debug2("testing event e1 %s: mismatched event, %s vs %s\n",
e1->name, e1->event, e2->event);
return -1;
}
if (!is_same(e1->desc, e2->desc)) {
pr_debug2("testing event e1 %s: mismatched desc, %s vs %s\n",
e1->name, e1->desc, e2->desc);
return -1;
}
if (!is_same(e1->topic, e2->topic)) {
pr_debug2("testing event e1 %s: mismatched topic, %s vs %s\n",
e1->name, e1->topic, e2->topic);
return -1;
}
if (!is_same(e1->long_desc, e2->long_desc)) {
pr_debug2("testing event e1 %s: mismatched long_desc, %s vs %s\n",
e1->name, e1->long_desc, e2->long_desc);
return -1;
}
if (!is_same(e1->pmu, e2->pmu)) {
pr_debug2("testing event e1 %s: mismatched pmu string, %s vs %s\n",
e1->name, e1->pmu, e2->pmu);
return -1;
}
if (!is_same(e1->unit, e2->unit)) {
pr_debug2("testing event e1 %s: mismatched unit, %s vs %s\n",
e1->name, e1->unit, e2->unit);
return -1;
}
if (e1->perpkg != e2->perpkg) {
pr_debug2("testing event e1 %s: mismatched perpkg, %d vs %d\n",
e1->name, e1->perpkg, e2->perpkg);
return -1;
}
if (e1->deprecated != e2->deprecated) {
pr_debug2("testing event e1 %s: mismatched deprecated, %d vs %d\n",
e1->name, e1->deprecated, e2->deprecated);
return -1;
}
return 0;
}
static int compare_alias_to_test_event(struct pmu_event_info *alias,
struct perf_pmu_test_event const *test_event,
char const *pmu_name)
{
struct pmu_event const *event = &test_event->event;
/* An alias was found, ensure everything is in order */
if (!is_same(alias->name, event->name)) {
pr_debug("testing aliases PMU %s: mismatched name, %s vs %s\n",
pmu_name, alias->name, event->name);
return -1;
}
if (!is_same(alias->desc, event->desc)) {
pr_debug("testing aliases PMU %s: mismatched desc, %s vs %s\n",
pmu_name, alias->desc, event->desc);
return -1;
}
if (!is_same(alias->long_desc, test_event->alias_long_desc)) {
pr_debug("testing aliases PMU %s: mismatched long_desc, %s vs %s\n",
pmu_name, alias->long_desc,
test_event->alias_long_desc);
return -1;
}
if (!is_same(alias->topic, event->topic)) {
pr_debug("testing aliases PMU %s: mismatched topic, %s vs %s\n",
pmu_name, alias->topic, event->topic);
return -1;
}
if (!is_same(alias->str, test_event->event.event)) {
pr_debug("testing aliases PMU %s: mismatched str, %s vs %s\n",
pmu_name, alias->str, test_event->event.event);
return -1;
}
if (!is_same(alias->long_desc, test_event->alias_long_desc)) {
pr_debug("testing aliases PMU %s: mismatched long desc, %s vs %s\n",
pmu_name, alias->str, test_event->alias_long_desc);
return -1;
}
if (!is_same(alias->pmu_name, test_event->event.pmu) &&
!is_same(alias->pmu_name, "default_core")) {
pr_debug("testing aliases PMU %s: mismatched pmu_name, %s vs %s\n",
pmu_name, alias->pmu_name, test_event->event.pmu);
return -1;
}
return 0;
}
static int test__pmu_event_table_core_callback(const struct pmu_event *pe,
const struct pmu_events_table *table __maybe_unused,
void *data)
{
int *map_events = data;
struct perf_pmu_test_event const **test_event_table;
bool found = false;
if (strcmp(pe->pmu, "default_core"))
test_event_table = &uncore_events[0];
else
test_event_table = &core_events[0];
for (; *test_event_table; test_event_table++) {
struct perf_pmu_test_event const *test_event = *test_event_table;
struct pmu_event const *event = &test_event->event;
if (strcmp(pe->name, event->name))
continue;
found = true;
(*map_events)++;
if (compare_pmu_events(pe, event))
return -1;
pr_debug("testing event table %s: pass\n", pe->name);
}
if (!found) {
pr_err("testing event table: could not find event %s\n", pe->name);
return -1;
}
return 0;
}
static int test__pmu_event_table_sys_callback(const struct pmu_event *pe,
const struct pmu_events_table *table __maybe_unused,
void *data)
{
int *map_events = data;
struct perf_pmu_test_event const **test_event_table;
bool found = false;
test_event_table = &sys_events[0];
for (; *test_event_table; test_event_table++) {
struct perf_pmu_test_event const *test_event = *test_event_table;
struct pmu_event const *event = &test_event->event;
if (strcmp(pe->name, event->name))
continue;
found = true;
(*map_events)++;
if (compare_pmu_events(pe, event))
return TEST_FAIL;
pr_debug("testing sys event table %s: pass\n", pe->name);
}
if (!found) {
pr_debug("testing sys event table: could not find event %s\n", pe->name);
return TEST_FAIL;
}
return TEST_OK;
}
/* Verify generated events from pmu-events.c are as expected */
static int test__pmu_event_table(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
const struct pmu_events_table *sys_event_table =
find_sys_events_table("pmu_events__test_soc_sys");
const struct pmu_events_table *table = find_core_events_table("testarch", "testcpu");
int map_events = 0, expected_events, err;
/* ignore 3x sentinels */
expected_events = ARRAY_SIZE(core_events) +
ARRAY_SIZE(uncore_events) +
ARRAY_SIZE(sys_events) - 3;
if (!table || !sys_event_table)
return -1;
err = pmu_events_table__for_each_event(table, /*pmu=*/ NULL,
test__pmu_event_table_core_callback,
&map_events);
if (err)
return err;
err = pmu_events_table__for_each_event(sys_event_table, /*pmu=*/ NULL,
test__pmu_event_table_sys_callback,
&map_events);
if (err)
return err;
if (map_events != expected_events) {
pr_err("testing event table: found %d, but expected %d\n",
map_events, expected_events);
return TEST_FAIL;
}
return 0;
}
struct test_core_pmu_event_aliases_cb_args {
struct perf_pmu_test_event const *test_event;
int *count;
};
static int test_core_pmu_event_aliases_cb(void *state, struct pmu_event_info *alias)
{
struct test_core_pmu_event_aliases_cb_args *args = state;
if (compare_alias_to_test_event(alias, args->test_event, alias->pmu->name))
return -1;
(*args->count)++;
pr_debug2("testing aliases core PMU %s: matched event %s\n",
alias->pmu_name, alias->name);
return 0;
}
/* Verify aliases are as expected */
static int __test_core_pmu_event_aliases(const char *pmu_name, int *count)
{
struct perf_pmu_test_event const **test_event_table;
struct perf_pmu *pmu;
int res = 0;
const struct pmu_events_table *table = find_core_events_table("testarch", "testcpu");
if (!table)
return -1;
test_event_table = &core_events[0];
pmu = zalloc(sizeof(*pmu));
if (!pmu)
return -1;
if (perf_pmu__init(pmu, PERF_PMU_TYPE_FAKE, pmu_name) != 0) {
perf_pmu__delete(pmu);
return -1;
}
pmu->is_core = true;
pmu->events_table = table;
pmu_add_cpu_aliases_table(pmu, table);
pmu->cpu_aliases_added = true;
pmu->sysfs_aliases_loaded = true;
res = pmu_events_table__find_event(table, pmu, "bp_l1_btb_correct", NULL, NULL);
if (res != 0) {
pr_debug("Missing test event in test architecture");
return res;
}
for (; *test_event_table; test_event_table++) {
struct perf_pmu_test_event test_event = **test_event_table;
struct pmu_event const *event = &test_event.event;
struct test_core_pmu_event_aliases_cb_args args = {
.test_event = &test_event,
.count = count,
};
int err;
test_event.event.pmu = pmu_name;
err = perf_pmu__find_event(pmu, event->name, &args,
test_core_pmu_event_aliases_cb);
if (err)
res = err;
}
perf_pmu__delete(pmu);
return res;
}
static int __test_uncore_pmu_event_aliases(struct perf_pmu_test_pmu *test_pmu)
{
int alias_count = 0, to_match_count = 0, matched_count = 0;
struct perf_pmu_test_event const **table;
struct perf_pmu *pmu;
const struct pmu_events_table *events_table;
int res = 0;
events_table = find_core_events_table("testarch", "testcpu");
if (!events_table)
return -1;
pmu = zalloc(sizeof(*pmu));
if (!pmu)
return -1;
if (perf_pmu__init(pmu, PERF_PMU_TYPE_FAKE, test_pmu->pmu_name) != 0) {
perf_pmu__delete(pmu);
return -1;
}
pmu->is_uncore = test_pmu->pmu_is_uncore;
if (test_pmu->pmu_id) {
pmu->id = strdup(test_pmu->pmu_id);
if (!pmu->id) {
perf_pmu__delete(pmu);
return -1;
}
}
pmu->events_table = events_table;
pmu_add_cpu_aliases_table(pmu, events_table);
pmu->cpu_aliases_added = true;
pmu->sysfs_aliases_loaded = true;
pmu_add_sys_aliases(pmu);
/* Count how many aliases we generated */
alias_count = perf_pmu__num_events(pmu);
/* Count how many aliases we expect from the known table */
for (table = &test_pmu->aliases[0]; *table; table++)
to_match_count++;
if (alias_count != to_match_count) {
pr_debug("testing aliases uncore PMU %s: mismatch expected aliases (%d) vs found (%d)\n",
pmu->name, to_match_count, alias_count);
perf_pmu__delete(pmu);
return -1;
}
for (table = &test_pmu->aliases[0]; *table; table++) {
struct perf_pmu_test_event test_event = **table;
struct pmu_event const *event = &test_event.event;
int err;
struct test_core_pmu_event_aliases_cb_args args = {
.test_event = &test_event,
.count = &matched_count,
};
if (strcmp(pmu->name, test_event.matching_pmu)) {
pr_debug("testing aliases uncore PMU %s: mismatched matching_pmu, %s vs %s\n",
pmu->name, test_event.matching_pmu, pmu->name);
perf_pmu__delete(pmu);
return -1;
}
err = perf_pmu__find_event(pmu, event->name, &args,
test_core_pmu_event_aliases_cb);
if (err) {
res = err;
pr_debug("testing aliases uncore PMU %s: could not match alias %s\n",
pmu->name, event->name);
perf_pmu__delete(pmu);
return -1;
}
}
if (alias_count != matched_count) {
pr_debug("testing aliases uncore PMU %s: mismatch found aliases (%d) vs matched (%d)\n",
pmu->name, matched_count, alias_count);
res = -1;
}
perf_pmu__delete(pmu);
return res;
}
static struct perf_pmu_test_pmu test_pmus[] = {
{
.pmu_name = "hisi_sccl1_ddrc2",
.pmu_is_uncore = 1,
.aliases = {
&uncore_hisi_ddrc_flux_wcmd,
},
},
{
.pmu_name = "uncore_cbox_0",
.pmu_is_uncore = 1,
.aliases = {
&unc_cbo_xsnp_response_miss_eviction,
&uncore_hyphen,
&uncore_two_hyph,
},
},
{
.pmu_name = "hisi_sccl3_l3c7",
.pmu_is_uncore = 1,
.aliases = {
&uncore_hisi_l3c_rd_hit_cpipe,
},
},
{
.pmu_name = "uncore_imc_free_running_0",
.pmu_is_uncore = 1,
.aliases = {
&uncore_imc_free_running_cache_miss,
},
},
{
.pmu_name = "uncore_imc_0",
.pmu_is_uncore = 1,
.aliases = {
&uncore_imc_cache_hits,
},
},
{
.pmu_name = "uncore_sys_ddr_pmu0",
.pmu_is_uncore = 1,
.pmu_id = "v8",
.aliases = {
&sys_ddr_pmu_write_cycles,
},
},
{
.pmu_name = "uncore_sys_ccn_pmu4",
.pmu_is_uncore = 1,
.pmu_id = "0x01",
.aliases = {
&sys_ccn_pmu_read_cycles,
},
},
{
.pmu_name = "uncore_sys_cmn_pmu0",
.pmu_is_uncore = 1,
.pmu_id = "43401",
.aliases = {
&sys_cmn_pmu_hnf_cache_miss,
},
},
{
.pmu_name = "uncore_sys_cmn_pmu0",
.pmu_is_uncore = 1,
.pmu_id = "43602",
.aliases = {
&sys_cmn_pmu_hnf_cache_miss,
},
},
{
.pmu_name = "uncore_sys_cmn_pmu0",
.pmu_is_uncore = 1,
.pmu_id = "43c03",
.aliases = {
&sys_cmn_pmu_hnf_cache_miss,
},
},
{
.pmu_name = "uncore_sys_cmn_pmu0",
.pmu_is_uncore = 1,
.pmu_id = "43a01",
.aliases = {
&sys_cmn_pmu_hnf_cache_miss,
},
}
};
/* Test that aliases generated are as expected */
static int test__aliases(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
struct perf_pmu *pmu = NULL;
unsigned long i;
while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
int count = 0;
if (list_empty(&pmu->format)) {
pr_debug2("skipping testing core PMU %s\n", pmu->name);
continue;
}
if (__test_core_pmu_event_aliases(pmu->name, &count)) {
pr_debug("testing core PMU %s aliases: failed\n", pmu->name);
return -1;
}
if (count == 0) {
pr_debug("testing core PMU %s aliases: no events to match\n",
pmu->name);
return -1;
}
pr_debug("testing core PMU %s aliases: pass\n", pmu->name);
}
for (i = 0; i < ARRAY_SIZE(test_pmus); i++) {
int res;
res = __test_uncore_pmu_event_aliases(&test_pmus[i]);
if (res)
return res;
}
return 0;
}
static bool is_number(const char *str)
{
char *end_ptr;
double v;
errno = 0;
v = strtod(str, &end_ptr);
(void)v; // We're not interested in this value, only if it is valid
return errno == 0 && end_ptr != str;
}
static int check_parse_id(const char *id, struct parse_events_error *error)
{
struct evlist *evlist;
int ret;
char *dup, *cur;
/* Numbers are always valid. */
if (is_number(id))
return 0;
evlist = evlist__new();
if (!evlist)
return -ENOMEM;
dup = strdup(id);
if (!dup)
return -ENOMEM;
for (cur = strchr(dup, '@') ; cur; cur = strchr(++cur, '@'))
*cur = '/';
ret = __parse_events(evlist, dup, /*pmu_filter=*/NULL, error, /*fake_pmu=*/true,
/*warn_if_reordered=*/true, /*fake_tp=*/false);
free(dup);
evlist__delete(evlist);
return ret;
}
static int check_parse_fake(const char *id)
{
struct parse_events_error error;
int ret;
parse_events_error__init(&error);
ret = check_parse_id(id, &error);
parse_events_error__exit(&error);
return ret;
}
struct metric {
struct list_head list;
struct metric_ref metric_ref;
};
static bool is_expected_broken_metric(const struct pmu_metric *pm)
{
if (!strcmp(pm->metric_name, "M1") || !strcmp(pm->metric_name, "M2") ||
!strcmp(pm->metric_name, "M3"))
return true;
#if defined(__aarch64__)
/*
* Arm64 platforms may return "#slots == 0", which is treated as a
* syntax error by the parser. Don't test these metrics when running
* on such platforms.
*/
if (strstr(pm->metric_expr, "#slots") &&
!tool_pmu__cpu_slots_per_cycle())
return true;
#endif
return false;
}
static int test__parsing_callback(const struct pmu_metric *pm,
const struct pmu_metrics_table *table,
void *data)
{
int *failures = data;
int k;
struct evlist *evlist;
struct perf_cpu_map *cpus;
struct evsel *evsel;
int err = 0;
if (!pm->metric_expr)
return 0;
pr_debug("Found metric '%s'\n", pm->metric_name);
(*failures)++;
/*
* We need to prepare evlist for stat mode running on CPU 0
* because that's where all the stats are going to be created.
*/
evlist = evlist__new();
if (!evlist)
return -ENOMEM;
cpus = perf_cpu_map__new("0");
if (!cpus) {
evlist__delete(evlist);
return -ENOMEM;
}
perf_evlist__set_maps(&evlist->core, cpus, NULL);
err = metricgroup__parse_groups_test(evlist, table, pm->metric_name);
if (err) {
if (is_expected_broken_metric(pm)) {
(*failures)--;
pr_debug("Expected broken metric %s skipping\n", pm->metric_name);
err = 0;
}
goto out_err;
}
err = evlist__alloc_stats(/*config=*/NULL, evlist, /*alloc_raw=*/false);
if (err)
goto out_err;
/*
* Add all ids with a made up value. The value may trigger divide by
* zero when subtracted and so try to make them unique.
*/
k = 1;
evlist__alloc_aggr_stats(evlist, 1);
evlist__for_each_entry(evlist, evsel) {
evsel->stats->aggr->counts.val = k;
k++;
}
evlist__for_each_entry(evlist, evsel) {
struct metric_event *me = metricgroup__lookup(&evlist->metric_events, evsel, false);
if (me != NULL) {
struct metric_expr *mexp;
list_for_each_entry (mexp, &me->head, nd) {
if (strcmp(mexp->metric_name, pm->metric_name))
continue;
pr_debug("Result %f\n", test_generic_metric(mexp, 0));
err = 0;
(*failures)--;
goto out_err;
}
}
}
pr_debug("Didn't find parsed metric %s", pm->metric_name);
err = 1;
out_err:
if (err)
pr_debug("Broken metric %s\n", pm->metric_name);
/* ... cleanup. */
evlist__free_stats(evlist);
perf_cpu_map__put(cpus);
evlist__delete(evlist);
return err;
}
static int test__parsing(struct test_suite *test, int subtest)
{
int failures = 0;
const struct pmu_metrics_table *table = NULL;
if (test->test_cases)
table = test->test_cases[subtest].priv;
if (table) {
pmu_metrics_table__for_each_metric(table, test__parsing_callback, &failures);
} else {
pmu_for_each_core_metric(test__parsing_callback, &failures);
pmu_for_each_sys_metric(test__parsing_callback, &failures);
}
return failures == 0 ? TEST_OK : TEST_FAIL;
}
struct test_metric {
const char *str;
};
static struct test_metric metrics[] = {
{ "(unc_p_power_state_occupancy.cores_c0 / unc_p_clockticks) * 100." },
{ "imx8_ddr0@read\\-cycles@ * 4 * 4", },
{ "imx8_ddr0@axid\\-read\\,axi_mask\\=0xffff\\,axi_id\\=0x0000@ * 4", },
{ "(cstate_pkg@c2\\-residency@ / msr@tsc@) * 100", },
{ "(imx8_ddr0@read\\-cycles@ + imx8_ddr0@write\\-cycles@)", },
};
static int metric_parse_fake(const char *metric_name, const char *str)
{
struct expr_parse_ctx *ctx;
struct hashmap_entry *cur;
double result;
int ret = -1;
size_t bkt;
int i;
pr_debug("parsing '%s': '%s'\n", metric_name, str);
ctx = expr__ctx_new();
if (!ctx) {
pr_debug("expr__ctx_new failed");
return TEST_FAIL;
}
ctx->sctx.is_test = true;
if (expr__find_ids(str, NULL, ctx) < 0) {
pr_err("expr__find_ids failed\n");
return -1;
}
/*
* Add all ids with a made up value. The value may
* trigger divide by zero when subtracted and so try to
* make them unique.
*/
i = 1;
hashmap__for_each_entry(ctx->ids, cur, bkt)
expr__add_id_val(ctx, strdup(cur->pkey), i++);
hashmap__for_each_entry(ctx->ids, cur, bkt) {
if (check_parse_fake(cur->pkey)) {
pr_err("check_parse_fake failed\n");
goto out;
}
}
ret = 0;
if (expr__parse(&result, ctx, str)) {
/*
* Parsing failed, make numbers go from large to small which can
* resolve divide by zero issues.
*/
i = 1024;
hashmap__for_each_entry(ctx->ids, cur, bkt)
expr__add_id_val(ctx, strdup(cur->pkey), i--);
if (expr__parse(&result, ctx, str)) {
pr_err("expr__parse failed for %s\n", metric_name);
/* The following have hard to avoid divide by zero. */
if (!strcmp(metric_name, "tma_clears_resteers") ||
!strcmp(metric_name, "tma_mispredicts_resteers"))
ret = 0;
else
ret = -1;
}
}
out:
expr__ctx_free(ctx);
return ret;
}
static int test__parsing_fake_callback(const struct pmu_metric *pm,
const struct pmu_metrics_table *table __maybe_unused,
void *data __maybe_unused)
{
return metric_parse_fake(pm->metric_name, pm->metric_expr);
}
/*
* Parse all the metrics for current architecture, or all defined cpus via the
* 'fake_pmu' in parse_events.
*/
static int test__parsing_fake_static(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
int err = 0;
for (size_t i = 0; i < ARRAY_SIZE(metrics); i++) {
err = metric_parse_fake("", metrics[i].str);
if (err)
return err;
}
return 0;
}
static int test__parsing_fake(struct test_suite *test, int subtest)
{
int err = 0;
const struct pmu_metrics_table *table = NULL;
if (test->test_cases)
table = test->test_cases[subtest].priv;
if (table)
return pmu_metrics_table__for_each_metric(table, test__parsing_fake_callback, NULL);
for (size_t i = 0; i < ARRAY_SIZE(metrics); i++) {
err = metric_parse_fake("", metrics[i].str);
if (err)
return err;
}
err = pmu_for_each_core_metric(test__parsing_fake_callback, NULL);
if (err)
return err;
return pmu_for_each_sys_metric(test__parsing_fake_callback, NULL);
}
static int test__parsing_threshold_callback(const struct pmu_metric *pm,
const struct pmu_metrics_table *table __maybe_unused,
void *data __maybe_unused)
{
if (!pm->metric_threshold)
return 0;
return metric_parse_fake(pm->metric_name, pm->metric_threshold);
}
static int test__parsing_threshold(struct test_suite *test __maybe_unused,
int subtest __maybe_unused)
{
int err = 0;
err = pmu_for_each_core_metric(test__parsing_threshold_callback, NULL);
if (err)
return err;
return pmu_for_each_sys_metric(test__parsing_threshold_callback, NULL);
}
struct populate_cb_data {
struct test_case *test_cases;
size_t curr;
};
static int count_metrics_tables_cb(const struct pmu_metrics_table *table __maybe_unused, void *data)
{
size_t *count = data;
(*count)++;
return 0;
}
static int populate_metrics_tables_cb(const struct pmu_metrics_table *table, void *data)
{
struct populate_cb_data *cb_data = data;
const char *table_name = pmu_metrics_table__name(table);
char *desc_real, *desc_fake;
if (!table_name)
table_name = "unknown";
if (asprintf(&desc_real, "PMU metric parsing: %s", table_name) < 0)
return -ENOMEM;
if (asprintf(&desc_fake, "PMU metric parsing with fake PMU: %s", table_name) < 0) {
free(desc_real);
return -ENOMEM;
}
cb_data->test_cases[cb_data->curr++] = (struct test_case){
.name = "parsing",
.desc = desc_real,
.run_case = test__parsing,
.priv = (void *)table,
.skip_reason = "some metrics failed",
};
cb_data->test_cases[cb_data->curr++] = (struct test_case){
.name = "parsing_fake",
.desc = desc_fake,
.run_case = test__parsing_fake,
.priv = (void *)table,
};
return 0;
}
static struct test_case pmu_events_tests[];
static int setup_pmu_events_suite(struct test_suite *suite)
{
size_t num_tables = 0;
size_t num_fixed_tests = 4;
size_t tests_per_table = 2;
size_t total_tests;
struct test_case *test_cases;
size_t curr = 0;
struct populate_cb_data cb_data;
int ret;
if (suite->test_cases != pmu_events_tests)
return 0;
ret = pmu_metrics_table__iterate_tables(count_metrics_tables_cb, &num_tables);
if (ret)
return ret;
total_tests = num_fixed_tests + (num_tables * tests_per_table) + 1;
test_cases = calloc(total_tests, sizeof(*test_cases));
if (!test_cases)
return -ENOMEM;
test_cases[curr++] = (struct test_case){
.name = "pmu_event_table",
.desc = "PMU event table sanity",
.run_case = test__pmu_event_table,
};
test_cases[curr++] = (struct test_case){
.name = "aliases",
.desc = "PMU event map aliases",
.run_case = test__aliases,
};
test_cases[curr++] = (struct test_case){
.name = "parsing_fake_static",
.desc = "Parsing of static metrics with fake PMU",
.run_case = test__parsing_fake_static,
};
test_cases[curr++] = (struct test_case){
.name = "parsing_threshold",
.desc = "Parsing of metric thresholds with fake PMU",
.run_case = test__parsing_threshold,
};
cb_data = (struct populate_cb_data){
.test_cases = test_cases,
.curr = curr,
};
ret = pmu_metrics_table__iterate_tables(populate_metrics_tables_cb, &cb_data);
if (ret) {
size_t i;
for (i = num_fixed_tests; i < cb_data.curr; i++)
free((char *)test_cases[i].desc);
free(test_cases);
return ret;
}
suite->test_cases = test_cases;
return 0;
}
static struct test_case pmu_events_tests[] = {
TEST_CASE("PMU event table sanity", pmu_event_table),
TEST_CASE("PMU event map aliases", aliases),
TEST_CASE_REASON("Parsing of PMU event table metrics", parsing,
"some metrics failed"),
TEST_CASE("Parsing of PMU event table metrics with fake PMU", parsing_fake),
TEST_CASE("Parsing of metric thresholds with fake PMU", parsing_threshold),
{ .name = NULL, }
};
struct test_suite suite__pmu_events = {
.desc = "PMU JSON event tests",
.test_cases = pmu_events_tests,
.setup = setup_pmu_events_suite,
};