Re: [PATCH] perf evlist: don't restrict uncore events to PMU CPUs

From: Nia Su

Date: Mon Sep 07 2026 - 23:24:38 EST


On Fri, Sep 4, 2026 at 11:34 PM Ian Rogers <irogers@xxxxxxxxxx> wrote:
>
> On Fri, Sep 4, 2026 at 6:45 AM Nia Su <nia.su@xxxxxxxxxx> wrote:
> >
> > The PMU CPU map has different semantics for core and uncore PMUs.
> >
> > For core PMUs, pmu_cpus contains the CPUs on which the event can be
> > opened. Intersect the user-requested CPU map with the PMU CPU map to
> > reject CPUs unsupported by the PMU.
> >
> > For uncore PMUs, pmu_cpus is just the default CPU to open the event on,
> > not the set of CPUs it can only be opened on.
>
> This isn't quite true. On a dual socket Intel machine, `pmu_cpus` will
> contain a CPU to open the event on per-socket.

Yes, I agree. My description was not precise enough. What I wanted to say
was that pmu_cpus provides the default CPU(s) used to open an event. It
is not necessarily an allowlist. A user may specify another valid CPU,
which the PMU driver can resolve to the appropriate control CPU.

> > The user-requested CPU may
> > override the default and be used directly, and the driver can redirect
> > the event to the appropriate control CPU.
>
> Agreed.
>
> > Currently, __perf_evlist__propagate_maps() intersects the user-requested
> > CPU map with pmu_cpus for all PMUs. If the user-requested CPU is not in
> > pmu_cpus, the intersection becomes empty and the evsel is removed before
> > perf_event_open() is called.
>
> Could you give a command line example of the perf tool doing this? I
> ask because I'm surprised the uncore specifying a CPU behavior has
> been broken. As well as `perf record -C` there is also a somewhat
> newer cpu event qualifier.

I tested both approaches, but they still fail whenever a perf event is
assigned to a CPU outside the uncore PMU's sysfs cpumask. for example,
assigning the event to CPU 1 fails when the cpumask is 0:

perf stat -C 1 -e <uncore event> -- <program>

Here, for an uncore PMU, pmu_cpumask() reads the sysfs cpumask into
pmu->cpus. When the event is created, perf copies pmu->cpus to the
local pmu_cpus variable and then stores it in evsel->core.pmu_cpus.
In this case, evsel->core.pmu_cpus is {0}. The CPU specified with
"-C 1" is stored in evsel->core.cpus as {1}. Then, in this function,
__perf_evlist__propagate_maps() then intersects the two maps, resulting
in an empty map, so the event is removed before reaching the PMU driver.

> > Skip the intersection when evsel->requires_cpu is true, which is set for
> > uncore PMUs. is_pmu_core is also false for other non-core PMUs, so
> > gating on it would have loosened this check for them too. Core PMUs
> > continue to be restricted to their PMU CPU map.
> >
> > requires_cpu can also be set for BPF counters regardless of PMU type,
> > but only when has_user_cpus is false, so this guard has no effect there
> > today.
>
> So the separation between libperf and perf is frustrating, and libperf
> lacks the necessary PMU abstraction. Using requires_cpu as a proxy for
> an uncore PMU test is okay, but it would be better if we had a boolean
> flag especially for this. Given this, a comment should be added saying
> that requires_cpu is used as a proxy for an uncore PMU. The real
> meaning of requires_cpu is that the event doesn't support
> perf_event_open with a thread.
>
> Thanks,
> Ian

That makes sense. I’ll add a comment clarifying that requires_cpu is used
as a proxy for uncore PMUs and keep the current approach for now.

Thanks,
Nia

> > Fixes: 811082e4b668 ("perf parse-events: Support user CPUs mixed with threads/processes")
> >
> > Assisted-by: Claude Sonnet 5
> > Signed-off-by: Nia Su <nia.su@xxxxxxxxxx>
> > ---
> > tools/lib/perf/evlist.c | 3 ++-
> > 1 file changed, 2 insertions(+), 1 deletion(-)
> >
> > diff --git a/tools/lib/perf/evlist.c b/tools/lib/perf/evlist.c
> > index 1f210dadd666ff818a23712ab95acaa659ccf8dc..a1064175f12ba655a9f7fe42e7c7e4c49ae60423 100644
> > --- a/tools/lib/perf/evlist.c
> > +++ b/tools/lib/perf/evlist.c
> > @@ -80,7 +80,8 @@ static void __perf_evlist__propagate_maps(struct perf_evlist *evlist,
> > }
> >
> > /* Ensure cpus only references valid PMU CPUs. */
> > - if (!perf_cpu_map__has_any_cpu(evsel->cpus) &&
> > + if (!evsel->requires_cpu &&
> > + !perf_cpu_map__has_any_cpu(evsel->cpus) &&
> > !perf_cpu_map__is_subset(evsel->pmu_cpus, evsel->cpus)) {
> > struct perf_cpu_map *tmp = perf_cpu_map__intersect(evsel->pmu_cpus, evsel->cpus);
> >
> >
> > ---
> > base-commit: 73e3f0710014fe6d4ed98cfc02292f6121db7558
> > change-id: 20260904-perf-evlist-uncore-cpu-b4-418af0f17c80
> >
> > --
> >