Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

perf parse-events: Allow the cpu term to be a PMU or CPU range

On hybrid systems, events like msr/tsc/ will aggregate counts across
all CPUs. Often metrics only want a value like msr/tsc/ for the cores
on which the metric is being computed. Listing each CPU with terms
cpu=0,cpu=1.. is laborious and would need to be encoded for all
variations of a CPU model.

Allow the cpumask from a PMU to be an argument to the cpu term. For
example in the following the cpumask of the cstate_pkg PMU selects the
CPUs to count msr/tsc/ counter upon:
```
$ cat /sys/bus/event_source/devices/cstate_pkg/cpumask
0
$ perf stat -A -e 'msr/tsc,cpu=cstate_pkg/' -a sleep 0.1

Performance counter stats for 'system wide':

CPU0 252,621,253 msr/tsc,cpu=cstate_pkg/

0.101184092 seconds time elapsed
```

As the cpu term is now also allowed to be a string, allow it to encode
a range of CPUs (a list can't be supported as ',' is already a special
token).

The "event qualifiers" section of the `perf list` man page is updated
to detail the additional behavior. The man page formatting is tidied
up in this section, as it was incorrectly appearing within the
"parameterized events" section.

Reviewed-by: Thomas Falcon <thomas.falcon@intel.com>
Signed-off-by: Ian Rogers <irogers@google.com>
Link: https://lore.kernel.org/r/20250719030517.1990983-5-irogers@google.com
Signed-off-by: Namhyung Kim <namhyung@kernel.org>

authored by

Ian Rogers and committed by
Namhyung Kim
bd741d80 ced4c249

+53 -17
+16 -9
tools/perf/Documentation/perf-list.txt
··· 278 278 279 279 perf stat -C 0 -e 'hv_gpci/dtbp_ptitc,phys_processor_idx=0x2/' ... 280 280 281 - EVENT QUALIFIERS: 281 + EVENT QUALIFIERS 282 + ---------------- 282 283 283 284 It is also possible to add extra qualifiers to an event: 284 285 285 286 percore: 286 287 287 - Sums up the event counts for all hardware threads in a core, e.g.: 288 - 289 - 290 - perf stat -e cpu/event=0,umask=0x3,percore=1/ 288 + Sums up the event counts for all hardware threads in a core, e.g.: 289 + perf stat -e cpu/event=0,umask=0x3,percore=1/ 291 290 292 291 cpu: 293 292 294 - Specifies the CPU to open the event upon. The value may be repeated to 295 - specify opening the event on multiple CPUs: 293 + Specifies a CPU or a range of CPUs to open the event upon. It may 294 + also reference a PMU to copy the CPU mask from. The value may be 295 + repeated to specify opening the event on multiple CPUs. 296 296 297 + Example 1: to open the instructions event on CPUs 0 and 2, the 298 + cycles event on CPUs 1 and 2: 299 + perf stat -e instructions/cpu=0,cpu=2/,cycles/cpu=1-2/ -a sleep 1 297 300 298 - perf stat -e instructions/cpu=0,cpu=2/,cycles/cpu=1,cpu=2/ -a sleep 1 299 - perf stat -e data_read/cpu=0/,data_write/cpu=1/ -a sleep 1 301 + Example 2: to open the data_read uncore event on CPU 0 and the 302 + data_write uncore event on CPU 1: 303 + perf stat -e data_read/cpu=0/,data_write/cpu=1/ -a sleep 1 300 304 305 + Example 3: to open the software msr/tsc/ event only on the CPUs 306 + matching those from the cpu_core PMU: 307 + perf stat -e msr/tsc,cpu=cpu_core/ -a sleep 1 301 308 302 309 EVENT GROUPS 303 310 ------------
+37 -8
tools/perf/util/parse-events.c
··· 187 187 188 188 list_for_each_entry(term, &head_terms->terms, list) { 189 189 if (term->type_term == PARSE_EVENTS__TERM_TYPE_CPU) { 190 - struct perf_cpu_map *cpu = perf_cpu_map__new_int(term->val.num); 190 + struct perf_cpu_map *term_cpus; 191 191 192 - perf_cpu_map__merge(&cpus, cpu); 193 - perf_cpu_map__put(cpu); 192 + if (term->type_val == PARSE_EVENTS__TERM_TYPE_NUM) { 193 + term_cpus = perf_cpu_map__new_int(term->val.num); 194 + } else { 195 + struct perf_pmu *pmu = perf_pmus__find(term->val.str); 196 + 197 + if (pmu && perf_cpu_map__is_empty(pmu->cpus)) 198 + term_cpus = pmu->is_core ? cpu_map__online() : NULL; 199 + else if (pmu) 200 + term_cpus = perf_cpu_map__get(pmu->cpus); 201 + else 202 + term_cpus = perf_cpu_map__new(term->val.str); 203 + } 204 + perf_cpu_map__merge(&cpus, term_cpus); 205 + perf_cpu_map__put(term_cpus); 194 206 } 195 207 } 196 208 ··· 1060 1048 return -EINVAL; 1061 1049 } 1062 1050 break; 1063 - case PARSE_EVENTS__TERM_TYPE_CPU: 1064 - CHECK_TYPE_VAL(NUM); 1065 - if (term->val.num >= (u64)cpu__max_present_cpu().cpu) { 1051 + case PARSE_EVENTS__TERM_TYPE_CPU: { 1052 + struct perf_cpu_map *map; 1053 + 1054 + if (term->type_val == PARSE_EVENTS__TERM_TYPE_NUM) { 1055 + if (term->val.num >= (u64)cpu__max_present_cpu().cpu) { 1056 + parse_events_error__handle(err, term->err_val, 1057 + strdup("too big"), 1058 + /*help=*/NULL); 1059 + return -EINVAL; 1060 + } 1061 + break; 1062 + } 1063 + assert(term->type_val == PARSE_EVENTS__TERM_TYPE_STR); 1064 + if (perf_pmus__find(term->val.str) != NULL) 1065 + break; 1066 + 1067 + map = perf_cpu_map__new(term->val.str); 1068 + if (!map) { 1066 1069 parse_events_error__handle(err, term->err_val, 1067 - strdup("too big"), 1068 - NULL); 1070 + strdup("not a valid PMU or CPU number"), 1071 + /*help=*/NULL); 1069 1072 return -EINVAL; 1070 1073 } 1074 + perf_cpu_map__put(map); 1071 1075 break; 1076 + } 1072 1077 case PARSE_EVENTS__TERM_TYPE_DRV_CFG: 1073 1078 case PARSE_EVENTS__TERM_TYPE_USER: 1074 1079 case PARSE_EVENTS__TERM_TYPE_LEGACY_CACHE: