From: Ian Rogers <irogers@google.com>
To: linux-perf-users@vger.kernel.org,
Arnaldo Carvalho de Melo <acme@kernel.org>,
Namhyung Kim <namhyung@kernel.org>
Cc: linux-kernel@vger.kernel.org,
Peter Zijlstra <peterz@infradead.org>,
Ingo Molnar <mingo@redhat.com>, Jiri Olsa <jolsa@kernel.org>,
Adrian Hunter <adrian.hunter@intel.com>,
James Clark <james.clark@linaro.org>,
Kan Liang <kan.liang@linux.intel.com>,
Dapeng Mi <dapeng1.mi@linux.intel.com>,
Thomas Falcon <thomas.falcon@intel.com>,
John Garry <john.garry@linux.dev>, Will Deacon <will@kernel.org>,
Leo Yan <leo.yan@arm.com>,
Athira Rajeev <atrajeev@linux.ibm.com>,
Thomas Richter <tmricht@linux.ibm.com>,
Ravi Bangoria <ravi.bangoria@amd.com>,
Sandipan Das <sandipan.das@amd.com>,
Ian Rogers <irogers@google.com>
Subject: [PATCH v1 33/41] perf pmu: Probe event group size limits
Date: Tue, 3 Nov 2026 15:44:49 -0800 [thread overview]
Message-ID: <20261103234502.1008382-34-irogers@google.com> (raw)
In-Reply-To: <20261103234502.1008382-1-irogers@google.com>
Extend perf_pmu__probe() with check_group_active() and
probe_group_limits() to determine the maximum number of events in a
group that are counted simultaneously, without multiplexing
(max_group_size). After each event is added to the group, the group is
enabled and checked to be counting by comparing time_running with
time_enabled.
The number of events in a group accepted by perf_event_open() isn't
reported as it doesn't reflect what can be counted: disabled events are
accepted so that groups may be scheduled by hand and many PMUs accept
groups of enabled events that can never be scheduled.
On core PMUs, where cycles and instructions may use dedicated fixed
counters, also probe a group of general-purpose (GP) hardware events to
determine max_gp_group_size.
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/pmu.c | 210 ++++++++++++++++++++++++++++++++++++++++++
tools/perf/util/pmu.h | 14 +++
2 files changed, 224 insertions(+)
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
index e7e05e29b695..3585147c5242 100644
--- a/tools/perf/util/pmu.c
+++ b/tools/perf/util/pmu.c
@@ -3119,6 +3119,123 @@ static bool try_open_attr_variants(struct perf_event_attr *attr,
return false;
}
+/*
+ * Enable @leader_fd's group briefly and verify that both the group leader and
+ * @last_fd run for 100% of their enabled time without multiplexing.
+ */
+static bool check_group_active(int leader_fd, int last_fd)
+{
+ u64 before[3] = { 0, 0, 0 };
+ u64 after[3] = { 0, 0, 0 };
+ u64 f_before[3] = { 0, 0, 0 };
+ u64 f_after[3] = { 0, 0, 0 };
+ u64 delta_ena, delta_run;
+ bool check_follower = false;
+
+ if (read(leader_fd, before, sizeof(before)) != sizeof(before))
+ return false;
+
+ if (last_fd >= 0 && read(last_fd, f_before, sizeof(f_before)) == sizeof(f_before))
+ check_follower = true;
+
+ if (ioctl(leader_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) < 0)
+ return false;
+
+ for (int spin = 0; spin < 20000; spin++)
+ barrier();
+
+ ioctl(leader_fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP);
+
+ if (read(leader_fd, after, sizeof(after)) != sizeof(after))
+ return false;
+
+ if (check_follower && read(last_fd, f_after, sizeof(f_after)) != sizeof(f_after))
+ return false;
+
+ delta_ena = after[1] - before[1];
+ delta_run = after[2] - before[2];
+ if (delta_ena == 0 || delta_run != delta_ena)
+ return false;
+
+ if (check_follower) {
+ u64 f_delta_ena = f_after[1] - f_before[1];
+ u64 f_delta_run = f_after[2] - f_before[2];
+
+ if (f_delta_ena == 0 || f_delta_run != f_delta_ena)
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Probe the maximum number of events in a group that are counted
+ * simultaneously, without multiplexing. Opening a group doesn't imply it can
+ * be scheduled, many PMUs accept groups of enabled events that can never be
+ * counted, so after each follower is added the group is enabled and checked
+ * to be running.
+ */
+static void probe_group_limits(const struct perf_event_attr *attrs, size_t nr_attrs,
+ pid_t pid, int cpu, unsigned long flags,
+ int *max_size, bool *capped)
+{
+ struct perf_event_attr leader_attr;
+ int leader_fd;
+ int fds[MAX_PROBE_GROUP_SIZE];
+ int nr_followers = 0;
+ enum rlimit_action set_rlimit = NO_CHANGE;
+
+ *max_size = 0;
+ *capped = false;
+
+ if (!attrs || nr_attrs == 0)
+ return;
+
+ leader_attr = attrs[0];
+ leader_attr.disabled = 1;
+ leader_attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING;
+
+ /* The times are needed to determine whether the group is counting. */
+ leader_fd = sys_perf_event_open(&leader_attr, pid, cpu, -1, flags);
+ if (leader_fd < 0)
+ return;
+
+ if (!check_group_active(leader_fd, -1))
+ goto out;
+ *max_size = 1;
+
+ for (int count = 2; count <= MAX_PROBE_GROUP_SIZE; count++) {
+ struct perf_event_attr follower_attr = attrs[(count - 1) % nr_attrs];
+ int fd;
+
+ follower_attr.disabled = 0;
+ follower_attr.read_format = leader_attr.read_format;
+
+ fd = sys_perf_event_open(&follower_attr, pid, cpu, leader_fd, flags);
+ if (fd < 0 && errno == EMFILE && rlimit__increase_nofile(&set_rlimit))
+ fd = sys_perf_event_open(&follower_attr, pid, cpu, leader_fd, flags);
+ if (fd < 0) {
+ /* The leader's times show whether the whole group is counting. */
+ follower_attr.read_format = 0;
+ fd = sys_perf_event_open(&follower_attr, pid, cpu, leader_fd, flags);
+ }
+ if (fd < 0)
+ break;
+
+ fds[nr_followers++] = fd;
+ if (!check_group_active(leader_fd, fd))
+ break;
+ *max_size = count;
+ }
+
+ if (*max_size >= MAX_PROBE_GROUP_SIZE)
+ *capped = true;
+out:
+ for (int i = 0; i < nr_followers; i++)
+ close(fds[i]);
+ close(leader_fd);
+}
+
enum probe_exclude_flag {
PROBE_EXCLUDE_USER,
PROBE_EXCLUDE_KERNEL,
@@ -3909,6 +4026,99 @@ static void __perf_pmu__probe(struct perf_pmu *pmu, int pmu_cpu)
}
}
+ /* 6. Max number of events in a group counted simultaneously. */
+ if (pmu->name && (!strcmp(pmu->name, "kprobe") || !strcmp(pmu->name, "uprobe"))) {
+ if (pmu->probed.can_leader_sampling || pmu->probed.can_sw_grouping) {
+ pmu->probed.max_group_size = MAX_PROBE_GROUP_SIZE;
+ pmu->probed.max_group_capped = true;
+ } else {
+ pmu->probed.max_group_size = 1;
+ }
+ } else {
+ probe_group_limits(group_attrs, nr_group_attrs, pid, cpu, flags,
+ &pmu->probed.max_group_size,
+ &pmu->probed.max_group_capped);
+ }
+
+ /*
+ * On core PMUs, cycles/instructions may use fixed counters in addition
+ * to general-purpose (GP) programmable counters. Probe a GP-only event
+ * group rotating through distinct GP events to determine the GP limit.
+ */
+ if (pmu->is_core) {
+ static const char * const gp_events[] = {
+ "branches",
+ "branch-misses",
+ "cache-misses",
+ "cache-references",
+ "bus-cycles",
+ "stalled-cycles-frontend",
+ "stalled-cycles-backend",
+ };
+ static const int gp_configs[] = {
+ PERF_COUNT_HW_BRANCH_INSTRUCTIONS,
+ PERF_COUNT_HW_BRANCH_MISSES,
+ PERF_COUNT_HW_CACHE_MISSES,
+ PERF_COUNT_HW_CACHE_REFERENCES,
+ PERF_COUNT_HW_BUS_CYCLES,
+ PERF_COUNT_HW_STALLED_CYCLES_FRONTEND,
+ PERF_COUNT_HW_STALLED_CYCLES_BACKEND,
+ };
+ struct perf_event_attr gp_attrs[MAX_PROBE_GROUP_SIZE];
+ size_t nr_gp_attrs = 0;
+ pid_t gp_pid = pid;
+ int gp_cpu = cpu;
+ int gp_err = 0;
+
+ for (size_t i = 0;
+ i < ARRAY_SIZE(gp_events) && nr_gp_attrs < MAX_PROBE_GROUP_SIZE; i++) {
+ char gp_buf[256];
+ struct perf_event_attr gp_attr;
+ bool parsed;
+
+ snprintf(gp_buf, sizeof(gp_buf), "%s/%s/", pmu->name, gp_events[i]);
+ memset(&gp_attr, 0, sizeof(gp_attr));
+ parsed = parse_candidate_attr(gp_buf, &gp_attr);
+ if (!parsed) {
+ gp_attr.type = PERF_TYPE_HARDWARE;
+ gp_attr.size = sizeof(gp_attr);
+ gp_attr.config = gp_configs[i];
+ if (perf_pmus__host_supports_extended_type())
+ gp_attr.config |= (__u64)pmu->type << PERF_PMU_TYPE_SHIFT;
+ }
+ gp_attr.sample_period = base_attr.sample_period;
+ gp_attr.freq = base_attr.freq;
+ gp_attr.sample_type = base_attr.sample_type;
+ gp_attr.exclude_user = base_attr.exclude_user;
+ gp_attr.exclude_kernel = base_attr.exclude_kernel;
+ gp_attr.exclude_hv = base_attr.exclude_hv;
+ gp_attr.exclude_idle = base_attr.exclude_idle;
+ gp_attr.exclude_host = base_attr.exclude_host;
+ gp_attr.exclude_guest = base_attr.exclude_guest;
+ gp_attr.disabled = 1;
+
+ if (nr_gp_attrs == 0) {
+ if (try_open_attr_variants(&gp_attr, false, pmu_cpu,
+ flags, &gp_pid, &gp_cpu, &gp_err)) {
+ gp_attrs[nr_gp_attrs++] = gp_attr;
+ }
+ } else {
+ int fd = sys_perf_event_open(&gp_attr, gp_pid, gp_cpu, -1, flags);
+
+ if (fd >= 0) {
+ close(fd);
+ gp_attrs[nr_gp_attrs++] = gp_attr;
+ }
+ }
+ }
+
+ if (nr_gp_attrs > 0) {
+ probe_group_limits(gp_attrs, nr_gp_attrs, gp_pid, gp_cpu, flags,
+ &pmu->probed.max_gp_group_size,
+ &pmu->probed.max_gp_group_capped);
+ }
+ }
+
if (keepalive_fd >= 0)
close(keepalive_fd);
pmu->probed.checked = true;
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
index 8efffbac3cbf..cacb8bc5198b 100644
--- a/tools/perf/util/pmu.h
+++ b/tools/perf/util/pmu.h
@@ -278,6 +278,10 @@ struct perf_pmu {
bool exclude_host;
/** @probed.exclude_guest: Supports perf_event_attr::exclude_guest. */
bool exclude_guest;
+ /** @probed.max_group_capped: Was max_group_size capped at probe limit? */
+ bool max_group_capped;
+ /** @probed.max_gp_group_capped: Was max_gp_group_size capped at probe limit? */
+ bool max_gp_group_capped;
/** @probed.probe_event: Name of the event used to probe the PMU. */
char *probe_event;
/** @probed.open_err: errno if open_supported is false. */
@@ -286,6 +290,16 @@ struct perf_pmu {
int max_precise;
/** @probed.precise_mask: Bitmask of supported precise_ip levels (1..3). */
unsigned int precise_mask;
+ /**
+ * @probed.max_group_size: Max events in a group counted
+ * simultaneously, without multiplexing.
+ */
+ int max_group_size;
+ /**
+ * @probed.max_gp_group_size: Max general-purpose counter events
+ * in a group counted simultaneously, without multiplexing.
+ */
+ int max_gp_group_size;
/**
* @probed.sample_types: Bitmask of supported
* enum perf_event_sample_format flags.
--
2.56.0.385.gac671c819a-goog
next prev parent reply other threads:[~2026-11-03 23:46 UTC|newest]
Thread overview: 42+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-03 23:44 [PATCH v1 00/41] perf: Per-env PMUs, recorded event metrics, and perf sysinfo Ian Rogers
2026-11-03 23:44 ` [PATCH v1 01/41] perf env: Make lazy perf_env initialization thread-safe Ian Rogers
2026-11-03 23:44 ` [PATCH v1 02/41] perf env: Add host_env() singleton Ian Rogers
2026-11-03 23:44 ` [PATCH v1 03/41] perf pmu: Add from_env parameter to perf_pmu__init() Ian Rogers
2026-11-03 23:44 ` [PATCH v1 04/41] perf pmus: Introduce struct perf_pmus and perf_env__pmus() Ian Rogers
2026-11-03 23:44 ` [PATCH v1 05/41] perf pmus: Pass struct perf_pmus to find helpers Ian Rogers
2026-11-03 23:44 ` [PATCH v1 06/41] perf pmus: Pass struct perf_pmus to scan helpers Ian Rogers
2026-11-03 23:44 ` [PATCH v1 07/41] perf pmu-events: Add e_machine to struct pmu_events_map Ian Rogers
2026-11-03 23:44 ` [PATCH v1 08/41] perf pmu-events: Pass e_machine to find_core_*_table() Ian Rogers
2026-11-03 23:44 ` [PATCH v1 09/41] perf header: Pass e_machine to strcmp_cpuid_str() Ian Rogers
2026-11-03 23:44 ` [PATCH v1 10/41] perf header: Support CPUID matching across all architectures Ian Rogers
2026-11-03 23:44 ` [PATCH v1 11/41] perf test: Make the CPUID matching test architecture independent Ian Rogers
2026-11-03 23:44 ` [PATCH v1 12/41] perf test: Add CPUID matching tests for other architectures Ian Rogers
2026-11-03 23:44 ` [PATCH v1 13/41] perf pmus: Add perf_pmus__read_from_env() Ian Rogers
2026-11-03 23:44 ` [PATCH v1 14/41] perf metricgroup: Pass struct perf_pmus to metricgroup__for_each_metric() Ian Rogers
2026-11-03 23:44 ` [PATCH v1 15/41] perf metricgroup: Move matching of metrics to recorded events from perf script Ian Rogers
2026-11-03 23:44 ` [PATCH v1 16/41] perf parse-events: Decode metric-id terms when adding events Ian Rogers
2026-11-03 23:44 ` [PATCH v1 17/41] perf evsel: Generalize metric_id to a set of metric_ids Ian Rogers
2026-11-03 23:44 ` [PATCH v1 18/41] perf pmu: Add perf_pmu__for_each_name_from_config() Ian Rogers
2026-11-03 23:44 ` [PATCH v1 19/41] perf metricgroup: Improve matching of metrics to recorded events Ian Rogers
2026-11-03 23:44 ` [PATCH v1 20/41] perf metricgroup: Add metricgroup__add_recorded_metrics() Ian Rogers
2026-11-03 23:44 ` [PATCH v1 21/41] perf script: Fix metrics computed from samples Ian Rogers
2026-11-03 23:44 ` [PATCH v1 22/41] perf script: Don't drop metric samples for CPUs missing from the evsel Ian Rogers
2026-11-03 23:44 ` [PATCH v1 23/41] perf stat report: Display metrics for recorded events Ian Rogers
2026-11-03 23:44 ` [PATCH v1 24/41] perf script: Display metrics for perf stat record data Ian Rogers
2026-11-03 23:44 ` [PATCH v1 25/41] perf test: Add stat record metrics test Ian Rogers
2026-11-03 23:44 ` [PATCH v1 26/41] perf pmus: Expose duplicate uncore PMU scanning helpers Ian Rogers
2026-11-03 23:44 ` [PATCH v1 27/41] perf mem-events: Expose PMU init and name formatting helpers Ian Rogers
2026-11-03 23:44 ` [PATCH v1 28/41] perf pmu: Add perf_pmu__probe() for basic open, counting, and sampling Ian Rogers
2026-11-03 23:44 ` [PATCH v1 29/41] perf pmu: Probe supported sample_type flags Ian Rogers
2026-11-03 23:44 ` [PATCH v1 30/41] perf pmu: Probe precise_ip levels Ian Rogers
2026-11-03 23:44 ` [PATCH v1 31/41] perf pmu: Probe privilege exclusion modifiers Ian Rogers
2026-11-03 23:44 ` [PATCH v1 32/41] perf pmu: Probe execution and AUX area features Ian Rogers
2026-11-03 23:44 ` Ian Rogers [this message]
2026-11-03 23:44 ` [PATCH v1 34/41] perf pmu: Probe user and interrupt sample register masks Ian Rogers
2026-11-03 23:44 ` [PATCH v1 35/41] perf pmu: Probe interrupt registers that require precise sampling Ian Rogers
2026-11-03 23:44 ` [PATCH v1 36/41] perf pmu: Probe memory profiling events and sample formats Ian Rogers
2026-11-03 23:44 ` [PATCH v1 37/41] perf pmu: Probe user-space mmap capabilities Ian Rogers
2026-11-03 23:44 ` [PATCH v1 38/41] perf pmu: Probe branch instruction skid Ian Rogers
2026-11-03 23:44 ` [PATCH v1 39/41] perf sysinfo: Add sysinfo subcommand Ian Rogers
2026-11-03 23:44 ` [PATCH v1 40/41] perf docs: Add perf-sysinfo man page Ian Rogers
2026-11-03 23:44 ` [PATCH v1 41/41] perf test: Add perf sysinfo shell test Ian Rogers
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261103234502.1008382-34-irogers@google.com \
--to=irogers@google.com \
--cc=acme@kernel.org \
--cc=adrian.hunter@intel.com \
--cc=atrajeev@linux.ibm.com \
--cc=dapeng1.mi@linux.intel.com \
--cc=james.clark@linaro.org \
--cc=john.garry@linux.dev \
--cc=jolsa@kernel.org \
--cc=kan.liang@linux.intel.com \
--cc=leo.yan@arm.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=mingo@redhat.com \
--cc=namhyung@kernel.org \
--cc=peterz@infradead.org \
--cc=ravi.bangoria@amd.com \
--cc=sandipan.das@amd.com \
--cc=thomas.falcon@intel.com \
--cc=tmricht@linux.ibm.com \
--cc=will@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®