* [RFC PATCH v1 0/2] perf sysinfo: Add subcommand to probe and summarize PMU capabilities
@ 2026-10-16 21:32 Ian Rogers
2026-10-16 21:32 ` [RFC PATCH v1 1/2] perf pmus: Add PMU environment loading and live capability probing Ian Rogers
` (2 more replies)
0 siblings, 3 replies; 13+ messages in thread
From: Ian Rogers @ 2026-10-16 21:32 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark
Cc: linux-perf-users, linux-kernel, Stephane Eranian
Determining what performance monitoring hardware, driver capabilities,
and event/metric support are available on a system currently requires
piecing together sysfs files under /sys/bus/event_source/devices/,
perf list output, and trial-and-error perf_event_open() calls. Across
architectures and SoCs, support varies widely: an advanced system
provides core PMUs with zero-skid precise event sampling (PEBS/IBS/SPE),
hardware branch stack recording (LBR/BRBE), user-space counter reading
(rdpmc), memory profiling (DATA_SRC), hardware AUX trace PMUs, memory
and PCIe/IO interconnect bandwidth PMUs, and multi-level pipeline metric
drill-down hierarchies, whereas a minimal system may only offer software
timer sampling.
This RFC series introduces 'perf sysinfo' to inspect, actively probe,
and summarize the PMU and performance monitoring capabilities of a live
system or a recorded perf.data file:
- Patch 1 adds PMU environment reconstruction from perf_env headers
(perf_pmus__read_from_env()), public duplicate-collapsing PMU scan
helpers, and active live capability probing via perf_event_open()
(perf_pmu__probe()). Probing tests counting, sampling, branch stack
sampling, precise_ip levels (:p, :pp, :ppp) and empirically measured
branch instruction skid across an 8,192-nop runway, supported
perf_event_sample_format bits, user/intr general-purpose and SIMD
register masks, memory profiling events, privilege exclusion
modifiers, execution features (leader sampling, sw_grouping,
auto-counter reload, AUX pause/resume/sample), user-space rdpmc
availability/enablement and PMC bit-width, and maximum event group
sizes (both openable and simultaneously active without multiplexing).
- Patch 2 adds the 'perf sysinfo' subcommand, documentation, and shell
test. By default, 'perf sysinfo' outputs a colorized system capability
summary, pipeline metric drill-down hierarchy (via <metric>_group
chains), overall system rating (Advanced, Standard, Basic, Minimal),
and any missing baseline features. Passing '-p'/'--pmus' (or a PMU
glob filter, '-v'/'--verbose', '-a'/'--all', or '-j'/'--json')
displays detailed per-PMU static and probed attributes.
Example default output on an Intel AlderLake hybrid system:
$ perf sysinfo
Summary:
Core PMUs (2, hybrid): cpu_atom, cpu_core
Core features:
Interrupt-based sampling: supported
Precise event sampling: supported (max_precise: 3 [3 - SAMPLE_IP must have 0 skid, or uses randomization])
Branch stack sampling (LBR/BRBE): supported (depth: 32 branches)
User-space counter reading (rdpmc): supported
Leader sampling: supported
Memory profiling events (DATA_SRC): supported
Hardware AUX trace PMUs: intel_bts, intel_pt
Bus & uncore monitoring:
Memory bandwidth PMUs: uncore_imc, uncore_imc_free_running
PCIe / IO interconnect bandwidth PMUs: uncore_arb
Events & metrics:
Available events: 3161 total (76 sysfs, 3085 json) across 28 PMUs
Available metrics: 438 metrics in 192 groups (advanced)
Metric drill-down levels (6): L1 (TopdownL1: 7 metrics), L2 (TopdownL2: 14 metrics), L3 (TopdownL3: 44 metrics), L4 (TopdownL4: 45 metrics), L5 (TopdownL5: 15 metrics), L6 (TopdownL6: 9 metrics)
System rating: Advanced (core PMU with precise sampling, branch stack (LBR), user rdpmc, bus PMUs, and metric drill-down)
Example filtered per-PMU output:
$ perf sysinfo cpu_core
cpu_core (type: 4 [PERF_TYPE_RAW], core):
CPUs: 0-15 (16 CPUs)
Capabilities: branches=32, max_precise=3, pmu_name=alderlake_hybrid
Formats: event (config:0-7), pc (config:19), edge (config:18), offcore_rsp (config1:0-63), ldlat (config1:0-15), inv (config:23), umask (config:8-15), frontend (config1:0-23), cmask (config:24-31)
Events: 1563 total (20 sysfs, 1543 json)
Memory events: ldlat-loads ({cpu_core/mem-loads-aux/,cpu_core/mem-loads,ldlat=30/}:P), ldlat-stores (cpu_core/mem-stores/P)
Probed capabilities (event: cpu_core/cycles/):
Modes: counting, sampling, branch_stack
Precise sampling: :p (1 - SAMPLE_IP must have constant skid), :pp (2 - SAMPLE_IP requested to have 0 skid), :ppp (3 - SAMPLE_IP must have 0 skid, or uses randomization) (max_precise: 3)
Measured branch skid (instructions from branch):
level 0 (no :p): min 105, max 174, avg 156.8 (0/32 exact_ip)
level 1 (:p) : min 1, max 1, avg 1.0 (0/31 exact_ip)
level 2 (:pp) : min 0, max 0, avg 0.0 (31/31 exact_ip)
level 3 (:ppp) : min 0, max 0, avg 0.0 (32/32 exact_ip)
Sample types: ip, tid, time, addr, read, callchain, id, cpu, period, stream_id, raw, branch_stack, regs_user, stack_user, weight, data_src, identifier, transaction, regs_intr, aux, cgroup, data_page_size, code_page_size, weight_struct
User registers: AX, BX, CX, DX, SI, DI, BP, SP, IP, FLAGS, CS, SS, R8, R9, R10, R11, R12, R13, R14, R15
Intr registers: AX, BX, CX, DX, SI, DI, BP, SP, IP, FLAGS, CS, SS, R8, R9, R10, R11, R12, R13, R14, R15, XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7, XMM8, XMM9, XMM10, XMM11, XMM12, XMM13, XMM14, XMM15
Memory events (probed): ldlat-loads, ldlat-stores (sample types: addr, weight, data_src, transaction, data_page_size, weight_struct)
Exclusion modifiers: exclude_user, exclude_kernel, exclude_hv, exclude_idle, exclude_host, exclude_guest
Features: leader_sampling, sw_grouping, inherit, sample_freq, write_backward, callchain, data_src, weight, pinned, exclusive, format_lost, user_rdpmc, user_time, user_time_zero
User space counter reading (rdpmc): available, enabled, working (width: 48 bits)
Max event group size: 18 (active: 8)
Max GP event group size: 16 (active: 7)
This patch series was generated with the assistance of antigravity and
Gemini 4.0.
Ian Rogers (2):
perf pmus: Add PMU environment loading and live capability probing
perf sysinfo: Add sysinfo subcommand, documentation, and test
tools/perf/Build | 1 +
tools/perf/Documentation/perf-sysinfo.txt | 109 ++
tools/perf/Documentation/perf.txt | 2 +-
tools/perf/builtin-help.c | 3 +-
tools/perf/builtin-sysinfo.c | 1958 ++++++++++++++++++++
tools/perf/builtin.h | 1 +
tools/perf/perf.c | 1 +
tools/perf/tests/shell/sysinfo.sh | 71 +
tools/perf/util/mem-events.c | 14 +-
tools/perf/util/mem-events.h | 3 +
tools/perf/util/pmu.c | 1976 ++++++++++++++++++++-
tools/perf/util/pmu.h | 135 +-
tools/perf/util/pmus.c | 221 ++-
tools/perf/util/pmus.h | 4 +
14 files changed, 4486 insertions(+), 13 deletions(-)
create mode 100644 tools/perf/Documentation/perf-sysinfo.txt
create mode 100644 tools/perf/builtin-sysinfo.c
create mode 100755 tools/perf/tests/shell/sysinfo.sh
--
2.56.0.212.g129ede20d9-goog
^ permalink raw reply [flat|nested] 13+ messages in thread* [RFC PATCH v1 1/2] perf pmus: Add PMU environment loading and live capability probing 2026-10-16 21:32 [RFC PATCH v1 0/2] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers @ 2026-10-16 21:32 ` Ian Rogers 2026-10-16 21:32 ` [RFC PATCH v1 2/2] perf sysinfo: Add sysinfo subcommand, documentation, and test Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 0/9] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers 2 siblings, 0 replies; 13+ messages in thread From: Ian Rogers @ 2026-10-16 21:32 UTC (permalink / raw) To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark Cc: linux-perf-users, linux-kernel, Stephane Eranian Add helpers to reconstruct PMU lists and capabilities from perf_env headers (for perf.data files), expose duplicate uncore PMU scanning and mem-events formatting helpers, and add perf_pmu__probe() to actively probe live PMU capabilities via perf_event_open(). Probed capabilities stored in struct perf_pmu::probed include counting, sampling, and branch_stack modes, precise_ip levels and measured branch instruction skid, supported perf_event_sample_format flags, user and interrupt sampling registers, memory profiling events and sample types, privilege exclusion modifiers, leader sampling, software event grouping, auto-counter reload, AUX actions (start_paused, pause, resume, aux_sample), user-space counter reading (rdpmc) and PMC width, and rotating event group size limits. Signed-off-by: Ian Rogers <irogers@google.com> --- tools/perf/util/mem-events.c | 14 +- tools/perf/util/mem-events.h | 3 + tools/perf/util/pmu.c | 1976 +++++++++++++++++++++++++++++++++- tools/perf/util/pmu.h | 135 ++- tools/perf/util/pmus.c | 221 +++- tools/perf/util/pmus.h | 4 + 6 files changed, 2342 insertions(+), 11 deletions(-) diff --git a/tools/perf/util/mem-events.c b/tools/perf/util/mem-events.c index 0cdcb47647bd..eb2a4973e385 100644 --- a/tools/perf/util/mem-events.c +++ b/tools/perf/util/mem-events.c @@ -79,12 +79,12 @@ int perf_pmu__mem_events_num_mem_pmus(struct perf_pmu *pmu) return num; } -static const char *perf_pmu__mem_events_name(struct perf_pmu *pmu, int i, - char *buf, size_t buf_size) +const char *perf_pmu__mem_events_name(struct perf_pmu *pmu, int i, + char *buf, size_t buf_size) { struct perf_mem_event *e; - if (i >= PERF_MEM_EVENTS__MAX || !pmu) + if (i >= PERF_MEM_EVENTS__MAX || !pmu || !pmu->mem_events) return NULL; e = &pmu->mem_events[i]; @@ -193,13 +193,13 @@ static bool perf_pmu__mem_events_supported(const char *mnt, struct perf_pmu *pmu return !stat(path, &st); } -static int __perf_pmu__mem_events_init(struct perf_pmu *pmu) +int perf_pmu__mem_events_init_pmu(struct perf_pmu *pmu) { const char *mnt = sysfs__mount(); bool found = false; int j; - if (!mnt) + if (!mnt || !pmu || !pmu->mem_events) return -ENOENT; for (j = 0; j < PERF_MEM_EVENTS__MAX; j++) { @@ -209,7 +209,7 @@ static int __perf_pmu__mem_events_init(struct perf_pmu *pmu) * If the event entry isn't valid, skip initialization * and "e->supported" will keep false. */ - if (!e->tag) + if (!e || !e->tag) continue; e->supported |= perf_pmu__mem_events_supported(mnt, pmu, e); @@ -225,7 +225,7 @@ int perf_pmu__mem_events_init(void) struct perf_pmu *pmu = NULL; while ((pmu = perf_pmus__scan_mem(pmu)) != NULL) { - if (__perf_pmu__mem_events_init(pmu)) + if (perf_pmu__mem_events_init_pmu(pmu)) return -ENOENT; } diff --git a/tools/perf/util/mem-events.h b/tools/perf/util/mem-events.h index 38b14c03392c..64517ca053d3 100644 --- a/tools/perf/util/mem-events.h +++ b/tools/perf/util/mem-events.h @@ -31,7 +31,10 @@ extern struct perf_mem_event perf_mem_events[PERF_MEM_EVENTS__MAX]; extern bool perf_mem_record[PERF_MEM_EVENTS__MAX]; int perf_pmu__mem_events_parse(struct perf_pmu *pmu, const char *str); +int perf_pmu__mem_events_init_pmu(struct perf_pmu *pmu); int perf_pmu__mem_events_init(void); +const char *perf_pmu__mem_events_name(struct perf_pmu *pmu, int i, + char *buf, size_t buf_size); struct perf_mem_event *perf_pmu__mem_events_ptr(struct perf_pmu *pmu, int i); struct perf_pmu *perf_mem_events_find_pmu(void); diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c index 01753d5c69d1..0d1c5ba7d426 100644 --- a/tools/perf/util/pmu.c +++ b/tools/perf/util/pmu.c @@ -38,7 +38,22 @@ #include "strbuf.h" #include "fncache.h" #include "util/evsel_config.h" +#include "cloexec.h" +#include "dwarf-regs.h" +#include "evlist.h" +#include "mem-events.h" +#include "mmap.h" +#include "perf_regs.h" +#include "rlimit.h" +#include "util/sample.h" +#include "../perf-sys.h" +#include <linux/hw_breakpoint.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sched.h> #include <regex.h> +#include <elf.h> +#include <link.h> #define UNIT_MAX_LEN 31 /* max length for event unit name */ @@ -2139,7 +2154,7 @@ size_t perf_pmu__num_events(struct perf_pmu *pmu) size_t nr; if (perf_pmu__is_tracepoint(pmu)) - return tp_pmu__num_events(pmu); + return pmu->sysfs_aliases_loaded ? 0 : tp_pmu__num_events(pmu); if (perf_pmu__is_hwmon(pmu)) return hwmon_pmu__num_events(pmu); if (perf_pmu__is_drm(pmu)) @@ -2275,7 +2290,7 @@ int perf_pmu__for_each_event(struct perf_pmu *pmu, bool skip_duplicate_pmus, size_t size_rem; if (perf_pmu__is_tracepoint(pmu)) - return tp_pmu__for_each_event(pmu, state, cb); + return pmu->sysfs_aliases_loaded ? 0 : tp_pmu__for_each_event(pmu, state, cb); if (perf_pmu__is_hwmon(pmu)) return hwmon_pmu__for_each_event(pmu, state, cb); if (perf_pmu__is_drm(pmu)) @@ -2564,6 +2579,37 @@ static int perf_pmu__new_caps(struct list_head *list, char *name, char *value) return -ENOMEM; } +int perf_pmu__add_cap(struct perf_pmu *pmu, const char *name, const char *value) +{ + struct perf_pmu_caps *caps; + + if (!pmu || !name || !value) + return -EINVAL; + if (perf_pmu__get_cap(pmu, name)) + return 0; + + caps = zalloc(sizeof(*caps)); + if (!caps) + return -ENOMEM; + + caps->name = strdup(name); + if (!caps->name) { + free(caps); + return -ENOMEM; + } + caps->value = strdup(value); + if (!caps->value) { + zfree(&caps->name); + free(caps); + return -ENOMEM; + } + list_add_tail(&caps->list, &pmu->caps); + pmu->nr_caps++; + if (!strcmp(name, "max_precise")) + pmu->max_precise = atoi(value); + return 0; +} + static void perf_pmu__del_caps(struct perf_pmu *pmu) { struct perf_pmu_caps *caps, *tmp; @@ -2798,9 +2844,1935 @@ void perf_pmu__delete(struct perf_pmu *pmu) zfree(&pmu->name); zfree(&pmu->alias_name); zfree(&pmu->id); + zfree(&pmu->probed.probe_event); free(pmu); } +#define MAX_PROBE_GROUP_SIZE 64 +#define MAX_PROBE_CANDIDATES 16 + +static unsigned long dummy_bp_var; + +static bool get_self_uprobe_target(char *exe_path, __u64 *out_offset) +{ + ElfW(Ehdr) ehdr; + int fd; + bool found = false; + + if (!realpath("/proc/self/exe", exe_path)) + return false; + + fd = open(exe_path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return false; + + if (pread(fd, &ehdr, sizeof(ehdr), 0) != sizeof(ehdr) || + memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0 || + ehdr.e_phentsize < sizeof(ElfW(Phdr))) { + close(fd); + return false; + } + + for (unsigned int i = 0; i < ehdr.e_phnum; i++) { + ElfW(Phdr) phdr; + off_t pos = (off_t)ehdr.e_phoff + (off_t)i * ehdr.e_phentsize; + + if (pread(fd, &phdr, sizeof(phdr), pos) != sizeof(phdr)) + break; + if (phdr.p_type != PT_LOAD || !(phdr.p_flags & PF_X)) + continue; + if (ehdr.e_entry >= phdr.p_vaddr && + ehdr.e_entry < phdr.p_vaddr + phdr.p_filesz) { + *out_offset = phdr.p_offset + (ehdr.e_entry - phdr.p_vaddr); + found = true; + break; + } + } + + close(fd); + return found; +} + +struct probe_candidates { + char *names[MAX_PROBE_CANDIDATES]; + size_t nr; +}; + +static void probe_candidates__add(struct probe_candidates *cands, const char *str) +{ + if (cands->nr >= MAX_PROBE_CANDIDATES || !str) + return; + for (size_t i = 0; i < cands->nr; i++) { + if (!strcmp(cands->names[i], str)) + return; + } + cands->names[cands->nr] = strdup(str); + if (cands->names[cands->nr]) + cands->nr++; +} + +static void probe_candidates__exit(struct probe_candidates *cands) +{ + for (size_t i = 0; i < cands->nr; i++) + zfree(&cands->names[i]); + cands->nr = 0; +} + +static int collect_probe_candidate_cb(void *vstate, struct pmu_event_info *info) +{ + struct probe_candidates *cands = vstate; + char buf[512]; + + if (info->deprecated) + return 0; + if (info->str && strstr(info->str, "=?")) + return 0; + if (!info->name) + return 0; + + if (info->pmu && info->pmu->type == PERF_TYPE_TRACEPOINT) { + probe_candidates__add(cands, info->name); + return 1; + } else if (strchr(info->name, '/')) { + probe_candidates__add(cands, info->name); + } else if (info->pmu && info->pmu->name) { + snprintf(buf, sizeof(buf), "%s/%s/", info->pmu->name, info->name); + probe_candidates__add(cands, buf); + } else { + probe_candidates__add(cands, info->name); + } + + return cands->nr >= MAX_PROBE_CANDIDATES ? 1 : 0; +} + +static bool parse_candidate_attr(const char *event_str, struct perf_event_attr *attr) +{ + struct evlist *evlist; + struct parse_events_error err; + struct evsel *evsel; + int ret; + + evlist = evlist__new(); + if (!evlist) + return false; + + parse_events_error__init(&err); + ret = __parse_events(evlist, event_str, /*pmu_filter=*/NULL, + /*cputype_filter=*/false, &err, /*fake_pmu=*/false, + /*warn_if_reordered=*/false, /*fake_tp=*/false); + parse_events_error__exit(&err); + if (ret || evlist__empty(evlist)) { + evlist__put(evlist); + return false; + } + + evsel = evlist__first(evlist); + *attr = evsel->core.attr; + evlist__put(evlist); + return true; +} + +static int select_probe_cpu(const struct perf_pmu *pmu) +{ + struct perf_cpu_map *online = perf_cpu_map__new_online_cpus(); + int cpu = 0; + + if (pmu->cpus && !perf_cpu_map__is_empty(pmu->cpus)) { + int nr = perf_cpu_map__nr(pmu->cpus); + + cpu = perf_cpu_map__cpu(pmu->cpus, 0).cpu; + if (online) { + for (int i = 0; i < nr; i++) { + struct perf_cpu c = perf_cpu_map__cpu(pmu->cpus, i); + + if (perf_cpu_map__has(online, c)) { + cpu = c.cpu; + break; + } + } + } + } else if (online && !perf_cpu_map__is_empty(online)) { + cpu = perf_cpu_map__cpu(online, 0).cpu; + } + + perf_cpu_map__put(online); + return cpu >= 0 ? cpu : 0; +} + +static bool try_open_attr_variants(struct perf_event_attr *attr, + bool is_uncore, int pmu_cpu, + unsigned long flags, + pid_t *out_pid, int *out_cpu, + int *last_err) +{ + pid_t pids[2]; + size_t nr_pids = 2; + + if (is_uncore) { + pids[0] = -1; + pids[1] = 0; + } else { + pids[0] = 0; + pids[1] = -1; + } + + for (size_t p = 0; p < nr_pids; p++) { + pid_t pid = pids[p]; + + for (int samp = 0; samp < 2; samp++) { + for (int priv = 0; priv < 4; priv++) { + struct perf_event_attr try_attr = *attr; + int fd; + + try_attr.size = sizeof(try_attr); + try_attr.disabled = 1; + try_attr.read_format = 0; + if (samp == 0) { + try_attr.sample_period = 0; + try_attr.freq = 0; + try_attr.sample_type = 0; + } else { + try_attr.sample_period = 10000; + try_attr.freq = 0; + try_attr.sample_type = PERF_SAMPLE_IP; + } + + if (priv >= 1) + try_attr.exclude_kernel = 1; + if (priv >= 2) + try_attr.exclude_hv = 1; + if (priv >= 3) + try_attr.exclude_guest = 1; + + fd = sys_perf_event_open(&try_attr, pid, pmu_cpu, -1, flags); + if (fd >= 0) { + close(fd); + *attr = try_attr; + *out_pid = pid; + *out_cpu = pmu_cpu; + return true; + } + if (*last_err == 0 || *last_err == ENOENT || *last_err == EINVAL) + *last_err = errno; + } + } + } + return false; +} + +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; +} + +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_open, bool *open_capped, + int *max_active, bool *active_capped) +{ + struct perf_event_attr leader_attr; + int leader_fd; + int fds[MAX_PROBE_GROUP_SIZE]; + int nr_followers = 0; + bool has_time_read = true; + bool active_ok = true; + enum rlimit_action set_rlimit = NO_CHANGE; + cpu_set_t saved_affinity, probe_affinity; + bool affinity_changed = false; + + *max_open = 0; + *open_capped = false; + *max_active = 0; + *active_capped = false; + + if (!attrs || nr_attrs == 0) + return; + + if (pid == 0 && cpu >= 0) { + CPU_ZERO(&saved_affinity); + if (sched_getaffinity(0, sizeof(saved_affinity), &saved_affinity) == 0) { + CPU_ZERO(&probe_affinity); + CPU_SET(cpu, &probe_affinity); + if (sched_setaffinity(0, sizeof(probe_affinity), &probe_affinity) == 0) + affinity_changed = true; + } + } + + leader_attr = attrs[0]; + leader_attr.disabled = 1; + leader_attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING; + + leader_fd = sys_perf_event_open(&leader_attr, pid, cpu, -1, flags); + if (leader_fd < 0) { + leader_attr.read_format = 0; + has_time_read = false; + leader_fd = sys_perf_event_open(&leader_attr, pid, cpu, -1, flags); + } + if (leader_fd < 0) + goto out_affinity; + + *max_open = 1; + if (has_time_read && check_group_active(leader_fd, -1)) + *max_active = 1; + else + active_ok = false; + + 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 && follower_attr.read_format != 0) { + 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; + *max_open = count; + if (active_ok && has_time_read) { + if (check_group_active(leader_fd, fd)) + *max_active = count; + else + active_ok = false; + } + } + + if (*max_open >= MAX_PROBE_GROUP_SIZE) + *open_capped = true; + if (*max_active >= MAX_PROBE_GROUP_SIZE) + *active_capped = true; + + for (int i = 0; i < nr_followers; i++) + close(fds[i]); + close(leader_fd); + +out_affinity: + if (affinity_changed) + sched_setaffinity(0, sizeof(saved_affinity), &saved_affinity); +} + +enum probe_exclude_flag { + PROBE_EXCLUDE_USER, + PROBE_EXCLUDE_KERNEL, + PROBE_EXCLUDE_HV, + PROBE_EXCLUDE_IDLE, + PROBE_EXCLUDE_HOST, + PROBE_EXCLUDE_GUEST, +}; + +static bool test_exclude_flag(const struct perf_event_attr *base_attr, + pid_t pid, int cpu, unsigned long flags, + enum probe_exclude_flag which) +{ + struct perf_event_attr attr = *base_attr; + int fd; + + switch (which) { + case PROBE_EXCLUDE_USER: + attr.exclude_user = 1; + break; + case PROBE_EXCLUDE_KERNEL: + attr.exclude_kernel = 1; + attr.exclude_user = 0; + break; + case PROBE_EXCLUDE_HV: + attr.exclude_hv = 1; + break; + case PROBE_EXCLUDE_IDLE: + attr.exclude_idle = 1; + break; + case PROBE_EXCLUDE_HOST: + attr.exclude_host = 1; + attr.exclude_guest = 0; + break; + case PROBE_EXCLUDE_GUEST: + attr.exclude_guest = 1; + attr.exclude_host = 0; + break; + default: + return false; + } + + fd = sys_perf_event_open(&attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + return true; + } + if (which == PROBE_EXCLUDE_USER && attr.exclude_kernel == 1) { + attr.exclude_kernel = 0; + fd = sys_perf_event_open(&attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + return true; + } + } + return false; +} + +static void probe_sample_types(struct perf_pmu *pmu, + const struct perf_event_attr *base_attr, + pid_t pid, int cpu, unsigned long flags) +{ + u64 reg_bit = BIT_ULL(perf_arch_reg_ip(EM_HOST)) ?: 1ULL; + u64 bulk_mask = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | + PERF_SAMPLE_ADDR | PERF_SAMPLE_READ | PERF_SAMPLE_CALLCHAIN | + PERF_SAMPLE_ID | PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD | + PERF_SAMPLE_STREAM_ID | PERF_SAMPLE_RAW | PERF_SAMPLE_REGS_USER | + PERF_SAMPLE_STACK_USER | PERF_SAMPLE_WEIGHT | PERF_SAMPLE_DATA_SRC | + PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_TRANSACTION | + PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_AUX | PERF_SAMPLE_CGROUP | + PERF_SAMPLE_DATA_PAGE_SIZE | PERF_SAMPLE_CODE_PAGE_SIZE | + PERF_SAMPLE_WEIGHT_STRUCT; + struct perf_event_attr b_attr = *base_attr; + int b_fd; + + b_attr.disabled = 1; + b_attr.sample_period = base_attr->sample_period ?: 10000; + b_attr.sample_type = bulk_mask; + b_attr.sample_regs_user = reg_bit; + b_attr.sample_regs_intr = reg_bit; + b_attr.sample_stack_user = 64; + b_attr.inherit = 0; + b_fd = sys_perf_event_open(&b_attr, pid, cpu, -1, flags); + if (b_fd >= 0) { + close(b_fd); + pmu->probed.sample_types |= bulk_mask; + } + + for (unsigned int shift = 0; (1ULL << shift) < PERF_SAMPLE_MAX; shift++) { + u64 bit = 1ULL << shift; + struct perf_event_attr attr = *base_attr; + int fd; + + if (pmu->probed.sample_types & bit) + continue; + + attr.disabled = 1; + attr.sample_period = base_attr->sample_period ?: 10000; + attr.sample_type = bit; + + switch (bit) { + case PERF_SAMPLE_BRANCH_STACK: + attr.branch_sample_type = PERF_SAMPLE_BRANCH_ANY | + (attr.exclude_kernel ? PERF_SAMPLE_BRANCH_USER : 0); + break; + case PERF_SAMPLE_REGS_USER: + attr.sample_regs_user = reg_bit; + break; + case PERF_SAMPLE_REGS_INTR: + attr.sample_regs_intr = reg_bit; + break; + case PERF_SAMPLE_STACK_USER: + attr.sample_stack_user = 64; + break; + case PERF_SAMPLE_READ: + attr.inherit = 0; + break; + default: + break; + } + + fd = sys_perf_event_open(&attr, pid, cpu, -1, flags); + if (fd < 0 && bit == PERF_SAMPLE_BRANCH_STACK) { + attr.branch_sample_type = PERF_SAMPLE_BRANCH_ANY; + fd = sys_perf_event_open(&attr, pid, cpu, -1, flags); + } + if (fd < 0 && bit != PERF_SAMPLE_IP) { + attr.sample_type = PERF_SAMPLE_IP | bit; + fd = sys_perf_event_open(&attr, pid, cpu, -1, flags); + } + if (fd >= 0) { + close(fd); + pmu->probed.sample_types |= bit; + } + } + + pmu->probed.can_branch_stack = !!(pmu->probed.sample_types & PERF_SAMPLE_BRANCH_STACK); + pmu->probed.can_callchain = !!(pmu->probed.sample_types & PERF_SAMPLE_CALLCHAIN); + pmu->probed.can_data_src = !!(pmu->probed.sample_types & PERF_SAMPLE_DATA_SRC); + pmu->probed.can_weight = !!(pmu->probed.sample_types & PERF_SAMPLE_WEIGHT); +} + +static void probe_pmu_regs_one(const struct perf_pmu *pmu, + const struct perf_event_attr *base_attr, + pid_t pid, int cpu, unsigned long flags, + int max_precise, bool intr, + u64 *out_mask, int *out_abi, + u64 *out_simd_mask, u64 *out_pred_mask) +{ + int dummy_abi = -1; + u64 gpr_mask = intr ? perf_intr_reg_mask(EM_HOST, &dummy_abi) : + perf_user_reg_mask(EM_HOST, &dummy_abi); + u64 sample_type = intr ? PERF_SAMPLE_REGS_INTR : PERF_SAMPLE_REGS_USER; + u64 mask = 0; + int abi = 0; + struct perf_event_attr attr = *base_attr; + int fd; + + attr.disabled = 1; + attr.sample_period = base_attr->sample_period ?: 10000; + attr.sample_type = sample_type; + + if (gpr_mask) { + if (intr) + attr.sample_regs_intr = gpr_mask; + else + attr.sample_regs_user = gpr_mask; + + fd = sys_perf_event_open(&attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + mask |= gpr_mask; + } else { + for (int b = 0; b < 64; b++) { + if (!(gpr_mask & BIT_ULL(b))) + continue; + if (intr) + attr.sample_regs_intr = BIT_ULL(b); + else + attr.sample_regs_user = BIT_ULL(b); + fd = sys_perf_event_open(&attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + mask |= BIT_ULL(b); + } + } + } + } + + if (perf_pmu__is_software(pmu)) { + *out_mask = mask; + *out_abi = abi; + return; + } + + /* + * Probe extended registers outside the base GPR mask. On x86, check + * APX EGPRs (R16-R31, bits 24..39) and SSP (bit 40) with + * sample_simd_regs_enabled=1 if the host supports SIMD ABI; otherwise + * check legacy PEBS XMM registers (bits 32..63) which require + * precise_ip > 0 on interrupt register sampling. + */ + if (EM_HOST == EM_X86_64 || EM_HOST == EM_386) { + int host_abi = 0; + u64 host_mask = intr ? perf_intr_reg_mask(EM_HOST, &host_abi) : + perf_user_reg_mask(EM_HOST, &host_abi); + + if ((host_abi & PERF_SAMPLE_REGS_ABI_SIMD) && (host_mask & ~gpr_mask)) { + u64 egpr_masks[] = { GENMASK_ULL(39, 24), BIT_ULL(40) }; + + for (size_t i = 0; i < ARRAY_SIZE(egpr_masks); i++) { + struct perf_event_attr e_attr = *base_attr; + + e_attr.size = sizeof(e_attr); + e_attr.disabled = 1; + e_attr.sample_period = base_attr->sample_period ?: 10000; + e_attr.sample_type = sample_type; + e_attr.sample_simd_regs_enabled = 1; + if (intr) + e_attr.sample_regs_intr = egpr_masks[i]; + else + e_attr.sample_regs_user = egpr_masks[i]; + + fd = sys_perf_event_open(&e_attr, pid, cpu, -1, flags); + if (fd < 0 && intr && max_precise > 0) { + e_attr.precise_ip = 1; + fd = sys_perf_event_open(&e_attr, pid, cpu, -1, flags); + } + if (fd >= 0) { + close(fd); + mask |= egpr_masks[i]; + abi |= PERF_SAMPLE_REGS_ABI_SIMD; + } + } + } + } + + if (!(abi & PERF_SAMPLE_REGS_ABI_SIMD)) { + u64 ext_mask = GENMASK_ULL(63, 32) & ~gpr_mask; + struct perf_event_attr x_attr = *base_attr; + bool batch_ok = false; + + x_attr.disabled = 1; + x_attr.sample_period = base_attr->sample_period ?: 10000; + x_attr.sample_type = sample_type; + if (intr && max_precise > 0) + x_attr.precise_ip = 1; + + if (ext_mask) { + if (intr) + x_attr.sample_regs_intr = ext_mask; + else + x_attr.sample_regs_user = ext_mask; + + fd = sys_perf_event_open(&x_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + mask |= ext_mask; + batch_ok = true; + } + } + + if (!batch_ok) { + for (int b = 0; b < 64; b++) { + const char *rname; + + if (gpr_mask & BIT_ULL(b)) + continue; + rname = perf_reg_name(b, EM_HOST, EF_HOST, 0); + if (!rname || !strcmp(rname, "unknown")) + continue; + if (intr) + x_attr.sample_regs_intr = BIT_ULL(b); + else + x_attr.sample_regs_user = BIT_ULL(b); + fd = sys_perf_event_open(&x_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + mask |= BIT_ULL(b); + } + } + } + } + + for (int is_pred = 0; is_pred <= 1; is_pred++) { + bool pred = is_pred != 0; + u64 class_mask = intr ? + perf_intr_simd_reg_class_mask(EM_HOST, pred) : + perf_user_simd_reg_class_mask(EM_HOST, pred); + u64 supported_classes = 0; + + for (int reg_c = 0; reg_c < 64; reg_c++) { + struct perf_event_attr s_attr = *base_attr; + u16 qwords = 0; + u64 bitmap; + + if (!(class_mask & BIT_ULL(reg_c))) + continue; + bitmap = intr ? + perf_intr_simd_reg_class_bitmap_qwords(EM_HOST, reg_c, + &qwords, pred) : + perf_user_simd_reg_class_bitmap_qwords(EM_HOST, reg_c, + &qwords, pred); + if (!bitmap || !qwords) + continue; + + s_attr.size = sizeof(s_attr); + s_attr.disabled = 1; + s_attr.sample_period = base_attr->sample_period ?: 10000; + s_attr.sample_type = sample_type; + s_attr.sample_simd_regs_enabled = 1; + if (pred) { + s_attr.sample_simd_pred_reg_qwords = qwords; + if (intr) + s_attr.sample_simd_pred_reg_intr = bitmap; + else + s_attr.sample_simd_pred_reg_user = bitmap; + } else { + s_attr.sample_simd_vec_reg_qwords = qwords; + if (intr) + s_attr.sample_simd_vec_reg_intr = bitmap; + else + s_attr.sample_simd_vec_reg_user = bitmap; + } + + fd = sys_perf_event_open(&s_attr, pid, cpu, -1, flags); + if (fd < 0 && intr && max_precise > 0) { + s_attr.precise_ip = 1; + fd = sys_perf_event_open(&s_attr, pid, cpu, -1, flags); + } + if (fd >= 0) { + close(fd); + supported_classes |= BIT_ULL(reg_c); + } + } + + if (pred) + *out_pred_mask = supported_classes; + else + *out_simd_mask = supported_classes; + } + + *out_mask = mask; + *out_abi = abi; +} + +static bool try_open_mem_evlist(struct evlist *evlist, + const struct perf_event_attr *base_attr, + pid_t pid, int cpu, unsigned long flags, + int precise_ip, u64 sample_type) +{ + int fds[16]; + size_t nr_fds = 0; + int leader_fd = -1; + struct evsel *evsel; + bool ok = true; + + evlist__for_each_entry(evlist, evsel) { + struct perf_event_attr attr = evsel->core.attr; + int fd; + + if (nr_fds >= ARRAY_SIZE(fds)) { + ok = false; + break; + } + + attr.disabled = (leader_fd < 0) ? 1 : 0; + attr.sample_period = attr.sample_period ?: 10000; + attr.sample_type = sample_type; + attr.exclude_user = base_attr->exclude_user; + attr.exclude_kernel = base_attr->exclude_kernel; + attr.exclude_hv = base_attr->exclude_hv; + attr.exclude_idle = base_attr->exclude_idle; + attr.exclude_host = base_attr->exclude_host; + attr.exclude_guest = base_attr->exclude_guest; + if (evsel->precise_max || precise_ip > 0) + attr.precise_ip = precise_ip; + + fd = sys_perf_event_open(&attr, pid, cpu, leader_fd, flags); + if (fd < 0) { + ok = false; + break; + } + if (leader_fd < 0) + leader_fd = fd; + fds[nr_fds++] = fd; + } + + for (size_t i = nr_fds; i > 0; i--) + close(fds[i - 1]); + + return ok && nr_fds > 0; +} + +static void probe_mem_events(struct perf_pmu *pmu, + const struct perf_event_attr *base_attr, + pid_t pid, int cpu, unsigned long flags) +{ + static const u64 extra_mem_stypes[] = { + PERF_SAMPLE_WEIGHT, + PERF_SAMPLE_WEIGHT_STRUCT, + PERF_SAMPLE_PHYS_ADDR, + PERF_SAMPLE_DATA_PAGE_SIZE, + PERF_SAMPLE_TRANSACTION, + }; + + if (!pmu->mem_events) + return; + + perf_pmu__mem_events_init_pmu(pmu); + + for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) { + struct perf_mem_event *e = perf_pmu__mem_events_ptr(pmu, j); + const char *s; + char buf[256]; + struct evlist *evlist; + struct parse_events_error err; + struct evsel *first; + int max_p, min_p, working_p = -1; + u64 base_stype = PERF_SAMPLE_IP | PERF_SAMPLE_ADDR | PERF_SAMPLE_DATA_SRC; + int ret; + + if (!e || !e->tag || !e->supported) + continue; + + s = perf_pmu__mem_events_name(pmu, j, buf, sizeof(buf)); + if (!s) + continue; + + evlist = evlist__new(); + if (!evlist) + continue; + + parse_events_error__init(&err); + ret = __parse_events(evlist, s, /*pmu_filter=*/NULL, + /*cputype_filter=*/false, &err, /*fake_pmu=*/false, + /*warn_if_reordered=*/false, /*fake_tp=*/false); + parse_events_error__exit(&err); + if (ret || evlist__empty(evlist)) { + evlist__put(evlist); + continue; + } + + first = evlist__first(evlist); + if (first->precise_max) { + max_p = pmu->probed.max_precise > 0 ? pmu->probed.max_precise : 3; + min_p = 1; + } else { + max_p = first->core.attr.precise_ip; + min_p = first->core.attr.precise_ip; + } + + for (int p = max_p; p >= min_p; p--) { + if (try_open_mem_evlist(evlist, base_attr, pid, cpu, + flags, p, base_stype)) { + working_p = p; + break; + } + } + + if (working_p >= 0) { + pmu->probed.mem_events_mask |= (1u << j); + pmu->probed.mem_sample_types |= PERF_SAMPLE_ADDR | PERF_SAMPLE_DATA_SRC; + + for (size_t k = 0; k < ARRAY_SIZE(extra_mem_stypes); k++) { + u64 st = base_stype | extra_mem_stypes[k]; + + if (try_open_mem_evlist(evlist, base_attr, pid, cpu, + flags, working_p, st)) { + pmu->probed.mem_sample_types |= extra_mem_stypes[k]; + } + } + } + + evlist__put(evlist); + } +} + +static void probe_mmap_caps(struct perf_pmu *pmu, + const struct perf_event_attr *base_attr, + int pmu_cpu, pid_t pid, int cpu, + unsigned long flags) +{ + struct perf_event_attr attr = *base_attr; + struct perf_event_mmap_page *pc; + long page_sz = sysconf(_SC_PAGESIZE); + cpu_set_t old_mask, mask; + bool affinity_changed = false; + bool has_rdpmc_cap = false; + int events_fd; + int fd = -1; + + pmu->probed.rdpmc_sysfs = -1; + + if (page_sz <= 0) + page_sz = 4096; + + events_fd = perf_pmu__event_source_devices_fd(); + if (events_fd >= 0) { + int pmu_dirfd = openat(events_fd, pmu->name, O_DIRECTORY | O_RDONLY | O_CLOEXEC); + + if (pmu_dirfd >= 0) { + if (faccessat(pmu_dirfd, "rdpmc", F_OK, 0) == 0) { + int rfd; + + has_rdpmc_cap = true; + rfd = openat(pmu_dirfd, "rdpmc", O_RDONLY | O_CLOEXEC); + if (rfd >= 0) { + char rbuf[16]; + ssize_t n = read(rfd, rbuf, sizeof(rbuf) - 1); + + if (n > 0) { + rbuf[n] = '\0'; + pmu->probed.rdpmc_sysfs = atoi(rbuf); + } + close(rfd); + } + } + close(pmu_dirfd); + } + close(events_fd); + } + + if (pmu_cpu >= 0 && pmu_cpu < CPU_SETSIZE && + sched_getaffinity(0, sizeof(old_mask), &old_mask) == 0) { + CPU_ZERO(&mask); + CPU_SET(pmu_cpu, &mask); + if (sched_setaffinity(0, sizeof(mask), &mask) == 0) + affinity_changed = true; + } + + attr.disabled = 0; + + /* + * On architectures such as arm64, user-space counter reading must be + * explicitly requested via format/rdpmc (config1:1). + */ + if (perf_pmu__has_format(pmu, "rdpmc")) { + struct perf_event_attr r_attr = attr; + int uval; + + has_rdpmc_cap = true; + if (pmu->probed.rdpmc_sysfs < 0 && + sysctl__read_int("kernel/perf_user_access", &uval) == 0) + pmu->probed.rdpmc_sysfs = uval; + + r_attr.config1 |= 0x2; + fd = sys_perf_event_open(&r_attr, 0, -1, -1, flags); + } + + if (fd < 0 && !pmu->is_uncore) + fd = sys_perf_event_open(&attr, 0, -1, -1, flags); + if (fd < 0) + fd = sys_perf_event_open(&attr, pid, cpu, -1, flags); + + if (fd >= 0) { + pc = mmap(NULL, page_sz, PROT_READ, MAP_SHARED, fd, 0); + if (pc != MAP_FAILED) { + if (has_rdpmc_cap || pc->cap_user_rdpmc) { + pmu->probed.rdpmc_available = true; + pmu->probed.pmc_width = pc->pmc_width; + } + if (pc->cap_user_rdpmc && pc->index != 0) { + struct perf_mmap map = { .base = pc }; + struct perf_counts_values start = { .val = 0 }; + struct perf_counts_values end = { .val = 0 }; + + pmu->probed.can_user_rdpmc = true; + if (perf_mmap__read_self(&map, &start) == 0) { + for (int spin = 0; spin < 10000; spin++) + barrier(); + if (perf_mmap__read_self(&map, &end) == 0 && + end.val >= start.val && end.val > 0) + pmu->probed.rdpmc_works = true; + } + } + pmu->probed.can_user_time = !!pc->cap_user_time; + pmu->probed.can_user_time_zero = !!pc->cap_user_time_zero; + pmu->probed.can_user_time_short = !!pc->cap_user_time_short; + munmap(pc, page_sz); + } + close(fd); + } + + if (affinity_changed) + sched_setaffinity(0, sizeof(old_mask), &old_mask); +} + +static bool run_skid_branch_loop(u64 iters, uintptr_t *out_s_ip, + uintptr_t *out_b_ip, uintptr_t *out_e_ip, + int *out_insn_sz, int *out_nop_cnt) +{ +#if defined(__x86_64__) + uintptr_t s_ip, b_ip, e_ip; + + asm volatile( + "lea 1f(%%rip), %[s_ip]\n\t" + "lea 2f(%%rip), %[b_ip]\n\t" + "lea 3f(%%rip), %[e_ip]\n\t" + ".balign 64\n" + "1:\n\t" + ".rept 8192\n\t" + "nop\n\t" + ".endr\n\t" + "dec %[cnt]\n\t" + "2:\n\t" + "jnz 1b\n" + "3:\n\t" + : [cnt] "+r" (iters), + [s_ip] "=&r" (s_ip), + [b_ip] "=&r" (b_ip), + [e_ip] "=&r" (e_ip) + : + : "cc", "memory" + ); + *out_s_ip = s_ip; + *out_b_ip = b_ip; + *out_e_ip = e_ip; + *out_insn_sz = 1; + *out_nop_cnt = 8192; + return true; +#elif defined(__aarch64__) + uintptr_t s_ip, b_ip, e_ip; + + asm volatile( + "adr %[s_ip], 1f\n\t" + "adr %[b_ip], 2f\n\t" + "adr %[e_ip], 3f\n\t" + ".balign 64\n" + "1:\n\t" + ".rept 8192\n\t" + "nop\n\t" + ".endr\n\t" + "subs %[cnt], %[cnt], #1\n\t" + "2:\n\t" + "b.ne 1b\n" + "3:\n\t" + : [cnt] "+r" (iters), + [s_ip] "=&r" (s_ip), + [b_ip] "=&r" (b_ip), + [e_ip] "=&r" (e_ip) + : + : "cc", "memory" + ); + *out_s_ip = s_ip; + *out_b_ip = b_ip; + *out_e_ip = e_ip; + *out_insn_sz = 4; + *out_nop_cnt = 8192; + return true; +#elif defined(__riscv) + uintptr_t s_ip, b_ip, e_ip; + + asm volatile( + ".option push\n\t" + ".option norvc\n\t" + "la %[s_ip], 1f\n\t" + "la %[b_ip], 2f\n\t" + "la %[e_ip], 3f\n\t" + ".balign 64\n" + "1:\n\t" + ".rept 1000\n\t" + "nop\n\t" + ".endr\n\t" + "addi %[cnt], %[cnt], -1\n\t" + "2:\n\t" + "bnez %[cnt], 1b\n" + "3:\n\t" + ".option pop\n\t" + : [cnt] "+r" (iters), + [s_ip] "=&r" (s_ip), + [b_ip] "=&r" (b_ip), + [e_ip] "=&r" (e_ip) + : + : "memory" + ); + *out_s_ip = s_ip; + *out_b_ip = b_ip; + *out_e_ip = e_ip; + *out_insn_sz = 4; + *out_nop_cnt = 1000; + return true; +#else + (void)iters; + *out_s_ip = 0; + *out_b_ip = 0; + *out_e_ip = 0; + *out_insn_sz = 0; + *out_nop_cnt = 0; + return false; +#endif +} + +static void probe_pmu_skid(struct perf_pmu *pmu, + const struct perf_event_attr *base_attr, + int pmu_cpu, unsigned long flags) +{ + struct perf_event_attr br_attr; + char br_buf[256]; + long page_sz = sysconf(_SC_PAGESIZE); + size_t data_sz, mmap_sz; + cpu_set_t old_mask, mask; + bool affinity_changed = false; + + if (!pmu->is_core || !pmu->probed.can_sample) + return; + + if (page_sz <= 0) + page_sz = 4096; + data_sz = 4 * (size_t)page_sz; + mmap_sz = (size_t)page_sz + data_sz; + + memset(&br_attr, 0, sizeof(br_attr)); + snprintf(br_buf, sizeof(br_buf), "%s/branches/", pmu->name); + if (!parse_candidate_attr(br_buf, &br_attr)) { + br_attr.type = PERF_TYPE_HARDWARE; + br_attr.size = base_attr->size; + br_attr.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS; + if (perf_pmus__supports_extended_type()) + br_attr.config |= (__u64)pmu->type << PERF_PMU_TYPE_SHIFT; + } + br_attr.exclude_user = base_attr->exclude_user; + br_attr.exclude_kernel = 1; + br_attr.exclude_hv = 1; + br_attr.exclude_idle = base_attr->exclude_idle; + br_attr.exclude_host = base_attr->exclude_host; + br_attr.exclude_guest = base_attr->exclude_guest; + + if (pmu_cpu >= 0 && pmu_cpu < CPU_SETSIZE && + sched_getaffinity(0, sizeof(old_mask), &old_mask) == 0) { + CPU_ZERO(&mask); + CPU_SET(pmu_cpu, &mask); + if (sched_setaffinity(0, sizeof(mask), &mask) == 0) + affinity_changed = true; + } + + for (int lvl = 0; lvl <= 3; lvl++) { + struct perf_event_attr attr = br_attr; + struct perf_event_mmap_page *pc; + uintptr_t s_ip = 0, b_ip = 0, e_ip = 0; + int insn_sz = 0, nop_cnt = 0; + void *ring; + u8 *data; + u64 head, tail = 0; + int samples = 0, exact = 0; + int min_skid = 999999, max_skid = 0; + long sum_skid = 0; + int fd; + + if (lvl > 0 && !(pmu->probed.precise_mask & (1u << lvl))) + continue; + + attr.disabled = 1; + attr.sample_period = 251; + attr.sample_type = PERF_SAMPLE_IP; + attr.precise_ip = lvl; + + fd = sys_perf_event_open(&attr, 0, -1, -1, flags); + if (fd < 0 && lvl > 0 && attr.exclude_kernel && attr.config2 == 0) { + attr.config2 = 1; + fd = sys_perf_event_open(&attr, 0, -1, -1, flags); + } + if (fd < 0) + continue; + + ring = mmap(NULL, mmap_sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (ring == MAP_FAILED) { + close(fd); + continue; + } + + ioctl(fd, PERF_EVENT_IOC_RESET, 0); + ioctl(fd, PERF_EVENT_IOC_ENABLE, 0); + if (!run_skid_branch_loop(8000, &s_ip, &b_ip, &e_ip, + &insn_sz, &nop_cnt)) { + ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); + munmap(ring, mmap_sz); + close(fd); + break; + } + ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); + + pc = ring; + head = READ_ONCE(pc->data_head); + /* Ensure data_head is read before reading ring buffer contents. */ + rmb(); + if (head > data_sz) + head = data_sz; + data = (u8 *)ring + page_sz; + + while (tail + sizeof(struct perf_event_header) <= head) { + struct perf_event_header *hdr = + (struct perf_event_header *)(data + tail); + + if (hdr->size == 0 || tail + hdr->size > head) + break; + if (hdr->type == PERF_RECORD_SAMPLE && + hdr->size >= sizeof(*hdr) + sizeof(u64)) { + u64 ip = *(u64 *)((u8 *)hdr + sizeof(*hdr)); + uintptr_t uip = (uintptr_t)ip; + + if (uip >= s_ip && uip <= e_ip && insn_sz > 0) { + int skid; + + if (uip == b_ip) { + skid = 0; + } else if (uip >= s_ip && uip < b_ip) { + long off = (long)(uip - s_ip) / insn_sz; + + skid = (off < nop_cnt) ? + (int)(off + 1) : (nop_cnt + 1); + } else { + skid = 1; + } + + samples++; + if (hdr->misc & PERF_RECORD_MISC_EXACT_IP) + exact++; + if (skid < min_skid) + min_skid = skid; + if (skid > max_skid) + max_skid = skid; + sum_skid += skid; + } + } + tail += hdr->size; + } + + if (samples > 0) { + pmu->probed.skid[lvl].measured = true; + pmu->probed.skid[lvl].samples = samples; + pmu->probed.skid[lvl].exact_samples = exact; + pmu->probed.skid[lvl].min_skid = min_skid; + pmu->probed.skid[lvl].max_skid = max_skid; + pmu->probed.skid[lvl].avg_skid = (double)sum_skid / samples; + } + + munmap(ring, mmap_sz); + close(fd); + } + + if (affinity_changed) + sched_setaffinity(0, sizeof(old_mask), &old_mask); +} + +void perf_pmu__probe(struct perf_pmu *pmu) +{ + struct probe_candidates cands = { .nr = 0 }; + struct perf_event_attr base_attr; + struct perf_event_attr group_attrs[MAX_PROBE_GROUP_SIZE]; + char uprobe_exe_path[PATH_MAX]; + __u64 uprobe_offset = 0; + size_t nr_group_attrs = 0; + unsigned long flags = perf_event_open_cloexec_flag(); + int keepalive_fd = -1; + int pmu_cpu; + pid_t pid = 0; + int cpu = 0; + int last_err = 0; + bool found = false; + + if (!pmu || pmu->probed.checked) + return; + if (perf_pmu__kind(pmu) != PERF_PMU_KIND_PE) + return; + + pmu->probed.checked = true; + pmu_cpu = select_probe_cpu(pmu); + + if (pmu->type == PERF_TYPE_TRACEPOINT) { + static const struct { + const char *sys; + const char *name; + } fast_tps[] = { + { "sched", "sched_switch" }, + { "raw_syscalls", "sys_enter" }, + { "irq", "irq_handler_entry" }, + }; + + for (size_t i = 0; i < ARRAY_SIZE(fast_tps); i++) { + int tp_id = tp_pmu__id(fast_tps[i].sys, fast_tps[i].name); + + if (tp_id >= 0) { + memset(&base_attr, 0, sizeof(base_attr)); + base_attr.type = PERF_TYPE_TRACEPOINT; + base_attr.config = tp_id; + if (try_open_attr_variants(&base_attr, false, pmu_cpu, + flags, &pid, &cpu, &last_err)) { + char tp_buf[128]; + + snprintf(tp_buf, sizeof(tp_buf), "%s:%s", + fast_tps[i].sys, fast_tps[i].name); + pmu->probed.probe_event = strdup(tp_buf); + found = true; + break; + } + } + } + if (!found) { + perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/false, + &cands, collect_probe_candidate_cb); + } + } else if (pmu->name && !strcmp(pmu->name, "kprobe")) { + static const char * const kprobe_funcs[] = { + "vfs_read", + "schedule", + "do_sys_openat2", + }; + + for (size_t i = 0; i < ARRAY_SIZE(kprobe_funcs); i++) { + memset(&base_attr, 0, sizeof(base_attr)); + base_attr.type = pmu->type; + base_attr.size = sizeof(base_attr); + base_attr.kprobe_func = (uintptr_t)kprobe_funcs[i]; + base_attr.probe_offset = 0; + if (try_open_attr_variants(&base_attr, false, pmu_cpu, + flags, &pid, &cpu, &last_err)) { + char kp_buf[128]; + + snprintf(kp_buf, sizeof(kp_buf), "kprobe:%s", + kprobe_funcs[i]); + pmu->probed.probe_event = strdup(kp_buf); + found = true; + break; + } + } + } else if (pmu->name && !strcmp(pmu->name, "uprobe")) { + if (get_self_uprobe_target(uprobe_exe_path, &uprobe_offset)) { + memset(&base_attr, 0, sizeof(base_attr)); + base_attr.type = pmu->type; + base_attr.size = sizeof(base_attr); + base_attr.uprobe_path = (uintptr_t)uprobe_exe_path; + base_attr.probe_offset = uprobe_offset; + if (try_open_attr_variants(&base_attr, false, pmu_cpu, + flags, &pid, &cpu, &last_err)) { + const char *slash = strrchr(uprobe_exe_path, '/'); + const char *bname = slash ? slash + 1 : uprobe_exe_path; + + if (asprintf(&pmu->probed.probe_event, "uprobe:%s+0x%llx", + bname, (unsigned long long)uprobe_offset) < 0) + pmu->probed.probe_event = NULL; + found = true; + } + } + } else if (pmu->is_core) { + static const char * const core_events[] = { + "cycles", + "instructions", + "branches", + "branch-misses", + "cache-misses", + "cache-references", + "bus-cycles", + "ref-cycles", + }; + char buf[256]; + + for (size_t i = 0; i < ARRAY_SIZE(core_events); i++) { + snprintf(buf, sizeof(buf), "%s/%s/", pmu->name, core_events[i]); + probe_candidates__add(&cands, buf); + } + perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/false, + &cands, collect_probe_candidate_cb); + } else if (pmu->type == PERF_TYPE_SOFTWARE) { + static const char * const sw_events[] = { + "software/cpu-clock/", + "software/task-clock/", + "software/page-faults/", + "software/context-switches/", + "software/cpu-migrations/", + "software/minor-faults/", + "software/major-faults/", + "software/dummy/", + }; + + for (size_t i = 0; i < ARRAY_SIZE(sw_events); i++) + probe_candidates__add(&cands, sw_events[i]); + } else if (pmu->type != PERF_TYPE_BREAKPOINT) { + if (pmu->selectable) { + char buf[256]; + + if (perf_pmu__has_format(pmu, "swfilt")) { + snprintf(buf, sizeof(buf), "%s/swfilt=1/", pmu->name); + probe_candidates__add(&cands, buf); + } + snprintf(buf, sizeof(buf), "%s//", pmu->name); + probe_candidates__add(&cands, buf); + } + perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/false, + &cands, collect_probe_candidate_cb); + } + + for (size_t i = 0; !found && i < cands.nr; i++) { + memset(&base_attr, 0, sizeof(base_attr)); + if (!parse_candidate_attr(cands.names[i], &base_attr)) + continue; + + if (try_open_attr_variants(&base_attr, pmu->is_uncore, pmu_cpu, + flags, &pid, &cpu, &last_err)) { + pmu->probed.probe_event = strdup(cands.names[i]); + found = true; + break; + } + } + + if (!found && pmu->is_core) { + memset(&base_attr, 0, sizeof(base_attr)); + base_attr.type = PERF_TYPE_HARDWARE; + base_attr.config = PERF_COUNT_HW_CPU_CYCLES; + if (perf_pmus__supports_extended_type()) + base_attr.config |= (__u64)pmu->type << PERF_PMU_TYPE_SHIFT; + if (try_open_attr_variants(&base_attr, false, pmu_cpu, + flags, &pid, &cpu, &last_err)) { + pmu->probed.probe_event = strdup("cycles"); + found = true; + } + } + + if (!found && pmu->type == PERF_TYPE_BREAKPOINT) { + memset(&base_attr, 0, sizeof(base_attr)); + base_attr.type = PERF_TYPE_BREAKPOINT; + base_attr.bp_addr = (unsigned long)&dummy_bp_var; + base_attr.bp_type = HW_BREAKPOINT_W; + base_attr.bp_len = HW_BREAKPOINT_LEN_4; + if (try_open_attr_variants(&base_attr, false, pmu_cpu, + flags, &pid, &cpu, &last_err)) { + pmu->probed.probe_event = strdup("mem:<addr>:w"); + found = true; + } + } + + if (!found && cands.nr == 0) { + memset(&base_attr, 0, sizeof(base_attr)); + base_attr.type = pmu->type; + base_attr.size = sizeof(base_attr); + if (pmu->perf_event_attr_init_default) + pmu->perf_event_attr_init_default(pmu, &base_attr); + if (try_open_attr_variants(&base_attr, pmu->is_uncore, pmu_cpu, + flags, &pid, &cpu, &last_err)) { + char buf[256]; + + snprintf(buf, sizeof(buf), "%s//", pmu->name); + pmu->probed.probe_event = strdup(buf); + found = true; + } + } + + if (!found) { + probe_candidates__exit(&cands); + pmu->probed.open_supported = false; + pmu->probed.open_err = last_err ?: ENOENT; + return; + } + + pmu->probed.open_supported = true; + + /* + * For tracepoint events, closing the last fd for a tracepoint triggers + * tracepoint_synchronize_unregister() (an RCU grace period) in the + * kernel. Hold one disabled fd open across the probe sequence so + * tp_event->perf_refcount stays >= 1 and each probe open/close is + * instantaneous. + */ + if (pmu->type == PERF_TYPE_TRACEPOINT) { + struct perf_event_attr ka_attr = base_attr; + + ka_attr.disabled = 1; + keepalive_fd = sys_perf_event_open(&ka_attr, pid, cpu, -1, flags); + } + + group_attrs[nr_group_attrs++] = base_attr; + + for (size_t i = 0; pmu->type != PERF_TYPE_TRACEPOINT && + i < cands.nr && nr_group_attrs < MAX_PROBE_GROUP_SIZE; i++) { + struct perf_event_attr cand_attr; + int fd; + bool dup = false; + + memset(&cand_attr, 0, sizeof(cand_attr)); + if (!parse_candidate_attr(cands.names[i], &cand_attr)) + continue; + + cand_attr.sample_period = base_attr.sample_period; + cand_attr.freq = base_attr.freq; + cand_attr.sample_type = base_attr.sample_type; + cand_attr.exclude_user = base_attr.exclude_user; + cand_attr.exclude_kernel = base_attr.exclude_kernel; + cand_attr.exclude_hv = base_attr.exclude_hv; + cand_attr.exclude_idle = base_attr.exclude_idle; + cand_attr.exclude_host = base_attr.exclude_host; + cand_attr.exclude_guest = base_attr.exclude_guest; + cand_attr.disabled = 1; + + for (size_t j = 0; j < nr_group_attrs; j++) { + if (group_attrs[j].type == cand_attr.type && + group_attrs[j].config == cand_attr.config && + group_attrs[j].config1 == cand_attr.config1 && + group_attrs[j].config2 == cand_attr.config2) { + dup = true; + break; + } + } + if (dup) + continue; + + fd = sys_perf_event_open(&cand_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + group_attrs[nr_group_attrs++] = cand_attr; + } + } + + probe_candidates__exit(&cands); + + /* 1. Counting vs Sampling support */ + if (base_attr.sample_period == 0) { + struct perf_event_attr samp_attr = base_attr; + int fd; + + pmu->probed.can_count = true; + samp_attr.sample_period = 10000; + samp_attr.sample_type = PERF_SAMPLE_IP; + fd = sys_perf_event_open(&samp_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + pmu->probed.can_sample = true; + } + } else { + pmu->probed.can_count = false; + pmu->probed.can_sample = true; + } + + /* 2. All enum perf_event_sample_format types */ + if (pmu->probed.can_sample) + probe_sample_types(pmu, &base_attr, pid, cpu, flags); + + /* + * 3. Precise sampling support (:p, :pp, :ppp). + * PMUs that do not validate attr.precise_ip (such as software, + * breakpoint, or intel_pt) silently accept precise_ip > 0 even when + * sample_period == 0. A PMU that genuinely implements precise_ip + * either rejects precise_ip > 0 on non-sampling events or is a + * sampling-only PMU (such as AMD ibs_op). + * Note: On AMD, core events like cycles:P are forwarded to IBS and + * support higher precision than advertised in sysfs caps/max_precise. + */ + if (pmu->probed.can_sample && !pmu->auxtrace) { + bool ignores_precise_ip = false; + + if (pmu->probed.can_count) { + struct perf_event_attr c_attr = base_attr; + int fd; + + c_attr.sample_period = 0; + c_attr.freq = 0; + c_attr.sample_type = 0; + c_attr.precise_ip = 1; + fd = sys_perf_event_open(&c_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + ignores_precise_ip = true; + } + } + + if (!ignores_precise_ip) { + for (int level = 1; level <= 3; level++) { + struct perf_event_attr p_attr = base_attr; + int fd; + + p_attr.sample_period = 10000; + p_attr.sample_type = PERF_SAMPLE_IP; + p_attr.precise_ip = level; + fd = sys_perf_event_open(&p_attr, pid, cpu, -1, flags); + if (fd < 0 && p_attr.exclude_kernel && p_attr.config2 == 0) { + p_attr.config2 = 1; + fd = sys_perf_event_open(&p_attr, pid, cpu, -1, flags); + } + if (fd >= 0) { + close(fd); + pmu->probed.precise_mask |= (1u << level); + pmu->probed.max_precise = level; + } + } + } + + probe_pmu_skid(pmu, &base_attr, pmu_cpu, flags); + } + + /* 3b. User and interrupt register sampling masks */ + if (pmu->probed.sample_types & PERF_SAMPLE_REGS_USER) { + probe_pmu_regs_one(pmu, &base_attr, pid, cpu, flags, + pmu->probed.max_precise, /*intr=*/false, + &pmu->probed.user_regs_mask, + &pmu->probed.user_regs_abi, + &pmu->probed.user_simd_mask, + &pmu->probed.user_pred_mask); + } + if (pmu->probed.sample_types & PERF_SAMPLE_REGS_INTR) { + probe_pmu_regs_one(pmu, &base_attr, pid, cpu, flags, + pmu->probed.max_precise, /*intr=*/true, + &pmu->probed.intr_regs_mask, + &pmu->probed.intr_regs_abi, + &pmu->probed.intr_simd_mask, + &pmu->probed.intr_pred_mask); + } + + /* 3c. Memory event types and memory sample formats */ + probe_mem_events(pmu, &base_attr, pid, cpu, flags); + + /* 4. Privilege / exclusion modifiers */ + pmu->probed.exclude_user = + test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_USER); + pmu->probed.exclude_kernel = + test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_KERNEL); + pmu->probed.exclude_hv = + test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_HV); + pmu->probed.exclude_idle = + test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_IDLE); + pmu->probed.exclude_host = + test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_HOST); + pmu->probed.exclude_guest = + test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_GUEST); + + /* 5. Execution features */ + if (pmu->probed.can_sample) { + struct perf_event_attr l_attr = base_attr; + struct perf_event_attr f_attr = nr_group_attrs > 1 ? group_attrs[1] : base_attr; + int l_fd; + + l_attr.disabled = 1; + l_attr.sample_period = 10000; + l_attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_READ; + l_attr.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID; + + f_attr.disabled = 0; + f_attr.sample_period = 0; + f_attr.sample_type = 0; + f_attr.read_format = 0; + + l_fd = sys_perf_event_open(&l_attr, pid, cpu, -1, flags); + if (l_fd >= 0) { + int f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags); + + if (f_fd >= 0) { + if (ioctl(l_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) >= 0) + pmu->probed.can_leader_sampling = true; + ioctl(l_fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP); + close(f_fd); + } + close(l_fd); + } + } + + { + struct perf_event_attr l_attr = base_attr; + int l_fd; + + l_attr.disabled = 1; + l_fd = sys_perf_event_open(&l_attr, pid, cpu, -1, flags); + if (l_fd >= 0) { + struct perf_event_attr sw_attr; + int sw_fd; + + memset(&sw_attr, 0, sizeof(sw_attr)); + sw_attr.type = PERF_TYPE_SOFTWARE; + sw_attr.size = sizeof(sw_attr); + sw_attr.config = PERF_COUNT_SW_DUMMY; + sw_attr.disabled = 0; + sw_attr.exclude_user = base_attr.exclude_user; + sw_attr.exclude_kernel = base_attr.exclude_kernel; + sw_attr.exclude_hv = base_attr.exclude_hv; + sw_attr.exclude_idle = base_attr.exclude_idle; + sw_attr.exclude_host = base_attr.exclude_host; + sw_attr.exclude_guest = base_attr.exclude_guest; + + sw_fd = sys_perf_event_open(&sw_attr, pid, cpu, l_fd, flags); + if (sw_fd < 0) { + sw_attr.config = PERF_COUNT_SW_CPU_CLOCK; + sw_fd = sys_perf_event_open(&sw_attr, pid, cpu, l_fd, flags); + } + if (sw_fd >= 0) { + pmu->probed.can_sw_grouping = true; + close(sw_fd); + } + close(l_fd); + } + } + + { + struct perf_event_attr inh_attr = base_attr; + int fd; + + inh_attr.disabled = 1; + inh_attr.inherit = 1; + fd = sys_perf_event_open(&inh_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + pmu->probed.can_inherit = true; + } + } + + if (pmu->probed.can_sample) { + struct perf_event_attr freq_attr = base_attr; + int fd; + + freq_attr.disabled = 1; + freq_attr.freq = 1; + freq_attr.sample_freq = 4000; + freq_attr.sample_type = PERF_SAMPLE_IP; + fd = sys_perf_event_open(&freq_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + pmu->probed.can_freq = true; + } + } + + if (pmu->probed.can_sample) { + struct perf_event_attr wb_attr = base_attr; + int fd; + + wb_attr.disabled = 1; + wb_attr.sample_period = 10000; + wb_attr.sample_type = PERF_SAMPLE_IP; + wb_attr.write_backward = 1; + fd = sys_perf_event_open(&wb_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + pmu->probed.can_write_backward = true; + } + } + + { + struct perf_event_attr pin_attr = base_attr; + int fd; + + pin_attr.disabled = 1; + pin_attr.pinned = 1; + fd = sys_perf_event_open(&pin_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + pmu->probed.can_pinned = true; + } + } + + { + struct perf_event_attr exc_attr = base_attr; + int fd; + + exc_attr.disabled = 1; + exc_attr.exclusive = 1; + fd = sys_perf_event_open(&exc_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + pmu->probed.can_exclusive = true; + } + } + + { + struct perf_event_attr lost_attr = base_attr; + int fd; + + lost_attr.disabled = 1; + lost_attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | + PERF_FORMAT_TOTAL_TIME_RUNNING | + PERF_FORMAT_LOST; + fd = sys_perf_event_open(&lost_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + pmu->probed.can_format_lost = true; + } + } + + /* 5b. User-space mmap capabilities (rdpmc, user_time, pmc_width) */ + probe_mmap_caps(pmu, &base_attr, pmu_cpu, pid, cpu, flags); + + /* 5c. Auto-counter reload (acr_mask / ratio-to-prev) */ + if (pmu->probed.can_sample && perf_pmu__has_format(pmu, "acr_mask")) { + struct perf_event_attr l_attr = base_attr; + struct perf_event_attr f_attr = nr_group_attrs > 1 ? group_attrs[1] : base_attr; + int l_fd; + + l_attr.disabled = 1; + l_attr.freq = 0; + l_attr.sample_period = 200000; + l_attr.sample_type = PERF_SAMPLE_IP; + l_attr.config2 = 0x2; + + f_attr.disabled = 0; + f_attr.freq = 0; + f_attr.sample_period = 100000; + f_attr.sample_type = PERF_SAMPLE_IP; + f_attr.config2 = 0x3; + + l_fd = sys_perf_event_open(&l_attr, pid, cpu, -1, flags); + if (l_fd >= 0) { + int f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags); + + if (f_fd >= 0) { + if (ioctl(l_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) >= 0) + pmu->probed.can_acr = true; + ioctl(l_fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP); + close(f_fd); + } + close(l_fd); + } + } + + /* 5d. AUX area event actions (start_paused, pause, resume, aux_sample) */ + if (pmu->auxtrace) { + struct perf_event_attr a_attr = base_attr; + int l_fd; + + a_attr.size = max_t(__u32, base_attr.size, PERF_ATTR_SIZE_VER8); + a_attr.disabled = 1; + a_attr.aux_start_paused = 1; + l_fd = sys_perf_event_open(&a_attr, pid, cpu, -1, flags); + if (l_fd >= 0) { + close(l_fd); + pmu->probed.can_aux_start_paused = true; + } + + a_attr = base_attr; + a_attr.disabled = 1; + l_fd = sys_perf_event_open(&a_attr, pid, cpu, -1, flags); + if (l_fd >= 0) { + struct perf_event_attr f_attr; + int f_fd; + + memset(&f_attr, 0, sizeof(f_attr)); + f_attr.type = PERF_TYPE_SOFTWARE; + f_attr.size = max_t(__u32, base_attr.size, PERF_ATTR_SIZE_VER8); + f_attr.config = PERF_COUNT_SW_CPU_CLOCK; + f_attr.sample_period = 10000; + f_attr.sample_type = PERF_SAMPLE_IP; + f_attr.exclude_user = base_attr.exclude_user; + f_attr.exclude_kernel = base_attr.exclude_kernel; + f_attr.exclude_hv = base_attr.exclude_hv; + f_attr.exclude_idle = base_attr.exclude_idle; + f_attr.exclude_host = base_attr.exclude_host; + f_attr.exclude_guest = base_attr.exclude_guest; + + f_attr.aux_pause = 1; + f_attr.aux_resume = 0; + f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags); + if (f_fd >= 0) { + close(f_fd); + pmu->probed.can_aux_pause = true; + } + + f_attr.aux_pause = 0; + f_attr.aux_resume = 1; + f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags); + if (f_fd >= 0) { + close(f_fd); + pmu->probed.can_aux_resume = true; + } + + f_attr.aux_resume = 0; + f_attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_AUX; + f_attr.aux_sample_size = 64; + f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags); + if (f_fd >= 0) { + close(f_fd); + pmu->probed.can_aux_sample = true; + } + + close(l_fd); + } + } + + /* 6. Max event group size & max simultaneously active group size */ + 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; + pmu->probed.max_active_group_size = MAX_PROBE_GROUP_SIZE; + pmu->probed.max_active_capped = true; + } else { + pmu->probed.max_group_size = 1; + pmu->probed.max_active_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, + &pmu->probed.max_active_group_size, + &pmu->probed.max_active_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", + "ref-cycles", + }; + 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, + PERF_COUNT_HW_REF_CPU_CYCLES, + }; + 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.config = gp_configs[i]; + if (perf_pmus__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, + &pmu->probed.max_active_gp_group_size, + &pmu->probed.max_active_gp_capped); + } + } + + if (keepalive_fd >= 0) + close(keepalive_fd); +} + const char *perf_pmu__name_from_config(struct perf_pmu *pmu, u64 config) { struct hashmap_entry *entry; diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h index 2c8916ad073e..fe8a90752396 100644 --- a/tools/perf/util/pmu.h +++ b/tools/perf/util/pmu.h @@ -207,6 +207,138 @@ struct perf_pmu { * @mem_events: List of the supported mem events */ struct perf_mem_event *mem_events; + + /** + * @probed: Capabilities discovered by probing perf_event_open() on live + * systems. + */ + struct { + /** @checked: Has perf_pmu__probe() been run on this PMU? */ + bool checked; + /** @open_supported: Could a probe event be opened on this PMU? */ + bool open_supported; + /** @can_count: Supports counting (sample_period == 0). */ + bool can_count; + /** @can_sample: Supports interrupt-based sampling (sample_period > 0). */ + bool can_sample; + /** @can_branch_stack: Supports PERF_SAMPLE_BRANCH_STACK. */ + bool can_branch_stack; + /** @can_leader_sampling: Supports group leader sampling (:S). */ + bool can_leader_sampling; + /** @can_sw_grouping: Supports grouping software events with this PMU's events. */ + bool can_sw_grouping; + /** @can_inherit: Supports perf_event_attr::inherit across child tasks. */ + bool can_inherit; + /** @can_freq: Supports dynamic sample frequency (perf_event_attr::freq). */ + bool can_freq; + /** @can_write_backward: Supports overwrite ring buffer (write_backward). */ + bool can_write_backward; + /** @can_callchain: Supports PERF_SAMPLE_CALLCHAIN. */ + bool can_callchain; + /** @can_data_src: Supports PERF_SAMPLE_DATA_SRC. */ + bool can_data_src; + /** @can_weight: Supports PERF_SAMPLE_WEIGHT. */ + bool can_weight; + /** @can_pinned: Supports perf_event_attr::pinned. */ + bool can_pinned; + /** @can_exclusive: Supports perf_event_attr::exclusive. */ + bool can_exclusive; + /** @can_format_lost: Supports PERF_FORMAT_LOST. */ + bool can_format_lost; + /** @can_acr: Supports auto-counter reload (acr_mask / ratio-to-prev). */ + bool can_acr; + /** @can_aux_start_paused: Supports aux_start_paused (PERF_PMU_CAP_AUX_PAUSE). */ + bool can_aux_start_paused; + /** @can_aux_pause: Supports aux_pause action on group followers. */ + bool can_aux_pause; + /** @can_aux_resume: Supports aux_resume action on group followers. */ + bool can_aux_resume; + /** @can_aux_sample: Supports AUX area snapshot sampling (aux_sample_size). */ + bool can_aux_sample; + /** @rdpmc_available: PMU supports user-space counter reading (rdpmc/mrs/csrr). */ + bool rdpmc_available; + /** @can_user_rdpmc: User-space counter reading enabled (cap_user_rdpmc & index). */ + bool can_user_rdpmc; + /** @rdpmc_works: User-space counter read instruction executed and worked. */ + bool rdpmc_works; + /** @can_user_time: Supports user-space time conversion (cap_user_time). */ + bool can_user_time; + /** @can_user_time_zero: Supports cap_user_time_zero in mmap page. */ + bool can_user_time_zero; + /** @can_user_time_short: Supports cap_user_time_short in mmap page. */ + bool can_user_time_short; + /** @exclude_user: Supports perf_event_attr::exclude_user. */ + bool exclude_user; + /** @exclude_kernel: Supports perf_event_attr::exclude_kernel. */ + bool exclude_kernel; + /** @exclude_hv: Supports perf_event_attr::exclude_hv. */ + bool exclude_hv; + /** @exclude_idle: Supports perf_event_attr::exclude_idle. */ + bool exclude_idle; + /** @exclude_host: Supports perf_event_attr::exclude_host. */ + bool exclude_host; + /** @exclude_guest: Supports perf_event_attr::exclude_guest. */ + bool exclude_guest; + /** @max_group_capped: Was max_group_size capped at probe limit? */ + bool max_group_capped; + /** @max_active_capped: Was max_active_group_size capped at probe limit? */ + bool max_active_capped; + /** @max_gp_group_capped: Was max_gp_group_size capped at probe limit? */ + bool max_gp_group_capped; + /** @max_active_gp_capped: Was max_active_gp_group_size capped at probe limit? */ + bool max_active_gp_capped; + /** @probe_event: Name of the event used to probe the PMU. */ + char *probe_event; + /** @open_err: errno if open_supported is false. */ + int open_err; + /** @max_precise: Maximum precise_ip level (0..3) supported. */ + int max_precise; + /** @precise_mask: Bitmask of supported precise_ip levels (1..3). */ + unsigned int precise_mask; + /** @max_group_size: Max events in a group accepted by perf_event_open(). */ + int max_group_size; + /** @max_active_group_size: Max events in a group active simultaneously. */ + int max_active_group_size; + /** @max_gp_group_size: Max GP events in a group accepted by perf_event_open(). */ + int max_gp_group_size; + /** @max_active_gp_group_size: Max GP events in a group active simultaneously. */ + int max_active_gp_group_size; + /** @user_regs_abi: ABI flags for user_regs_mask. */ + int user_regs_abi; + /** @intr_regs_abi: ABI flags for intr_regs_mask. */ + int intr_regs_abi; + /** @rdpmc_sysfs: Value from sysfs rdpmc or perf_user_access (-1 if unknown). */ + int rdpmc_sysfs; + /** @mem_events_mask: Bitmask of memory event types verified by probing. */ + unsigned int mem_events_mask; + /** @pmc_width: Bit-width of hardware counter for user-space rdpmc. */ + u16 pmc_width; + /** @sample_types: Bitmask of supported enum perf_event_sample_format flags. */ + u64 sample_types; + /** @mem_sample_types: Bitmask of memory sample formats verified on mem_events. */ + u64 mem_sample_types; + /** @user_regs_mask: Bitmask of supported sample_regs_user registers. */ + u64 user_regs_mask; + /** @intr_regs_mask: Bitmask of supported sample_regs_intr registers. */ + u64 intr_regs_mask; + /** @user_simd_mask: Bitmask of supported user SIMD vector register classes. */ + u64 user_simd_mask; + /** @user_pred_mask: Bitmask of supported user SIMD predicate register classes. */ + u64 user_pred_mask; + /** @intr_simd_mask: Bitmask of supported intr SIMD vector register classes. */ + u64 intr_simd_mask; + /** @intr_pred_mask: Bitmask of supported intr SIMD predicate register classes. */ + u64 intr_pred_mask; + /** @skid: Measured branch instruction skid at precise_ip levels (0..3). */ + struct { + bool measured; + int samples; + int exact_samples; + int min_skid; + int max_skid; + double avg_skid; + } skid[4]; + } probed; }; struct perf_pmu_info { @@ -329,8 +461,9 @@ bool pmu_uncore_identifier_match(const char *compat, const char *id); int perf_pmu__convert_scale(const char *scale, char **end, double *sval); struct perf_pmu_caps *perf_pmu__get_cap(struct perf_pmu *pmu, const char *name); - +int perf_pmu__add_cap(struct perf_pmu *pmu, const char *name, const char *value); int perf_pmu__caps_parse(struct perf_pmu *pmu); +void perf_pmu__probe(struct perf_pmu *pmu); void perf_pmu__warn_invalid_config(struct perf_pmu *pmu, __u64 config, const char *name, int config_num, diff --git a/tools/perf/util/pmus.c b/tools/perf/util/pmus.c index 1355b317f3ed..44f24e3a177a 100644 --- a/tools/perf/util/pmus.c +++ b/tools/perf/util/pmus.c @@ -7,13 +7,16 @@ #include <subcmd/pager.h> #include <sys/types.h> #include <ctype.h> +#include <errno.h> #include <pthread.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "cpumap.h" #include "debug.h" #include "drm_pmu.h" +#include "env.h" #include "evsel.h" #include "pmus.h" #include "pmu.h" @@ -452,7 +455,7 @@ struct perf_pmu *perf_pmus__scan_matching_wildcard(struct perf_pmu *pmu, const c return NULL; } -static struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmu *pmu) +struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmu *pmu) { bool use_core_pmus = !pmu || pmu->is_core; int last_pmu_name_len = 0; @@ -489,6 +492,27 @@ static struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmu *pmu) return NULL; } +size_t perf_pmu__num_duplicates(const struct perf_pmu *pmu) +{ + const struct perf_pmu *pos = pmu; + size_t base_len; + size_t count = 1; + + if (!pmu || pmu->is_core) + return 1; + + base_len = pmu_name_len_no_suffix(pmu->name ?: ""); + list_for_each_entry_continue(pos, &other_pmus, list) { + size_t pos_len = pmu_name_len_no_suffix(pos->name ?: ""); + + if (pos_len != base_len || + strncmp(pmu->name ?: "", pos->name ?: "", base_len)) + break; + count++; + } + return count; +} + struct perf_pmu *perf_pmus__scan_for_uncore_id(struct perf_pmu *pmu, const char *compat) { if (!pmu) { @@ -916,3 +940,198 @@ struct perf_pmu *perf_pmus__fake_pmu(void) return &fake; } + +static bool env_is_pmu_core(const char *name, struct perf_env *env) +{ + if (!strcmp(name, "cpu") || !strcmp(name, "cpum_cf") || + !strcmp(name, "default_core") || strstarts(name, "cpu_") || + strstarts(name, "armv") || + (strstarts(name, "apple_") && strstr(name, "_pmu"))) + return true; + + if (env && env->hybrid_nodes) { + for (int i = 0; i < env->nr_hybrid_nodes; i++) { + const char *pmu_name = env->hybrid_nodes[i].pmu_name; + + if (pmu_name && !strcmp(name, pmu_name)) + return true; + } + return false; + } + + if (!env || !strcmp(perf_env__arch(env), perf_env__arch(NULL))) + return is_pmu_core(name); + + return false; +} + +static struct perf_pmu *env_pmu_find_or_add(const char *name, __u32 type, + struct perf_env *env, + const struct pmu_events_table *core_table) +{ + struct perf_pmu *pmu = pmu_find(name); + + if (pmu) { + if (pmu->type == PERF_PMU_TYPE_FAKE && type != PERF_PMU_TYPE_FAKE) + pmu->type = type; + return pmu; + } + + pmu = zalloc(sizeof(*pmu)); + if (!pmu) + return NULL; + + if (perf_pmu__init(pmu, type, name) != 0) { + perf_pmu__delete(pmu); + return NULL; + } + + pmu->sysfs_aliases_loaded = true; + pmu->caps_initialized = true; + pmu->max_precise = -1; + pmu->is_core = env_is_pmu_core(name, env); + pmu->is_uncore = strstarts(name, "uncore_"); + + if (!strcmp(name, "software") || !strcmp(name, "tool")) + pmu->events_table = perf_pmu__default_core_events_table(); + else if (core_table && (pmu->is_core || pmu->is_uncore)) + pmu->events_table = core_table; + + list_add_tail(&pmu->list, pmu->is_core ? &core_pmus : &other_pmus); + return pmu; +} + +static void env_pmu_add_caps_strings(struct perf_pmu *pmu, int nr_caps, char **caps) +{ + if (!pmu || nr_caps <= 0 || !caps) + return; + + for (int i = 0; i < nr_caps; i++) { + const char *eq; + + if (!caps[i]) + continue; + eq = strchr(caps[i], '='); + + if (eq) { + char *key = strndup(caps[i], eq - caps[i]); + + if (key) { + perf_pmu__add_cap(pmu, key, eq + 1); + free(key); + } + } else { + perf_pmu__add_cap(pmu, caps[i], ""); + } + } +} + +int perf_pmus__read_from_env(struct perf_env *env) +{ + const struct pmu_events_table *core_table = NULL; + struct perf_pmu *pmu; + + if (!env) + return -EINVAL; + + perf_pmus__destroy(); + read_pmu_types = PERF_TOOL_PMU_TYPE_ALL_MASK; + + if (env->cpuid) { + char cpuid_str[128]; + char vendor[32]; + unsigned int family, model, step; + const char *cpuid = env->cpuid; + + /* + * x86 HEADER_CPUID stores "vendor,family,model,step" in decimal + * while pmu-events tables match "vendor-family-HEXMODEL-HEXSTEP". + */ + if (sscanf(env->cpuid, "%31[^,],%u,%u,%u", + vendor, &family, &model, &step) == 4) { + snprintf(cpuid_str, sizeof(cpuid_str), "%s-%u-%X-%X", + vendor, family, model, step); + cpuid = cpuid_str; + } + core_table = find_core_events_table(perf_env__arch(env), cpuid); + } + + /* 1. Populate from HEADER_PMU_MAPPINGS */ + if (env->nr_pmu_mappings > 0 && env->pmu_mappings) { + char *str = env->pmu_mappings; + + for (int i = 0; i < env->nr_pmu_mappings; i++) { + char *colon = NULL; + unsigned long type = strtoul(str, &colon, 0); + + if (!colon || *colon != ':') + break; + str = colon + 1; + env_pmu_find_or_add(str, (__u32)type, env, core_table); + str += strlen(str) + 1; + } + } + + /* 2. Populate/enrich from HEADER_HYBRID_TOPOLOGY */ + for (int i = 0; i < env->nr_hybrid_nodes; i++) { + struct hybrid_node *hn = &env->hybrid_nodes[i]; + + if (!hn->pmu_name) + continue; + pmu = env_pmu_find_or_add(hn->pmu_name, PERF_PMU_TYPE_FAKE, + env, core_table); + if (!pmu) + continue; + if (!pmu->is_core) { + pmu->is_core = true; + if (!pmu->events_table && core_table) + pmu->events_table = core_table; + list_move_tail(&pmu->list, &core_pmus); + } + if (hn->cpus && !pmu->cpus) + pmu->cpus = perf_cpu_map__new(hn->cpus); + } + + /* + * 3. Populate/enrich from HEADER_CPU_PMU_CAPS. + * On hybrid systems HEADER_CPU_PMU_CAPS duplicates the first core PMU + * while HEADER_PMU_CAPS has the per-PMU capabilities, so only create a + * "cpu" PMU entry if no core PMUs exist yet. + */ + if (env->nr_cpu_pmu_caps > 0 && env->cpu_pmu_caps) { + pmu = pmu_find("cpu"); + if (!pmu && list_empty(&core_pmus)) { + pmu = env_pmu_find_or_add("cpu", PERF_TYPE_RAW, + env, core_table); + } + if (pmu) + env_pmu_add_caps_strings(pmu, env->nr_cpu_pmu_caps, env->cpu_pmu_caps); + } + + /* 4. Populate/enrich from HEADER_PMU_CAPS */ + for (int i = 0; i < env->nr_pmus_with_caps; i++) { + struct pmu_caps *pc = &env->pmu_caps[i]; + + if (!pc->pmu_name) + continue; + pmu = env_pmu_find_or_add(pc->pmu_name, PERF_PMU_TYPE_FAKE, + env, core_table); + if (pmu) + env_pmu_add_caps_strings(pmu, pc->nr_caps, pc->caps); + } + + /* 5. Set default core CPUs on non-hybrid systems */ + if (env->nr_cpus_avail > 0 && env->nr_hybrid_nodes == 0) { + char buf[64]; + + snprintf(buf, sizeof(buf), "0-%d", env->nr_cpus_avail - 1); + list_for_each_entry(pmu, &core_pmus, list) { + if (!pmu->cpus) + pmu->cpus = perf_cpu_map__new(buf); + } + } + + list_sort(NULL, &core_pmus, pmus_cmp); + list_sort(NULL, &other_pmus, pmus_cmp); + return 0; +} diff --git a/tools/perf/util/pmus.h b/tools/perf/util/pmus.h index 0d55edb3f2fc..f1402e50b5b9 100644 --- a/tools/perf/util/pmus.h +++ b/tools/perf/util/pmus.h @@ -6,6 +6,7 @@ #include <stddef.h> struct perf_event_attr; +struct perf_env; struct perf_pmu; struct print_callbacks; @@ -14,16 +15,19 @@ size_t pmu_name_len_no_suffix(const char *str); int pmu_name_cmp(const char *lhs_pmu_name, const char *rhs_pmu_name); void perf_pmus__destroy(void); +int perf_pmus__read_from_env(struct perf_env *env); struct perf_pmu *perf_pmus__find(const char *name); struct perf_pmu *perf_pmus__find_by_type(unsigned int type); struct perf_pmu *perf_pmus__find_by_attr(const struct perf_event_attr *attr); struct perf_pmu *perf_pmus__scan(struct perf_pmu *pmu); +struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmu *pmu); struct perf_pmu *perf_pmus__scan_core(struct perf_pmu *pmu); struct perf_pmu *perf_pmus__scan_for_event(struct perf_pmu *pmu, const char *event); struct perf_pmu *perf_pmus__scan_matching_wildcard(struct perf_pmu *pmu, const char *wildcard); struct perf_pmu *perf_pmus__scan_for_uncore_id(struct perf_pmu *pmu, const char *compat); +size_t perf_pmu__num_duplicates(const struct perf_pmu *pmu); const struct perf_pmu *perf_pmus__pmu_for_pmu_filter(const char *str); -- 2.56.0.212.g129ede20d9-goog ^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v1 2/2] perf sysinfo: Add sysinfo subcommand, documentation, and test 2026-10-16 21:32 [RFC PATCH v1 0/2] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers 2026-10-16 21:32 ` [RFC PATCH v1 1/2] perf pmus: Add PMU environment loading and live capability probing Ian Rogers @ 2026-10-16 21:32 ` Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 0/9] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers 2 siblings, 0 replies; 13+ messages in thread From: Ian Rogers @ 2026-10-16 21:32 UTC (permalink / raw) To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo, Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark Cc: linux-perf-users, linux-kernel, Stephane Eranian Add the 'perf sysinfo' subcommand to display and probe PMUs and system performance monitoring capabilities on the running system or from a perf.data file. For each PMU, 'perf sysinfo' reports static attributes (type, core/uncore/software/auxtrace/drm/hwmon/tool classification, CPUs, capabilities, sysfs formats, event counts, and memory profiling events) as well as probed runtime capabilities in text or JSON format, followed by a system-wide summary of core features, memory and PCIe/IO bus bandwidth monitoring, total events and metrics, pipeline metric drill-down levels, and an overall system rating. Signed-off-by: Ian Rogers <irogers@google.com> --- tools/perf/Build | 1 + tools/perf/Documentation/perf-sysinfo.txt | 109 ++ tools/perf/Documentation/perf.txt | 2 +- tools/perf/builtin-help.c | 3 +- tools/perf/builtin-sysinfo.c | 1958 +++++++++++++++++++++ tools/perf/builtin.h | 1 + tools/perf/perf.c | 1 + tools/perf/tests/shell/sysinfo.sh | 71 + 8 files changed, 2144 insertions(+), 2 deletions(-) create mode 100644 tools/perf/Documentation/perf-sysinfo.txt create mode 100644 tools/perf/builtin-sysinfo.c create mode 100755 tools/perf/tests/shell/sysinfo.sh diff --git a/tools/perf/Build b/tools/perf/Build index dcba5ba55206..8a3f6071fa40 100644 --- a/tools/perf/Build +++ b/tools/perf/Build @@ -13,6 +13,7 @@ perf-y += builtin-list.o perf-y += builtin-record.o perf-y += builtin-report.o perf-y += builtin-stat.o +perf-y += builtin-sysinfo.o perf-y += builtin-top.o perf-y += builtin-script.o perf-y += builtin-kvm.o diff --git a/tools/perf/Documentation/perf-sysinfo.txt b/tools/perf/Documentation/perf-sysinfo.txt new file mode 100644 index 000000000000..4fc5fe229060 --- /dev/null +++ b/tools/perf/Documentation/perf-sysinfo.txt @@ -0,0 +1,109 @@ +perf-sysinfo(1) +=============== + +NAME +---- +perf-sysinfo - Display and probe PMU and system performance monitoring information + +SYNOPSIS +-------- +[verse] +'perf sysinfo' [<options>] [pmu_glob ...] + +DESCRIPTION +----------- +This command displays a summary of the performance monitoring capabilities of +the current system or a perf.data file, including core PMU features (precise +sampling, branch stack sampling, user-space counter reading, memory profiling, +and hardware AUX trace PMUs), memory and PCIe/IO interconnect bus bandwidth +monitoring support, total events and metrics, pipeline metric drill-down levels +discovered via '<metric>_group' hierarchies, and an overall system rating. + +When '-p'/'--pmus' is specified (or when PMU filter globs, '-v'/'--verbose', +'-a'/'--all', or '-j'/'--json' are given), it also displays detailed +information for each PMU, including its perf_event_attr type, kind (core, +uncore, software, auxtrace, tool, hwmon, drm), CPUs/cpumask, capabilities, +sysfs format bitfields, event counts, and memory profiling events. + +When run on a live system (without '-i'/'--input' and with probing enabled), +it also actively probes the kernel via 'perf_event_open' to determine: +- Supported modes (counting, interrupt-based sampling, branch_stack sampling) +- Supported precise sampling levels, skid semantics, and empirically measured + branch instruction skid (minimum, maximum, average instructions from the + branch and 'PERF_RECORD_MISC_EXACT_IP' ratio) at each supported precision + level: + 0 ('SAMPLE_IP' can have arbitrary skid), ':p' / 1 ('SAMPLE_IP' must have + constant skid), ':pp' / 2 ('SAMPLE_IP' requested to have 0 skid), and + ':ppp' / 3 ('SAMPLE_IP' must have 0 skid, or uses randomization to avoid + sample shadowing effects) +- Supported 'perf_event_sample_format' sample types ('ip', 'tid', 'time', + 'addr', 'read', 'callchain', 'id', 'cpu', 'period', 'stream_id', 'raw', + 'branch_stack', 'regs_user', 'stack_user', 'weight', 'data_src', + 'identifier', 'transaction', 'regs_intr', 'phys_addr', 'aux', 'cgroup', + 'data_page_size', 'code_page_size', 'weight_struct') +- Supported user and interrupt registers for 'PERF_SAMPLE_REGS_USER' and + 'PERF_SAMPLE_REGS_INTR' +- Supported memory profiling events ('ldlat-loads', 'ldlat-stores', 'mem-ldst') + and their supported memory sample types ('data_src', 'addr', 'weight', etc.) +- Supported privilege/exclusion modifiers ('exclude_user', 'exclude_kernel', + 'exclude_hv', 'exclude_idle', 'exclude_host', 'exclude_guest') +- Supported execution features ('leader_sampling', 'sw_grouping', 'inherit', + 'sample_freq', 'write_backward', 'callchain', 'data_src', 'weight', + 'pinned', 'exclusive', 'format_lost', 'auto_counter_reload', + 'aux_start_paused', 'aux_pause', 'aux_resume', 'aux_sample', 'user_rdpmc', + 'user_time', 'user_time_zero', 'user_time_short') and hardware counter + bit-width ('pmc_width') when user-space counter reading is supported +- Maximum event group size accepted by 'perf_event_open' and maximum + simultaneously active counters in a group without multiplexing + +At the end of the text output, a summary section reports overall core PMU +capabilities (including hardware AUX trace PMUs), memory and PCIe/IO +interconnect bus bandwidth monitoring support, total events and metrics, and +pipeline metric drill-down levels discovered via '<metric>_group' hierarchies. + +By default, numbered uncore PMU instances sharing a common prefix are collapsed +into a single entry showing the instance count, unless '-v'/'--verbose' is +specified. + +OPTIONS +------- +-i:: +--input=:: + Input file name to read PMU information from (e.g. perf.data). + +-f:: +--force:: + Don't complain, do it (when reading input perf.data file). + +-o:: +--output=:: + Output file name. + +-p:: +--pmus:: + Display detailed information for each PMU in addition to the system + summary. + +-j:: +--json:: + Encode PMU information as JSON. + +-a:: +--all:: +-v:: +--verbose:: + Display all PMUs including all similar PMUs with alphanumeric suffixes + instead of collapsing numbered uncore PMU instances. + +--probe:: +--no-probe:: + Enable or disable active kernel probing of PMU capabilities on the live + system (default: enabled). + +--debug:: + Enable debugging output. + +SEE ALSO +-------- +linkperf:perf-list[1], linkperf:perf-evlist[1], linkperf:perf-stat[1], +linkperf:perf-record[1] diff --git a/tools/perf/Documentation/perf.txt b/tools/perf/Documentation/perf.txt index cbcc2e4d557e..0890dd5a1794 100644 --- a/tools/perf/Documentation/perf.txt +++ b/tools/perf/Documentation/perf.txt @@ -92,5 +92,5 @@ linkperf:perf-help[1], linkperf:perf-inject[1], linkperf:perf-intel-pt[1], linkperf:perf-iostat[1], linkperf:perf-kallsyms[1], linkperf:perf-kmem[1], linkperf:perf-kvm[1], linkperf:perf-lock[1], linkperf:perf-mem[1], linkperf:perf-probe[1], linkperf:perf-sched[1], -linkperf:perf-script[1], linkperf:perf-test[1], +linkperf:perf-script[1], linkperf:perf-sysinfo[1], linkperf:perf-test[1], linkperf:perf-trace[1], linkperf:perf-version[1] diff --git a/tools/perf/builtin-help.c b/tools/perf/builtin-help.c index 5bbdb4d9b9c0..fbab4b77761b 100644 --- a/tools/perf/builtin-help.c +++ b/tools/perf/builtin-help.c @@ -329,6 +329,7 @@ void list_common_cmds_help(void) {"report", "Read perf.data (created by perf record) and display the profile"}, {"script", "Read perf.data (created by perf record) and display trace output"}, {"stat", "Run a command and gather performance counter statistics"}, + {"sysinfo", "Display and probe PMU and system performance monitoring information"}, {"test", "Runs sanity tests."}, {"top", "System profiling tool."}, {"version", "display the version of perf binary"}, @@ -485,7 +486,7 @@ int cmd_help(int argc, const char **argv) }; const char * const builtin_help_subcommands[] = { "buildid-cache", "buildid-list", "diff", "evlist", "help", "list", - "record", "report", "bench", "stat", "timechart", "top", "annotate", + "record", "report", "bench", "stat", "sysinfo", "timechart", "top", "annotate", "script", "sched", "kallsyms", "kmem", "lock", "kvm", "test", "inject", "mem", "data", #ifdef HAVE_LIBELF_SUPPORT "probe", diff --git a/tools/perf/builtin-sysinfo.c b/tools/perf/builtin-sysinfo.c new file mode 100644 index 000000000000..8bc13f3dedb0 --- /dev/null +++ b/tools/perf/builtin-sysinfo.c @@ -0,0 +1,1958 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * builtin-sysinfo.c + * + * Builtin sysinfo command: Display and probe PMU and system performance + * monitoring information in a system or a perf.data file. + */ +#include "builtin.h" + +#include "util/color.h" +#include "util/cpumap.h" +#include "util/data.h" +#include "util/debug.h" +#include "util/evlist.h" +#include "util/evsel.h" +#include "util/header.h" +#include "util/mem-events.h" +#include "util/metricgroup.h" +#include "util/perf_regs.h" +#include "util/pmu.h" +#include "util/pmus.h" +#include "util/session.h" +#include "util/strbuf.h" +#include "util/string2.h" +#include "util/strlist.h" +#include "util/tool.h" + +#include <dwarf-regs.h> +#include <linux/bitmap.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/perf_event.h> +#include <linux/zalloc.h> +#include <subcmd/pager.h> +#include <subcmd/parse-options.h> + +#include <errno.h> +#include <inttypes.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> + +static const char *pmu_type_desc(u32 type) +{ + switch (type) { + case PERF_TYPE_HARDWARE: + return "PERF_TYPE_HARDWARE"; + case PERF_TYPE_SOFTWARE: + return "PERF_TYPE_SOFTWARE"; + case PERF_TYPE_TRACEPOINT: + return "PERF_TYPE_TRACEPOINT"; + case PERF_TYPE_HW_CACHE: + return "PERF_TYPE_HW_CACHE"; + case PERF_TYPE_RAW: + return "PERF_TYPE_RAW"; + case PERF_TYPE_BREAKPOINT: + return "PERF_TYPE_BREAKPOINT"; + default: + if (type <= PERF_PMU_TYPE_PE_END) + return "dynamic"; + if (type <= PERF_PMU_TYPE_DRM_END) + return "drm"; + if (type <= PERF_PMU_TYPE_HWMON_END) + return "hwmon"; + if (type == PERF_PMU_TYPE_TOOL) + return "tool"; + return "fake"; + } +} + +static const char *format_config_name(int config) +{ + static const char * const config_names[PERF_PMU_FORMAT_VALUE_CONFIG_END] = { + [PERF_PMU_FORMAT_VALUE_CONFIG] = "config", + [PERF_PMU_FORMAT_VALUE_CONFIG1] = "config1", + [PERF_PMU_FORMAT_VALUE_CONFIG2] = "config2", + [PERF_PMU_FORMAT_VALUE_CONFIG3] = "config3", + [PERF_PMU_FORMAT_VALUE_CONFIG4] = "config4", + }; + + if (config >= 0 && config < PERF_PMU_FORMAT_VALUE_CONFIG_END) + return config_names[config]; + return "config?"; +} + +static bool pmu_matches_filters(const struct perf_pmu *pmu, + int argc, const char **argv) +{ + if (argc <= 0) + return true; + + for (int i = 0; i < argc; i++) { + const char *pat = argv[i]; + + if (perf_pmu__wildcard_match(pmu, pat)) + return true; + if (pmu->name && (strglobmatch(pmu->name, pat) || !strcmp(pmu->name, pat))) + return true; + if (pmu->alias_name && + (strglobmatch(pmu->alias_name, pat) || !strcmp(pmu->alias_name, pat))) + return true; + if (pmu->name) { + size_t base_len = pmu_name_len_no_suffix(pmu->name); + + if (strlen(pat) == base_len && !strncmp(pmu->name, pat, base_len)) + return true; + } + } + return false; +} + +static void json_escape_fprintf(FILE *fp, struct strbuf *buf, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + strbuf_setlen(buf, 0); + for (size_t fmt_pos = 0; fmt_pos < strlen(fmt); fmt_pos++) { + switch (fmt[fmt_pos]) { + case '%': + fmt_pos++; + switch (fmt[fmt_pos]) { + case 's': { + const char *s = va_arg(args, const char*); + + strbuf_addstr(buf, s); + break; + } + case 'S': { + const char *s = va_arg(args, const char*); + + for (size_t s_pos = 0; s[s_pos] != '\0'; s_pos++) { + switch (s[s_pos]) { + case '\n': + strbuf_addstr(buf, "\\n"); + break; + case '\r': + strbuf_addstr(buf, "\\r"); + break; + case '\t': + strbuf_addstr(buf, "\\t"); + break; + case '\\': + fallthrough; + case '\"': + strbuf_addch(buf, '\\'); + fallthrough; + default: + strbuf_addch(buf, s[s_pos]); + break; + } + } + break; + } + default: + strbuf_addch(buf, '%'); + strbuf_addch(buf, fmt[fmt_pos]); + break; + } + break; + default: + strbuf_addch(buf, fmt[fmt_pos]); + break; + } + } + va_end(args); + fputs(buf->buf, fp); +} + +struct print_format_state { + FILE *fp; + struct strbuf *buf; + bool json; + int count; +}; + +static int print_format_cb(void *vstate, const char *name, int config, + const unsigned long *bits) +{ + struct print_format_state *state = vstate; + char bits_buf[128]; + + if (!bits) + return 0; + + bitmap_scnprintf((unsigned long *)bits, PERF_PMU_FORMAT_BITS, + bits_buf, sizeof(bits_buf)); + + if (state->json) { + char spec[160]; + + if (state->count == 0) + fprintf(state->fp, ",\n\t\"Formats\": {\n"); + + snprintf(spec, sizeof(spec), "%s:%s", + format_config_name(config), bits_buf); + json_escape_fprintf(state->fp, state->buf, "%s\t\t\"%S\": \"%S\"", + state->count > 0 ? ",\n" : "", + name, spec); + } else { + if (state->count == 0) + fprintf(state->fp, " Formats: "); + + fprintf(state->fp, "%s%s (%s:%s)", + state->count > 0 ? ", " : "", + name, format_config_name(config), bits_buf); + } + state->count++; + return 0; +} + +static bool evsel_matches_pmu(const struct evsel *evsel, + const struct perf_pmu *pmu, bool show_all) +{ + struct perf_pmu *ev_pmu = evsel__find_pmu(evsel); + const char *ev_pmu_name; + + if (ev_pmu == pmu) + return true; + + if (ev_pmu) { + if (!show_all && !pmu->is_core && pmu->name && ev_pmu->name) { + size_t base_len = pmu_name_len_no_suffix(pmu->name); + + if (pmu_name_len_no_suffix(ev_pmu->name) == base_len && + !strncmp(pmu->name, ev_pmu->name, base_len)) + return true; + } + return false; + } + + ev_pmu_name = evsel__pmu_name(evsel); + if (ev_pmu_name && pmu->name) { + if (!strcmp(ev_pmu_name, pmu->name)) + return true; + if (!show_all && !pmu->is_core) { + size_t base_len = pmu_name_len_no_suffix(pmu->name); + + if (pmu_name_len_no_suffix(ev_pmu_name) == base_len && + !strncmp(pmu->name, ev_pmu_name, base_len)) + return true; + } + } + return false; +} + +static void print_recorded_events(FILE *fp, struct strbuf *buf, + const struct perf_pmu *pmu, + const struct perf_session *session, + bool show_all, bool json) +{ + struct strlist *ev_names; + struct str_node *pos; + struct evsel *evsel; + size_t count = 0; + + if (!session || !session->evlist) + return; + + ev_names = strlist__new(NULL, NULL); + if (!ev_names) + return; + + evlist__for_each_entry(session->evlist, evsel) { + if (evsel_matches_pmu(evsel, pmu, show_all)) + strlist__add(ev_names, evsel__name(evsel)); + } + + if (strlist__empty(ev_names)) { + strlist__delete(ev_names); + return; + } + + if (json) { + fprintf(fp, ",\n\t\"RecordedEvents\": ["); + strlist__for_each_entry(pos, ev_names) { + json_escape_fprintf(fp, buf, "%s\"%S\"", + count > 0 ? ", " : "", pos->s); + count++; + } + fprintf(fp, "]"); + } else { + fprintf(fp, " Recorded events: "); + strlist__for_each_entry(pos, ev_names) { + fprintf(fp, "%s%s", count > 0 ? ", " : "", pos->s); + count++; + } + fputc('\n', fp); + } + + strlist__delete(ev_names); +} + +static const struct { + u64 bit; + const char *name; +} sample_type_names[] = { + { PERF_SAMPLE_IP, "ip" }, + { PERF_SAMPLE_TID, "tid" }, + { PERF_SAMPLE_TIME, "time" }, + { PERF_SAMPLE_ADDR, "addr" }, + { PERF_SAMPLE_READ, "read" }, + { PERF_SAMPLE_CALLCHAIN, "callchain" }, + { PERF_SAMPLE_ID, "id" }, + { PERF_SAMPLE_CPU, "cpu" }, + { PERF_SAMPLE_PERIOD, "period" }, + { PERF_SAMPLE_STREAM_ID, "stream_id" }, + { PERF_SAMPLE_RAW, "raw" }, + { PERF_SAMPLE_BRANCH_STACK, "branch_stack" }, + { PERF_SAMPLE_REGS_USER, "regs_user" }, + { PERF_SAMPLE_STACK_USER, "stack_user" }, + { PERF_SAMPLE_WEIGHT, "weight" }, + { PERF_SAMPLE_DATA_SRC, "data_src" }, + { PERF_SAMPLE_IDENTIFIER, "identifier" }, + { PERF_SAMPLE_TRANSACTION, "transaction" }, + { PERF_SAMPLE_REGS_INTR, "regs_intr" }, + { PERF_SAMPLE_PHYS_ADDR, "phys_addr" }, + { PERF_SAMPLE_AUX, "aux" }, + { PERF_SAMPLE_CGROUP, "cgroup" }, + { PERF_SAMPLE_DATA_PAGE_SIZE, "data_page_size" }, + { PERF_SAMPLE_CODE_PAGE_SIZE, "code_page_size" }, + { PERF_SAMPLE_WEIGHT_STRUCT, "weight_struct" }, +}; + +static const char *precise_level_desc(int lvl) +{ + switch (lvl) { + case 0: + return "0 - SAMPLE_IP can have arbitrary skid"; + case 1: + return "1 - SAMPLE_IP must have constant skid"; + case 2: + return "2 - SAMPLE_IP requested to have 0 skid"; + case 3: + return "3 - SAMPLE_IP must have 0 skid, or uses randomization"; + default: + return "unknown"; + } +} + +static void print_precise_levels(FILE *fp, const struct perf_pmu *pmu, + unsigned int precise_mask, + int max_precise, bool json) +{ + static const char * const pnames[] = { "", ":p", ":pp", ":ppp" }; + bool need_sep = false; + + if (json) { + fprintf(fp, ",\n\t\t\"PreciseLevels\": ["); + for (int lvl = 1; lvl <= 3; lvl++) { + if (precise_mask & (1u << lvl)) { + fprintf(fp, "%s\"%s\"", + need_sep ? ", " : "", pnames[lvl]); + need_sep = true; + } + } + fprintf(fp, "]"); + return; + } + + fprintf(fp, " Precise sampling: "); + if (precise_mask == 0) { + if (perf_pmu__is_software(pmu)) { + color_fprintf(fp, PERF_COLOR_GREEN, "always precise"); + fprintf(fp, " (software event, SAMPLE_IP has 0 skid)\n"); + } else { + color_fprintf(fp, PERF_COLOR_RED, "not supported"); + fprintf(fp, " (%s)\n", precise_level_desc(0)); + } + return; + } + + for (int lvl = 1; lvl <= 3; lvl++) { + if (precise_mask & (1u << lvl)) { + if (need_sep) + fprintf(fp, ", "); + color_fprintf(fp, PERF_COLOR_GREEN, "%s", pnames[lvl]); + fprintf(fp, " (%s)", precise_level_desc(lvl)); + need_sep = true; + } + } + fprintf(fp, " (max_precise: "); + color_fprintf(fp, PERF_COLOR_GREEN, "%d", max_precise); + fprintf(fp, ")\n"); +} + +static void print_measured_skid(FILE *fp, const struct perf_pmu *pmu, bool json) +{ + static const char * const lvl_keys[] = { "0", ":p", ":pp", ":ppp" }; + static const char * const lvl_labels[] = { + "level 0 (no :p)", + "level 1 (:p)", + "level 2 (:pp)", + "level 3 (:ppp)", + }; + const typeof(pmu->probed) *p = &pmu->probed; + bool any = false; + bool first = true; + + for (int lvl = 0; lvl <= 3; lvl++) { + if (p->skid[lvl].measured) { + any = true; + break; + } + } + if (!any) + return; + + if (json) + fprintf(fp, ",\n\t\t\"MeasuredSkid\": {\n"); + else + fprintf(fp, " Measured branch skid (instructions from branch):\n"); + + for (int lvl = 0; lvl <= 3; lvl++) { + if (!p->skid[lvl].measured) + continue; + if (json) { + fprintf(fp, + "%s\t\t\t\"%s\": { \"Samples\": %d, \"ExactIp\": %d, \"MinInsn\": %d, \"MaxInsn\": %d, \"AvgInsn\": %.1f }", + first ? "" : ",\n", + lvl_keys[lvl], + p->skid[lvl].samples, + p->skid[lvl].exact_samples, + p->skid[lvl].min_skid, + p->skid[lvl].max_skid, + p->skid[lvl].avg_skid); + } else { + const char *skid_color = PERF_COLOR_YELLOW; + + if (p->skid[lvl].max_skid == 0) + skid_color = PERF_COLOR_GREEN; + else if (p->skid[lvl].max_skid <= 2) + skid_color = PERF_COLOR_CYAN; + + fprintf(fp, " %-15s: ", lvl_labels[lvl]); + color_fprintf(fp, skid_color, "min %d, max %d, avg %.1f", + p->skid[lvl].min_skid, + p->skid[lvl].max_skid, + p->skid[lvl].avg_skid); + fprintf(fp, " (%d/%d exact_ip)\n", + p->skid[lvl].exact_samples, + p->skid[lvl].samples); + } + first = false; + } + + if (json) + fprintf(fp, "\n\t\t}"); +} + +static void print_sample_type_list(FILE *fp, u64 mask, bool json) +{ + bool need_sep = false; + + for (size_t x = 0; x < ARRAY_SIZE(sample_type_names); x++) { + if (!(mask & sample_type_names[x].bit)) + continue; + if (json) + fprintf(fp, "%s\"%s\"", need_sep ? ", " : "", sample_type_names[x].name); + else + fprintf(fp, "%s%s", need_sep ? ", " : "", sample_type_names[x].name); + need_sep = true; + } + if (!json && !need_sep) + fprintf(fp, "none"); +} + +static void print_probed_regs(FILE *fp, struct strbuf *buf, const char *label, + u64 mask, int abi, u64 simd_mask, u64 pred_mask, + bool intr, bool json) +{ + const char *last_name = NULL; + bool need_sep = false; + + if (!mask && !simd_mask && !pred_mask) + return; + + if (json) + fprintf(fp, ",\n\t\t\"%s\": [", label); + else + fprintf(fp, " %s: ", label); + + for (int reg = 0; reg < 64; reg++) { + const char *name; + + if (!(mask & BIT_ULL(reg))) + continue; + name = perf_reg_name(reg, EM_HOST, EF_HOST, abi); + if (!name || !strcmp(name, "unknown")) + continue; + if (last_name && !strcmp(last_name, name)) + continue; + last_name = name; + + if (json) { + json_escape_fprintf(fp, buf, "%s\"%S\"", + need_sep ? ", " : "", name); + } else { + fprintf(fp, "%s%s", need_sep ? ", " : "", name); + } + need_sep = true; + } + + for (int is_pred = 0; is_pred <= 1; is_pred++) { + bool pred = is_pred != 0; + u64 cmask = pred ? pred_mask : simd_mask; + + for (int reg_c = 0; reg_c < 64; reg_c++) { + const char *cname; + u16 qwords = 0; + u64 bitmap; + char reg_range[64]; + + if (!(cmask & BIT_ULL(reg_c))) + continue; + cname = perf_simd_reg_class_name(EM_HOST, reg_c, pred); + bitmap = intr ? + perf_intr_simd_reg_class_bitmap_qwords(EM_HOST, reg_c, + &qwords, pred) : + perf_user_simd_reg_class_bitmap_qwords(EM_HOST, reg_c, + &qwords, pred); + if (!cname || !bitmap) + continue; + snprintf(reg_range, sizeof(reg_range), "%s0-%d", + cname, fls64(bitmap) - 1); + if (json) { + json_escape_fprintf(fp, buf, "%s\"%S\"", + need_sep ? ", " : "", reg_range); + } else { + fprintf(fp, "%s%s", need_sep ? ", " : "", reg_range); + } + need_sep = true; + } + } + + if (json) + fprintf(fp, "]"); + else + fputc('\n', fp); +} + +static void print_mem_events_static(FILE *fp, struct strbuf *buf, + const struct perf_pmu *pmu, bool json) +{ + bool first = true; + + if (!pmu->mem_events) + return; + + perf_pmu__mem_events_init_pmu((struct perf_pmu *)pmu); + + for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) { + struct perf_mem_event *e = perf_pmu__mem_events_ptr((struct perf_pmu *)pmu, j); + char ev_buf[256]; + const char *s; + + if (!e || !e->tag || !e->supported) + continue; + s = perf_pmu__mem_events_name((struct perf_pmu *)pmu, j, + ev_buf, sizeof(ev_buf)); + if (!s) + continue; + + if (json) { + if (first) + fprintf(fp, ",\n\t\"MemoryEvents\": {\n"); + json_escape_fprintf(fp, buf, "%s\t\t\"%S\": \"%S\"", + first ? "" : ",\n", e->tag, s); + } else { + if (first) + fprintf(fp, " Memory events: "); + fprintf(fp, "%s%s (%s)", first ? "" : ", ", e->tag, s); + } + first = false; + } + + if (!first) { + if (json) + fprintf(fp, "\n\t}"); + else + fputc('\n', fp); + } +} + +static void print_pmu_json(FILE *fp, const struct perf_pmu *pmu, + const struct perf_session *session, bool show_all) +{ + struct strbuf buf; + size_t num_instances = show_all ? 1 : perf_pmu__num_duplicates(pmu); + char display_name[128]; + size_t total_events; + struct print_format_state format_state = { + .fp = fp, + .buf = &buf, + .json = true, + .count = 0, + }; + + strbuf_init(&buf, 0); + + if (num_instances > 1) { + size_t base_len = pmu_name_len_no_suffix(pmu->name ?: ""); + + snprintf(display_name, sizeof(display_name), "%.*s", + (int)base_len, pmu->name ?: ""); + } else { + snprintf(display_name, sizeof(display_name), "%s", pmu->name ?: ""); + } + + fprintf(fp, "{\n"); + json_escape_fprintf(fp, &buf, "\t\"Unit\": \"%S\"", display_name); + if (pmu->name) + json_escape_fprintf(fp, &buf, ",\n\t\"FullName\": \"%S\"", pmu->name); + fprintf(fp, ",\n\t\"Instances\": %zu", num_instances); + if (pmu->alias_name) + json_escape_fprintf(fp, &buf, ",\n\t\"Alias\": \"%S\"", pmu->alias_name); + if (pmu->id) + json_escape_fprintf(fp, &buf, ",\n\t\"Identifier\": \"%S\"", pmu->id); + if (pmu->type != PERF_PMU_TYPE_FAKE) { + fprintf(fp, ",\n\t\"Type\": %u", pmu->type); + json_escape_fprintf(fp, &buf, ",\n\t\"TypeDesc\": \"%S\"", + pmu_type_desc(pmu->type)); + } + fprintf(fp, ",\n\t\"IsCore\": %s", pmu->is_core ? "true" : "false"); + fprintf(fp, ",\n\t\"IsUncore\": %s", pmu->is_uncore ? "true" : "false"); + fprintf(fp, ",\n\t\"IsSoftware\": %s", perf_pmu__is_software(pmu) ? "true" : "false"); + fprintf(fp, ",\n\t\"Auxtrace\": %s", pmu->auxtrace ? "true" : "false"); + fprintf(fp, ",\n\t\"Selectable\": %s", pmu->selectable ? "true" : "false"); + + if (pmu->cpus) { + char cpus_buf[256]; + int nr_cpus = perf_cpu_map__nr(pmu->cpus); + + cpu_map__snprint(pmu->cpus, cpus_buf, sizeof(cpus_buf)); + if (cpus_buf[0]) { + json_escape_fprintf(fp, &buf, ",\n\t\"CPUs\": \"%S\"", cpus_buf); + fprintf(fp, ",\n\t\"NrCPUs\": %d", nr_cpus); + } + } + + perf_pmu__caps_parse((struct perf_pmu *)pmu); + if (!list_empty(&pmu->caps)) { + struct perf_pmu_caps *cap; + bool first = true; + + fprintf(fp, ",\n\t\"Capabilities\": {\n"); + list_for_each_entry(cap, &pmu->caps, list) { + json_escape_fprintf(fp, &buf, "%s\t\t\"%S\": \"%S\"", + first ? "" : ",\n", + cap->name, cap->value); + first = false; + } + fprintf(fp, "\n\t}"); + } + + perf_pmu__for_each_format((struct perf_pmu *)pmu, &format_state, print_format_cb); + if (format_state.count > 0) + fprintf(fp, "\n\t}"); + + total_events = perf_pmu__num_events((struct perf_pmu *)pmu); + if (total_events > 0) { + size_t sysfs_events = pmu->sysfs_aliases; + size_t json_events = total_events > sysfs_events ? total_events - sysfs_events : 0; + + fprintf(fp, ",\n\t\"Events\": {\n"); + fprintf(fp, "\t\t\"Total\": %zu,\n", total_events); + fprintf(fp, "\t\t\"Sysfs\": %zu,\n", sysfs_events); + fprintf(fp, "\t\t\"Json\": %zu\n", json_events); + fprintf(fp, "\t}"); + } + + print_mem_events_static(fp, &buf, pmu, /*json=*/true); + print_recorded_events(fp, &buf, pmu, session, show_all, /*json=*/true); + + if (pmu->probed.checked) { + const typeof(pmu->probed) *p = &pmu->probed; + + fprintf(fp, ",\n\t\"Probed\": {\n"); + fprintf(fp, "\t\t\"OpenSupported\": %s", + p->open_supported ? "true" : "false"); + if (p->open_supported) { + bool need_sep = false; + const struct { + bool supported; + const char *name; + } excl[] = { + { p->exclude_user, "exclude_user" }, + { p->exclude_kernel, "exclude_kernel" }, + { p->exclude_hv, "exclude_hv" }, + { p->exclude_idle, "exclude_idle" }, + { p->exclude_host, "exclude_host" }, + { p->exclude_guest, "exclude_guest" }, + }; + + if (p->probe_event) { + json_escape_fprintf(fp, &buf, + ",\n\t\t\"ProbeEvent\": \"%S\"", + p->probe_event); + } + fprintf(fp, ",\n\t\t\"Counting\": %s", + p->can_count ? "true" : "false"); + fprintf(fp, ",\n\t\t\"Sampling\": %s", + p->can_sample ? "true" : "false"); + fprintf(fp, ",\n\t\t\"BranchStack\": %s", + p->can_branch_stack ? "true" : "false"); + if (p->can_sample) { + if (!pmu->auxtrace) { + const char *pdesc = (p->max_precise == 0 && + perf_pmu__is_software(pmu)) ? + "software event - SAMPLE_IP has 0 skid" : + precise_level_desc(p->max_precise); + + fprintf(fp, ",\n\t\t\"MaxPrecise\": %d", p->max_precise); + json_escape_fprintf(fp, &buf, + ",\n\t\t\"MaxPreciseDesc\": \"%S\"", + pdesc); + print_precise_levels(fp, pmu, p->precise_mask, + p->max_precise, /*json=*/true); + + print_measured_skid(fp, pmu, /*json=*/true); + } + + fprintf(fp, ",\n\t\t\"SampleTypes\": ["); + print_sample_type_list(fp, p->sample_types, /*json=*/true); + fprintf(fp, "]"); + } + + print_probed_regs(fp, &buf, "UserRegisters", + p->user_regs_mask, p->user_regs_abi, + p->user_simd_mask, p->user_pred_mask, + /*intr=*/false, /*json=*/true); + print_probed_regs(fp, &buf, "IntrRegisters", + p->intr_regs_mask, p->intr_regs_abi, + p->intr_simd_mask, p->intr_pred_mask, + /*intr=*/true, /*json=*/true); + + if (p->mem_events_mask) { + need_sep = false; + fprintf(fp, ",\n\t\t\"MemoryEvents\": ["); + for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) { + struct perf_mem_event *e = + perf_pmu__mem_events_ptr((struct perf_pmu *)pmu, j); + + if (e && e->tag && (p->mem_events_mask & (1u << j))) { + json_escape_fprintf(fp, &buf, "%s\"%S\"", + need_sep ? ", " : "", + e->tag); + need_sep = true; + } + } + fprintf(fp, "]"); + + fprintf(fp, ",\n\t\t\"MemorySampleTypes\": ["); + print_sample_type_list(fp, p->mem_sample_types, /*json=*/true); + fprintf(fp, "]"); + } + + need_sep = false; + fprintf(fp, ",\n\t\t\"ExcludeModifiers\": ["); + for (size_t x = 0; x < ARRAY_SIZE(excl); x++) { + if (excl[x].supported) { + fprintf(fp, "%s\"%s\"", + need_sep ? ", " : "", + excl[x].name); + need_sep = true; + } + } + fprintf(fp, "]"); + + { + const struct { + bool supported; + const char *name; + } feats[] = { + { p->can_leader_sampling, "leader_sampling" }, + { p->can_sw_grouping, "sw_grouping" }, + { p->can_inherit, "inherit" }, + { p->can_freq, "sample_freq" }, + { p->can_write_backward, "write_backward" }, + { p->can_callchain, "callchain" }, + { p->can_data_src, "data_src" }, + { p->can_weight, "weight" }, + { p->can_pinned, "pinned" }, + { p->can_exclusive, "exclusive" }, + { p->can_format_lost, "format_lost" }, + { p->can_acr, "auto_counter_reload" }, + { p->can_aux_start_paused, "aux_start_paused" }, + { p->can_aux_pause, "aux_pause" }, + { p->can_aux_resume, "aux_resume" }, + { p->can_aux_sample, "aux_sample" }, + { p->can_user_rdpmc, "user_rdpmc" }, + { p->can_user_time, "user_time" }, + { p->can_user_time_zero, "user_time_zero" }, + { p->can_user_time_short, "user_time_short" }, + }; + + need_sep = false; + fprintf(fp, ",\n\t\t\"Features\": ["); + for (size_t x = 0; x < ARRAY_SIZE(feats); x++) { + if (feats[x].supported) { + fprintf(fp, "%s\"%s\"", + need_sep ? ", " : "", + feats[x].name); + need_sep = true; + } + } + fprintf(fp, "]"); + } + + if (pmu->auxtrace) { + const struct { + bool supported; + const char *name; + } aux_acts[] = { + { p->can_aux_start_paused, "aux_start_paused" }, + { p->can_aux_pause, "aux_pause" }, + { p->can_aux_resume, "aux_resume" }, + { p->can_aux_sample, "aux_sample" }, + }; + + need_sep = false; + fprintf(fp, ",\n\t\t\"AuxActions\": ["); + for (size_t x = 0; x < ARRAY_SIZE(aux_acts); x++) { + if (aux_acts[x].supported) { + fprintf(fp, "%s\"%s\"", + need_sep ? ", " : "", + aux_acts[x].name); + need_sep = true; + } + } + fprintf(fp, "]"); + } + + if (p->can_user_rdpmc && p->pmc_width > 0) + fprintf(fp, ",\n\t\t\"PmcWidth\": %u", p->pmc_width); + + fprintf(fp, ",\n\t\t\"UserRdpmc\": {\n"); + fprintf(fp, "\t\t\t\"Available\": %s,\n", + p->rdpmc_available ? "true" : "false"); + fprintf(fp, "\t\t\t\"Enabled\": %s,\n", + p->can_user_rdpmc ? "true" : "false"); + fprintf(fp, "\t\t\t\"Working\": %s", + p->rdpmc_works ? "true" : "false"); + if (p->rdpmc_available && p->pmc_width > 0) + fprintf(fp, ",\n\t\t\t\"PmcWidth\": %u", p->pmc_width); + if (p->rdpmc_sysfs >= 0) + fprintf(fp, ",\n\t\t\t\"Sysfs\": %d", p->rdpmc_sysfs); + fprintf(fp, "\n\t\t}"); + + fprintf(fp, ",\n\t\t\"MaxGroupSize\": %d", + p->max_group_size); + fprintf(fp, ",\n\t\t\"MaxGroupSizeCapped\": %s", + p->max_group_capped ? "true" : "false"); + fprintf(fp, ",\n\t\t\"MaxActiveGroupSize\": %d", + p->max_active_group_size); + fprintf(fp, ",\n\t\t\"MaxActiveGroupSizeCapped\": %s", + p->max_active_capped ? "true" : "false"); + if (p->max_gp_group_size > 0) { + fprintf(fp, ",\n\t\t\"MaxGpGroupSize\": %d", + p->max_gp_group_size); + fprintf(fp, ",\n\t\t\"MaxActiveGpGroupSize\": %d", + p->max_active_gp_group_size); + } + } else { + json_escape_fprintf(fp, &buf, ",\n\t\t\"Error\": \"%S\"", + strerror(p->open_err)); + } + fprintf(fp, "\n\t}"); + } + + fprintf(fp, "\n}"); + strbuf_release(&buf); +} + +static void print_pmu_text(FILE *fp, const struct perf_pmu *pmu, + const struct perf_session *session, bool show_all) +{ + size_t num_instances = show_all ? 1 : perf_pmu__num_duplicates(pmu); + char display_name[128]; + const char *kind_sep = ""; + size_t total_events; + struct print_format_state format_state = { + .fp = fp, + .buf = NULL, + .json = false, + .count = 0, + }; + + if (num_instances > 1) { + size_t base_len = pmu_name_len_no_suffix(pmu->name ?: ""); + + snprintf(display_name, sizeof(display_name), "%.*s", + (int)base_len, pmu->name ?: ""); + } else { + snprintf(display_name, sizeof(display_name), "%s", pmu->name ?: ""); + } + + color_fprintf(fp, PERF_COLOR_BOLD, "%s", display_name); + fprintf(fp, " ("); + if (pmu->type != PERF_PMU_TYPE_FAKE) { + fprintf(fp, "type: %u [%s]", pmu->type, pmu_type_desc(pmu->type)); + kind_sep = ", "; + } + if (pmu->is_core) { + fprintf(fp, "%s", kind_sep); + color_fprintf(fp, PERF_COLOR_GREEN, "core"); + kind_sep = ", "; + } + if (pmu->is_uncore) { + fprintf(fp, "%s", kind_sep); + color_fprintf(fp, PERF_COLOR_CYAN, "uncore"); + kind_sep = ", "; + } + if (perf_pmu__is_software(pmu)) { + fprintf(fp, "%s", kind_sep); + color_fprintf(fp, PERF_COLOR_BLUE, "software"); + kind_sep = ", "; + } + if (pmu->auxtrace) { + fprintf(fp, "%s", kind_sep); + color_fprintf(fp, PERF_COLOR_MAGENTA, "auxtrace"); + kind_sep = ", "; + } + if (pmu->selectable) { + fprintf(fp, "%sselectable", kind_sep); + kind_sep = ", "; + } + fprintf(fp, "):\n"); + + if (num_instances > 1 || (pmu->name && strcmp(pmu->name, display_name) != 0)) + fprintf(fp, " Instances: %zu (first: %s)\n", num_instances, pmu->name); + if (pmu->alias_name) + fprintf(fp, " Alias: %s\n", pmu->alias_name); + if (pmu->id) + fprintf(fp, " Identifier: %s\n", pmu->id); + if (pmu->cpus) { + char cpus_buf[256]; + int nr_cpus = perf_cpu_map__nr(pmu->cpus); + + cpu_map__snprint(pmu->cpus, cpus_buf, sizeof(cpus_buf)); + if (cpus_buf[0]) { + if (nr_cpus > 0) + fprintf(fp, " CPUs: %s (%d %s)\n", + cpus_buf, nr_cpus, + nr_cpus == 1 ? "CPU" : "CPUs"); + else + fprintf(fp, " CPUs: %s\n", cpus_buf); + } + } + + perf_pmu__caps_parse((struct perf_pmu *)pmu); + if (!list_empty(&pmu->caps)) { + struct perf_pmu_caps *cap; + bool first = true; + + fprintf(fp, " Capabilities: "); + list_for_each_entry(cap, &pmu->caps, list) { + fprintf(fp, "%s%s=%s", first ? "" : ", ", cap->name, cap->value); + first = false; + } + fputc('\n', fp); + } + + perf_pmu__for_each_format((struct perf_pmu *)pmu, &format_state, print_format_cb); + if (format_state.count > 0) + fputc('\n', fp); + + total_events = perf_pmu__num_events((struct perf_pmu *)pmu); + if (total_events > 0) { + size_t sysfs_events = pmu->sysfs_aliases; + size_t json_events = total_events > sysfs_events ? total_events - sysfs_events : 0; + + fprintf(fp, " Events: %zu total (%zu sysfs, %zu json)\n", + total_events, sysfs_events, json_events); + } + + print_mem_events_static(fp, NULL, pmu, /*json=*/false); + print_recorded_events(fp, NULL, pmu, session, show_all, /*json=*/false); + + if (pmu->probed.checked) { + const typeof(pmu->probed) *p = &pmu->probed; + + if (!p->open_supported) { + fprintf(fp, " Probed capabilities: "); + if (p->open_err == EACCES && pmu->name && + !strcmp(pmu->name, "kprobe")) { + color_fprintf(fp, PERF_COLOR_YELLOW, + "unavailable (%s; requires CAP_PERFMON or CAP_SYS_ADMIN)", + strerror(p->open_err)); + } else if (p->open_err == EACCES && pmu->name && + !strcmp(pmu->name, "uprobe")) { + color_fprintf(fp, PERF_COLOR_YELLOW, + "unavailable (%s; requires CAP_SYS_ADMIN)", + strerror(p->open_err)); + } else { + color_fprintf(fp, PERF_COLOR_YELLOW, "unavailable (%s)", + strerror(p->open_err)); + } + fputc('\n', fp); + } else { + const char *sep = ""; + const struct { + bool supported; + const char *name; + } excl[] = { + { p->exclude_user, "exclude_user" }, + { p->exclude_kernel, "exclude_kernel" }, + { p->exclude_hv, "exclude_hv" }, + { p->exclude_idle, "exclude_idle" }, + { p->exclude_host, "exclude_host" }, + { p->exclude_guest, "exclude_guest" }, + }; + + fprintf(fp, " Probed capabilities (event: "); + color_fprintf(fp, PERF_COLOR_CYAN, "%s", + p->probe_event ?: "default"); + fprintf(fp, "):\n"); + fprintf(fp, " Modes: "); + if (p->can_count) { + fprintf(fp, "%s", sep); + color_fprintf(fp, PERF_COLOR_GREEN, "counting"); + sep = ", "; + } + if (p->can_sample) { + fprintf(fp, "%s", sep); + color_fprintf(fp, PERF_COLOR_GREEN, "sampling"); + sep = ", "; + } + if (p->can_branch_stack) { + fprintf(fp, "%s", sep); + color_fprintf(fp, PERF_COLOR_GREEN, "branch_stack"); + sep = ", "; + } + if (*sep == '\0') + color_fprintf(fp, PERF_COLOR_RED, "none"); + fputc('\n', fp); + + if (p->can_sample) { + if (!pmu->auxtrace) { + print_precise_levels(fp, pmu, p->precise_mask, + p->max_precise, /*json=*/false); + + print_measured_skid(fp, pmu, /*json=*/false); + } + + fprintf(fp, " Sample types: "); + print_sample_type_list(fp, p->sample_types, /*json=*/false); + fputc('\n', fp); + } + + print_probed_regs(fp, NULL, "User registers", + p->user_regs_mask, p->user_regs_abi, + p->user_simd_mask, p->user_pred_mask, + /*intr=*/false, /*json=*/false); + print_probed_regs(fp, NULL, "Intr registers", + p->intr_regs_mask, p->intr_regs_abi, + p->intr_simd_mask, p->intr_pred_mask, + /*intr=*/true, /*json=*/false); + + if (p->mem_events_mask) { + sep = ""; + fprintf(fp, " Memory events (probed): "); + for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) { + struct perf_mem_event *e = + perf_pmu__mem_events_ptr((struct perf_pmu *)pmu, j); + + if (e && e->tag && (p->mem_events_mask & (1u << j))) { + fprintf(fp, "%s%s", sep, e->tag); + sep = ", "; + } + } + if (p->mem_sample_types) { + fprintf(fp, " (sample types: "); + print_sample_type_list(fp, p->mem_sample_types, + /*json=*/false); + fprintf(fp, ")"); + } + fputc('\n', fp); + } + + sep = ""; + fprintf(fp, " Exclusion modifiers: "); + for (size_t x = 0; x < ARRAY_SIZE(excl); x++) { + if (excl[x].supported) { + fprintf(fp, "%s%s", sep, excl[x].name); + sep = ", "; + } + } + if (*sep == '\0') + fprintf(fp, "none"); + fputc('\n', fp); + + { + const struct { + bool supported; + const char *name; + } feats[] = { + { p->can_leader_sampling, "leader_sampling" }, + { p->can_sw_grouping, "sw_grouping" }, + { p->can_inherit, "inherit" }, + { p->can_freq, "sample_freq" }, + { p->can_write_backward, "write_backward" }, + { p->can_callchain, "callchain" }, + { p->can_data_src, "data_src" }, + { p->can_weight, "weight" }, + { p->can_pinned, "pinned" }, + { p->can_exclusive, "exclusive" }, + { p->can_format_lost, "format_lost" }, + { p->can_acr, "auto_counter_reload" }, + { p->can_aux_start_paused, "aux_start_paused" }, + { p->can_aux_pause, "aux_pause" }, + { p->can_aux_resume, "aux_resume" }, + { p->can_aux_sample, "aux_sample" }, + { p->can_user_rdpmc, "user_rdpmc" }, + { p->can_user_time, "user_time" }, + { p->can_user_time_zero, "user_time_zero" }, + { p->can_user_time_short, "user_time_short" }, + }; + + sep = ""; + fprintf(fp, " Features: "); + for (size_t x = 0; x < ARRAY_SIZE(feats); x++) { + if (feats[x].supported) { + fprintf(fp, "%s%s", sep, feats[x].name); + sep = ", "; + } + } + if (*sep == '\0') + fprintf(fp, "none"); + fputc('\n', fp); + } + + if (pmu->auxtrace) { + const struct { + bool supported; + const char *name; + } aux_acts[] = { + { p->can_aux_start_paused, "aux_start_paused" }, + { p->can_aux_pause, "aux_pause" }, + { p->can_aux_resume, "aux_resume" }, + { p->can_aux_sample, "aux_sample" }, + }; + + sep = ""; + fprintf(fp, " AUX actions: "); + for (size_t x = 0; x < ARRAY_SIZE(aux_acts); x++) { + if (aux_acts[x].supported) { + fprintf(fp, "%s%s", sep, aux_acts[x].name); + sep = ", "; + } + } + if (*sep == '\0') + fprintf(fp, "none"); + fputc('\n', fp); + } + + if (pmu->is_core || p->rdpmc_available) { + fprintf(fp, " User space counter reading (rdpmc): "); + if (!p->rdpmc_available) { + color_fprintf(fp, PERF_COLOR_RED, "not supported"); + fputc('\n', fp); + } else { + color_fprintf(fp, PERF_COLOR_GREEN, "available"); + fprintf(fp, ", "); + color_fprintf(fp, + p->can_user_rdpmc ? + PERF_COLOR_GREEN : PERF_COLOR_YELLOW, + "%s", + p->can_user_rdpmc ? "enabled" : "disabled"); + if (p->rdpmc_sysfs >= 0) + fprintf(fp, " (sysfs: %d)", p->rdpmc_sysfs); + if (p->can_user_rdpmc) { + fprintf(fp, ", "); + color_fprintf(fp, + p->rdpmc_works ? + PERF_COLOR_GREEN : PERF_COLOR_RED, + "%s", + p->rdpmc_works ? + "working" : "read failed"); + } + if (p->pmc_width > 0) + fprintf(fp, " (width: %u bits)", p->pmc_width); + fputc('\n', fp); + } + } + + fprintf(fp, " Max event group size: %s%d (active: %s%d)\n", + p->max_group_capped ? ">=" : "", + p->max_group_size, + p->max_active_capped ? ">=" : "", + p->max_active_group_size); + if (p->max_gp_group_size > 0 && + (p->max_gp_group_size != p->max_group_size || + p->max_active_gp_group_size != p->max_active_group_size)) { + fprintf(fp, " Max GP event group size: %s%d (active: %s%d)\n", + p->max_gp_group_capped ? ">=" : "", + p->max_gp_group_size, + p->max_active_gp_capped ? ">=" : "", + p->max_active_gp_group_size); + } + } + } +} + +static int process_header_feature(const struct perf_tool *tool, + struct perf_session *session, + union perf_event *event) +{ + int ret = perf_event__process_feature(tool, session, event); + + if (ret == 0 && event->header.size == sizeof(struct perf_record_header_feature) && + (int)event->feat.feat_id >= session->header.last_feat) { + session_done = 1; + } + return ret; +} + +static int process_sample_stop(const struct perf_tool *tool __maybe_unused, + union perf_event *event __maybe_unused, + struct perf_sample *sample __maybe_unused, + struct machine *machine __maybe_unused) +{ + session_done = 1; + return 0; +} + +#define MAX_SUMMARY_METRICS 2048 +#define MAX_DRILLDOWN_LEVELS 16 + +struct summary_metric_item { + char *pmu; + char *name; + char *groups; + int level; + bool has_children; + bool has_parent; +}; + +struct summary_metrics_state { + struct strlist *metrics; + struct strlist *groups; + struct summary_metric_item items[MAX_SUMMARY_METRICS]; + size_t nr_items; +}; + +static bool group_list_has_token(const char *groups, const char *token) +{ + size_t tok_len; + const char *p; + + if (!groups || !token || !*token) + return false; + tok_len = strlen(token); + p = groups; + while (*p) { + const char *semi = strchr(p, ';'); + size_t len = semi ? (size_t)(semi - p) : strlen(p); + + if (len == tok_len && !strncmp(p, token, len)) + return true; + if (!semi) + break; + p = semi + 1; + } + return false; +} + +static int collect_summary_metric_cb(const struct pmu_metric *pm, + const struct pmu_metrics_table *table __maybe_unused, + void *vdata) +{ + struct summary_metrics_state *st = vdata; + + if (pm->metric_name) + strlist__add(st->metrics, pm->metric_name); + + if (pm->metric_group) { + char *copy = strdup(pm->metric_group); + char *tok, *saveptr = NULL; + + if (copy) { + for (tok = strtok_r(copy, ";", &saveptr); tok; + tok = strtok_r(NULL, ";", &saveptr)) { + if (*tok) + strlist__add(st->groups, tok); + } + free(copy); + } + } + + if (pm->metric_name) { + const char *pmu_name = pm->pmu ?: "cpu"; + struct summary_metric_item *existing = NULL; + + for (size_t i = 0; i < st->nr_items; i++) { + if (st->items[i].pmu && st->items[i].name && + !strcmp(st->items[i].pmu, pmu_name) && + !strcmp(st->items[i].name, pm->metric_name)) { + existing = &st->items[i]; + break; + } + } + if (!existing && st->nr_items < MAX_SUMMARY_METRICS) { + existing = &st->items[st->nr_items++]; + existing->pmu = strdup(pmu_name); + existing->name = strdup(pm->metric_name); + existing->groups = strdup(pm->metric_group ?: ""); + } else if (existing && pm->metric_group && existing->groups && + !strstr(existing->groups, pm->metric_group)) { + char *merged = NULL; + + if (asprintf(&merged, "%s;%s", existing->groups, pm->metric_group) > 0) { + free(existing->groups); + existing->groups = merged; + } + } + } + return 0; +} + +static int compute_metric_drilldown(struct summary_metrics_state *st, + int level_counts[MAX_DRILLDOWN_LEVELS], + char *level_labels[MAX_DRILLDOWN_LEVELS]) +{ + int max_level = 0; + + memset(level_counts, 0, sizeof(int) * MAX_DRILLDOWN_LEVELS); + memset(level_labels, 0, sizeof(char *) * MAX_DRILLDOWN_LEVELS); + + for (size_t i = 0; i < st->nr_items; i++) { + char child_grp[256]; + + if (!st->items[i].name || !st->items[i].pmu) + continue; + snprintf(child_grp, sizeof(child_grp), "%s_group", st->items[i].name); + if (!strlist__has_entry(st->groups, child_grp)) + continue; + + for (size_t j = 0; j < st->nr_items; j++) { + if (i == j || !st->items[j].groups || !st->items[j].pmu) + continue; + if (strcmp(st->items[i].pmu, st->items[j].pmu) != 0) + continue; + if (group_list_has_token(st->items[j].groups, child_grp)) { + st->items[i].has_children = true; + st->items[j].has_parent = true; + } + } + } + + for (size_t i = 0; i < st->nr_items; i++) { + if (st->items[i].has_children && !st->items[i].has_parent) { + st->items[i].level = 1; + max_level = 1; + } + } + + for (int d = 1; d < MAX_DRILLDOWN_LEVELS - 1; d++) { + bool progress = false; + + for (size_t i = 0; i < st->nr_items; i++) { + char child_grp[256]; + + if (st->items[i].level != d || !st->items[i].has_children) + continue; + snprintf(child_grp, sizeof(child_grp), "%s_group", st->items[i].name); + for (size_t j = 0; j < st->nr_items; j++) { + if (i == j || !st->items[j].groups || !st->items[j].pmu) + continue; + if (strcmp(st->items[i].pmu, st->items[j].pmu) != 0) + continue; + if (group_list_has_token(st->items[j].groups, child_grp) && + st->items[j].level < d + 1) { + st->items[j].level = d + 1; + if (d + 1 > max_level) + max_level = d + 1; + progress = true; + } + } + } + if (!progress) + break; + } + + for (int d = 1; d <= max_level; d++) { + struct str_node *pos; + const char *best_label = NULL; + int best_hits = 0; + int total_at_d = 0; + + for (size_t i = 0; i < st->nr_items; i++) { + if (st->items[i].level == d) + total_at_d++; + } + level_counts[d] = total_at_d; + + strlist__for_each_entry(pos, st->groups) { + const char *g = pos->s; + size_t glen = strlen(g); + int hits_in_d = 0; + bool in_other_d = false; + + if (!strcasecmp(g, "Default") || !strcasecmp(g, "Summary") || + (glen > 6 && !strcmp(g + glen - 6, "_group"))) + continue; + + for (size_t i = 0; i < st->nr_items; i++) { + if (!st->items[i].groups || st->items[i].level == 0) + continue; + if (group_list_has_token(st->items[i].groups, g)) { + if (st->items[i].level == d) + hits_in_d++; + else + in_other_d = true; + } + } + + if (!in_other_d && + (hits_in_d > best_hits || + (hits_in_d == best_hits && best_label && + strlen(g) > strlen(best_label)))) { + best_hits = hits_in_d; + best_label = g; + } + } + + if (best_label && best_hits * 2 >= total_at_d) + level_labels[d] = strdup(best_label); + } + + return max_level; +} + +static bool is_mem_bw_pmu_name(const char *name) +{ + if (!name) + return false; + return strstarts(name, "uncore_imc") || + strstarts(name, "uncore_mc") || + strstarts(name, "uncore_edc") || + strstarts(name, "uncore_mchbm") || + strstarts(name, "amd_df") || + strstarts(name, "amd_umc") || + strstarts(name, "arm_dmc") || + strstarts(name, "dmc620") || + strstarts(name, "arm_cmn") || + strstr(name, "ddrc") != NULL || + strstr(name, "ddr") != NULL || + !strcmp(name, "hv_24x7") || + strstarts(name, "nest_mc"); +} + +static bool is_io_bw_pmu_name(const char *name) +{ + if (!name) + return false; + return strstarts(name, "uncore_iio") || + strstarts(name, "uncore_irp") || + strstarts(name, "uncore_pcie") || + strstarts(name, "uncore_upi") || + strstarts(name, "uncore_qpi") || + strstarts(name, "uncore_m2pcie") || + strstarts(name, "uncore_m3upi") || + strstarts(name, "uncore_arb") || + strstarts(name, "cxl") || + strstarts(name, "amd_iommu") || + strstarts(name, "amd_xgmi") || + strstarts(name, "arm_smmu") || + strstr(name, "pcie") != NULL || + strstarts(name, "dwc_rootport") || + strstarts(name, "cci") || + strstarts(name, "ccn"); +} + +static void print_strlist_color(FILE *fp, const char *color, struct strlist *list) +{ + struct str_node *pos; + bool first = true; + + strlist__for_each_entry(pos, list) { + if (!first) + fprintf(fp, ", "); + color_fprintf(fp, color, "%s", pos->s); + first = false; + } +} + +static void add_pmu_display_name(struct strlist *list, const struct perf_pmu *pmu) +{ + size_t num_inst = perf_pmu__num_duplicates(pmu); + char buf[128]; + + if (!pmu->name) + return; + if (num_inst > 1) { + size_t base_len = pmu_name_len_no_suffix(pmu->name); + + snprintf(buf, sizeof(buf), "%.*s", (int)base_len, pmu->name); + strlist__add(list, buf); + } else { + strlist__add(list, pmu->name); + } +} + +static void print_system_summary(FILE *fp, const struct perf_session *session, + bool probed, bool leading_newline) +{ + const struct pmu_metrics_table *mtable; + struct perf_pmu *pmu = NULL; + struct strlist *core_pmus = strlist__new(NULL, NULL); + struct strlist *mem_bw_pmus = strlist__new(NULL, NULL); + struct strlist *io_bw_pmus = strlist__new(NULL, NULL); + struct strlist *aux_pmus = strlist__new(NULL, NULL); + struct strlist *missing = strlist__new(NULL, NULL); + struct summary_metrics_state *mstate = zalloc(sizeof(*mstate)); + int level_counts[MAX_DRILLDOWN_LEVELS]; + char *level_labels[MAX_DRILLDOWN_LEVELS]; + int max_drill_lvl = 0; + bool has_sw_sampling = false; + bool core_can_sample = false; + bool core_precise = false; + int max_precise = 0; + bool core_rdpmc = false; + bool core_branch_stack = false; + int max_br_depth = 0; + bool core_mem_events = false; + bool core_leader_sampling = false; + size_t total_pmus = 0, total_events = 0, total_sysfs_events = 0, total_json_events = 0; + size_t nr_core, nr_metrics, nr_groups; + + if (!mstate) + goto out_free; + mstate->metrics = strlist__new(NULL, NULL); + mstate->groups = strlist__new(NULL, NULL); + + if (!core_pmus || !mem_bw_pmus || !io_bw_pmus || !aux_pmus || + !missing || !mstate->metrics || !mstate->groups) + goto out_free; + + while ((pmu = perf_pmus__scan_skip_duplicates(pmu)) != NULL) { + size_t pmu_events = perf_pmu__num_events(pmu); + size_t sysfs_ev = pmu->sysfs_aliases; + size_t json_ev = pmu_events > sysfs_ev ? pmu_events - sysfs_ev : 0; + + total_pmus++; + total_events += pmu_events; + total_sysfs_events += sysfs_ev; + total_json_events += json_ev; + + if (pmu->is_core) { + struct perf_pmu_caps *br_cap; + + add_pmu_display_name(core_pmus, pmu); + perf_pmu__caps_parse(pmu); + br_cap = perf_pmu__get_cap(pmu, "branches"); + if (br_cap && br_cap->value) { + int depth = atoi(br_cap->value); + + if (depth > max_br_depth) + max_br_depth = depth; + } + if (probed && pmu->probed.checked && pmu->probed.open_supported) { + if (pmu->probed.can_sample) + core_can_sample = true; + if (pmu->probed.max_precise > 0) { + core_precise = true; + if (pmu->probed.max_precise > max_precise) + max_precise = pmu->probed.max_precise; + } + if (pmu->probed.can_user_rdpmc) + core_rdpmc = true; + if (pmu->probed.can_branch_stack) + core_branch_stack = true; + if (pmu->probed.mem_events_mask) + core_mem_events = true; + if (pmu->probed.can_leader_sampling) + core_leader_sampling = true; + } else { + core_can_sample = true; + if (pmu->max_precise > 0) { + core_precise = true; + if (pmu->max_precise > max_precise) + max_precise = pmu->max_precise; + } + if (max_br_depth > 0) + core_branch_stack = true; + if (pmu->mem_events) + core_mem_events = true; + } + } else if (pmu->type == PERF_TYPE_SOFTWARE) { + if (!probed || !pmu->probed.checked || pmu->probed.can_sample) + has_sw_sampling = true; + } + + if (pmu->auxtrace) + add_pmu_display_name(aux_pmus, pmu); + if (is_mem_bw_pmu_name(pmu->name)) + add_pmu_display_name(mem_bw_pmus, pmu); + if (is_io_bw_pmu_name(pmu->name)) + add_pmu_display_name(io_bw_pmus, pmu); + } + + if (session && session->header.env.arch && session->header.env.cpuid) + mtable = find_core_metrics_table(session->header.env.arch, + session->header.env.cpuid); + else + mtable = pmu_metrics_table__find(); + + metricgroup__for_each_metric(mtable, collect_summary_metric_cb, mstate); + max_drill_lvl = compute_metric_drilldown(mstate, level_counts, level_labels); + + nr_core = strlist__nr_entries(core_pmus); + nr_metrics = strlist__nr_entries(mstate->metrics); + nr_groups = strlist__nr_entries(mstate->groups); + + if (leading_newline) + fputc('\n', fp); + color_fprintf(fp, PERF_COLOR_BOLD, "Summary:"); + fputc('\n', fp); + if (nr_core == 0) { + const char *sw_desc = has_sw_sampling ? + "minimal system; sampling only via software timer events" : + "no sampling support detected"; + + color_fprintf(fp, PERF_COLOR_BOLD, " Core PMUs:"); + fprintf(fp, " "); + color_fprintf(fp, PERF_COLOR_RED, "none"); + fprintf(fp, " (%s)\n", sw_desc); + strlist__add(missing, "hardware core PMU"); + } else { + color_fprintf(fp, PERF_COLOR_BOLD, " Core PMUs (%zu%s):", nr_core, + nr_core > 1 ? ", hybrid" : ""); + fprintf(fp, " "); + print_strlist_color(fp, PERF_COLOR_GREEN, core_pmus); + fputc('\n', fp); + + color_fprintf(fp, PERF_COLOR_BOLD, " Core features:"); + fputc('\n', fp); + fprintf(fp, " Interrupt-based sampling: "); + color_fprintf(fp, core_can_sample ? PERF_COLOR_GREEN : PERF_COLOR_RED, + "%s", core_can_sample ? "supported" : "missing"); + fputc('\n', fp); + if (!core_can_sample) + strlist__add(missing, "core interrupt sampling"); + + fprintf(fp, " Precise event sampling: "); + if (core_precise) { + color_fprintf(fp, PERF_COLOR_GREEN, "supported"); + fprintf(fp, " (max_precise: %d [%s])\n", + max_precise, precise_level_desc(max_precise)); + } else { + color_fprintf(fp, PERF_COLOR_RED, "missing"); + fprintf(fp, " (%s)\n", precise_level_desc(0)); + strlist__add(missing, "precise event sampling (PEBS/IBS/SPE)"); + } + + fprintf(fp, " Branch stack sampling (LBR/BRBE): "); + if (core_branch_stack) { + color_fprintf(fp, PERF_COLOR_GREEN, "supported"); + if (max_br_depth > 0) + fprintf(fp, " (depth: %d branches)\n", max_br_depth); + else + fputc('\n', fp); + } else { + color_fprintf(fp, PERF_COLOR_RED, "missing"); + fputc('\n', fp); + strlist__add(missing, "hardware branch stack sampling (LBR/BRBE)"); + } + + if (probed) { + fprintf(fp, " User-space counter reading (rdpmc): "); + color_fprintf(fp, core_rdpmc ? PERF_COLOR_GREEN : PERF_COLOR_YELLOW, + "%s", core_rdpmc ? "supported" : "missing/disabled"); + fputc('\n', fp); + if (!core_rdpmc) + strlist__add(missing, "user-space counter reading (rdpmc)"); + + fprintf(fp, " Leader sampling: "); + color_fprintf(fp, + core_leader_sampling ? PERF_COLOR_GREEN : PERF_COLOR_RED, + "%s", + core_leader_sampling ? "supported" : "missing"); + fputc('\n', fp); + } + + fprintf(fp, " Memory profiling events (DATA_SRC): "); + color_fprintf(fp, core_mem_events ? PERF_COLOR_GREEN : PERF_COLOR_RED, + "%s", core_mem_events ? "supported" : "missing"); + fputc('\n', fp); + if (!core_mem_events) + strlist__add(missing, "memory load/store profiling events"); + + fprintf(fp, " Hardware AUX trace PMUs: "); + if (!strlist__empty(aux_pmus)) { + print_strlist_color(fp, PERF_COLOR_GREEN, aux_pmus); + fputc('\n', fp); + } else { + color_fprintf(fp, PERF_COLOR_RED, "missing"); + fputc('\n', fp); + strlist__add(missing, "hardware AUX trace PMU"); + } + } + + color_fprintf(fp, PERF_COLOR_BOLD, " Bus & uncore monitoring:"); + fputc('\n', fp); + fprintf(fp, " Memory bandwidth PMUs: "); + if (!strlist__empty(mem_bw_pmus)) { + print_strlist_color(fp, PERF_COLOR_GREEN, mem_bw_pmus); + fputc('\n', fp); + } else { + color_fprintf(fp, PERF_COLOR_RED, "missing"); + fputc('\n', fp); + strlist__add(missing, "memory bus bandwidth PMUs"); + } + + fprintf(fp, " PCIe / IO interconnect bandwidth PMUs: "); + if (!strlist__empty(io_bw_pmus)) { + print_strlist_color(fp, PERF_COLOR_GREEN, io_bw_pmus); + fputc('\n', fp); + } else { + color_fprintf(fp, PERF_COLOR_RED, "missing"); + fputc('\n', fp); + strlist__add(missing, "PCIe/IO interconnect bandwidth PMUs"); + } + + color_fprintf(fp, PERF_COLOR_BOLD, " Events & metrics:"); + fputc('\n', fp); + fprintf(fp, " Available events: %zu total (%zu sysfs, %zu json) across %zu PMUs\n", + total_events, total_sysfs_events, total_json_events, total_pmus); + if (nr_metrics == 0) { + fprintf(fp, " Available metrics: "); + color_fprintf(fp, PERF_COLOR_RED, "none"); + fprintf(fp, " (missing JSON metrics)\n"); + strlist__add(missing, "multi-level pipeline drill-down and JSON metrics"); + } else { + const char *tier = (max_drill_lvl >= 2) ? "advanced" : "minimal"; + const char *tier_col = (max_drill_lvl >= 2) ? + PERF_COLOR_GREEN : PERF_COLOR_YELLOW; + + fprintf(fp, " Available metrics: %zu metrics in %zu groups (", + nr_metrics, nr_groups); + color_fprintf(fp, tier_col, "%s", tier); + fprintf(fp, ")\n"); + if (max_drill_lvl > 0) { + fprintf(fp, " Metric drill-down levels (%d): ", max_drill_lvl); + for (int d = 1; d <= max_drill_lvl; d++) { + if (d > 1) + fprintf(fp, ", "); + color_fprintf(fp, PERF_COLOR_CYAN, "L%d", d); + if (level_labels[d]) + fprintf(fp, " (%s: %d metrics)", + level_labels[d], level_counts[d]); + else + fprintf(fp, " (%d metrics)", level_counts[d]); + } + fputc('\n', fp); + } else { + fprintf(fp, " Metric drill-down levels: "); + color_fprintf(fp, PERF_COLOR_YELLOW, "none"); + fprintf(fp, " (flat metrics only)\n"); + } + if (max_drill_lvl < 2) { + strlist__add(missing, + "multi-level pipeline drill-down metrics (<metric>_group)"); + } + } + + color_fprintf(fp, PERF_COLOR_BOLD, " System rating:"); + if (nr_core == 0) { + const char *min_desc = + "no hardware core PMU; profiling limited to software timer events"; + + fprintf(fp, " "); + color_fprintf(fp, PERF_COLOR_RED, "Minimal"); + fprintf(fp, " (%s)\n", min_desc); + } else if (core_precise && core_branch_stack && (!probed || core_rdpmc) && + !strlist__empty(mem_bw_pmus) && max_drill_lvl >= 2) { + const char *core_adv = "precise sampling, branch stack (LBR), user rdpmc"; + const char *sys_adv = "bus PMUs, and metric drill-down"; + + fprintf(fp, " "); + color_fprintf(fp, PERF_COLOR_GREEN, "Advanced"); + fprintf(fp, " (core PMU with %s, %s)\n", core_adv, sys_adv); + } else if (core_can_sample && + (core_precise || core_branch_stack || max_drill_lvl >= 1)) { + const char *std_desc = + "core PMU with hardware sampling and partial advanced features or metrics"; + + fprintf(fp, " "); + color_fprintf(fp, PERF_COLOR_CYAN, "Standard"); + fprintf(fp, " (%s)\n", std_desc); + } else { + const char *bas_desc = + "core PMU with basic counting/sampling only; missing advanced features"; + + fprintf(fp, " "); + color_fprintf(fp, PERF_COLOR_YELLOW, "Basic"); + fprintf(fp, " (%s)\n", bas_desc); + } + + if (!strlist__empty(missing)) { + color_fprintf(fp, PERF_COLOR_BOLD, " Missing baseline features:"); + fprintf(fp, " "); + print_strlist_color(fp, PERF_COLOR_RED, missing); + fputc('\n', fp); + } + + for (int d = 1; d <= max_drill_lvl; d++) + free(level_labels[d]); + +out_free: + strlist__delete(core_pmus); + strlist__delete(mem_bw_pmus); + strlist__delete(io_bw_pmus); + strlist__delete(aux_pmus); + strlist__delete(missing); + if (mstate) { + for (size_t i = 0; i < mstate->nr_items; i++) { + free(mstate->items[i].pmu); + free(mstate->items[i].name); + free(mstate->items[i].groups); + } + strlist__delete(mstate->metrics); + strlist__delete(mstate->groups); + free(mstate); + } +} + +int cmd_sysinfo(int argc, const char **argv) +{ + bool json = false; + bool show_pmus = false; + bool show_all = false; + bool force = false; + bool do_probe = true; + const char *output_path = NULL; + const char *input_name = NULL; + FILE *fp = stdout; + struct perf_data data = { + .mode = PERF_DATA_MODE_READ, + }; + struct perf_tool tool; + struct perf_session *session = NULL; + struct perf_pmu *pmu = NULL; + size_t pmu_idx = 0; + int ret = 0; + const struct option sysinfo_options[] = { + OPT_STRING('i', "input", &input_name, "file", + "Input perf.data file name to read PMUs from"), + OPT_BOOLEAN('f', "force", &force, "don't complain, do it"), + OPT_STRING('o', "output", &output_path, "file", "output file name"), + OPT_BOOLEAN('p', "pmus", &show_pmus, + "Display detailed information for each PMU"), + OPT_BOOLEAN('j', "json", &json, "JSON encode PMU information"), + OPT_BOOLEAN('v', "verbose", &show_all, + "Show all similar PMUs with alphanumeric suffixes"), + OPT_BOOLEAN('a', "all", &show_all, + "Show all similar PMUs with alphanumeric suffixes"), + OPT_BOOLEAN(0, "probe", &do_probe, + "Probe PMU capabilities on live system (default: on)"), + OPT_INCR(0, "debug", &verbose, "Enable debugging output"), + OPT_END() + }; + const char * const sysinfo_usage[] = { + "perf sysinfo [<options>] [pmu_glob ...]", + NULL + }; + + argc = parse_options(argc, argv, sysinfo_options, sysinfo_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (show_all || json || argc > 0) + show_pmus = true; + + if (output_path) { + perf_use_color_default = 0; + fp = fopen(output_path, "w"); + if (!fp) { + pr_err("Failed to open output file '%s': %m\n", output_path); + return -1; + } + } else { + setup_pager(); + } + + if (input_name) { + data.path = input_name; + data.force = force; + + perf_tool__init(&tool, /*ordered_events=*/false); + tool.attr = perf_event__process_attr; + tool.feature = process_header_feature; + tool.sample = process_sample_stop; + + session = perf_session__new(&data, &tool); + if (IS_ERR(session)) { + ret = PTR_ERR(session); + pr_err("Error opening %s: %s\n", input_name, strerror(-ret)); + goto out_close; + } + + if (data.is_pipe) + perf_session__process_events(session); + + ret = perf_pmus__read_from_env(&session->header.env); + if (ret) { + pr_err("Failed to read PMU information from %s: %s\n", + input_name, strerror(-ret)); + goto out_session; + } + } + + if (json) + fputs("[", fp); + + while ((pmu = show_all ? perf_pmus__scan(pmu) : + perf_pmus__scan_skip_duplicates(pmu)) != NULL) { + if (!pmu_matches_filters(pmu, argc, argv)) + continue; + + if (do_probe && !input_name && + (show_pmus || pmu->is_core || pmu->type == PERF_TYPE_SOFTWARE)) + perf_pmu__probe(pmu); + + if (!show_pmus) + continue; + + if (json) { + if (pmu_idx > 0) + fputs(",\n", fp); + else + fputs("\n", fp); + print_pmu_json(fp, pmu, session, show_all); + } else { + if (pmu_idx > 0) + fputc('\n', fp); + print_pmu_text(fp, pmu, session, show_all); + } + pmu_idx++; + } + + if (json) + fputs(pmu_idx > 0 ? "\n]\n" : "]\n", fp); + else if (argc == 0) + print_system_summary(fp, session, do_probe && !input_name, + pmu_idx > 0); + +out_session: + if (session) + perf_session__delete(session); +out_close: + if (output_path && fp) + fclose(fp); + return ret; +} diff --git a/tools/perf/builtin.h b/tools/perf/builtin.h index e0b430821abc..28e85c054468 100644 --- a/tools/perf/builtin.h +++ b/tools/perf/builtin.h @@ -37,6 +37,7 @@ int cmd_list(int argc, const char **argv); int cmd_record(int argc, const char **argv); int cmd_report(int argc, const char **argv); int cmd_stat(int argc, const char **argv); +int cmd_sysinfo(int argc, const char **argv); int cmd_timechart(int argc, const char **argv); int cmd_top(int argc, const char **argv); int cmd_script(int argc, const char **argv); diff --git a/tools/perf/perf.c b/tools/perf/perf.c index d60bc929c257..a6fab1e6eb1c 100644 --- a/tools/perf/perf.c +++ b/tools/perf/perf.c @@ -112,6 +112,7 @@ static const struct cmd_struct commands[] = { { "report", cmd_report, 0 }, { "bench", cmd_bench, 0 }, { "stat", cmd_stat, 0 }, + { "sysinfo", cmd_sysinfo, 0 }, { "timechart", cmd_timechart, 0 }, { "top", cmd_top, 0 }, { "annotate", cmd_annotate, 0 }, diff --git a/tools/perf/tests/shell/sysinfo.sh b/tools/perf/tests/shell/sysinfo.sh new file mode 100755 index 000000000000..40e90af6dca2 --- /dev/null +++ b/tools/perf/tests/shell/sysinfo.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# perf sysinfo tests + +set -e + +shelldir=$(dirname "$0") +. "${shelldir}"/lib/setup_python.sh + +sysinfo_output=$(mktemp /tmp/__perf_test.sysinfo_output.XXXXX) +perf_data=$(mktemp /tmp/__perf_test.sysinfo.perf.data.XXXXX) + +cleanup() { + rm -f "${sysinfo_output}" "${perf_data}" "${perf_data}.old" + trap - EXIT TERM INT +} + +trap_cleanup() { + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +test_sysinfo_default() { + echo "Default perf sysinfo output test" + perf sysinfo -o "${sysinfo_output}" + grep -q "^Summary:" "${sysinfo_output}" + grep -q "System rating:" "${sysinfo_output}" + perf sysinfo --pmus -o "${sysinfo_output}" + grep -q "^software (" "${sysinfo_output}" + grep -q "^Summary:" "${sysinfo_output}" + echo "Default perf sysinfo output test [Success]" +} + +test_sysinfo_json() { + echo "Json perf sysinfo output test" + perf sysinfo -j -o "${sysinfo_output}" + $PYTHON -m json.tool "${sysinfo_output}" /dev/null + echo "Json perf sysinfo output test [Success]" +} + +test_sysinfo_filter() { + echo "Filtered perf sysinfo output test" + perf sysinfo -o "${sysinfo_output}" software + grep -q "^software (" "${sysinfo_output}" + grep -q "Sample types:.*ip" "${sysinfo_output}" + grep -q "User registers:" "${sysinfo_output}" + grep -q "Intr registers:" "${sysinfo_output}" + grep -q "Features:.*leader_sampling" "${sysinfo_output}" + grep -q "Features:.*sw_grouping" "${sysinfo_output}" + echo "Filtered perf sysinfo output test [Success]" +} + +test_sysinfo_perf_data() { + echo "perf.data perf sysinfo test" + perf record -e software/cpu-clock/u -o "${perf_data}" -- true 2>/dev/null + perf sysinfo -i "${perf_data}" -o "${sysinfo_output}" + grep -q "^Summary:" "${sysinfo_output}" + perf sysinfo --pmus -i "${perf_data}" -o "${sysinfo_output}" + grep -q "software" "${sysinfo_output}" + perf sysinfo -i "${perf_data}" -j -o "${sysinfo_output}" + $PYTHON -m json.tool "${sysinfo_output}" /dev/null + echo "perf.data perf sysinfo test [Success]" +} + +test_sysinfo_default +test_sysinfo_json +test_sysinfo_filter +test_sysinfo_perf_data +cleanup +exit 0 -- 2.56.0.212.g129ede20d9-goog ^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v2 0/9] perf sysinfo: Add subcommand to probe and summarize PMU capabilities 2026-10-16 21:32 [RFC PATCH v1 0/2] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers 2026-10-16 21:32 ` [RFC PATCH v1 1/2] perf pmus: Add PMU environment loading and live capability probing Ian Rogers 2026-10-16 21:32 ` [RFC PATCH v1 2/2] perf sysinfo: Add sysinfo subcommand, documentation, and test Ian Rogers @ 2026-10-17 18:54 ` Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 1/9] perf pmu: Add from_env parameter to perf_pmu__init() Ian Rogers ` (8 more replies) 2 siblings, 9 replies; 13+ messages in thread From: Ian Rogers @ 2026-10-17 18:54 UTC (permalink / raw) To: irogers, acme, namhyung Cc: adrian.hunter, eranian, james.clark, jolsa, linux-kernel, linux-perf-users, mingo, peterz Determining what performance monitoring hardware, driver capabilities, and event/metric support are available on a system currently requires piecing together sysfs files under /sys/bus/event_source/devices/, perf list output, and trial-and-error perf_event_open() calls. Across architectures and SoCs, support varies widely: an advanced system provides core PMUs with zero-skid precise event sampling (PEBS/IBS/SPE), hardware branch stack recording (LBR/BRBE), user-space counter reading (rdpmc), memory profiling (DATA_SRC), hardware AUX trace PMUs, memory and PCIe/IO interconnect bandwidth PMUs, and multi-level pipeline metric drill-down hierarchies, whereas a minimal system may only offer software timer sampling. This RFC series introduces 'perf sysinfo' to inspect, actively probe, and summarize the PMU and performance monitoring capabilities of a live system or a recorded perf.data file: - Patch 1 adds a from_env constructor parameter to perf_pmu__init() to distinguish PMUs reconstructed from perf_env headers from live host PMUs. - Patch 2 adds perf_pmus__read_from_env() and perf_pmu__add_cap() to reconstruct core_pmus and other_pmus lists and their capabilities from perf.data headers. - Patch 3 exports perf_pmus__scan_skip_duplicates() and adds perf_pmu__num_duplicates() to count numbered uncore PMU instances. - Patch 4 exports perf_pmu__mem_events_init_pmu() and perf_pmu__mem_events_name() for per-PMU memory profiling events. - Patch 5 adds struct perf_pmu::probed and perf_pmu__probe() to probe counting/sampling/branch_stack modes, precise_ip levels, sample format flags, privilege exclusion modifiers, execution features (leader sampling, software grouping, auto-counter reload, AUX pause/resume), and event group size limits. - Patch 6 extends perf_pmu__probe() to probe user/interrupt sampling register masks (general-purpose and SIMD), memory profiling events, user-space rdpmc counter reading and PMC bit-width, and empirically measured branch instruction skid across precision levels. - Patch 7 adds the 'perf sysinfo' subcommand. By default, 'perf sysinfo' outputs a colorized system capability summary, pipeline metric drill-down hierarchy (via <metric>_group chains), overall system rating (Advanced, Standard, Basic, Minimal), and any missing baseline features. Passing '-p'/'--pmus' (or a PMU glob filter, '-v'/'--verbose', '-a'/'--all', or '-j'/'--json') displays detailed per-PMU static and probed attributes. - Patch 8 adds the 'perf-sysinfo' man page documentation. - Patch 9 adds the 'perf sysinfo' shell test. v2: - Split the initial PMU environment, capability probing, and subcommand patches into 9 smaller, single-purpose patches. - Disable the pager in 'perf sysinfo'. - Fix ARM64 and RISC-V branch-range assembly errors in run_skid_branch_loop() by marking it noinline and reducing the .rept nop counts on ARM64 and RISC-V. - Count tracepoint, drm, and hwmon events under sysfs rather than json in per-PMU and summary event totals. - Replace glibc-specific ElfW() macro in get_self_uprobe_target() with standard <elf.h> Elf64_Ehdr/Elf64_Phdr and Elf32_Ehdr/Elf32_Phdr for musl libc compatibility. Example default output on an Intel AlderLake hybrid system: $ perf sysinfo Summary: Core PMUs (2, hybrid): cpu_atom, cpu_core Core features: Interrupt-based sampling: supported Precise event sampling: supported (max_precise: 3 [3 - SAMPLE_IP must have 0 skid, or uses randomization]) Branch stack sampling (LBR/BRBE): supported (depth: 32 branches) User-space counter reading (rdpmc): supported Leader sampling: supported Memory profiling events (DATA_SRC): supported Hardware AUX trace PMUs: intel_bts, intel_pt Bus & uncore monitoring: Memory bandwidth PMUs: uncore_imc, uncore_imc_free_running PCIe / IO interconnect bandwidth PMUs: uncore_arb Events & metrics: Available events: 3161 total (120 sysfs, 3041 json) across 28 PMUs Available metrics: 438 metrics in 192 groups (advanced) Metric drill-down levels (6): L1 (TopdownL1: 7 metrics), L2 (TopdownL2: 14 metrics), L3 (TopdownL3: 44 metrics), L4 (TopdownL4: 45 metrics), L5 (TopdownL5: 15 metrics), L6 (TopdownL6: 9 metrics) System rating: Advanced (core PMU with precise sampling, branch stack (LBR), user rdpmc, bus PMUs, and metric drill-down) Example filtered per-PMU output: $ perf sysinfo cpu_core cpu_core (type: 4 [PERF_TYPE_RAW], core): CPUs: 0-15 (16 CPUs) Capabilities: branches=32, max_precise=3, pmu_name=alderlake_hybrid Formats: event (config:0-7), pc (config:19), edge (config:18), offcore_rsp (config1:0-63), ldlat (config1:0-15), inv (config:23), umask (config:8-15), frontend (config1:0-23), cmask (config:24-31) Events: 1563 total (20 sysfs, 1543 json) Memory events: ldlat-loads ({cpu_core/mem-loads-aux/,cpu_core/mem-loads,ldlat=30/}:P), ldlat-stores (cpu_core/mem-stores/P) Probed capabilities (event: cpu_core/cycles/): Modes: counting, sampling, branch_stack Precise sampling: :p (1 - SAMPLE_IP must have constant skid), :pp (2 - SAMPLE_IP requested to have 0 skid), :ppp (3 - SAMPLE_IP must have 0 skid, or uses randomization) (max_precise: 3) Measured branch skid (instructions from branch): level 0 (no :p): min 105, max 174, avg 156.8 (0/32 exact_ip) level 1 (:p) : min 1, max 1, avg 1.0 (0/31 exact_ip) level 2 (:pp) : min 0, max 0, avg 0.0 (31/31 exact_ip) level 3 (:ppp) : min 0, max 0, avg 0.0 (32/32 exact_ip) Sample types: ip, tid, time, addr, read, callchain, id, cpu, period, stream_id, raw, branch_stack, regs_user, stack_user, weight, data_src, identifier, transaction, regs_intr, aux, cgroup, data_page_size, code_page_size, weight_struct User registers: AX, BX, CX, DX, SI, DI, BP, SP, IP, FLAGS, CS, SS, R8, R9, R10, R11, R12, R13, R14, R15 Intr registers: AX, BX, CX, DX, SI, DI, BP, SP, IP, FLAGS, CS, SS, R8, R9, R10, R11, R12, R13, R14, R15, XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7, XMM8, XMM9, XMM10, XMM11, XMM12, XMM13, XMM14, XMM15 Memory events (probed): ldlat-loads, ldlat-stores (sample types: addr, weight, data_src, transaction, data_page_size, weight_struct) Exclusion modifiers: exclude_user, exclude_kernel, exclude_hv, exclude_idle, exclude_host, exclude_guest Features: leader_sampling, sw_grouping, inherit, sample_freq, write_backward, callchain, data_src, weight, pinned, exclusive, format_lost, user_rdpmc, user_time, user_time_zero User space counter reading (rdpmc): available, enabled, working (width: 48 bits) Max event group size: 18 (active: 8) Max GP event group size: 16 (active: 7) Example output on a Raspberry Pi 5 (ARM Cortex-A76): $ sudo perf sysinfo armv8_cortex_a76 armv8_cortex_a76 (type: 9 [dynamic], core): CPUs: 0-3 (4 CPUs) Capabilities: slots=0x00000000, threshold_max=0x00000000, bus_slots=0x00000000, bus_width=0x00000000 Formats: threshold_count (config1:2), event (config:0-15), long (config1:0), rdpmc (config1:1), threshold (config1:5-16), threshold_compare (config1:3-4) Events: 1336 total (40 sysfs, 1296 json) Probed capabilities (event: armv8_cortex_a76/cycles/): Modes: counting, sampling Precise sampling: not supported (0 - SAMPLE_IP can have arbitrary skid) Measured branch skid (instructions from branch): level 0 (no :p): min 340, max 352, avg 346.2 (0/31 exact_ip) Sample types: ip, tid, time, addr, read, callchain, id, cpu, period, stream_id, raw, regs_user, stack_user, weight, data_src, identifier, transaction, regs_intr, phys_addr, aux, cgroup, data_page_size, code_page_size, weight_struct User registers: x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, lr, sp, pc Intr registers: x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22, x23, x24, x25, x26, x27, x28, x29, lr, sp, pc Exclusion modifiers: exclude_user, exclude_kernel, exclude_hv, exclude_host, exclude_guest Features: leader_sampling, sw_grouping, inherit, sample_freq, write_backward, callchain, data_src, weight, pinned, exclusive, format_lost, user_time, user_time_zero, user_time_short User space counter reading (rdpmc): available, disabled (sysfs: 0) (width: 32 bits) Max event group size: 14 (active: 7) Max GP event group size: 13 (active: 6) $ sudo perf sysinfo Summary: Core PMUs (1): armv8_cortex_a76 Core features: Interrupt-based sampling: supported Precise event sampling: missing (0 - SAMPLE_IP can have arbitrary skid) Branch stack sampling (LBR/BRBE): missing User-space counter reading (rdpmc): missing/disabled Leader sampling: supported Memory profiling events (DATA_SRC): missing Hardware AUX trace PMUs: missing Bus & uncore monitoring: Memory bandwidth PMUs: missing PCIe / IO interconnect bandwidth PMUs: missing Events & metrics: Available events: 3695 total (2371 sysfs, 1324 json) across 11 PMUs Available metrics: 21 metrics in 5 groups (minimal) Metric drill-down levels: none (flat metrics only) System rating: Basic (core PMU with basic counting/sampling only; missing advanced features) Missing baseline features: PCIe/IO interconnect bandwidth PMUs, hardware AUX trace PMU, hardware branch stack sampling (LBR/BRBE), memory bus bandwidth PMUs, memory load/store profiling events, multi-level pipeline drill-down metrics (<metric>_group), precise event sampling (PEBS/IBS/SPE), user-space counter reading (rdpmc) This patch series was generated with the assistance of antigravity and Gemini 4.0. Ian Rogers (9): perf pmu: Add from_env parameter to perf_pmu__init() perf pmus: Add perf_pmus__read_from_env() perf pmus: Expose duplicate uncore PMU scanning helpers perf mem-events: Expose PMU init and name formatting helpers perf pmu: Add perf_pmu__probe() for live capability probing perf pmu: Probe registers, memory events, rdpmc, and branch skid perf sysinfo: Add sysinfo subcommand perf docs: Add perf-sysinfo man page perf test: Add perf sysinfo shell test tools/perf/Build | 1 + tools/perf/Documentation/perf-sysinfo.txt | 109 ++ tools/perf/Documentation/perf.txt | 2 +- tools/perf/builtin-help.c | 3 +- tools/perf/builtin-sysinfo.c | 2010 ++++++++++++++++++++ tools/perf/builtin.h | 1 + tools/perf/perf.c | 1 + tools/perf/tests/pmu-events.c | 4 +- tools/perf/tests/shell/sysinfo.sh | 71 + tools/perf/util/drm_pmu.c | 2 +- tools/perf/util/hwmon_pmu.c | 2 +- tools/perf/util/mem-events.c | 14 +- tools/perf/util/mem-events.h | 3 + tools/perf/util/pmu.c | 2047 ++++++++++++++++++++- tools/perf/util/pmu.h | 139 +- tools/perf/util/pmus.c | 228 ++- tools/perf/util/pmus.h | 4 + tools/perf/util/tool_pmu.c | 2 +- 18 files changed, 4611 insertions(+), 32 deletions(-) create mode 100644 tools/perf/Documentation/perf-sysinfo.txt create mode 100644 tools/perf/builtin-sysinfo.c create mode 100755 tools/perf/tests/shell/sysinfo.sh -- 2.56.0.212.g129ede20d9-goog ^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v2 1/9] perf pmu: Add from_env parameter to perf_pmu__init() 2026-10-17 18:54 ` [RFC PATCH v2 0/9] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers @ 2026-10-17 18:54 ` Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 2/9] perf pmus: Add perf_pmus__read_from_env() Ian Rogers ` (7 subsequent siblings) 8 siblings, 0 replies; 13+ messages in thread From: Ian Rogers @ 2026-10-17 18:54 UTC (permalink / raw) To: irogers, acme, namhyung Cc: adrian.hunter, eranian, james.clark, jolsa, linux-kernel, linux-perf-users, mingo, peterz Add a boolean from_env field to struct perf_pmu initialized via perf_pmu__init() to distinguish PMUs reconstructed from perf_env headers from live host PMUs. Guard tp_pmu, hwmon_pmu, and drm_pmu callbacks on !pmu->from_env so perf.data-derived PMUs do not access host tracepoints or cast plain struct perf_pmu allocations via container_of(). Signed-off-by: Ian Rogers <irogers@google.com> --- tools/perf/tests/pmu-events.c | 4 ++-- tools/perf/util/drm_pmu.c | 2 +- tools/perf/util/hwmon_pmu.c | 2 +- tools/perf/util/pmu.c | 33 ++++++++++++++++++--------------- tools/perf/util/pmu.h | 4 +++- tools/perf/util/tool_pmu.c | 2 +- 6 files changed, 26 insertions(+), 21 deletions(-) diff --git a/tools/perf/tests/pmu-events.c b/tools/perf/tests/pmu-events.c index f507ce39439a..cbcc01227a76 100644 --- a/tools/perf/tests/pmu-events.c +++ b/tools/perf/tests/pmu-events.c @@ -514,7 +514,7 @@ static int __test_core_pmu_event_aliases(const char *pmu_name, int *count) if (!pmu) return -1; - if (perf_pmu__init(pmu, PERF_PMU_TYPE_FAKE, pmu_name) != 0) { + if (perf_pmu__init(pmu, PERF_PMU_TYPE_FAKE, pmu_name, /*from_env=*/false) != 0) { perf_pmu__delete(pmu); return -1; } @@ -566,7 +566,7 @@ static int __test_uncore_pmu_event_aliases(struct perf_pmu_test_pmu *test_pmu) if (!pmu) return -1; - if (perf_pmu__init(pmu, PERF_PMU_TYPE_FAKE, test_pmu->pmu_name) != 0) { + if (perf_pmu__init(pmu, PERF_PMU_TYPE_FAKE, test_pmu->pmu_name, /*from_env=*/false) != 0) { perf_pmu__delete(pmu); return -1; } diff --git a/tools/perf/util/drm_pmu.c b/tools/perf/util/drm_pmu.c index b8badae7015c..03764efb3342 100644 --- a/tools/perf/util/drm_pmu.c +++ b/tools/perf/util/drm_pmu.c @@ -116,7 +116,7 @@ static struct drm_pmu *add_drm_pmu(struct list_head *pmus, char *line, size_t li if (!drm) return NULL; - if (perf_pmu__init(&drm->pmu, type, name) != 0) { + if (perf_pmu__init(&drm->pmu, type, name, /*from_env=*/false) != 0) { perf_pmu__delete(&drm->pmu); return NULL; } diff --git a/tools/perf/util/hwmon_pmu.c b/tools/perf/util/hwmon_pmu.c index 96aeb379e169..d9e41eb88b6d 100644 --- a/tools/perf/util/hwmon_pmu.c +++ b/tools/perf/util/hwmon_pmu.c @@ -365,7 +365,7 @@ struct perf_pmu *hwmon_pmu__new(struct list_head *pmus, const char *hwmon_dir, if (!hwm) return NULL; - if (perf_pmu__init(&hwm->pmu, type, buf) != 0) { + if (perf_pmu__init(&hwm->pmu, type, buf, /*from_env=*/false) != 0) { perf_pmu__delete(&hwm->pmu); return NULL; } diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c index 01753d5c69d1..bb2e79802def 100644 --- a/tools/perf/util/pmu.c +++ b/tools/perf/util/pmu.c @@ -1188,9 +1188,10 @@ static bool aliases__equal(long key1, long key2, void *ctx __maybe_unused) return strcasecmp((const char *)key1, (const char *)key2) == 0; } -int perf_pmu__init(struct perf_pmu *pmu, __u32 type, const char *name) +int perf_pmu__init(struct perf_pmu *pmu, __u32 type, const char *name, bool from_env) { pmu->type = type; + pmu->from_env = from_env; INIT_LIST_HEAD(&pmu->format); INIT_LIST_HEAD(&pmu->caps); @@ -1240,7 +1241,7 @@ struct perf_pmu *perf_pmu__lookup(struct list_head *pmus, int dirfd, const char if (!pmu) return NULL; - if (perf_pmu__init(pmu, PERF_PMU_TYPE_FAKE, name) != 0) { + if (perf_pmu__init(pmu, PERF_PMU_TYPE_FAKE, name, /*from_env=*/false) != 0) { perf_pmu__delete(pmu); return NULL; } @@ -2118,11 +2119,11 @@ bool perf_pmu__have_event(struct perf_pmu *pmu, const char *name) if (perf_pmu__is_tool(pmu) && tool_pmu__skip_event(name)) return false; if (perf_pmu__is_tracepoint(pmu)) - return tp_pmu__have_event(pmu, name); + return pmu->from_env ? false : tp_pmu__have_event(pmu, name); if (perf_pmu__is_hwmon(pmu)) - return hwmon_pmu__have_event(pmu, name); + return pmu->from_env ? false : hwmon_pmu__have_event(pmu, name); if (perf_pmu__is_drm(pmu)) - return drm_pmu__have_event(pmu, name); + return pmu->from_env ? false : drm_pmu__have_event(pmu, name); if (perf_pmu__find_alias(pmu, name, /*load=*/ true) != NULL) return true; if (pmu->cpu_aliases_added || (!pmu->events_table && !pmu->is_core)) @@ -2139,11 +2140,11 @@ size_t perf_pmu__num_events(struct perf_pmu *pmu) size_t nr; if (perf_pmu__is_tracepoint(pmu)) - return tp_pmu__num_events(pmu); + return pmu->from_env ? 0 : tp_pmu__num_events(pmu); if (perf_pmu__is_hwmon(pmu)) - return hwmon_pmu__num_events(pmu); + return pmu->from_env ? 0 : hwmon_pmu__num_events(pmu); if (perf_pmu__is_drm(pmu)) - return drm_pmu__num_events(pmu); + return pmu->from_env ? 0 : drm_pmu__num_events(pmu); pmu_aliases_parse(pmu); nr = pmu->sysfs_aliases + pmu->sys_json_aliases; @@ -2275,11 +2276,11 @@ int perf_pmu__for_each_event(struct perf_pmu *pmu, bool skip_duplicate_pmus, size_t size_rem; if (perf_pmu__is_tracepoint(pmu)) - return tp_pmu__for_each_event(pmu, state, cb); + return pmu->from_env ? 0 : tp_pmu__for_each_event(pmu, state, cb); if (perf_pmu__is_hwmon(pmu)) - return hwmon_pmu__for_each_event(pmu, state, cb); + return pmu->from_env ? 0 : hwmon_pmu__for_each_event(pmu, state, cb); if (perf_pmu__is_drm(pmu)) - return drm_pmu__for_each_event(pmu, state, cb); + return pmu->from_env ? 0 : drm_pmu__for_each_event(pmu, state, cb); pmu_aliases_parse(pmu); pmu_add_cpu_aliases(pmu); @@ -2784,10 +2785,12 @@ void perf_pmu__delete(struct perf_pmu *pmu) if (!pmu) return; - if (perf_pmu__is_hwmon(pmu)) - hwmon_pmu__exit(pmu); - else if (perf_pmu__is_drm(pmu)) - drm_pmu__exit(pmu); + if (!pmu->from_env) { + if (perf_pmu__is_hwmon(pmu)) + hwmon_pmu__exit(pmu); + else if (perf_pmu__is_drm(pmu)) + drm_pmu__exit(pmu); + } perf_pmu__del_formats(&pmu->format); perf_pmu__del_aliases(pmu); diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h index 2c8916ad073e..44212f6361c7 100644 --- a/tools/perf/util/pmu.h +++ b/tools/perf/util/pmu.h @@ -161,6 +161,8 @@ struct perf_pmu { uint32_t cpu_common_json_aliases; /** @sysfs_aliases_loaded: Are sysfs aliases loaded from disk? */ bool sysfs_aliases_loaded; + /** @from_env: Was the PMU reconstructed from a perf_env header? */ + bool from_env; /** * @cpu_aliases_added: Have all json events table entries for the PMU * been added? @@ -345,7 +347,7 @@ int perf_pmu__pathname_scnprintf(char *buf, size_t size, int perf_pmu__event_source_devices_fd(void); int perf_pmu__pathname_fd(int dirfd, const char *pmu_name, const char *filename, int flags); -int perf_pmu__init(struct perf_pmu *pmu, __u32 type, const char *name); +int perf_pmu__init(struct perf_pmu *pmu, __u32 type, const char *name, bool from_env); struct perf_pmu *perf_pmu__lookup(struct list_head *pmus, int dirfd, const char *lookup_name, bool eager_load); struct perf_pmu *perf_pmu__create_placeholder_core_pmu(struct list_head *core_pmus); diff --git a/tools/perf/util/tool_pmu.c b/tools/perf/util/tool_pmu.c index d2f7591747d9..6054d5d5aece 100644 --- a/tools/perf/util/tool_pmu.c +++ b/tools/perf/util/tool_pmu.c @@ -714,7 +714,7 @@ struct perf_pmu *tool_pmu__new(void) if (!tool) return NULL; - if (perf_pmu__init(tool, PERF_PMU_TYPE_TOOL, "tool") != 0) { + if (perf_pmu__init(tool, PERF_PMU_TYPE_TOOL, "tool", /*from_env=*/false) != 0) { perf_pmu__delete(tool); return NULL; } -- 2.56.0.212.g129ede20d9-goog ^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v2 2/9] perf pmus: Add perf_pmus__read_from_env() 2026-10-17 18:54 ` [RFC PATCH v2 0/9] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 1/9] perf pmu: Add from_env parameter to perf_pmu__init() Ian Rogers @ 2026-10-17 18:54 ` Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 3/9] perf pmus: Expose duplicate uncore PMU scanning helpers Ian Rogers ` (6 subsequent siblings) 8 siblings, 0 replies; 13+ messages in thread From: Ian Rogers @ 2026-10-17 18:54 UTC (permalink / raw) To: irogers, acme, namhyung Cc: adrian.hunter, eranian, james.clark, jolsa, linux-kernel, linux-perf-users, mingo, peterz Add perf_pmus__read_from_env() and perf_pmu__add_cap() to reconstruct core_pmus and other_pmus lists and their capabilities from HEADER_PMU_MAPPINGS, HEADER_HYBRID_TOPOLOGY, HEADER_CPU_PMU_CAPS, and HEADER_PMU_CAPS in a perf.data header environment. Signed-off-by: Ian Rogers <irogers@google.com> --- tools/perf/util/pmu.c | 31 +++++++ tools/perf/util/pmu.h | 1 + tools/perf/util/pmus.c | 205 +++++++++++++++++++++++++++++++++++++++++ tools/perf/util/pmus.h | 2 + 4 files changed, 239 insertions(+) diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c index bb2e79802def..cd8211f87291 100644 --- a/tools/perf/util/pmu.c +++ b/tools/perf/util/pmu.c @@ -2565,6 +2565,37 @@ static int perf_pmu__new_caps(struct list_head *list, char *name, char *value) return -ENOMEM; } +int perf_pmu__add_cap(struct perf_pmu *pmu, const char *name, const char *value) +{ + struct perf_pmu_caps *caps; + + if (!pmu || !name || !value) + return -EINVAL; + if (perf_pmu__get_cap(pmu, name)) + return 0; + + caps = zalloc(sizeof(*caps)); + if (!caps) + return -ENOMEM; + + caps->name = strdup(name); + if (!caps->name) { + free(caps); + return -ENOMEM; + } + caps->value = strdup(value); + if (!caps->value) { + zfree(&caps->name); + free(caps); + return -ENOMEM; + } + list_add_tail(&caps->list, &pmu->caps); + pmu->nr_caps++; + if (!strcmp(name, "max_precise")) + pmu->max_precise = atoi(value); + return 0; +} + static void perf_pmu__del_caps(struct perf_pmu *pmu) { struct perf_pmu_caps *caps, *tmp; diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h index 44212f6361c7..adb33a9fc94b 100644 --- a/tools/perf/util/pmu.h +++ b/tools/perf/util/pmu.h @@ -331,6 +331,7 @@ bool pmu_uncore_identifier_match(const char *compat, const char *id); int perf_pmu__convert_scale(const char *scale, char **end, double *sval); struct perf_pmu_caps *perf_pmu__get_cap(struct perf_pmu *pmu, const char *name); +int perf_pmu__add_cap(struct perf_pmu *pmu, const char *name, const char *value); int perf_pmu__caps_parse(struct perf_pmu *pmu); diff --git a/tools/perf/util/pmus.c b/tools/perf/util/pmus.c index 1355b317f3ed..ea727d577c89 100644 --- a/tools/perf/util/pmus.c +++ b/tools/perf/util/pmus.c @@ -7,13 +7,16 @@ #include <subcmd/pager.h> #include <sys/types.h> #include <ctype.h> +#include <errno.h> #include <pthread.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "cpumap.h" #include "debug.h" #include "drm_pmu.h" +#include "env.h" #include "evsel.h" #include "pmus.h" #include "pmu.h" @@ -916,3 +919,205 @@ struct perf_pmu *perf_pmus__fake_pmu(void) return &fake; } + +static bool env_is_pmu_core(const char *name, struct perf_env *env) +{ + if (!strcmp(name, "cpu") || !strcmp(name, "cpum_cf") || + !strcmp(name, "default_core") || strstarts(name, "cpu_") || + strstarts(name, "armv") || + (strstarts(name, "apple_") && strstr(name, "_pmu"))) + return true; + + if (env && env->hybrid_nodes) { + for (int i = 0; i < env->nr_hybrid_nodes; i++) { + const char *pmu_name = env->hybrid_nodes[i].pmu_name; + + if (pmu_name && !strcmp(name, pmu_name)) + return true; + } + return false; + } + + if (!env || !strcmp(perf_env__arch(env), perf_env__arch(NULL))) + return is_pmu_core(name); + + return false; +} + +static struct perf_pmu *env_pmu_find_or_add(const char *name, __u32 type, + struct perf_env *env, + const struct pmu_events_table *core_table) +{ + struct perf_pmu *pmu = pmu_find(name); + + if (pmu) { + if (pmu->type == PERF_PMU_TYPE_FAKE && type != PERF_PMU_TYPE_FAKE) + pmu->type = type; + return pmu; + } + + pmu = zalloc(sizeof(*pmu)); + if (!pmu) + return NULL; + + if (perf_pmu__init(pmu, type, name, /*from_env=*/true) != 0) { + perf_pmu__delete(pmu); + return NULL; + } + + pmu->sysfs_aliases_loaded = true; + pmu->caps_initialized = true; + pmu->max_precise = -1; + pmu->is_core = env_is_pmu_core(name, env); + pmu->is_uncore = strstarts(name, "uncore_"); + + if (!strcmp(name, "software") || !strcmp(name, "tool")) + pmu->events_table = perf_pmu__default_core_events_table(); + else if (core_table && (pmu->is_core || pmu->is_uncore)) + pmu->events_table = core_table; + + list_add_tail(&pmu->list, pmu->is_core ? &core_pmus : &other_pmus); + return pmu; +} + +static void env_pmu_add_caps_strings(struct perf_pmu *pmu, int nr_caps, char **caps) +{ + if (!pmu || nr_caps <= 0 || !caps) + return; + + for (int i = 0; i < nr_caps; i++) { + const char *eq; + + if (!caps[i]) + continue; + eq = strchr(caps[i], '='); + + if (eq) { + char *key = strndup(caps[i], eq - caps[i]); + + if (key) { + perf_pmu__add_cap(pmu, key, eq + 1); + free(key); + } + } else { + perf_pmu__add_cap(pmu, caps[i], ""); + } + } +} + +int perf_pmus__read_from_env(struct perf_env *env) +{ + const struct pmu_events_table *core_table = NULL; + struct perf_pmu *pmu; + + if (!env) + return -EINVAL; + + perf_pmus__destroy(); + read_pmu_types = PERF_TOOL_PMU_TYPE_ALL_MASK; + + if (env->cpuid) { + const char *arch = perf_env__arch(env); + char cpuid_str[128]; + char vendor[32]; + unsigned int family, model, step; + char extra; + + core_table = find_core_events_table(arch, env->cpuid); + /* + * x86 HEADER_CPUID stores "vendor,family,model,step" in decimal + * while pmu-events tables match "vendor-family-HEXMODEL[-HEXSTEP]". + */ + if (!core_table && !strcmp(arch, "x86") && + sscanf(env->cpuid, "%31[^,],%u,%u,%u%c", + vendor, &family, &model, &step, &extra) == 4) { + snprintf(cpuid_str, sizeof(cpuid_str), "%s-%u-%X-%X", + vendor, family, model, step); + core_table = find_core_events_table(arch, cpuid_str); + if (!core_table) { + snprintf(cpuid_str, sizeof(cpuid_str), "%s-%u-%X", + vendor, family, model); + core_table = find_core_events_table(arch, cpuid_str); + } + } + } + + /* 1. Populate from HEADER_PMU_MAPPINGS */ + if (env->nr_pmu_mappings > 0 && env->pmu_mappings) { + char *str = env->pmu_mappings; + + for (int i = 0; i < env->nr_pmu_mappings; i++) { + char *colon = NULL; + unsigned long type = strtoul(str, &colon, 0); + + if (!colon || *colon != ':') + break; + str = colon + 1; + env_pmu_find_or_add(str, (__u32)type, env, core_table); + str += strlen(str) + 1; + } + } + + /* 2. Populate/enrich from HEADER_HYBRID_TOPOLOGY */ + for (int i = 0; i < env->nr_hybrid_nodes; i++) { + struct hybrid_node *hn = &env->hybrid_nodes[i]; + + if (!hn->pmu_name) + continue; + pmu = env_pmu_find_or_add(hn->pmu_name, PERF_PMU_TYPE_FAKE, + env, core_table); + if (!pmu) + continue; + if (!pmu->is_core) { + pmu->is_core = true; + if (!pmu->events_table && core_table) + pmu->events_table = core_table; + list_move_tail(&pmu->list, &core_pmus); + } + if (hn->cpus && !pmu->cpus) + pmu->cpus = perf_cpu_map__new(hn->cpus); + } + + /* + * 3. Populate/enrich from HEADER_CPU_PMU_CAPS. + * On hybrid systems HEADER_CPU_PMU_CAPS duplicates the first core PMU + * while HEADER_PMU_CAPS has the per-PMU capabilities, so only create a + * "cpu" PMU entry if no core PMUs exist yet. + */ + if (env->nr_cpu_pmu_caps > 0 && env->cpu_pmu_caps) { + pmu = pmu_find("cpu"); + if (!pmu && list_empty(&core_pmus)) { + pmu = env_pmu_find_or_add("cpu", PERF_TYPE_RAW, + env, core_table); + } + if (pmu) + env_pmu_add_caps_strings(pmu, env->nr_cpu_pmu_caps, env->cpu_pmu_caps); + } + + /* 4. Populate/enrich from HEADER_PMU_CAPS */ + for (int i = 0; i < env->nr_pmus_with_caps; i++) { + struct pmu_caps *pc = &env->pmu_caps[i]; + + if (!pc->pmu_name) + continue; + pmu = env_pmu_find_or_add(pc->pmu_name, PERF_PMU_TYPE_FAKE, + env, core_table); + if (pmu) + env_pmu_add_caps_strings(pmu, pc->nr_caps, pc->caps); + } + + /* 5. Set default core CPUs on non-hybrid systems */ + if (env->nr_cpus_avail > 0 && env->nr_hybrid_nodes == 0) { + char buf[64]; + + snprintf(buf, sizeof(buf), "0-%d", env->nr_cpus_avail - 1); + list_for_each_entry(pmu, &core_pmus, list) { + if (!pmu->cpus) + pmu->cpus = perf_cpu_map__new(buf); + } + } + + list_sort(NULL, &core_pmus, pmus_cmp); + list_sort(NULL, &other_pmus, pmus_cmp); + return 0; +} diff --git a/tools/perf/util/pmus.h b/tools/perf/util/pmus.h index 0d55edb3f2fc..d1646f91036e 100644 --- a/tools/perf/util/pmus.h +++ b/tools/perf/util/pmus.h @@ -6,6 +6,7 @@ #include <stddef.h> struct perf_event_attr; +struct perf_env; struct perf_pmu; struct print_callbacks; @@ -14,6 +15,7 @@ size_t pmu_name_len_no_suffix(const char *str); int pmu_name_cmp(const char *lhs_pmu_name, const char *rhs_pmu_name); void perf_pmus__destroy(void); +int perf_pmus__read_from_env(struct perf_env *env); struct perf_pmu *perf_pmus__find(const char *name); struct perf_pmu *perf_pmus__find_by_type(unsigned int type); -- 2.56.0.212.g129ede20d9-goog ^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v2 3/9] perf pmus: Expose duplicate uncore PMU scanning helpers 2026-10-17 18:54 ` [RFC PATCH v2 0/9] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 1/9] perf pmu: Add from_env parameter to perf_pmu__init() Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 2/9] perf pmus: Add perf_pmus__read_from_env() Ian Rogers @ 2026-10-17 18:54 ` Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 4/9] perf mem-events: Expose PMU init and name formatting helpers Ian Rogers ` (5 subsequent siblings) 8 siblings, 0 replies; 13+ messages in thread From: Ian Rogers @ 2026-10-17 18:54 UTC (permalink / raw) To: irogers, acme, namhyung Cc: adrian.hunter, eranian, james.clark, jolsa, linux-kernel, linux-perf-users, mingo, peterz Export perf_pmus__scan_skip_duplicates() and add perf_pmu__num_duplicates() to count numbered uncore PMU instances sharing a common prefix. Signed-off-by: Ian Rogers <irogers@google.com> --- tools/perf/util/pmus.c | 23 ++++++++++++++++++++++- tools/perf/util/pmus.h | 2 ++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/tools/perf/util/pmus.c b/tools/perf/util/pmus.c index ea727d577c89..8657b36767ff 100644 --- a/tools/perf/util/pmus.c +++ b/tools/perf/util/pmus.c @@ -455,7 +455,7 @@ struct perf_pmu *perf_pmus__scan_matching_wildcard(struct perf_pmu *pmu, const c return NULL; } -static struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmu *pmu) +struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmu *pmu) { bool use_core_pmus = !pmu || pmu->is_core; int last_pmu_name_len = 0; @@ -492,6 +492,27 @@ static struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmu *pmu) return NULL; } +size_t perf_pmu__num_duplicates(const struct perf_pmu *pmu) +{ + const struct perf_pmu *pos = pmu; + size_t base_len; + size_t count = 1; + + if (!pmu || pmu->is_core) + return 1; + + base_len = pmu_name_len_no_suffix(pmu->name ?: ""); + list_for_each_entry_continue(pos, &other_pmus, list) { + size_t pos_len = pmu_name_len_no_suffix(pos->name ?: ""); + + if (pos_len != base_len || + strncmp(pmu->name ?: "", pos->name ?: "", base_len)) + break; + count++; + } + return count; +} + struct perf_pmu *perf_pmus__scan_for_uncore_id(struct perf_pmu *pmu, const char *compat) { if (!pmu) { diff --git a/tools/perf/util/pmus.h b/tools/perf/util/pmus.h index d1646f91036e..58ecab3e3da3 100644 --- a/tools/perf/util/pmus.h +++ b/tools/perf/util/pmus.h @@ -23,6 +23,8 @@ struct perf_pmu *perf_pmus__find_by_attr(const struct perf_event_attr *attr); struct perf_pmu *perf_pmus__scan(struct perf_pmu *pmu); struct perf_pmu *perf_pmus__scan_core(struct perf_pmu *pmu); +struct perf_pmu *perf_pmus__scan_skip_duplicates(struct perf_pmu *pmu); +size_t perf_pmu__num_duplicates(const struct perf_pmu *pmu); struct perf_pmu *perf_pmus__scan_for_event(struct perf_pmu *pmu, const char *event); struct perf_pmu *perf_pmus__scan_matching_wildcard(struct perf_pmu *pmu, const char *wildcard); struct perf_pmu *perf_pmus__scan_for_uncore_id(struct perf_pmu *pmu, const char *compat); -- 2.56.0.212.g129ede20d9-goog ^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v2 4/9] perf mem-events: Expose PMU init and name formatting helpers 2026-10-17 18:54 ` [RFC PATCH v2 0/9] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers ` (2 preceding siblings ...) 2026-10-17 18:54 ` [RFC PATCH v2 3/9] perf pmus: Expose duplicate uncore PMU scanning helpers Ian Rogers @ 2026-10-17 18:54 ` Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 5/9] perf pmu: Add perf_pmu__probe() for live capability probing Ian Rogers ` (4 subsequent siblings) 8 siblings, 0 replies; 13+ messages in thread From: Ian Rogers @ 2026-10-17 18:54 UTC (permalink / raw) To: irogers, acme, namhyung Cc: adrian.hunter, eranian, james.clark, jolsa, linux-kernel, linux-perf-users, mingo, peterz Export perf_pmu__mem_events_init_pmu() and perf_pmu__mem_events_name() so callers can initialize and format per-PMU memory profiling events. Signed-off-by: Ian Rogers <irogers@google.com> --- tools/perf/util/mem-events.c | 14 +++++++------- tools/perf/util/mem-events.h | 3 +++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/tools/perf/util/mem-events.c b/tools/perf/util/mem-events.c index 0cdcb47647bd..8b3af1585652 100644 --- a/tools/perf/util/mem-events.c +++ b/tools/perf/util/mem-events.c @@ -79,12 +79,12 @@ int perf_pmu__mem_events_num_mem_pmus(struct perf_pmu *pmu) return num; } -static const char *perf_pmu__mem_events_name(struct perf_pmu *pmu, int i, - char *buf, size_t buf_size) +const char *perf_pmu__mem_events_name(struct perf_pmu *pmu, int i, + char *buf, size_t buf_size) { struct perf_mem_event *e; - if (i >= PERF_MEM_EVENTS__MAX || !pmu) + if (i >= PERF_MEM_EVENTS__MAX || !pmu || !pmu->mem_events) return NULL; e = &pmu->mem_events[i]; @@ -193,13 +193,13 @@ static bool perf_pmu__mem_events_supported(const char *mnt, struct perf_pmu *pmu return !stat(path, &st); } -static int __perf_pmu__mem_events_init(struct perf_pmu *pmu) +int perf_pmu__mem_events_init_pmu(struct perf_pmu *pmu) { const char *mnt = sysfs__mount(); bool found = false; int j; - if (!mnt) + if (!mnt || !pmu || pmu->from_env || !pmu->mem_events) return -ENOENT; for (j = 0; j < PERF_MEM_EVENTS__MAX; j++) { @@ -209,7 +209,7 @@ static int __perf_pmu__mem_events_init(struct perf_pmu *pmu) * If the event entry isn't valid, skip initialization * and "e->supported" will keep false. */ - if (!e->tag) + if (!e || !e->tag) continue; e->supported |= perf_pmu__mem_events_supported(mnt, pmu, e); @@ -225,7 +225,7 @@ int perf_pmu__mem_events_init(void) struct perf_pmu *pmu = NULL; while ((pmu = perf_pmus__scan_mem(pmu)) != NULL) { - if (__perf_pmu__mem_events_init(pmu)) + if (perf_pmu__mem_events_init_pmu(pmu)) return -ENOENT; } diff --git a/tools/perf/util/mem-events.h b/tools/perf/util/mem-events.h index 38b14c03392c..64517ca053d3 100644 --- a/tools/perf/util/mem-events.h +++ b/tools/perf/util/mem-events.h @@ -31,7 +31,10 @@ extern struct perf_mem_event perf_mem_events[PERF_MEM_EVENTS__MAX]; extern bool perf_mem_record[PERF_MEM_EVENTS__MAX]; int perf_pmu__mem_events_parse(struct perf_pmu *pmu, const char *str); +int perf_pmu__mem_events_init_pmu(struct perf_pmu *pmu); int perf_pmu__mem_events_init(void); +const char *perf_pmu__mem_events_name(struct perf_pmu *pmu, int i, + char *buf, size_t buf_size); struct perf_mem_event *perf_pmu__mem_events_ptr(struct perf_pmu *pmu, int i); struct perf_pmu *perf_mem_events_find_pmu(void); -- 2.56.0.212.g129ede20d9-goog ^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v2 5/9] perf pmu: Add perf_pmu__probe() for live capability probing 2026-10-17 18:54 ` [RFC PATCH v2 0/9] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers ` (3 preceding siblings ...) 2026-10-17 18:54 ` [RFC PATCH v2 4/9] perf mem-events: Expose PMU init and name formatting helpers Ian Rogers @ 2026-10-17 18:54 ` Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 6/9] perf pmu: Probe registers, memory events, rdpmc, and branch skid Ian Rogers ` (3 subsequent siblings) 8 siblings, 0 replies; 13+ messages in thread From: Ian Rogers @ 2026-10-17 18:54 UTC (permalink / raw) To: irogers, acme, namhyung Cc: adrian.hunter, eranian, james.clark, jolsa, linux-kernel, linux-perf-users, mingo, peterz Add struct perf_pmu::probed and perf_pmu__probe() to actively probe live PMU capabilities via perf_event_open(), including counting/sampling/branch_stack modes, precise_ip levels, supported perf_event_sample_format flags, privilege exclusion modifiers, execution features (leader sampling, software grouping, auto-counter reload, AUX pause/resume/sample), and event group size limits. Signed-off-by: Ian Rogers <irogers@google.com> --- tools/perf/util/pmu.c | 1225 +++++++++++++++++++++++++++++++++++++++++ tools/perf/util/pmu.h | 134 ++++- 2 files changed, 1358 insertions(+), 1 deletion(-) diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c index cd8211f87291..83c8d6f392ba 100644 --- a/tools/perf/util/pmu.c +++ b/tools/perf/util/pmu.c @@ -9,6 +9,7 @@ #include <sys/stat.h> #include <unistd.h> #include <errno.h> +#include <limits.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> @@ -38,7 +39,21 @@ #include "strbuf.h" #include "fncache.h" #include "util/evsel_config.h" +#include "cloexec.h" +#include "dwarf-regs.h" +#include "evlist.h" +#include "mem-events.h" +#include "mmap.h" +#include "perf_regs.h" +#include "rlimit.h" +#include "util/sample.h" +#include "../perf-sys.h" +#include <linux/hw_breakpoint.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sched.h> #include <regex.h> +#include <elf.h> #define UNIT_MAX_LEN 31 /* max length for event unit name */ @@ -2832,9 +2847,1219 @@ void perf_pmu__delete(struct perf_pmu *pmu) zfree(&pmu->name); zfree(&pmu->alias_name); zfree(&pmu->id); + zfree(&pmu->probed.probe_event); free(pmu); } +#define MAX_PROBE_GROUP_SIZE 64 +#define MAX_PROBE_CANDIDATES 16 + +static unsigned long dummy_bp_var; + +static bool get_self_uprobe_target(char *exe_path, __u64 *out_offset) +{ +#if __SIZEOF_POINTER__ == 8 + Elf64_Ehdr ehdr; +#else + Elf32_Ehdr ehdr; +#endif + int fd; + bool found = false; + + if (!realpath("/proc/self/exe", exe_path)) + return false; + + fd = open(exe_path, O_RDONLY | O_CLOEXEC); + if (fd < 0) + return false; + + if (pread(fd, &ehdr, sizeof(ehdr), 0) != sizeof(ehdr) || + memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0) { + close(fd); + return false; + } + + for (unsigned int i = 0; i < ehdr.e_phnum; i++) { +#if __SIZEOF_POINTER__ == 8 + Elf64_Phdr phdr; +#else + Elf32_Phdr phdr; +#endif + off_t pos = (off_t)ehdr.e_phoff + (off_t)i * ehdr.e_phentsize; + + if (ehdr.e_phentsize < sizeof(phdr)) + break; + + if (pread(fd, &phdr, sizeof(phdr), pos) != sizeof(phdr)) + break; + if (phdr.p_type != PT_LOAD || !(phdr.p_flags & PF_X)) + continue; + if (ehdr.e_entry >= phdr.p_vaddr && + ehdr.e_entry < phdr.p_vaddr + phdr.p_filesz) { + *out_offset = phdr.p_offset + (ehdr.e_entry - phdr.p_vaddr); + found = true; + break; + } + } + + close(fd); + return found; +} + +struct probe_candidates { + char *names[MAX_PROBE_CANDIDATES]; + size_t nr; +}; + +static void probe_candidates__add(struct probe_candidates *cands, const char *str) +{ + if (cands->nr >= MAX_PROBE_CANDIDATES || !str) + return; + for (size_t i = 0; i < cands->nr; i++) { + if (!strcmp(cands->names[i], str)) + return; + } + cands->names[cands->nr] = strdup(str); + if (cands->names[cands->nr]) + cands->nr++; +} + +static void probe_candidates__exit(struct probe_candidates *cands) +{ + for (size_t i = 0; i < cands->nr; i++) + zfree(&cands->names[i]); + cands->nr = 0; +} + +static int collect_probe_candidate_cb(void *vstate, struct pmu_event_info *info) +{ + struct probe_candidates *cands = vstate; + char buf[512]; + + if (info->deprecated) + return 0; + if (info->str && strstr(info->str, "=?")) + return 0; + if (!info->name) + return 0; + + if (info->pmu && info->pmu->type == PERF_TYPE_TRACEPOINT) { + probe_candidates__add(cands, info->name); + return 1; + } else if (strchr(info->name, '/')) { + probe_candidates__add(cands, info->name); + } else if (info->pmu && info->pmu->name) { + snprintf(buf, sizeof(buf), "%s/%s/", info->pmu->name, info->name); + probe_candidates__add(cands, buf); + } else { + probe_candidates__add(cands, info->name); + } + + return cands->nr >= MAX_PROBE_CANDIDATES ? 1 : 0; +} + +static bool parse_candidate_attr(const char *event_str, struct perf_event_attr *attr) +{ + struct evlist *evlist; + struct parse_events_error err; + struct evsel *evsel; + int ret; + + evlist = evlist__new(); + if (!evlist) + return false; + + parse_events_error__init(&err); + ret = __parse_events(evlist, event_str, /*pmu_filter=*/NULL, + /*cputype_filter=*/false, &err, /*fake_pmu=*/false, + /*warn_if_reordered=*/false, /*fake_tp=*/false); + parse_events_error__exit(&err); + if (ret || evlist__empty(evlist)) { + evlist__put(evlist); + return false; + } + + evsel = evlist__first(evlist); + *attr = evsel->core.attr; + evlist__put(evlist); + return true; +} + +static int select_probe_cpu(const struct perf_pmu *pmu) +{ + struct perf_cpu_map *online = perf_cpu_map__new_online_cpus(); + int cpu = 0; + + if (pmu->cpus && !perf_cpu_map__is_empty(pmu->cpus)) { + int nr = perf_cpu_map__nr(pmu->cpus); + + cpu = perf_cpu_map__cpu(pmu->cpus, 0).cpu; + if (online) { + for (int i = 0; i < nr; i++) { + struct perf_cpu c = perf_cpu_map__cpu(pmu->cpus, i); + + if (perf_cpu_map__has(online, c)) { + cpu = c.cpu; + break; + } + } + } + } else if (online && !perf_cpu_map__is_empty(online)) { + cpu = perf_cpu_map__cpu(online, 0).cpu; + } + + perf_cpu_map__put(online); + return cpu >= 0 ? cpu : 0; +} + +static bool try_open_attr_variants(struct perf_event_attr *attr, + bool is_uncore, int pmu_cpu, + unsigned long flags, + pid_t *out_pid, int *out_cpu, + int *last_err) +{ + pid_t pids[2]; + size_t nr_pids = 2; + + if (is_uncore) { + pids[0] = -1; + pids[1] = 0; + } else { + pids[0] = 0; + pids[1] = -1; + } + + for (size_t p = 0; p < nr_pids; p++) { + pid_t pid = pids[p]; + + for (int samp = 0; samp < 2; samp++) { + for (int priv = 0; priv < 4; priv++) { + struct perf_event_attr try_attr = *attr; + int fd; + + try_attr.size = sizeof(try_attr); + try_attr.disabled = 1; + try_attr.read_format = 0; + if (samp == 0) { + try_attr.sample_period = 0; + try_attr.freq = 0; + try_attr.sample_type = 0; + } else { + try_attr.sample_period = 10000; + try_attr.freq = 0; + try_attr.sample_type = PERF_SAMPLE_IP; + } + + if (priv >= 1) + try_attr.exclude_kernel = 1; + if (priv >= 2) + try_attr.exclude_hv = 1; + if (priv >= 3) + try_attr.exclude_guest = 1; + + fd = sys_perf_event_open(&try_attr, pid, pmu_cpu, -1, flags); + if (fd >= 0) { + close(fd); + *attr = try_attr; + *out_pid = pid; + *out_cpu = pmu_cpu; + return true; + } + if (*last_err == 0 || *last_err == ENOENT || *last_err == EINVAL) + *last_err = errno; + } + } + } + return false; +} + +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; +} + +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_open, bool *open_capped, + int *max_active, bool *active_capped) +{ + struct perf_event_attr leader_attr; + int leader_fd; + int fds[MAX_PROBE_GROUP_SIZE]; + int nr_followers = 0; + bool has_time_read = true; + bool active_ok = true; + enum rlimit_action set_rlimit = NO_CHANGE; + cpu_set_t saved_affinity, probe_affinity; + bool affinity_changed = false; + + *max_open = 0; + *open_capped = false; + *max_active = 0; + *active_capped = false; + + if (!attrs || nr_attrs == 0) + return; + + if (pid == 0 && cpu >= 0 && cpu < CPU_SETSIZE) { + CPU_ZERO(&saved_affinity); + if (sched_getaffinity(0, sizeof(saved_affinity), &saved_affinity) == 0) { + CPU_ZERO(&probe_affinity); + CPU_SET(cpu, &probe_affinity); + if (sched_setaffinity(0, sizeof(probe_affinity), &probe_affinity) == 0) + affinity_changed = true; + } + } + + leader_attr = attrs[0]; + leader_attr.disabled = 1; + leader_attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING; + + leader_fd = sys_perf_event_open(&leader_attr, pid, cpu, -1, flags); + if (leader_fd < 0) { + leader_attr.read_format = 0; + has_time_read = false; + leader_fd = sys_perf_event_open(&leader_attr, pid, cpu, -1, flags); + } + if (leader_fd < 0) + goto out_affinity; + + *max_open = 1; + if (has_time_read && check_group_active(leader_fd, -1)) + *max_active = 1; + else + active_ok = false; + + 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 && follower_attr.read_format != 0) { + 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; + *max_open = count; + if (active_ok && has_time_read) { + if (check_group_active(leader_fd, fd)) + *max_active = count; + else + active_ok = false; + } + } + + if (*max_open >= MAX_PROBE_GROUP_SIZE) + *open_capped = true; + if (*max_active >= MAX_PROBE_GROUP_SIZE) + *active_capped = true; + + for (int i = 0; i < nr_followers; i++) + close(fds[i]); + close(leader_fd); + +out_affinity: + if (affinity_changed) + sched_setaffinity(0, sizeof(saved_affinity), &saved_affinity); +} + +enum probe_exclude_flag { + PROBE_EXCLUDE_USER, + PROBE_EXCLUDE_KERNEL, + PROBE_EXCLUDE_HV, + PROBE_EXCLUDE_IDLE, + PROBE_EXCLUDE_HOST, + PROBE_EXCLUDE_GUEST, +}; + +static bool test_exclude_flag(const struct perf_event_attr *base_attr, + pid_t pid, int cpu, unsigned long flags, + enum probe_exclude_flag which) +{ + struct perf_event_attr attr = *base_attr; + int fd; + + switch (which) { + case PROBE_EXCLUDE_USER: + attr.exclude_user = 1; + break; + case PROBE_EXCLUDE_KERNEL: + attr.exclude_kernel = 1; + attr.exclude_user = 0; + break; + case PROBE_EXCLUDE_HV: + attr.exclude_hv = 1; + break; + case PROBE_EXCLUDE_IDLE: + attr.exclude_idle = 1; + break; + case PROBE_EXCLUDE_HOST: + attr.exclude_host = 1; + attr.exclude_guest = 0; + break; + case PROBE_EXCLUDE_GUEST: + attr.exclude_guest = 1; + attr.exclude_host = 0; + break; + default: + return false; + } + + fd = sys_perf_event_open(&attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + return true; + } + if (which == PROBE_EXCLUDE_USER && attr.exclude_kernel == 1) { + attr.exclude_kernel = 0; + fd = sys_perf_event_open(&attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + return true; + } + } + return false; +} + +static void probe_sample_types(struct perf_pmu *pmu, + const struct perf_event_attr *base_attr, + pid_t pid, int cpu, unsigned long flags) +{ + u64 reg_bit = BIT_ULL(perf_arch_reg_ip(EM_HOST)) ?: 1ULL; + u64 bulk_mask = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME | + PERF_SAMPLE_ADDR | PERF_SAMPLE_READ | PERF_SAMPLE_CALLCHAIN | + PERF_SAMPLE_ID | PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD | + PERF_SAMPLE_STREAM_ID | PERF_SAMPLE_RAW | PERF_SAMPLE_REGS_USER | + PERF_SAMPLE_STACK_USER | PERF_SAMPLE_WEIGHT | PERF_SAMPLE_DATA_SRC | + PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_TRANSACTION | + PERF_SAMPLE_REGS_INTR | PERF_SAMPLE_CGROUP | + PERF_SAMPLE_DATA_PAGE_SIZE | PERF_SAMPLE_CODE_PAGE_SIZE; + struct perf_event_attr b_attr = *base_attr; + int b_fd; + + b_attr.disabled = 1; + b_attr.sample_period = base_attr->sample_period ?: 10000; + b_attr.sample_type = bulk_mask; + b_attr.sample_regs_user = reg_bit; + b_attr.sample_regs_intr = reg_bit; + b_attr.sample_stack_user = 64; + b_attr.exclude_callchain_kernel = b_attr.exclude_kernel; + b_attr.inherit = 0; + b_fd = sys_perf_event_open(&b_attr, pid, cpu, -1, flags); + if (b_fd >= 0) { + close(b_fd); + pmu->probed.sample_types |= bulk_mask; + } + + for (unsigned int shift = 0; (1ULL << shift) < PERF_SAMPLE_MAX; shift++) { + u64 bit = 1ULL << shift; + struct perf_event_attr attr = *base_attr; + int fd; + + if (pmu->probed.sample_types & bit) + continue; + + attr.disabled = 1; + attr.sample_period = base_attr->sample_period ?: 10000; + attr.sample_type = bit; + + switch (bit) { + case PERF_SAMPLE_BRANCH_STACK: + attr.branch_sample_type = PERF_SAMPLE_BRANCH_ANY | + (attr.exclude_kernel ? PERF_SAMPLE_BRANCH_USER : 0); + break; + case PERF_SAMPLE_CALLCHAIN: + attr.exclude_callchain_kernel = attr.exclude_kernel; + break; + case PERF_SAMPLE_REGS_USER: + attr.sample_regs_user = reg_bit; + break; + case PERF_SAMPLE_REGS_INTR: + attr.sample_regs_intr = reg_bit; + break; + case PERF_SAMPLE_STACK_USER: + attr.sample_stack_user = 64; + break; + case PERF_SAMPLE_READ: + attr.inherit = 0; + break; + default: + break; + } + + fd = sys_perf_event_open(&attr, pid, cpu, -1, flags); + if (fd < 0 && bit == PERF_SAMPLE_BRANCH_STACK) { + attr.branch_sample_type = PERF_SAMPLE_BRANCH_ANY; + fd = sys_perf_event_open(&attr, pid, cpu, -1, flags); + } + if (fd < 0 && bit != PERF_SAMPLE_IP) { + attr.sample_type = PERF_SAMPLE_IP | bit; + fd = sys_perf_event_open(&attr, pid, cpu, -1, flags); + } + if (fd >= 0) { + close(fd); + pmu->probed.sample_types |= bit; + } + } + + pmu->probed.can_branch_stack = !!(pmu->probed.sample_types & PERF_SAMPLE_BRANCH_STACK); + pmu->probed.can_callchain = !!(pmu->probed.sample_types & PERF_SAMPLE_CALLCHAIN); + pmu->probed.can_data_src = !!(pmu->probed.sample_types & PERF_SAMPLE_DATA_SRC); + pmu->probed.can_weight = !!(pmu->probed.sample_types & PERF_SAMPLE_WEIGHT); +} + +void perf_pmu__probe(struct perf_pmu *pmu) +{ + struct probe_candidates cands = { .nr = 0 }; + struct perf_event_attr base_attr; + struct perf_event_attr group_attrs[MAX_PROBE_GROUP_SIZE]; + char uprobe_exe_path[PATH_MAX]; + __u64 uprobe_offset = 0; + size_t nr_group_attrs = 0; + unsigned long flags = perf_event_open_cloexec_flag(); + int keepalive_fd = -1; + int pmu_cpu; + pid_t pid = 0; + int cpu = 0; + int last_err = 0; + bool found = false; + + if (!pmu || pmu->from_env || pmu->probed.checked) + return; + if (perf_pmu__kind(pmu) != PERF_PMU_KIND_PE) + return; + + pmu_cpu = select_probe_cpu(pmu); + + if (pmu->type == PERF_TYPE_TRACEPOINT) { + static const struct { + const char *sys; + const char *name; + } fast_tps[] = { + { "sched", "sched_switch" }, + { "raw_syscalls", "sys_enter" }, + { "irq", "irq_handler_entry" }, + }; + + for (size_t i = 0; i < ARRAY_SIZE(fast_tps); i++) { + int tp_id = tp_pmu__id(fast_tps[i].sys, fast_tps[i].name); + + if (tp_id >= 0) { + memset(&base_attr, 0, sizeof(base_attr)); + base_attr.type = PERF_TYPE_TRACEPOINT; + base_attr.config = tp_id; + if (try_open_attr_variants(&base_attr, false, pmu_cpu, + flags, &pid, &cpu, &last_err)) { + char tp_buf[128]; + + snprintf(tp_buf, sizeof(tp_buf), "%s:%s", + fast_tps[i].sys, fast_tps[i].name); + pmu->probed.probe_event = strdup(tp_buf); + found = true; + break; + } + } + } + if (!found) { + perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/false, + &cands, collect_probe_candidate_cb); + } + } else if (pmu->name && !strcmp(pmu->name, "kprobe")) { + static const char * const kprobe_funcs[] = { + "vfs_read", + "schedule", + "do_sys_openat2", + }; + + for (size_t i = 0; i < ARRAY_SIZE(kprobe_funcs); i++) { + memset(&base_attr, 0, sizeof(base_attr)); + base_attr.type = pmu->type; + base_attr.size = sizeof(base_attr); + base_attr.kprobe_func = (uintptr_t)kprobe_funcs[i]; + base_attr.probe_offset = 0; + if (try_open_attr_variants(&base_attr, false, pmu_cpu, + flags, &pid, &cpu, &last_err)) { + char kp_buf[128]; + + snprintf(kp_buf, sizeof(kp_buf), "kprobe:%s", + kprobe_funcs[i]); + pmu->probed.probe_event = strdup(kp_buf); + found = true; + break; + } + } + } else if (pmu->name && !strcmp(pmu->name, "uprobe")) { + if (get_self_uprobe_target(uprobe_exe_path, &uprobe_offset)) { + memset(&base_attr, 0, sizeof(base_attr)); + base_attr.type = pmu->type; + base_attr.size = sizeof(base_attr); + base_attr.uprobe_path = (uintptr_t)uprobe_exe_path; + base_attr.probe_offset = uprobe_offset; + if (try_open_attr_variants(&base_attr, false, pmu_cpu, + flags, &pid, &cpu, &last_err)) { + const char *slash = strrchr(uprobe_exe_path, '/'); + const char *bname = slash ? slash + 1 : uprobe_exe_path; + + if (asprintf(&pmu->probed.probe_event, "uprobe:%s+0x%llx", + bname, (unsigned long long)uprobe_offset) < 0) + pmu->probed.probe_event = NULL; + found = true; + } + } + } else if (pmu->is_core) { + static const char * const core_events[] = { + "cycles", + "instructions", + "branches", + "branch-misses", + "cache-misses", + "cache-references", + "bus-cycles", + "ref-cycles", + }; + char buf[256]; + + for (size_t i = 0; i < ARRAY_SIZE(core_events); i++) { + snprintf(buf, sizeof(buf), "%s/%s/", pmu->name, core_events[i]); + probe_candidates__add(&cands, buf); + } + perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/false, + &cands, collect_probe_candidate_cb); + } else if (pmu->type == PERF_TYPE_SOFTWARE) { + static const char * const sw_events[] = { + "software/cpu-clock/", + "software/task-clock/", + "software/page-faults/", + "software/context-switches/", + "software/cpu-migrations/", + "software/minor-faults/", + "software/major-faults/", + "software/dummy/", + }; + + for (size_t i = 0; i < ARRAY_SIZE(sw_events); i++) + probe_candidates__add(&cands, sw_events[i]); + } else if (pmu->type != PERF_TYPE_BREAKPOINT) { + if (pmu->selectable) { + char buf[256]; + + if (perf_pmu__has_format(pmu, "swfilt")) { + snprintf(buf, sizeof(buf), "%s/swfilt=1/", pmu->name); + probe_candidates__add(&cands, buf); + } + snprintf(buf, sizeof(buf), "%s//", pmu->name); + probe_candidates__add(&cands, buf); + } + perf_pmu__for_each_event(pmu, /*skip_duplicate_pmus=*/false, + &cands, collect_probe_candidate_cb); + } + + for (size_t i = 0; !found && i < cands.nr; i++) { + memset(&base_attr, 0, sizeof(base_attr)); + if (!parse_candidate_attr(cands.names[i], &base_attr)) + continue; + + if (try_open_attr_variants(&base_attr, pmu->is_uncore, pmu_cpu, + flags, &pid, &cpu, &last_err)) { + pmu->probed.probe_event = strdup(cands.names[i]); + found = true; + break; + } + } + + if (!found && pmu->is_core) { + memset(&base_attr, 0, sizeof(base_attr)); + base_attr.type = PERF_TYPE_HARDWARE; + base_attr.config = PERF_COUNT_HW_CPU_CYCLES; + if (perf_pmus__supports_extended_type()) + base_attr.config |= (__u64)pmu->type << PERF_PMU_TYPE_SHIFT; + if (try_open_attr_variants(&base_attr, false, pmu_cpu, + flags, &pid, &cpu, &last_err)) { + pmu->probed.probe_event = strdup("cycles"); + found = true; + } + } + + if (!found && pmu->type == PERF_TYPE_BREAKPOINT) { + memset(&base_attr, 0, sizeof(base_attr)); + base_attr.type = PERF_TYPE_BREAKPOINT; + base_attr.bp_addr = (unsigned long)&dummy_bp_var; + base_attr.bp_type = HW_BREAKPOINT_W; + base_attr.bp_len = HW_BREAKPOINT_LEN_4; + if (try_open_attr_variants(&base_attr, false, pmu_cpu, + flags, &pid, &cpu, &last_err)) { + pmu->probed.probe_event = strdup("mem:<addr>:w"); + found = true; + } + } + + if (!found && cands.nr == 0) { + memset(&base_attr, 0, sizeof(base_attr)); + base_attr.type = pmu->type; + base_attr.size = sizeof(base_attr); + if (pmu->perf_event_attr_init_default) + pmu->perf_event_attr_init_default(pmu, &base_attr); + if (try_open_attr_variants(&base_attr, pmu->is_uncore, pmu_cpu, + flags, &pid, &cpu, &last_err)) { + char buf[256]; + + snprintf(buf, sizeof(buf), "%s//", pmu->name); + pmu->probed.probe_event = strdup(buf); + found = true; + } + } + + if (!found) { + probe_candidates__exit(&cands); + pmu->probed.open_supported = false; + pmu->probed.open_err = last_err ?: ENOENT; + pmu->probed.checked = true; + return; + } + + pmu->probed.open_supported = true; + + /* + * For tracepoint events, closing the last fd for a tracepoint triggers + * tracepoint_synchronize_unregister() (an RCU grace period) in the + * kernel. Hold one disabled fd open across the probe sequence so + * tp_event->perf_refcount stays >= 1 and each probe open/close is + * instantaneous. + */ + if (pmu->type == PERF_TYPE_TRACEPOINT) { + struct perf_event_attr ka_attr = base_attr; + + ka_attr.disabled = 1; + keepalive_fd = sys_perf_event_open(&ka_attr, pid, cpu, -1, flags); + } + + group_attrs[nr_group_attrs++] = base_attr; + + for (size_t i = 0; pmu->type != PERF_TYPE_TRACEPOINT && + i < cands.nr && nr_group_attrs < MAX_PROBE_GROUP_SIZE; i++) { + struct perf_event_attr cand_attr; + int fd; + bool dup = false; + + memset(&cand_attr, 0, sizeof(cand_attr)); + if (!parse_candidate_attr(cands.names[i], &cand_attr)) + continue; + + cand_attr.sample_period = base_attr.sample_period; + cand_attr.freq = base_attr.freq; + cand_attr.sample_type = base_attr.sample_type; + cand_attr.exclude_user = base_attr.exclude_user; + cand_attr.exclude_kernel = base_attr.exclude_kernel; + cand_attr.exclude_hv = base_attr.exclude_hv; + cand_attr.exclude_idle = base_attr.exclude_idle; + cand_attr.exclude_host = base_attr.exclude_host; + cand_attr.exclude_guest = base_attr.exclude_guest; + cand_attr.disabled = 1; + + for (size_t j = 0; j < nr_group_attrs; j++) { + if (group_attrs[j].type == cand_attr.type && + group_attrs[j].config == cand_attr.config && + group_attrs[j].config1 == cand_attr.config1 && + group_attrs[j].config2 == cand_attr.config2) { + dup = true; + break; + } + } + if (dup) + continue; + + fd = sys_perf_event_open(&cand_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + group_attrs[nr_group_attrs++] = cand_attr; + } + } + + probe_candidates__exit(&cands); + + /* 1. Counting vs Sampling support */ + if (base_attr.sample_period == 0) { + struct perf_event_attr samp_attr = base_attr; + int fd; + + pmu->probed.can_count = true; + samp_attr.sample_period = 10000; + samp_attr.sample_type = PERF_SAMPLE_IP; + fd = sys_perf_event_open(&samp_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + pmu->probed.can_sample = true; + } + } else { + pmu->probed.can_count = false; + pmu->probed.can_sample = true; + } + + /* 2. All enum perf_event_sample_format types */ + if (pmu->probed.can_sample) + probe_sample_types(pmu, &base_attr, pid, cpu, flags); + + /* + * 3. Precise sampling support (:p, :pp, :ppp). + * PMUs that do not validate attr.precise_ip (such as software, + * breakpoint, or intel_pt) silently accept precise_ip > 0 even when + * sample_period == 0. A PMU that genuinely implements precise_ip + * either rejects precise_ip > 0 on non-sampling events or is a + * sampling-only PMU (such as AMD ibs_op). + * Note: On AMD, core events like cycles:P are forwarded to IBS and + * support higher precision than advertised in sysfs caps/max_precise. + */ + if (pmu->probed.can_sample && !pmu->auxtrace) { + bool ignores_precise_ip = false; + + if (pmu->probed.can_count) { + struct perf_event_attr c_attr = base_attr; + int fd; + + c_attr.sample_period = 0; + c_attr.freq = 0; + c_attr.sample_type = 0; + c_attr.precise_ip = 1; + fd = sys_perf_event_open(&c_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + ignores_precise_ip = true; + } + } + + if (!ignores_precise_ip) { + for (int level = 1; level <= 3; level++) { + struct perf_event_attr p_attr = base_attr; + int fd; + + p_attr.sample_period = 10000; + p_attr.sample_type = PERF_SAMPLE_IP; + p_attr.precise_ip = level; + fd = sys_perf_event_open(&p_attr, pid, cpu, -1, flags); + if (fd < 0 && p_attr.exclude_kernel && p_attr.config2 == 0) { + p_attr.config2 = 1; + fd = sys_perf_event_open(&p_attr, pid, cpu, -1, flags); + } + if (fd >= 0) { + close(fd); + pmu->probed.precise_mask |= (1u << level); + pmu->probed.max_precise = level; + } + } + } + + } + + /* 4. Privilege / exclusion modifiers */ + pmu->probed.exclude_user = + test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_USER); + pmu->probed.exclude_kernel = + test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_KERNEL); + pmu->probed.exclude_hv = + test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_HV); + pmu->probed.exclude_idle = + test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_IDLE); + pmu->probed.exclude_host = + test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_HOST); + pmu->probed.exclude_guest = + test_exclude_flag(&base_attr, pid, cpu, flags, PROBE_EXCLUDE_GUEST); + + /* 5. Execution features */ + if (pmu->probed.can_sample) { + struct perf_event_attr l_attr = base_attr; + struct perf_event_attr f_attr = nr_group_attrs > 1 ? group_attrs[1] : base_attr; + int l_fd; + + l_attr.disabled = 1; + l_attr.sample_period = 10000; + l_attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_READ; + l_attr.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID; + + f_attr.disabled = 0; + f_attr.sample_period = 0; + f_attr.sample_type = 0; + f_attr.read_format = 0; + + l_fd = sys_perf_event_open(&l_attr, pid, cpu, -1, flags); + if (l_fd >= 0) { + int f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags); + + if (f_fd >= 0) { + if (ioctl(l_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) >= 0) + pmu->probed.can_leader_sampling = true; + ioctl(l_fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP); + close(f_fd); + } + close(l_fd); + } + } + + { + struct perf_event_attr l_attr = base_attr; + int l_fd; + + l_attr.disabled = 1; + l_fd = sys_perf_event_open(&l_attr, pid, cpu, -1, flags); + if (l_fd >= 0) { + struct perf_event_attr sw_attr; + int sw_fd; + + memset(&sw_attr, 0, sizeof(sw_attr)); + sw_attr.type = PERF_TYPE_SOFTWARE; + sw_attr.size = sizeof(sw_attr); + sw_attr.config = PERF_COUNT_SW_DUMMY; + sw_attr.disabled = 0; + sw_attr.exclude_user = base_attr.exclude_user; + sw_attr.exclude_kernel = base_attr.exclude_kernel; + sw_attr.exclude_hv = base_attr.exclude_hv; + sw_attr.exclude_idle = base_attr.exclude_idle; + sw_attr.exclude_host = base_attr.exclude_host; + sw_attr.exclude_guest = base_attr.exclude_guest; + + sw_fd = sys_perf_event_open(&sw_attr, pid, cpu, l_fd, flags); + if (sw_fd < 0) { + sw_attr.config = PERF_COUNT_SW_CPU_CLOCK; + sw_fd = sys_perf_event_open(&sw_attr, pid, cpu, l_fd, flags); + } + if (sw_fd >= 0) { + pmu->probed.can_sw_grouping = true; + close(sw_fd); + } + close(l_fd); + } + } + + { + struct perf_event_attr inh_attr = base_attr; + int fd; + + inh_attr.disabled = 1; + inh_attr.inherit = 1; + fd = sys_perf_event_open(&inh_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + pmu->probed.can_inherit = true; + } + } + + if (pmu->probed.can_sample) { + struct perf_event_attr freq_attr = base_attr; + int fd; + + freq_attr.disabled = 1; + freq_attr.freq = 1; + freq_attr.sample_freq = 4000; + freq_attr.sample_type = PERF_SAMPLE_IP; + fd = sys_perf_event_open(&freq_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + pmu->probed.can_freq = true; + } + } + + if (pmu->probed.can_sample) { + struct perf_event_attr wb_attr = base_attr; + int fd; + + wb_attr.disabled = 1; + wb_attr.sample_period = 10000; + wb_attr.sample_type = PERF_SAMPLE_IP; + wb_attr.write_backward = 1; + fd = sys_perf_event_open(&wb_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + pmu->probed.can_write_backward = true; + } + } + + { + struct perf_event_attr pin_attr = base_attr; + int fd; + + pin_attr.disabled = 1; + pin_attr.pinned = 1; + fd = sys_perf_event_open(&pin_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + pmu->probed.can_pinned = true; + } + } + + { + struct perf_event_attr exc_attr = base_attr; + int fd; + + exc_attr.disabled = 1; + exc_attr.exclusive = 1; + fd = sys_perf_event_open(&exc_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + pmu->probed.can_exclusive = true; + } + } + + { + struct perf_event_attr lost_attr = base_attr; + int fd; + + lost_attr.disabled = 1; + lost_attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | + PERF_FORMAT_TOTAL_TIME_RUNNING | + PERF_FORMAT_LOST; + fd = sys_perf_event_open(&lost_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + pmu->probed.can_format_lost = true; + } + } + + /* 5c. Auto-counter reload (acr_mask / ratio-to-prev) */ + if (pmu->probed.can_sample && perf_pmu__has_format(pmu, "acr_mask")) { + struct perf_event_attr l_attr = base_attr; + struct perf_event_attr f_attr = nr_group_attrs > 1 ? group_attrs[1] : base_attr; + int l_fd; + + l_attr.disabled = 1; + l_attr.freq = 0; + l_attr.sample_period = 200000; + l_attr.sample_type = PERF_SAMPLE_IP; + l_attr.config2 = 0x2; + + f_attr.disabled = 0; + f_attr.freq = 0; + f_attr.sample_period = 100000; + f_attr.sample_type = PERF_SAMPLE_IP; + f_attr.config2 = 0x3; + + l_fd = sys_perf_event_open(&l_attr, pid, cpu, -1, flags); + if (l_fd >= 0) { + int f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags); + + if (f_fd >= 0) { + if (ioctl(l_fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) >= 0) + pmu->probed.can_acr = true; + ioctl(l_fd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP); + close(f_fd); + } + close(l_fd); + } + } + + /* 5d. AUX area event actions (start_paused, pause, resume, aux_sample) */ + if (pmu->auxtrace) { + struct perf_event_attr a_attr = base_attr; + int l_fd; + + a_attr.size = max_t(__u32, base_attr.size, PERF_ATTR_SIZE_VER8); + a_attr.disabled = 1; + a_attr.aux_start_paused = 1; + l_fd = sys_perf_event_open(&a_attr, pid, cpu, -1, flags); + if (l_fd >= 0) { + close(l_fd); + pmu->probed.can_aux_start_paused = true; + } + + a_attr = base_attr; + a_attr.disabled = 1; + l_fd = sys_perf_event_open(&a_attr, pid, cpu, -1, flags); + if (l_fd >= 0) { + struct perf_event_attr f_attr; + int f_fd; + + memset(&f_attr, 0, sizeof(f_attr)); + f_attr.type = PERF_TYPE_SOFTWARE; + f_attr.size = max_t(__u32, base_attr.size, PERF_ATTR_SIZE_VER8); + f_attr.config = PERF_COUNT_SW_CPU_CLOCK; + f_attr.sample_period = 10000; + f_attr.sample_type = PERF_SAMPLE_IP; + f_attr.exclude_user = base_attr.exclude_user; + f_attr.exclude_kernel = base_attr.exclude_kernel; + f_attr.exclude_hv = base_attr.exclude_hv; + f_attr.exclude_idle = base_attr.exclude_idle; + f_attr.exclude_host = base_attr.exclude_host; + f_attr.exclude_guest = base_attr.exclude_guest; + + f_attr.aux_pause = 1; + f_attr.aux_resume = 0; + f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags); + if (f_fd >= 0) { + close(f_fd); + pmu->probed.can_aux_pause = true; + } + + f_attr.aux_pause = 0; + f_attr.aux_resume = 1; + f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags); + if (f_fd >= 0) { + close(f_fd); + pmu->probed.can_aux_resume = true; + } + + f_attr.aux_resume = 0; + f_attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_AUX; + f_attr.aux_sample_size = 64; + f_fd = sys_perf_event_open(&f_attr, pid, cpu, l_fd, flags); + if (f_fd >= 0) { + close(f_fd); + pmu->probed.can_aux_sample = true; + } + + close(l_fd); + } + } + + /* 6. Max event group size & max simultaneously active group size */ + 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; + pmu->probed.max_active_group_size = MAX_PROBE_GROUP_SIZE; + pmu->probed.max_active_capped = true; + } else { + pmu->probed.max_group_size = 1; + pmu->probed.max_active_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, + &pmu->probed.max_active_group_size, + &pmu->probed.max_active_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.config = gp_configs[i]; + if (perf_pmus__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, + &pmu->probed.max_active_gp_group_size, + &pmu->probed.max_active_gp_capped); + } + } + + if (keepalive_fd >= 0) + close(keepalive_fd); + pmu->probed.checked = true; +} + const char *perf_pmu__name_from_config(struct perf_pmu *pmu, u64 config) { struct hashmap_entry *entry; diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h index adb33a9fc94b..6da57b167d71 100644 --- a/tools/perf/util/pmu.h +++ b/tools/perf/util/pmu.h @@ -209,6 +209,138 @@ struct perf_pmu { * @mem_events: List of the supported mem events */ struct perf_mem_event *mem_events; + + /** + * @probed: Capabilities discovered by probing perf_event_open() on live + * systems. + */ + struct { + /** @checked: Has perf_pmu__probe() been run on this PMU? */ + bool checked; + /** @open_supported: Could a probe event be opened on this PMU? */ + bool open_supported; + /** @can_count: Supports counting (sample_period == 0). */ + bool can_count; + /** @can_sample: Supports interrupt-based sampling (sample_period > 0). */ + bool can_sample; + /** @can_branch_stack: Supports PERF_SAMPLE_BRANCH_STACK. */ + bool can_branch_stack; + /** @can_leader_sampling: Supports group leader sampling (:S). */ + bool can_leader_sampling; + /** @can_sw_grouping: Supports grouping software events with this PMU's events. */ + bool can_sw_grouping; + /** @can_inherit: Supports perf_event_attr::inherit across child tasks. */ + bool can_inherit; + /** @can_freq: Supports dynamic sample frequency (perf_event_attr::freq). */ + bool can_freq; + /** @can_write_backward: Supports overwrite ring buffer (write_backward). */ + bool can_write_backward; + /** @can_callchain: Supports PERF_SAMPLE_CALLCHAIN. */ + bool can_callchain; + /** @can_data_src: Supports PERF_SAMPLE_DATA_SRC. */ + bool can_data_src; + /** @can_weight: Supports PERF_SAMPLE_WEIGHT. */ + bool can_weight; + /** @can_pinned: Supports perf_event_attr::pinned. */ + bool can_pinned; + /** @can_exclusive: Supports perf_event_attr::exclusive. */ + bool can_exclusive; + /** @can_format_lost: Supports PERF_FORMAT_LOST. */ + bool can_format_lost; + /** @can_acr: Supports auto-counter reload (acr_mask / ratio-to-prev). */ + bool can_acr; + /** @can_aux_start_paused: Supports aux_start_paused (PERF_PMU_CAP_AUX_PAUSE). */ + bool can_aux_start_paused; + /** @can_aux_pause: Supports aux_pause action on group followers. */ + bool can_aux_pause; + /** @can_aux_resume: Supports aux_resume action on group followers. */ + bool can_aux_resume; + /** @can_aux_sample: Supports AUX area snapshot sampling (aux_sample_size). */ + bool can_aux_sample; + /** @rdpmc_available: PMU supports user-space counter reading (rdpmc/mrs/csrr). */ + bool rdpmc_available; + /** @can_user_rdpmc: User-space counter reading enabled (cap_user_rdpmc & index). */ + bool can_user_rdpmc; + /** @rdpmc_works: User-space counter read instruction executed and worked. */ + bool rdpmc_works; + /** @can_user_time: Supports user-space time conversion (cap_user_time). */ + bool can_user_time; + /** @can_user_time_zero: Supports cap_user_time_zero in mmap page. */ + bool can_user_time_zero; + /** @can_user_time_short: Supports cap_user_time_short in mmap page. */ + bool can_user_time_short; + /** @exclude_user: Supports perf_event_attr::exclude_user. */ + bool exclude_user; + /** @exclude_kernel: Supports perf_event_attr::exclude_kernel. */ + bool exclude_kernel; + /** @exclude_hv: Supports perf_event_attr::exclude_hv. */ + bool exclude_hv; + /** @exclude_idle: Supports perf_event_attr::exclude_idle. */ + bool exclude_idle; + /** @exclude_host: Supports perf_event_attr::exclude_host. */ + bool exclude_host; + /** @exclude_guest: Supports perf_event_attr::exclude_guest. */ + bool exclude_guest; + /** @max_group_capped: Was max_group_size capped at probe limit? */ + bool max_group_capped; + /** @max_active_capped: Was max_active_group_size capped at probe limit? */ + bool max_active_capped; + /** @max_gp_group_capped: Was max_gp_group_size capped at probe limit? */ + bool max_gp_group_capped; + /** @max_active_gp_capped: Was max_active_gp_group_size capped at probe limit? */ + bool max_active_gp_capped; + /** @probe_event: Name of the event used to probe the PMU. */ + char *probe_event; + /** @open_err: errno if open_supported is false. */ + int open_err; + /** @max_precise: Maximum precise_ip level (0..3) supported. */ + int max_precise; + /** @precise_mask: Bitmask of supported precise_ip levels (1..3). */ + unsigned int precise_mask; + /** @max_group_size: Max events in a group accepted by perf_event_open(). */ + int max_group_size; + /** @max_active_group_size: Max events in a group active simultaneously. */ + int max_active_group_size; + /** @max_gp_group_size: Max GP events in a group accepted by perf_event_open(). */ + int max_gp_group_size; + /** @max_active_gp_group_size: Max GP events in a group active simultaneously. */ + int max_active_gp_group_size; + /** @user_regs_abi: ABI flags for user_regs_mask. */ + int user_regs_abi; + /** @intr_regs_abi: ABI flags for intr_regs_mask. */ + int intr_regs_abi; + /** @rdpmc_sysfs: Value from sysfs rdpmc or perf_user_access (-1 if unknown). */ + int rdpmc_sysfs; + /** @mem_events_mask: Bitmask of memory event types verified by probing. */ + unsigned int mem_events_mask; + /** @pmc_width: Bit-width of hardware counter for user-space rdpmc. */ + u16 pmc_width; + /** @sample_types: Bitmask of supported enum perf_event_sample_format flags. */ + u64 sample_types; + /** @mem_sample_types: Bitmask of memory sample formats verified on mem_events. */ + u64 mem_sample_types; + /** @user_regs_mask: Bitmask of supported sample_regs_user registers. */ + u64 user_regs_mask; + /** @intr_regs_mask: Bitmask of supported sample_regs_intr registers. */ + u64 intr_regs_mask; + /** @user_simd_mask: Bitmask of supported user SIMD vector register classes. */ + u64 user_simd_mask; + /** @user_pred_mask: Bitmask of supported user SIMD predicate register classes. */ + u64 user_pred_mask; + /** @intr_simd_mask: Bitmask of supported intr SIMD vector register classes. */ + u64 intr_simd_mask; + /** @intr_pred_mask: Bitmask of supported intr SIMD predicate register classes. */ + u64 intr_pred_mask; + /** @skid: Measured branch instruction skid at precise_ip levels (0..3). */ + struct { + bool measured; + int samples; + int exact_samples; + int min_skid; + int max_skid; + double avg_skid; + } skid[4]; + } probed; }; struct perf_pmu_info { @@ -332,8 +464,8 @@ int perf_pmu__convert_scale(const char *scale, char **end, double *sval); struct perf_pmu_caps *perf_pmu__get_cap(struct perf_pmu *pmu, const char *name); int perf_pmu__add_cap(struct perf_pmu *pmu, const char *name, const char *value); - int perf_pmu__caps_parse(struct perf_pmu *pmu); +void perf_pmu__probe(struct perf_pmu *pmu); void perf_pmu__warn_invalid_config(struct perf_pmu *pmu, __u64 config, const char *name, int config_num, -- 2.56.0.212.g129ede20d9-goog ^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v2 6/9] perf pmu: Probe registers, memory events, rdpmc, and branch skid 2026-10-17 18:54 ` [RFC PATCH v2 0/9] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers ` (4 preceding siblings ...) 2026-10-17 18:54 ` [RFC PATCH v2 5/9] perf pmu: Add perf_pmu__probe() for live capability probing Ian Rogers @ 2026-10-17 18:54 ` Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 7/9] perf sysinfo: Add sysinfo subcommand Ian Rogers ` (2 subsequent siblings) 8 siblings, 0 replies; 13+ messages in thread From: Ian Rogers @ 2026-10-17 18:54 UTC (permalink / raw) To: irogers, acme, namhyung Cc: adrian.hunter, eranian, james.clark, jolsa, linux-kernel, linux-perf-users, mingo, peterz Extend perf_pmu__probe() to probe supported user and interrupt sampling register masks (both general-purpose and SIMD), memory profiling events and sample formats, user-space mmap counter reading (rdpmc) and PMC bit- width, and empirically measured branch instruction skid across precision levels. Signed-off-by: Ian Rogers <irogers@google.com> --- tools/perf/util/pmu.c | 758 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 758 insertions(+) diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c index 83c8d6f392ba..15a21f8c65ed 100644 --- a/tools/perf/util/pmu.c +++ b/tools/perf/util/pmu.c @@ -3356,6 +3356,739 @@ static void probe_sample_types(struct perf_pmu *pmu, pmu->probed.can_weight = !!(pmu->probed.sample_types & PERF_SAMPLE_WEIGHT); } +static void probe_pmu_regs_one(const struct perf_pmu *pmu, + const struct perf_event_attr *base_attr, + pid_t pid, int cpu, unsigned long flags, + int max_precise, bool intr, + u64 *out_mask, int *out_abi, + u64 *out_simd_mask, u64 *out_pred_mask) +{ + int dummy_abi = -1; + u64 gpr_mask = intr ? perf_intr_reg_mask(EM_HOST, &dummy_abi) : + perf_user_reg_mask(EM_HOST, &dummy_abi); + u64 sample_type = intr ? PERF_SAMPLE_REGS_INTR : PERF_SAMPLE_REGS_USER; + u64 mask = 0; + int abi = 0; + struct perf_event_attr attr = *base_attr; + int fd; + + attr.disabled = 1; + attr.sample_period = base_attr->sample_period ?: 10000; + attr.sample_type = sample_type; + + if (gpr_mask) { + if (intr) + attr.sample_regs_intr = gpr_mask; + else + attr.sample_regs_user = gpr_mask; + + fd = sys_perf_event_open(&attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + mask |= gpr_mask; + } else { + for (int b = 0; b < 64; b++) { + if (!(gpr_mask & BIT_ULL(b))) + continue; + if (intr) + attr.sample_regs_intr = BIT_ULL(b); + else + attr.sample_regs_user = BIT_ULL(b); + fd = sys_perf_event_open(&attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + mask |= BIT_ULL(b); + } + } + } + } + + if (perf_pmu__is_software(pmu)) { + *out_mask = mask; + *out_abi = abi; + return; + } + + /* + * Probe extended GPRs outside the base GPR mask when the host ABI + * supports PERF_SAMPLE_REGS_ABI_SIMD (e.g. APX R16-R31 and CET SSP). + * Note: perf_pmu__probe() actively probes the running host kernel via + * perf_event_open(), so EM_HOST is used intentionally here. + */ + { + int host_abi = 0; + u64 host_mask = intr ? perf_intr_reg_mask(EM_HOST, &host_abi) : + perf_user_reg_mask(EM_HOST, &host_abi); + u64 ext_gpr_mask = host_mask & ~gpr_mask; + + if ((host_abi & PERF_SAMPLE_REGS_ABI_SIMD) && ext_gpr_mask) { + struct perf_event_attr e_attr = *base_attr; + bool ext_batch_ok = false; + + e_attr.size = sizeof(e_attr); + e_attr.disabled = 1; + e_attr.sample_period = base_attr->sample_period ?: 10000; + e_attr.sample_type = sample_type; + e_attr.sample_simd_regs_enabled = 1; + if (intr) + e_attr.sample_regs_intr = ext_gpr_mask; + else + e_attr.sample_regs_user = ext_gpr_mask; + + fd = sys_perf_event_open(&e_attr, pid, cpu, -1, flags); + if (fd < 0 && intr && max_precise > 0) { + e_attr.precise_ip = 1; + fd = sys_perf_event_open(&e_attr, pid, cpu, -1, flags); + } + if (fd >= 0) { + close(fd); + mask |= ext_gpr_mask; + abi |= PERF_SAMPLE_REGS_ABI_SIMD; + ext_batch_ok = true; + } + + if (!ext_batch_ok) { + for (int b = 0; b < 64; b++) { + if (!(ext_gpr_mask & BIT_ULL(b))) + continue; + e_attr.precise_ip = base_attr->precise_ip; + if (intr) + e_attr.sample_regs_intr = BIT_ULL(b); + else + e_attr.sample_regs_user = BIT_ULL(b); + fd = sys_perf_event_open(&e_attr, pid, cpu, -1, flags); + if (fd < 0 && intr && max_precise > 0) { + e_attr.precise_ip = 1; + fd = sys_perf_event_open(&e_attr, pid, cpu, -1, + flags); + } + if (fd >= 0) { + close(fd); + mask |= BIT_ULL(b); + abi |= PERF_SAMPLE_REGS_ABI_SIMD; + } + } + } + } + } + + for (int is_pred = 0; is_pred <= 1; is_pred++) { + bool pred = is_pred != 0; + u64 class_mask = intr ? + perf_intr_simd_reg_class_mask(EM_HOST, pred) : + perf_user_simd_reg_class_mask(EM_HOST, pred); + u64 supported_classes = 0; + + for (int reg_c = 0; reg_c < 64; reg_c++) { + struct perf_event_attr s_attr = *base_attr; + u16 qwords = 0; + u64 bitmap; + + if (!(class_mask & BIT_ULL(reg_c))) + continue; + bitmap = intr ? + perf_intr_simd_reg_class_bitmap_qwords(EM_HOST, reg_c, + &qwords, pred) : + perf_user_simd_reg_class_bitmap_qwords(EM_HOST, reg_c, + &qwords, pred); + if (!bitmap || !qwords) + continue; + + s_attr.size = sizeof(s_attr); + s_attr.disabled = 1; + s_attr.sample_period = base_attr->sample_period ?: 10000; + s_attr.sample_type = sample_type; + s_attr.sample_simd_regs_enabled = 1; + if (pred) { + s_attr.sample_simd_pred_reg_qwords = qwords; + if (intr) + s_attr.sample_simd_pred_reg_intr = bitmap; + else + s_attr.sample_simd_pred_reg_user = bitmap; + } else { + s_attr.sample_simd_vec_reg_qwords = qwords; + if (intr) + s_attr.sample_simd_vec_reg_intr = bitmap; + else + s_attr.sample_simd_vec_reg_user = bitmap; + } + + fd = sys_perf_event_open(&s_attr, pid, cpu, -1, flags); + if (fd < 0 && intr && max_precise > 0) { + s_attr.precise_ip = 1; + fd = sys_perf_event_open(&s_attr, pid, cpu, -1, flags); + } + if (fd >= 0) { + close(fd); + supported_classes |= BIT_ULL(reg_c); + abi |= PERF_SAMPLE_REGS_ABI_SIMD; + } + } + + if (pred) + *out_pred_mask = supported_classes; + else + *out_simd_mask = supported_classes; + } + + if (!(abi & PERF_SAMPLE_REGS_ABI_SIMD)) { + u64 ext_mask = GENMASK_ULL(63, 32) & ~gpr_mask; + struct perf_event_attr x_attr = *base_attr; + bool batch_ok = false; + + x_attr.disabled = 1; + x_attr.sample_period = base_attr->sample_period ?: 10000; + x_attr.sample_type = sample_type; + if (intr && max_precise > 0) + x_attr.precise_ip = 1; + + if (ext_mask) { + if (intr) + x_attr.sample_regs_intr = ext_mask; + else + x_attr.sample_regs_user = ext_mask; + + fd = sys_perf_event_open(&x_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + mask |= ext_mask; + batch_ok = true; + } + } + + if (!batch_ok) { + for (int b = 0; b < 64; b++) { + const char *rname; + + if (gpr_mask & BIT_ULL(b)) + continue; + rname = perf_reg_name(b, EM_HOST, EF_HOST, 0); + if (!rname || !strcmp(rname, "unknown")) + continue; + if (intr) + x_attr.sample_regs_intr = BIT_ULL(b); + else + x_attr.sample_regs_user = BIT_ULL(b); + fd = sys_perf_event_open(&x_attr, pid, cpu, -1, flags); + if (fd >= 0) { + close(fd); + mask |= BIT_ULL(b); + } + } + } + } + + *out_mask = mask; + *out_abi = abi; +} + +static bool try_open_mem_evlist(struct evlist *evlist, + const struct perf_event_attr *base_attr, + pid_t pid, int cpu, unsigned long flags, + int precise_ip, u64 sample_type) +{ + int fds[16]; + size_t nr_fds = 0; + int leader_fd = -1; + struct evsel *evsel; + bool ok = true; + + evlist__for_each_entry(evlist, evsel) { + struct perf_event_attr attr = evsel->core.attr; + int fd; + + if (nr_fds >= ARRAY_SIZE(fds)) { + ok = false; + break; + } + + attr.disabled = (leader_fd < 0) ? 1 : 0; + attr.sample_period = attr.sample_period ?: 10000; + attr.sample_type = sample_type; + attr.exclude_user = base_attr->exclude_user; + attr.exclude_kernel = base_attr->exclude_kernel; + attr.exclude_hv = base_attr->exclude_hv; + attr.exclude_idle = base_attr->exclude_idle; + attr.exclude_host = base_attr->exclude_host; + attr.exclude_guest = base_attr->exclude_guest; + if (evsel->precise_max || precise_ip > 0) + attr.precise_ip = precise_ip; + + fd = sys_perf_event_open(&attr, pid, cpu, leader_fd, flags); + if (fd < 0) { + ok = false; + break; + } + if (leader_fd < 0) + leader_fd = fd; + fds[nr_fds++] = fd; + } + + for (size_t i = nr_fds; i > 0; i--) + close(fds[i - 1]); + + return ok && nr_fds > 0; +} + +static void probe_mem_events(struct perf_pmu *pmu, + const struct perf_event_attr *base_attr, + pid_t pid, int cpu, unsigned long flags) +{ + static const u64 extra_mem_stypes[] = { + PERF_SAMPLE_WEIGHT, + PERF_SAMPLE_WEIGHT_STRUCT, + PERF_SAMPLE_PHYS_ADDR, + PERF_SAMPLE_DATA_PAGE_SIZE, + PERF_SAMPLE_TRANSACTION, + }; + + if (!pmu->mem_events) + return; + + perf_pmu__mem_events_init_pmu(pmu); + + for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) { + struct perf_mem_event *e = perf_pmu__mem_events_ptr(pmu, j); + const char *s; + char buf[256]; + struct evlist *evlist; + struct parse_events_error err; + struct evsel *first; + int max_p, min_p, working_p = -1; + u64 base_stype = PERF_SAMPLE_IP | PERF_SAMPLE_ADDR | PERF_SAMPLE_DATA_SRC; + int ret; + + if (!e || !e->tag || !e->supported) + continue; + + s = perf_pmu__mem_events_name(pmu, j, buf, sizeof(buf)); + if (!s) + continue; + + evlist = evlist__new(); + if (!evlist) + continue; + + parse_events_error__init(&err); + ret = __parse_events(evlist, s, /*pmu_filter=*/NULL, + /*cputype_filter=*/false, &err, /*fake_pmu=*/false, + /*warn_if_reordered=*/false, /*fake_tp=*/false); + parse_events_error__exit(&err); + if (ret || evlist__empty(evlist)) { + evlist__put(evlist); + continue; + } + + first = evlist__first(evlist); + if (first->precise_max) { + max_p = pmu->probed.max_precise > 0 ? pmu->probed.max_precise : 3; + min_p = 1; + } else { + max_p = first->core.attr.precise_ip; + min_p = first->core.attr.precise_ip; + } + + for (int p = max_p; p >= min_p; p--) { + if (try_open_mem_evlist(evlist, base_attr, pid, cpu, + flags, p, base_stype)) { + working_p = p; + break; + } + } + + if (working_p >= 0) { + pmu->probed.mem_events_mask |= (1u << j); + pmu->probed.mem_sample_types |= PERF_SAMPLE_ADDR | PERF_SAMPLE_DATA_SRC; + + for (size_t k = 0; k < ARRAY_SIZE(extra_mem_stypes); k++) { + u64 st = base_stype | extra_mem_stypes[k]; + + if (try_open_mem_evlist(evlist, base_attr, pid, cpu, + flags, working_p, st)) { + pmu->probed.mem_sample_types |= extra_mem_stypes[k]; + } + } + } + + evlist__put(evlist); + } +} + +static void probe_mmap_caps(struct perf_pmu *pmu, + const struct perf_event_attr *base_attr, + int pmu_cpu, pid_t pid, int cpu, + unsigned long flags) +{ + struct perf_event_attr attr = *base_attr; + struct perf_event_mmap_page *pc; + long page_sz = sysconf(_SC_PAGESIZE); + cpu_set_t old_mask, mask; + bool affinity_changed = false; + bool has_rdpmc_cap = false; + int events_fd; + int fd = -1; + + pmu->probed.rdpmc_sysfs = -1; + + if (page_sz <= 0) + page_sz = 4096; + + events_fd = perf_pmu__event_source_devices_fd(); + if (events_fd >= 0) { + int pmu_dirfd = openat(events_fd, pmu->name, O_DIRECTORY | O_RDONLY | O_CLOEXEC); + + if (pmu_dirfd >= 0) { + if (faccessat(pmu_dirfd, "rdpmc", F_OK, 0) == 0) { + int rfd; + + has_rdpmc_cap = true; + rfd = openat(pmu_dirfd, "rdpmc", O_RDONLY | O_CLOEXEC); + if (rfd >= 0) { + char rbuf[16]; + ssize_t n = read(rfd, rbuf, sizeof(rbuf) - 1); + + if (n > 0) { + rbuf[n] = '\0'; + pmu->probed.rdpmc_sysfs = atoi(rbuf); + } + close(rfd); + } + } + close(pmu_dirfd); + } + close(events_fd); + } + + if (pmu_cpu >= 0 && pmu_cpu < CPU_SETSIZE && + sched_getaffinity(0, sizeof(old_mask), &old_mask) == 0) { + CPU_ZERO(&mask); + CPU_SET(pmu_cpu, &mask); + if (sched_setaffinity(0, sizeof(mask), &mask) == 0) + affinity_changed = true; + } + + attr.disabled = 0; + + /* + * On architectures such as arm64, user-space counter reading must be + * explicitly requested via format/rdpmc (config1:1). + */ + if (perf_pmu__has_format(pmu, "rdpmc")) { + struct perf_event_attr r_attr = attr; + int uval; + + has_rdpmc_cap = true; + if (pmu->probed.rdpmc_sysfs < 0 && + sysctl__read_int("kernel/perf_user_access", &uval) == 0) + pmu->probed.rdpmc_sysfs = uval; + + r_attr.config1 |= 0x2; + fd = sys_perf_event_open(&r_attr, 0, -1, -1, flags); + } + + if (fd < 0 && !pmu->is_uncore) + fd = sys_perf_event_open(&attr, 0, -1, -1, flags); + if (fd < 0) + fd = sys_perf_event_open(&attr, pid, cpu, -1, flags); + + if (fd >= 0) { + pc = mmap(NULL, page_sz, PROT_READ, MAP_SHARED, fd, 0); + if (pc != MAP_FAILED) { + if (has_rdpmc_cap || pc->cap_user_rdpmc) { + pmu->probed.rdpmc_available = true; + pmu->probed.pmc_width = pc->pmc_width; + } + if (pc->cap_user_rdpmc && pc->index != 0) { + struct perf_mmap map = { .base = pc }; + struct perf_counts_values start = { .val = 0 }; + struct perf_counts_values end = { .val = 0 }; + + pmu->probed.can_user_rdpmc = true; + if (perf_mmap__read_self(&map, &start) == 0) { + for (int spin = 0; spin < 10000; spin++) + barrier(); + if (perf_mmap__read_self(&map, &end) == 0 && + end.val >= start.val && end.val > 0) + pmu->probed.rdpmc_works = true; + } + } + pmu->probed.can_user_time = !!pc->cap_user_time; + pmu->probed.can_user_time_zero = !!pc->cap_user_time_zero; + pmu->probed.can_user_time_short = !!pc->cap_user_time_short; + munmap(pc, page_sz); + } + close(fd); + } + + if (affinity_changed) + sched_setaffinity(0, sizeof(old_mask), &old_mask); +} + +/* + * Mark noinline so the compiler does not inline the multi-kilobyte .rept nop + * runway into callers; GCC estimates inline asm size by counting newlines in + * the template string and would otherwise emit short-range conditional + * branches (such as ARM64 tbz/tbnz with a +-32KB range) across the inlined + * block. + */ +static noinline bool run_skid_branch_loop(u64 iters, uintptr_t *out_s_ip, + uintptr_t *out_b_ip, + uintptr_t *out_e_ip, + int *out_insn_sz, int *out_nop_cnt) +{ +#if defined(__x86_64__) + uintptr_t s_ip, b_ip, e_ip; + + asm volatile( + "lea 1f(%%rip), %[s_ip]\n\t" + "lea 2f(%%rip), %[b_ip]\n\t" + "lea 3f(%%rip), %[e_ip]\n\t" + ".balign 64\n" + "1:\n\t" + ".rept 8192\n\t" + "nop\n\t" + ".endr\n\t" + "dec %[cnt]\n\t" + "2:\n\t" + "jnz 1b\n" + "3:\n\t" + : [cnt] "+r" (iters), + [s_ip] "=&r" (s_ip), + [b_ip] "=&r" (b_ip), + [e_ip] "=&r" (e_ip) + : + : "cc", "memory" + ); + *out_s_ip = s_ip; + *out_b_ip = b_ip; + *out_e_ip = e_ip; + *out_insn_sz = 1; + *out_nop_cnt = 8192; + return true; +#elif defined(__aarch64__) + uintptr_t s_ip, b_ip, e_ip; + + asm volatile( + "adr %[s_ip], 1f\n\t" + "adr %[b_ip], 2f\n\t" + "adr %[e_ip], 3f\n\t" + ".balign 64\n" + "1:\n\t" + ".rept 1024\n\t" + "nop\n\t" + ".endr\n\t" + "subs %[cnt], %[cnt], #1\n\t" + "2:\n\t" + "b.ne 1b\n" + "3:\n\t" + : [cnt] "+r" (iters), + [s_ip] "=&r" (s_ip), + [b_ip] "=&r" (b_ip), + [e_ip] "=&r" (e_ip) + : + : "cc", "memory" + ); + *out_s_ip = s_ip; + *out_b_ip = b_ip; + *out_e_ip = e_ip; + *out_insn_sz = 4; + *out_nop_cnt = 1024; + return true; +#elif defined(__riscv) + uintptr_t s_ip, b_ip, e_ip; + + asm volatile( + ".option push\n\t" + ".option norvc\n\t" + "la %[s_ip], 1f\n\t" + "la %[b_ip], 2f\n\t" + "la %[e_ip], 3f\n\t" + ".balign 64\n" + "1:\n\t" + ".rept 512\n\t" + "nop\n\t" + ".endr\n\t" + "addi %[cnt], %[cnt], -1\n\t" + "2:\n\t" + "bnez %[cnt], 1b\n" + "3:\n\t" + ".option pop\n\t" + : [cnt] "+r" (iters), + [s_ip] "=&r" (s_ip), + [b_ip] "=&r" (b_ip), + [e_ip] "=&r" (e_ip) + : + : "memory" + ); + *out_s_ip = s_ip; + *out_b_ip = b_ip; + *out_e_ip = e_ip; + *out_insn_sz = 4; + *out_nop_cnt = 512; + return true; +#else + (void)iters; + *out_s_ip = 0; + *out_b_ip = 0; + *out_e_ip = 0; + *out_insn_sz = 0; + *out_nop_cnt = 0; + return false; +#endif +} + +static void probe_pmu_skid(struct perf_pmu *pmu, + const struct perf_event_attr *base_attr, + int pmu_cpu, unsigned long flags) +{ + struct perf_event_attr br_attr; + char br_buf[256]; + long page_sz = sysconf(_SC_PAGESIZE); + size_t data_sz, mmap_sz; + cpu_set_t old_mask, mask; + bool affinity_changed = false; + + if (!pmu->is_core || !pmu->probed.can_sample) + return; + + if (page_sz <= 0) + page_sz = 4096; + data_sz = 4 * (size_t)page_sz; + mmap_sz = (size_t)page_sz + data_sz; + + memset(&br_attr, 0, sizeof(br_attr)); + snprintf(br_buf, sizeof(br_buf), "%s/branches/", pmu->name); + if (!parse_candidate_attr(br_buf, &br_attr)) { + br_attr.type = PERF_TYPE_HARDWARE; + br_attr.size = base_attr->size; + br_attr.config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS; + if (perf_pmus__supports_extended_type()) + br_attr.config |= (__u64)pmu->type << PERF_PMU_TYPE_SHIFT; + } + br_attr.exclude_user = base_attr->exclude_user; + br_attr.exclude_kernel = 1; + br_attr.exclude_hv = 1; + br_attr.exclude_idle = base_attr->exclude_idle; + br_attr.exclude_host = base_attr->exclude_host; + br_attr.exclude_guest = base_attr->exclude_guest; + + if (pmu_cpu >= 0 && pmu_cpu < CPU_SETSIZE && + sched_getaffinity(0, sizeof(old_mask), &old_mask) == 0) { + CPU_ZERO(&mask); + CPU_SET(pmu_cpu, &mask); + if (sched_setaffinity(0, sizeof(mask), &mask) == 0) + affinity_changed = true; + } + + for (int lvl = 0; lvl <= 3; lvl++) { + struct perf_event_attr attr = br_attr; + struct perf_event_mmap_page *pc; + uintptr_t s_ip = 0, b_ip = 0, e_ip = 0; + int insn_sz = 0, nop_cnt = 0; + void *ring; + u8 *data; + u64 head, tail = 0; + int samples = 0, exact = 0; + int min_skid = 999999, max_skid = 0; + long sum_skid = 0; + int fd; + + if (lvl > 0 && !(pmu->probed.precise_mask & (1u << lvl))) + continue; + + attr.disabled = 1; + attr.sample_period = 251; + attr.sample_type = PERF_SAMPLE_IP; + attr.precise_ip = lvl; + + fd = sys_perf_event_open(&attr, 0, -1, -1, flags); + if (fd < 0 && lvl > 0 && attr.exclude_kernel && attr.config2 == 0) { + attr.config2 = 1; + fd = sys_perf_event_open(&attr, 0, -1, -1, flags); + } + if (fd < 0) + continue; + + ring = mmap(NULL, mmap_sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (ring == MAP_FAILED) { + close(fd); + continue; + } + + ioctl(fd, PERF_EVENT_IOC_RESET, 0); + ioctl(fd, PERF_EVENT_IOC_ENABLE, 0); + if (!run_skid_branch_loop(8000, &s_ip, &b_ip, &e_ip, + &insn_sz, &nop_cnt)) { + ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); + munmap(ring, mmap_sz); + close(fd); + break; + } + ioctl(fd, PERF_EVENT_IOC_DISABLE, 0); + + pc = ring; + head = READ_ONCE(pc->data_head); + /* Ensure data_head is read before reading ring buffer contents. */ + rmb(); + if (head > data_sz) + head = data_sz; + data = (u8 *)ring + page_sz; + + while (tail + sizeof(struct perf_event_header) <= head) { + struct perf_event_header *hdr = + (struct perf_event_header *)(data + tail); + + if (hdr->size == 0 || tail + hdr->size > head) + break; + if (hdr->type == PERF_RECORD_SAMPLE && + hdr->size >= sizeof(*hdr) + sizeof(u64)) { + u64 ip = *(u64 *)((u8 *)hdr + sizeof(*hdr)); + uintptr_t uip = (uintptr_t)ip; + + if (uip >= s_ip && uip <= e_ip && insn_sz > 0) { + int skid; + + if (uip == b_ip) { + skid = 0; + } else if (uip >= s_ip && uip < b_ip) { + long off = (long)(uip - s_ip) / insn_sz; + + skid = (off < nop_cnt) ? + (int)(off + 1) : (nop_cnt + 1); + } else { + skid = 1; + } + + samples++; + if (hdr->misc & PERF_RECORD_MISC_EXACT_IP) + exact++; + if (skid < min_skid) + min_skid = skid; + if (skid > max_skid) + max_skid = skid; + sum_skid += skid; + } + } + tail += hdr->size; + } + + if (samples > 0) { + pmu->probed.skid[lvl].measured = true; + pmu->probed.skid[lvl].samples = samples; + pmu->probed.skid[lvl].exact_samples = exact; + pmu->probed.skid[lvl].min_skid = min_skid; + pmu->probed.skid[lvl].max_skid = max_skid; + pmu->probed.skid[lvl].avg_skid = (double)sum_skid / samples; + } + + munmap(ring, mmap_sz); + close(fd); + } + + if (affinity_changed) + sched_setaffinity(0, sizeof(old_mask), &old_mask); +} + void perf_pmu__probe(struct perf_pmu *pmu) { struct probe_candidates cands = { .nr = 0 }; @@ -3696,7 +4429,29 @@ void perf_pmu__probe(struct perf_pmu *pmu) } } + probe_pmu_skid(pmu, &base_attr, pmu_cpu, flags); + } + + /* 3b. User and interrupt register sampling masks */ + if (pmu->probed.sample_types & PERF_SAMPLE_REGS_USER) { + probe_pmu_regs_one(pmu, &base_attr, pid, cpu, flags, + pmu->probed.max_precise, /*intr=*/false, + &pmu->probed.user_regs_mask, + &pmu->probed.user_regs_abi, + &pmu->probed.user_simd_mask, + &pmu->probed.user_pred_mask); } + if (pmu->probed.sample_types & PERF_SAMPLE_REGS_INTR) { + probe_pmu_regs_one(pmu, &base_attr, pid, cpu, flags, + pmu->probed.max_precise, /*intr=*/true, + &pmu->probed.intr_regs_mask, + &pmu->probed.intr_regs_abi, + &pmu->probed.intr_simd_mask, + &pmu->probed.intr_pred_mask); + } + + /* 3c. Memory event types and memory sample formats */ + probe_mem_events(pmu, &base_attr, pid, cpu, flags); /* 4. Privilege / exclusion modifiers */ pmu->probed.exclude_user = @@ -3861,6 +4616,9 @@ void perf_pmu__probe(struct perf_pmu *pmu) } } + /* 5b. User-space mmap capabilities (rdpmc, user_time, pmc_width) */ + probe_mmap_caps(pmu, &base_attr, pmu_cpu, pid, cpu, flags); + /* 5c. Auto-counter reload (acr_mask / ratio-to-prev) */ if (pmu->probed.can_sample && perf_pmu__has_format(pmu, "acr_mask")) { struct perf_event_attr l_attr = base_attr; -- 2.56.0.212.g129ede20d9-goog ^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v2 7/9] perf sysinfo: Add sysinfo subcommand 2026-10-17 18:54 ` [RFC PATCH v2 0/9] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers ` (5 preceding siblings ...) 2026-10-17 18:54 ` [RFC PATCH v2 6/9] perf pmu: Probe registers, memory events, rdpmc, and branch skid Ian Rogers @ 2026-10-17 18:54 ` Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 8/9] perf docs: Add perf-sysinfo man page Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 9/9] perf test: Add perf sysinfo shell test Ian Rogers 8 siblings, 0 replies; 13+ messages in thread From: Ian Rogers @ 2026-10-17 18:54 UTC (permalink / raw) To: irogers, acme, namhyung Cc: adrian.hunter, eranian, james.clark, jolsa, linux-kernel, linux-perf-users, mingo, peterz Add the 'perf sysinfo' subcommand to display and probe PMUs and system performance monitoring capabilities on the running system or from a perf.data file. By default, 'perf sysinfo' reports a system-wide summary of core PMU features (precise sampling, branch stack sampling, user-space counter reading, leader sampling, memory profiling events, and hardware AUX trace PMUs), memory and PCIe/IO interconnect bus bandwidth monitoring, total events and metrics, pipeline metric drill-down levels, and an overall system rating. When '-p'/'--pmus' (or a PMU glob filter, '-v'/'--verbose', '-a'/'--all', or '-j'/'--json') is specified, it also reports per-PMU static attributes and live probed capabilities in text or JSON format. Signed-off-by: Ian Rogers <irogers@google.com> --- tools/perf/Build | 1 + tools/perf/builtin-help.c | 3 +- tools/perf/builtin-sysinfo.c | 2010 ++++++++++++++++++++++++++++++++++ tools/perf/builtin.h | 1 + tools/perf/perf.c | 1 + 5 files changed, 2015 insertions(+), 1 deletion(-) create mode 100644 tools/perf/builtin-sysinfo.c diff --git a/tools/perf/Build b/tools/perf/Build index dcba5ba55206..8a3f6071fa40 100644 --- a/tools/perf/Build +++ b/tools/perf/Build @@ -13,6 +13,7 @@ perf-y += builtin-list.o perf-y += builtin-record.o perf-y += builtin-report.o perf-y += builtin-stat.o +perf-y += builtin-sysinfo.o perf-y += builtin-top.o perf-y += builtin-script.o perf-y += builtin-kvm.o diff --git a/tools/perf/builtin-help.c b/tools/perf/builtin-help.c index 5bbdb4d9b9c0..fbab4b77761b 100644 --- a/tools/perf/builtin-help.c +++ b/tools/perf/builtin-help.c @@ -329,6 +329,7 @@ void list_common_cmds_help(void) {"report", "Read perf.data (created by perf record) and display the profile"}, {"script", "Read perf.data (created by perf record) and display trace output"}, {"stat", "Run a command and gather performance counter statistics"}, + {"sysinfo", "Display and probe PMU and system performance monitoring information"}, {"test", "Runs sanity tests."}, {"top", "System profiling tool."}, {"version", "display the version of perf binary"}, @@ -485,7 +486,7 @@ int cmd_help(int argc, const char **argv) }; const char * const builtin_help_subcommands[] = { "buildid-cache", "buildid-list", "diff", "evlist", "help", "list", - "record", "report", "bench", "stat", "timechart", "top", "annotate", + "record", "report", "bench", "stat", "sysinfo", "timechart", "top", "annotate", "script", "sched", "kallsyms", "kmem", "lock", "kvm", "test", "inject", "mem", "data", #ifdef HAVE_LIBELF_SUPPORT "probe", diff --git a/tools/perf/builtin-sysinfo.c b/tools/perf/builtin-sysinfo.c new file mode 100644 index 000000000000..ea544b3d70a0 --- /dev/null +++ b/tools/perf/builtin-sysinfo.c @@ -0,0 +1,2010 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * builtin-sysinfo.c + * + * Builtin sysinfo command: Display and probe PMU and system performance + * monitoring information in a system or a perf.data file. + */ +#include "builtin.h" + +#include "util/color.h" +#include "util/cpumap.h" +#include "util/data.h" +#include "util/debug.h" +#include "util/env.h" +#include "util/evlist.h" +#include "util/evsel.h" +#include "util/header.h" +#include "util/mem-events.h" +#include "util/metricgroup.h" +#include "util/perf_regs.h" +#include "util/pmu.h" +#include "util/pmus.h" +#include "util/session.h" +#include "util/strbuf.h" +#include "util/string2.h" +#include "util/strlist.h" +#include "util/tool.h" + +#include <dwarf-regs.h> +#include <linux/bitmap.h> +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/perf_event.h> +#include <linux/zalloc.h> +#include <subcmd/parse-options.h> + +#include <errno.h> +#include <inttypes.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> + +static const char *pmu_type_desc(u32 type) +{ + switch (type) { + case PERF_TYPE_HARDWARE: + return "PERF_TYPE_HARDWARE"; + case PERF_TYPE_SOFTWARE: + return "PERF_TYPE_SOFTWARE"; + case PERF_TYPE_TRACEPOINT: + return "PERF_TYPE_TRACEPOINT"; + case PERF_TYPE_HW_CACHE: + return "PERF_TYPE_HW_CACHE"; + case PERF_TYPE_RAW: + return "PERF_TYPE_RAW"; + case PERF_TYPE_BREAKPOINT: + return "PERF_TYPE_BREAKPOINT"; + default: + if (type <= PERF_PMU_TYPE_PE_END) + return "dynamic"; + if (type <= PERF_PMU_TYPE_DRM_END) + return "drm"; + if (type <= PERF_PMU_TYPE_HWMON_END) + return "hwmon"; + if (type == PERF_PMU_TYPE_TOOL) + return "tool"; + return "fake"; + } +} + +static const char *format_config_name(int config) +{ + static const char * const config_names[PERF_PMU_FORMAT_VALUE_CONFIG_END] = { + [PERF_PMU_FORMAT_VALUE_CONFIG] = "config", + [PERF_PMU_FORMAT_VALUE_CONFIG1] = "config1", + [PERF_PMU_FORMAT_VALUE_CONFIG2] = "config2", + [PERF_PMU_FORMAT_VALUE_CONFIG3] = "config3", + [PERF_PMU_FORMAT_VALUE_CONFIG4] = "config4", + }; + + if (config >= 0 && config < PERF_PMU_FORMAT_VALUE_CONFIG_END) + return config_names[config]; + return "config?"; +} + +static bool pmu_matches_filters(const struct perf_pmu *pmu, + int argc, const char **argv) +{ + if (argc <= 0) + return true; + + for (int i = 0; i < argc; i++) { + const char *pat = argv[i]; + + if (perf_pmu__wildcard_match(pmu, pat)) + return true; + if (pmu->name && (strglobmatch(pmu->name, pat) || !strcmp(pmu->name, pat))) + return true; + if (pmu->alias_name && + (strglobmatch(pmu->alias_name, pat) || !strcmp(pmu->alias_name, pat))) + return true; + if (pmu->name) { + size_t base_len = pmu_name_len_no_suffix(pmu->name); + + if (strlen(pat) == base_len && !strncmp(pmu->name, pat, base_len)) + return true; + } + } + return false; +} + +static void json_escape_fprintf(FILE *fp, struct strbuf *buf, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + strbuf_setlen(buf, 0); + for (size_t fmt_pos = 0; fmt_pos < strlen(fmt); fmt_pos++) { + switch (fmt[fmt_pos]) { + case '%': + fmt_pos++; + switch (fmt[fmt_pos]) { + case 's': { + const char *s = va_arg(args, const char*); + + strbuf_addstr(buf, s); + break; + } + case 'S': { + const char *s = va_arg(args, const char*); + + for (size_t s_pos = 0; s[s_pos] != '\0'; s_pos++) { + switch (s[s_pos]) { + case '\n': + strbuf_addstr(buf, "\\n"); + break; + case '\r': + strbuf_addstr(buf, "\\r"); + break; + case '\t': + strbuf_addstr(buf, "\\t"); + break; + case '\\': + fallthrough; + case '\"': + strbuf_addch(buf, '\\'); + fallthrough; + default: + strbuf_addch(buf, s[s_pos]); + break; + } + } + break; + } + default: + strbuf_addch(buf, '%'); + strbuf_addch(buf, fmt[fmt_pos]); + break; + } + break; + default: + strbuf_addch(buf, fmt[fmt_pos]); + break; + } + } + va_end(args); + fputs(buf->buf, fp); +} + +struct print_format_state { + FILE *fp; + struct strbuf *buf; + bool json; + int count; +}; + +static int print_format_cb(void *vstate, const char *name, int config, + const unsigned long *bits) +{ + struct print_format_state *state = vstate; + char bits_buf[128]; + + if (!bits) + return 0; + + bitmap_scnprintf((unsigned long *)bits, PERF_PMU_FORMAT_BITS, + bits_buf, sizeof(bits_buf)); + + if (state->json) { + char spec[160]; + + if (state->count == 0) + fprintf(state->fp, ",\n\t\"Formats\": {\n"); + + snprintf(spec, sizeof(spec), "%s:%s", + format_config_name(config), bits_buf); + json_escape_fprintf(state->fp, state->buf, "%s\t\t\"%S\": \"%S\"", + state->count > 0 ? ",\n" : "", + name, spec); + } else { + if (state->count == 0) + fprintf(state->fp, " Formats: "); + + fprintf(state->fp, "%s%s (%s:%s)", + state->count > 0 ? ", " : "", + name, format_config_name(config), bits_buf); + } + state->count++; + return 0; +} + +static bool evsel_matches_pmu(const struct evsel *evsel, + const struct perf_pmu *pmu, bool show_all) +{ + struct perf_pmu *ev_pmu = evsel__find_pmu(evsel); + const char *ev_pmu_name; + + if (ev_pmu == pmu) + return true; + + if (ev_pmu) { + if (!show_all && !pmu->is_core && pmu->name && ev_pmu->name) { + size_t base_len = pmu_name_len_no_suffix(pmu->name); + + if (pmu_name_len_no_suffix(ev_pmu->name) == base_len && + !strncmp(pmu->name, ev_pmu->name, base_len)) + return true; + } + return false; + } + + ev_pmu_name = evsel__pmu_name(evsel); + if (ev_pmu_name && pmu->name) { + if (!strcmp(ev_pmu_name, pmu->name)) + return true; + if (!show_all && !pmu->is_core) { + size_t base_len = pmu_name_len_no_suffix(pmu->name); + + if (pmu_name_len_no_suffix(ev_pmu_name) == base_len && + !strncmp(pmu->name, ev_pmu_name, base_len)) + return true; + } + } + return false; +} + +static void print_recorded_events(FILE *fp, struct strbuf *buf, + const struct perf_pmu *pmu, + const struct perf_session *session, + bool show_all, bool json) +{ + struct strlist *ev_names; + struct str_node *pos; + struct evsel *evsel; + size_t count = 0; + + if (!session || !session->evlist) + return; + + ev_names = strlist__new(NULL, NULL); + if (!ev_names) + return; + + evlist__for_each_entry(session->evlist, evsel) { + if (evsel_matches_pmu(evsel, pmu, show_all)) + strlist__add(ev_names, evsel__name(evsel)); + } + + if (strlist__empty(ev_names)) { + strlist__delete(ev_names); + return; + } + + if (json) { + fprintf(fp, ",\n\t\"RecordedEvents\": ["); + strlist__for_each_entry(pos, ev_names) { + json_escape_fprintf(fp, buf, "%s\"%S\"", + count > 0 ? ", " : "", pos->s); + count++; + } + fprintf(fp, "]"); + } else { + fprintf(fp, " Recorded events: "); + strlist__for_each_entry(pos, ev_names) { + fprintf(fp, "%s%s", count > 0 ? ", " : "", pos->s); + count++; + } + fputc('\n', fp); + } + + strlist__delete(ev_names); +} + +static const struct { + u64 bit; + const char *name; +} sample_type_names[] = { + { PERF_SAMPLE_IP, "ip" }, + { PERF_SAMPLE_TID, "tid" }, + { PERF_SAMPLE_TIME, "time" }, + { PERF_SAMPLE_ADDR, "addr" }, + { PERF_SAMPLE_READ, "read" }, + { PERF_SAMPLE_CALLCHAIN, "callchain" }, + { PERF_SAMPLE_ID, "id" }, + { PERF_SAMPLE_CPU, "cpu" }, + { PERF_SAMPLE_PERIOD, "period" }, + { PERF_SAMPLE_STREAM_ID, "stream_id" }, + { PERF_SAMPLE_RAW, "raw" }, + { PERF_SAMPLE_BRANCH_STACK, "branch_stack" }, + { PERF_SAMPLE_REGS_USER, "regs_user" }, + { PERF_SAMPLE_STACK_USER, "stack_user" }, + { PERF_SAMPLE_WEIGHT, "weight" }, + { PERF_SAMPLE_DATA_SRC, "data_src" }, + { PERF_SAMPLE_IDENTIFIER, "identifier" }, + { PERF_SAMPLE_TRANSACTION, "transaction" }, + { PERF_SAMPLE_REGS_INTR, "regs_intr" }, + { PERF_SAMPLE_PHYS_ADDR, "phys_addr" }, + { PERF_SAMPLE_AUX, "aux" }, + { PERF_SAMPLE_CGROUP, "cgroup" }, + { PERF_SAMPLE_DATA_PAGE_SIZE, "data_page_size" }, + { PERF_SAMPLE_CODE_PAGE_SIZE, "code_page_size" }, + { PERF_SAMPLE_WEIGHT_STRUCT, "weight_struct" }, +}; + +static const char *precise_level_desc(int lvl) +{ + switch (lvl) { + case 0: + return "0 - SAMPLE_IP can have arbitrary skid"; + case 1: + return "1 - SAMPLE_IP must have constant skid"; + case 2: + return "2 - SAMPLE_IP requested to have 0 skid"; + case 3: + return "3 - SAMPLE_IP must have 0 skid, or uses randomization"; + default: + return "unknown"; + } +} + +static void print_precise_levels(FILE *fp, const struct perf_pmu *pmu, + unsigned int precise_mask, + int max_precise, bool json) +{ + static const char * const pnames[] = { "", ":p", ":pp", ":ppp" }; + bool need_sep = false; + + if (json) { + fprintf(fp, ",\n\t\t\"PreciseLevels\": ["); + for (int lvl = 1; lvl <= 3; lvl++) { + if (precise_mask & (1u << lvl)) { + fprintf(fp, "%s\"%s\"", + need_sep ? ", " : "", pnames[lvl]); + need_sep = true; + } + } + fprintf(fp, "]"); + return; + } + + fprintf(fp, " Precise sampling: "); + if (precise_mask == 0) { + if (perf_pmu__is_software(pmu)) { + color_fprintf(fp, PERF_COLOR_GREEN, "always precise"); + fprintf(fp, " (software event, SAMPLE_IP has 0 skid)\n"); + } else { + color_fprintf(fp, PERF_COLOR_RED, "not supported"); + fprintf(fp, " (%s)\n", precise_level_desc(0)); + } + return; + } + + for (int lvl = 1; lvl <= 3; lvl++) { + if (precise_mask & (1u << lvl)) { + if (need_sep) + fprintf(fp, ", "); + color_fprintf(fp, PERF_COLOR_GREEN, "%s", pnames[lvl]); + fprintf(fp, " (%s)", precise_level_desc(lvl)); + need_sep = true; + } + } + fprintf(fp, " (max_precise: "); + color_fprintf(fp, PERF_COLOR_GREEN, "%d", max_precise); + fprintf(fp, ")\n"); +} + +static void print_measured_skid(FILE *fp, const struct perf_pmu *pmu, bool json) +{ + static const char * const lvl_keys[] = { "0", ":p", ":pp", ":ppp" }; + static const char * const lvl_labels[] = { + "level 0 (no :p)", + "level 1 (:p)", + "level 2 (:pp)", + "level 3 (:ppp)", + }; + const typeof(pmu->probed) *p = &pmu->probed; + bool any = false; + bool first = true; + + for (int lvl = 0; lvl <= 3; lvl++) { + if (p->skid[lvl].measured) { + any = true; + break; + } + } + if (!any) + return; + + if (json) + fprintf(fp, ",\n\t\t\"MeasuredSkid\": {\n"); + else + fprintf(fp, " Measured branch skid (instructions from branch):\n"); + + for (int lvl = 0; lvl <= 3; lvl++) { + if (!p->skid[lvl].measured) + continue; + if (json) { + fprintf(fp, + "%s\t\t\t\"%s\": { \"Samples\": %d, \"ExactIp\": %d, \"MinInsn\": %d, \"MaxInsn\": %d, \"AvgInsn\": %.1f }", + first ? "" : ",\n", + lvl_keys[lvl], + p->skid[lvl].samples, + p->skid[lvl].exact_samples, + p->skid[lvl].min_skid, + p->skid[lvl].max_skid, + p->skid[lvl].avg_skid); + } else { + const char *skid_color = PERF_COLOR_YELLOW; + + if (p->skid[lvl].max_skid == 0) + skid_color = PERF_COLOR_GREEN; + else if (p->skid[lvl].max_skid <= 2) + skid_color = PERF_COLOR_CYAN; + + fprintf(fp, " %-15s: ", lvl_labels[lvl]); + color_fprintf(fp, skid_color, "min %d, max %d, avg %.1f", + p->skid[lvl].min_skid, + p->skid[lvl].max_skid, + p->skid[lvl].avg_skid); + fprintf(fp, " (%d/%d exact_ip)\n", + p->skid[lvl].exact_samples, + p->skid[lvl].samples); + } + first = false; + } + + if (json) + fprintf(fp, "\n\t\t}"); +} + +static void print_sample_type_list(FILE *fp, u64 mask, bool json) +{ + bool need_sep = false; + + for (size_t x = 0; x < ARRAY_SIZE(sample_type_names); x++) { + if (!(mask & sample_type_names[x].bit)) + continue; + if (json) + fprintf(fp, "%s\"%s\"", need_sep ? ", " : "", sample_type_names[x].name); + else + fprintf(fp, "%s%s", need_sep ? ", " : "", sample_type_names[x].name); + need_sep = true; + } + if (!json && !need_sep) + fprintf(fp, "none"); +} + +static void print_probed_regs(FILE *fp, struct strbuf *buf, const char *label, + u64 mask, int abi, u64 simd_mask, u64 pred_mask, + bool intr, bool json) +{ + const char *last_name = NULL; + bool need_sep = false; + + if (!mask && !simd_mask && !pred_mask) + return; + + if (json) + fprintf(fp, ",\n\t\t\"%s\": [", label); + else + fprintf(fp, " %s: ", label); + + for (int reg = 0; reg < 64; reg++) { + const char *name; + + if (!(mask & BIT_ULL(reg))) + continue; + name = perf_reg_name(reg, EM_HOST, EF_HOST, abi); + if (!name || !strcmp(name, "unknown")) + continue; + if (last_name && !strcmp(last_name, name)) + continue; + last_name = name; + + if (json) { + json_escape_fprintf(fp, buf, "%s\"%S\"", + need_sep ? ", " : "", name); + } else { + fprintf(fp, "%s%s", need_sep ? ", " : "", name); + } + need_sep = true; + } + + for (int is_pred = 0; is_pred <= 1; is_pred++) { + bool pred = is_pred != 0; + u64 cmask = pred ? pred_mask : simd_mask; + + for (int reg_c = 0; reg_c < 64; reg_c++) { + const char *cname; + u16 qwords = 0; + u64 bitmap; + char reg_range[64]; + + if (!(cmask & BIT_ULL(reg_c))) + continue; + cname = perf_simd_reg_class_name(EM_HOST, reg_c, pred); + bitmap = intr ? + perf_intr_simd_reg_class_bitmap_qwords(EM_HOST, reg_c, + &qwords, pred) : + perf_user_simd_reg_class_bitmap_qwords(EM_HOST, reg_c, + &qwords, pred); + if (!cname || !bitmap) + continue; + snprintf(reg_range, sizeof(reg_range), "%s0-%d", + cname, fls64(bitmap) - 1); + if (json) { + json_escape_fprintf(fp, buf, "%s\"%S\"", + need_sep ? ", " : "", reg_range); + } else { + fprintf(fp, "%s%s", need_sep ? ", " : "", reg_range); + } + need_sep = true; + } + } + + if (json) + fprintf(fp, "]"); + else + fputc('\n', fp); +} + +static void pmu_event_counts(struct perf_pmu *pmu, size_t *out_total, + size_t *out_sysfs, size_t *out_json) +{ + size_t total = perf_pmu__num_events(pmu); + size_t sysfs; + + if (pmu->type == PERF_TYPE_TRACEPOINT || + (pmu->type >= PERF_PMU_TYPE_DRM_START && + pmu->type <= PERF_PMU_TYPE_DRM_END) || + (pmu->type >= PERF_PMU_TYPE_HWMON_START && + pmu->type <= PERF_PMU_TYPE_HWMON_END)) + sysfs = total; + else + sysfs = pmu->sysfs_aliases; + + *out_total = total; + *out_sysfs = sysfs; + *out_json = total > sysfs ? total - sysfs : 0; +} + +static void print_mem_events_static(FILE *fp, struct strbuf *buf, + const struct perf_pmu *pmu, bool json) +{ + bool first = true; + + if (!pmu->mem_events) + return; + + perf_pmu__mem_events_init_pmu((struct perf_pmu *)pmu); + + for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) { + struct perf_mem_event *e = perf_pmu__mem_events_ptr((struct perf_pmu *)pmu, j); + char ev_buf[256]; + const char *s; + + if (!e || !e->tag || !e->supported) + continue; + s = perf_pmu__mem_events_name((struct perf_pmu *)pmu, j, + ev_buf, sizeof(ev_buf)); + if (!s) + continue; + + if (json) { + if (first) + fprintf(fp, ",\n\t\"MemoryEvents\": {\n"); + json_escape_fprintf(fp, buf, "%s\t\t\"%S\": \"%S\"", + first ? "" : ",\n", e->tag, s); + } else { + if (first) + fprintf(fp, " Memory events: "); + fprintf(fp, "%s%s (%s)", first ? "" : ", ", e->tag, s); + } + first = false; + } + + if (!first) { + if (json) + fprintf(fp, "\n\t}"); + else + fputc('\n', fp); + } +} + +static void print_pmu_json(FILE *fp, const struct perf_pmu *pmu, + const struct perf_session *session, bool show_all) +{ + struct strbuf buf; + size_t num_instances = show_all ? 1 : perf_pmu__num_duplicates(pmu); + char display_name[128]; + size_t total_events, sysfs_events, json_events; + struct print_format_state format_state = { + .fp = fp, + .buf = &buf, + .json = true, + .count = 0, + }; + + strbuf_init(&buf, 0); + + if (num_instances > 1) { + size_t base_len = pmu_name_len_no_suffix(pmu->name ?: ""); + + snprintf(display_name, sizeof(display_name), "%.*s", + (int)base_len, pmu->name ?: ""); + } else { + snprintf(display_name, sizeof(display_name), "%s", pmu->name ?: ""); + } + + fprintf(fp, "{\n"); + json_escape_fprintf(fp, &buf, "\t\"Unit\": \"%S\"", display_name); + if (pmu->name) + json_escape_fprintf(fp, &buf, ",\n\t\"FullName\": \"%S\"", pmu->name); + fprintf(fp, ",\n\t\"Instances\": %zu", num_instances); + if (pmu->alias_name) + json_escape_fprintf(fp, &buf, ",\n\t\"Alias\": \"%S\"", pmu->alias_name); + if (pmu->id) + json_escape_fprintf(fp, &buf, ",\n\t\"Identifier\": \"%S\"", pmu->id); + if (pmu->type != PERF_PMU_TYPE_FAKE) { + fprintf(fp, ",\n\t\"Type\": %u", pmu->type); + json_escape_fprintf(fp, &buf, ",\n\t\"TypeDesc\": \"%S\"", + pmu_type_desc(pmu->type)); + } + fprintf(fp, ",\n\t\"IsCore\": %s", pmu->is_core ? "true" : "false"); + fprintf(fp, ",\n\t\"IsUncore\": %s", pmu->is_uncore ? "true" : "false"); + fprintf(fp, ",\n\t\"IsSoftware\": %s", perf_pmu__is_software(pmu) ? "true" : "false"); + fprintf(fp, ",\n\t\"Auxtrace\": %s", pmu->auxtrace ? "true" : "false"); + fprintf(fp, ",\n\t\"Selectable\": %s", pmu->selectable ? "true" : "false"); + + if (pmu->cpus) { + char cpus_buf[256]; + int nr_cpus = perf_cpu_map__nr(pmu->cpus); + + cpu_map__snprint(pmu->cpus, cpus_buf, sizeof(cpus_buf)); + if (cpus_buf[0]) { + json_escape_fprintf(fp, &buf, ",\n\t\"CPUs\": \"%S\"", cpus_buf); + fprintf(fp, ",\n\t\"NrCPUs\": %d", nr_cpus); + } + } + + perf_pmu__caps_parse((struct perf_pmu *)pmu); + if (!list_empty(&pmu->caps)) { + struct perf_pmu_caps *cap; + bool first = true; + + fprintf(fp, ",\n\t\"Capabilities\": {\n"); + list_for_each_entry(cap, &pmu->caps, list) { + json_escape_fprintf(fp, &buf, "%s\t\t\"%S\": \"%S\"", + first ? "" : ",\n", + cap->name, cap->value); + first = false; + } + fprintf(fp, "\n\t}"); + } + + perf_pmu__for_each_format((struct perf_pmu *)pmu, &format_state, print_format_cb); + if (format_state.count > 0) + fprintf(fp, "\n\t}"); + + pmu_event_counts((struct perf_pmu *)pmu, &total_events, + &sysfs_events, &json_events); + if (total_events > 0) { + fprintf(fp, ",\n\t\"Events\": {\n"); + fprintf(fp, "\t\t\"Total\": %zu,\n", total_events); + fprintf(fp, "\t\t\"Sysfs\": %zu,\n", sysfs_events); + fprintf(fp, "\t\t\"Json\": %zu\n", json_events); + fprintf(fp, "\t}"); + } + + print_mem_events_static(fp, &buf, pmu, /*json=*/true); + print_recorded_events(fp, &buf, pmu, session, show_all, /*json=*/true); + + if (pmu->probed.checked) { + const typeof(pmu->probed) *p = &pmu->probed; + + fprintf(fp, ",\n\t\"Probed\": {\n"); + fprintf(fp, "\t\t\"OpenSupported\": %s", + p->open_supported ? "true" : "false"); + if (p->open_supported) { + bool need_sep = false; + const struct { + bool supported; + const char *name; + } excl[] = { + { p->exclude_user, "exclude_user" }, + { p->exclude_kernel, "exclude_kernel" }, + { p->exclude_hv, "exclude_hv" }, + { p->exclude_idle, "exclude_idle" }, + { p->exclude_host, "exclude_host" }, + { p->exclude_guest, "exclude_guest" }, + }; + + if (p->probe_event) { + json_escape_fprintf(fp, &buf, + ",\n\t\t\"ProbeEvent\": \"%S\"", + p->probe_event); + } + fprintf(fp, ",\n\t\t\"Counting\": %s", + p->can_count ? "true" : "false"); + fprintf(fp, ",\n\t\t\"Sampling\": %s", + p->can_sample ? "true" : "false"); + fprintf(fp, ",\n\t\t\"BranchStack\": %s", + p->can_branch_stack ? "true" : "false"); + if (p->can_sample) { + if (!pmu->auxtrace) { + const char *pdesc = (p->max_precise == 0 && + perf_pmu__is_software(pmu)) ? + "software event - SAMPLE_IP has 0 skid" : + precise_level_desc(p->max_precise); + + fprintf(fp, ",\n\t\t\"MaxPrecise\": %d", p->max_precise); + json_escape_fprintf(fp, &buf, + ",\n\t\t\"MaxPreciseDesc\": \"%S\"", + pdesc); + print_precise_levels(fp, pmu, p->precise_mask, + p->max_precise, /*json=*/true); + + print_measured_skid(fp, pmu, /*json=*/true); + } + + fprintf(fp, ",\n\t\t\"SampleTypes\": ["); + print_sample_type_list(fp, p->sample_types, /*json=*/true); + fprintf(fp, "]"); + } + + print_probed_regs(fp, &buf, "UserRegisters", + p->user_regs_mask, p->user_regs_abi, + p->user_simd_mask, p->user_pred_mask, + /*intr=*/false, /*json=*/true); + print_probed_regs(fp, &buf, "IntrRegisters", + p->intr_regs_mask, p->intr_regs_abi, + p->intr_simd_mask, p->intr_pred_mask, + /*intr=*/true, /*json=*/true); + + if (p->mem_events_mask) { + need_sep = false; + fprintf(fp, ",\n\t\t\"MemoryEvents\": ["); + for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) { + struct perf_mem_event *e = + perf_pmu__mem_events_ptr((struct perf_pmu *)pmu, j); + + if (e && e->tag && (p->mem_events_mask & (1u << j))) { + json_escape_fprintf(fp, &buf, "%s\"%S\"", + need_sep ? ", " : "", + e->tag); + need_sep = true; + } + } + fprintf(fp, "]"); + + fprintf(fp, ",\n\t\t\"MemorySampleTypes\": ["); + print_sample_type_list(fp, p->mem_sample_types, /*json=*/true); + fprintf(fp, "]"); + } + + need_sep = false; + fprintf(fp, ",\n\t\t\"ExcludeModifiers\": ["); + for (size_t x = 0; x < ARRAY_SIZE(excl); x++) { + if (excl[x].supported) { + fprintf(fp, "%s\"%s\"", + need_sep ? ", " : "", + excl[x].name); + need_sep = true; + } + } + fprintf(fp, "]"); + + { + const struct { + bool supported; + const char *name; + } feats[] = { + { p->can_leader_sampling, "leader_sampling" }, + { p->can_sw_grouping, "sw_grouping" }, + { p->can_inherit, "inherit" }, + { p->can_freq, "sample_freq" }, + { p->can_write_backward, "write_backward" }, + { p->can_callchain, "callchain" }, + { p->can_data_src, "data_src" }, + { p->can_weight, "weight" }, + { p->can_pinned, "pinned" }, + { p->can_exclusive, "exclusive" }, + { p->can_format_lost, "format_lost" }, + { p->can_acr, "auto_counter_reload" }, + { p->can_aux_start_paused, "aux_start_paused" }, + { p->can_aux_pause, "aux_pause" }, + { p->can_aux_resume, "aux_resume" }, + { p->can_aux_sample, "aux_sample" }, + { p->can_user_rdpmc, "user_rdpmc" }, + { p->can_user_time, "user_time" }, + { p->can_user_time_zero, "user_time_zero" }, + { p->can_user_time_short, "user_time_short" }, + }; + + need_sep = false; + fprintf(fp, ",\n\t\t\"Features\": ["); + for (size_t x = 0; x < ARRAY_SIZE(feats); x++) { + if (feats[x].supported) { + fprintf(fp, "%s\"%s\"", + need_sep ? ", " : "", + feats[x].name); + need_sep = true; + } + } + fprintf(fp, "]"); + } + + if (pmu->auxtrace) { + const struct { + bool supported; + const char *name; + } aux_acts[] = { + { p->can_aux_start_paused, "aux_start_paused" }, + { p->can_aux_pause, "aux_pause" }, + { p->can_aux_resume, "aux_resume" }, + { p->can_aux_sample, "aux_sample" }, + }; + + need_sep = false; + fprintf(fp, ",\n\t\t\"AuxActions\": ["); + for (size_t x = 0; x < ARRAY_SIZE(aux_acts); x++) { + if (aux_acts[x].supported) { + fprintf(fp, "%s\"%s\"", + need_sep ? ", " : "", + aux_acts[x].name); + need_sep = true; + } + } + fprintf(fp, "]"); + } + + if (p->can_user_rdpmc && p->pmc_width > 0) + fprintf(fp, ",\n\t\t\"PmcWidth\": %u", p->pmc_width); + + fprintf(fp, ",\n\t\t\"UserRdpmc\": {\n"); + fprintf(fp, "\t\t\t\"Available\": %s,\n", + p->rdpmc_available ? "true" : "false"); + fprintf(fp, "\t\t\t\"Enabled\": %s,\n", + p->can_user_rdpmc ? "true" : "false"); + fprintf(fp, "\t\t\t\"Working\": %s", + p->rdpmc_works ? "true" : "false"); + if (p->rdpmc_available && p->pmc_width > 0) + fprintf(fp, ",\n\t\t\t\"PmcWidth\": %u", p->pmc_width); + if (p->rdpmc_sysfs >= 0) + fprintf(fp, ",\n\t\t\t\"Sysfs\": %d", p->rdpmc_sysfs); + fprintf(fp, "\n\t\t}"); + + fprintf(fp, ",\n\t\t\"MaxGroupSize\": %d", + p->max_group_size); + fprintf(fp, ",\n\t\t\"MaxGroupSizeCapped\": %s", + p->max_group_capped ? "true" : "false"); + fprintf(fp, ",\n\t\t\"MaxActiveGroupSize\": %d", + p->max_active_group_size); + fprintf(fp, ",\n\t\t\"MaxActiveGroupSizeCapped\": %s", + p->max_active_capped ? "true" : "false"); + if (p->max_gp_group_size > 0) { + fprintf(fp, ",\n\t\t\"MaxGpGroupSize\": %d", + p->max_gp_group_size); + fprintf(fp, ",\n\t\t\"MaxActiveGpGroupSize\": %d", + p->max_active_gp_group_size); + } + } else { + json_escape_fprintf(fp, &buf, ",\n\t\t\"Error\": \"%S\"", + strerror(p->open_err)); + } + fprintf(fp, "\n\t}"); + } + + fprintf(fp, "\n}"); + strbuf_release(&buf); +} + +static void print_pmu_text(FILE *fp, const struct perf_pmu *pmu, + const struct perf_session *session, bool show_all) +{ + size_t num_instances = show_all ? 1 : perf_pmu__num_duplicates(pmu); + char display_name[128]; + const char *kind_sep = ""; + size_t total_events, sysfs_events, json_events; + struct print_format_state format_state = { + .fp = fp, + .buf = NULL, + .json = false, + .count = 0, + }; + + if (num_instances > 1) { + size_t base_len = pmu_name_len_no_suffix(pmu->name ?: ""); + + snprintf(display_name, sizeof(display_name), "%.*s", + (int)base_len, pmu->name ?: ""); + } else { + snprintf(display_name, sizeof(display_name), "%s", pmu->name ?: ""); + } + + color_fprintf(fp, PERF_COLOR_BOLD, "%s", display_name); + fprintf(fp, " ("); + if (pmu->type != PERF_PMU_TYPE_FAKE) { + fprintf(fp, "type: %u [%s]", pmu->type, pmu_type_desc(pmu->type)); + kind_sep = ", "; + } + if (pmu->is_core) { + fprintf(fp, "%s", kind_sep); + color_fprintf(fp, PERF_COLOR_GREEN, "core"); + kind_sep = ", "; + } + if (pmu->is_uncore) { + fprintf(fp, "%s", kind_sep); + color_fprintf(fp, PERF_COLOR_CYAN, "uncore"); + kind_sep = ", "; + } + if (perf_pmu__is_software(pmu)) { + fprintf(fp, "%s", kind_sep); + color_fprintf(fp, PERF_COLOR_BLUE, "software"); + kind_sep = ", "; + } + if (pmu->auxtrace) { + fprintf(fp, "%s", kind_sep); + color_fprintf(fp, PERF_COLOR_MAGENTA, "auxtrace"); + kind_sep = ", "; + } + if (pmu->selectable) { + fprintf(fp, "%sselectable", kind_sep); + kind_sep = ", "; + } + fprintf(fp, "):\n"); + + if (num_instances > 1 || (pmu->name && strcmp(pmu->name, display_name) != 0)) + fprintf(fp, " Instances: %zu (first: %s)\n", num_instances, pmu->name); + if (pmu->alias_name) + fprintf(fp, " Alias: %s\n", pmu->alias_name); + if (pmu->id) + fprintf(fp, " Identifier: %s\n", pmu->id); + if (pmu->cpus) { + char cpus_buf[256]; + int nr_cpus = perf_cpu_map__nr(pmu->cpus); + + cpu_map__snprint(pmu->cpus, cpus_buf, sizeof(cpus_buf)); + if (cpus_buf[0]) { + if (nr_cpus > 0) + fprintf(fp, " CPUs: %s (%d %s)\n", + cpus_buf, nr_cpus, + nr_cpus == 1 ? "CPU" : "CPUs"); + else + fprintf(fp, " CPUs: %s\n", cpus_buf); + } + } + + perf_pmu__caps_parse((struct perf_pmu *)pmu); + if (!list_empty(&pmu->caps)) { + struct perf_pmu_caps *cap; + bool first = true; + + fprintf(fp, " Capabilities: "); + list_for_each_entry(cap, &pmu->caps, list) { + fprintf(fp, "%s%s=%s", first ? "" : ", ", cap->name, cap->value); + first = false; + } + fputc('\n', fp); + } + + perf_pmu__for_each_format((struct perf_pmu *)pmu, &format_state, print_format_cb); + if (format_state.count > 0) + fputc('\n', fp); + + pmu_event_counts((struct perf_pmu *)pmu, &total_events, + &sysfs_events, &json_events); + if (total_events > 0) { + fprintf(fp, " Events: %zu total (%zu sysfs, %zu json)\n", + total_events, sysfs_events, json_events); + } + + print_mem_events_static(fp, NULL, pmu, /*json=*/false); + print_recorded_events(fp, NULL, pmu, session, show_all, /*json=*/false); + + if (pmu->probed.checked) { + const typeof(pmu->probed) *p = &pmu->probed; + + if (!p->open_supported) { + fprintf(fp, " Probed capabilities: "); + if (p->open_err == EACCES && pmu->name && + !strcmp(pmu->name, "kprobe")) { + color_fprintf(fp, PERF_COLOR_YELLOW, + "unavailable (%s; requires CAP_PERFMON or CAP_SYS_ADMIN)", + strerror(p->open_err)); + } else if (p->open_err == EACCES && pmu->name && + !strcmp(pmu->name, "uprobe")) { + color_fprintf(fp, PERF_COLOR_YELLOW, + "unavailable (%s; requires CAP_SYS_ADMIN)", + strerror(p->open_err)); + } else { + color_fprintf(fp, PERF_COLOR_YELLOW, "unavailable (%s)", + strerror(p->open_err)); + } + fputc('\n', fp); + } else { + const char *sep = ""; + const struct { + bool supported; + const char *name; + } excl[] = { + { p->exclude_user, "exclude_user" }, + { p->exclude_kernel, "exclude_kernel" }, + { p->exclude_hv, "exclude_hv" }, + { p->exclude_idle, "exclude_idle" }, + { p->exclude_host, "exclude_host" }, + { p->exclude_guest, "exclude_guest" }, + }; + + fprintf(fp, " Probed capabilities (event: "); + color_fprintf(fp, PERF_COLOR_CYAN, "%s", + p->probe_event ?: "default"); + fprintf(fp, "):\n"); + fprintf(fp, " Modes: "); + if (p->can_count) { + fprintf(fp, "%s", sep); + color_fprintf(fp, PERF_COLOR_GREEN, "counting"); + sep = ", "; + } + if (p->can_sample) { + fprintf(fp, "%s", sep); + color_fprintf(fp, PERF_COLOR_GREEN, "sampling"); + sep = ", "; + } + if (p->can_branch_stack) { + fprintf(fp, "%s", sep); + color_fprintf(fp, PERF_COLOR_GREEN, "branch_stack"); + sep = ", "; + } + if (*sep == '\0') + color_fprintf(fp, PERF_COLOR_RED, "none"); + fputc('\n', fp); + + if (p->can_sample) { + if (!pmu->auxtrace) { + print_precise_levels(fp, pmu, p->precise_mask, + p->max_precise, /*json=*/false); + + print_measured_skid(fp, pmu, /*json=*/false); + } + + fprintf(fp, " Sample types: "); + print_sample_type_list(fp, p->sample_types, /*json=*/false); + fputc('\n', fp); + } + + print_probed_regs(fp, NULL, "User registers", + p->user_regs_mask, p->user_regs_abi, + p->user_simd_mask, p->user_pred_mask, + /*intr=*/false, /*json=*/false); + print_probed_regs(fp, NULL, "Intr registers", + p->intr_regs_mask, p->intr_regs_abi, + p->intr_simd_mask, p->intr_pred_mask, + /*intr=*/true, /*json=*/false); + + if (p->mem_events_mask) { + sep = ""; + fprintf(fp, " Memory events (probed): "); + for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) { + struct perf_mem_event *e = + perf_pmu__mem_events_ptr((struct perf_pmu *)pmu, j); + + if (e && e->tag && (p->mem_events_mask & (1u << j))) { + fprintf(fp, "%s%s", sep, e->tag); + sep = ", "; + } + } + if (p->mem_sample_types) { + fprintf(fp, " (sample types: "); + print_sample_type_list(fp, p->mem_sample_types, + /*json=*/false); + fprintf(fp, ")"); + } + fputc('\n', fp); + } + + sep = ""; + fprintf(fp, " Exclusion modifiers: "); + for (size_t x = 0; x < ARRAY_SIZE(excl); x++) { + if (excl[x].supported) { + fprintf(fp, "%s%s", sep, excl[x].name); + sep = ", "; + } + } + if (*sep == '\0') + fprintf(fp, "none"); + fputc('\n', fp); + + { + const struct { + bool supported; + const char *name; + } feats[] = { + { p->can_leader_sampling, "leader_sampling" }, + { p->can_sw_grouping, "sw_grouping" }, + { p->can_inherit, "inherit" }, + { p->can_freq, "sample_freq" }, + { p->can_write_backward, "write_backward" }, + { p->can_callchain, "callchain" }, + { p->can_data_src, "data_src" }, + { p->can_weight, "weight" }, + { p->can_pinned, "pinned" }, + { p->can_exclusive, "exclusive" }, + { p->can_format_lost, "format_lost" }, + { p->can_acr, "auto_counter_reload" }, + { p->can_aux_start_paused, "aux_start_paused" }, + { p->can_aux_pause, "aux_pause" }, + { p->can_aux_resume, "aux_resume" }, + { p->can_aux_sample, "aux_sample" }, + { p->can_user_rdpmc, "user_rdpmc" }, + { p->can_user_time, "user_time" }, + { p->can_user_time_zero, "user_time_zero" }, + { p->can_user_time_short, "user_time_short" }, + }; + + sep = ""; + fprintf(fp, " Features: "); + for (size_t x = 0; x < ARRAY_SIZE(feats); x++) { + if (feats[x].supported) { + fprintf(fp, "%s%s", sep, feats[x].name); + sep = ", "; + } + } + if (*sep == '\0') + fprintf(fp, "none"); + fputc('\n', fp); + } + + if (pmu->auxtrace) { + const struct { + bool supported; + const char *name; + } aux_acts[] = { + { p->can_aux_start_paused, "aux_start_paused" }, + { p->can_aux_pause, "aux_pause" }, + { p->can_aux_resume, "aux_resume" }, + { p->can_aux_sample, "aux_sample" }, + }; + + sep = ""; + fprintf(fp, " AUX actions: "); + for (size_t x = 0; x < ARRAY_SIZE(aux_acts); x++) { + if (aux_acts[x].supported) { + fprintf(fp, "%s%s", sep, aux_acts[x].name); + sep = ", "; + } + } + if (*sep == '\0') + fprintf(fp, "none"); + fputc('\n', fp); + } + + if (pmu->is_core || p->rdpmc_available) { + fprintf(fp, " User space counter reading (rdpmc): "); + if (!p->rdpmc_available) { + color_fprintf(fp, PERF_COLOR_RED, "not supported"); + fputc('\n', fp); + } else { + color_fprintf(fp, PERF_COLOR_GREEN, "available"); + fprintf(fp, ", "); + color_fprintf(fp, + p->can_user_rdpmc ? + PERF_COLOR_GREEN : PERF_COLOR_YELLOW, + "%s", + p->can_user_rdpmc ? "enabled" : "disabled"); + if (p->rdpmc_sysfs >= 0) + fprintf(fp, " (sysfs: %d)", p->rdpmc_sysfs); + if (p->can_user_rdpmc) { + fprintf(fp, ", "); + color_fprintf(fp, + p->rdpmc_works ? + PERF_COLOR_GREEN : PERF_COLOR_RED, + "%s", + p->rdpmc_works ? + "working" : "read failed"); + } + if (p->pmc_width > 0) + fprintf(fp, " (width: %u bits)", p->pmc_width); + fputc('\n', fp); + } + } + + fprintf(fp, " Max event group size: %s%d (active: %s%d)\n", + p->max_group_capped ? ">=" : "", + p->max_group_size, + p->max_active_capped ? ">=" : "", + p->max_active_group_size); + if (p->max_gp_group_size > 0 && + (p->max_gp_group_size != p->max_group_size || + p->max_active_gp_group_size != p->max_active_group_size)) { + fprintf(fp, " Max GP event group size: %s%d (active: %s%d)\n", + p->max_gp_group_capped ? ">=" : "", + p->max_gp_group_size, + p->max_active_gp_capped ? ">=" : "", + p->max_active_gp_group_size); + } + } + } +} + +static int process_header_feature(const struct perf_tool *tool, + struct perf_session *session, + union perf_event *event) +{ + int ret = perf_event__process_feature(tool, session, event); + + if (ret == 0 && event->header.size == sizeof(struct perf_record_header_feature) && + (int)event->feat.feat_id >= session->header.last_feat) { + session_done = 1; + } + return ret; +} + +static int process_sample_stop(const struct perf_tool *tool __maybe_unused, + union perf_event *event __maybe_unused, + struct perf_sample *sample __maybe_unused, + struct machine *machine __maybe_unused) +{ + session_done = 1; + return 0; +} + +#define MAX_SUMMARY_METRICS 2048 +#define MAX_DRILLDOWN_LEVELS 16 + +struct summary_metric_item { + char *pmu; + char *name; + char *groups; + int level; + bool has_children; + bool has_parent; +}; + +struct summary_metrics_state { + struct strlist *metrics; + struct strlist *groups; + struct summary_metric_item items[MAX_SUMMARY_METRICS]; + size_t nr_items; +}; + +static bool group_list_has_token(const char *groups, const char *token) +{ + size_t tok_len; + const char *p; + + if (!groups || !token || !*token) + return false; + tok_len = strlen(token); + p = groups; + while (*p) { + const char *semi = strchr(p, ';'); + size_t len = semi ? (size_t)(semi - p) : strlen(p); + + if (len == tok_len && !strncmp(p, token, len)) + return true; + if (!semi) + break; + p = semi + 1; + } + return false; +} + +static int collect_summary_metric_cb(const struct pmu_metric *pm, + const struct pmu_metrics_table *table __maybe_unused, + void *vdata) +{ + struct summary_metrics_state *st = vdata; + + if (pm->metric_name) + strlist__add(st->metrics, pm->metric_name); + + if (pm->metric_group) { + char *copy = strdup(pm->metric_group); + char *tok, *saveptr = NULL; + + if (copy) { + for (tok = strtok_r(copy, ";", &saveptr); tok; + tok = strtok_r(NULL, ";", &saveptr)) { + if (*tok) + strlist__add(st->groups, tok); + } + free(copy); + } + } + + if (pm->metric_name) { + const char *pmu_name = pm->pmu ?: "cpu"; + struct summary_metric_item *existing = NULL; + + for (size_t i = 0; i < st->nr_items; i++) { + if (st->items[i].pmu && st->items[i].name && + !strcmp(st->items[i].pmu, pmu_name) && + !strcmp(st->items[i].name, pm->metric_name)) { + existing = &st->items[i]; + break; + } + } + if (!existing && st->nr_items < MAX_SUMMARY_METRICS) { + existing = &st->items[st->nr_items++]; + existing->pmu = strdup(pmu_name); + existing->name = strdup(pm->metric_name); + existing->groups = strdup(pm->metric_group ?: ""); + } else if (existing && pm->metric_group && existing->groups && + !strstr(existing->groups, pm->metric_group)) { + char *merged = NULL; + + if (asprintf(&merged, "%s;%s", existing->groups, pm->metric_group) > 0) { + free(existing->groups); + existing->groups = merged; + } + } + } + return 0; +} + +static int compute_metric_drilldown(struct summary_metrics_state *st, + int level_counts[MAX_DRILLDOWN_LEVELS], + char *level_labels[MAX_DRILLDOWN_LEVELS]) +{ + int max_level = 0; + + memset(level_counts, 0, sizeof(int) * MAX_DRILLDOWN_LEVELS); + memset(level_labels, 0, sizeof(char *) * MAX_DRILLDOWN_LEVELS); + + for (size_t i = 0; i < st->nr_items; i++) { + char child_grp[256]; + + if (!st->items[i].name || !st->items[i].pmu) + continue; + snprintf(child_grp, sizeof(child_grp), "%s_group", st->items[i].name); + if (!strlist__has_entry(st->groups, child_grp)) + continue; + + for (size_t j = 0; j < st->nr_items; j++) { + if (i == j || !st->items[j].groups || !st->items[j].pmu) + continue; + if (strcmp(st->items[i].pmu, st->items[j].pmu) != 0) + continue; + if (group_list_has_token(st->items[j].groups, child_grp)) { + st->items[i].has_children = true; + st->items[j].has_parent = true; + } + } + } + + for (size_t i = 0; i < st->nr_items; i++) { + if (st->items[i].has_children && !st->items[i].has_parent) { + st->items[i].level = 1; + max_level = 1; + } + } + + for (int d = 1; d < MAX_DRILLDOWN_LEVELS - 1; d++) { + bool progress = false; + + for (size_t i = 0; i < st->nr_items; i++) { + char child_grp[256]; + + if (st->items[i].level != d || !st->items[i].has_children) + continue; + snprintf(child_grp, sizeof(child_grp), "%s_group", st->items[i].name); + for (size_t j = 0; j < st->nr_items; j++) { + if (i == j || !st->items[j].groups || !st->items[j].pmu) + continue; + if (strcmp(st->items[i].pmu, st->items[j].pmu) != 0) + continue; + if (group_list_has_token(st->items[j].groups, child_grp) && + st->items[j].level < d + 1) { + st->items[j].level = d + 1; + if (d + 1 > max_level) + max_level = d + 1; + progress = true; + } + } + } + if (!progress) + break; + } + + for (int d = 1; d <= max_level; d++) { + struct str_node *pos; + const char *best_label = NULL; + int best_hits = 0; + int total_at_d = 0; + + for (size_t i = 0; i < st->nr_items; i++) { + if (st->items[i].level == d) + total_at_d++; + } + level_counts[d] = total_at_d; + + strlist__for_each_entry(pos, st->groups) { + const char *g = pos->s; + size_t glen = strlen(g); + int hits_in_d = 0; + bool in_other_d = false; + + if (!strcasecmp(g, "Default") || !strcasecmp(g, "Summary") || + (glen > 6 && !strcmp(g + glen - 6, "_group"))) + continue; + + for (size_t i = 0; i < st->nr_items; i++) { + if (!st->items[i].groups || st->items[i].level == 0) + continue; + if (group_list_has_token(st->items[i].groups, g)) { + if (st->items[i].level == d) + hits_in_d++; + else + in_other_d = true; + } + } + + if (!in_other_d && + (hits_in_d > best_hits || + (hits_in_d == best_hits && best_label && + strlen(g) > strlen(best_label)))) { + best_hits = hits_in_d; + best_label = g; + } + } + + if (best_label && best_hits * 2 >= total_at_d) + level_labels[d] = strdup(best_label); + } + + return max_level; +} + +static bool is_mem_bw_pmu_name(const char *name) +{ + if (!name) + return false; + return strstarts(name, "uncore_imc") || + strstarts(name, "uncore_mc") || + strstarts(name, "uncore_edc") || + strstarts(name, "uncore_mchbm") || + strstarts(name, "amd_df") || + strstarts(name, "amd_umc") || + strstarts(name, "arm_dmc") || + strstarts(name, "dmc620") || + strstarts(name, "arm_cmn") || + strstr(name, "ddrc") != NULL || + strstr(name, "ddr") != NULL || + !strcmp(name, "hv_24x7") || + strstarts(name, "nest_mc"); +} + +static bool is_io_bw_pmu_name(const char *name) +{ + if (!name) + return false; + return strstarts(name, "uncore_iio") || + strstarts(name, "uncore_irp") || + strstarts(name, "uncore_pcie") || + strstarts(name, "uncore_upi") || + strstarts(name, "uncore_qpi") || + strstarts(name, "uncore_m2pcie") || + strstarts(name, "uncore_m3upi") || + strstarts(name, "uncore_arb") || + strstarts(name, "cxl") || + strstarts(name, "amd_iommu") || + strstarts(name, "amd_xgmi") || + strstarts(name, "arm_smmu") || + strstr(name, "pcie") != NULL || + strstarts(name, "dwc_rootport") || + strstarts(name, "cci") || + strstarts(name, "ccn"); +} + +static void print_strlist_color(FILE *fp, const char *color, struct strlist *list) +{ + struct str_node *pos; + bool first = true; + + strlist__for_each_entry(pos, list) { + if (!first) + fprintf(fp, ", "); + color_fprintf(fp, color, "%s", pos->s); + first = false; + } +} + +static void add_pmu_display_name(struct strlist *list, const struct perf_pmu *pmu) +{ + size_t num_inst = perf_pmu__num_duplicates(pmu); + char buf[128]; + + if (!pmu->name) + return; + if (num_inst > 1) { + size_t base_len = pmu_name_len_no_suffix(pmu->name); + + snprintf(buf, sizeof(buf), "%.*s", (int)base_len, pmu->name); + strlist__add(list, buf); + } else { + strlist__add(list, pmu->name); + } +} + +static void print_system_summary(FILE *fp, const struct perf_session *session, + bool probed, bool leading_newline) +{ + const struct pmu_metrics_table *mtable; + struct perf_pmu *pmu = NULL; + struct strlist *core_pmus = strlist__new(NULL, NULL); + struct strlist *mem_bw_pmus = strlist__new(NULL, NULL); + struct strlist *io_bw_pmus = strlist__new(NULL, NULL); + struct strlist *aux_pmus = strlist__new(NULL, NULL); + struct strlist *missing = strlist__new(NULL, NULL); + struct summary_metrics_state *mstate = zalloc(sizeof(*mstate)); + int level_counts[MAX_DRILLDOWN_LEVELS]; + char *level_labels[MAX_DRILLDOWN_LEVELS]; + int max_drill_lvl = 0; + bool has_sw_sampling = false; + bool core_can_sample = false; + bool core_precise = false; + int max_precise = 0; + bool core_rdpmc = false; + bool core_branch_stack = false; + int max_br_depth = 0; + bool core_mem_events = false; + bool core_leader_sampling = false; + size_t total_pmus = 0, total_events = 0, total_sysfs_events = 0, total_json_events = 0; + size_t nr_core, nr_metrics, nr_groups; + + if (!mstate) + goto out_free; + mstate->metrics = strlist__new(NULL, NULL); + mstate->groups = strlist__new(NULL, NULL); + + if (!core_pmus || !mem_bw_pmus || !io_bw_pmus || !aux_pmus || + !missing || !mstate->metrics || !mstate->groups) + goto out_free; + + while ((pmu = perf_pmus__scan_skip_duplicates(pmu)) != NULL) { + size_t pmu_events, sysfs_ev, json_ev; + + pmu_event_counts(pmu, &pmu_events, &sysfs_ev, &json_ev); + + total_pmus++; + total_events += pmu_events; + total_sysfs_events += sysfs_ev; + total_json_events += json_ev; + + if (pmu->is_core) { + struct perf_pmu_caps *br_cap; + + add_pmu_display_name(core_pmus, pmu); + perf_pmu__caps_parse(pmu); + br_cap = perf_pmu__get_cap(pmu, "branches"); + if (br_cap && br_cap->value) { + int depth = atoi(br_cap->value); + + if (depth > max_br_depth) + max_br_depth = depth; + } + if (probed && pmu->probed.checked && pmu->probed.open_supported) { + if (pmu->probed.can_sample) + core_can_sample = true; + if (pmu->probed.max_precise > 0) { + core_precise = true; + if (pmu->probed.max_precise > max_precise) + max_precise = pmu->probed.max_precise; + } + if (pmu->probed.can_user_rdpmc) + core_rdpmc = true; + if (pmu->probed.can_branch_stack) + core_branch_stack = true; + if (pmu->probed.can_leader_sampling) + core_leader_sampling = true; + } else { + core_can_sample = true; + if (pmu->max_precise > 0) { + core_precise = true; + if (pmu->max_precise > max_precise) + max_precise = pmu->max_precise; + } + if (max_br_depth > 0) + core_branch_stack = true; + } + } else if (pmu->type == PERF_TYPE_SOFTWARE) { + if (!probed || !pmu->probed.checked || pmu->probed.can_sample) + has_sw_sampling = true; + } + + if (pmu->mem_events) { + if (probed && pmu->probed.checked && pmu->probed.open_supported) { + if (pmu->probed.mem_events_mask) + core_mem_events = true; + } else if (!session) { + perf_pmu__mem_events_init_pmu(pmu); + for (int j = 0; j < PERF_MEM_EVENTS__MAX; j++) { + struct perf_mem_event *e = + perf_pmu__mem_events_ptr(pmu, j); + + if (e && e->tag && e->supported) { + core_mem_events = true; + break; + } + } + } + } + + if (pmu->auxtrace) + add_pmu_display_name(aux_pmus, pmu); + if (is_mem_bw_pmu_name(pmu->name)) + add_pmu_display_name(mem_bw_pmus, pmu); + if (is_io_bw_pmu_name(pmu->name)) + add_pmu_display_name(io_bw_pmus, pmu); + } + + if (session) { + struct perf_env *env = (struct perf_env *)&session->header.env; + + mtable = NULL; + if (env->cpuid) { + const char *arch = perf_env__arch(env); + char cpuid_str[128]; + char vendor[32]; + unsigned int family, model, step; + char extra; + + mtable = find_core_metrics_table(arch, env->cpuid); + if (!mtable && !strcmp(arch, "x86") && + sscanf(env->cpuid, "%31[^,],%u,%u,%u%c", + vendor, &family, &model, &step, &extra) == 4) { + snprintf(cpuid_str, sizeof(cpuid_str), "%s-%u-%X-%X", + vendor, family, model, step); + mtable = find_core_metrics_table(arch, cpuid_str); + if (!mtable) { + snprintf(cpuid_str, sizeof(cpuid_str), "%s-%u-%X", + vendor, family, model); + mtable = find_core_metrics_table(arch, cpuid_str); + } + } + } + } else { + mtable = pmu_metrics_table__find(); + } + + metricgroup__for_each_metric(mtable, collect_summary_metric_cb, mstate); + max_drill_lvl = compute_metric_drilldown(mstate, level_counts, level_labels); + + nr_core = strlist__nr_entries(core_pmus); + nr_metrics = strlist__nr_entries(mstate->metrics); + nr_groups = strlist__nr_entries(mstate->groups); + + if (leading_newline) + fputc('\n', fp); + color_fprintf(fp, PERF_COLOR_BOLD, "Summary:"); + fputc('\n', fp); + if (nr_core == 0) { + const char *sw_desc = has_sw_sampling ? + "minimal system; sampling only via software timer events" : + "no sampling support detected"; + + color_fprintf(fp, PERF_COLOR_BOLD, " Core PMUs:"); + fprintf(fp, " "); + color_fprintf(fp, PERF_COLOR_RED, "none"); + fprintf(fp, " (%s)\n", sw_desc); + strlist__add(missing, "hardware core PMU"); + } else { + color_fprintf(fp, PERF_COLOR_BOLD, " Core PMUs (%zu%s):", nr_core, + nr_core > 1 ? ", hybrid" : ""); + fprintf(fp, " "); + print_strlist_color(fp, PERF_COLOR_GREEN, core_pmus); + fputc('\n', fp); + + color_fprintf(fp, PERF_COLOR_BOLD, " Core features:"); + fputc('\n', fp); + fprintf(fp, " Interrupt-based sampling: "); + color_fprintf(fp, core_can_sample ? PERF_COLOR_GREEN : PERF_COLOR_RED, + "%s", core_can_sample ? "supported" : "missing"); + fputc('\n', fp); + if (!core_can_sample) + strlist__add(missing, "core interrupt sampling"); + + fprintf(fp, " Precise event sampling: "); + if (core_precise) { + color_fprintf(fp, PERF_COLOR_GREEN, "supported"); + fprintf(fp, " (max_precise: %d [%s])\n", + max_precise, precise_level_desc(max_precise)); + } else { + color_fprintf(fp, PERF_COLOR_RED, "missing"); + fprintf(fp, " (%s)\n", precise_level_desc(0)); + strlist__add(missing, "precise event sampling (PEBS/IBS/SPE)"); + } + + fprintf(fp, " Branch stack sampling (LBR/BRBE): "); + if (core_branch_stack) { + color_fprintf(fp, PERF_COLOR_GREEN, "supported"); + if (max_br_depth > 0) + fprintf(fp, " (depth: %d branches)\n", max_br_depth); + else + fputc('\n', fp); + } else { + color_fprintf(fp, PERF_COLOR_RED, "missing"); + fputc('\n', fp); + strlist__add(missing, "hardware branch stack sampling (LBR/BRBE)"); + } + + if (probed) { + fprintf(fp, " User-space counter reading (rdpmc): "); + color_fprintf(fp, core_rdpmc ? PERF_COLOR_GREEN : PERF_COLOR_YELLOW, + "%s", core_rdpmc ? "supported" : "missing/disabled"); + fputc('\n', fp); + if (!core_rdpmc) + strlist__add(missing, "user-space counter reading (rdpmc)"); + + fprintf(fp, " Leader sampling: "); + color_fprintf(fp, + core_leader_sampling ? PERF_COLOR_GREEN : PERF_COLOR_RED, + "%s", + core_leader_sampling ? "supported" : "missing"); + fputc('\n', fp); + } + + fprintf(fp, " Memory profiling events (DATA_SRC): "); + color_fprintf(fp, core_mem_events ? PERF_COLOR_GREEN : PERF_COLOR_RED, + "%s", core_mem_events ? "supported" : "missing"); + fputc('\n', fp); + if (!core_mem_events) + strlist__add(missing, "memory load/store profiling events"); + + fprintf(fp, " Hardware AUX trace PMUs: "); + if (!strlist__empty(aux_pmus)) { + print_strlist_color(fp, PERF_COLOR_GREEN, aux_pmus); + fputc('\n', fp); + } else { + color_fprintf(fp, PERF_COLOR_RED, "missing"); + fputc('\n', fp); + strlist__add(missing, "hardware AUX trace PMU"); + } + } + + color_fprintf(fp, PERF_COLOR_BOLD, " Bus & uncore monitoring:"); + fputc('\n', fp); + fprintf(fp, " Memory bandwidth PMUs: "); + if (!strlist__empty(mem_bw_pmus)) { + print_strlist_color(fp, PERF_COLOR_GREEN, mem_bw_pmus); + fputc('\n', fp); + } else { + color_fprintf(fp, PERF_COLOR_RED, "missing"); + fputc('\n', fp); + strlist__add(missing, "memory bus bandwidth PMUs"); + } + + fprintf(fp, " PCIe / IO interconnect bandwidth PMUs: "); + if (!strlist__empty(io_bw_pmus)) { + print_strlist_color(fp, PERF_COLOR_GREEN, io_bw_pmus); + fputc('\n', fp); + } else { + color_fprintf(fp, PERF_COLOR_RED, "missing"); + fputc('\n', fp); + strlist__add(missing, "PCIe/IO interconnect bandwidth PMUs"); + } + + color_fprintf(fp, PERF_COLOR_BOLD, " Events & metrics:"); + fputc('\n', fp); + fprintf(fp, " Available events: %zu total (%zu sysfs, %zu json) across %zu PMUs\n", + total_events, total_sysfs_events, total_json_events, total_pmus); + if (nr_metrics == 0) { + fprintf(fp, " Available metrics: "); + color_fprintf(fp, PERF_COLOR_RED, "none"); + fprintf(fp, " (missing JSON metrics)\n"); + strlist__add(missing, "multi-level pipeline drill-down and JSON metrics"); + } else { + const char *tier = (max_drill_lvl >= 2) ? "advanced" : "minimal"; + const char *tier_col = (max_drill_lvl >= 2) ? + PERF_COLOR_GREEN : PERF_COLOR_YELLOW; + + fprintf(fp, " Available metrics: %zu metrics in %zu groups (", + nr_metrics, nr_groups); + color_fprintf(fp, tier_col, "%s", tier); + fprintf(fp, ")\n"); + if (max_drill_lvl > 0) { + fprintf(fp, " Metric drill-down levels (%d): ", max_drill_lvl); + for (int d = 1; d <= max_drill_lvl; d++) { + if (d > 1) + fprintf(fp, ", "); + color_fprintf(fp, PERF_COLOR_CYAN, "L%d", d); + if (level_labels[d]) + fprintf(fp, " (%s: %d metrics)", + level_labels[d], level_counts[d]); + else + fprintf(fp, " (%d metrics)", level_counts[d]); + } + fputc('\n', fp); + } else { + fprintf(fp, " Metric drill-down levels: "); + color_fprintf(fp, PERF_COLOR_YELLOW, "none"); + fprintf(fp, " (flat metrics only)\n"); + } + if (max_drill_lvl < 2) { + strlist__add(missing, + "multi-level pipeline drill-down metrics (<metric>_group)"); + } + } + + color_fprintf(fp, PERF_COLOR_BOLD, " System rating:"); + if (nr_core == 0) { + const char *min_desc = + "no hardware core PMU; profiling limited to software timer events"; + + fprintf(fp, " "); + color_fprintf(fp, PERF_COLOR_RED, "Minimal"); + fprintf(fp, " (%s)\n", min_desc); + } else if (core_precise && core_branch_stack && (!probed || core_rdpmc) && + !strlist__empty(mem_bw_pmus) && max_drill_lvl >= 2) { + const char *core_adv = "precise sampling, branch stack (LBR), user rdpmc"; + const char *sys_adv = "bus PMUs, and metric drill-down"; + + fprintf(fp, " "); + color_fprintf(fp, PERF_COLOR_GREEN, "Advanced"); + fprintf(fp, " (core PMU with %s, %s)\n", core_adv, sys_adv); + } else if (core_can_sample && + (core_precise || core_branch_stack || max_drill_lvl >= 1)) { + const char *std_desc = + "core PMU with hardware sampling and partial advanced features or metrics"; + + fprintf(fp, " "); + color_fprintf(fp, PERF_COLOR_CYAN, "Standard"); + fprintf(fp, " (%s)\n", std_desc); + } else { + const char *bas_desc = + "core PMU with basic counting/sampling only; missing advanced features"; + + fprintf(fp, " "); + color_fprintf(fp, PERF_COLOR_YELLOW, "Basic"); + fprintf(fp, " (%s)\n", bas_desc); + } + + if (!strlist__empty(missing)) { + color_fprintf(fp, PERF_COLOR_BOLD, " Missing baseline features:"); + fprintf(fp, " "); + print_strlist_color(fp, PERF_COLOR_RED, missing); + fputc('\n', fp); + } + + for (int d = 1; d <= max_drill_lvl; d++) + free(level_labels[d]); + +out_free: + strlist__delete(core_pmus); + strlist__delete(mem_bw_pmus); + strlist__delete(io_bw_pmus); + strlist__delete(aux_pmus); + strlist__delete(missing); + if (mstate) { + for (size_t i = 0; i < mstate->nr_items; i++) { + free(mstate->items[i].pmu); + free(mstate->items[i].name); + free(mstate->items[i].groups); + } + strlist__delete(mstate->metrics); + strlist__delete(mstate->groups); + free(mstate); + } +} + +int cmd_sysinfo(int argc, const char **argv) +{ + bool json = false; + bool show_pmus = false; + bool show_all = false; + bool force = false; + bool do_probe = true; + const char *output_path = NULL; + const char *input_name = NULL; + FILE *fp = stdout; + struct perf_data data = { + .mode = PERF_DATA_MODE_READ, + }; + struct perf_tool tool; + struct perf_session *session = NULL; + struct perf_pmu *pmu = NULL; + size_t pmu_idx = 0; + int ret = 0; + const struct option sysinfo_options[] = { + OPT_STRING('i', "input", &input_name, "file", + "Input perf.data file name to read PMUs from"), + OPT_BOOLEAN('f', "force", &force, "don't complain, do it"), + OPT_STRING('o', "output", &output_path, "file", "output file name"), + OPT_BOOLEAN('p', "pmus", &show_pmus, + "Display detailed information for each PMU"), + OPT_BOOLEAN('j', "json", &json, "JSON encode PMU information"), + OPT_BOOLEAN('v', "verbose", &show_all, + "Show all similar PMUs with alphanumeric suffixes"), + OPT_BOOLEAN('a', "all", &show_all, + "Show all similar PMUs with alphanumeric suffixes"), + OPT_BOOLEAN(0, "probe", &do_probe, + "Probe PMU capabilities on live system (default: on)"), + OPT_INCR(0, "debug", &verbose, "Enable debugging output"), + OPT_END() + }; + const char * const sysinfo_usage[] = { + "perf sysinfo [<options>] [pmu_glob ...]", + NULL + }; + + argc = parse_options(argc, argv, sysinfo_options, sysinfo_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (show_all || json || argc > 0) + show_pmus = true; + + if (output_path) { + perf_use_color_default = 0; + fp = fopen(output_path, "w"); + if (!fp) { + pr_err("Failed to open output file '%s': %m\n", output_path); + return -1; + } + } + + if (input_name) { + data.path = input_name; + data.force = force; + + perf_tool__init(&tool, /*ordered_events=*/false); + tool.attr = perf_event__process_attr; + tool.feature = process_header_feature; + tool.sample = process_sample_stop; + + session = perf_session__new(&data, &tool); + if (IS_ERR(session)) { + ret = PTR_ERR(session); + pr_err("Error opening %s: %s\n", input_name, strerror(-ret)); + goto out_close; + } + + if (data.is_pipe) + perf_session__process_events(session); + + ret = perf_pmus__read_from_env(&session->header.env); + if (ret) { + pr_err("Failed to read PMU information from %s: %s\n", + input_name, strerror(-ret)); + goto out_session; + } + } + + if (json) + fputs("[", fp); + + while ((pmu = show_all ? perf_pmus__scan(pmu) : + perf_pmus__scan_skip_duplicates(pmu)) != NULL) { + if (!pmu_matches_filters(pmu, argc, argv)) + continue; + + if (do_probe && !input_name && + (show_pmus || pmu->is_core || pmu->mem_events || + pmu->type == PERF_TYPE_SOFTWARE)) + perf_pmu__probe(pmu); + + if (!show_pmus) + continue; + + if (json) { + if (pmu_idx > 0) + fputs(",\n", fp); + else + fputs("\n", fp); + print_pmu_json(fp, pmu, session, show_all); + } else { + if (pmu_idx > 0) + fputc('\n', fp); + print_pmu_text(fp, pmu, session, show_all); + } + pmu_idx++; + } + + if (json) + fputs(pmu_idx > 0 ? "\n]\n" : "]\n", fp); + else if (argc == 0) + print_system_summary(fp, session, do_probe && !input_name, + pmu_idx > 0); + +out_session: + if (session) + perf_session__delete(session); +out_close: + if (output_path && fp) + fclose(fp); + return ret; +} diff --git a/tools/perf/builtin.h b/tools/perf/builtin.h index e0b430821abc..28e85c054468 100644 --- a/tools/perf/builtin.h +++ b/tools/perf/builtin.h @@ -37,6 +37,7 @@ int cmd_list(int argc, const char **argv); int cmd_record(int argc, const char **argv); int cmd_report(int argc, const char **argv); int cmd_stat(int argc, const char **argv); +int cmd_sysinfo(int argc, const char **argv); int cmd_timechart(int argc, const char **argv); int cmd_top(int argc, const char **argv); int cmd_script(int argc, const char **argv); diff --git a/tools/perf/perf.c b/tools/perf/perf.c index d60bc929c257..a6fab1e6eb1c 100644 --- a/tools/perf/perf.c +++ b/tools/perf/perf.c @@ -112,6 +112,7 @@ static const struct cmd_struct commands[] = { { "report", cmd_report, 0 }, { "bench", cmd_bench, 0 }, { "stat", cmd_stat, 0 }, + { "sysinfo", cmd_sysinfo, 0 }, { "timechart", cmd_timechart, 0 }, { "top", cmd_top, 0 }, { "annotate", cmd_annotate, 0 }, -- 2.56.0.212.g129ede20d9-goog ^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v2 8/9] perf docs: Add perf-sysinfo man page 2026-10-17 18:54 ` [RFC PATCH v2 0/9] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers ` (6 preceding siblings ...) 2026-10-17 18:54 ` [RFC PATCH v2 7/9] perf sysinfo: Add sysinfo subcommand Ian Rogers @ 2026-10-17 18:54 ` Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 9/9] perf test: Add perf sysinfo shell test Ian Rogers 8 siblings, 0 replies; 13+ messages in thread From: Ian Rogers @ 2026-10-17 18:54 UTC (permalink / raw) To: irogers, acme, namhyung Cc: adrian.hunter, eranian, james.clark, jolsa, linux-kernel, linux-perf-users, mingo, peterz Document the 'perf sysinfo' subcommand and its options in Documentation/perf-sysinfo.txt and link it from Documentation/perf.txt. Signed-off-by: Ian Rogers <irogers@google.com> --- tools/perf/Documentation/perf-sysinfo.txt | 109 ++++++++++++++++++++++ tools/perf/Documentation/perf.txt | 2 +- 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 tools/perf/Documentation/perf-sysinfo.txt diff --git a/tools/perf/Documentation/perf-sysinfo.txt b/tools/perf/Documentation/perf-sysinfo.txt new file mode 100644 index 000000000000..4fc5fe229060 --- /dev/null +++ b/tools/perf/Documentation/perf-sysinfo.txt @@ -0,0 +1,109 @@ +perf-sysinfo(1) +=============== + +NAME +---- +perf-sysinfo - Display and probe PMU and system performance monitoring information + +SYNOPSIS +-------- +[verse] +'perf sysinfo' [<options>] [pmu_glob ...] + +DESCRIPTION +----------- +This command displays a summary of the performance monitoring capabilities of +the current system or a perf.data file, including core PMU features (precise +sampling, branch stack sampling, user-space counter reading, memory profiling, +and hardware AUX trace PMUs), memory and PCIe/IO interconnect bus bandwidth +monitoring support, total events and metrics, pipeline metric drill-down levels +discovered via '<metric>_group' hierarchies, and an overall system rating. + +When '-p'/'--pmus' is specified (or when PMU filter globs, '-v'/'--verbose', +'-a'/'--all', or '-j'/'--json' are given), it also displays detailed +information for each PMU, including its perf_event_attr type, kind (core, +uncore, software, auxtrace, tool, hwmon, drm), CPUs/cpumask, capabilities, +sysfs format bitfields, event counts, and memory profiling events. + +When run on a live system (without '-i'/'--input' and with probing enabled), +it also actively probes the kernel via 'perf_event_open' to determine: +- Supported modes (counting, interrupt-based sampling, branch_stack sampling) +- Supported precise sampling levels, skid semantics, and empirically measured + branch instruction skid (minimum, maximum, average instructions from the + branch and 'PERF_RECORD_MISC_EXACT_IP' ratio) at each supported precision + level: + 0 ('SAMPLE_IP' can have arbitrary skid), ':p' / 1 ('SAMPLE_IP' must have + constant skid), ':pp' / 2 ('SAMPLE_IP' requested to have 0 skid), and + ':ppp' / 3 ('SAMPLE_IP' must have 0 skid, or uses randomization to avoid + sample shadowing effects) +- Supported 'perf_event_sample_format' sample types ('ip', 'tid', 'time', + 'addr', 'read', 'callchain', 'id', 'cpu', 'period', 'stream_id', 'raw', + 'branch_stack', 'regs_user', 'stack_user', 'weight', 'data_src', + 'identifier', 'transaction', 'regs_intr', 'phys_addr', 'aux', 'cgroup', + 'data_page_size', 'code_page_size', 'weight_struct') +- Supported user and interrupt registers for 'PERF_SAMPLE_REGS_USER' and + 'PERF_SAMPLE_REGS_INTR' +- Supported memory profiling events ('ldlat-loads', 'ldlat-stores', 'mem-ldst') + and their supported memory sample types ('data_src', 'addr', 'weight', etc.) +- Supported privilege/exclusion modifiers ('exclude_user', 'exclude_kernel', + 'exclude_hv', 'exclude_idle', 'exclude_host', 'exclude_guest') +- Supported execution features ('leader_sampling', 'sw_grouping', 'inherit', + 'sample_freq', 'write_backward', 'callchain', 'data_src', 'weight', + 'pinned', 'exclusive', 'format_lost', 'auto_counter_reload', + 'aux_start_paused', 'aux_pause', 'aux_resume', 'aux_sample', 'user_rdpmc', + 'user_time', 'user_time_zero', 'user_time_short') and hardware counter + bit-width ('pmc_width') when user-space counter reading is supported +- Maximum event group size accepted by 'perf_event_open' and maximum + simultaneously active counters in a group without multiplexing + +At the end of the text output, a summary section reports overall core PMU +capabilities (including hardware AUX trace PMUs), memory and PCIe/IO +interconnect bus bandwidth monitoring support, total events and metrics, and +pipeline metric drill-down levels discovered via '<metric>_group' hierarchies. + +By default, numbered uncore PMU instances sharing a common prefix are collapsed +into a single entry showing the instance count, unless '-v'/'--verbose' is +specified. + +OPTIONS +------- +-i:: +--input=:: + Input file name to read PMU information from (e.g. perf.data). + +-f:: +--force:: + Don't complain, do it (when reading input perf.data file). + +-o:: +--output=:: + Output file name. + +-p:: +--pmus:: + Display detailed information for each PMU in addition to the system + summary. + +-j:: +--json:: + Encode PMU information as JSON. + +-a:: +--all:: +-v:: +--verbose:: + Display all PMUs including all similar PMUs with alphanumeric suffixes + instead of collapsing numbered uncore PMU instances. + +--probe:: +--no-probe:: + Enable or disable active kernel probing of PMU capabilities on the live + system (default: enabled). + +--debug:: + Enable debugging output. + +SEE ALSO +-------- +linkperf:perf-list[1], linkperf:perf-evlist[1], linkperf:perf-stat[1], +linkperf:perf-record[1] diff --git a/tools/perf/Documentation/perf.txt b/tools/perf/Documentation/perf.txt index cbcc2e4d557e..0890dd5a1794 100644 --- a/tools/perf/Documentation/perf.txt +++ b/tools/perf/Documentation/perf.txt @@ -92,5 +92,5 @@ linkperf:perf-help[1], linkperf:perf-inject[1], linkperf:perf-intel-pt[1], linkperf:perf-iostat[1], linkperf:perf-kallsyms[1], linkperf:perf-kmem[1], linkperf:perf-kvm[1], linkperf:perf-lock[1], linkperf:perf-mem[1], linkperf:perf-probe[1], linkperf:perf-sched[1], -linkperf:perf-script[1], linkperf:perf-test[1], +linkperf:perf-script[1], linkperf:perf-sysinfo[1], linkperf:perf-test[1], linkperf:perf-trace[1], linkperf:perf-version[1] -- 2.56.0.212.g129ede20d9-goog ^ permalink raw reply [flat|nested] 13+ messages in thread
* [RFC PATCH v2 9/9] perf test: Add perf sysinfo shell test 2026-10-17 18:54 ` [RFC PATCH v2 0/9] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers ` (7 preceding siblings ...) 2026-10-17 18:54 ` [RFC PATCH v2 8/9] perf docs: Add perf-sysinfo man page Ian Rogers @ 2026-10-17 18:54 ` Ian Rogers 8 siblings, 0 replies; 13+ messages in thread From: Ian Rogers @ 2026-10-17 18:54 UTC (permalink / raw) To: irogers, acme, namhyung Cc: adrian.hunter, eranian, james.clark, jolsa, linux-kernel, linux-perf-users, mingo, peterz Add a shell test exercising default 'perf sysinfo' summary output, '--pmus' per-PMU output, '-j' JSON validation, PMU glob filtering, and '-i perf.data' inspection. Signed-off-by: Ian Rogers <irogers@google.com> --- tools/perf/tests/shell/sysinfo.sh | 71 +++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100755 tools/perf/tests/shell/sysinfo.sh diff --git a/tools/perf/tests/shell/sysinfo.sh b/tools/perf/tests/shell/sysinfo.sh new file mode 100755 index 000000000000..40e90af6dca2 --- /dev/null +++ b/tools/perf/tests/shell/sysinfo.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# perf sysinfo tests + +set -e + +shelldir=$(dirname "$0") +. "${shelldir}"/lib/setup_python.sh + +sysinfo_output=$(mktemp /tmp/__perf_test.sysinfo_output.XXXXX) +perf_data=$(mktemp /tmp/__perf_test.sysinfo.perf.data.XXXXX) + +cleanup() { + rm -f "${sysinfo_output}" "${perf_data}" "${perf_data}.old" + trap - EXIT TERM INT +} + +trap_cleanup() { + cleanup + exit 1 +} +trap trap_cleanup EXIT TERM INT + +test_sysinfo_default() { + echo "Default perf sysinfo output test" + perf sysinfo -o "${sysinfo_output}" + grep -q "^Summary:" "${sysinfo_output}" + grep -q "System rating:" "${sysinfo_output}" + perf sysinfo --pmus -o "${sysinfo_output}" + grep -q "^software (" "${sysinfo_output}" + grep -q "^Summary:" "${sysinfo_output}" + echo "Default perf sysinfo output test [Success]" +} + +test_sysinfo_json() { + echo "Json perf sysinfo output test" + perf sysinfo -j -o "${sysinfo_output}" + $PYTHON -m json.tool "${sysinfo_output}" /dev/null + echo "Json perf sysinfo output test [Success]" +} + +test_sysinfo_filter() { + echo "Filtered perf sysinfo output test" + perf sysinfo -o "${sysinfo_output}" software + grep -q "^software (" "${sysinfo_output}" + grep -q "Sample types:.*ip" "${sysinfo_output}" + grep -q "User registers:" "${sysinfo_output}" + grep -q "Intr registers:" "${sysinfo_output}" + grep -q "Features:.*leader_sampling" "${sysinfo_output}" + grep -q "Features:.*sw_grouping" "${sysinfo_output}" + echo "Filtered perf sysinfo output test [Success]" +} + +test_sysinfo_perf_data() { + echo "perf.data perf sysinfo test" + perf record -e software/cpu-clock/u -o "${perf_data}" -- true 2>/dev/null + perf sysinfo -i "${perf_data}" -o "${sysinfo_output}" + grep -q "^Summary:" "${sysinfo_output}" + perf sysinfo --pmus -i "${perf_data}" -o "${sysinfo_output}" + grep -q "software" "${sysinfo_output}" + perf sysinfo -i "${perf_data}" -j -o "${sysinfo_output}" + $PYTHON -m json.tool "${sysinfo_output}" /dev/null + echo "perf.data perf sysinfo test [Success]" +} + +test_sysinfo_default +test_sysinfo_json +test_sysinfo_filter +test_sysinfo_perf_data +cleanup +exit 0 -- 2.56.0.212.g129ede20d9-goog ^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-10-17 18:55 UTC | newest] Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-10-16 21:32 [RFC PATCH v1 0/2] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers 2026-10-16 21:32 ` [RFC PATCH v1 1/2] perf pmus: Add PMU environment loading and live capability probing Ian Rogers 2026-10-16 21:32 ` [RFC PATCH v1 2/2] perf sysinfo: Add sysinfo subcommand, documentation, and test Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 0/9] perf sysinfo: Add subcommand to probe and summarize PMU capabilities Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 1/9] perf pmu: Add from_env parameter to perf_pmu__init() Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 2/9] perf pmus: Add perf_pmus__read_from_env() Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 3/9] perf pmus: Expose duplicate uncore PMU scanning helpers Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 4/9] perf mem-events: Expose PMU init and name formatting helpers Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 5/9] perf pmu: Add perf_pmu__probe() for live capability probing Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 6/9] perf pmu: Probe registers, memory events, rdpmc, and branch skid Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 7/9] perf sysinfo: Add sysinfo subcommand Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 8/9] perf docs: Add perf-sysinfo man page Ian Rogers 2026-10-17 18:54 ` [RFC PATCH v2 9/9] perf test: Add perf sysinfo shell test Ian Rogers
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®