From: Matt Turner <mattst88@gmail.com>
To: Peter Zijlstra <peterz@infradead.org>,
Ingo Molnar <mingo@redhat.com>,
Arnaldo Carvalho de Melo <acme@kernel.org>,
Namhyung Kim <namhyung@kernel.org>,
Mark Rutland <mark.rutland@arm.com>,
Alexander Shishkin <alexander.shishkin@linux.intel.com>,
Jiri Olsa <jolsa@kernel.org>, Ian Rogers <irogers@google.com>,
Adrian Hunter <adrian.hunter@intel.com>,
James Clark <james.clark@linaro.org>
Cc: Magnus Lindholm <linmag7@gmail.com>,
linux-alpha@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-perf-users@vger.kernel.org,
Matt Turner <mattst88@gmail.com>
Subject: [PATCH v4 2/3] perf python: Add alpha-profileme.py
Date: Wed, 04 Nov 2026 13:53:57 -0500 [thread overview]
Message-ID: <20261104-perf-alpha-profileme-v4-2-1771ea02950e@gmail.com> (raw)
In-Reply-To: <20261104-perf-alpha-profileme-v4-0-1771ea02950e@gmail.com>
perf report shows where Alpha ProfileMe samples land but cannot
aggregate the raw status attached to them.
Add a standalone script, using the perf Python module, that summarizes
it by symbol or, with --pc, by instruction, in the style of DCPI: the
fraction of samples killed on a wrong path, not retired, mispredicted,
taken or stalled, the mean PCTR1 window count, and trap counts by type.
Mispredict and trap types are only counted when I_STAT[TRP] is set
and the instruction was not killed before it was mapped, since they are
stale otherwise. Redirect traps are counted by their
PALcode entry offset in PMPC.
Raw data is only read from cpu PMU events, as a tracepoint's payload can
pass for ProfileMe status. Rows are kept apart by PCTR_CTL[SL1], as a
mean over two things PCTR1 can count means nothing.
On an EV7, sampling a loop whose branch tests random data reports that
branch mispredicted in 21% of its samples, and 0% with sorted data.
Signed-off-by: Matt Turner <mattst88@gmail.com>
---
tools/perf/python/alpha-profileme.py | 178 +++++++++++++++++++++++++++++++++++
1 file changed, 178 insertions(+)
diff --git a/tools/perf/python/alpha-profileme.py b/tools/perf/python/alpha-profileme.py
new file mode 100755
index 000000000000..da374d206187
--- /dev/null
+++ b/tools/perf/python/alpha-profileme.py
@@ -0,0 +1,178 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""Summarize Alpha ProfileMe samples.
+
+Aggregates the ProfileMe status that the Alpha cpu PMU attaches to each
+sample as PERF_SAMPLE_RAW data, by symbol or, with --pc, by instruction.
+
+Record with raw data, e.g.:
+ perf record -R -e cpu/instructions,profileme/ -- ./workload
+A precise event such as instructions:p samples only retired instructions,
+so its killed% and invalid% columns read 0.
+ alpha-profileme.py [-i perf.data] [--pc] [--top N]
+
+Columns:
+ samples ProfileMe samples attributed to the row
+ killed% profiled instruction killed before it was mapped (PM_KILLED_BM)
+ invalid% instruction did not trap and did not retire valid (VAL clear)
+ mispr% conditional branch mispredicted (TRP and MIS)
+ taken% TAK set; only meaningful for rows that are conditional branches
+ stall% instruction stalled between fetch and map (PM_STALLED)
+ pctr1 mean PCTR1 count during the window; what it counts depends on
+ the event: inum retire delay for cycles:p, window cycles for
+ instructions:p, Bcache misses for bcache-misses:p and Mbox
+ replay traps for cpu/mbox-replay/p
+ traps trap counts by type, only for samples with TRP set
+
+TRP, and MIS and the trap type with it, are stale for a killed instruction,
+so mispr%, invalid% and traps ignore them there.
+
+Samples from events that set PCTR1 to count different things are printed
+in separate tables, as their means cannot be compared.
+"""
+from __future__ import annotations
+
+import argparse
+from collections import defaultdict
+import struct
+import sys
+from typing import Optional
+import perf
+
+TRAP_TYPES = {
+ 0: "replay", 2: "dtb2-3l", 3: "dtb2-4l", 4: "fen", 5: "unalign",
+ 6: "dtb1", 7: "dfault", 8: "opcdec", 10: "mchk", 12: "arith",
+ 14: "mt_fpcr", 15: "reset",
+}
+REDIRECTS = {0x481: "iacv", 0x581: "itbmiss", 0x681: "intr"}
+PCTR1_NAMES = ("cycles", "retire delay", "bcache misses", "mbox replays")
+SAMPLE = struct.Struct("<IQQQ")
+
+
+class Row:
+ """ProfileMe status totals for one symbol or instruction."""
+ def __init__(self) -> None:
+ self.samples = 0
+ self.killed = 0
+ self.invalid = 0
+ self.mispredict = 0
+ self.taken = 0
+ self.stalled = 0
+ self.pctr1 = 0
+ self.traps: dict[str, int] = defaultdict(int)
+
+
+def pct(n: int, d: int) -> float:
+ """Return n as a percentage of d."""
+ return 100.0 * n / d if d else 0.0
+
+
+class ProfileMe:
+ """Aggregates ProfileMe raw sample data."""
+ def __init__(self, by_pc: bool, top: int) -> None:
+ self.by_pc = by_pc
+ self.top = top
+ # Keyed by PCTR_CTL[SL1], which sets what PCTR1 counts.
+ self.tables: dict[int, dict[str, Row]] = defaultdict(lambda: defaultdict(Row))
+ self.skipped = 0
+ self.session: Optional[perf.session] = None
+
+ def process_event(self, sample: perf.sample_event) -> None:
+ """Add one sample's ProfileMe status to its row."""
+ raw = sample.raw_buf
+ # Raw data from another PMU or a tracepoint is not ProfileMe status.
+ if (sample.evsel.type not in (perf.TYPE_HARDWARE, perf.TYPE_RAW) or
+ not raw or len(raw) < SAMPLE.size):
+ self.skipped += 1
+ return
+ version, pmpc, i_stat, ctl = SAMPLE.unpack_from(raw)
+ if version < 1:
+ self.skipped += 1
+ return
+
+ symbol = sample.symbol or "[unknown]"
+ if self.by_pc:
+ if pmpc & 1:
+ key = f"PAL+{pmpc & 0x7fff:#x}"
+ else:
+ key = f"{pmpc & ~3:#018x} {symbol}"
+ else:
+ key = f"{sample.dso or '[unknown]'} {symbol}"
+
+ r = self.tables[(ctl >> 2) & 3][key]
+ r.samples += 1
+ killed = (ctl >> 26) & 1
+ trp = (i_stat >> 39) & 1 and not killed
+ if killed:
+ r.killed += 1
+ if not trp and not (ctl >> 1) & 1:
+ r.invalid += 1
+ if ctl & 1:
+ r.taken += 1
+ if (ctl >> 27) & 1:
+ r.stalled += 1
+ r.pctr1 += (ctl >> 6) & 0xfffff
+
+ redirect = REDIRECTS.get(pmpc & 0x7fff) if pmpc & 1 else None
+ if redirect:
+ r.traps[redirect] += 1
+ elif trp:
+ if (i_stat >> 40) & 1:
+ r.mispredict += 1
+ else:
+ tt = (i_stat >> 34) & 0xf
+ r.traps[TRAP_TYPES.get(tt, "other")] += 1
+
+ def print_totals(self) -> None:
+ """Print the busiest rows of each table."""
+ total = sum(r.samples for rows in self.tables.values() for r in rows.values())
+ print(f"{total} ProfileMe samples, {self.skipped} without ProfileMe data")
+ for sl1, rows in sorted(self.tables.items()):
+ print(f"\npctr1 counts {PCTR1_NAMES[sl1]}:")
+ self.print_table(rows)
+
+ def print_table(self, rows: dict[str, Row]) -> None:
+ """Print the busiest rows of one table."""
+ print(f"{'samples':>8} {'killed%':>7} {'invalid%':>8} {'mispr%':>6} "
+ f"{'taken%':>7} {'stall%':>6} {'pctr1':>7} {'location':<40} traps")
+ ordered = sorted(rows.items(), key=lambda kv: kv[1].samples, reverse=True)
+ for key, r in ordered[:self.top]:
+ traps = " ".join(f"{k}={v}" for k, v in
+ sorted(r.traps.items(), key=lambda kv: -kv[1]))
+ print(f"{r.samples:8d} {pct(r.killed, r.samples):7.1f} "
+ f"{pct(r.invalid, r.samples):8.1f} "
+ f"{pct(r.mispredict, r.samples):6.1f} "
+ f"{pct(r.taken, r.samples):7.1f} "
+ f"{pct(r.stalled, r.samples):6.1f} "
+ f"{r.pctr1 / r.samples:7.1f} {key:<40} {traps}")
+
+ def run(self, input_file: str) -> None:
+ """Run the session."""
+ self.session = perf.session(perf.data(input_file), sample=self.process_event)
+ try:
+ self.session.process_events()
+ finally:
+ # Break the reference cycle between perf.session and self.process_event
+ # because perf.session lacks cyclic GC support (tp_traverse).
+ self.session = None
+ self.print_totals()
+
+
+def main() -> None:
+ """Main function."""
+ parser = argparse.ArgumentParser(description="Summarize Alpha ProfileMe samples")
+ parser.add_argument("-i", "--input", default="perf.data", help="Input file")
+ parser.add_argument("--pc", action="store_true", help="aggregate by instruction")
+ parser.add_argument("--top", type=int, default=30, help="rows to print")
+ args = parser.parse_args()
+
+ analyzer = ProfileMe(args.pc, args.top)
+ try:
+ analyzer.run(args.input)
+ except IOError as e:
+ print(e, file=sys.stderr)
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
--
2.55.0
next prev parent reply other threads:[~2026-11-04 18:54 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-04 18:53 [PATCH v4 0/3] perf alpha: Decode ProfileMe samples Matt Turner
2026-11-04 18:53 ` [PATCH v4 1/3] perf alpha: Decode ProfileMe raw samples Matt Turner
2026-11-04 18:53 ` Matt Turner [this message]
2026-11-04 22:27 ` [PATCH v4 2/3] perf python: Add alpha-profileme.py Magnus Lindholm
2026-11-04 18:53 ` [PATCH v4 3/3] perf alpha: Add perf mem support Matt Turner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261104-perf-alpha-profileme-v4-2-1771ea02950e@gmail.com \
--to=mattst88@gmail.com \
--cc=acme@kernel.org \
--cc=adrian.hunter@intel.com \
--cc=alexander.shishkin@linux.intel.com \
--cc=irogers@google.com \
--cc=james.clark@linaro.org \
--cc=jolsa@kernel.org \
--cc=linmag7@gmail.com \
--cc=linux-alpha@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=mark.rutland@arm.com \
--cc=mingo@redhat.com \
--cc=namhyung@kernel.org \
--cc=peterz@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®