[PATCH v1 6/6] perf parse-events: Add "cpu" term to set the CPU an event is recorded on

From: Ian Rogers
Date: Wed Jul 17 2024 - 18:49:13 EST


The -C option allows the CPUs for a list of events to be specified but
its not possible to set the CPU for a single event. Add a term to
allow this. The term isn't a general CPU list due to ',' already being
a special character in event parsing.

An example of mixing different types of events counted on different CPUs:
```
$ perf stat -A -C 0,4,8 -e "instructions/cpu=0/,l1d-misses/cpu=4/,inst_retired.any/cpu=8/,cycles" -a sleep 0.1

Performance counter stats for 'system wide':

CPU0 419,426 instructions/cpu=0/ # 0.25 insn per cycle
CPU4 <not counted> instructions/cpu=0/
CPU8 <not counted> instructions/cpu=0/
CPU0 <not counted> l1d-misses [cpu]
CPU4 45,574 l1d-misses [cpu]
CPU8 <not counted> l1d-misses [cpu]
CPU0 <not counted> cpu/cpu=8/
CPU4 <not counted> cpu/cpu=8/
CPU8 164,073 cpu/cpu=8/
CPU0 1,689,993 cycles
CPU4 5,204,403 cycles
CPU8 668,986 cycles
```

An example of spreading uncore overhead across two CPUs:
```
$ perf stat -A -e "data_read/cpu=0/,data_write/cpu=1/" -a sleep 0.1

Performance counter stats for 'system wide':

CPU0 223.65 MiB uncore_imc_free_running_0/cpu=0/
CPU0 223.66 MiB uncore_imc_free_running_1/cpu=0/
CPU0 <not counted> MiB uncore_imc_free_running_0/cpu=1/
CPU1 5.78 MiB uncore_imc_free_running_0/cpu=1/
CPU0 <not counted> MiB uncore_imc_free_running_1/cpu=1/
CPU1 5.74 MiB uncore_imc_free_running_1/cpu=1/
```

Note, the event names are missing as there are unmerged uniquify fixes
like:
https://lore.kernel.org/lkml/20240510053705.2462258-3-irogers@xxxxxxxxxx/

Manually fixing the output should be:
```
CPU0 223.65 MiB uncore_imc_free_running_0/data_read,cpu=0/
CPU0 223.66 MiB uncore_imc_free_running_1/data_read,cpu=0/
CPU1 5.78 MiB uncore_imc_free_running_0/data_write,cpu=1/
CPU1 5.74 MiB uncore_imc_free_running_1/data_write,cpu=1/
```

That is data_read from 2 PMUs was read on CPU0 and data_write was read
on CPU1.

Signed-off-by: Ian Rogers <irogers@xxxxxxxxxx>
---
tools/perf/Documentation/perf-list.txt | 7 +++
tools/perf/util/evsel_config.h | 1 +
tools/perf/util/parse-events.c | 67 ++++++++++++++++++++++----
tools/perf/util/parse-events.h | 3 +-
tools/perf/util/parse-events.l | 1 +
tools/perf/util/pmu.c | 1 +
6 files changed, 69 insertions(+), 11 deletions(-)

diff --git a/tools/perf/Documentation/perf-list.txt b/tools/perf/Documentation/perf-list.txt
index 6bf2468f59d3..abbff01665eb 100644
--- a/tools/perf/Documentation/perf-list.txt
+++ b/tools/perf/Documentation/perf-list.txt
@@ -273,6 +273,13 @@ Sums up the event counts for all hardware threads in a core, e.g.:

perf stat -e cpu/event=0,umask=0x3,percore=1/

+cpu:
+
+Specifies the CPU to open the event upon:
+
+
+ perf stat -e data_read/cpu=0/,data_write/cpu=1/ -a sleep 1
+

EVENT GROUPS
------------
diff --git a/tools/perf/util/evsel_config.h b/tools/perf/util/evsel_config.h
index aee6f808b512..9630c4a24721 100644
--- a/tools/perf/util/evsel_config.h
+++ b/tools/perf/util/evsel_config.h
@@ -47,6 +47,7 @@ struct evsel_config_term {
u32 aux_sample_size;
u64 cfg_chg;
char *str;
+ int cpu;
} val;
bool weak;
};
diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c
index 8c0c33361c5e..86d074aa6c9a 100644
--- a/tools/perf/util/parse-events.c
+++ b/tools/perf/util/parse-events.c
@@ -7,6 +7,7 @@
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/param.h>
+#include "cpumap.h"
#include "term.h"
#include "evlist.h"
#include "evsel.h"
@@ -177,6 +178,20 @@ static char *get_config_name(const struct parse_events_terms *head_terms)
return get_config_str(head_terms, PARSE_EVENTS__TERM_TYPE_NAME);
}

+static struct perf_cpu_map *get_config_cpu(const struct parse_events_terms *head_terms)
+{
+ struct parse_events_term *term;
+
+ if (!head_terms)
+ return NULL;
+
+ list_for_each_entry(term, &head_terms->terms, list)
+ if (term->type_term == PARSE_EVENTS__TERM_TYPE_CPU)
+ return perf_cpu_map__new_int(term->val.num);
+
+ return NULL;
+}
+
/**
* fix_raw - For each raw term see if there is an event (aka alias) in pmu that
* matches the raw's string value. If the string value matches an
@@ -468,11 +483,12 @@ int parse_events_add_cache(struct list_head *list, int *idx, const char *name,
bool found_supported = false;
const char *config_name = get_config_name(parsed_terms);
const char *metric_id = get_config_metric_id(parsed_terms);
+ struct perf_cpu_map *cpus = get_config_cpu(parsed_terms);
+ int ret = 0;

while ((pmu = perf_pmus__scan(pmu)) != NULL) {
LIST_HEAD(config_terms);
struct perf_event_attr attr;
- int ret;

if (parse_events__filter_pmu(parse_state, pmu))
continue;
@@ -486,7 +502,7 @@ int parse_events_add_cache(struct list_head *list, int *idx, const char *name,
parsed_terms,
perf_pmu__auto_merge_stats(pmu));
if (ret)
- return ret;
+ goto out_err;
continue;
}

@@ -506,20 +522,27 @@ int parse_events_add_cache(struct list_head *list, int *idx, const char *name,

if (parsed_terms) {
if (config_attr(&attr, parsed_terms, parse_state->error,
- config_term_common))
- return -EINVAL;
-
- if (get_config_terms(parsed_terms, &config_terms))
- return -ENOMEM;
+ config_term_common)) {
+ ret = -EINVAL;
+ goto out_err;
+ }
+ if (get_config_terms(parsed_terms, &config_terms)) {
+ ret = -ENOMEM;
+ goto out_err;
+ }
}

if (__add_event(list, idx, &attr, /*init_attr*/true, config_name ?: name,
metric_id, pmu, &config_terms, /*auto_merge_stats=*/false,
- /*cpu_list=*/NULL) == NULL)
- return -ENOMEM;
+ cpus) == NULL)
+ ret = -ENOMEM;

free_config_terms(&config_terms);
+ if (ret)
+ goto out_err;
}
+out_err:
+ perf_cpu_map__put(cpus);
return found_supported ? 0 : -EINVAL;
}

@@ -814,6 +837,7 @@ static const char *config_term_name(enum parse_events__term_type term_type)
[PARSE_EVENTS__TERM_TYPE_RAW] = "raw",
[PARSE_EVENTS__TERM_TYPE_LEGACY_CACHE] = "legacy-cache",
[PARSE_EVENTS__TERM_TYPE_HARDWARE] = "hardware",
+ [PARSE_EVENTS__TERM_TYPE_CPU] = "cpu",
};
if ((unsigned int)term_type >= __PARSE_EVENTS__TERM_TYPE_NR)
return "unknown term";
@@ -843,6 +867,7 @@ config_term_avail(enum parse_events__term_type term_type, struct parse_events_er
case PARSE_EVENTS__TERM_TYPE_METRIC_ID:
case PARSE_EVENTS__TERM_TYPE_SAMPLE_PERIOD:
case PARSE_EVENTS__TERM_TYPE_PERCORE:
+ case PARSE_EVENTS__TERM_TYPE_CPU:
return true;
case PARSE_EVENTS__TERM_TYPE_USER:
case PARSE_EVENTS__TERM_TYPE_SAMPLE_FREQ:
@@ -986,6 +1011,15 @@ do { \
return -EINVAL;
}
break;
+ case PARSE_EVENTS__TERM_TYPE_CPU:
+ CHECK_TYPE_VAL(NUM);
+ if (term->val.num >= (u64)cpu__max_present_cpu().cpu) {
+ parse_events_error__handle(err, term->err_val,
+ strdup("too big"),
+ NULL);
+ return -EINVAL;
+ }
+ break;
case PARSE_EVENTS__TERM_TYPE_DRV_CFG:
case PARSE_EVENTS__TERM_TYPE_USER:
case PARSE_EVENTS__TERM_TYPE_LEGACY_CACHE:
@@ -1112,6 +1146,7 @@ static int config_term_tracepoint(struct perf_event_attr *attr,
case PARSE_EVENTS__TERM_TYPE_RAW:
case PARSE_EVENTS__TERM_TYPE_LEGACY_CACHE:
case PARSE_EVENTS__TERM_TYPE_HARDWARE:
+ case PARSE_EVENTS__TERM_TYPE_CPU:
default:
if (err) {
parse_events_error__handle(err, term->err_term,
@@ -1243,6 +1278,7 @@ do { \
case PARSE_EVENTS__TERM_TYPE_RAW:
case PARSE_EVENTS__TERM_TYPE_LEGACY_CACHE:
case PARSE_EVENTS__TERM_TYPE_HARDWARE:
+ case PARSE_EVENTS__TERM_TYPE_CPU:
default:
break;
}
@@ -1296,6 +1332,7 @@ static int get_config_chgs(struct perf_pmu *pmu, struct parse_events_terms *head
case PARSE_EVENTS__TERM_TYPE_RAW:
case PARSE_EVENTS__TERM_TYPE_LEGACY_CACHE:
case PARSE_EVENTS__TERM_TYPE_HARDWARE:
+ case PARSE_EVENTS__TERM_TYPE_CPU:
default:
break;
}
@@ -1350,6 +1387,7 @@ static int __parse_events_add_numeric(struct parse_events_state *parse_state,
struct perf_event_attr attr;
LIST_HEAD(config_terms);
const char *name, *metric_id;
+ struct perf_cpu_map *cpus;
int ret;

memset(&attr, 0, sizeof(attr));
@@ -1371,9 +1409,11 @@ static int __parse_events_add_numeric(struct parse_events_state *parse_state,

name = get_config_name(head_config);
metric_id = get_config_metric_id(head_config);
+ cpus = get_config_cpu(head_config);
ret = __add_event(list, &parse_state->idx, &attr, /*init_attr*/true, name,
metric_id, pmu, &config_terms, /*auto_merge_stats=*/false,
- /*cpu_list=*/NULL) ? 0 : -ENOMEM;
+ cpus) ? 0 : -ENOMEM;
+ perf_cpu_map__put(cpus);
free_config_terms(&config_terms);
return ret;
}
@@ -1440,6 +1480,7 @@ static int parse_events_add_pmu(struct parse_events_state *parse_state,
LIST_HEAD(config_terms);
struct parse_events_terms parsed_terms;
bool alias_rewrote_terms = false;
+ struct perf_cpu_map *term_cpu = NULL;
int ret = 0;

if (verbose > 1) {
@@ -1531,6 +1572,12 @@ static int parse_events_add_pmu(struct parse_events_state *parse_state,
goto out_err;
}

+ term_cpu = get_config_cpu(&parsed_terms);
+ if (!perf_cpu_map__is_empty(term_cpu)) {
+ perf_cpu_map__put(info.cpus);
+ info.cpus = term_cpu;
+ term_cpu = NULL;
+ }
evsel = __add_event(list, &parse_state->idx, &attr, /*init_attr=*/true,
get_config_name(&parsed_terms),
get_config_metric_id(&parsed_terms), pmu,
diff --git a/tools/perf/util/parse-events.h b/tools/perf/util/parse-events.h
index e13de2c8b706..b03857499030 100644
--- a/tools/perf/util/parse-events.h
+++ b/tools/perf/util/parse-events.h
@@ -79,7 +79,8 @@ enum parse_events__term_type {
PARSE_EVENTS__TERM_TYPE_RAW,
PARSE_EVENTS__TERM_TYPE_LEGACY_CACHE,
PARSE_EVENTS__TERM_TYPE_HARDWARE,
-#define __PARSE_EVENTS__TERM_TYPE_NR (PARSE_EVENTS__TERM_TYPE_HARDWARE + 1)
+ PARSE_EVENTS__TERM_TYPE_CPU,
+#define __PARSE_EVENTS__TERM_TYPE_NR (PARSE_EVENTS__TERM_TYPE_CPU + 1)
};

struct parse_events_term {
diff --git a/tools/perf/util/parse-events.l b/tools/perf/util/parse-events.l
index 16045c383ada..e06097a62796 100644
--- a/tools/perf/util/parse-events.l
+++ b/tools/perf/util/parse-events.l
@@ -330,6 +330,7 @@ percore { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_PERCORE); }
aux-output { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_AUX_OUTPUT); }
aux-sample-size { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_AUX_SAMPLE_SIZE); }
metric-id { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_METRIC_ID); }
+cpu { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_CPU); }
cpu-cycles|cycles { return hw_term(yyscanner, PERF_COUNT_HW_CPU_CYCLES); }
stalled-cycles-frontend|idle-cycles-frontend { return hw_term(yyscanner, PERF_COUNT_HW_STALLED_CYCLES_FRONTEND); }
stalled-cycles-backend|idle-cycles-backend { return hw_term(yyscanner, PERF_COUNT_HW_STALLED_CYCLES_BACKEND); }
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index 280b2499c861..27e2ff23799e 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -1767,6 +1767,7 @@ int perf_pmu__for_each_format(struct perf_pmu *pmu, void *state, pmu_format_call
"percore",
"aux-output",
"aux-sample-size=number",
+ "cpu=number",
};
struct perf_pmu_format *format;
int ret;
--
2.45.2.1089.g2a221341d9-goog