* [PATCH v7 34/59] perf compaction-times: Port compaction-times to use python module
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Add a port of the compaction-times script that uses the perf python
module directly. This approach is significantly faster than using perf
script callbacks as it avoids creating intermediate dictionaries for
all event fields.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2: Fixed Closure Call: Changed cls.fobj.filter(pid, comm) to
cls.fobj(pid, comm) . Since fobj is a function (closure) and not a
class instance, calling .filter() on it would raise an
AttributeError .
---
tools/perf/python/compaction-times.py | 326 ++++++++++++++++++++++++++
1 file changed, 326 insertions(+)
create mode 100755 tools/perf/python/compaction-times.py
diff --git a/tools/perf/python/compaction-times.py b/tools/perf/python/compaction-times.py
new file mode 100755
index 000000000000..7f17c251ded7
--- /dev/null
+++ b/tools/perf/python/compaction-times.py
@@ -0,0 +1,326 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+Report time spent in memory compaction.
+
+Memory compaction is a feature in the Linux kernel that defragments memory
+by moving used pages to create larger contiguous blocks of free memory. This
+is particularly useful for allocating huge pages.
+
+This script processes trace events related to memory compaction and reports:
+- Total time spent in compaction (stall time).
+- Statistics for page migration (moved vs. failed).
+- Statistics for the free scanner (scanned vs. isolated pages).
+- Statistics for the migration scanner (scanned vs. isolated pages).
+
+Definitions:
+- **Compaction**: Defragmenting memory by moving allocated pages.
+- **Migration**: Moving pages from their current location to free pages found by the free scanner.
+- **Free Scanner**: Scans memory (typically from the end of a zone) to find free pages.
+- **Migration Scanner**: Scans memory (typically from the beginning of a zone)
+ to find pages to move.
+- **Isolated Pages**: Pages that have been temporarily removed from the buddy
+ system for migration or as migration targets.
+
+Ported from tools/perf/scripts/python/compaction-times.py to the modern perf Python module.
+"""
+
+import argparse
+import enum
+import re
+import sys
+from typing import Callable, Dict, List, Optional, Any
+import perf
+
+class Popt(enum.IntEnum):
+ """Process display options."""
+ DISP_DFL = 0
+ DISP_PROC = 1
+ DISP_PROC_VERBOSE = 2
+
+class Topt(enum.IntFlag):
+ """Trace display options."""
+ DISP_TIME = 0
+ DISP_MIG = 1
+ DISP_ISOLFREE = 2
+ DISP_ISOLMIG = 4
+ DISP_ALL = DISP_MIG | DISP_ISOLFREE | DISP_ISOLMIG
+
+# Globals to satisfy pylint when accessed in functions before assignment in main.
+OPT_NS = True
+opt_disp = Topt.DISP_ALL
+opt_proc = Popt.DISP_DFL
+session = None
+
+def get_comm_filter(regex: re.Pattern) -> Callable[[int, str], bool]:
+ """Returns a filter function based on command regex."""
+ def filter_func(_pid: int, comm: str) -> bool:
+ regex_match = regex.search(comm)
+ return regex_match is None or regex_match.group() == ""
+ return filter_func
+
+def get_pid_filter(low_str: str, high_str: str) -> Callable[[int, str], bool]:
+ """Returns a filter function based on PID range."""
+ low = 0 if low_str == "" else int(low_str)
+ high = 0 if high_str == "" else int(high_str)
+
+ def filter_func(pid: int, _comm: str) -> bool:
+ return not (pid >= low and (high == 0 or pid <= high))
+ return filter_func
+
+def ns_to_time(ns: int) -> str:
+ """Format nanoseconds to string based on options."""
+ return f"{ns}ns" if OPT_NS else f"{round(ns, -3) // 1000}us"
+
+class Pair:
+ """Represents a pair of related counters (e.g., scanned vs isolated, moved vs failed)."""
+ def __init__(self, aval: int, bval: int,
+ alabel: Optional[str] = None, blabel: Optional[str] = None):
+ self.alabel = alabel
+ self.blabel = blabel
+ self.aval = aval
+ self.bval = bval
+
+ def __add__(self, rhs: 'Pair') -> 'Pair':
+ self.aval += rhs.aval
+ self.bval += rhs.bval
+ return self
+
+ def __str__(self) -> str:
+ return f"{self.alabel}={self.aval} {self.blabel}={self.bval}"
+
+class Cnode:
+ """Holds statistics for a single compaction event or an aggregated set of events."""
+ def __init__(self, ns: int):
+ self.ns = ns
+ self.migrated = Pair(0, 0, "moved", "failed")
+ self.fscan = Pair(0, 0, "scanned", "isolated")
+ self.mscan = Pair(0, 0, "scanned", "isolated")
+
+ def __add__(self, rhs: 'Cnode') -> 'Cnode':
+ self.ns += rhs.ns
+ self.migrated += rhs.migrated
+ self.fscan += rhs.fscan
+ self.mscan += rhs.mscan
+ return self
+
+ def __str__(self) -> str:
+ prev = False
+ s = f"{ns_to_time(self.ns)} "
+ if opt_disp & Topt.DISP_MIG:
+ s += f"migration: {self.migrated}"
+ prev = True
+ if opt_disp & Topt.DISP_ISOLFREE:
+ s += f"{' ' if prev else ''}free_scanner: {self.fscan}"
+ prev = True
+ if opt_disp & Topt.DISP_ISOLMIG:
+ s += f"{' ' if prev else ''}migration_scanner: {self.mscan}"
+ return s
+
+ def complete(self, secs: int, nsecs: int) -> None:
+ """Complete the node with duration."""
+ self.ns = (secs * 1000000000 + nsecs) - self.ns
+
+ def increment(self, migrated: Optional[Pair], fscan: Optional[Pair],
+ mscan: Optional[Pair]) -> None:
+ """Increment statistics."""
+ if migrated is not None:
+ self.migrated += migrated
+ if fscan is not None:
+ self.fscan += fscan
+ if mscan is not None:
+ self.mscan += mscan
+
+class Chead:
+ """Aggregates compaction statistics per process (PID) and maintains total statistics."""
+ heads: Dict[int, 'Chead'] = {}
+ val = Cnode(0)
+ fobj: Optional[Any] = None
+
+ @classmethod
+ def add_filter(cls, fobj: Any) -> None:
+ """Add a filter object."""
+ cls.fobj = fobj
+
+ @classmethod
+ def create_pending(cls, pid: int, comm: str, start_secs: int, start_nsecs: int) -> None:
+ """Create a pending node for a process."""
+ filtered = False
+ try:
+ head = cls.heads[pid]
+ filtered = head.is_filtered()
+ except KeyError:
+ if cls.fobj is not None:
+ filtered = cls.fobj(pid, comm)
+ head = cls.heads[pid] = Chead(comm, pid, filtered)
+
+ if not filtered:
+ head.mark_pending(start_secs, start_nsecs)
+
+ @classmethod
+ def increment_pending(cls, pid: int, migrated: Optional[Pair],
+ fscan: Optional[Pair], mscan: Optional[Pair]) -> None:
+ """Increment pending stats for a process."""
+ if pid not in cls.heads:
+ return
+ head = cls.heads[pid]
+ if not head.is_filtered():
+ if head.is_pending():
+ head.do_increment(migrated, fscan, mscan)
+ else:
+ sys.stderr.write(f"missing start compaction event for pid {pid}\n")
+
+ @classmethod
+ def complete_pending(cls, pid: int, secs: int, nsecs: int) -> None:
+ """Complete pending stats for a process."""
+ if pid not in cls.heads:
+ return
+ head = cls.heads[pid]
+ if not head.is_filtered():
+ if head.is_pending():
+ head.make_complete(secs, nsecs)
+ else:
+ sys.stderr.write(f"missing start compaction event for pid {pid}\n")
+
+ @classmethod
+ def gen(cls):
+ """Generate heads for display."""
+ if opt_proc != Popt.DISP_DFL:
+ yield from cls.heads.values()
+
+ @classmethod
+ def get_total(cls) -> Cnode:
+ """Get total statistics."""
+ return cls.val
+
+ def __init__(self, comm: str, pid: int, filtered: bool):
+ self.comm = comm
+ self.pid = pid
+ self.val = Cnode(0)
+ self.pending: Optional[Cnode] = None
+ self.filtered = filtered
+ self.list: List[Cnode] = []
+
+ def mark_pending(self, secs: int, nsecs: int) -> None:
+ """Mark node as pending."""
+ self.pending = Cnode(secs * 1000000000 + nsecs)
+
+ def do_increment(self, migrated: Optional[Pair], fscan: Optional[Pair],
+ mscan: Optional[Pair]) -> None:
+ """Increment pending stats."""
+ if self.pending is not None:
+ self.pending.increment(migrated, fscan, mscan)
+
+ def make_complete(self, secs: int, nsecs: int) -> None:
+ """Make pending stats complete."""
+ if self.pending is not None:
+ self.pending.complete(secs, nsecs)
+ Chead.val += self.pending
+
+ if opt_proc != Popt.DISP_DFL:
+ self.val += self.pending
+
+ if opt_proc == Popt.DISP_PROC_VERBOSE:
+ self.list.append(self.pending)
+ self.pending = None
+
+ def enumerate(self) -> None:
+ """Enumerate verbose stats."""
+ if opt_proc == Popt.DISP_PROC_VERBOSE and not self.is_filtered():
+ for i, pelem in enumerate(self.list):
+ sys.stdout.write(f"{self.pid}[{self.comm}].{i+1}: {pelem}\n")
+
+ def is_pending(self) -> bool:
+ """Check if node is pending."""
+ return self.pending is not None
+
+ def is_filtered(self) -> bool:
+ """Check if node is filtered."""
+ return self.filtered
+
+ def display(self) -> None:
+ """Display stats."""
+ if not self.is_filtered():
+ sys.stdout.write(f"{self.pid}[{self.comm}]: {self.val}\n")
+
+def trace_end() -> None:
+ """Called at the end of trace processing."""
+ sys.stdout.write(f"total: {Chead.get_total()}\n")
+ for i in Chead.gen():
+ i.display()
+ i.enumerate()
+
+def process_event(sample: perf.sample_event) -> None:
+ """Callback for processing events."""
+ event_name = str(sample.evsel)
+ pid = sample.sample_pid
+ comm = session.find_thread(pid).comm() if session else "[unknown]"
+ secs = sample.sample_time // 1000000000
+ nsecs = sample.sample_time % 1000000000
+
+ if "evsel(compaction:mm_compaction_begin)" in event_name:
+ Chead.create_pending(pid, comm, secs, nsecs)
+ elif "evsel(compaction:mm_compaction_end)" in event_name:
+ Chead.complete_pending(pid, secs, nsecs)
+ elif "evsel(compaction:mm_compaction_migratepages)" in event_name:
+ Chead.increment_pending(pid, Pair(sample.nr_migrated, sample.nr_failed), None, None)
+ elif "evsel(compaction:mm_compaction_isolate_freepages)" in event_name:
+ Chead.increment_pending(pid, None, Pair(sample.nr_scanned, sample.nr_taken), None)
+ elif "evsel(compaction:mm_compaction_isolate_migratepages)" in event_name:
+ Chead.increment_pending(pid, None, None, Pair(sample.nr_scanned, sample.nr_taken))
+
+if __name__ == "__main__":
+ ap = argparse.ArgumentParser(description="Report time spent in compaction")
+ ap.add_argument("-p", action="store_true", help="display by process")
+ ap.add_argument("-pv", action="store_true", help="display by process (verbose)")
+ ap.add_argument("-u", action="store_true", help="display results in microseconds")
+ ap.add_argument("-t", action="store_true", help="display stall times only")
+ ap.add_argument("-m", action="store_true", help="display stats for migration")
+ ap.add_argument("-fs", action="store_true", help="display stats for free scanner")
+ ap.add_argument("-ms", action="store_true", help="display stats for migration scanner")
+ ap.add_argument("filter", nargs="?", help="pid|pid-range|comm-regex")
+ ap.add_argument("-i", "--input", default="perf.data", help="Input file name")
+ args = ap.parse_args()
+
+ opt_proc = Popt.DISP_DFL
+ if args.pv:
+ opt_proc = Popt.DISP_PROC_VERBOSE
+ elif args.p:
+ opt_proc = Popt.DISP_PROC
+
+ OPT_NS = not args.u
+
+ opt_disp = Topt.DISP_ALL
+ if args.t or args.m or args.fs or args.ms:
+ opt_disp = Topt(0)
+ if args.t:
+ opt_disp |= Topt.DISP_TIME
+ if args.m:
+ opt_disp |= Topt.DISP_MIG
+ if args.fs:
+ opt_disp |= Topt.DISP_ISOLFREE
+ if args.ms:
+ opt_disp |= Topt.DISP_ISOLMIG
+
+ if args.filter:
+ PID_PATTERN = r"^(\d*)-(\d*)$|^(\d*)$"
+ pid_re = re.compile(PID_PATTERN)
+ match = pid_re.search(args.filter)
+ filter_obj: Any = None
+ if match is not None and match.group() != "":
+ if match.group(3) is not None:
+ filter_obj = get_pid_filter(match.group(3), match.group(3))
+ else:
+ filter_obj = get_pid_filter(match.group(1), match.group(2))
+ else:
+ try:
+ comm_re = re.compile(args.filter)
+ except re.error:
+ sys.stderr.write(f"invalid regex '{args.filter}'\n")
+ sys.exit(1)
+ filter_obj = get_comm_filter(comm_re)
+ Chead.add_filter(filter_obj)
+
+ session = perf.session(perf.data(args.input), sample=process_event)
+ session.process_events()
+ trace_end()
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 40/59] perf net_dropmonitor: Port net_dropmonitor to use python module
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Ported from tools/perf/scripts/python/.
- Refactored the script to use a class structure (DropMonitor) to
encapsulate state.
- Used perf.session for event processing instead of legacy global
handlers.
- Maintained the manual /proc/kallsyms reading and binary search for
symbol resolution as in the original script.
- Cleaned up Python 2 compatibility artifacts.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2:
1. Fixed Sorting of Locations: Kept location as an integer in the
drop_log dictionary keys so that they are sorted numerically rather
than lexicographically when generating the report.
2. Fixed Interrupt Handling: Moved the call to get_kallsyms_table()
and print_drop_table() outside the try-except block. This ensures
that the reporting phase happens exactly once, even if the user
interrupts the trace with Ctrl-C.
---
tools/perf/python/net_dropmonitor.py | 58 ++++++++++++++++++++++++++++
1 file changed, 58 insertions(+)
create mode 100755 tools/perf/python/net_dropmonitor.py
diff --git a/tools/perf/python/net_dropmonitor.py b/tools/perf/python/net_dropmonitor.py
new file mode 100755
index 000000000000..25ea2a66ed3c
--- /dev/null
+++ b/tools/perf/python/net_dropmonitor.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+Monitor the system for dropped packets and produce a report of drop locations and counts.
+Ported from tools/perf/scripts/python/net_dropmonitor.py
+"""
+
+import argparse
+from collections import defaultdict
+import sys
+import perf
+
+
+class DropMonitor:
+ """Monitors dropped packets and aggregates counts by location."""
+
+ def __init__(self):
+ self.drop_log: dict[tuple[str, int], int] = defaultdict(int)
+ self.unhandled: dict[str, int] = defaultdict(int)
+
+ def print_drop_table(self) -> None:
+ """Print aggregated results."""
+ print(f"{'LOCATION':>25} {'OFFSET':>25} {'COUNT':>25}")
+ for (sym, off) in sorted(self.drop_log.keys()):
+ print(f"{sym:>25} {off:>25d} {self.drop_log[(sym, off)]:>25d}")
+
+ def process_event(self, sample: perf.sample_event) -> None:
+ """Process a single sample event."""
+ if str(sample.evsel) != "evsel(skb:kfree_skb)":
+ return
+
+ try:
+ symbol = getattr(sample, "symbol", "[unknown]")
+ symoff = getattr(sample, "symoff", 0)
+ self.drop_log[(symbol, symoff)] += 1
+ except AttributeError:
+ self.unhandled[str(sample.evsel)] += 1
+
+
+if __name__ == "__main__":
+ ap = argparse.ArgumentParser(
+ description="Monitor the system for dropped packets and produce a "
+ "report of drop locations and counts.")
+ ap.add_argument("-i", "--input", default="perf.data", help="Input file name")
+ args = ap.parse_args()
+
+ monitor = DropMonitor()
+
+ try:
+ session = perf.session(perf.data(args.input), sample=monitor.process_event)
+ session.process_events()
+ except KeyboardInterrupt:
+ print("\nStopping trace...")
+ except Exception as e:
+ print(f"Error processing events: {e}")
+ sys.exit(1)
+
+ monitor.print_drop_table()
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 36/59] perf export-to-sqlite: Port export-to-sqlite to use python module
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
This commit ports the export-to-sqlite.py script to use the modern perf
Python module and the standard library sqlite3 module. It drops the
dependency on PySide2.QtSql.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v5:
1. Fix Callchain Resolution: Corrected attribute lookups on callchain
nodes. The `dso` and `symbol` properties already return strings, so
attempting to get a `.name` attribute from them failed and caused
fallback to "Unknown_...".
---
tools/perf/python/export-to-sqlite.py | 372 ++++++++++++++++++++++++++
1 file changed, 372 insertions(+)
create mode 100755 tools/perf/python/export-to-sqlite.py
diff --git a/tools/perf/python/export-to-sqlite.py b/tools/perf/python/export-to-sqlite.py
new file mode 100755
index 000000000000..736b56ff8d59
--- /dev/null
+++ b/tools/perf/python/export-to-sqlite.py
@@ -0,0 +1,372 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+Export perf data to a sqlite3 database.
+
+This script has been ported to use the modern perf Python module and the
+standard library sqlite3 module. It no longer requires PySide2 or QtSql
+for exporting.
+
+Examples of using this script with Intel PT:
+
+ $ perf record -e intel_pt//u ls
+ $ python tools/perf/python/export-to-sqlite.py -i perf.data -o pt_example
+
+To browse the database, sqlite3 can be used e.g.
+
+ $ sqlite3 pt_example
+ sqlite> .header on
+ sqlite> select * from samples_view where id < 10;
+ sqlite> .mode column
+ sqlite> select * from samples_view where id < 10;
+ sqlite> .tables
+ sqlite> .schema samples_view
+ sqlite> .quit
+
+An example of using the database is provided by the script
+exported-sql-viewer.py. Refer to that script for details.
+
+Ported from tools/perf/scripts/python/export-to-sqlite.py
+"""
+
+import argparse
+import os
+import sqlite3
+import sys
+from typing import Dict, Optional
+import perf
+
+
+class DatabaseExporter:
+ """Handles database connection and exporting of perf events."""
+
+ def __init__(self, db_path: str):
+ self.con = sqlite3.connect(db_path)
+ self.session: Optional[perf.session] = None
+ self.sample_count = 0
+
+ # Caches and counters grouped to reduce instance attributes
+ self.caches: Dict[str, dict] = {
+ 'threads': {},
+ 'comms': {},
+ 'dsos': {},
+ 'symbols': {},
+ 'events': {},
+ 'branch_types': {},
+ 'call_paths': {}
+ }
+
+ self.next_id = {
+ 'thread': 1,
+ 'comm': 1,
+ 'dso': 1,
+ 'symbol': 1,
+ 'event': 1,
+ 'branch_type': 1,
+ 'call_path': 1
+ }
+
+ self.create_tables()
+
+ def create_tables(self) -> None:
+ """Create database tables."""
+ self.con.execute("""
+ CREATE TABLE IF NOT EXISTS selected_events (
+ id INTEGER NOT NULL PRIMARY KEY,
+ name VARCHAR(80))
+ """)
+ self.con.execute("""
+ CREATE TABLE IF NOT EXISTS machines (
+ id INTEGER NOT NULL PRIMARY KEY,
+ pid INTEGER,
+ root_dir VARCHAR(4096))
+ """)
+ self.con.execute("""
+ CREATE TABLE IF NOT EXISTS threads (
+ id INTEGER NOT NULL PRIMARY KEY,
+ machine_id BIGINT,
+ process_id BIGINT,
+ pid INTEGER,
+ tid INTEGER)
+ """)
+ self.con.execute("""
+ CREATE TABLE IF NOT EXISTS comms (
+ id INTEGER NOT NULL PRIMARY KEY,
+ comm VARCHAR(16),
+ c_thread_id BIGINT,
+ c_time BIGINT,
+ exec_flag BOOLEAN)
+ """)
+ self.con.execute("""
+ CREATE TABLE IF NOT EXISTS comm_threads (
+ id INTEGER NOT NULL PRIMARY KEY,
+ comm_id BIGINT,
+ thread_id BIGINT)
+ """)
+ self.con.execute("""
+ CREATE TABLE IF NOT EXISTS dsos (
+ id INTEGER NOT NULL PRIMARY KEY,
+ machine_id BIGINT,
+ short_name VARCHAR(256),
+ long_name VARCHAR(4096),
+ build_id VARCHAR(64))
+ """)
+ self.con.execute("""
+ CREATE TABLE IF NOT EXISTS symbols (
+ id INTEGER NOT NULL PRIMARY KEY,
+ dso_id BIGINT,
+ sym_start BIGINT,
+ sym_end BIGINT,
+ binding INTEGER,
+ name VARCHAR(2048))
+ """)
+ self.con.execute("""
+ CREATE TABLE IF NOT EXISTS branch_types (
+ id INTEGER NOT NULL PRIMARY KEY,
+ name VARCHAR(80))
+ """)
+ self.con.execute("""
+ CREATE TABLE IF NOT EXISTS samples (
+ id INTEGER NOT NULL PRIMARY KEY,
+ evsel_id BIGINT,
+ machine_id BIGINT,
+ thread_id BIGINT,
+ comm_id BIGINT,
+ dso_id BIGINT,
+ symbol_id BIGINT,
+ sym_offset BIGINT,
+ ip BIGINT,
+ time BIGINT,
+ cpu INTEGER,
+ to_dso_id BIGINT,
+ to_symbol_id BIGINT,
+ to_sym_offset BIGINT,
+ to_ip BIGINT,
+ period BIGINT,
+ weight BIGINT,
+ transaction_ BIGINT,
+ data_src BIGINT,
+ branch_type INTEGER,
+ in_tx BOOLEAN,
+ call_path_id BIGINT,
+ insn_count BIGINT,
+ cyc_count BIGINT,
+ flags INTEGER)
+ """)
+ self.con.execute("""
+ CREATE TABLE IF NOT EXISTS call_paths (
+ id INTEGER NOT NULL PRIMARY KEY,
+ parent_id BIGINT,
+ symbol_id BIGINT,
+ ip BIGINT)
+ """)
+ self.con.execute("""
+ CREATE VIEW IF NOT EXISTS samples_view AS
+ SELECT s.id, e.name as event, t.pid, t.tid, c.comm,
+ d.short_name as dso, sym.name as symbol, s.sym_offset,
+ s.ip, s.time, s.cpu
+ FROM samples s
+ JOIN selected_events e ON s.evsel_id = e.id
+ JOIN threads t ON s.thread_id = t.id
+ JOIN comms c ON s.comm_id = c.id
+ JOIN dsos d ON s.dso_id = d.id
+ JOIN symbols sym ON s.symbol_id = sym.id;
+ """)
+
+ # id == 0 means unknown. It is easier to create records for them than
+ # replace the zeroes with NULLs
+ self.con.execute("INSERT OR IGNORE INTO selected_events VALUES (0, 'unknown')")
+ self.con.execute("INSERT OR IGNORE INTO machines VALUES (0, 0, 'unknown')")
+ self.con.execute("INSERT OR IGNORE INTO threads VALUES (0, 0, 0, -1, -1)")
+ self.con.execute("INSERT OR IGNORE INTO comms VALUES (0, 'unknown', 0, 0, 0)")
+ self.con.execute("INSERT OR IGNORE INTO dsos VALUES (0, 0, 'unknown', 'unknown', '')")
+ self.con.execute("INSERT OR IGNORE INTO symbols VALUES (0, 0, 0, 0, 0, 'unknown')")
+
+ def get_event_id(self, name: str) -> int:
+ """Get or create event ID."""
+ if name in self.caches['events']:
+ return self.caches['events'][name]
+ event_id = self.next_id['event']
+ self.con.execute("INSERT INTO selected_events VALUES (?, ?)",
+ (event_id, name))
+ self.caches['events'][name] = event_id
+ self.next_id['event'] += 1
+ return event_id
+
+ def get_thread_id(self, pid: int, tid: int) -> int:
+ """Get or create thread ID."""
+ key = (pid, tid)
+ if key in self.caches['threads']:
+ return self.caches['threads'][key]
+ thread_id = self.next_id['thread']
+ self.con.execute("INSERT INTO threads VALUES (?, ?, ?, ?, ?)",
+ (thread_id, 0, pid, pid, tid))
+ self.caches['threads'][key] = thread_id
+ self.next_id['thread'] += 1
+ return thread_id
+
+ def get_comm_id(self, comm: str, thread_id: int) -> int:
+ """Get or create comm ID."""
+ if comm in self.caches['comms']:
+ return self.caches['comms'][comm]
+ comm_id = self.next_id['comm']
+ self.con.execute("INSERT INTO comms VALUES (?, ?, ?, ?, ?)",
+ (comm_id, comm, thread_id, 0, 0))
+ self.con.execute("INSERT INTO comm_threads VALUES (?, ?, ?)",
+ (comm_id, comm_id, thread_id))
+ self.caches['comms'][comm] = comm_id
+ self.next_id['comm'] += 1
+ return comm_id
+
+ def get_dso_id(self, short_name: str, long_name: str,
+ build_id: str) -> int:
+ """Get or create DSO ID."""
+ if short_name in self.caches['dsos']:
+ return self.caches['dsos'][short_name]
+ dso_id = self.next_id['dso']
+ self.con.execute("INSERT INTO dsos VALUES (?, ?, ?, ?, ?)",
+ (dso_id, 0, short_name, long_name, build_id))
+ self.caches['dsos'][short_name] = dso_id
+ self.next_id['dso'] += 1
+ return dso_id
+
+ def get_symbol_id(self, dso_id: int, name: str, start: int,
+ end: int) -> int:
+ """Get or create symbol ID."""
+ key = (dso_id, name)
+ if key in self.caches['symbols']:
+ return self.caches['symbols'][key]
+ symbol_id = self.next_id['symbol']
+ self.con.execute("INSERT INTO symbols VALUES (?, ?, ?, ?, ?, ?)",
+ (symbol_id, dso_id, start, end, 0, name))
+ self.caches['symbols'][key] = symbol_id
+ self.next_id['symbol'] += 1
+ return symbol_id
+
+ def get_call_path_id(self, parent_id: int, symbol_id: int,
+ ip: int) -> int:
+ """Get or create call path ID."""
+ key = (parent_id, symbol_id, ip)
+ if key in self.caches['call_paths']:
+ return self.caches['call_paths'][key]
+ call_path_id = self.next_id['call_path']
+ self.con.execute("INSERT INTO call_paths VALUES (?, ?, ?, ?)",
+ (call_path_id, parent_id, symbol_id, ip))
+ self.caches['call_paths'][key] = call_path_id
+ self.next_id['call_path'] += 1
+ return call_path_id
+
+ def process_event(self, sample: perf.sample_event) -> None:
+ """Callback for processing events."""
+ thread_id = self.get_thread_id(sample.sample_pid, sample.sample_tid)
+
+ comm = "Unknown_comm"
+ try:
+ if self.session is not None:
+ proc = self.session.find_thread(sample.sample_pid)
+ if proc:
+ comm = proc.comm()
+ except TypeError:
+ pass
+ comm_id = self.get_comm_id(comm, thread_id)
+
+ dso_id = self.get_dso_id(
+ getattr(sample, 'dso', "Unknown_dso") or "Unknown_dso",
+ getattr(sample, 'dso_long_name', "Unknown_dso_long") or "Unknown_dso_long",
+ getattr(sample, 'dso_bid', "") or ""
+ )
+
+ symbol_id = self.get_symbol_id(
+ dso_id,
+ getattr(sample, 'symbol', "Unknown_symbol") or "Unknown_symbol",
+ getattr(sample, 'sym_start', 0) or 0,
+ getattr(sample, 'sym_end', 0) or 0
+ )
+
+ # Handle callchain
+ call_path_id = 0
+ if hasattr(sample, 'callchain') and sample.callchain:
+ parent_id = 0
+ for node in sample.callchain:
+ dso_name = getattr(node, 'dso', "Unknown_dso") or "Unknown_dso"
+ symbol_name = getattr(node, 'symbol', "Unknown_symbol") or "Unknown_symbol"
+
+ node_dso_id = self.get_dso_id(dso_name, dso_name, "")
+ node_symbol_id = self.get_symbol_id(node_dso_id, symbol_name, 0, 0)
+
+ parent_id = self.get_call_path_id(parent_id, node_symbol_id, node.ip)
+ call_path_id = parent_id
+ else:
+ call_path_id = 0
+
+ # Insert sample
+ self.con.execute("""
+ INSERT INTO samples VALUES (
+ NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
+ )
+ """, (
+ self.get_event_id(getattr(sample.evsel, 'name', str(sample.evsel))),
+ 0, thread_id, comm_id,
+ dso_id, symbol_id, getattr(sample, 'sym_offset', 0),
+ sample.sample_ip, sample.sample_time, sample.sample_cpu,
+ 0, 0, 0, 0, # to_dso, to_symbol, to_sym_offset, to_ip
+ getattr(sample, 'sample_period', 0) or 0,
+ getattr(sample, 'sample_weight', 0) or 0,
+ getattr(sample, 'transaction_', 0),
+ getattr(sample, 'data_src', 0),
+ 0, # branch_type
+ getattr(sample, 'in_tx', 0),
+ call_path_id,
+ getattr(sample, 'insn_count', 0),
+ getattr(sample, 'cyc_count', 0),
+ getattr(sample, 'flags', 0)
+ ))
+
+ self.sample_count += 1
+ if self.sample_count % 10000 == 0:
+ self.commit()
+
+ def commit(self) -> None:
+ """Commit transaction."""
+ self.con.commit()
+
+ def close(self) -> None:
+ """Close connection."""
+ self.con.close()
+
+
+if __name__ == "__main__":
+ ap = argparse.ArgumentParser(
+ description="Export perf data to a sqlite3 database")
+ ap.add_argument("-i", "--input", default="perf.data",
+ help="Input file name")
+ ap.add_argument("-o", "--output", default="perf.db",
+ help="Output database name")
+ args = ap.parse_args()
+
+ try:
+ fd = os.open(args.output, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
+ os.close(fd)
+ except FileExistsError:
+ print(f"Error: {args.output} already exists")
+ sys.exit(1)
+
+ exporter = DatabaseExporter(args.output)
+
+ session = None
+ error_occurred = False
+ try:
+ session = perf.session(perf.data(args.input),
+ sample=exporter.process_event)
+ exporter.session = session
+ session.process_events()
+ exporter.commit()
+ print(f"Successfully exported to {args.output}")
+ except Exception as e:
+ print(f"Error processing events: {e}")
+ error_occurred = True
+ finally:
+ exporter.close()
+ if error_occurred:
+ if os.path.exists(args.output):
+ os.remove(args.output)
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 41/59] perf netdev-times: Port netdev-times to use python module
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Ported from tools/perf/scripts/python/.
- Refactored the script to use a class structure (NetDevTimesAnalyzer)
to encapsulate state.
- Used perf.session for event collection and processed them in time
order at the end to match legacy behavior.
- Extracted tracepoint fields directly from sample attributes.
- Moved format string constants to module level.
- Cleaned up Python 2 compatibility artifacts (like cmp_to_key).
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2: Corrected Field Names: Fixed getattr calls for skblen and dev_name
to use "len" and "name" respectively, as exposed by the actual
tracepoints.
---
tools/perf/python/netdev-times.py | 472 ++++++++++++++++++++++++++++++
1 file changed, 472 insertions(+)
create mode 100755 tools/perf/python/netdev-times.py
diff --git a/tools/perf/python/netdev-times.py b/tools/perf/python/netdev-times.py
new file mode 100755
index 000000000000..3fe46b4e7f21
--- /dev/null
+++ b/tools/perf/python/netdev-times.py
@@ -0,0 +1,472 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+Display a process of packets and processed time.
+It helps us to investigate networking or network device.
+
+Ported from tools/perf/scripts/python/netdev-times.py
+"""
+
+import argparse
+from collections import defaultdict
+import sys
+from typing import Optional
+import perf
+
+# Format for displaying rx packet processing
+PF_IRQ_ENTRY = " irq_entry(+%.3fmsec irq=%d:%s)"
+PF_SOFT_ENTRY = " softirq_entry(+%.3fmsec)"
+PF_NAPI_POLL = " napi_poll_exit(+%.3fmsec %s)"
+PF_JOINT = " |"
+PF_WJOINT = " | |"
+PF_NET_RECV = " |---netif_receive_skb(+%.3fmsec skb=%x len=%d)"
+PF_NET_RX = " |---netif_rx(+%.3fmsec skb=%x)"
+PF_CPY_DGRAM = " | skb_copy_datagram_iovec(+%.3fmsec %d:%s)"
+PF_KFREE_SKB = " | kfree_skb(+%.3fmsec location=%x)"
+PF_CONS_SKB = " | consume_skb(+%.3fmsec)"
+
+
+class NetDevTimesAnalyzer:
+ """Analyzes network device events and prints charts."""
+
+ def __init__(self, cfg: argparse.Namespace):
+ self.args = cfg
+ self.session: Optional[perf.session] = None
+ self.show_tx = cfg.tx or (not cfg.tx and not cfg.rx)
+ self.show_rx = cfg.rx or (not cfg.tx and not cfg.rx)
+ self.dev = cfg.dev
+ self.debug = cfg.debug
+ self.buffer_budget = 65536
+ self.irq_dic: dict[int, list[dict]] = defaultdict(list)
+ self.net_rx_dic: dict[int, dict] = {}
+ self.receive_hunk_list: list[dict] = []
+ self.rx_skb_list: list[dict] = []
+ self.tx_queue_list: list[dict] = []
+ self.tx_xmit_list: list[dict] = []
+ self.tx_free_list: list[dict] = []
+
+ self.buffer_budget = 65536
+ self.of_count_rx_skb_list = 0
+ self.of_count_tx_queue_list = 0
+ self.of_count_tx_xmit_list = 0
+
+ def diff_msec(self, src: int, dst: int) -> float:
+ """Calculate a time interval(msec) from src(nsec) to dst(nsec)."""
+ return (dst - src) / 1000000.0
+
+ def print_transmit(self, hunk: dict) -> None:
+ """Display a process of transmitting a packet."""
+ if self.dev and hunk['dev'].find(self.dev) < 0:
+ return
+ queue_t_sec = hunk['queue_t'] // 1000000000
+ queue_t_usec = hunk['queue_t'] % 1000000000 // 1000
+ print(f"{hunk['dev']:7s} {hunk['len']:5d} "
+ f"{queue_t_sec:6d}.{queue_t_usec:06d}sec "
+ f"{self.diff_msec(hunk['queue_t'], hunk['xmit_t']):12.3f}msec "
+ f"{self.diff_msec(hunk['xmit_t'], hunk['free_t']):12.3f}msec")
+
+ def print_receive(self, hunk: dict) -> None:
+ """Display a process of received packets and interrupts."""
+ show_hunk = False
+ irq_list = hunk['irq_list']
+ if not irq_list:
+ return
+ cpu = irq_list[0]['cpu']
+ base_t = irq_list[0]['irq_ent_t']
+
+ if self.dev:
+ for irq in irq_list:
+ if irq['name'].find(self.dev) >= 0:
+ show_hunk = True
+ break
+ else:
+ show_hunk = True
+
+ if not show_hunk:
+ return
+
+ base_t_sec = base_t // 1000000000
+ base_t_usec = base_t % 1000000000 // 1000
+ print(f"{base_t_sec}.{base_t_usec:06d}sec cpu={cpu}")
+ for irq in irq_list:
+ print(PF_IRQ_ENTRY %
+ (self.diff_msec(base_t, irq['irq_ent_t']),
+ irq['irq'], irq['name']))
+ print(PF_JOINT)
+ irq_event_list = irq['event_list']
+ for irq_event in irq_event_list:
+ if irq_event['event'] == 'netif_rx':
+ print(PF_NET_RX %
+ (self.diff_msec(base_t, irq_event['time']),
+ irq_event['skbaddr']))
+ print(PF_JOINT)
+
+ print(PF_SOFT_ENTRY % self.diff_msec(base_t, hunk['sirq_ent_t']))
+ print(PF_JOINT)
+ event_list = hunk['event_list']
+ for i, event in enumerate(event_list):
+ if event['event_name'] == 'napi_poll':
+ print(PF_NAPI_POLL %
+ (self.diff_msec(base_t, event['event_t']),
+ event['dev']))
+ if i == len(event_list) - 1:
+ print("")
+ else:
+ print(PF_JOINT)
+ else:
+ print(PF_NET_RECV %
+ (self.diff_msec(base_t, event['event_t']),
+ event['skbaddr'],
+ event['len']))
+ if 'comm' in event:
+ print(PF_WJOINT)
+ print(PF_CPY_DGRAM %
+ (self.diff_msec(base_t, event['comm_t']),
+ event['pid'], event['comm']))
+ elif 'handle' in event:
+ print(PF_WJOINT)
+ if event['handle'] == "kfree_skb":
+ print(PF_KFREE_SKB %
+ (self.diff_msec(base_t, event['comm_t']),
+ event['location']))
+ elif event['handle'] == "consume_skb":
+ print(PF_CONS_SKB %
+ self.diff_msec(base_t, event['comm_t']))
+ print(PF_JOINT)
+
+ def handle_irq_handler_entry(self, event: dict) -> None:
+ """Handle irq:irq_handler_entry event."""
+ time = event['time']
+ cpu = event['cpu']
+ irq = event['irq']
+ irq_name = event['irq_name']
+ irq_record = {'irq': irq, 'name': irq_name, 'cpu': cpu,
+ 'irq_ent_t': time, 'event_list': []}
+ self.irq_dic[cpu].append(irq_record)
+
+ def handle_irq_handler_exit(self, event: dict) -> None:
+ """Handle irq:irq_handler_exit event."""
+ time = event['time']
+ cpu = event['cpu']
+ irq = event['irq']
+ if cpu not in self.irq_dic or not self.irq_dic[cpu]:
+ return
+ irq_record = self.irq_dic[cpu].pop()
+ if irq != irq_record['irq']:
+ return
+ irq_record['irq_ext_t'] = time
+ # if an irq doesn't include NET_RX softirq, drop.
+ if irq_record['event_list']:
+ self.irq_dic[cpu].append(irq_record)
+
+ def handle_irq_softirq_raise(self, event: dict) -> None:
+ """Handle irq:softirq_raise event."""
+ time = event['time']
+ cpu = event['cpu']
+ if cpu not in self.irq_dic or not self.irq_dic[cpu]:
+ return
+ irq_record = self.irq_dic[cpu].pop()
+ irq_record['event_list'].append({'time': time, 'event': 'sirq_raise'})
+ self.irq_dic[cpu].append(irq_record)
+
+ def handle_irq_softirq_entry(self, event: dict) -> None:
+ """Handle irq:softirq_entry event."""
+ time = event['time']
+ cpu = event['cpu']
+ self.net_rx_dic[cpu] = {'sirq_ent_t': time, 'event_list': []}
+
+ def handle_irq_softirq_exit(self, event: dict) -> None:
+ """Handle irq:softirq_exit event."""
+ time = event['time']
+ cpu = event['cpu']
+ irq_list = []
+ event_list = []
+ sirq_ent_t = None
+
+ if cpu in self.irq_dic:
+ irq_list = self.irq_dic[cpu]
+ del self.irq_dic[cpu]
+ if cpu in self.net_rx_dic:
+ sirq_ent_t = self.net_rx_dic[cpu]['sirq_ent_t']
+ event_list = self.net_rx_dic[cpu]['event_list']
+ del self.net_rx_dic[cpu]
+ if not irq_list or not event_list or sirq_ent_t is None:
+ return
+ rec_data = {'sirq_ent_t': sirq_ent_t, 'sirq_ext_t': time,
+ 'irq_list': irq_list, 'event_list': event_list}
+ self.receive_hunk_list.append(rec_data)
+
+ def handle_napi_poll(self, event: dict) -> None:
+ """Handle napi:napi_poll event."""
+ time = event['time']
+ cpu = event['cpu']
+ dev_name = event['dev_name']
+ work = event['work']
+ budget = event['budget']
+ if cpu in self.net_rx_dic:
+ event_list = self.net_rx_dic[cpu]['event_list']
+ rec_data = {'event_name': 'napi_poll',
+ 'dev': dev_name, 'event_t': time,
+ 'work': work, 'budget': budget}
+ event_list.append(rec_data)
+
+ def handle_netif_rx(self, event: dict) -> None:
+ """Handle net:netif_rx event."""
+ time = event['time']
+ cpu = event['cpu']
+ skbaddr = event['skbaddr']
+ skblen = event['skblen']
+ dev_name = event['dev_name']
+ if cpu not in self.irq_dic or not self.irq_dic[cpu]:
+ return
+ irq_record = self.irq_dic[cpu].pop()
+ irq_record['event_list'].append({'time': time, 'event': 'netif_rx',
+ 'skbaddr': skbaddr, 'skblen': skblen,
+ 'dev_name': dev_name})
+ self.irq_dic[cpu].append(irq_record)
+
+ def handle_netif_receive_skb(self, event: dict) -> None:
+ """Handle net:netif_receive_skb event."""
+ time = event['time']
+ cpu = event['cpu']
+ skbaddr = event['skbaddr']
+ skblen = event['skblen']
+ if cpu in self.net_rx_dic:
+ rec_data = {'event_name': 'netif_receive_skb',
+ 'event_t': time, 'skbaddr': skbaddr, 'len': skblen}
+ event_list = self.net_rx_dic[cpu]['event_list']
+ event_list.append(rec_data)
+ self.rx_skb_list.insert(0, rec_data)
+ if len(self.rx_skb_list) > self.buffer_budget:
+ self.rx_skb_list.pop()
+ self.of_count_rx_skb_list += 1
+
+ def handle_net_dev_queue(self, event: dict) -> None:
+ """Handle net:net_dev_queue event."""
+ time = event['time']
+ skbaddr = event['skbaddr']
+ skblen = event['skblen']
+ dev_name = event['dev_name']
+ skb = {'dev': dev_name, 'skbaddr': skbaddr, 'len': skblen, 'queue_t': time}
+ self.tx_queue_list.insert(0, skb)
+ if len(self.tx_queue_list) > self.buffer_budget:
+ self.tx_queue_list.pop()
+ self.of_count_tx_queue_list += 1
+
+ def handle_net_dev_xmit(self, event: dict) -> None:
+ """Handle net:net_dev_xmit event."""
+ time = event['time']
+ skbaddr = event['skbaddr']
+ rc = event['rc']
+ if rc == 0: # NETDEV_TX_OK
+ for i, skb in enumerate(self.tx_queue_list):
+ if skb['skbaddr'] == skbaddr:
+ skb['xmit_t'] = time
+ self.tx_xmit_list.insert(0, skb)
+ del self.tx_queue_list[i]
+ if len(self.tx_xmit_list) > self.buffer_budget:
+ self.tx_xmit_list.pop()
+ self.of_count_tx_xmit_list += 1
+ return
+
+ def handle_kfree_skb(self, event: dict) -> None:
+ """Handle skb:kfree_skb event."""
+ time = event['time']
+ skbaddr = event['skbaddr']
+ comm = event['comm']
+ pid = event['pid']
+ location = event['location']
+ for i, skb in enumerate(self.tx_queue_list):
+ if skb['skbaddr'] == skbaddr:
+ del self.tx_queue_list[i]
+ return
+ for i, skb in enumerate(self.tx_xmit_list):
+ if skb['skbaddr'] == skbaddr:
+ skb['free_t'] = time
+ self.tx_free_list.append(skb)
+ del self.tx_xmit_list[i]
+ return
+ for i, rec_data in enumerate(self.rx_skb_list):
+ if rec_data['skbaddr'] == skbaddr:
+ rec_data.update({'handle': "kfree_skb",
+ 'comm': comm, 'pid': pid, 'comm_t': time, 'location': location})
+ del self.rx_skb_list[i]
+ return
+
+ def handle_consume_skb(self, event: dict) -> None:
+ """Handle skb:consume_skb event."""
+ time = event['time']
+ skbaddr = event['skbaddr']
+ for i, skb in enumerate(self.tx_xmit_list):
+ if skb['skbaddr'] == skbaddr:
+ skb['free_t'] = time
+ self.tx_free_list.append(skb)
+ del self.tx_xmit_list[i]
+ return
+
+ def handle_skb_copy_datagram_iovec(self, event: dict) -> None:
+ """Handle skb:skb_copy_datagram_iovec event."""
+ time = event['time']
+ skbaddr = event['skbaddr']
+ comm = event['comm']
+ pid = event['pid']
+ for i, rec_data in enumerate(self.rx_skb_list):
+ if skbaddr == rec_data['skbaddr']:
+ rec_data.update({'handle': "skb_copy_datagram_iovec",
+ 'comm': comm, 'pid': pid, 'comm_t': time})
+ del self.rx_skb_list[i]
+ return
+
+
+
+ def print_summary(self) -> None:
+ """Print charts."""
+
+ # display receive hunks
+ if self.show_rx:
+ for hunk in self.receive_hunk_list:
+ self.print_receive(hunk)
+
+ # display transmit hunks
+ if self.show_tx:
+ print(" dev len Qdisc "
+ " netdevice free")
+ for hunk in self.tx_free_list:
+ self.print_transmit(hunk)
+
+ if self.debug:
+ print("debug buffer status")
+ print("----------------------------")
+ print(f"xmit Qdisc:remain:{len(self.tx_queue_list)} "
+ f"overflow:{self.of_count_tx_queue_list}")
+ print(f"xmit netdevice:remain:{len(self.tx_xmit_list)} "
+ f"overflow:{self.of_count_tx_xmit_list}")
+ print(f"receive:remain:{len(self.rx_skb_list)} "
+ f"overflow:{self.of_count_rx_skb_list}")
+
+ def handle_single_event(self, event: dict) -> None:
+ """Handle a single processed event."""
+ name = event['name']
+ if name == 'irq:softirq_exit':
+ self.handle_irq_softirq_exit(event)
+ elif name == 'irq:softirq_entry':
+ self.handle_irq_softirq_entry(event)
+ elif name == 'irq:softirq_raise':
+ self.handle_irq_softirq_raise(event)
+ elif name == 'irq:irq_handler_entry':
+ self.handle_irq_handler_entry(event)
+ elif name == 'irq:irq_handler_exit':
+ self.handle_irq_handler_exit(event)
+ elif name == 'napi:napi_poll':
+ self.handle_napi_poll(event)
+ elif name == 'net:netif_receive_skb':
+ self.handle_netif_receive_skb(event)
+ elif name == 'net:netif_rx':
+ self.handle_netif_rx(event)
+ elif name == 'skb:skb_copy_datagram_iovec':
+ self.handle_skb_copy_datagram_iovec(event)
+ elif name == 'net:net_dev_queue':
+ self.handle_net_dev_queue(event)
+ elif name == 'net:net_dev_xmit':
+ self.handle_net_dev_xmit(event)
+ elif name == 'skb:kfree_skb':
+ self.handle_kfree_skb(event)
+ elif name == 'skb:consume_skb':
+ self.handle_consume_skb(event)
+
+ def process_event(self, sample: perf.sample_event) -> None:
+ """Process events directly on-the-fly."""
+ name = str(sample.evsel)
+ pid = sample.sample_pid
+ if hasattr(self, 'session') and self.session:
+ comm = self.session.find_thread(pid).comm()
+ else:
+ comm = "Unknown"
+ event_data = {
+ 'name': name[6:-1] if name.startswith("evsel(") else name,
+ 'time': sample.sample_time,
+ 'cpu': sample.sample_cpu,
+ 'pid': pid,
+ 'comm': comm,
+ }
+
+ # Extract specific fields based on event type
+ if name.startswith("evsel(irq:softirq_"):
+ event_data['vec'] = getattr(sample, "vec", 0)
+ # Filter for NET_RX
+ try:
+ if perf.symbol_str("irq:softirq_entry", "vec", # type: ignore
+ event_data['vec']) != "NET_RX":
+ return
+ except AttributeError:
+ # Fallback if symbol_str not available or fails
+ if event_data['vec'] != 3: # NET_RX_SOFTIRQ is usually 3
+ return
+ elif name == "evsel(irq:irq_handler_entry)":
+ event_data['irq'] = getattr(sample, "irq", -1)
+ event_data['irq_name'] = getattr(sample, "name", "[unknown]")
+ elif name == "evsel(irq:irq_handler_exit)":
+ event_data['irq'] = getattr(sample, "irq", -1)
+ event_data['ret'] = getattr(sample, "ret", 0)
+ elif name == "evsel(napi:napi_poll)":
+ event_data['napi'] = getattr(sample, "napi", 0)
+ event_data['dev_name'] = getattr(sample, "dev_name", "[unknown]")
+ event_data['work'] = getattr(sample, "work", 0)
+ event_data['budget'] = getattr(sample, "budget", 0)
+ elif name in ("evsel(net:netif_receive_skb)", "evsel(net:netif_rx)",
+ "evsel(net:net_dev_queue)"):
+ event_data['skbaddr'] = getattr(sample, "skbaddr", 0)
+ event_data['skblen'] = getattr(sample, "len", 0)
+ event_data['dev_name'] = getattr(sample, "name", "[unknown]")
+ elif name == "evsel(net:net_dev_xmit)":
+ event_data['skbaddr'] = getattr(sample, "skbaddr", 0)
+ event_data['skblen'] = getattr(sample, "len", 0)
+ event_data['rc'] = getattr(sample, "rc", 0)
+ event_data['dev_name'] = getattr(sample, "name", "[unknown]")
+ elif name == "evsel(skb:kfree_skb)":
+ event_data['skbaddr'] = getattr(sample, "skbaddr", 0)
+ event_data['location'] = getattr(sample, "location", 0)
+ event_data['protocol'] = getattr(sample, "protocol", 0)
+ event_data['reason'] = getattr(sample, "reason", 0)
+ elif name == "evsel(skb:consume_skb)":
+ event_data['skbaddr'] = getattr(sample, "skbaddr", 0)
+ event_data['location'] = getattr(sample, "location", 0)
+ elif name == "evsel(skb:skb_copy_datagram_iovec)":
+ event_data['skbaddr'] = getattr(sample, "skbaddr", 0)
+ event_data['skblen'] = getattr(sample, "skblen", 0)
+
+ self.handle_single_event(event_data)
+
+
+if __name__ == "__main__":
+ ap = argparse.ArgumentParser(description="Display a process of packets and processed time.")
+ ap.add_argument("-i", "--input", default="perf.data", help="Input file name")
+ ap.add_argument("tx", nargs="?", help="show only tx chart")
+ ap.add_argument("rx", nargs="?", help="show only rx chart")
+ ap.add_argument("dev", nargs="?", help="show only specified device")
+ ap.add_argument("debug", nargs="?", help="work with debug mode. It shows buffer status.")
+ args = ap.parse_args()
+
+ parsed_args = argparse.Namespace(tx=False, rx=False, dev=None, debug=False, input=args.input)
+
+ for arg in sys.argv[1:]:
+ if arg == 'tx':
+ parsed_args.tx = True
+ elif arg == 'rx':
+ parsed_args.rx = True
+ elif arg.startswith('dev='):
+ parsed_args.dev = arg[4:]
+ elif arg == 'debug':
+ parsed_args.debug = True
+
+ analyzer = NetDevTimesAnalyzer(parsed_args)
+
+ try:
+ session = perf.session(perf.data(parsed_args.input), sample=analyzer.process_event)
+ analyzer.session = session
+ session.process_events()
+ analyzer.print_summary()
+ except KeyboardInterrupt:
+ analyzer.print_summary()
+ except Exception as e:
+ print(f"Error processing events: {e}")
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 39/59] perf intel-pt-events: Port intel-pt-events/libxed to use python module
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Ported from tools/perf/scripts/python/.
- Refactored intel-pt-events.py to use a class structure to eliminate
global state and improve maintainability.
- Removed Python 2 compatibility checks.
- Renamed methods in libxed.py to snake_case (Instruction ->
instruction, SetMode -> set_mode, DisassembleOne -> disassemble_one)
to comply with pylint.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2:
1. Robustness in print_cbr : Added checks to ensure raw_buf is at
least 12 bytes long and that the frequency divisor is not zero,
avoiding struct.error and ZeroDivisionError .
2. Robustness in print_evt : Added buffer length checks in the loop to
prevent struct.error if event data count exceeds available buffer.
3. Buffer Handling in disassem : Used
ctypes.create_string_buffer(insn, 64) to properly initialize the
buffer with raw bytes, preventing truncation on \x00 bytes.
4. Corrected Field Names: Reverted short names to sample_ip ,
sample_time , and sample_cpu across multiple methods.
5. Comm Resolution: Used session.process(sample.sample_pid).comm() to
get the thread name, rather than failing back to "Unknown" .
6. Event Name Cleanup: Stripped evsel( and ) from event names.
7. Fixed Broken Pipe Handling: Prevented sys.stdout from being closed
before exiting in the handler.
8. Eliminated Hardcoded Offset in libxed.py : Added
xed_decoded_inst_get_length from the official LibXED API rather
than relying on the hardcoded byte offset 166 .
---
tools/perf/python/intel-pt-events.py | 435 +++++++++++++++++++++++++++
tools/perf/python/libxed.py | 122 ++++++++
2 files changed, 557 insertions(+)
create mode 100755 tools/perf/python/intel-pt-events.py
create mode 100755 tools/perf/python/libxed.py
diff --git a/tools/perf/python/intel-pt-events.py b/tools/perf/python/intel-pt-events.py
new file mode 100755
index 000000000000..19a0faec8f5f
--- /dev/null
+++ b/tools/perf/python/intel-pt-events.py
@@ -0,0 +1,435 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+Print Intel PT Events including Power Events and PTWRITE.
+Ported from tools/perf/scripts/python/intel-pt-events.py
+"""
+
+import argparse
+import contextlib
+from ctypes import addressof, create_string_buffer
+import io
+import os
+import struct
+import sys
+from typing import Any, Optional
+import perf
+
+# Try to import LibXED from legacy directory if available in PYTHONPATH
+try:
+ from libxed import LibXED # type: ignore
+except ImportError:
+ LibXED = None # type: ignore
+
+
+class IntelPTAnalyzer:
+ """Analyzes Intel PT events and prints details."""
+
+ def __init__(self, cfg: argparse.Namespace):
+ self.args = cfg
+ self.session: Optional[perf.session] = None
+ self.insn = False
+ self.src = False
+ self.source_file_name: Optional[str] = None
+ self.line_number: int = 0
+ self.dso: Optional[str] = None
+ self.stash_dict: dict[int, list[str]] = {}
+ self.output: Any = None
+ self.output_pos: int = 0
+ self.cpu: int = -1
+ self.time: int = 0
+ self.switch_str: dict[int, str] = {}
+
+ if cfg.insn_trace:
+ print("Intel PT Instruction Trace")
+ self.insn = True
+ elif cfg.src_trace:
+ print("Intel PT Source Trace")
+ self.insn = True
+ self.src = True
+ else:
+ print("Intel PT Branch Trace, Power Events, Event Trace and PTWRITE")
+
+ self.disassembler: Any = None
+ if self.insn and LibXED is not None:
+ try:
+ self.disassembler = LibXED()
+ except Exception as e:
+ print(f"Failed to initialize LibXED: {e}")
+ self.disassembler = None
+
+ def print_ptwrite(self, raw_buf: bytes) -> None:
+ """Print PTWRITE data."""
+ data = struct.unpack_from("<IQ", raw_buf)
+ flags = data[0]
+ payload = data[1]
+ exact_ip = flags & 1
+ try:
+ s = payload.to_bytes(8, "little").decode("ascii").rstrip("\x00")
+ if not s.isprintable():
+ s = ""
+ except (UnicodeDecodeError, ValueError):
+ s = ""
+ print(f"IP: {exact_ip} payload: {payload:#x} {s}", end=' ')
+
+ def print_cbr(self, raw_buf: bytes) -> None:
+ """Print CBR data."""
+ if len(raw_buf) < 12:
+ return
+ data = struct.unpack_from("<BBBBII", raw_buf)
+ cbr = data[0]
+ f = (data[4] + 500) // 1000
+ if data[2] == 0:
+ return
+ p = ((cbr * 1000 // data[2]) + 5) // 10
+ print(f"{cbr:3u} freq: {f:4u} MHz ({p:3u}%)", end=' ')
+
+ def print_mwait(self, raw_buf: bytes) -> None:
+ """Print MWAIT data."""
+ data = struct.unpack_from("<IQ", raw_buf)
+ payload = data[1]
+ hints = payload & 0xff
+ extensions = (payload >> 32) & 0x3
+ print(f"hints: {hints:#x} extensions: {extensions:#x}", end=' ')
+
+ def print_pwre(self, raw_buf: bytes) -> None:
+ """Print PWRE data."""
+ data = struct.unpack_from("<IQ", raw_buf)
+ payload = data[1]
+ hw = (payload >> 7) & 1
+ cstate = (payload >> 12) & 0xf
+ subcstate = (payload >> 8) & 0xf
+ print(f"hw: {hw} cstate: {cstate} sub-cstate: {subcstate}", end=' ')
+
+ def print_exstop(self, raw_buf: bytes) -> None:
+ """Print EXSTOP data."""
+ data = struct.unpack_from("<I", raw_buf)
+ flags = data[0]
+ exact_ip = flags & 1
+ print(f"IP: {exact_ip}", end=' ')
+
+ def print_pwrx(self, raw_buf: bytes) -> None:
+ """Print PWRX data."""
+ data = struct.unpack_from("<IQ", raw_buf)
+ payload = data[1]
+ deepest_cstate = payload & 0xf
+ last_cstate = (payload >> 4) & 0xf
+ wake_reason = (payload >> 8) & 0xf
+ print(f"deepest cstate: {deepest_cstate} last cstate: {last_cstate} "
+ f"wake reason: {wake_reason:#x}", end=' ')
+
+ def print_psb(self, raw_buf: bytes) -> None:
+ """Print PSB data."""
+ data = struct.unpack_from("<IQ", raw_buf)
+ offset = data[1]
+ print(f"offset: {offset:#x}", end=' ')
+
+ def print_evt(self, raw_buf: bytes) -> None:
+ """Print EVT data."""
+ glb_cfe = ["", "INTR", "IRET", "SMI", "RSM", "SIPI", "INIT", "VMENTRY", "VMEXIT",
+ "VMEXIT_INTR", "SHUTDOWN", "", "UINT", "UIRET"] + [""] * 18
+ glb_evd = ["", "PFA", "VMXQ", "VMXR"] + [""] * 60
+
+ data = struct.unpack_from("<BBH", raw_buf)
+ typ = data[0] & 0x1f
+ ip_flag = (data[0] & 0x80) >> 7
+ vector = data[1]
+ evd_cnt = data[2]
+ s = glb_cfe[typ]
+ if s:
+ print(f" cfe: {s} IP: {ip_flag} vector: {vector}", end=' ')
+ else:
+ print(f" cfe: {typ} IP: {ip_flag} vector: {vector}", end=' ')
+ pos = 4
+ for _ in range(evd_cnt):
+ if len(raw_buf) < pos + 16:
+ break
+ data = struct.unpack_from("<QQ", raw_buf, pos)
+ et = data[0] & 0x3f
+ s = glb_evd[et]
+ if s:
+ print(f"{s}: {data[1]:#x}", end=' ')
+ else:
+ print(f"EVD_{et}: {data[1]:#x}", end=' ')
+ pos += 16
+
+ def print_iflag(self, raw_buf: bytes) -> None:
+ """Print IFLAG data."""
+ data = struct.unpack_from("<IQ", raw_buf)
+ iflag = data[0] & 1
+ old_iflag = iflag ^ 1
+ via_branch = data[0] & 2
+ s = "via" if via_branch else "non"
+ print(f"IFLAG: {old_iflag}->{iflag} {s} branch", end=' ')
+
+ def common_start_str(self, comm: str, sample: perf.sample_event) -> str:
+ """Return common start string for display."""
+ ts = sample.sample_time
+ cpu = sample.sample_cpu
+ pid = sample.sample_pid
+ tid = sample.tid
+ machine_pid = getattr(sample, "machine_pid", 0)
+ if machine_pid:
+ vcpu = getattr(sample, "vcpu", -1)
+ return (f"VM:{machine_pid:5d} VCPU:{vcpu:03d} {comm:>16s} {pid:5u}/{tid:<5u} "
+ f"[{cpu:03u}] {ts // 1000000000:9u}.{ts % 1000000000:09u} ")
+ return (f"{comm:>16s} {pid:5u}/{tid:<5u} [{cpu:03u}] "
+ f"{ts // 1000000000:9u}.{ts % 1000000000:09u} ")
+
+ def print_common_start(self, comm: str, sample: perf.sample_event, name: str) -> None:
+ """Print common start info."""
+ flags_disp = getattr(sample, "flags_disp", "")
+ print(self.common_start_str(comm, sample) + f"{name:>8s} {flags_disp:>21s}", end=' ')
+
+ def print_instructions_start(self, comm: str, sample: perf.sample_event) -> None:
+ """Print instructions start info."""
+ flags = getattr(sample, "flags_disp", "")
+ if "x" in flags:
+ print(self.common_start_str(comm, sample) + "x", end=' ')
+ else:
+ print(self.common_start_str(comm, sample), end=' ')
+
+ def disassem(self, insn: bytes, ip: int) -> tuple[int, str]:
+ """Disassemble instruction using LibXED."""
+ inst = self.disassembler.instruction()
+ self.disassembler.set_mode(inst, 0) # Assume 64-bit
+ buf = create_string_buffer(insn, 64)
+ return self.disassembler.disassemble_one(inst, addressof(buf), len(insn), ip)
+
+ def print_common_ip(self, sample: perf.sample_event, symbol: str, dso: str) -> None:
+ """Print IP and symbol info."""
+ ip = sample.sample_ip
+ offs = f"+{sample.symoff:#x}" if hasattr(sample, "symoff") else ""
+ cyc_cnt = getattr(sample, "cyc_cnt", 0)
+ if cyc_cnt:
+ insn_cnt = getattr(sample, "insn_cnt", 0)
+ ipc_str = f" IPC: {insn_cnt / cyc_cnt:#.2f} ({insn_cnt}/{cyc_cnt})"
+ else:
+ ipc_str = ""
+
+ if self.insn and self.disassembler is not None:
+ try:
+ insn = sample.insn()
+ except AttributeError:
+ insn = None
+ if insn:
+ cnt, text = self.disassem(insn, ip)
+ byte_str = (f"{ip:x}").rjust(16)
+ for k in range(cnt):
+ byte_str += f" {insn[k]:02x}"
+ print(f"{byte_str:-40s} {text:-30s}", end=' ')
+ print(f"{symbol}{offs} ({dso})", end=' ')
+ else:
+ print(f"{ip:16x} {symbol}{offs} ({dso})", end=' ')
+
+ addr_correlates_sym = getattr(sample, "addr_correlates_sym", False)
+ if addr_correlates_sym:
+ addr = sample.addr
+ addr_dso = getattr(sample, "addr_dso", "[unknown]")
+ addr_symbol = getattr(sample, "addr_symbol", "[unknown]")
+ addr_offs = f"+{sample.addr_symoff:#x}" if hasattr(sample, "addr_symoff") else ""
+ print(f"=> {addr:x} {addr_symbol}{addr_offs} ({addr_dso}){ipc_str}")
+ else:
+ print(ipc_str)
+
+ def print_srccode(self, comm: str, sample: perf.sample_event,
+ symbol: str, dso: str, with_insn: bool) -> None:
+ """Print source code info."""
+ ip = sample.sample_ip
+ if symbol == "[unknown]":
+ start_str = self.common_start_str(comm, sample) + (f"{ip:x}").rjust(16).ljust(40)
+ else:
+ offs = f"+{sample.symoff:#x}" if hasattr(sample, "symoff") else ""
+ start_str = self.common_start_str(comm, sample) + (symbol + offs).ljust(40)
+
+ if with_insn and self.insn and self.disassembler is not None:
+ try:
+ insn = sample.insn()
+ except AttributeError:
+ insn = None
+ if insn:
+ _, text = self.disassem(insn, ip)
+ start_str += text.ljust(30)
+
+ try:
+ source_file_name, line_number, source_line = sample.srccode()
+ except (AttributeError, ValueError):
+ source_file_name, line_number, source_line = None, 0, None
+
+ if source_file_name:
+ if self.line_number == line_number and self.source_file_name == source_file_name:
+ src_str = ""
+ else:
+ if len(source_file_name) > 40:
+ src_file = ("..." + source_file_name[-37:]) + " "
+ else:
+ src_file = source_file_name.ljust(41)
+ if source_line is None:
+ src_str = src_file + str(line_number).rjust(4) + " <source not found>"
+ else:
+ src_str = src_file + str(line_number).rjust(4) + " " + source_line
+ self.dso = None
+ elif dso == self.dso:
+ src_str = ""
+ else:
+ src_str = dso
+ self.dso = dso
+
+ self.line_number = line_number
+ self.source_file_name = source_file_name
+ print(start_str, src_str)
+
+ def do_process_event(self, sample: perf.sample_event) -> None:
+ """Process event and print info."""
+ comm = "Unknown"
+ if hasattr(self, 'session') and self.session:
+ try:
+ comm = self.session.find_thread(sample.sample_pid).comm()
+ except Exception:
+ pass
+ name = getattr(sample.evsel, 'name', str(sample.evsel))
+ if name.startswith("evsel("):
+ name = name[6:-1]
+ dso = getattr(sample, "dso", "[unknown]")
+ symbol = getattr(sample, "symbol", "[unknown]")
+
+ cpu = sample.sample_cpu
+ if cpu in self.switch_str:
+ print(self.switch_str[cpu])
+ del self.switch_str[cpu]
+
+ try:
+ raw_buf = sample.raw_buf
+ except AttributeError:
+ raw_buf = b""
+
+ if name.startswith("instructions"):
+ if self.src:
+ self.print_srccode(comm, sample, symbol, dso, True)
+ else:
+ self.print_instructions_start(comm, sample)
+ self.print_common_ip(sample, symbol, dso)
+ elif name.startswith("branches"):
+ if self.src:
+ self.print_srccode(comm, sample, symbol, dso, False)
+ else:
+ self.print_common_start(comm, sample, name)
+ self.print_common_ip(sample, symbol, dso)
+ elif name == "ptwrite":
+ self.print_common_start(comm, sample, name)
+ self.print_ptwrite(raw_buf)
+ self.print_common_ip(sample, symbol, dso)
+ elif name == "cbr":
+ self.print_common_start(comm, sample, name)
+ self.print_cbr(raw_buf)
+ self.print_common_ip(sample, symbol, dso)
+ elif name == "mwait":
+ self.print_common_start(comm, sample, name)
+ self.print_mwait(raw_buf)
+ self.print_common_ip(sample, symbol, dso)
+ elif name == "pwre":
+ self.print_common_start(comm, sample, name)
+ self.print_pwre(raw_buf)
+ self.print_common_ip(sample, symbol, dso)
+ elif name == "exstop":
+ self.print_common_start(comm, sample, name)
+ self.print_exstop(raw_buf)
+ self.print_common_ip(sample, symbol, dso)
+ elif name == "pwrx":
+ self.print_common_start(comm, sample, name)
+ self.print_pwrx(raw_buf)
+ self.print_common_ip(sample, symbol, dso)
+ elif name == "psb":
+ self.print_common_start(comm, sample, name)
+ self.print_psb(raw_buf)
+ self.print_common_ip(sample, symbol, dso)
+ elif name == "evt":
+ self.print_common_start(comm, sample, name)
+ self.print_evt(raw_buf)
+ self.print_common_ip(sample, symbol, dso)
+ elif name == "iflag":
+ self.print_common_start(comm, sample, name)
+ self.print_iflag(raw_buf)
+ self.print_common_ip(sample, symbol, dso)
+ else:
+ self.print_common_start(comm, sample, name)
+ self.print_common_ip(sample, symbol, dso)
+
+ def interleave_events(self, sample: perf.sample_event) -> None:
+ """Interleave output to avoid garbled lines from different CPUs."""
+ self.cpu = sample.sample_cpu
+ ts = sample.sample_time
+
+ if self.time != ts:
+ self.time = ts
+ self.flush_stashed_output()
+
+ self.output_pos = 0
+ with contextlib.redirect_stdout(io.StringIO()) as self.output:
+ self.do_process_event(sample)
+
+ self.stash_output()
+
+ def stash_output(self) -> None:
+ """Stash output for later flushing."""
+ output_str = self.output.getvalue()[self.output_pos:]
+ n = len(output_str)
+ if n:
+ self.output_pos += n
+ if self.cpu not in self.stash_dict:
+ self.stash_dict[self.cpu] = []
+ self.stash_dict[self.cpu].append(output_str)
+ if len(self.stash_dict[self.cpu]) > 1000:
+ self.flush_stashed_output()
+
+ def flush_stashed_output(self) -> None:
+ """Flush stashed output."""
+ while self.stash_dict:
+ cpus = list(self.stash_dict.keys())
+ for cpu in cpus:
+ items = self.stash_dict[cpu]
+ countdown = self.args.interleave
+ while len(items) and countdown:
+ sys.stdout.write(items[0])
+ del items[0]
+ countdown -= 1
+ if not items:
+ del self.stash_dict[cpu]
+
+ def process_event(self, sample: perf.sample_event) -> None:
+ """Wrapper to handle interleaving and exceptions."""
+ try:
+ if self.args.interleave:
+ self.interleave_events(sample)
+ else:
+ self.do_process_event(sample)
+ except BrokenPipeError:
+ # Stop python printing broken pipe errors and traceback
+ sys.stdout = open(os.devnull, 'w', encoding='utf-8')
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ ap = argparse.ArgumentParser()
+ ap.add_argument("-i", "--input", default="perf.data", help="Input file name")
+ ap.add_argument("--insn-trace", action='store_true')
+ ap.add_argument("--src-trace", action='store_true')
+ ap.add_argument("--all-switch-events", action='store_true')
+ ap.add_argument("--interleave", type=int, nargs='?', const=4, default=0)
+ args = ap.parse_args()
+
+ analyzer = IntelPTAnalyzer(args)
+
+ try:
+ session = perf.session(perf.data(args.input), sample=analyzer.process_event)
+ analyzer.session = session
+ session.process_events()
+ if args.interleave:
+ analyzer.flush_stashed_output()
+ print("End")
+ except KeyboardInterrupt:
+ if args.interleave:
+ analyzer.flush_stashed_output()
+ print("End")
+ except Exception as e:
+ print(f"Error processing events: {e}")
diff --git a/tools/perf/python/libxed.py b/tools/perf/python/libxed.py
new file mode 100755
index 000000000000..0e622e6959c2
--- /dev/null
+++ b/tools/perf/python/libxed.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+Python wrapper for libxed.so
+Ported from tools/perf/scripts/python/libxed.py
+"""
+
+from ctypes import CDLL, Structure, create_string_buffer, addressof, sizeof, \
+ c_void_p, c_byte, c_int, c_uint, c_ulonglong
+
+# To use Intel XED, libxed.so must be present. To build and install
+# libxed.so:
+# git clone https://github.com/intelxed/mbuild.git mbuild
+# git clone https://github.com/intelxed/xed
+# cd xed
+# ./mfile.py --share
+# sudo ./mfile.py --prefix=/usr/local install
+# sudo ldconfig
+#
+
+
+class XedStateT(Structure):
+ """xed_state_t structure."""
+ _fields_ = [
+ ("mode", c_int),
+ ("width", c_int)
+ ]
+
+
+class XEDInstruction():
+ """Represents a decoded instruction."""
+
+ def __init__(self, libxed):
+ # Current xed_decoded_inst_t structure is 192 bytes. Use 512 to allow for future expansion
+ xedd_t = c_byte * 512
+ self.xedd = xedd_t()
+ self.xedp = addressof(self.xedd)
+ libxed.xed_decoded_inst_zero(self.xedp)
+ self.state = XedStateT()
+ self.statep = addressof(self.state)
+ # Buffer for disassembled instruction text
+ self.buffer = create_string_buffer(256)
+ self.bufferp = addressof(self.buffer)
+
+
+class LibXED():
+ """Wrapper for libxed.so."""
+
+ def __init__(self):
+ try:
+ self.libxed = CDLL("libxed.so")
+ except OSError:
+ self.libxed = None
+ if not self.libxed:
+ try:
+ self.libxed = CDLL("/usr/local/lib/libxed.so")
+ except OSError:
+ self.libxed = None
+
+ if not self.libxed:
+ raise ImportError("libxed.so not found. Please install Intel XED.")
+
+ self.xed_tables_init = self.libxed.xed_tables_init
+ self.xed_tables_init.restype = None
+ self.xed_tables_init.argtypes = []
+
+ self.xed_decoded_inst_zero = self.libxed.xed_decoded_inst_zero
+ self.xed_decoded_inst_zero.restype = None
+ self.xed_decoded_inst_zero.argtypes = [c_void_p]
+
+ self.xed_operand_values_set_mode = self.libxed.xed_operand_values_set_mode
+ self.xed_operand_values_set_mode.restype = None
+ self.xed_operand_values_set_mode.argtypes = [c_void_p, c_void_p]
+
+ self.xed_decoded_inst_zero_keep_mode = self.libxed.xed_decoded_inst_zero_keep_mode
+ self.xed_decoded_inst_zero_keep_mode.restype = None
+ self.xed_decoded_inst_zero_keep_mode.argtypes = [c_void_p]
+
+ self.xed_decode = self.libxed.xed_decode
+ self.xed_decode.restype = c_int
+ self.xed_decode.argtypes = [c_void_p, c_void_p, c_uint]
+
+ self.xed_format_context = self.libxed.xed_format_context
+ self.xed_format_context.restype = c_uint
+ self.xed_format_context.argtypes = [
+ c_int, c_void_p, c_void_p, c_int, c_ulonglong, c_void_p, c_void_p
+ ]
+
+ self.xed_decoded_inst_get_length = self.libxed.xed_decoded_inst_get_length
+ self.xed_decoded_inst_get_length.restype = c_uint
+ self.xed_decoded_inst_get_length.argtypes = [c_void_p]
+
+ self.xed_tables_init()
+
+ def instruction(self):
+ """Create a new XEDInstruction."""
+ return XEDInstruction(self)
+
+ def set_mode(self, inst, mode):
+ """Set 32-bit or 64-bit mode."""
+ if mode:
+ inst.state.mode = 4 # 32-bit
+ inst.state.width = 4 # 4 bytes
+ else:
+ inst.state.mode = 1 # 64-bit
+ inst.state.width = 8 # 8 bytes
+ self.xed_operand_values_set_mode(inst.xedp, inst.statep)
+
+ def disassemble_one(self, inst, bytes_ptr, bytes_cnt, ip):
+ """Disassemble one instruction."""
+ self.xed_decoded_inst_zero_keep_mode(inst.xedp)
+ err = self.xed_decode(inst.xedp, bytes_ptr, bytes_cnt)
+ if err:
+ return 0, ""
+ # Use AT&T mode (2), alternative is Intel (3)
+ ok = self.xed_format_context(2, inst.xedp, inst.bufferp, sizeof(inst.buffer), ip, 0, 0)
+ if not ok:
+ return 0, ""
+
+ result = inst.buffer.value.decode('utf-8')
+ # Return instruction length and the disassembled instruction text
+ return self.xed_decoded_inst_get_length(inst.xedp), result
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 30/59] perf flamegraph: Port flamegraph to use python module
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Add a port of the flamegraph script that uses the perf python module
directly. This approach improves performance by avoiding intermediate
dictionaries for event fields.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v5:
1. Fix Event Filtering: Corrected event filtering check to search for a
substring match within the parsed event string, preventing all events
from being dropped due to the `evsel(...)` wrapper.
v6:
- Fixed terminal injection risk by not printing unverified content in
prompt.
---
tools/perf/python/flamegraph.py | 250 ++++++++++++++++++++++++++++++++
1 file changed, 250 insertions(+)
create mode 100755 tools/perf/python/flamegraph.py
diff --git a/tools/perf/python/flamegraph.py b/tools/perf/python/flamegraph.py
new file mode 100755
index 000000000000..b0eb5844b772
--- /dev/null
+++ b/tools/perf/python/flamegraph.py
@@ -0,0 +1,250 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+flamegraph.py - create flame graphs from perf samples using perf python module
+"""
+
+import argparse
+import hashlib
+import json
+import os
+import subprocess
+import sys
+import urllib.request
+from typing import Dict, Optional, Union
+import perf
+
+MINIMAL_HTML = """<head>
+ <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/d3-flamegraph.css">
+</head>
+<body>
+ <div id="chart"></div>
+ <script type="text/javascript" src="https://d3js.org/d3.v7.js"></script>
+ <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/d3-flamegraph.min.js"></script>
+ <script type="text/javascript">
+ const stacks = [/** @flamegraph_json **/];
+ // Note, options is unused.
+ const options = [/** @options_json **/];
+
+ var chart = flamegraph();
+ d3.select("#chart")
+ .datum(stacks[0])
+ .call(chart);
+ </script>
+</body>
+"""
+
+class Node:
+ """A node in the flame graph tree."""
+ def __init__(self, name: str, libtype: str):
+ self.name = name
+ self.libtype = libtype
+ self.value: int = 0
+ self.children: dict[str, Node] = {}
+
+ def to_json(self) -> Dict[str, Union[str, int, list[Dict]]]:
+ """Convert the node to a JSON-serializable dictionary."""
+ return {
+ "n": self.name,
+ "l": self.libtype,
+ "v": self.value,
+ "c": [x.to_json() for x in self.children.values()]
+ }
+
+
+class FlameGraphCLI:
+ """Command-line interface for generating flame graphs."""
+ def __init__(self, args):
+ self.args = args
+ self.stack = Node("all", "root")
+ self.session = None
+
+ @staticmethod
+ def get_libtype_from_dso(dso: Optional[str]) -> str:
+ """Determine the library type from the DSO name."""
+ if dso and (dso == "[kernel.kallsyms]" or dso.endswith("/vmlinux") or dso == "[kernel]"):
+ return "kernel"
+ return ""
+
+ @staticmethod
+ def find_or_create_node(node: Node, name: str, libtype: str) -> Node:
+ """Find a child node with the given name or create a new one."""
+ if name in node.children:
+ return node.children[name]
+ child = Node(name, libtype)
+ node.children[name] = child
+ return child
+
+ def process_event(self, sample) -> None:
+ """Process a single perf sample event."""
+ if self.args.event_name and self.args.event_name not in str(sample.evsel):
+ return
+
+ pid = sample.sample_pid
+ dso_type = ""
+ try:
+ thread = self.session.find_thread(sample.sample_tid)
+ comm = thread.comm()
+ except Exception:
+ comm = "[unknown]"
+
+ if pid == 0:
+ comm = "swapper"
+ dso_type = "kernel"
+ else:
+ comm = f"{comm} ({pid})"
+
+ node = self.find_or_create_node(self.stack, comm, dso_type)
+
+ callchain = sample.callchain
+ if callchain:
+ # We want to traverse from root to leaf.
+ # perf callchain iterator gives leaf to root.
+ # We collect them and reverse.
+ frames = list(callchain)
+ for entry in reversed(frames):
+ name = entry.symbol or "[unknown]"
+ libtype = self.get_libtype_from_dso(entry.dso)
+ node = self.find_or_create_node(node, name, libtype)
+ else:
+ # Fallback if no callchain
+ name = getattr(sample, "symbol", "[unknown]")
+ libtype = self.get_libtype_from_dso(getattr(sample, "dso", "[unknown]"))
+ node = self.find_or_create_node(node, name, libtype)
+
+ node.value += 1
+
+ def get_report_header(self) -> str:
+ """Get the header from the perf report."""
+ try:
+ input_file = self.args.input or "perf.data"
+ output = subprocess.check_output(["perf", "report", "--header-only", "-i", input_file])
+ result = output.decode("utf-8")
+ if self.args.event_name:
+ result += "\nFocused event: " + self.args.event_name
+ return result
+ except Exception:
+ return ""
+
+ def run(self) -> None:
+ """Run the flame graph generation."""
+ input_file = self.args.input or "perf.data"
+ if not os.path.exists(input_file):
+ print(f"Error: {input_file} not found. (try 'perf record' first)", file=sys.stderr)
+ sys.exit(1)
+
+ try:
+ self.session = perf.session(perf.data(input_file),
+ sample=self.process_event)
+ except Exception as e:
+ print(f"Error opening session: {e}", file=sys.stderr)
+ sys.exit(1)
+
+ self.session.process_events()
+
+ stacks_json = json.dumps(self.stack, default=lambda x: x.to_json())
+ # Escape HTML special characters to prevent XSS
+ stacks_json = stacks_json.replace("<", "\\u003c") \
+ .replace(">", "\\u003e").replace("&", "\\u0026")
+
+ if self.args.format == "html":
+ report_header = self.get_report_header()
+ options = {
+ "colorscheme": self.args.colorscheme,
+ "context": report_header
+ }
+ options_json = json.dumps(options)
+ options_json = options_json.replace("<", "\\u003c") \
+ .replace(">", "\\u003e").replace("&", "\\u0026")
+
+ template = self.args.template
+ template_md5sum = None
+ output_str = None
+
+ if not os.path.isfile(template):
+ if template.startswith("http://") or template.startswith("https://"):
+ if not self.args.allow_download:
+ print("Warning: Downloading templates is disabled. "
+ "Use --allow-download.", file=sys.stderr)
+ template = None
+ else:
+ print(f"Warning: Template file '{template}' not found.", file=sys.stderr)
+ if self.args.allow_download:
+ print("Using default CDN template.", file=sys.stderr)
+ template = (
+ "https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/"
+ "d3-flamegraph-base.html"
+ )
+ template_md5sum = "143e0d06ba69b8370b9848dcd6ae3f36"
+ else:
+ template = None
+
+ use_minimal = False
+ try:
+ if not template:
+ use_minimal = True
+ elif template.startswith("http"):
+ with urllib.request.urlopen(template) as url_template:
+ output_str = "".join([l.decode("utf-8") for l in url_template.readlines()])
+ else:
+ with open(template, "r", encoding="utf-8") as f:
+ output_str = f.read()
+ except Exception as err:
+ print(f"Error reading template {template}: {err}\n", file=sys.stderr)
+ use_minimal = True
+
+ if use_minimal:
+ print("Using internal minimal HTML that refers to d3's web site. JavaScript " +
+ "loaded this way from a local file may be blocked unless your " +
+ "browser has relaxed permissions. Run with '--allow-download' to fetch" +
+ "the full D3 HTML template.", file=sys.stderr)
+ output_str = MINIMAL_HTML
+
+ elif template_md5sum:
+ assert output_str is not None
+ download_md5sum = hashlib.md5(output_str.encode("utf-8")).hexdigest()
+ if download_md5sum != template_md5sum:
+ s = None
+ while s not in ["y", "n"]:
+ s = input(f"""Unexpected template md5sum.
+{download_md5sum} != {template_md5sum}, for:
+{template}
+continue?[yn] """).lower()
+ if s == "n":
+ sys.exit(1)
+
+ assert output_str is not None
+ output_str = output_str.replace("/** @options_json **/", options_json)
+ output_str = output_str.replace("/** @flamegraph_json **/", stacks_json)
+ output_fn = self.args.output or "flamegraph.html"
+ else:
+ output_str = stacks_json
+ output_fn = self.args.output or "stacks.json"
+
+ if output_fn == "-":
+ sys.stdout.write(output_str)
+ else:
+ print(f"dumping data to {output_fn}")
+ with open(output_fn, "w", encoding="utf-8") as out:
+ out.write(output_str)
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Create flame graphs using perf python module.")
+ parser.add_argument("-f", "--format", default="html", choices=["json", "html"],
+ help="output file format")
+ parser.add_argument("-o", "--output", help="output file name")
+ parser.add_argument("--template",
+ default="/usr/share/d3-flame-graph/d3-flamegraph-base.html",
+ help="path to flame graph HTML template")
+ parser.add_argument("--colorscheme", default="blue-green",
+ help="flame graph color scheme", choices=["blue-green", "orange"])
+ parser.add_argument("-i", "--input", help="input perf.data file")
+ parser.add_argument("--allow-download", default=False, action="store_true",
+ help="allow unprompted downloading of HTML template")
+ parser.add_argument("-e", "--event", default="", dest="event_name", type=str,
+ help="specify the event to generate flamegraph for")
+
+ cli_args = parser.parse_args()
+ cli = FlameGraphCLI(cli_args)
+ cli.run()
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 33/59] perf check-perf-trace: Port check-perf-trace to use python module
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Add a port of the check-perf-trace script that uses the perf python
module directly. This approach is significantly faster than using perf
script callbacks as it avoids creating intermediate dictionaries for
all event fields.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2:
1. String Match Accuracy: Replaced the substring check for `irq:softirq_entry`
events with a robust exact string match.
v3:
1. Safe Thread Resolution: Swapped out sample.sample_pid with
sample.sample_tid and safeguarded the session process lookup with
a try-except block.
v4:
1. Git Fixup Cleanup: Squashed the lingering fixup commit from the previous
session into its proper patch.
---
tools/perf/python/check-perf-trace.py | 113 ++++++++++++++++++++++++++
1 file changed, 113 insertions(+)
create mode 100755 tools/perf/python/check-perf-trace.py
diff --git a/tools/perf/python/check-perf-trace.py b/tools/perf/python/check-perf-trace.py
new file mode 100755
index 000000000000..7c1c7632a091
--- /dev/null
+++ b/tools/perf/python/check-perf-trace.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+Basic test of Python scripting support for perf.
+Ported from tools/perf/scripts/python/check-perf-trace.py
+"""
+
+import argparse
+import collections
+import perf
+
+unhandled: collections.defaultdict[str, int] = collections.defaultdict(int)
+session = None
+
+softirq_vecs = {
+ 0: "HI_SOFTIRQ",
+ 1: "TIMER_SOFTIRQ",
+ 2: "NET_TX_SOFTIRQ",
+ 3: "NET_RX_SOFTIRQ",
+ 4: "BLOCK_SOFTIRQ",
+ 5: "IRQ_POLL_SOFTIRQ",
+ 6: "TASKLET_SOFTIRQ",
+ 7: "SCHED_SOFTIRQ",
+ 8: "HRTIMER_SOFTIRQ",
+ 9: "RCU_SOFTIRQ",
+}
+
+def trace_begin() -> None:
+ """Called at the start of trace processing."""
+ print("trace_begin")
+
+def trace_end() -> None:
+ """Called at the end of trace processing."""
+ print_unhandled()
+
+def symbol_str(event_name: str, field_name: str, value: int) -> str:
+ """Resolves symbol values to strings."""
+ if event_name == "irq__softirq_entry" and field_name == "vec":
+ return softirq_vecs.get(value, str(value))
+ return str(value)
+
+def flag_str(event_name: str, field_name: str, value: int) -> str:
+ """Resolves flag values to strings."""
+ if event_name == "kmem__kmalloc" and field_name == "gfp_flags":
+ return f"0x{value:x}"
+ return str(value)
+
+def print_header(event_name: str, sample: perf.sample_event) -> None:
+ """Prints common header for events."""
+ secs = sample.sample_time // 1000000000
+ nsecs = sample.sample_time % 1000000000
+ comm = session.find_thread(sample.sample_pid).comm() if session else "[unknown]"
+ print(f"{event_name:<20} {sample.sample_cpu:5} {secs:05}.{nsecs:09} "
+ f"{sample.sample_pid:8} {comm:<20} ", end=' ')
+
+def print_uncommon(sample: perf.sample_event) -> None:
+ """Prints uncommon fields for tracepoints."""
+ # Fallback to 0 if field not found (e.g. on older kernels or if not tracepoint)
+ pc = getattr(sample, "common_preempt_count", 0)
+ flags = getattr(sample, "common_flags", 0)
+ lock_depth = getattr(sample, "common_lock_depth", 0)
+
+ print(f"common_preempt_count={pc}, common_flags={flags}, "
+ f"common_lock_depth={lock_depth}, ")
+
+def irq__softirq_entry(sample: perf.sample_event) -> None:
+ """Handles irq:softirq_entry events."""
+ print_header("irq__softirq_entry", sample)
+ print_uncommon(sample)
+ print(f"vec={symbol_str('irq__softirq_entry', 'vec', sample.vec)}")
+
+def kmem__kmalloc(sample: perf.sample_event) -> None:
+ """Handles kmem:kmalloc events."""
+ print_header("kmem__kmalloc", sample)
+ print_uncommon(sample)
+
+ print(f"call_site={sample.call_site:d}, ptr={sample.ptr:d}, "
+ f"bytes_req={sample.bytes_req:d}, bytes_alloc={sample.bytes_alloc:d}, "
+ f"gfp_flags={flag_str('kmem__kmalloc', 'gfp_flags', sample.gfp_flags)}")
+
+def trace_unhandled(event_name: str) -> None:
+ """Tracks unhandled events."""
+ unhandled[event_name] += 1
+
+def print_unhandled() -> None:
+ """Prints summary of unhandled events."""
+ if not unhandled:
+ return
+ print("\nunhandled events:\n")
+ print(f"{'event':<40} {'count':>10}")
+ print("---------------------------------------- -----------")
+ for event_name, count in unhandled.items():
+ print(f"{event_name:<40} {count:10}")
+
+def process_event(sample: perf.sample_event) -> None:
+ """Callback for processing events."""
+ event_name = str(sample.evsel)
+ if event_name == "evsel(irq:softirq_entry)":
+ irq__softirq_entry(sample)
+ elif "evsel(kmem:kmalloc)" in event_name:
+ kmem__kmalloc(sample)
+ else:
+ trace_unhandled(event_name)
+
+if __name__ == "__main__":
+ ap = argparse.ArgumentParser()
+ ap.add_argument("-i", "--input", default="perf.data", help="Input file name")
+ args = ap.parse_args()
+
+ trace_begin()
+ session = perf.session(perf.data(args.input), sample=process_event)
+ session.process_events()
+ trace_end()
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 35/59] perf event_analyzing_sample: Port event_analyzing_sample to use python module
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Add a port of the event_analyzing_sample script that uses the perf
python module directly. This approach is significantly faster than
using perf script callbacks as it avoids creating intermediate
dictionaries for all event fields.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2:
1. Dynamic Database Path: Moved DB_PATH to a command-line argument (
-d / --database ) that defaults to "perf.db" .
2. Security: Avoided using /dev/shm by default to prevent symlink
attacks, while retaining the performance suggestion in the help
text.
3. Corrected Closure Call: Fixed the bug where it was trying to call
.filter() on a closure.
v6:
- Fixed performance issue by removing autocommit mode in SQLite and
batching commits.
---
tools/perf/python/event_analyzing_sample.py | 297 ++++++++++++++++++++
1 file changed, 297 insertions(+)
create mode 100755 tools/perf/python/event_analyzing_sample.py
diff --git a/tools/perf/python/event_analyzing_sample.py b/tools/perf/python/event_analyzing_sample.py
new file mode 100755
index 000000000000..2132db7f0e56
--- /dev/null
+++ b/tools/perf/python/event_analyzing_sample.py
@@ -0,0 +1,297 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+General event handler in Python, using SQLite to analyze events.
+
+The 2 database related functions in this script just show how to gather
+the basic information, and users can modify and write their own functions
+according to their specific requirement.
+
+The first function "show_general_events" just does a basic grouping for all
+generic events with the help of sqlite, and the 2nd one "show_pebs_ll" is
+for a x86 HW PMU event: PEBS with load latency data.
+
+Ported from tools/perf/scripts/python/event_analyzing_sample.py
+"""
+
+import argparse
+import math
+import sqlite3
+import struct
+from typing import Any
+import perf
+
+# Event types, user could add more here
+EVTYPE_GENERIC = 0
+EVTYPE_PEBS = 1 # Basic PEBS event
+EVTYPE_PEBS_LL = 2 # PEBS event with load latency info
+EVTYPE_IBS = 3
+
+#
+# Currently we don't have good way to tell the event type, but by
+# the size of raw buffer, raw PEBS event with load latency data's
+# size is 176 bytes, while the pure PEBS event's size is 144 bytes.
+#
+def create_event(name, comm, dso, symbol, raw_buf):
+ """Create an event object based on raw buffer size."""
+ if len(raw_buf) == 144:
+ event = PebsEvent(name, comm, dso, symbol, raw_buf)
+ elif len(raw_buf) == 176:
+ event = PebsNHM(name, comm, dso, symbol, raw_buf)
+ else:
+ event = PerfEvent(name, comm, dso, symbol, raw_buf)
+
+ return event
+
+class PerfEvent:
+ """Base class for all perf event samples."""
+ event_num = 0
+ def __init__(self, name, comm, dso, symbol, raw_buf, ev_type=EVTYPE_GENERIC):
+ self.name = name
+ self.comm = comm
+ self.dso = dso
+ self.symbol = symbol
+ self.raw_buf = raw_buf
+ self.ev_type = ev_type
+ PerfEvent.event_num += 1
+
+ def show(self):
+ """Display PMU event info."""
+ print(f"PMU event: name={self.name:12s}, symbol={self.symbol:24s}, "
+ f"comm={self.comm:8s}, dso={self.dso:12s}")
+
+#
+# Basic Intel PEBS (Precise Event-based Sampling) event, whose raw buffer
+# contains the context info when that event happened: the EFLAGS and
+# linear IP info, as well as all the registers.
+#
+class PebsEvent(PerfEvent):
+ """Intel PEBS event."""
+ pebs_num = 0
+ def __init__(self, name, comm, dso, symbol, raw_buf, ev_type=EVTYPE_PEBS):
+ tmp_buf = raw_buf[0:80]
+ flags, ip, ax, bx, cx, dx, si, di, bp, sp = struct.unpack('QQQQQQQQQQ', tmp_buf)
+ self.flags = flags
+ self.ip = ip
+ self.ax = ax
+ self.bx = bx
+ self.cx = cx
+ self.dx = dx
+ self.si = si
+ self.di = di
+ self.bp = bp
+ self.sp = sp
+
+ super().__init__(name, comm, dso, symbol, raw_buf, ev_type)
+ PebsEvent.pebs_num += 1
+ del tmp_buf
+
+#
+# Intel Nehalem and Westmere support PEBS plus Load Latency info which lie
+# in the four 64 bit words write after the PEBS data:
+# Status: records the IA32_PERF_GLOBAL_STATUS register value
+# DLA: Data Linear Address (EIP)
+# DSE: Data Source Encoding, where the latency happens, hit or miss
+# in L1/L2/L3 or IO operations
+# LAT: the actual latency in cycles
+#
+class PebsNHM(PebsEvent):
+ """Intel Nehalem/Westmere PEBS event with load latency."""
+ pebs_nhm_num = 0
+ def __init__(self, name, comm, dso, symbol, raw_buf, ev_type=EVTYPE_PEBS_LL):
+ tmp_buf = raw_buf[144:176]
+ status, dla, dse, lat = struct.unpack('QQQQ', tmp_buf)
+ self.status = status
+ self.dla = dla
+ self.dse = dse
+ self.lat = lat
+
+ super().__init__(name, comm, dso, symbol, raw_buf, ev_type)
+ PebsNHM.pebs_nhm_num += 1
+ del tmp_buf
+
+session: Any = None
+
+con = None
+
+def trace_begin(db_path: str) -> None:
+ """Initialize database tables."""
+ print("In trace_begin:\n")
+ global con
+ con = sqlite3.connect(db_path)
+ assert con is not None
+
+ # Will create several tables at the start, pebs_ll is for PEBS data with
+ # load latency info, while gen_events is for general event.
+ con.execute("""
+ create table if not exists gen_events (
+ name text,
+ symbol text,
+ comm text,
+ dso text
+ );""")
+ con.execute("""
+ create table if not exists pebs_ll (
+ name text,
+ symbol text,
+ comm text,
+ dso text,
+ flags integer,
+ ip integer,
+ status integer,
+ dse integer,
+ dla integer,
+ lat integer
+ );""")
+
+def insert_db(event: Any) -> None:
+ """Insert event into database."""
+ assert con is not None
+ if event.ev_type == EVTYPE_GENERIC:
+ con.execute("insert into gen_events values(?, ?, ?, ?)",
+ (event.name, event.symbol, event.comm, event.dso))
+ elif event.ev_type == EVTYPE_PEBS_LL:
+ event.ip &= 0x7fffffffffffffff
+ event.dla &= 0x7fffffffffffffff
+ con.execute("insert into pebs_ll values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ (event.name, event.symbol, event.comm, event.dso, event.flags,
+ event.ip, event.status, event.dse, event.dla, event.lat))
+
+def process_event(sample: perf.sample_event) -> None:
+ """Callback for processing events."""
+ # Create and insert event object to a database so that user could
+ # do more analysis with simple database commands.
+
+ # Resolve comm, symbol, dso
+ comm = "Unknown_comm"
+ try:
+ if session is not None:
+ # FIXME: session.find_thread() only takes one argument and uses it as both
+ # PID and TID in C. This means it only resolves main threads correctly.
+ # Sub-threads will get the main thread's comm.
+ proc = session.find_thread(sample.sample_pid)
+ if proc:
+ comm = proc.comm()
+ except TypeError:
+ pass
+
+ # Symbol and dso info are not always resolved
+ dso = sample.dso if hasattr(sample, 'dso') and sample.dso else "Unknown_dso"
+ symbol = sample.symbol if hasattr(sample, 'symbol') and sample.symbol else "Unknown_symbol"
+ name = str(sample.evsel)
+ if name.startswith("evsel("):
+ name = name[6:-1]
+
+ # Create the event object and insert it to the right table in database
+ try:
+ event = create_event(name, comm, dso, symbol, sample.raw_buf)
+ insert_db(event)
+ except (sqlite3.Error, ValueError, TypeError) as e:
+ print(f"Error creating/inserting event: {e}")
+
+def num2sym(num: int) -> str:
+ """Convert number to a histogram symbol (log2)."""
+ # As the event number may be very big, so we can't use linear way
+ # to show the histogram in real number, but use a log2 algorithm.
+ if num <= 0:
+ return ""
+ snum = '#' * (int(math.log(num, 2)) + 1)
+ return snum
+
+def show_general_events() -> None:
+ """Display statistics for general events."""
+ assert con is not None
+ count = con.execute("select count(*) from gen_events")
+ for t in count:
+ print(f"There is {t[0]} records in gen_events table")
+ if t[0] == 0:
+ return
+
+ print("Statistics about the general events grouped by thread/symbol/dso: \n")
+
+ # Group by thread
+ commq = con.execute("""
+ select comm, count(comm) from gen_events
+ group by comm order by -count(comm)
+ """)
+ print(f"\n{ 'comm':>16} {'number':>8} {'histogram':>16}\n{'='*42}")
+ for row in commq:
+ print(f"{row[0]:>16} {row[1]:>8} {num2sym(row[1])}")
+
+ # Group by symbol
+ print(f"\n{'symbol':>32} {'number':>8} {'histogram':>16}\n{'='*58}")
+ symbolq = con.execute("""
+ select symbol, count(symbol) from gen_events
+ group by symbol order by -count(symbol)
+ """)
+ for row in symbolq:
+ print(f"{row[0]:>32} {row[1]:>8} {num2sym(row[1])}")
+
+ # Group by dso
+ print(f"\n{'dso':>40} {'number':>8} {'histogram':>16}\n{'='*74}")
+ dsoq = con.execute("select dso, count(dso) from gen_events group by dso order by -count(dso)")
+ for row in dsoq:
+ print(f"{row[0]:>40} {row[1]:>8} {num2sym(row[1])}")
+
+def show_pebs_ll() -> None:
+ """Display statistics for PEBS load latency events."""
+ assert con is not None
+ # This function just shows the basic info, and we could do more with the
+ # data in the tables, like checking the function parameters when some
+ # big latency events happen.
+ count = con.execute("select count(*) from pebs_ll")
+ for t in count:
+ print(f"There is {t[0]} records in pebs_ll table")
+ if t[0] == 0:
+ return
+
+ print("Statistics about the PEBS Load Latency events grouped by thread/symbol/dse/latency: \n")
+
+ # Group by thread
+ commq = con.execute("select comm, count(comm) from pebs_ll group by comm order by -count(comm)")
+ print(f"\n{'comm':>16} {'number':>8} {'histogram':>16}\n{'='*42}")
+ for row in commq:
+ print(f"{row[0]:>16} {row[1]:>8} {num2sym(row[1])}")
+
+ # Group by symbol
+ print(f"\n{'symbol':>32} {'number':>8} {'histogram':>16}\n{'='*58}")
+ symbolq = con.execute("""
+ select symbol, count(symbol) from pebs_ll
+ group by symbol order by -count(symbol)
+ """)
+ for row in symbolq:
+ print(f"{row[0]:>32} {row[1]:>8} {num2sym(row[1])}")
+
+ # Group by dse
+ dseq = con.execute("select dse, count(dse) from pebs_ll group by dse order by -count(dse)")
+ print(f"\n{'dse':>32} {'number':>8} {'histogram':>16}\n{'='*58}")
+ for row in dseq:
+ print(f"{row[0]:>32} {row[1]:>8} {num2sym(row[1])}")
+
+ # Group by latency
+ latq = con.execute("select lat, count(lat) from pebs_ll group by lat order by lat")
+ print(f"\n{'latency':>32} {'number':>8} {'histogram':>16}\n{'='*58}")
+ for row in latq:
+ print(f"{str(row[0]):>32} {row[1]:>8} {num2sym(row[1])}")
+
+def trace_end() -> None:
+ """Called at the end of trace processing."""
+ print("In trace_end:\n")
+ if con:
+ con.commit()
+ show_general_events()
+ show_pebs_ll()
+ if con:
+ con.close()
+
+if __name__ == "__main__":
+ ap = argparse.ArgumentParser(description="Analyze events with SQLite")
+ ap.add_argument("-i", "--input", default="perf.data", help="Input file name")
+ ap.add_argument("-d", "--database", default="perf.db",
+ help="Database file name (tip: use /dev/shm/perf.db for speedup)")
+ args = ap.parse_args()
+
+ trace_begin(args.database)
+ session = perf.session(perf.data(args.input), sample=process_event)
+ session.process_events()
+ trace_end()
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 31/59] perf gecko: Port gecko to use python module
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Add a port of the gecko script that uses the perf python module
directly. This approach is significantly faster than using perf script
callbacks as it avoids creating intermediate dictionaries for all
event fields.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2:
1. Improved Portability: Replaced the non-portable uname -op call with
platform.system() and platform.machine() , preventing potential
crashes on non-Linux platforms like macOS or BSD.
2. Robust Fallbacks: Fixed getattr calls for symbol and dso to
explicitly handle None values, preventing literal "None (in None)"
strings in the output when resolution fails.
3. Network Security: Bound the HTTP server to 127.0.0.1 (localhost)
instead of 0.0.0.0 (all interfaces), ensuring the current directory
is not exposed to the local network.
4. Avoided Port Conflicts: Switched from hardcoded port 8000 to port
0, allowing the operating system to automatically select an
available free port.
5. Fixed Race Condition: Moved HTTPServer creation to the main thread,
ensuring the server is bound and listening before the browser is
launched to fetch the file.
6. Browser Spec Compliance: Used 127.0.0.1 instead of localhost in the
generated URL to ensure modern browsers treat the connection as a
secure origin, avoiding mixed content blocks.
v6:
- Fixed CWD exposure and symlink attack risks by using a secure
temporary directory for the HTTP server.
---
tools/perf/python/gecko.py | 385 +++++++++++++++++++++++++++++++++++++
1 file changed, 385 insertions(+)
create mode 100755 tools/perf/python/gecko.py
diff --git a/tools/perf/python/gecko.py b/tools/perf/python/gecko.py
new file mode 100755
index 000000000000..1f152e1eca52
--- /dev/null
+++ b/tools/perf/python/gecko.py
@@ -0,0 +1,385 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+gecko.py - Convert perf record output to Firefox's gecko profile format
+"""
+
+import argparse
+import functools
+import json
+import os
+import platform
+import sys
+import tempfile
+import threading
+import urllib.parse
+import webbrowser
+from dataclasses import dataclass, field
+from http.server import HTTPServer, SimpleHTTPRequestHandler
+from typing import Dict, List, NamedTuple, Optional, Tuple
+
+import perf
+
+
+# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L156
+class Frame(NamedTuple):
+ """A single stack frame in the gecko profile format."""
+ string_id: int
+ relevantForJS: bool
+ innerWindowID: int
+ implementation: None
+ optimizations: None
+ line: None
+ column: None
+ category: int
+ subcategory: Optional[int]
+
+
+# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L216
+class Stack(NamedTuple):
+ """A single stack in the gecko profile format."""
+ prefix_id: Optional[int]
+ frame_id: int
+
+
+# https://github.com/firefox-devtools/profiler/blob/53970305b51b9b472e26d7457fee1d66cd4e2737/src/types/gecko-profile.js#L90
+class Sample(NamedTuple):
+ """A single sample in the gecko profile format."""
+ stack_id: Optional[int]
+ time_ms: float
+ responsiveness: int
+
+
+@dataclass
+class Tables:
+ """Interned tables for the gecko profile format."""
+ frame_table: List[Frame] = field(default_factory=list)
+ string_table: List[str] = field(default_factory=list)
+ string_map: Dict[str, int] = field(default_factory=dict)
+ stack_table: List[Stack] = field(default_factory=list)
+ stack_map: Dict[Tuple[Optional[int], int], int] = field(default_factory=dict)
+ frame_map: Dict[str, int] = field(default_factory=dict)
+
+
+@dataclass
+class Thread:
+ """A builder for a profile of the thread."""
+ comm: str
+ pid: int
+ tid: int
+ user_category: int
+ kernel_category: int
+ samples: List[Sample] = field(default_factory=list)
+ tables: Tables = field(default_factory=Tables)
+
+ def _intern_stack(self, frame_id: int, prefix_id: Optional[int]) -> int:
+ """Gets a matching stack, or saves the new stack. Returns a Stack ID."""
+ key = (prefix_id, frame_id)
+ stack_id = self.tables.stack_map.get(key)
+ if stack_id is None:
+ stack_id = len(self.tables.stack_table)
+ self.tables.stack_table.append(Stack(prefix_id=prefix_id, frame_id=frame_id))
+ self.tables.stack_map[key] = stack_id
+ return stack_id
+
+ def _intern_string(self, string: str) -> int:
+ """Gets a matching string, or saves the new string. Returns a String ID."""
+ string_id = self.tables.string_map.get(string)
+ if string_id is not None:
+ return string_id
+ string_id = len(self.tables.string_table)
+ self.tables.string_table.append(string)
+ self.tables.string_map[string] = string_id
+ return string_id
+
+ def _intern_frame(self, frame_str: str) -> int:
+ """Gets a matching stack frame, or saves the new frame. Returns a Frame ID."""
+ frame_id = self.tables.frame_map.get(frame_str)
+ if frame_id is not None:
+ return frame_id
+ frame_id = len(self.tables.frame_table)
+ self.tables.frame_map[frame_str] = frame_id
+ string_id = self._intern_string(frame_str)
+
+ category = self.user_category
+ if (frame_str.find('kallsyms') != -1 or
+ frame_str.find('/vmlinux') != -1 or
+ frame_str.endswith('.ko)')):
+ category = self.kernel_category
+
+ self.tables.frame_table.append(Frame(
+ string_id=string_id,
+ relevantForJS=False,
+ innerWindowID=0,
+ implementation=None,
+ optimizations=None,
+ line=None,
+ column=None,
+ category=category,
+ subcategory=None,
+ ))
+ return frame_id
+
+ def add_sample(self, comm: str, stack: List[str], time_ms: float) -> None:
+ """Add a timestamped stack trace sample to the thread builder."""
+ if self.comm != comm:
+ self.comm = comm
+
+ prefix_stack_id: Optional[int] = None
+ for frame in stack:
+ frame_id = self._intern_frame(frame)
+ prefix_stack_id = self._intern_stack(frame_id, prefix_stack_id)
+
+ if prefix_stack_id is not None:
+ self.samples.append(Sample(stack_id=prefix_stack_id,
+ time_ms=time_ms,
+ responsiveness=0))
+
+ def to_json_dict(self) -> Dict:
+ """Converts current Thread to GeckoThread JSON format."""
+ return {
+ "tid": self.tid,
+ "pid": self.pid,
+ "name": self.comm,
+ "markers": {
+ "schema": {
+ "name": 0,
+ "startTime": 1,
+ "endTime": 2,
+ "phase": 3,
+ "category": 4,
+ "data": 5,
+ },
+ "data": [],
+ },
+ "samples": {
+ "schema": {
+ "stack": 0,
+ "time": 1,
+ "responsiveness": 2,
+ },
+ "data": self.samples
+ },
+ "frameTable": {
+ "schema": {
+ "location": 0,
+ "relevantForJS": 1,
+ "innerWindowID": 2,
+ "implementation": 3,
+ "optimizations": 4,
+ "line": 5,
+ "column": 6,
+ "category": 7,
+ "subcategory": 8,
+ },
+ "data": self.tables.frame_table,
+ },
+ "stackTable": {
+ "schema": {
+ "prefix": 0,
+ "frame": 1,
+ },
+ "data": self.tables.stack_table,
+ },
+ "stringTable": self.tables.string_table,
+ "registerTime": 0,
+ "unregisterTime": None,
+ "processType": "default",
+ }
+
+
+class CORSRequestHandler(SimpleHTTPRequestHandler):
+ """Enable CORS for requests from profiler.firefox.com."""
+ def end_headers(self):
+ self.send_header('Access-Control-Allow-Origin', 'https://profiler.firefox.com')
+ super().end_headers()
+
+
+@dataclass
+class CategoryData:
+ """Category configuration for the gecko profile."""
+ user_index: int = 0
+ kernel_index: int = 1
+ categories: List[Dict] = field(default_factory=list)
+
+
+class GeckoCLI:
+ """Command-line interface for converting perf data to Gecko format."""
+ def __init__(self, args):
+ self.args = args
+ self.tid_to_thread: Dict[int, Thread] = {}
+ self.start_time_ms: Optional[float] = None
+ self.session = None
+ self.product = f"{platform.system()} {platform.machine()}"
+ self.cat_data = CategoryData(
+ categories=[
+ {
+ "name": 'User',
+ "color": args.user_color,
+ "subcategories": ['Other']
+ },
+ {
+ "name": 'Kernel',
+ "color": args.kernel_color,
+ "subcategories": ['Other']
+ },
+ ]
+ )
+
+ def process_event(self, sample) -> None:
+ """Process a single perf sample event."""
+ if self.args.event_name and self.args.event_name not in str(sample.evsel):
+ return
+
+ # sample_time is in nanoseconds. Gecko wants milliseconds.
+ time_ms = sample.sample_time / 1000000.0
+ pid = sample.sample_pid
+ tid = sample.sample_tid
+
+ if self.start_time_ms is None:
+ self.start_time_ms = time_ms
+
+ try:
+ thread_info = self.session.find_thread(tid)
+ comm = thread_info.comm()
+ except Exception:
+ comm = "[unknown]"
+
+ stack = []
+ callchain = sample.callchain
+ if callchain:
+ for entry in callchain:
+ symbol = entry.symbol or "[unknown]"
+ dso = entry.dso or "[unknown]"
+ stack.append(f"{symbol} (in {dso})")
+ # Reverse because Gecko wants root first.
+ stack.reverse()
+ else:
+ # Fallback if no callchain is present
+ try:
+ # If the perf module exposes symbol/dso directly on sample
+ # when callchain is missing, we use them.
+ symbol = getattr(sample, 'symbol', '[unknown]') or '[unknown]'
+ dso = getattr(sample, 'dso', '[unknown]') or '[unknown]'
+ stack.append(f"{symbol} (in {dso})")
+ except AttributeError:
+ stack.append("[unknown] (in [unknown])")
+
+ thread = self.tid_to_thread.get(tid)
+ if thread is None:
+ thread = Thread(comm=comm, pid=pid, tid=tid,
+ user_category=self.cat_data.user_index,
+ kernel_category=self.cat_data.kernel_index)
+ self.tid_to_thread[tid] = thread
+ thread.add_sample(comm=comm, stack=stack, time_ms=time_ms)
+
+ def run(self) -> None:
+ """Run the conversion process."""
+ input_file = self.args.input or "perf.data"
+ if not os.path.exists(input_file):
+ print(f"Error: {input_file} not found.", file=sys.stderr)
+ sys.exit(1)
+
+ try:
+ self.session = perf.session(perf.data(input_file), sample=self.process_event)
+ except Exception as e:
+ print(f"Error opening session: {e}", file=sys.stderr)
+ sys.exit(1)
+
+ self.session.process_events()
+
+ threads = [t.to_json_dict() for t in self.tid_to_thread.values()]
+
+ gecko_profile = {
+ "meta": {
+ "interval": 1,
+ "processType": 0,
+ "product": self.product,
+ "stackwalk": 1,
+ "debug": 0,
+ "gcpoison": 0,
+ "asyncstack": 1,
+ "startTime": self.start_time_ms,
+ "shutdownTime": None,
+ "version": 24,
+ "presymbolicated": True,
+ "categories": self.cat_data.categories,
+ "markerSchema": [],
+ },
+ "libs": [],
+ "threads": threads,
+ "processes": [],
+ "pausedRanges": [],
+ }
+
+ output_file = self.args.save_only
+ if output_file is None:
+ self._write_and_launch(gecko_profile)
+ else:
+ print(f'[ perf gecko: Captured and wrote into {output_file} ]')
+ with open(output_file, 'w', encoding='utf-8') as f:
+ json.dump(gecko_profile, f, indent=2)
+
+ def _write_and_launch(self, profile: Dict) -> None:
+ """Write the profile to a file and launch the Firefox profiler."""
+ print("Starting Firefox Profiler on your default browser...")
+
+ with tempfile.TemporaryDirectory() as tmp_dir_name:
+ filename = os.path.join(tmp_dir_name, 'gecko_profile.json')
+
+ with open(filename, 'w', encoding='utf-8') as f:
+ json.dump(profile, f, indent=2)
+
+ handler = functools.partial(CORSRequestHandler, directory=tmp_dir_name)
+ try:
+ httpd = HTTPServer(('127.0.0.1', 0), handler)
+ except OSError as e:
+ print(f"Error starting HTTP server: {e}", file=sys.stderr)
+ sys.exit(1)
+
+ port = httpd.server_port
+
+ def start_server():
+ httpd.serve_forever()
+
+ thread = threading.Thread(target=start_server, daemon=True)
+ thread.start()
+
+ # Open the browser
+ safe_string = urllib.parse.quote_plus(f'http://127.0.0.1:{port}/gecko_profile.json')
+ url = f'https://profiler.firefox.com/from-url/{safe_string}'
+ webbrowser.open(url)
+
+ print(f'[ perf gecko: Captured and wrote into {filename} ]')
+ print("Press Ctrl+C to stop the local server.")
+ try:
+ # Keep the main thread alive so the daemon thread can serve requests
+ stop_event = threading.Event()
+ while True:
+ stop_event.wait(1)
+ except KeyboardInterrupt:
+ print("\nStopping server...")
+ httpd.shutdown()
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(
+ description="Convert perf.data to Firefox's Gecko Profile format"
+ )
+ parser.add_argument('--user-color', default='yellow',
+ help='Color for the User category',
+ choices=['yellow', 'blue', 'purple', 'green', 'orange', 'red',
+ 'grey', 'magenta'])
+ parser.add_argument('--kernel-color', default='orange',
+ help='Color for the Kernel category',
+ choices=['yellow', 'blue', 'purple', 'green', 'orange', 'red',
+ 'grey', 'magenta'])
+ parser.add_argument('--save-only',
+ help='Save the output to a file instead of opening Firefox\'s profiler')
+ parser.add_argument("-i", "--input", help="input perf.data file")
+ parser.add_argument("-e", "--event", default="", dest="event_name", type=str,
+ help="specify the event to generate gecko profile for")
+
+ cli_args = parser.parse_args()
+ cli = GeckoCLI(cli_args)
+ cli.run()
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 32/59] perf arm-cs-trace-disasm: Port arm-cs-trace-disasm to use python module
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Add a port of the arm-cs-trace-disasm script that uses the perf python
module directly. This approach is significantly faster than using perf
script callbacks as it avoids creating intermediate dictionaries for
all event fields. Update the testing to use the ported script.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2:
1. Added Missing Import: Added import perf at the top of
arm-cs-trace-disasm.py .
2. Fixed Unpacking Error: Updated the call to sample.srccode() to
expect a 3-tuple instead of a 4-tuple, matching the return type in
the C extension.
3. Fixed Termination Logic: Replaced return with sys.exit(0) when the
stop_time or stop_sample limits are reached to properly terminate
the processing loop.
4. Fixed Test Path: Updated script_path in
test_arm_coresight_disasm.sh to point to
../../python/arm-cs-trace-disasm.py instead of the old legacy path.
---
tools/perf/python/arm-cs-trace-disasm.py | 338 ++++++++++++++++++
.../tests/shell/test_arm_coresight_disasm.sh | 12 +-
2 files changed, 345 insertions(+), 5 deletions(-)
create mode 100755 tools/perf/python/arm-cs-trace-disasm.py
diff --git a/tools/perf/python/arm-cs-trace-disasm.py b/tools/perf/python/arm-cs-trace-disasm.py
new file mode 100755
index 000000000000..92c97cca6f66
--- /dev/null
+++ b/tools/perf/python/arm-cs-trace-disasm.py
@@ -0,0 +1,338 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+arm-cs-trace-disasm.py: ARM CoreSight Trace Dump With Disassember using perf python module
+"""
+
+import os
+from os import path
+import re
+from subprocess import check_output
+import argparse
+import platform
+import sys
+from typing import Dict, List, Optional
+
+import perf
+
+# Initialize global dicts and regular expression
+DISASM_CACHE: Dict[str, List[str]] = {}
+CPU_DATA: Dict[str, int] = {}
+DISASM_RE = re.compile(r"^\s*([0-9a-fA-F]+):")
+DISASM_FUNC_RE = re.compile(r"^\s*([0-9a-fA-F]+)\s.*:")
+CACHE_SIZE = 64*1024
+SAMPLE_IDX = -1
+
+GLB_SOURCE_FILE_NAME: Optional[str] = None
+GLB_LINE_NUMBER: Optional[int] = None
+GLB_DSO: Optional[str] = None
+
+KVER = platform.release()
+VMLINUX_PATHS = [
+ f"/usr/lib/debug/boot/vmlinux-{KVER}.debug",
+ f"/usr/lib/debug/lib/modules/{KVER}/vmlinux",
+ f"/lib/modules/{KVER}/build/vmlinux",
+ f"/usr/lib/debug/boot/vmlinux-{KVER}",
+ f"/boot/vmlinux-{KVER}",
+ "/boot/vmlinux",
+ "vmlinux"
+]
+
+def default_objdump() -> str:
+ """Return the default objdump path from perf config or 'objdump'."""
+ try:
+ config = perf.config_get("annotate.objdump")
+ return str(config) if config else "objdump"
+ except (AttributeError, TypeError):
+ return "objdump"
+
+def find_vmlinux() -> Optional[str]:
+ """Find the vmlinux file in standard paths."""
+ if hasattr(find_vmlinux, "path"):
+ return getattr(find_vmlinux, "path")
+
+ for v in VMLINUX_PATHS:
+ if os.access(v, os.R_OK):
+ setattr(find_vmlinux, "path", v)
+ return v
+ setattr(find_vmlinux, "path", None)
+ return None
+
+def get_dso_file_path(dso_name: str, dso_build_id: str, vmlinux: Optional[str]) -> str:
+ """Return the path to the DSO file."""
+ if dso_name in ("[kernel.kallsyms]", "vmlinux"):
+ if vmlinux:
+ return vmlinux
+ return find_vmlinux() or dso_name
+
+ if dso_name == "[vdso]":
+ append = "/vdso"
+ else:
+ append = "/elf"
+
+ buildid_dir = os.environ.get('PERF_BUILDID_DIR')
+ if not buildid_dir:
+ buildid_dir = os.path.join(os.environ.get('HOME', ''), '.debug')
+
+ dso_path = buildid_dir + "/" + dso_name + "/" + dso_build_id + append
+ # Replace duplicate slash chars to single slash char
+ dso_path = dso_path.replace('//', '/', 1)
+ return dso_path
+
+def read_disam(dso_fname: str, dso_start: int, start_addr: int,
+ stop_addr: int, objdump: str) -> List[str]:
+ """Read disassembly from a DSO file using objdump."""
+ addr_range = f"{start_addr}:{stop_addr}:{dso_fname}"
+
+ # Don't let the cache get too big, clear it when it hits max size
+ if len(DISASM_CACHE) > CACHE_SIZE:
+ DISASM_CACHE.clear()
+
+ if addr_range in DISASM_CACHE:
+ disasm_output = DISASM_CACHE[addr_range]
+ else:
+ start_addr = start_addr - dso_start
+ stop_addr = stop_addr - dso_start
+ disasm = [objdump, "-d", "-z",
+ f"--start-address={start_addr:#x}",
+ f"--stop-address={stop_addr:#x}"]
+ disasm += [dso_fname]
+ disasm_output = check_output(disasm).decode('utf-8').split('\n')
+ DISASM_CACHE[addr_range] = disasm_output
+
+ return disasm_output
+
+def print_disam(dso_fname: str, dso_start: int, start_addr: int,
+ stop_addr: int, objdump: str) -> None:
+ """Print disassembly for a given address range."""
+ for line in read_disam(dso_fname, dso_start, start_addr, stop_addr, objdump):
+ m = DISASM_FUNC_RE.search(line)
+ if m is None:
+ m = DISASM_RE.search(line)
+ if m is None:
+ continue
+ print(f"\t{line}")
+
+def print_sample(sample: perf.sample_event) -> None:
+ """Print sample details."""
+ print(f"Sample = {{ cpu: {sample.sample_cpu:04d} addr: {sample.sample_addr:016x} "
+ f"phys_addr: {sample.sample_phys_addr:016x} ip: {sample.sample_ip:016x} "
+ f"pid: {sample.sample_pid} tid: {sample.sample_tid} period: {sample.sample_period} "
+ f"time: {sample.sample_time} index: {SAMPLE_IDX}}}")
+
+def common_start_str(comm: str, sample: perf.sample_event) -> str:
+ """Return common start string for sample output."""
+ sec = int(sample.sample_time / 1000000000)
+ ns = sample.sample_time % 1000000000
+ cpu = sample.sample_cpu
+ pid = sample.sample_pid
+ tid = sample.sample_tid
+ return f"{comm:>16s} {pid:5u}/{tid:<5u} [{cpu:04d}] {sec:9d}.{ns:09d} "
+
+def print_srccode(comm: str, sample: perf.sample_event, symbol: str, dso: str) -> None:
+ """Print source code and symbols for a sample."""
+ ip = sample.sample_ip
+ if symbol == "[unknown]":
+ start_str = common_start_str(comm, sample) + f"{ip:x}".rjust(16).ljust(40)
+ else:
+ symoff = 0
+ sym_start = sample.sym_start
+ if sym_start is not None:
+ symoff = ip - sym_start
+ offs = f"+{symoff:#x}" if symoff != 0 else ""
+ start_str = common_start_str(comm, sample) + (symbol + offs).ljust(40)
+
+ global GLB_SOURCE_FILE_NAME, GLB_LINE_NUMBER, GLB_DSO
+
+ source_file_name, line_number, source_line = sample.srccode()
+ if source_file_name:
+ if GLB_LINE_NUMBER == line_number and GLB_SOURCE_FILE_NAME == source_file_name:
+ src_str = ""
+ else:
+ if len(source_file_name) > 40:
+ src_file = f"...{source_file_name[-37:]} "
+ else:
+ src_file = source_file_name.ljust(41)
+
+ if source_line is None:
+ src_str = f"{src_file}{line_number:>4d} <source not found>"
+ else:
+ src_str = f"{src_file}{line_number:>4d} {source_line}"
+ GLB_DSO = None
+ elif dso == GLB_DSO:
+ src_str = ""
+ else:
+ src_str = dso
+ GLB_DSO = dso
+
+ GLB_LINE_NUMBER = line_number
+ GLB_SOURCE_FILE_NAME = source_file_name
+
+ print(start_str, src_str)
+
+class TraceDisasm:
+ """Class to handle trace disassembly."""
+ def __init__(self, cli_options: argparse.Namespace):
+ self.options = cli_options
+ self.sample_idx = -1
+ self.session: Optional[perf.session] = None
+
+ def process_event(self, sample: perf.sample_event) -> None:
+ """Process a single perf event."""
+ self.sample_idx += 1
+ global SAMPLE_IDX
+ SAMPLE_IDX = self.sample_idx
+
+ if self.options.start_time and sample.sample_time < self.options.start_time:
+ return
+ if self.options.stop_time and sample.sample_time > self.options.stop_time:
+ sys.exit(0)
+ if self.options.start_sample and self.sample_idx < self.options.start_sample:
+ return
+ if self.options.stop_sample and self.sample_idx > self.options.stop_sample:
+ sys.exit(0)
+
+ ev_name = str(sample.evsel)
+ if self.options.verbose:
+ print(f"Event type: {ev_name}")
+ print_sample(sample)
+
+ dso = sample.dso or '[unknown]'
+ symbol = sample.symbol or '[unknown]'
+ dso_bid = sample.dso_bid or '[unknown]'
+ dso_start = sample.map_start
+ dso_end = sample.map_end
+ map_pgoff = sample.map_pgoff or 0
+
+ comm = "[unknown]"
+ try:
+ if self.session:
+ thread_info = self.session.find_thread(sample.sample_tid)
+ if thread_info:
+ comm = thread_info.comm()
+ except (TypeError, AttributeError):
+ pass
+
+ cpu = sample.sample_cpu
+ addr = sample.sample_addr
+
+ if CPU_DATA.get(str(cpu) + 'addr') is None:
+ CPU_DATA[str(cpu) + 'addr'] = addr
+ return
+
+ if dso == '[unknown]':
+ return
+
+ if dso_start is None or dso_end is None:
+ print(f"Failed to find valid dso map for dso {dso}")
+ return
+
+ if ev_name.startswith("instructions"):
+ print_srccode(comm, sample, symbol, dso)
+ return
+
+ if not ev_name.startswith("branches"):
+ return
+
+ self._process_branch(sample, comm, symbol, dso, dso_bid, dso_start, dso_end, map_pgoff)
+
+ def _process_branch(self, sample: perf.sample_event, comm: str, symbol: str, dso: str,
+ dso_bid: str, dso_start: int, dso_end: int, map_pgoff: int) -> None:
+ """Helper to process branch events."""
+ cpu = sample.sample_cpu
+ ip = sample.sample_ip
+ addr = sample.sample_addr
+
+ start_addr = CPU_DATA[str(cpu) + 'addr']
+ stop_addr = ip + 4
+
+ # Record for previous sample packet
+ CPU_DATA[str(cpu) + 'addr'] = addr
+
+ # Filter out zero start_address. Optionally identify CS_ETM_TRACE_ON packet
+ if start_addr == 0:
+ if stop_addr == 4 and self.options.verbose:
+ print(f"CPU{cpu}: CS_ETM_TRACE_ON packet is inserted")
+ return
+
+ if start_addr < dso_start or start_addr > dso_end:
+ print(f"Start address {start_addr:#x} is out of range [ {dso_start:#x} .. "
+ f"{dso_end:#x} ] for dso {dso}")
+ return
+
+ if stop_addr < dso_start or stop_addr > dso_end:
+ print(f"Stop address {stop_addr:#x} is out of range [ {dso_start:#x} .. "
+ f"{dso_end:#x} ] for dso {dso}")
+ return
+
+ if self.options.objdump is not None:
+ if dso == "[kernel.kallsyms]" or dso_start == 0x400000:
+ dso_vm_start = 0
+ map_pgoff_local = 0
+ else:
+ dso_vm_start = dso_start
+ map_pgoff_local = map_pgoff
+
+ dso_fname = get_dso_file_path(dso, dso_bid, self.options.vmlinux)
+ if path.exists(dso_fname):
+ print_disam(dso_fname, dso_vm_start, start_addr + map_pgoff_local,
+ stop_addr + map_pgoff_local, self.options.objdump)
+ else:
+ print(f"Failed to find dso {dso} for address range [ "
+ f"{start_addr + map_pgoff_local:#x} .. {stop_addr + map_pgoff_local:#x} ]")
+
+ print_srccode(comm, sample, symbol, dso)
+
+ def run(self) -> None:
+ """Run the trace disassembly session."""
+ input_file = self.options.input or "perf.data"
+ if not os.path.exists(input_file):
+ print(f"Error: {input_file} not found.", file=sys.stderr)
+ sys.exit(1)
+
+ print('ARM CoreSight Trace Data Assembler Dump')
+ try:
+ self.session = perf.session(perf.data(input_file), sample=self.process_event)
+ except Exception as e:
+ print(f"Error opening session: {e}", file=sys.stderr)
+ sys.exit(1)
+
+ self.session.process_events()
+ print('End')
+
+if __name__ == "__main__":
+ def int_arg(v: str) -> int:
+ """Helper for integer command line arguments."""
+ val = int(v)
+ if val < 0:
+ raise argparse.ArgumentTypeError("Argument must be a positive integer")
+ return val
+
+ arg_parser = argparse.ArgumentParser(description="ARM CoreSight Trace Dump With Disassembler")
+ arg_parser.add_argument("-i", "--input", help="input perf.data file")
+ arg_parser.add_argument("-k", "--vmlinux",
+ help="Set path to vmlinux file. Omit to autodetect")
+ arg_parser.add_argument("-d", "--objdump", nargs="?", const=default_objdump(),
+ help="Show disassembly. Can also be used to change the objdump path")
+ arg_parser.add_argument("-v", "--verbose", action="store_true", help="Enable debugging log")
+ arg_parser.add_argument("--start-time", type=int_arg,
+ help="Monotonic clock time of sample to start from.")
+ arg_parser.add_argument("--stop-time", type=int_arg,
+ help="Monotonic clock time of sample to stop at.")
+ arg_parser.add_argument("--start-sample", type=int_arg,
+ help="Index of sample to start from.")
+ arg_parser.add_argument("--stop-sample", type=int_arg,
+ help="Index of sample to stop at.")
+
+ parsed_options = arg_parser.parse_args()
+ if (parsed_options.start_time and parsed_options.stop_time and \
+ parsed_options.start_time >= parsed_options.stop_time):
+ print("--start-time must less than --stop-time")
+ sys.exit(2)
+ if (parsed_options.start_sample and parsed_options.stop_sample and \
+ parsed_options.start_sample >= parsed_options.stop_sample):
+ print("--start-sample must less than --stop-sample")
+ sys.exit(2)
+
+ td = TraceDisasm(parsed_options)
+ td.run()
diff --git a/tools/perf/tests/shell/test_arm_coresight_disasm.sh b/tools/perf/tests/shell/test_arm_coresight_disasm.sh
index 0dfb4fadf531..c15cd60e1c24 100755
--- a/tools/perf/tests/shell/test_arm_coresight_disasm.sh
+++ b/tools/perf/tests/shell/test_arm_coresight_disasm.sh
@@ -24,7 +24,7 @@ perfdata_dir=$(mktemp -d /tmp/__perf_test.perf.data.XXXXX)
perfdata=${perfdata_dir}/perf.data
file=$(mktemp /tmp/temporary_file.XXXXX)
# Relative path works whether it's installed or running from repo
-script_path=$(dirname "$0")/../../scripts/python/arm-cs-trace-disasm.py
+script_path=$(dirname "$0")/../../python/arm-cs-trace-disasm.py
cleanup_files()
{
@@ -45,8 +45,9 @@ branch_search="\sbl${sep}b${sep}b.ne${sep}b.eq${sep}cbz\s"
if [ -e /proc/kcore ]; then
echo "Testing kernel disassembly"
perf record -o ${perfdata} -e cs_etm//k --kcore -- touch $file > /dev/null 2>&1
- perf script -i ${perfdata} -s python:${script_path} -- \
- -d --stop-sample=30 2> /dev/null > ${file}
+ # shellcheck source=lib/setup_python.sh
+ . "$(dirname "$0")"/lib/setup_python.sh
+ $PYTHON ${script_path} -i ${perfdata} -d --stop-sample=30 2> /dev/null > ${file}
grep -q -e ${branch_search} ${file}
echo "Found kernel branches"
else
@@ -57,8 +58,9 @@ fi
## Test user ##
echo "Testing userspace disassembly"
perf record -o ${perfdata} -e cs_etm//u -- touch $file > /dev/null 2>&1
-perf script -i ${perfdata} -s python:${script_path} -- \
- -d --stop-sample=30 2> /dev/null > ${file}
+# shellcheck source=lib/setup_python.sh
+. "$(dirname "$0")"/lib/setup_python.sh
+$PYTHON ${script_path} -i ${perfdata} -d --stop-sample=30 2> /dev/null > ${file}
grep -q -e ${branch_search} ${file}
echo "Found userspace branches"
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 28/59] perf syscall-counts-by-pid: Port syscall-counts-by-pid to use python module
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Rewrite tools/perf/scripts/python/syscall-counts-by-pid.py to use the
python module and various style changes. By avoiding the overheads in
the `perf script` execution the performance improves by more than 3.8x
as shown in the following (with PYTHON_PATH and PERF_EXEC_PATH set as
necessary):
```
$ perf record -e raw_syscalls:sys_enter -a sleep 1
...
$ time perf script tools/perf/scripts/python/syscall-counts-by-pid.py perf
Install the python-audit package to get syscall names.
For example:
# apt-get install python3-audit (Ubuntu)
# yum install python3-audit (Fedora)
etc.
Press control+C to stop and show the summary
Warning:
1 out of order events recorded.
syscall events for perf:
comm [pid]/syscalls count
--------------------------------------- ----------
perf [3886080]
1 538989
16 32
203 17
3 2
257 1
204 1
15 1
0 1
perf [3886082]
7 1
real 0m3.852s
user 0m3.512s
sys 0m0.336s
$ time python3 tools/perf/python/syscall-counts-by-pid.py perf
Warning:
1 out of order events recorded.
syscall events for perf:
comm [pid]/syscalls count
--------------------------------------- -----------
perf [3886080]
write 538989
ioctl 32
sched_setaffinity 17
close 2
openat 1
sched_getaffinity 1
rt_sigreturn 1
read 1
perf [3886082]
poll 1
real 0m1.011s
user 0m0.963s
sys 0m0.048s
```
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2:
1. Removed Unused Variable: Removed id_keys which was assigned but
never read.
2. Fallback for Unknown Syscalls: If perf.syscall_name() returns None
for an unmapped ID, it now falls back to the numeric ID string to
prevent TypeError crashes during string formatting.
3. Fallback for Syscall Number Attribute: It now checks for
__syscall_nr first, and if missing, falls back to checking for nr .
4. Robust Process Resolution: Added a try-except block around
session.process(sample.pid).comm() to handle untracked PIDs
gracefully instead of crashing on a TypeError .
5. Restored PID Filtering: The script now attempts to parse the
positional argument as an integer to filter by Process ID. If that
fails, it treats it as a command name (COMM) string to filter by,
restoring behavior from the original legacy script.
6. Support for Custom Input Files: Added a -i / --input command-line
argument to support arbitrarily named trace files, removing the
hardcoded "perf.data" restriction.
---
tools/perf/python/syscall-counts-by-pid.py | 88 ++++++++++++++++++++++
1 file changed, 88 insertions(+)
create mode 100755 tools/perf/python/syscall-counts-by-pid.py
diff --git a/tools/perf/python/syscall-counts-by-pid.py b/tools/perf/python/syscall-counts-by-pid.py
new file mode 100755
index 000000000000..ff962334a143
--- /dev/null
+++ b/tools/perf/python/syscall-counts-by-pid.py
@@ -0,0 +1,88 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+Displays system-wide system call totals, broken down by syscall.
+If a [comm] arg is specified, only syscalls called by [comm] are displayed.
+"""
+
+import argparse
+from collections import defaultdict
+import perf
+
+syscalls: dict[tuple[str, int, int], int] = defaultdict(int)
+for_comm = None
+for_pid = None
+session = None
+
+
+def print_syscall_totals():
+ """Print aggregated statistics."""
+ if for_comm is not None:
+ print(f"\nsyscall events for {for_comm}:\n")
+ elif for_pid is not None:
+ print(f"\nsyscall events for PID {for_pid}:\n")
+ else:
+ print("\nsyscall events:\n")
+
+ print(f"{'comm [pid]/syscalls':<40} {'count':>10}")
+ print("---------------------------------------- -----------")
+
+ sorted_keys = sorted(syscalls.keys(), key=lambda k: (k[0], k[1], k[2]))
+ current_comm_pid = None
+ for comm, pid, sc_id in sorted_keys:
+ if current_comm_pid != (comm, pid):
+ print(f"\n{comm} [{pid}]")
+ current_comm_pid = (comm, pid)
+ name = perf.syscall_name(sc_id) or str(sc_id)
+ print(f" {name:<38} {syscalls[(comm, pid, sc_id)]:>10}")
+
+
+def process_event(sample):
+ """Process a single sample event."""
+ event_name = str(sample.evsel)
+ if event_name == "evsel(raw_syscalls:sys_enter)":
+ sc_id = getattr(sample, "id", -1)
+ elif event_name.startswith("evsel(syscalls:sys_enter_"):
+ sc_id = getattr(sample, "__syscall_nr", None)
+ if sc_id is None:
+ sc_id = getattr(sample, "nr", -1)
+ else:
+ return
+
+ if sc_id == -1:
+ return
+
+ pid = sample.sample_pid
+
+ if for_pid and pid != for_pid:
+ return
+
+ comm = "unknown"
+ try:
+ if session:
+ proc = session.find_thread(pid)
+ if proc:
+ comm = proc.comm()
+ except (TypeError, AttributeError):
+ pass
+
+ if for_comm and comm != for_comm:
+ return
+ syscalls[(comm, pid, sc_id)] += 1
+
+
+if __name__ == "__main__":
+ ap = argparse.ArgumentParser()
+ ap.add_argument("filter", nargs="?", help="COMM or PID to filter by")
+ ap.add_argument("-i", "--input", default="perf.data", help="Input file name")
+ args = ap.parse_args()
+
+ if args.filter:
+ try:
+ for_pid = int(args.filter)
+ except ValueError:
+ for_comm = args.filter
+
+ session = perf.session(perf.data(args.input), sample=process_event)
+ session.process_events()
+ print_syscall_totals()
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 27/59] perf syscall-counts: Port syscall-counts to use python module
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Rewrite tools/perf/scripts/python/syscall-counts.py to use the python
module and various style changes. By avoiding the overheads in the
`perf script` execution the performance improves by more than 4x as
shown in the following (with PYTHON_PATH and PERF_EXEC_PATH set as
necessary):
```
$ perf record -e raw_syscalls:sys_enter -a sleep 1
...
$ time perf script tools/perf/scripts/python/syscall-counts.py perf
Install the python-audit package to get syscall names.
For example:
# apt-get install python3-audit (Ubuntu)
# yum install python3-audit (Fedora)
etc.
Press control+C to stop and show the summary
Warning:
1 out of order events recorded.
syscall events for perf:
event count
-------------------------------------- ------------
1 538989
16 32
203 17
3 2
257 1
204 1
15 1
7 1
0 1
real 0m3.887s
user 0m3.578s
sys 0m0.308s
$ time python3 tools/perf/python/syscall-counts.py perf
Warning:
1 out of order events recorded.
syscall events for perf:
event count
-------------------------------------- ------------
write 538989
ioctl 32
sched_setaffinity 17
close 2
openat 1
sched_getaffinity 1
rt_sigreturn 1
poll 1
read 1
real 0m0.953s
user 0m0.905s
sys 0m0.048s
```
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2:
1. Fallback for Unknown Syscalls: If perf.syscall_name() returns None
for an unmapped ID, the script now falls back to using the numeric
ID string. This prevents a TypeError when applying string alignment
formatting.
2. Fallback for Syscall Number Attribute: The script now checks for
__syscall_nr first, and if not present (as on some older kernels),
falls back to checking for nr .
3. Robust Process Resolution: Added a try-except block around
session.process(sample.pid).comm() . If the process lookup fails
(returning NULL/None), it falls back to "unknown" instead of
letting a TypeError crash the script.
4. Support for Custom Input Files: Added a -i / --input command-line
argument to allow processing arbitrarily named trace files,
removing the hardcoded "perf.data" restriction.
---
tools/perf/python/syscall-counts.py | 72 +++++++++++++++++++++++++++++
1 file changed, 72 insertions(+)
create mode 100755 tools/perf/python/syscall-counts.py
diff --git a/tools/perf/python/syscall-counts.py b/tools/perf/python/syscall-counts.py
new file mode 100755
index 000000000000..ef2bd8c7b24c
--- /dev/null
+++ b/tools/perf/python/syscall-counts.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""
+Displays system-wide system call totals, broken down by syscall.
+
+If a [comm] arg is specified, only syscalls called by [comm] are displayed.
+"""
+
+import argparse
+from collections import defaultdict
+from typing import DefaultDict
+import perf
+
+syscalls: DefaultDict[int, int] = defaultdict(int)
+for_comm = None
+session = None
+
+
+def print_syscall_totals():
+ """Print aggregated statistics."""
+ if for_comm is not None:
+ print(f"\nsyscall events for {for_comm}:\n")
+ else:
+ print("\nsyscall events:\n")
+
+ print(f"{'event':<40} {'count':>10}")
+ print("---------------------------------------- -----------")
+
+ for sc_id, val in sorted(syscalls.items(),
+ key=lambda kv: (kv[1], kv[0]), reverse=True):
+ name = perf.syscall_name(sc_id) or str(sc_id)
+ print(f"{name:<40} {val:>10}")
+
+
+def process_event(sample):
+ """Process a single sample event."""
+ event_name = str(sample.evsel)
+ if event_name == "evsel(raw_syscalls:sys_enter)":
+ sc_id = getattr(sample, "id", -1)
+ elif event_name.startswith("evsel(syscalls:sys_enter_"):
+ sc_id = getattr(sample, "__syscall_nr", None)
+ if sc_id is None:
+ sc_id = getattr(sample, "nr", -1)
+ else:
+ return
+
+ if sc_id == -1:
+ return
+
+ comm = "unknown"
+ try:
+ if session:
+ proc = session.find_thread(sample.sample_pid)
+ if proc:
+ comm = proc.comm()
+ except (TypeError, AttributeError):
+ pass
+
+ if for_comm and comm != for_comm:
+ return
+ syscalls[sc_id] += 1
+
+
+if __name__ == "__main__":
+ ap = argparse.ArgumentParser()
+ ap.add_argument("comm", nargs="?", help="Only report syscalls for comm")
+ ap.add_argument("-i", "--input", default="perf.data", help="Input file name")
+ args = ap.parse_args()
+ for_comm = args.comm
+ session = perf.session(perf.data(args.input), sample=process_event)
+ session.process_events()
+ print_syscall_totals()
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 25/59] perf stat-cpi: Port stat-cpi to use python module
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Port stat-cpi.py from the legacy framework to a standalone script.
Support both file processing mode (using perf.session) and live mode
(reading counters directly via perf.parse_events and evsel.read). Use
argparse for command line options handling. Calculate and display CPI
(Cycles Per Instruction) per interval per CPU/thread.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2:
1. Accurate CPI Calculation (Multiplexing Support):
- Before: The get() method returned the raw counter value directly,
ignoring whether the counter ran for the full interval.
- After: The get() method now scales the raw value by the ratio of
enabled time to running time ( val * (ena / float(run)) ) when run
> 0 . This handles cases where PMU counters are overcommitted and
multiplexed.
2. Per-Interval CPI in File Mode:
- Before: store() saved absolute counter values as read from
PERF_RECORD_STAT . Since these are cumulative from the start of
the trace, and data.clear() was called every round, the script
computed cumulative CPI rather than per-interval CPI.
- After: store() now computes the delta between the current absolute
value and the value from the previous interval. It saves this
delta in self.data and retains the absolute value in self.
prev_data for the next delta computation.
3. Prevention of Dummy Output (Cartesian Product Fix):
- Before: self.cpus and self.threads lists accumulated all unique
CPUs and threads seen independently. The nested loops in
print_interval() then created a Cartesian product of all seen CPUs
and threads, querying data for combinations that might never have
occurred.
- After: Replaced lists with a self.recorded_pairs set that stores
(cpu, thread) tuples only when a sample actually records them. The
output loop now iterates strictly over these verified pairs.
---
tools/perf/python/stat-cpi.py | 151 ++++++++++++++++++++++++++++++++++
1 file changed, 151 insertions(+)
create mode 100755 tools/perf/python/stat-cpi.py
diff --git a/tools/perf/python/stat-cpi.py b/tools/perf/python/stat-cpi.py
new file mode 100755
index 000000000000..4b1f1f69c94a
--- /dev/null
+++ b/tools/perf/python/stat-cpi.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""Calculate CPI from perf stat data or live."""
+
+import argparse
+import sys
+import time
+from typing import Any, Optional
+import perf
+
+class StatCpiAnalyzer:
+ """Accumulates cycles and instructions and calculates CPI."""
+
+ def __init__(self, args: argparse.Namespace) -> None:
+ self.args = args
+ self.data: dict[str, tuple[int, int, int]] = {}
+ self.prev_data: dict[str, tuple[int, int, int]] = {}
+ self.recorded_pairs: set[tuple[int, int]] = set()
+
+ def get_key(self, event: str, cpu: int, thread: int) -> str:
+ """Get key for data dictionary."""
+ return f"{event}-{cpu}-{thread}"
+
+ def store_key(self, cpu: int, thread: int) -> None:
+ """Store CPU and thread IDs."""
+ self.recorded_pairs.add((cpu, thread))
+
+ def store(self, event: str, cpu: int, thread: int, counts: tuple[int, int, int]) -> None:
+ """Store counter values, computing difference from previous absolute values."""
+ self.store_key(cpu, thread)
+ key = self.get_key(event, cpu, thread)
+
+ val, ena, run = counts
+ if key in self.prev_data:
+ prev_val, prev_ena, prev_run = self.prev_data[key]
+ cur_val = val - prev_val
+ cur_ena = ena - prev_ena
+ cur_run = run - prev_run
+ else:
+ cur_val = val
+ cur_ena = ena
+ cur_run = run
+
+ self.data[key] = (cur_val, cur_ena, cur_run)
+ self.prev_data[key] = counts # Store absolute value for next time
+
+ def get(self, event: str, cpu: int, thread: int) -> float:
+ """Get scaled counter value."""
+ key = self.get_key(event, cpu, thread)
+ if key not in self.data:
+ return 0.0
+ val, ena, run = self.data[key]
+ if run > 0:
+ return val * (ena / float(run))
+ return float(val)
+
+ def process_stat_event(self, event: Any, name: Optional[str] = None) -> None:
+ """Process PERF_RECORD_STAT and PERF_RECORD_STAT_ROUND events."""
+ if event.type == perf.RECORD_STAT:
+ if name:
+ if "cycles" in name:
+ event_name = "cycles"
+ elif "instructions" in name:
+ event_name = "instructions"
+ else:
+ return
+ self.store(event_name, event.cpu, event.thread, (event.val, event.ena, event.run))
+ elif event.type == perf.RECORD_STAT_ROUND:
+ timestamp = getattr(event, "time", 0)
+ self.print_interval(timestamp)
+ self.data.clear()
+ self.recorded_pairs.clear()
+
+ def print_interval(self, timestamp: int) -> None:
+ """Print CPI for the current interval."""
+ for cpu, thread in sorted(self.recorded_pairs):
+ cyc = self.get("cycles", cpu, thread)
+ ins = self.get("instructions", cpu, thread)
+ cpi = 0.0
+ if ins != 0:
+ cpi = cyc / float(ins)
+ t_sec = timestamp / 1000000000.0
+ print(f"{t_sec:15f}: cpu {cpu}, thread {thread} -> cpi {cpi:f} ({cyc:.0f}/{ins:.0f})")
+
+ def read_counters(self, evlist: Any) -> None:
+ """Read counters live."""
+ for evsel in evlist:
+ name = str(evsel)
+ if "cycles" in name:
+ event_name = "cycles"
+ elif "instructions" in name:
+ event_name = "instructions"
+ else:
+ continue
+
+ for cpu in evsel.cpus():
+ for thread in evsel.threads():
+ try:
+ counts = evsel.read(cpu, thread)
+ self.store(event_name, cpu, thread,
+ (counts.val, counts.ena, counts.run))
+ except OSError:
+ pass
+
+ def run_file(self) -> None:
+ """Process events from file."""
+ session = perf.session(perf.data(self.args.input), stat=self.process_stat_event)
+ session.process_events()
+
+ def run_live(self) -> None:
+ """Read counters live."""
+ evlist = perf.parse_events("cycles,instructions")
+ if not evlist:
+ print("Failed to parse events", file=sys.stderr)
+ return
+ try:
+ evlist.open()
+ except OSError as e:
+ print(f"Failed to open events: {e}", file=sys.stderr)
+ return
+
+ print("Live mode started. Press Ctrl+C to stop.")
+ try:
+ while True:
+ time.sleep(self.args.interval)
+ timestamp = time.time_ns()
+ self.read_counters(evlist)
+ self.print_interval(timestamp)
+ self.data.clear()
+ self.recorded_pairs.clear()
+ except KeyboardInterrupt:
+ print("\nStopped.")
+ finally:
+ evlist.close()
+
+def main() -> None:
+ """Main function."""
+ ap = argparse.ArgumentParser(description="Calculate CPI from perf stat data or live")
+ ap.add_argument("-i", "--input", help="Input file name (enables file mode)")
+ ap.add_argument("-I", "--interval", type=float, default=1.0,
+ help="Interval in seconds for live mode")
+ args = ap.parse_args()
+
+ analyzer = StatCpiAnalyzer(args)
+ if args.input:
+ analyzer.run_file()
+ else:
+ analyzer.run_live()
+
+if __name__ == "__main__":
+ main()
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 29/59] perf futex-contention: Port futex-contention to use python module
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Rewrite tools/perf/scripts/python/futex-contention.py to use the
python module and various style changes. By avoiding the overheads in
the `perf script` execution the performance improves by more than 3.2x
as shown in the following (with PYTHON_PATH and PERF_EXEC_PATH set as
necessary):
```
$ perf record -e syscalls:sys_*_futex -a sleep 1
...
$ time perf script tools/perf/scripts/python/futex-contention.py
Install the python-audit package to get syscall names.
For example:
# apt-get install python3-audit (Ubuntu)
# yum install python3-audit (Fedora)
etc.
Press control+C to stop and show the summary
aaa/4[2435653] lock 7f76b380c878 contended 1 times, 1099 avg ns [max: 1099 ns, min 1099 ns]
...
real 0m1.007s
user 0m0.935s
sys 0m0.072s
$ time python3 tools/perf/python/futex-contention.py
...
real 0m0.314s
user 0m0.259s
sys 0m0.056s
```
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2:
1. Fixed Module Import Failure: Corrected the type annotations from
[int, int] to Tuple[int, int] . The previous code would raise a
TypeError at module import time because lists cannot be used as
types in dictionary annotations.
2. Prevented Out-Of-Memory Crashes: Replaced the approach of storing
every single duration in a list with a LockStats class that
maintains running aggregates (count, total time, min, max). This
ensures O(1) memory usage per lock/thread pair rather than
unbounded memory growth.
3. Support for Custom Input Files: Added a -i / --input command-line
argument to support processing arbitrarily named trace files,
removing the hardcoded "perf.data" restriction.
4. Robust Process Lookup: Added a check to ensure session is
initialized before calling session. process() , preventing
potential NoneType attribute errors if events are processed during
initialization.
---
tools/perf/python/futex-contention.py | 87 +++++++++++++++++++++++++++
1 file changed, 87 insertions(+)
create mode 100755 tools/perf/python/futex-contention.py
diff --git a/tools/perf/python/futex-contention.py b/tools/perf/python/futex-contention.py
new file mode 100755
index 000000000000..1fc87ec0e6e5
--- /dev/null
+++ b/tools/perf/python/futex-contention.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""Measures futex contention."""
+
+import argparse
+from collections import defaultdict
+from typing import Dict, Tuple
+import perf
+
+class LockStats:
+ """Aggregate lock contention information."""
+ def __init__(self) -> None:
+ self.count = 0
+ self.total_time = 0
+ self.min_time = 0
+ self.max_time = 0
+
+ def add(self, duration: int) -> None:
+ """Add a new duration measurement."""
+ self.count += 1
+ self.total_time += duration
+ if self.count == 1:
+ self.min_time = duration
+ self.max_time = duration
+ else:
+ self.min_time = min(self.min_time, duration)
+ self.max_time = max(self.max_time, duration)
+
+ def avg(self) -> float:
+ """Return average duration."""
+ return self.total_time / self.count if self.count > 0 else 0.0
+
+process_names: Dict[int, str] = {}
+start_times: Dict[int, Tuple[int, int]] = {}
+session = None
+durations: Dict[Tuple[int, int], LockStats] = defaultdict(LockStats)
+
+FUTEX_WAIT = 0
+FUTEX_WAKE = 1
+FUTEX_PRIVATE_FLAG = 128
+FUTEX_CLOCK_REALTIME = 256
+FUTEX_CMD_MASK = ~(FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME)
+
+
+def process_event(sample: perf.sample_event) -> None:
+ """Process a single sample event."""
+ def handle_start(tid: int, uaddr: int, op: int, start_time: int) -> None:
+ if (op & FUTEX_CMD_MASK) != FUTEX_WAIT:
+ return
+ if tid not in process_names:
+ try:
+ if session:
+ process = session.find_thread(tid)
+ if process:
+ process_names[tid] = process.comm()
+ except (TypeError, AttributeError):
+ return
+ start_times[tid] = (uaddr, start_time)
+
+ def handle_end(tid: int, end_time: int) -> None:
+ if tid not in start_times:
+ return
+ (uaddr, start_time) = start_times[tid]
+ del start_times[tid]
+ durations[(tid, uaddr)].add(end_time - start_time)
+
+ event_name = str(sample.evsel)
+ if event_name == "evsel(syscalls:sys_enter_futex)":
+ uaddr = getattr(sample, "uaddr", 0)
+ op = getattr(sample, "op", 0)
+ handle_start(sample.sample_tid, uaddr, op, sample.sample_time)
+ elif event_name == "evsel(syscalls:sys_exit_futex)":
+ handle_end(sample.sample_tid, sample.sample_time)
+
+
+if __name__ == "__main__":
+ ap = argparse.ArgumentParser(description="Measure futex contention")
+ ap.add_argument("-i", "--input", default="perf.data", help="Input file name")
+ args = ap.parse_args()
+
+ session = perf.session(perf.data(args.input), sample=process_event)
+ session.process_events()
+
+ for ((t, u), stats) in sorted(durations.items()):
+ avg_ns = stats.avg()
+ print(f"{process_names.get(t, 'unknown')}[{t}] lock {u:x} contended {stats.count} times, "
+ f"{avg_ns:.0f} avg ns [max: {stats.max_time} ns, min {stats.min_time} ns]")
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 23/59] perf python: Add LiveSession helper
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Add LiveSession class in tools/perf/python/perf_live.py to support
live event collection using perf.evlist and perf.parse_events,
avoiding the need to fork a separate perf record process.
Signed-off-by: Ian Rogers <irogers@google.com>
Assisted-by: Gemini:gemini-3.1-pro-preview
---
v2:
1. Fixed File Descriptor Leak: I moved self.evlist.mmap() inside the
try block so that if it raises an exception, the finally block will
still be executed and call self.evlist.close() , preventing file
descriptor leaks.
2. Handled InterruptedError in poll() : I wrapped the poll() call in a
try-except block to catch InterruptedError and continue the
loop. This prevents the live session from crashing on non-fatal
signals like SIGWINCH .
3. Added evlist.config() : I added a call to self.evlist.config() in
the constructor after parse_events() . This applies the default
record options to the events, enabling sampling and setting up
PERF_SAMPLE_* fields so that the kernel will actually generate
PERF_RECORD_SAMPLE events.
4. Enable the evlist and be robust to exceptions from reading unsupported
events like mmap2.
---
tools/perf/python/perf_live.py | 48 ++++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
create mode 100755 tools/perf/python/perf_live.py
diff --git a/tools/perf/python/perf_live.py b/tools/perf/python/perf_live.py
new file mode 100755
index 000000000000..d1dcbab1150b
--- /dev/null
+++ b/tools/perf/python/perf_live.py
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: GPL-2.0
+"""
+Live event session helper using perf.evlist.
+
+This module provides a LiveSession class that allows running a callback
+for each event collected live from the system, similar to perf.session
+but without requiring a perf.data file.
+"""
+
+import perf
+
+
+class LiveSession:
+ """Represents a live event collection session."""
+
+ def __init__(self, event_string: str, sample_callback):
+ self.event_string = event_string
+ self.sample_callback = sample_callback
+ # Create a cpu map for all online CPUs
+ self.cpus = perf.cpu_map()
+ # Parse events and set maps
+ self.evlist = perf.parse_events(self.event_string, self.cpus)
+ self.evlist.config()
+
+ def run(self):
+ """Run the live session."""
+ self.evlist.open()
+ try:
+ self.evlist.mmap()
+ self.evlist.enable()
+
+ while True:
+ # Poll for events with 100ms timeout
+ try:
+ self.evlist.poll(100)
+ except InterruptedError:
+ continue
+ for cpu in self.cpus:
+ try:
+ event = self.evlist.read_on_cpu(cpu)
+ if event and event.type == perf.RECORD_SAMPLE:
+ self.sample_callback(event)
+ except Exception:
+ pass
+ except KeyboardInterrupt:
+ pass
+ finally:
+ self.evlist.close()
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 22/59] perf python: Add perf.pyi stubs file
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Add Python type stubs for the perf module to improve IDE support and
static analysis. Includes docstrings for classes, methods, and
constants derived from C source and JSON definitions.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2:
1. Added Missing Module Functions: Added parse_metrics and
pmus. Renamed metrics to parse_metrics to match python.c .
2. Added Constructors: Added __init__ methods for data , evsel , and
evlist with their appropriate arguments.
3. Removed sample_comm : Removed it from sample_event since it is not
exported in python.c .
4. Keyword Handling in branch_entry : I used from_ip and to_ip in the
stubs to match the rename I did in python.c (in turn 145) to avoid
the Python from keyword conflict.
5. Added Missing Event Classes: Added mmap_event , lost_event ,
comm_event , task_event , throttle_event , read_event , and
switch_event .
6. Added Missing evlist Methods: Added get_pollfd and add .
7. Updated Return Types: Changed process_events to return int .
v6:
- Updated `perf.pyi` to use `find_thread` and `elf_machine`.
---
tools/perf/python/perf.pyi | 581 +++++++++++++++++++++++++++++++++++++
1 file changed, 581 insertions(+)
create mode 100644 tools/perf/python/perf.pyi
diff --git a/tools/perf/python/perf.pyi b/tools/perf/python/perf.pyi
new file mode 100644
index 000000000000..91e19704e595
--- /dev/null
+++ b/tools/perf/python/perf.pyi
@@ -0,0 +1,581 @@
+"""Type stubs for the perf Python module."""
+from typing import Callable, Dict, List, Optional, Any, Iterator
+
+def config_get(name: str) -> Optional[str]:
+ """Get a configuration value from perf config.
+
+ Args:
+ name: The configuration variable name (e.g., 'colors.top').
+
+ Returns:
+ The configuration value as a string, or None if not set.
+ """
+ ...
+
+def metrics() -> List[Dict[str, str]]:
+ """Get a list of available metrics.
+
+ Returns:
+ A list of dictionaries, each describing a metric.
+ """
+ ...
+
+def syscall_name(sc_id: int, *, elf_machine: Optional[int] = None) -> str:
+ """Convert a syscall number to its name.
+
+ Args:
+ sc_id: The syscall number.
+ elf_machine: Optional ELF machine type.
+
+ Returns:
+ The name of the syscall.
+ """
+ ...
+
+def syscall_id(name: str, *, elf_machine: Optional[int] = None) -> int:
+ """Convert a syscall name to its number.
+
+ Args:
+ name: The syscall name.
+ elf_machine: Optional ELF machine type.
+
+ Returns:
+ The number of the syscall.
+ """
+ ...
+
+def parse_events(
+ event_string: str,
+ cpus: Optional[cpu_map] = None,
+ threads: Optional[Any] = None
+) -> 'evlist':
+ """Parse an event string and return an evlist.
+
+ Args:
+ event_string: The event string (e.g., 'cycles,instructions').
+ cpus: Optional CPU map to bind events to.
+ threads: Optional thread map to bind events to.
+
+ Returns:
+ An evlist containing the parsed events.
+ """
+ ...
+
+def parse_metrics(metrics_string: str) -> 'evlist':
+ """Parse a string of metrics or metric groups and return an evlist."""
+ ...
+
+def pmus() -> Iterator[Any]:
+ """Returns a sequence of pmus."""
+ ...
+
+class data:
+ """Represents a perf data file."""
+ def __init__(self, path: str = ..., fd: int = ...) -> None: ...
+
+class thread:
+ """Represents a thread in the system."""
+ def comm(self) -> str:
+ """Get the command name of the thread."""
+ ...
+
+class counts_values:
+ """Raw counter values."""
+ val: int
+ ena: int
+ run: int
+
+class thread_map:
+ """Map of threads being monitored."""
+ def __init__(self, pid: int = -1, tid: int = -1) -> None:
+ """Initialize a thread map.
+
+ Args:
+ pid: Process ID to monitor (-1 for all).
+ tid: Thread ID to monitor (-1 for all).
+ """
+ ...
+ def __len__(self) -> int: ...
+ def __getitem__(self, index: int) -> int: ...
+ def __iter__(self) -> Iterator[int]: ...
+
+class evsel:
+ """Event selector, represents a single event being monitored."""
+ def __init__(
+ self,
+ type: int = ...,
+ config: int = ...,
+ sample_freq: int = ...,
+ sample_period: int = ...,
+ sample_type: int = ...,
+ read_format: int = ...,
+ disabled: bool = ...,
+ inherit: bool = ...,
+ pinned: bool = ...,
+ exclusive: bool = ...,
+ exclude_user: bool = ...,
+ exclude_kernel: bool = ...,
+ exclude_hv: bool = ...,
+ exclude_idle: bool = ...,
+ mmap: bool = ...,
+ context_switch: bool = ...,
+ comm: bool = ...,
+ freq: bool = ...,
+ idx: int = ...,
+ ) -> None: ...
+ def __str__(self) -> str:
+ """Return string representation of the event."""
+ ...
+ def open(self) -> None:
+ """Open the event selector file descriptor table."""
+ ...
+ def read(self, cpu: int, thread: int) -> counts_values:
+ """Read counter values for a specific CPU and thread."""
+ ...
+ ids: List[int]
+ def cpus(self) -> cpu_map:
+ """Get CPU map for this event."""
+ ...
+ def threads(self) -> thread_map:
+ """Get thread map for this event."""
+ ...
+
+
+class sample_event:
+ """Represents a sample event from perf."""
+ evsel: evsel
+ sample_cpu: int
+ sample_time: int
+ sample_pid: int
+ type: int
+ brstack: Optional['branch_stack']
+ callchain: Optional['callchain']
+ def __getattr__(self, name: str) -> Any: ...
+
+class mmap_event:
+ """Represents a mmap event from perf."""
+ type: int
+ pid: int
+ tid: int
+ addr: int
+ len: int
+ pgoff: int
+ filename: str
+
+class lost_event:
+ """Represents a lost events record."""
+ type: int
+ id: int
+ lost: int
+
+class comm_event:
+ """Represents a COMM record."""
+ type: int
+ pid: int
+ tid: int
+ comm: str
+
+class task_event:
+ """Represents an EXIT or FORK record."""
+ type: int
+ pid: int
+ ppid: int
+ tid: int
+ ptid: int
+ time: int
+
+class throttle_event:
+ """Represents a THROTTLE or UNTHROTTLE record."""
+ type: int
+ time: int
+ id: int
+ stream_id: int
+
+class read_event:
+ """Represents a READ record."""
+ type: int
+ pid: int
+ tid: int
+ value: int
+
+class switch_event:
+ """Represents a SWITCH or SWITCH_CPU_WIDE record."""
+ type: int
+
+class branch_entry:
+ """Represents a branch entry in the branch stack.
+
+ Attributes:
+ from_ip: Source address of the branch (corresponds to 'from' keyword in C).
+ to_ip: Destination address of the branch.
+ mispred: True if the branch was mispredicted.
+ predicted: True if the branch was predicted.
+ in_tx: True if the branch was in a transaction.
+ abort: True if the branch was an abort.
+ cycles: Number of cycles since the last branch.
+ type: Type of branch.
+ """
+ from_ip: int
+ to_ip: int
+ mispred: bool
+ predicted: bool
+ in_tx: bool
+ abort: bool
+ cycles: int
+ type: int
+
+class branch_stack:
+ """Iterator over branch entries in the branch stack."""
+ def __iter__(self) -> Iterator[branch_entry]: ...
+ def __next__(self) -> branch_entry: ...
+
+class callchain_node:
+ """Represents a frame in the callchain."""
+ ip: int
+ sym: Optional[Any]
+ map: Optional[Any]
+
+class callchain:
+ """Iterator over callchain frames."""
+ def __iter__(self) -> Iterator[callchain_node]: ...
+ def __next__(self) -> callchain_node: ...
+
+class stat_event:
+ """Represents a stat event from perf."""
+ type: int
+ id: int
+ cpu: int
+ thread: int
+ val: int
+ ena: int
+ run: int
+
+class stat_round_event:
+ """Represents a stat round event from perf."""
+ type: int
+ time: int
+
+class cpu_map:
+ """Map of CPUs being monitored."""
+ def __init__(self, cpustr: Optional[str] = None) -> None: ...
+ def __len__(self) -> int: ...
+ def __getitem__(self, index: int) -> int: ...
+ def __iter__(self) -> Iterator[int]: ...
+
+
+class evlist:
+ def __init__(self, cpus: cpu_map, threads: thread_map) -> None: ...
+ def open(self) -> None:
+ """Open the events in the list."""
+ ...
+ def close(self) -> None:
+ """Close the events in the list."""
+ ...
+ def mmap(self) -> None:
+ """Memory map the event buffers."""
+ ...
+ def poll(self, timeout: int) -> int:
+ """Poll for events.
+
+ Args:
+ timeout: Timeout in milliseconds.
+
+ Returns:
+ Number of events ready.
+ """
+ ...
+ def read_on_cpu(self, cpu: int) -> Optional[sample_event]:
+ """Read a sample event from a specific CPU.
+
+ Args:
+ cpu: The CPU number.
+
+ Returns:
+ A sample_event if available, or None.
+ """
+ ...
+ def all_cpus(self) -> cpu_map:
+ """Get a cpu_map of all CPUs in the system."""
+ ...
+ def metrics(self) -> List[str]:
+ """Get a list of metric names within the evlist."""
+ ...
+ def compute_metric(self, metric: str, cpu: int, thread: int) -> float:
+ """Compute metric for given name, cpu and thread.
+
+ Args:
+ metric: The metric name.
+ cpu: The CPU number.
+ thread: The thread ID.
+
+ Returns:
+ The computed metric value.
+ """
+ ...
+ def config(self) -> None:
+ """Configure the events in the list."""
+ ...
+ def disable(self) -> None:
+ """Disable all events in the list."""
+ ...
+ def enable(self) -> None:
+ """Enable all events in the list."""
+ ...
+ def get_pollfd(self) -> List[int]:
+ """Get a list of file descriptors for polling."""
+ ...
+ def add(self, evsel: evsel) -> int:
+ """Add an event to the list."""
+ ...
+ def __iter__(self) -> Iterator[evsel]:
+ """Iterate over the events (evsel) in the list."""
+ ...
+
+
+class session:
+ def __init__(
+ self,
+ data: data,
+ sample: Optional[Callable[[sample_event], None]] = None,
+ stat: Optional[Callable[[Any, Optional[str]], None]] = None
+ ) -> None:
+ """Initialize a perf session.
+
+ Args:
+ data: The perf data file to read.
+ sample: Callback for sample events.
+ stat: Callback for stat events.
+ """
+ ...
+ def process_events(self) -> int:
+ """Process all events in the session."""
+ ...
+ def find_thread(self, pid: int) -> thread:
+ """Returns the thread associated with a pid."""
+ ...
+
+# Event Types
+TYPE_HARDWARE: int
+"""Hardware event."""
+
+TYPE_SOFTWARE: int
+"""Software event."""
+
+TYPE_TRACEPOINT: int
+"""Tracepoint event."""
+
+TYPE_HW_CACHE: int
+"""Hardware cache event."""
+
+TYPE_RAW: int
+"""Raw hardware event."""
+
+TYPE_BREAKPOINT: int
+"""Breakpoint event."""
+
+
+# Hardware Counters
+COUNT_HW_CPU_CYCLES: int
+"""Total cycles. Be wary of what happens during CPU frequency scaling."""
+
+COUNT_HW_INSTRUCTIONS: int
+"""Retired instructions. Be careful, these can be affected by various issues,
+most notably hardware interrupt counts."""
+
+COUNT_HW_CACHE_REFERENCES: int
+"""Cache accesses. Usually this indicates Last Level Cache accesses but this
+may vary depending on your CPU."""
+
+COUNT_HW_CACHE_MISSES: int
+"""Cache misses. Usually this indicates Last Level Cache misses."""
+
+COUNT_HW_BRANCH_INSTRUCTIONS: int
+"""Retired branch instructions."""
+
+COUNT_HW_BRANCH_MISSES: int
+"""Mispredicted branch instructions."""
+
+COUNT_HW_BUS_CYCLES: int
+"""Bus cycles, which can be different from total cycles."""
+
+COUNT_HW_STALLED_CYCLES_FRONTEND: int
+"""Stalled cycles during issue [This event is an alias of idle-cycles-frontend]."""
+
+COUNT_HW_STALLED_CYCLES_BACKEND: int
+"""Stalled cycles during retirement [This event is an alias of idle-cycles-backend]."""
+
+COUNT_HW_REF_CPU_CYCLES: int
+"""Total cycles; not affected by CPU frequency scaling."""
+
+
+# Cache Counters
+COUNT_HW_CACHE_L1D: int
+"""Level 1 data cache."""
+
+COUNT_HW_CACHE_L1I: int
+"""Level 1 instruction cache."""
+
+COUNT_HW_CACHE_LL: int
+"""Last Level Cache."""
+
+COUNT_HW_CACHE_DTLB: int
+"""Data TLB."""
+
+COUNT_HW_CACHE_ITLB: int
+"""Instruction TLB."""
+
+COUNT_HW_CACHE_BPU: int
+"""Branch Processing Unit."""
+
+COUNT_HW_CACHE_OP_READ: int
+"""Read accesses."""
+
+COUNT_HW_CACHE_OP_WRITE: int
+"""Write accesses."""
+
+COUNT_HW_CACHE_OP_PREFETCH: int
+"""Prefetch accesses."""
+
+COUNT_HW_CACHE_RESULT_ACCESS: int
+"""Accesses."""
+
+COUNT_HW_CACHE_RESULT_MISS: int
+"""Misses."""
+
+
+# Software Counters
+COUNT_SW_CPU_CLOCK: int
+"""CPU clock event."""
+
+COUNT_SW_TASK_CLOCK: int
+"""Task clock event."""
+
+COUNT_SW_PAGE_FAULTS: int
+"""Page faults."""
+
+COUNT_SW_CONTEXT_SWITCHES: int
+"""Context switches."""
+
+COUNT_SW_CPU_MIGRATIONS: int
+"""CPU migrations."""
+
+COUNT_SW_PAGE_FAULTS_MIN: int
+"""Minor page faults."""
+
+COUNT_SW_PAGE_FAULTS_MAJ: int
+"""Major page faults."""
+
+COUNT_SW_ALIGNMENT_FAULTS: int
+"""Alignment faults."""
+
+COUNT_SW_EMULATION_FAULTS: int
+"""Emulation faults."""
+
+COUNT_SW_DUMMY: int
+"""Dummy event."""
+
+
+# Sample Fields
+SAMPLE_IP: int
+"""Instruction pointer."""
+
+SAMPLE_TID: int
+"""Process and thread ID."""
+
+SAMPLE_TIME: int
+"""Timestamp."""
+
+SAMPLE_ADDR: int
+"""Sampled address."""
+
+SAMPLE_READ: int
+"""Read barcode."""
+
+SAMPLE_CALLCHAIN: int
+"""Call chain."""
+
+SAMPLE_ID: int
+"""Unique ID."""
+
+SAMPLE_CPU: int
+"""CPU number."""
+
+SAMPLE_PERIOD: int
+"""Sample period."""
+
+SAMPLE_STREAM_ID: int
+"""Stream ID."""
+
+SAMPLE_RAW: int
+"""Raw sample."""
+
+
+# Format Fields
+FORMAT_TOTAL_TIME_ENABLED: int
+"""Total time enabled."""
+
+FORMAT_TOTAL_TIME_RUNNING: int
+"""Total time running."""
+
+FORMAT_ID: int
+"""Event ID."""
+
+FORMAT_GROUP: int
+"""Event group."""
+
+
+# Record Types
+RECORD_MMAP: int
+"""MMAP record. Contains header, pid, tid, addr, len, pgoff, filename, and sample_id."""
+
+RECORD_LOST: int
+"""Lost events record. Contains header, id, lost count, and sample_id."""
+
+RECORD_COMM: int
+"""COMM record. Contains header, pid, tid, comm, and sample_id."""
+
+RECORD_EXIT: int
+"""EXIT record. Contains header, pid, ppid, tid, ptid, time, and sample_id."""
+
+RECORD_THROTTLE: int
+"""THROTTLE record. Contains header, time, id, stream_id, and sample_id."""
+
+RECORD_UNTHROTTLE: int
+"""UNTHROTTLE record. Contains header, time, id, stream_id, and sample_id."""
+
+RECORD_FORK: int
+"""FORK record. Contains header, pid, ppid, tid, ptid, time, and sample_id."""
+
+RECORD_READ: int
+"""READ record. Contains header, and read values."""
+
+RECORD_SAMPLE: int
+"""SAMPLE record. Contains header, and sample data requested by sample_type."""
+
+RECORD_MMAP2: int
+"""MMAP2 record. Contains header, pid, tid, addr, len, pgoff, maj, min, ino,
+ino_generation, prot, flags, filename, and sample_id."""
+
+RECORD_AUX: int
+"""AUX record. Contains header, aux_offset, aux_size, flags, and sample_id."""
+
+RECORD_ITRACE_START: int
+"""ITRACE_START record. Contains header, pid, tid, and sample_id."""
+
+RECORD_LOST_SAMPLES: int
+"""LOST_SAMPLES record. Contains header, lost count, and sample_id."""
+
+RECORD_SWITCH: int
+"""SWITCH record. Contains header, and sample_id."""
+
+RECORD_SWITCH_CPU_WIDE: int
+"""SWITCH_CPU_WIDE record. Contains header, and sample_id."""
+
+RECORD_STAT: int
+"""STAT record."""
+
+RECORD_STAT_ROUND: int
+"""STAT_ROUND record."""
+
+RECORD_MISC_SWITCH_OUT: int
+"""MISC_SWITCH_OUT record."""
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 26/59] perf mem-phys-addr: Port mem-phys-addr to use python module
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Give an example of using the perf python session API to load a
perf.data file and perform the behavior of
tools/perf/scripts/python/mem-phys-addr.py.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2: Added command line '-i' option and cleaned up pylint issues.
---
tools/perf/python/mem-phys-addr.py | 117 +++++++++++++++++++++++++++++
1 file changed, 117 insertions(+)
create mode 100755 tools/perf/python/mem-phys-addr.py
diff --git a/tools/perf/python/mem-phys-addr.py b/tools/perf/python/mem-phys-addr.py
new file mode 100755
index 000000000000..ba874d7a2011
--- /dev/null
+++ b/tools/perf/python/mem-phys-addr.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+"""mem-phys-addr.py: Resolve physical address samples"""
+import argparse
+import bisect
+import collections
+from dataclasses import dataclass
+import re
+from typing import (Dict, Optional)
+
+import perf
+
+@dataclass(frozen=True)
+class IomemEntry:
+ """Read from a line in /proc/iomem"""
+ begin: int
+ end: int
+ indent: int
+ label: str
+
+# Physical memory layout from /proc/iomem. Key is the indent and then
+# a list of ranges.
+iomem: Dict[int, list[IomemEntry]] = collections.defaultdict(list)
+# Child nodes from the iomem parent.
+children: Dict[IomemEntry, set[IomemEntry]] = collections.defaultdict(set)
+# Maximum indent seen before an entry in the iomem file.
+max_indent: int = 0
+# Count for each range of memory.
+load_mem_type_cnt: Dict[IomemEntry, int] = collections.Counter()
+# Perf event name set from the first sample in the data.
+event_name: Optional[str] = None
+
+def parse_iomem(iomem_path: str):
+ """Populate iomem from iomem file"""
+ global max_indent
+ with open(iomem_path, 'r', encoding='ascii') as f:
+ for line in f:
+ indent = 0
+ while line[indent] == ' ':
+ indent += 1
+ max_indent = max(max_indent, indent)
+ m = re.split('-|:', line, maxsplit=2)
+ begin = int(m[0], 16)
+ end = int(m[1], 16)
+ label = m[2].strip()
+ entry = IomemEntry(begin, end, indent, label)
+ # Before adding entry, search for a parent node using its begin.
+ if indent > 0:
+ parent = find_memory_type(begin)
+ assert parent, f"Given indent expected a parent for {label}"
+ children[parent].add(entry)
+ iomem[indent].append(entry)
+
+def find_memory_type(phys_addr) -> Optional[IomemEntry]:
+ """Search iomem for the range containing phys_addr with the maximum indent"""
+ for i in range(max_indent, -1, -1):
+ if i not in iomem:
+ continue
+ position = bisect.bisect_right(iomem[i], phys_addr,
+ key=lambda entry: entry.begin)
+ if position is None:
+ continue
+ iomem_entry = iomem[i][position-1]
+ if iomem_entry.begin <= phys_addr <= iomem_entry.end:
+ return iomem_entry
+ print(f"Didn't find {phys_addr}")
+ return None
+
+def print_memory_type():
+ """Print the resolved memory types and their counts."""
+ print(f"Event: {event_name}")
+ print(f"{'Memory type':<40} {'count':>10} {'percentage':>10}")
+ print(f"{'-' * 40:<40} {'-' * 10:>10} {'-' * 10:>10}")
+ total = sum(load_mem_type_cnt.values())
+ # Add count from children into the parent.
+ for i in range(max_indent, -1, -1):
+ if i not in iomem:
+ continue
+ for entry in iomem[i]:
+ for child in children[entry]:
+ if load_mem_type_cnt[child] > 0:
+ load_mem_type_cnt[entry] += load_mem_type_cnt[child]
+
+ def print_entries(entries):
+ """Print counts from parents down to their children"""
+ for entry in sorted(entries,
+ key = lambda entry: load_mem_type_cnt[entry],
+ reverse = True):
+ count = load_mem_type_cnt[entry]
+ if count > 0:
+ mem_type = ' ' * entry.indent + f"{entry.begin:x}-{entry.end:x} : {entry.label}"
+ percent = 100 * count / total
+ print(f"{mem_type:<40} {count:>10} {percent:>10.1f}")
+ print_entries(children[entry])
+
+ print_entries(iomem[0])
+
+if __name__ == "__main__":
+ ap = argparse.ArgumentParser(description="Resolve physical address samples")
+ ap.add_argument("-i", "--input", default="perf.data", help="Input file name")
+ ap.add_argument("--iomem", default="/proc/iomem", help="Path to iomem file")
+ args = ap.parse_args()
+
+ def process_event(sample):
+ """Process a single sample event."""
+ phys_addr = sample.sample_phys_addr
+ entry = find_memory_type(phys_addr)
+ if entry:
+ load_mem_type_cnt[entry] += 1
+
+ global event_name
+ if event_name is None:
+ event_name = str(sample.evsel)
+
+ parse_iomem(args.iomem)
+ perf.session(perf.data(args.input), sample=process_event).process_events()
+ print_memory_type()
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 24/59] perf python: Move exported-sql-viewer.py and parallel-perf.py to tools/perf/python/
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
These scripts are standalone and not using the `perf script` libpython
support. Move to tools/perf/python in an effort to deprecate the
tools/perf/scripts/python support.
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2:
1. Updated exported-sql-viewer.py : I updated the comments at the top
of the script to use the new path
tools/perf/python/exported-sql-viewer.py in the usage examples.
2. Fixed Test Path in script.sh : I updated the path in
tools/perf/tests/shell/script.sh to point to the new location of
parallel-perf.py at ../../python/parallel-perf.py .
v5:
1. Fix Test 105 Failure: Added a shebang line and marked the generated
`db_test.py` script as executable in `script.sh`, preventing
permission denied errors during standalone execution.
---
tools/perf/{scripts => }/python/exported-sql-viewer.py | 4 ++--
tools/perf/{scripts => }/python/parallel-perf.py | 0
tools/perf/tests/shell/script.sh | 4 +++-
3 files changed, 5 insertions(+), 3 deletions(-)
rename tools/perf/{scripts => }/python/exported-sql-viewer.py (99%)
rename tools/perf/{scripts => }/python/parallel-perf.py (100%)
diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/python/exported-sql-viewer.py
similarity index 99%
rename from tools/perf/scripts/python/exported-sql-viewer.py
rename to tools/perf/python/exported-sql-viewer.py
index e0b2e7268ef6..f3ac96ada1f5 100755
--- a/tools/perf/scripts/python/exported-sql-viewer.py
+++ b/tools/perf/python/exported-sql-viewer.py
@@ -10,12 +10,12 @@
# Following on from the example in the export scripts, a
# call-graph can be displayed for the pt_example database like this:
#
-# python tools/perf/scripts/python/exported-sql-viewer.py pt_example
+# python tools/perf/python/exported-sql-viewer.py pt_example
#
# Note that for PostgreSQL, this script supports connecting to remote databases
# by setting hostname, port, username, password, and dbname e.g.
#
-# python tools/perf/scripts/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
+# python tools/perf/python/exported-sql-viewer.py "hostname=myhost username=myuser password=mypassword dbname=pt_example"
#
# The result is a GUI window with a tree representing a context-sensitive
# call-graph. Expanding a couple of levels of the tree and adjusting column
diff --git a/tools/perf/scripts/python/parallel-perf.py b/tools/perf/python/parallel-perf.py
similarity index 100%
rename from tools/perf/scripts/python/parallel-perf.py
rename to tools/perf/python/parallel-perf.py
diff --git a/tools/perf/tests/shell/script.sh b/tools/perf/tests/shell/script.sh
index 7007f1cdf761..f983b80e77b7 100755
--- a/tools/perf/tests/shell/script.sh
+++ b/tools/perf/tests/shell/script.sh
@@ -43,6 +43,7 @@ test_db()
fi
cat << "_end_of_file_" > "${db_test}"
+#!/usr/bin/env python3
perf_db_export_mode = True
perf_db_export_calls = False
perf_db_export_callchains = True
@@ -53,6 +54,7 @@ def sample_table(*args):
def call_path_table(*args):
print(f'call_path_table({args}')
_end_of_file_
+ chmod +x "${db_test}"
case $(uname -m)
in s390x)
cmd_flags="--call-graph dwarf -e cpu-clock";;
@@ -76,7 +78,7 @@ test_parallel_perf()
err=2
return
fi
- pp=$(dirname "$0")/../../scripts/python/parallel-perf.py
+ pp=$(dirname "$0")/../../python/parallel-perf.py
if [ ! -f "${pp}" ] ; then
echo "SKIP: parallel-perf.py script not found "
err=2
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 17/59] perf python: Refactor and add accessors to sample event
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Add common evsel field for events and move sample specific fields to
only be present in sample events. Add accessors for sample events.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v5:
1. Fix Uninitialized Memory: Restore zero-initialization of `pevent->sample`
in `pyrf_event__new()` to prevent wild free crashes on error paths.
v6:
- Refactored `pyrf_event__new` to take `evsel` and `session`, and use
dynamic allocation based on event size. Updated callers.
---
tools/perf/util/python.c | 516 ++++++++++++++++++++++++++++++++-------
1 file changed, 434 insertions(+), 82 deletions(-)
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 861973144106..824cf58645e0 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -8,22 +8,29 @@
#include <internal/lib.h>
#include <perf/mmap.h>
+#include "addr_location.h"
+#include "build-id.h"
#include "callchain.h"
#include "comm.h"
#include "counts.h"
#include "data.h"
#include "debug.h"
+#include "dso.h"
#include "dwarf-regs.h"
#include "event.h"
#include "evlist.h"
#include "evsel.h"
#include "expr.h"
+#include "map.h"
#include "metricgroup.h"
#include "mmap.h"
#include "pmus.h"
#include "print_binary.h"
#include "record.h"
+#include "sample.h"
#include "session.h"
+#include "srccode.h"
+#include "srcline.h"
#include "strbuf.h"
#include "symbol.h"
#include "syscalltbl.h"
@@ -32,7 +39,6 @@
#include "tool.h"
#include "tp_pmu.h"
#include "trace-event.h"
-#include "util/sample.h"
#ifdef HAVE_LIBTRACEEVENT
#include <event-parse.h>
@@ -40,6 +46,8 @@
PyMODINIT_FUNC PyInit_perf(void);
+static PyObject *pyrf_evsel__from_evsel(struct evsel *evsel);
+
#define member_def(type, member, ptype, help) \
{ #member, ptype, \
offsetof(struct pyrf_event, event) + offsetof(struct type, member), \
@@ -52,21 +60,53 @@ PyMODINIT_FUNC PyInit_perf(void);
struct pyrf_event {
PyObject_HEAD
+ /** @sample: The parsed sample from the event. */
struct perf_sample sample;
- union perf_event event;
+ /** @al: The address location from machine__resolve, lazily computed. */
+ struct addr_location al;
+ /** @al_resolved: True when machine__resolve been called. */
+ bool al_resolved;
+ /** @event: The underlying perf_event that may be in a file or ring buffer. */
+ union perf_event event;
};
#define sample_members \
- sample_member_def(sample_ip, ip, T_ULONGLONG, "event ip"), \
sample_member_def(sample_pid, pid, T_INT, "event pid"), \
sample_member_def(sample_tid, tid, T_INT, "event tid"), \
sample_member_def(sample_time, time, T_ULONGLONG, "event timestamp"), \
- sample_member_def(sample_addr, addr, T_ULONGLONG, "event addr"), \
sample_member_def(sample_id, id, T_ULONGLONG, "event id"), \
sample_member_def(sample_stream_id, stream_id, T_ULONGLONG, "event stream id"), \
sample_member_def(sample_period, period, T_ULONGLONG, "event period"), \
sample_member_def(sample_cpu, cpu, T_UINT, "event cpu"),
+static PyObject *pyrf_event__get_evsel(PyObject *self, void *closure __maybe_unused)
+{
+ struct pyrf_event *pevent = (void *)self;
+
+ if (!pevent->sample.evsel)
+ Py_RETURN_NONE;
+
+ return pyrf_evsel__from_evsel(pevent->sample.evsel);
+}
+
+static PyGetSetDef pyrf_event__getset[] = {
+ {
+ .name = "evsel",
+ .get = pyrf_event__get_evsel,
+ .set = NULL,
+ .doc = "tracking event.",
+ },
+ { .name = NULL, },
+};
+
+static void pyrf_event__delete(struct pyrf_event *pevent)
+{
+ if (pevent->al_resolved)
+ addr_location__exit(&pevent->al);
+ perf_sample__exit(&pevent->sample);
+ Py_TYPE(pevent)->tp_free((PyObject *)pevent);
+}
+
static const char pyrf_mmap_event__doc[] = PyDoc_STR("perf mmap event object.");
static PyMemberDef pyrf_mmap_event__members[] = {
@@ -105,9 +145,12 @@ static PyTypeObject pyrf_mmap_event__type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "perf.mmap_event",
.tp_basicsize = sizeof(struct pyrf_event),
+ .tp_new = PyType_GenericNew,
+ .tp_dealloc = (destructor)pyrf_event__delete,
.tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
.tp_doc = pyrf_mmap_event__doc,
.tp_members = pyrf_mmap_event__members,
+ .tp_getset = pyrf_event__getset,
.tp_repr = (reprfunc)pyrf_mmap_event__repr,
};
@@ -140,9 +183,12 @@ static PyTypeObject pyrf_task_event__type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "perf.task_event",
.tp_basicsize = sizeof(struct pyrf_event),
+ .tp_new = PyType_GenericNew,
+ .tp_dealloc = (destructor)pyrf_event__delete,
.tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
.tp_doc = pyrf_task_event__doc,
.tp_members = pyrf_task_event__members,
+ .tp_getset = pyrf_event__getset,
.tp_repr = (reprfunc)pyrf_task_event__repr,
};
@@ -172,6 +218,7 @@ static PyTypeObject pyrf_comm_event__type = {
.tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
.tp_doc = pyrf_comm_event__doc,
.tp_members = pyrf_comm_event__members,
+ .tp_getset = pyrf_event__getset,
.tp_repr = (reprfunc)pyrf_comm_event__repr,
};
@@ -201,9 +248,12 @@ static PyTypeObject pyrf_throttle_event__type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "perf.throttle_event",
.tp_basicsize = sizeof(struct pyrf_event),
+ .tp_new = PyType_GenericNew,
+ .tp_dealloc = (destructor)pyrf_event__delete,
.tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
.tp_doc = pyrf_throttle_event__doc,
.tp_members = pyrf_throttle_event__members,
+ .tp_getset = pyrf_event__getset,
.tp_repr = (reprfunc)pyrf_throttle_event__repr,
};
@@ -236,9 +286,12 @@ static PyTypeObject pyrf_lost_event__type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "perf.lost_event",
.tp_basicsize = sizeof(struct pyrf_event),
+ .tp_new = PyType_GenericNew,
+ .tp_dealloc = (destructor)pyrf_event__delete,
.tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
.tp_doc = pyrf_lost_event__doc,
.tp_members = pyrf_lost_event__members,
+ .tp_getset = pyrf_event__getset,
.tp_repr = (reprfunc)pyrf_lost_event__repr,
};
@@ -269,6 +322,7 @@ static PyTypeObject pyrf_read_event__type = {
.tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
.tp_doc = pyrf_read_event__doc,
.tp_members = pyrf_read_event__members,
+ .tp_getset = pyrf_event__getset,
.tp_repr = (reprfunc)pyrf_read_event__repr,
};
@@ -276,16 +330,17 @@ static const char pyrf_sample_event__doc[] = PyDoc_STR("perf sample event object
static PyMemberDef pyrf_sample_event__members[] = {
sample_members
+ sample_member_def(sample_ip, ip, T_ULONGLONG, "event ip"),
+ sample_member_def(sample_addr, addr, T_ULONGLONG, "event addr"),
+ sample_member_def(sample_phys_addr, phys_addr, T_ULONGLONG, "event physical addr"),
+ sample_member_def(sample_weight, weight, T_ULONGLONG, "event weight"),
+ sample_member_def(sample_data_src, data_src, T_ULONGLONG, "event data source"),
+ sample_member_def(sample_insn_count, insn_cnt, T_ULONGLONG, "event instruction count"),
+ sample_member_def(sample_cyc_count, cyc_cnt, T_ULONGLONG, "event cycle count"),
member_def(perf_event_header, type, T_UINT, "event type"),
{ .name = NULL, },
};
-static void pyrf_sample_event__delete(struct pyrf_event *pevent)
-{
- perf_sample__exit(&pevent->sample);
- Py_TYPE(pevent)->tp_free((PyObject*)pevent);
-}
-
static PyObject *pyrf_sample_event__repr(const struct pyrf_event *pevent)
{
PyObject *ret;
@@ -373,6 +428,199 @@ get_tracepoint_field(struct pyrf_event *pevent, PyObject *attr_name)
}
#endif /* HAVE_LIBTRACEEVENT */
+static int pyrf_sample_event__resolve_al(struct pyrf_event *pevent)
+{
+ struct evsel *evsel = pevent->sample.evsel;
+ struct evlist *evlist = evsel ? evsel->evlist : NULL;
+ struct perf_session *session = evlist ? evlist__session(evlist) : NULL;
+
+ if (pevent->al_resolved)
+ return 0;
+
+ if (!session)
+ return -1;
+
+ addr_location__init(&pevent->al);
+ if (machine__resolve(&session->machines.host, &pevent->al, &pevent->sample) < 0) {
+ addr_location__exit(&pevent->al);
+ return -1;
+ }
+
+ pevent->al_resolved = true;
+ return 0;
+}
+
+static PyObject *pyrf_sample_event__get_dso(struct pyrf_event *pevent,
+ void *closure __maybe_unused)
+{
+ if (pyrf_sample_event__resolve_al(pevent) < 0 || !pevent->al.map)
+ Py_RETURN_NONE;
+
+ return PyUnicode_FromString(dso__name(map__dso(pevent->al.map)));
+}
+
+static PyObject *pyrf_sample_event__get_dso_long_name(struct pyrf_event *pevent,
+ void *closure __maybe_unused)
+{
+ if (pyrf_sample_event__resolve_al(pevent) < 0 || !pevent->al.map)
+ Py_RETURN_NONE;
+
+ return PyUnicode_FromString(dso__long_name(map__dso(pevent->al.map)));
+}
+
+static PyObject *pyrf_sample_event__get_dso_bid(struct pyrf_event *pevent,
+ void *closure __maybe_unused)
+{
+ char sbuild_id[SBUILD_ID_SIZE];
+
+ if (pyrf_sample_event__resolve_al(pevent) < 0 || !pevent->al.map)
+ Py_RETURN_NONE;
+
+ build_id__snprintf(dso__bid(map__dso(pevent->al.map)), sbuild_id, sizeof(sbuild_id));
+ return PyUnicode_FromString(sbuild_id);
+}
+
+static PyObject *pyrf_sample_event__get_map_start(struct pyrf_event *pevent,
+ void *closure __maybe_unused)
+{
+ if (pyrf_sample_event__resolve_al(pevent) < 0 || !pevent->al.map)
+ Py_RETURN_NONE;
+
+ return PyLong_FromUnsignedLong(map__start(pevent->al.map));
+}
+
+static PyObject *pyrf_sample_event__get_map_end(struct pyrf_event *pevent,
+ void *closure __maybe_unused)
+{
+ if (pyrf_sample_event__resolve_al(pevent) < 0 || !pevent->al.map)
+ Py_RETURN_NONE;
+
+ return PyLong_FromUnsignedLong(map__end(pevent->al.map));
+}
+
+static PyObject *pyrf_sample_event__get_map_pgoff(struct pyrf_event *pevent,
+ void *closure __maybe_unused)
+{
+ if (pyrf_sample_event__resolve_al(pevent) < 0 || !pevent->al.map)
+ Py_RETURN_NONE;
+
+ return PyLong_FromUnsignedLongLong(map__pgoff(pevent->al.map));
+}
+
+static PyObject *pyrf_sample_event__get_symbol(struct pyrf_event *pevent,
+ void *closure __maybe_unused)
+{
+ if (pyrf_sample_event__resolve_al(pevent) < 0 || !pevent->al.sym)
+ Py_RETURN_NONE;
+
+ return PyUnicode_FromString(pevent->al.sym->name);
+}
+
+static PyObject *pyrf_sample_event__get_sym_start(struct pyrf_event *pevent,
+ void *closure __maybe_unused)
+{
+ if (pyrf_sample_event__resolve_al(pevent) < 0 || !pevent->al.sym)
+ Py_RETURN_NONE;
+
+ return PyLong_FromUnsignedLongLong(pevent->al.sym->start);
+}
+
+static PyObject *pyrf_sample_event__get_sym_end(struct pyrf_event *pevent,
+ void *closure __maybe_unused)
+{
+ if (pyrf_sample_event__resolve_al(pevent) < 0 || !pevent->al.sym)
+ Py_RETURN_NONE;
+
+ return PyLong_FromUnsignedLongLong(pevent->al.sym->end);
+}
+
+static PyObject *pyrf_sample_event__get_raw_buf(struct pyrf_event *pevent,
+ void *closure __maybe_unused)
+{
+ if (pevent->event.header.type != PERF_RECORD_SAMPLE)
+ Py_RETURN_NONE;
+
+ return PyBytes_FromStringAndSize((const char *)pevent->sample.raw_data,
+ pevent->sample.raw_size);
+}
+
+static PyObject *pyrf_sample_event__srccode(PyObject *self, PyObject *args)
+{
+ struct pyrf_event *pevent = (void *)self;
+ u64 addr = pevent->sample.ip;
+ char *srcfile = NULL;
+ char *srccode = NULL;
+ unsigned int line = 0;
+ int len = 0;
+ PyObject *result;
+ struct addr_location al;
+
+ if (!PyArg_ParseTuple(args, "|K", &addr))
+ return NULL;
+
+ if (pyrf_sample_event__resolve_al(pevent) < 0)
+ Py_RETURN_NONE;
+
+ if (addr != pevent->sample.ip) {
+ addr_location__init(&al);
+ thread__find_symbol_fb(pevent->al.thread, pevent->sample.cpumode, addr, &al);
+ } else {
+ addr_location__init(&al);
+ al.thread = thread__get(pevent->al.thread);
+ al.map = map__get(pevent->al.map);
+ al.sym = pevent->al.sym;
+ al.addr = pevent->al.addr;
+ }
+
+ if (al.map) {
+ struct dso *dso = map__dso(al.map);
+
+ if (dso) {
+ srcfile = get_srcline_split(dso, map__rip_2objdump(al.map, addr),
+ &line);
+ }
+ }
+ addr_location__exit(&al);
+
+ if (srcfile) {
+ srccode = find_sourceline(srcfile, line, &len);
+ result = Py_BuildValue("(sIs#)", srcfile, line, srccode, (Py_ssize_t)len);
+ free(srcfile);
+ } else {
+ result = Py_BuildValue("(sIs#)", NULL, 0, NULL, (Py_ssize_t)0);
+ }
+
+ return result;
+}
+
+static PyObject *pyrf_sample_event__insn(PyObject *self, PyObject *args __maybe_unused)
+{
+ struct pyrf_event *pevent = (void *)self;
+ struct thread *thread;
+ struct machine *machine;
+
+ if (pyrf_sample_event__resolve_al(pevent) < 0)
+ Py_RETURN_NONE;
+
+ thread = pevent->al.thread;
+
+ if (!thread || !thread__maps(thread))
+ Py_RETURN_NONE;
+
+ machine = maps__machine(thread__maps(thread));
+ if (!machine)
+ Py_RETURN_NONE;
+
+ if (pevent->sample.ip && !pevent->sample.insn_len)
+ perf_sample__fetch_insn(&pevent->sample, thread, machine);
+
+ if (!pevent->sample.insn_len)
+ Py_RETURN_NONE;
+
+ return PyBytes_FromStringAndSize((const char *)pevent->sample.insn,
+ pevent->sample.insn_len);
+}
+
static PyObject*
pyrf_sample_event__getattro(struct pyrf_event *pevent, PyObject *attr_name)
{
@@ -386,13 +634,103 @@ pyrf_sample_event__getattro(struct pyrf_event *pevent, PyObject *attr_name)
return obj ?: PyObject_GenericGetAttr((PyObject *) pevent, attr_name);
}
+static PyGetSetDef pyrf_sample_event__getset[] = {
+ {
+ .name = "raw_buf",
+ .get = (getter)pyrf_sample_event__get_raw_buf,
+ .set = NULL,
+ .doc = "event raw buffer.",
+ },
+ {
+ .name = "evsel",
+ .get = pyrf_event__get_evsel,
+ .set = NULL,
+ .doc = "tracking event.",
+ },
+ {
+ .name = "dso",
+ .get = (getter)pyrf_sample_event__get_dso,
+ .set = NULL,
+ .doc = "event dso short name.",
+ },
+ {
+ .name = "dso_long_name",
+ .get = (getter)pyrf_sample_event__get_dso_long_name,
+ .set = NULL,
+ .doc = "event dso long name.",
+ },
+ {
+ .name = "dso_bid",
+ .get = (getter)pyrf_sample_event__get_dso_bid,
+ .set = NULL,
+ .doc = "event dso build id.",
+ },
+ {
+ .name = "map_start",
+ .get = (getter)pyrf_sample_event__get_map_start,
+ .set = NULL,
+ .doc = "event map start address.",
+ },
+ {
+ .name = "map_end",
+ .get = (getter)pyrf_sample_event__get_map_end,
+ .set = NULL,
+ .doc = "event map end address.",
+ },
+ {
+ .name = "map_pgoff",
+ .get = (getter)pyrf_sample_event__get_map_pgoff,
+ .set = NULL,
+ .doc = "event map page offset.",
+ },
+ {
+ .name = "symbol",
+ .get = (getter)pyrf_sample_event__get_symbol,
+ .set = NULL,
+ .doc = "event symbol name.",
+ },
+ {
+ .name = "sym_start",
+ .get = (getter)pyrf_sample_event__get_sym_start,
+ .set = NULL,
+ .doc = "event symbol start address.",
+ },
+ {
+ .name = "sym_end",
+ .get = (getter)pyrf_sample_event__get_sym_end,
+ .set = NULL,
+ .doc = "event symbol end address.",
+ },
+ { .name = NULL, },
+};
+
+static PyMethodDef pyrf_sample_event__methods[] = {
+ {
+ .ml_name = "srccode",
+ .ml_meth = (PyCFunction)pyrf_sample_event__srccode,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = PyDoc_STR("Get source code for an address.")
+ },
+ {
+ .ml_name = "insn",
+ .ml_meth = (PyCFunction)pyrf_sample_event__insn,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = PyDoc_STR("Get instruction bytes for a sample.")
+ },
+ { .ml_name = NULL, }
+};
+
static PyTypeObject pyrf_sample_event__type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "perf.sample_event",
.tp_basicsize = sizeof(struct pyrf_event),
+ .tp_new = PyType_GenericNew,
+ .tp_dealloc = (destructor)pyrf_event__delete,
.tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
.tp_doc = pyrf_sample_event__doc,
.tp_members = pyrf_sample_event__members,
+ .tp_getset = pyrf_sample_event__getset,
+ .tp_methods = pyrf_sample_event__methods,
.tp_repr = (reprfunc)pyrf_sample_event__repr,
.tp_getattro = (getattrofunc) pyrf_sample_event__getattro,
};
@@ -428,25 +766,18 @@ static PyTypeObject pyrf_context_switch_event__type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "perf.context_switch_event",
.tp_basicsize = sizeof(struct pyrf_event),
+ .tp_new = PyType_GenericNew,
+ .tp_dealloc = (destructor)pyrf_event__delete,
.tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
.tp_doc = pyrf_context_switch_event__doc,
.tp_members = pyrf_context_switch_event__members,
+ .tp_getset = pyrf_event__getset,
.tp_repr = (reprfunc)pyrf_context_switch_event__repr,
};
static int pyrf_event__setup_types(void)
{
int err;
- pyrf_mmap_event__type.tp_new =
- pyrf_task_event__type.tp_new =
- pyrf_comm_event__type.tp_new =
- pyrf_lost_event__type.tp_new =
- pyrf_read_event__type.tp_new =
- pyrf_sample_event__type.tp_new =
- pyrf_context_switch_event__type.tp_new =
- pyrf_throttle_event__type.tp_new = PyType_GenericNew;
-
- pyrf_sample_event__type.tp_dealloc = (destructor)pyrf_sample_event__delete,
err = PyType_Ready(&pyrf_mmap_event__type);
if (err < 0)
@@ -490,10 +821,14 @@ static PyTypeObject *pyrf_event__type[] = {
[PERF_RECORD_SWITCH_CPU_WIDE] = &pyrf_context_switch_event__type,
};
-static PyObject *pyrf_event__new(const union perf_event *event)
+static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *evsel,
+ struct perf_session *session __maybe_unused)
{
struct pyrf_event *pevent;
PyTypeObject *ptype;
+ size_t size;
+ int err;
+ size_t min_size = sizeof(struct perf_event_header);
if ((event->header.type < PERF_RECORD_MMAP ||
event->header.type > PERF_RECORD_SAMPLE) &&
@@ -504,19 +839,62 @@ static PyObject *pyrf_event__new(const union perf_event *event)
return NULL;
}
- // FIXME this better be dynamic or we need to parse everything
- // before calling perf_mmap__consume(), including tracepoint fields.
- if (sizeof(pevent->event) < event->header.size) {
- PyErr_Format(PyExc_TypeError, "Unexpected event size: %zd < %u",
- sizeof(pevent->event), event->header.size);
- return NULL;
+ ptype = pyrf_event__type[event->header.type];
+
+ switch (event->header.type) {
+ case PERF_RECORD_MMAP:
+ min_size = sizeof(struct perf_record_mmap);
+ break;
+ case PERF_RECORD_COMM:
+ min_size = sizeof(struct perf_record_comm);
+ break;
+ case PERF_RECORD_FORK:
+ case PERF_RECORD_EXIT:
+ min_size = sizeof(struct perf_record_fork);
+ break;
+ case PERF_RECORD_THROTTLE:
+ case PERF_RECORD_UNTHROTTLE:
+ min_size = sizeof(struct perf_record_throttle);
+ break;
+ case PERF_RECORD_LOST:
+ min_size = sizeof(struct perf_record_lost);
+ break;
+ case PERF_RECORD_READ:
+ min_size = sizeof(struct perf_record_read);
+ break;
+ case PERF_RECORD_SWITCH:
+ case PERF_RECORD_SWITCH_CPU_WIDE:
+ min_size = sizeof(struct perf_record_switch);
+ break;
+ default:
+ break;
}
+ if (event->header.size < min_size)
+ return PyErr_Format(PyExc_ValueError, "Event size %u too small for type %u",
+ event->header.size, event->header.type);
+
+ /* Allocate just enough memory for the size of event. */
+ size = offsetof(struct pyrf_event, event) + event->header.size;
+ pevent = (struct pyrf_event *)PyObject_Malloc(size);
+ if (pevent == NULL)
+ return PyErr_NoMemory();
- ptype = pyrf_event__type[event->header.type];
- pevent = PyObject_New(struct pyrf_event, ptype);
- if (pevent != NULL) {
- memcpy(&pevent->event, event, event->header.size);
- perf_sample__init(&pevent->sample, /*all=*/false);
+ /* Copy the event for memory safety and initilaize variables. */
+ PyObject_Init((PyObject *)pevent, ptype);
+ memcpy(&pevent->event, event, event->header.size);
+ perf_sample__init(&pevent->sample, /*all=*/true);
+ pevent->al_resolved = false;
+ addr_location__init(&pevent->al);
+
+ if (!evsel)
+ return (PyObject *)pevent;
+
+ /* Parse the sample again so that pointers are within the copied event. */
+ err = evsel__parse_sample(evsel, &pevent->event, &pevent->sample);
+ if (err < 0) {
+ Py_DECREF(pevent);
+ return PyErr_Format(PyExc_OSError,
+ "perf: can't parse sample, err=%d", err);
}
return (PyObject *)pevent;
}
@@ -1209,7 +1587,7 @@ static PyObject *pyrf_evsel__str(PyObject *self)
struct pyrf_evsel *pevsel = (void *)self;
struct evsel *evsel = pevsel->evsel;
- return PyUnicode_FromFormat("evsel(%s/%s/)", evsel__pmu_name(evsel), evsel__name(evsel));
+ return PyUnicode_FromFormat("evsel(%s)", evsel__name(evsel));
}
static PyMethodDef pyrf_evsel__methods[] = {
@@ -1771,9 +2149,11 @@ static PyObject *pyrf_evlist__read_on_cpu(struct pyrf_evlist *pevlist,
{
struct evlist *evlist = pevlist->evlist;
union perf_event *event;
+ struct evsel *evsel;
int sample_id_all = 1, cpu;
static char *kwlist[] = { "cpu", "sample_id_all", NULL };
struct mmap *md;
+ PyObject *pyevent;
int err;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|i", kwlist,
@@ -1781,44 +2161,31 @@ static PyObject *pyrf_evlist__read_on_cpu(struct pyrf_evlist *pevlist,
return NULL;
md = get_md(evlist, cpu);
- if (!md) {
- PyErr_Format(PyExc_TypeError, "Unknown CPU '%d'", cpu);
- return NULL;
- }
+ if (!md)
+ return PyErr_Format(PyExc_TypeError, "Unknown CPU '%d'", cpu);
- if (perf_mmap__read_init(&md->core) < 0)
- goto end;
+ err = perf_mmap__read_init(&md->core);
+ if (err < 0) {
+ return PyErr_Format(PyExc_OSError,
+ "perf: error mmap read init, err=%d", err);
+ }
event = perf_mmap__read_event(&md->core);
- if (event != NULL) {
- PyObject *pyevent = pyrf_event__new(event);
- struct pyrf_event *pevent = (struct pyrf_event *)pyevent;
- struct evsel *evsel;
-
- if (pyevent == NULL)
- return PyErr_NoMemory();
-
- evsel = evlist__event2evsel(evlist, event);
- if (!evsel) {
- Py_DECREF(pyevent);
- Py_INCREF(Py_None);
- return Py_None;
- }
+ if (event == NULL)
+ Py_RETURN_NONE;
+ evsel = evlist__event2evsel(evlist, event);
+ if (!evsel) {
+ /* Unknown evsel. */
perf_mmap__consume(&md->core);
-
- err = evsel__parse_sample(evsel, &pevent->event, &pevent->sample);
- if (err) {
- Py_DECREF(pyevent);
- return PyErr_Format(PyExc_OSError,
- "perf: can't parse sample, err=%d", err);
- }
-
- return pyevent;
+ Py_RETURN_NONE;
}
-end:
- Py_INCREF(Py_None);
- return Py_None;
+ pyevent = pyrf_event__new(event, evsel, evlist__session(evlist));
+ perf_mmap__consume(&md->core);
+ if (pyevent == NULL)
+ return PyErr_Occurred() ? NULL : PyErr_NoMemory();
+
+ return pyevent;
}
static PyObject *pyrf_evlist__open(struct pyrf_evlist *pevlist,
@@ -2013,10 +2380,7 @@ static PyObject *pyrf_evlist__str(PyObject *self)
evlist__for_each_entry(pevlist->evlist, pos) {
if (!first)
strbuf_addch(&sb, ',');
- if (!pos->pmu)
- strbuf_addstr(&sb, evsel__name(pos));
- else
- strbuf_addf(&sb, "%s/%s/", pos->pmu->name, evsel__name(pos));
+ strbuf_addstr(&sb, evsel__name(pos));
first = false;
}
strbuf_addstr(&sb, "])");
@@ -2499,24 +2863,12 @@ static int pyrf_session_tool__sample(const struct perf_tool *tool,
struct machine *machine __maybe_unused)
{
struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool);
- PyObject *pyevent = pyrf_event__new(event);
- struct pyrf_event *pevent = (struct pyrf_event *)pyevent;
+ PyObject *pyevent = pyrf_event__new(event, sample->evsel, psession->session);
PyObject *ret;
if (pyevent == NULL)
return -ENOMEM;
- memcpy(&pevent->event, event, event->header.size);
- if (evsel__parse_sample(evsel, &pevent->event, &pevent->sample) < 0) {
- Py_DECREF(pyevent);
- return -1;
- }
- /* Avoid shallow copy pointing to lazily allocated memory that would be double freed. */
- pevent->sample.user_regs = NULL;
- pevent->sample.intr_regs = NULL;
- if (pevent->sample.merged_callchain)
- pevent->sample.callchain = NULL;
-
ret = PyObject_CallFunction(psession->sample, "O", pyevent);
if (!ret) {
PyErr_Print();
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 20/59] perf python: Extend API for stat events in python.c
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Add stat information to the session. Add call backs for stat events.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v5:
1. Fix Memory Corruption: Corrected the memory offset for `stat_round_type`
in `pyrf_stat_round_event__members` by adding the base offset of
`struct pyrf_event`.
2. Fix Memory Leak: Added `Py_XDECREF()` to free the unused return value
of the Python callback in `pyrf_session_tool__stat_round()`.
---
v7:
- Added comprehensive size checks in pyrf_event__new for all event
types.
- Fixed line length warning.
---
tools/perf/util/python.c | 271 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 264 insertions(+), 7 deletions(-)
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 5478561ca62c..17d0ee15336f 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -299,6 +299,77 @@ static PyTypeObject pyrf_lost_event__type = {
.tp_repr = (reprfunc)pyrf_lost_event__repr,
};
+static const char pyrf_stat_event__doc[] = PyDoc_STR("perf stat event object.");
+
+static PyMemberDef pyrf_stat_event__members[] = {
+ sample_members
+ member_def(perf_event_header, type, T_UINT, "event type"),
+ member_def(perf_record_stat, id, T_ULONGLONG, "event id"),
+ member_def(perf_record_stat, cpu, T_UINT, "event cpu"),
+ member_def(perf_record_stat, thread, T_UINT, "event thread"),
+ member_def(perf_record_stat, val, T_ULONGLONG, "counter value"),
+ member_def(perf_record_stat, ena, T_ULONGLONG, "enabled time"),
+ member_def(perf_record_stat, run, T_ULONGLONG, "running time"),
+ { .name = NULL, },
+};
+
+static PyObject *pyrf_stat_event__repr(const struct pyrf_event *pevent)
+{
+ return PyUnicode_FromFormat(
+ "{ type: stat, id: %llu, cpu: %u, thread: %u, val: %llu, ena: %llu, run: %llu }",
+ pevent->event.stat.id,
+ pevent->event.stat.cpu,
+ pevent->event.stat.thread,
+ pevent->event.stat.val,
+ pevent->event.stat.ena,
+ pevent->event.stat.run);
+}
+
+static PyTypeObject pyrf_stat_event__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.stat_event",
+ .tp_basicsize = sizeof(struct pyrf_event),
+ .tp_new = PyType_GenericNew,
+ .tp_dealloc = (destructor)pyrf_event__delete,
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = pyrf_stat_event__doc,
+ .tp_members = pyrf_stat_event__members,
+ .tp_getset = pyrf_event__getset,
+ .tp_repr = (reprfunc)pyrf_stat_event__repr,
+};
+
+static const char pyrf_stat_round_event__doc[] = PyDoc_STR("perf stat round event object.");
+
+static PyMemberDef pyrf_stat_round_event__members[] = {
+ sample_members
+ member_def(perf_event_header, type, T_UINT, "event type"),
+ { .name = "stat_round_type", .type = T_ULONGLONG,
+ .offset = offsetof(struct pyrf_event, event) + offsetof(struct perf_record_stat_round, type),
+ .doc = "round type" },
+ member_def(perf_record_stat_round, time, T_ULONGLONG, "round time"),
+ { .name = NULL, },
+};
+
+static PyObject *pyrf_stat_round_event__repr(const struct pyrf_event *pevent)
+{
+ return PyUnicode_FromFormat("{ type: stat_round, type: %llu, time: %llu }",
+ pevent->event.stat_round.type,
+ pevent->event.stat_round.time);
+}
+
+static PyTypeObject pyrf_stat_round_event__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.stat_round_event",
+ .tp_basicsize = sizeof(struct pyrf_event),
+ .tp_new = PyType_GenericNew,
+ .tp_dealloc = (destructor)pyrf_event__delete,
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = pyrf_stat_round_event__doc,
+ .tp_members = pyrf_stat_round_event__members,
+ .tp_getset = pyrf_event__getset,
+ .tp_repr = (reprfunc)pyrf_stat_round_event__repr,
+};
+
static const char pyrf_read_event__doc[] = PyDoc_STR("perf read event object.");
static PyMemberDef pyrf_read_event__members[] = {
@@ -986,6 +1057,12 @@ static int pyrf_event__setup_types(void)
if (err < 0)
goto out;
err = PyType_Ready(&pyrf_context_switch_event__type);
+ if (err < 0)
+ goto out;
+ err = PyType_Ready(&pyrf_stat_event__type);
+ if (err < 0)
+ goto out;
+ err = PyType_Ready(&pyrf_stat_round_event__type);
if (err < 0)
goto out;
err = PyType_Ready(&pyrf_callchain_node__type);
@@ -1010,6 +1087,8 @@ static PyTypeObject *pyrf_event__type[] = {
[PERF_RECORD_SAMPLE] = &pyrf_sample_event__type,
[PERF_RECORD_SWITCH] = &pyrf_context_switch_event__type,
[PERF_RECORD_SWITCH_CPU_WIDE] = &pyrf_context_switch_event__type,
+ [PERF_RECORD_STAT] = &pyrf_stat_event__type,
+ [PERF_RECORD_STAT_ROUND] = &pyrf_stat_round_event__type,
};
static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *evsel,
@@ -1026,7 +1105,9 @@ static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *ev
if ((event->header.type < PERF_RECORD_MMAP ||
event->header.type > PERF_RECORD_SAMPLE) &&
!(event->header.type == PERF_RECORD_SWITCH ||
- event->header.type == PERF_RECORD_SWITCH_CPU_WIDE)) {
+ event->header.type == PERF_RECORD_SWITCH_CPU_WIDE ||
+ event->header.type == PERF_RECORD_STAT ||
+ event->header.type == PERF_RECORD_STAT_ROUND)) {
PyErr_Format(PyExc_TypeError, "Unexpected header type %u",
event->header.type);
return NULL;
@@ -1038,6 +1119,9 @@ static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *ev
case PERF_RECORD_MMAP:
min_size = sizeof(struct perf_record_mmap);
break;
+ case PERF_RECORD_MMAP2:
+ min_size = sizeof(struct perf_record_mmap2);
+ break;
case PERF_RECORD_COMM:
min_size = sizeof(struct perf_record_comm);
break;
@@ -1045,13 +1129,13 @@ static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *ev
case PERF_RECORD_EXIT:
min_size = sizeof(struct perf_record_fork);
break;
+ case PERF_RECORD_LOST:
+ min_size = sizeof(struct perf_record_lost);
+ break;
case PERF_RECORD_THROTTLE:
case PERF_RECORD_UNTHROTTLE:
min_size = sizeof(struct perf_record_throttle);
break;
- case PERF_RECORD_LOST:
- min_size = sizeof(struct perf_record_lost);
- break;
case PERF_RECORD_READ:
min_size = sizeof(struct perf_record_read);
break;
@@ -1059,6 +1143,96 @@ static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *ev
case PERF_RECORD_SWITCH_CPU_WIDE:
min_size = sizeof(struct perf_record_switch);
break;
+ case PERF_RECORD_AUX:
+ min_size = sizeof(struct perf_record_aux);
+ break;
+ case PERF_RECORD_ITRACE_START:
+ min_size = sizeof(struct perf_record_itrace_start);
+ break;
+ case PERF_RECORD_LOST_SAMPLES:
+ min_size = sizeof(struct perf_record_lost_samples);
+ break;
+ case PERF_RECORD_NAMESPACES:
+ min_size = sizeof(struct perf_record_namespaces);
+ break;
+ case PERF_RECORD_KSYMBOL:
+ min_size = sizeof(struct perf_record_ksymbol);
+ break;
+ case PERF_RECORD_BPF_EVENT:
+ min_size = sizeof(struct perf_record_bpf_event);
+ break;
+ case PERF_RECORD_CGROUP:
+ min_size = sizeof(struct perf_record_cgroup);
+ break;
+ case PERF_RECORD_TEXT_POKE:
+ min_size = sizeof(struct perf_record_text_poke_event);
+ break;
+ case PERF_RECORD_AUX_OUTPUT_HW_ID:
+ min_size = sizeof(struct perf_record_aux_output_hw_id);
+ break;
+ case PERF_RECORD_CALLCHAIN_DEFERRED:
+ min_size = sizeof(struct perf_record_callchain_deferred);
+ break;
+ case PERF_RECORD_HEADER_ATTR:
+ min_size = sizeof(struct perf_record_header_attr);
+ break;
+ case PERF_RECORD_HEADER_TRACING_DATA:
+ min_size = sizeof(struct perf_record_header_tracing_data);
+ break;
+ case PERF_RECORD_HEADER_BUILD_ID:
+ min_size = sizeof(struct perf_record_header_build_id);
+ break;
+ case PERF_RECORD_ID_INDEX:
+ min_size = sizeof(struct perf_record_id_index);
+ break;
+ case PERF_RECORD_AUXTRACE_INFO:
+ min_size = sizeof(struct perf_record_auxtrace_info);
+ break;
+ case PERF_RECORD_AUXTRACE:
+ min_size = sizeof(struct perf_record_auxtrace);
+ break;
+ case PERF_RECORD_AUXTRACE_ERROR:
+ min_size = sizeof(struct perf_record_auxtrace_error);
+ break;
+ case PERF_RECORD_THREAD_MAP:
+ min_size = sizeof(struct perf_record_thread_map);
+ break;
+ case PERF_RECORD_CPU_MAP:
+ min_size = sizeof(struct perf_record_cpu_map);
+ break;
+ case PERF_RECORD_STAT_CONFIG:
+ min_size = sizeof(struct perf_record_stat_config);
+ break;
+ case PERF_RECORD_STAT:
+ min_size = sizeof(struct perf_record_stat);
+ break;
+ case PERF_RECORD_STAT_ROUND:
+ min_size = sizeof(struct perf_record_stat_round);
+ break;
+ case PERF_RECORD_EVENT_UPDATE:
+ min_size = sizeof(struct perf_record_event_update);
+ break;
+ case PERF_RECORD_TIME_CONV:
+ min_size = sizeof(struct perf_record_time_conv);
+ break;
+ case PERF_RECORD_HEADER_FEATURE:
+ min_size = sizeof(struct perf_record_header_feature);
+ break;
+ case PERF_RECORD_COMPRESSED:
+ min_size = sizeof(struct perf_record_compressed);
+ break;
+ case PERF_RECORD_COMPRESSED2:
+ min_size = sizeof(struct perf_record_compressed2);
+ break;
+ case PERF_RECORD_BPF_METADATA:
+ min_size = sizeof(struct perf_record_bpf_metadata);
+ break;
+ case PERF_RECORD_SCHEDSTAT_CPU:
+ min_size = sizeof(struct perf_record_schedstat_cpu);
+ break;
+ case PERF_RECORD_SCHEDSTAT_DOMAIN:
+ min_size = sizeof(struct perf_record_schedstat_domain);
+ break;
default:
break;
}
@@ -1970,7 +2144,40 @@ static PyObject *pyrf_evsel__get_attr_wakeup_events(PyObject *self, void */*clos
return PyLong_FromUnsignedLong(pevsel->evsel->core.attr.wakeup_events);
}
+static PyObject *pyrf_evsel__get_ids(struct pyrf_evsel *pevsel, void *closure __maybe_unused)
+{
+ struct evsel *evsel = pevsel->evsel;
+ PyObject *list = PyList_New(0);
+
+ if (!list)
+ return NULL;
+
+ for (u32 i = 0; i < evsel->core.ids; i++) {
+ PyObject *id = PyLong_FromUnsignedLongLong(evsel->core.id[i]);
+ int ret;
+
+ if (!id) {
+ Py_DECREF(list);
+ return NULL;
+ }
+ ret = PyList_Append(list, id);
+ Py_DECREF(id);
+ if (ret < 0) {
+ Py_DECREF(list);
+ return NULL;
+ }
+ }
+
+ return list;
+}
+
static PyGetSetDef pyrf_evsel__getset[] = {
+ {
+ .name = "ids",
+ .get = (getter)pyrf_evsel__get_ids,
+ .set = NULL,
+ .doc = "event IDs.",
+ },
{
.name = "tracking",
.get = pyrf_evsel__get_tracking,
@@ -2743,6 +2950,8 @@ static const struct perf_constant perf__constants[] = {
PERF_CONST(RECORD_LOST_SAMPLES),
PERF_CONST(RECORD_SWITCH),
PERF_CONST(RECORD_SWITCH_CPU_WIDE),
+ PERF_CONST(RECORD_STAT),
+ PERF_CONST(RECORD_STAT_ROUND),
PERF_CONST(RECORD_MISC_SWITCH_OUT),
{ .name = NULL, },
@@ -3117,6 +3326,47 @@ static int pyrf_session_tool__sample(const struct perf_tool *tool,
return 0;
}
+static int pyrf_session_tool__stat(const struct perf_tool *tool,
+ struct perf_session *session,
+ union perf_event *event)
+{
+ struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool);
+ struct evsel *evsel = evlist__id2evsel(session->evlist, event->stat.id);
+ PyObject *pyevent = pyrf_event__new(event, evsel, psession->session);
+ const char *name = evsel ? evsel__name(evsel) : "unknown";
+ PyObject *ret;
+
+ if (pyevent == NULL)
+ return -ENOMEM;
+
+ ret = PyObject_CallFunction(psession->stat, "Os", pyevent, name);
+ if (!ret) {
+ PyErr_Print();
+ Py_DECREF(pyevent);
+ return -1;
+ }
+ Py_DECREF(ret);
+ Py_DECREF(pyevent);
+ return 0;
+}
+
+static int pyrf_session_tool__stat_round(const struct perf_tool *tool,
+ struct perf_session *session __maybe_unused,
+ union perf_event *event)
+{
+ struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool);
+ PyObject *pyevent = pyrf_event__new(event, /*evsel=*/NULL, psession->session);
+ PyObject *ret;
+
+ if (pyevent == NULL)
+ return -ENOMEM;
+
+ ret = PyObject_CallFunction(psession->stat, "O", pyevent);
+ Py_XDECREF(ret);
+ Py_DECREF(pyevent);
+ return 0;
+}
+
static PyObject *pyrf_session__find_thread(struct pyrf_session *psession, PyObject *args)
{
struct machine *machine;
@@ -3149,10 +3399,11 @@ static int pyrf_session__init(struct pyrf_session *psession, PyObject *args, PyO
{
struct pyrf_data *pdata;
PyObject *sample = NULL;
- static char *kwlist[] = { "data", "sample", NULL };
+ PyObject *stat = NULL;
+ static char *kwlist[] = { "data", "sample", "stat", NULL };
- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|O", kwlist, &pyrf_data__type, &pdata,
- &sample))
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|OO", kwlist, &pyrf_data__type, &pdata,
+ &sample, &stat))
return -1;
Py_INCREF(pdata);
@@ -3174,8 +3425,13 @@ static int pyrf_session__init(struct pyrf_session *psession, PyObject *args, PyO
} while (0)
ADD_TOOL(sample);
+ ADD_TOOL(stat);
#undef ADD_TOOL
+ if (stat)
+ psession->tool.stat_round = pyrf_session_tool__stat_round;
+
+
psession->tool.comm = perf_event__process_comm;
psession->tool.mmap = perf_event__process_mmap;
psession->tool.mmap2 = perf_event__process_mmap2;
@@ -3217,6 +3473,7 @@ static void pyrf_session__delete(struct pyrf_session *psession)
{
Py_XDECREF(psession->pdata);
Py_XDECREF(psession->sample);
+ Py_XDECREF(psession->stat);
perf_session__delete(psession->session);
Py_TYPE(psession)->tp_free((PyObject *)psession);
}
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 21/59] perf python: Expose brstack in sample event
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Implement pyrf_branch_entry and pyrf_branch_stack for lazy
iteration over branch stack entries.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2:
1. Avoided Keyword Collision: Renamed the properties "from" and "to"
to "from_ip" and "to_ip" in pyrf_branch_entry__getset[] to prevent
syntax errors in Python.
2. Eager Branch Stack Resolution: Updated pyrf_session_tool__sample()
to allocate and copy the branch stack entries eagerly when the
event is processed, instead of deferring it to iteration time. This
avoids reading from potentially overwritten or unmapped mmap
buffers.
3. Updated Iterators: Updated pyrf_branch_stack and
pyrf_branch_stack__next() to use the copied entries rather than
pointing directly to the sample's buffer.
4. Avoided Reference Leak on Init Failure: Added proper error checking
for PyModule_AddObject() in the module initialization function,
decrementing references on failure.
v6:
- Moved branch stack resolution from `session_tool__sample` to
`pyrf_event__new`.
---
tools/perf/util/python.c | 188 +++++++++++++++++++++++++++++++++++++++
1 file changed, 188 insertions(+)
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 17d0ee15336f..256129fef4f8 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -19,6 +19,7 @@
#include "dso.h"
#include "dwarf-regs.h"
#include "event.h"
+#include "branch.h"
#include "evlist.h"
#include "evsel.h"
#include "expr.h"
@@ -69,6 +70,8 @@ struct pyrf_event {
bool al_resolved;
/** @callchain: Resolved callchain, eagerly computed if requested. */
PyObject *callchain;
+ /** @brstack: Resolved branch stack, eagerly computed if requested. */
+ PyObject *brstack;
/** @event: The underlying perf_event that may be in a file or ring buffer. */
union perf_event event;
};
@@ -107,6 +110,7 @@ static void pyrf_event__delete(struct pyrf_event *pevent)
if (pevent->al_resolved)
addr_location__exit(&pevent->al);
Py_XDECREF(pevent->callchain);
+ Py_XDECREF(pevent->brstack);
perf_sample__exit(&pevent->sample);
Py_TYPE(pevent)->tp_free((PyObject *)pevent);
}
@@ -871,6 +875,144 @@ static PyObject *pyrf_sample_event__get_callchain(PyObject *self, void *closure
return pevent->callchain;
}
+struct pyrf_branch_entry {
+ PyObject_HEAD
+ u64 from;
+ u64 to;
+ struct branch_flags flags;
+};
+
+static void pyrf_branch_entry__delete(struct pyrf_branch_entry *pentry)
+{
+ Py_TYPE(pentry)->tp_free((PyObject *)pentry);
+}
+
+static PyObject *pyrf_branch_entry__get_from(struct pyrf_branch_entry *pentry,
+ void *closure __maybe_unused)
+{
+ return PyLong_FromUnsignedLongLong(pentry->from);
+}
+
+static PyObject *pyrf_branch_entry__get_to(struct pyrf_branch_entry *pentry,
+ void *closure __maybe_unused)
+{
+ return PyLong_FromUnsignedLongLong(pentry->to);
+}
+
+static PyObject *pyrf_branch_entry__get_mispred(struct pyrf_branch_entry *pentry,
+ void *closure __maybe_unused)
+{
+ return PyBool_FromLong(pentry->flags.mispred);
+}
+
+static PyObject *pyrf_branch_entry__get_predicted(struct pyrf_branch_entry *pentry,
+ void *closure __maybe_unused)
+{
+ return PyBool_FromLong(pentry->flags.predicted);
+}
+
+static PyObject *pyrf_branch_entry__get_in_tx(struct pyrf_branch_entry *pentry,
+ void *closure __maybe_unused)
+{
+ return PyBool_FromLong(pentry->flags.in_tx);
+}
+
+static PyObject *pyrf_branch_entry__get_abort(struct pyrf_branch_entry *pentry,
+ void *closure __maybe_unused)
+{
+ return PyBool_FromLong(pentry->flags.abort);
+}
+
+static PyObject *pyrf_branch_entry__get_cycles(struct pyrf_branch_entry *pentry,
+ void *closure __maybe_unused)
+{
+ return PyLong_FromUnsignedLongLong(pentry->flags.cycles);
+}
+
+static PyObject *pyrf_branch_entry__get_type(struct pyrf_branch_entry *pentry,
+ void *closure __maybe_unused)
+{
+ return PyLong_FromLong(pentry->flags.type);
+}
+
+static PyGetSetDef pyrf_branch_entry__getset[] = {
+ { .name = "from_ip", .get = (getter)pyrf_branch_entry__get_from, },
+ { .name = "to_ip", .get = (getter)pyrf_branch_entry__get_to, },
+ { .name = "mispred", .get = (getter)pyrf_branch_entry__get_mispred, },
+ { .name = "predicted", .get = (getter)pyrf_branch_entry__get_predicted, },
+ { .name = "in_tx", .get = (getter)pyrf_branch_entry__get_in_tx, },
+ { .name = "abort", .get = (getter)pyrf_branch_entry__get_abort, },
+ { .name = "cycles", .get = (getter)pyrf_branch_entry__get_cycles, },
+ { .name = "type", .get = (getter)pyrf_branch_entry__get_type, },
+ { .name = NULL, },
+};
+
+static PyTypeObject pyrf_branch_entry__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.branch_entry",
+ .tp_basicsize = sizeof(struct pyrf_branch_entry),
+ .tp_dealloc = (destructor)pyrf_branch_entry__delete,
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = "perf branch entry object.",
+ .tp_getset = pyrf_branch_entry__getset,
+};
+
+struct pyrf_branch_stack {
+ PyObject_HEAD
+ struct pyrf_event *pevent;
+ struct branch_entry *entries;
+ u64 nr;
+ u64 pos;
+};
+
+static void pyrf_branch_stack__delete(struct pyrf_branch_stack *pstack)
+{
+ Py_XDECREF(pstack->pevent);
+ free(pstack->entries);
+ Py_TYPE(pstack)->tp_free((PyObject *)pstack);
+}
+
+static PyObject *pyrf_branch_stack__next(struct pyrf_branch_stack *pstack)
+{
+ struct pyrf_branch_entry *pentry;
+
+ if (pstack->pos >= pstack->nr)
+ return NULL;
+
+ pentry = PyObject_New(struct pyrf_branch_entry, &pyrf_branch_entry__type);
+ if (!pentry)
+ return NULL;
+
+ pentry->from = pstack->entries[pstack->pos].from;
+ pentry->to = pstack->entries[pstack->pos].to;
+ pentry->flags = pstack->entries[pstack->pos].flags;
+
+ pstack->pos++;
+ return (PyObject *)pentry;
+}
+
+static PyTypeObject pyrf_branch_stack__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.branch_stack",
+ .tp_basicsize = sizeof(struct pyrf_branch_stack),
+ .tp_dealloc = (destructor)pyrf_branch_stack__delete,
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = "perf branch stack object.",
+ .tp_iter = PyObject_SelfIter,
+ .tp_iternext = (iternextfunc)pyrf_branch_stack__next,
+};
+
+static PyObject *pyrf_sample_event__get_brstack(PyObject *self, void *closure __maybe_unused)
+{
+ struct pyrf_event *pevent = (void *)self;
+
+ if (!pevent->brstack)
+ Py_RETURN_NONE;
+
+ Py_INCREF(pevent->brstack);
+ return pevent->brstack;
+}
+
static PyObject*
pyrf_sample_event__getattro(struct pyrf_event *pevent, PyObject *attr_name)
{
@@ -891,6 +1033,12 @@ static PyGetSetDef pyrf_sample_event__getset[] = {
.set = NULL,
.doc = "event callchain.",
},
+ {
+ .name = "brstack",
+ .get = pyrf_sample_event__get_brstack,
+ .set = NULL,
+ .doc = "event branch stack.",
+ },
{
.name = "raw_buf",
.get = (getter)pyrf_sample_event__get_raw_buf,
@@ -1071,6 +1219,12 @@ static int pyrf_event__setup_types(void)
err = PyType_Ready(&pyrf_callchain__type);
if (err < 0)
goto out;
+ err = PyType_Ready(&pyrf_branch_entry__type);
+ if (err < 0)
+ goto out;
+ err = PyType_Ready(&pyrf_branch_stack__type);
+ if (err < 0)
+ goto out;
out:
return err;
}
@@ -1251,6 +1405,7 @@ static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *ev
memcpy(&pevent->event, event, event->header.size);
perf_sample__init(&pevent->sample, /*all=*/true);
pevent->callchain = NULL;
+ pevent->brstack = NULL;
pevent->al_resolved = false;
addr_location__init(&pevent->al);
@@ -1307,6 +1462,27 @@ static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *ev
addr_location__exit(&al);
}
}
+ if (sample->branch_stack) {
+ struct branch_stack *bs = sample->branch_stack;
+ struct branch_entry *entries = perf_sample__branch_entries(sample);
+ struct pyrf_branch_stack *pstack;
+
+ pstack = PyObject_New(struct pyrf_branch_stack, &pyrf_branch_stack__type);
+ if (pstack) {
+ Py_INCREF(pevent);
+ pstack->pevent = pevent;
+ pstack->pos = 0;
+ pstack->nr = bs->nr;
+ pstack->entries = calloc(bs->nr, sizeof(struct branch_entry));
+ if (pstack->entries) {
+ memcpy(pstack->entries, entries,
+ bs->nr * sizeof(struct branch_entry));
+ pevent->brstack = (PyObject *)pstack;
+ } else {
+ Py_DECREF(pstack);
+ }
+ }
+ }
return (PyObject *)pevent;
}
@@ -3708,6 +3884,18 @@ PyMODINIT_FUNC PyInit_perf(void)
Py_INCREF(&pyrf_session__type);
PyModule_AddObject(module, "session", (PyObject *)&pyrf_session__type);
+ Py_INCREF(&pyrf_branch_entry__type);
+ if (PyModule_AddObject(module, "branch_entry", (PyObject *)&pyrf_branch_entry__type) < 0) {
+ Py_DECREF(&pyrf_branch_entry__type);
+ goto error;
+ }
+
+ Py_INCREF(&pyrf_branch_stack__type);
+ if (PyModule_AddObject(module, "branch_stack", (PyObject *)&pyrf_branch_stack__type) < 0) {
+ Py_DECREF(&pyrf_branch_stack__type);
+ goto error;
+ }
+
dict = PyModule_GetDict(module);
if (dict == NULL)
goto error;
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 19/59] perf python: Add config file access
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Add perf.config_get(name) to expose the perf configuration system.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/util/python.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 2953c4c8e142..5478561ca62c 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -12,6 +12,7 @@
#include "build-id.h"
#include "callchain.h"
#include "comm.h"
+#include "config.h"
#include "counts.h"
#include "data.h"
#include "debug.h"
@@ -3296,7 +3297,26 @@ static PyObject *pyrf__syscall_id(PyObject *self, PyObject *args, PyObject *kwar
return PyLong_FromLong(id);
}
+static PyObject *pyrf__config_get(PyObject *self, PyObject *args)
+{
+ const char *config_name, *val;
+
+ if (!PyArg_ParseTuple(args, "s", &config_name))
+ return NULL;
+
+ val = perf_config_get(config_name);
+ if (!val)
+ Py_RETURN_NONE;
+ return PyUnicode_FromString(val);
+}
+
static PyMethodDef perf__methods[] = {
+ {
+ .ml_name = "config_get",
+ .ml_meth = (PyCFunction) pyrf__config_get,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = PyDoc_STR("Get a perf config value.")
+ },
{
.ml_name = "metrics",
.ml_meth = (PyCFunction) pyrf__metrics,
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 18/59] perf python: Add callchain support
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Implement pyrf_callchain_node and pyrf_callchain types for lazy
iteration over callchain frames. Add callchain property to
sample_event.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2:
1. Eager Callchain Resolution: Moved the callchain resolution from
deferred iteration to eager processing in
pyrf_session_tool__sample() . This avoids risks of reading from
unmapped memory or following dangling pointers to closed sessions.
2. Cached Callchain: Added a callchain field to struct pyrf_event to
store the resolved object.
3. Simplified Access: pyrf_sample_event__get_callchain() now just
returns the cached object if available.
4. Avoided Double Free: Handled lazy cleanups properly.
v6:
- Moved callchain resolution from `session_tool__sample` to
`pyrf_event__new`.
---
tools/perf/util/python.c | 241 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 240 insertions(+), 1 deletion(-)
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 824cf58645e0..2953c4c8e142 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -66,6 +66,8 @@ struct pyrf_event {
struct addr_location al;
/** @al_resolved: True when machine__resolve been called. */
bool al_resolved;
+ /** @callchain: Resolved callchain, eagerly computed if requested. */
+ PyObject *callchain;
/** @event: The underlying perf_event that may be in a file or ring buffer. */
union perf_event event;
};
@@ -103,6 +105,7 @@ static void pyrf_event__delete(struct pyrf_event *pevent)
{
if (pevent->al_resolved)
addr_location__exit(&pevent->al);
+ Py_XDECREF(pevent->callchain);
perf_sample__exit(&pevent->sample);
Py_TYPE(pevent)->tp_free((PyObject *)pevent);
}
@@ -621,6 +624,181 @@ static PyObject *pyrf_sample_event__insn(PyObject *self, PyObject *args __maybe_
pevent->sample.insn_len);
}
+struct pyrf_callchain_node {
+ PyObject_HEAD
+ u64 ip;
+ struct map *map;
+ struct symbol *sym;
+};
+
+static void pyrf_callchain_node__delete(struct pyrf_callchain_node *pnode)
+{
+ map__put(pnode->map);
+ Py_TYPE(pnode)->tp_free((PyObject*)pnode);
+}
+
+static PyObject *pyrf_callchain_node__get_ip(struct pyrf_callchain_node *pnode,
+ void *closure __maybe_unused)
+{
+ return PyLong_FromUnsignedLongLong(pnode->ip);
+}
+
+static PyObject *pyrf_callchain_node__get_symbol(struct pyrf_callchain_node *pnode,
+ void *closure __maybe_unused)
+{
+ if (pnode->sym)
+ return PyUnicode_FromString(pnode->sym->name);
+ return PyUnicode_FromString("[unknown]");
+}
+
+static PyObject *pyrf_callchain_node__get_dso(struct pyrf_callchain_node *pnode,
+ void *closure __maybe_unused)
+{
+ const char *dsoname = "[unknown]";
+
+ if (pnode->map) {
+ struct dso *dso = map__dso(pnode->map);
+ if (dso) {
+ if (symbol_conf.show_kernel_path && dso__long_name(dso))
+ dsoname = dso__long_name(dso);
+ else
+ dsoname = dso__name(dso);
+ }
+ }
+ return PyUnicode_FromString(dsoname);
+}
+
+static PyGetSetDef pyrf_callchain_node__getset[] = {
+ { .name = "ip", .get = (getter)pyrf_callchain_node__get_ip, },
+ { .name = "symbol", .get = (getter)pyrf_callchain_node__get_symbol, },
+ { .name = "dso", .get = (getter)pyrf_callchain_node__get_dso, },
+ { .name = NULL, },
+};
+
+static PyTypeObject pyrf_callchain_node__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.callchain_node",
+ .tp_basicsize = sizeof(struct pyrf_callchain_node),
+ .tp_dealloc = (destructor)pyrf_callchain_node__delete,
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = "perf callchain node object.",
+ .tp_getset = pyrf_callchain_node__getset,
+};
+
+struct pyrf_callchain_frame {
+ u64 ip;
+ struct map *map;
+ struct symbol *sym;
+};
+
+struct pyrf_callchain {
+ PyObject_HEAD
+ struct pyrf_event *pevent;
+ struct pyrf_callchain_frame *frames;
+ u64 nr_frames;
+ u64 pos;
+ bool resolved;
+};
+
+static void pyrf_callchain__delete(struct pyrf_callchain *pchain)
+{
+ Py_XDECREF(pchain->pevent);
+ if (pchain->frames) {
+ for (u64 i = 0; i < pchain->nr_frames; i++)
+ map__put(pchain->frames[i].map);
+ free(pchain->frames);
+ }
+ Py_TYPE(pchain)->tp_free((PyObject*)pchain);
+}
+
+static PyObject *pyrf_callchain__next(struct pyrf_callchain *pchain)
+{
+ struct pyrf_callchain_node *pnode;
+
+ if (!pchain->resolved) {
+ struct evsel *evsel = pchain->pevent->sample.evsel;
+ struct evlist *evlist = evsel->evlist;
+ struct perf_session *session = evlist ? evlist__session(evlist) : NULL;
+ struct addr_location al;
+ struct callchain_cursor *cursor;
+ struct callchain_cursor_node *node;
+ u64 i;
+
+ if (!session || !pchain->pevent->sample.callchain)
+ return NULL;
+
+ addr_location__init(&al);
+ if (machine__resolve(&session->machines.host, &al, &pchain->pevent->sample) < 0) {
+ addr_location__exit(&al);
+ return NULL;
+ }
+
+ cursor = get_tls_callchain_cursor();
+ if (thread__resolve_callchain(al.thread, cursor, evsel,
+ &pchain->pevent->sample, NULL, NULL,
+ PERF_MAX_STACK_DEPTH) != 0) {
+ addr_location__exit(&al);
+ return NULL;
+ }
+ callchain_cursor_commit(cursor);
+
+ pchain->nr_frames = cursor->nr;
+ if (pchain->nr_frames > 0) {
+ pchain->frames = calloc(pchain->nr_frames, sizeof(*pchain->frames));
+ if (!pchain->frames) {
+ addr_location__exit(&al);
+ return PyErr_NoMemory();
+ }
+
+ for (i = 0; i < pchain->nr_frames; i++) {
+ node = callchain_cursor_current(cursor);
+ pchain->frames[i].ip = node->ip;
+ pchain->frames[i].map = map__get(node->ms.map);
+ pchain->frames[i].sym = node->ms.sym;
+ callchain_cursor_advance(cursor);
+ }
+ }
+ pchain->resolved = true;
+ addr_location__exit(&al);
+ }
+
+ if (pchain->pos >= pchain->nr_frames)
+ return NULL;
+
+ pnode = PyObject_New(struct pyrf_callchain_node, &pyrf_callchain_node__type);
+ if (!pnode)
+ return NULL;
+
+ pnode->ip = pchain->frames[pchain->pos].ip;
+ pnode->map = map__get(pchain->frames[pchain->pos].map);
+ pnode->sym = pchain->frames[pchain->pos].sym;
+
+ pchain->pos++;
+ return (PyObject *)pnode;
+}
+
+static PyTypeObject pyrf_callchain__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.callchain",
+ .tp_basicsize = sizeof(struct pyrf_callchain),
+ .tp_dealloc = (destructor)pyrf_callchain__delete,
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = "perf callchain object.",
+ .tp_iter = PyObject_SelfIter,
+ .tp_iternext = (iternextfunc)pyrf_callchain__next,
+};
+
+static PyObject *pyrf_sample_event__get_callchain(PyObject *self, void *closure __maybe_unused)
+{
+ struct pyrf_event *pevent = (void *)self;
+
+ if (!pevent->callchain)
+ Py_RETURN_NONE;
+
+ Py_INCREF(pevent->callchain);
+ return pevent->callchain;
+}
+
static PyObject*
pyrf_sample_event__getattro(struct pyrf_event *pevent, PyObject *attr_name)
{
@@ -635,6 +813,12 @@ pyrf_sample_event__getattro(struct pyrf_event *pevent, PyObject *attr_name)
}
static PyGetSetDef pyrf_sample_event__getset[] = {
+ {
+ .name = "callchain",
+ .get = pyrf_sample_event__get_callchain,
+ .set = NULL,
+ .doc = "event callchain.",
+ },
{
.name = "raw_buf",
.get = (getter)pyrf_sample_event__get_raw_buf,
@@ -803,6 +987,12 @@ static int pyrf_event__setup_types(void)
err = PyType_Ready(&pyrf_context_switch_event__type);
if (err < 0)
goto out;
+ err = PyType_Ready(&pyrf_callchain_node__type);
+ if (err < 0)
+ goto out;
+ err = PyType_Ready(&pyrf_callchain__type);
+ if (err < 0)
+ goto out;
out:
return err;
}
@@ -822,10 +1012,12 @@ static PyTypeObject *pyrf_event__type[] = {
};
static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *evsel,
- struct perf_session *session __maybe_unused)
+ struct perf_session *session)
{
struct pyrf_event *pevent;
PyTypeObject *ptype;
+ struct perf_sample *sample;
+ struct machine *machine = session ? &session->machines.host : NULL;
size_t size;
int err;
size_t min_size = sizeof(struct perf_event_header);
@@ -883,6 +1075,7 @@ static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *ev
PyObject_Init((PyObject *)pevent, ptype);
memcpy(&pevent->event, event, event->header.size);
perf_sample__init(&pevent->sample, /*all=*/true);
+ pevent->callchain = NULL;
pevent->al_resolved = false;
addr_location__init(&pevent->al);
@@ -896,6 +1089,49 @@ static PyObject *pyrf_event__new(const union perf_event *event, struct evsel *ev
return PyErr_Format(PyExc_OSError,
"perf: can't parse sample, err=%d", err);
}
+ sample = &pevent->sample;
+ if (machine && sample->callchain) {
+ struct addr_location al;
+ struct callchain_cursor *cursor;
+ u64 i;
+ struct pyrf_callchain *pchain;
+
+ addr_location__init(&al);
+ if (machine__resolve(machine, &al, sample) >= 0) {
+ cursor = get_tls_callchain_cursor();
+ if (thread__resolve_callchain(al.thread, cursor, evsel, sample,
+ NULL, NULL, PERF_MAX_STACK_DEPTH) == 0) {
+ callchain_cursor_commit(cursor);
+
+ pchain = PyObject_New(struct pyrf_callchain, &pyrf_callchain__type);
+ if (pchain) {
+ pchain->pevent = pevent;
+ Py_INCREF(pevent);
+ pchain->nr_frames = cursor->nr;
+ pchain->pos = 0;
+ pchain->resolved = true;
+ pchain->frames = calloc(pchain->nr_frames,
+ sizeof(*pchain->frames));
+ if (pchain->frames) {
+ struct callchain_cursor_node *node;
+
+ for (i = 0; i < pchain->nr_frames; i++) {
+ node = callchain_cursor_current(cursor);
+ pchain->frames[i].ip = node->ip;
+ pchain->frames[i].map =
+ map__get(node->ms.map);
+ pchain->frames[i].sym = node->ms.sym;
+ callchain_cursor_advance(cursor);
+ }
+ pevent->callchain = (PyObject *)pchain;
+ } else {
+ Py_DECREF(pchain);
+ }
+ }
+ }
+ addr_location__exit(&al);
+ }
+ }
return (PyObject *)pevent;
}
@@ -2959,6 +3195,9 @@ static int pyrf_session__init(struct pyrf_session *psession, PyObject *args, PyO
return -1;
}
+ symbol_conf.use_callchain = true;
+ symbol_conf.show_kernel_path = true;
+ symbol_conf.inline_name = false;
if (symbol__init(perf_session__env(psession->session)) < 0) {
perf_session__delete(psession->session);
psession->session = NULL;
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 16/59] perf python: Add syscall name/id to convert syscall number and name
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
Use perf's syscalltbl support to convert syscall number to name
assuming the number is for the host machine. This avoids python
libaudit support as tools/perf/scripts/python/syscall-counts.py
requires.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2:
1. Guarded with HAVE_LIBTRACEEVENT : Wrapped the syscall functions and
their entries in perf__methods with #ifdef HAVE_LIBTRACEEVENT to
avoid potential linker errors if CONFIG_TRACE is disabled.
2. Changed Exception Type: Updated pyrf__syscall_id to raise a
ValueError instead of a TypeError when a syscall is not found.
3. Fixed Typo: Corrected "name number" to "name" in the docstring for
syscall_id.
v6:
- Added optional keyword-only `elf_machine` argument to `syscall_name`
and `syscall_id`.
v7:
- Made syscalltbl.o unconditional in Build to fix undefined symbol
when building without libtraceevent.
---
tools/perf/util/Build | 2 +-
tools/perf/util/python.c | 48 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 49 insertions(+), 1 deletion(-)
diff --git a/tools/perf/util/Build b/tools/perf/util/Build
index 70cc91d00804..fd55d02dd433 100644
--- a/tools/perf/util/Build
+++ b/tools/perf/util/Build
@@ -75,7 +75,7 @@ perf-util-y += sample.o
perf-util-y += sample-raw.o
perf-util-y += s390-sample-raw.o
perf-util-y += amd-sample-raw.o
-perf-util-$(CONFIG_TRACE) += syscalltbl.o
+perf-util-y += syscalltbl.o
perf-util-y += ordered-events.o
perf-util-y += namespaces.o
perf-util-y += comm.o
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 1af53480661f..861973144106 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -13,6 +13,7 @@
#include "counts.h"
#include "data.h"
#include "debug.h"
+#include "dwarf-regs.h"
#include "event.h"
#include "evlist.h"
#include "evsel.h"
@@ -25,6 +26,7 @@
#include "session.h"
#include "strbuf.h"
#include "symbol.h"
+#include "syscalltbl.h"
#include "thread.h"
#include "thread_map.h"
#include "tool.h"
@@ -2669,6 +2671,40 @@ static int pyrf_session__setup_types(void)
return PyType_Ready(&pyrf_session__type);
}
+static PyObject *pyrf__syscall_name(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char *name;
+ int id;
+ int elf_machine = EM_HOST;
+ static char * const kwlist[] = { "id", "elf_machine", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|$i", kwlist, &id, &elf_machine))
+ return NULL;
+
+ name = syscalltbl__name(elf_machine, id);
+ if (!name)
+ Py_RETURN_NONE;
+ return PyUnicode_FromString(name);
+}
+
+static PyObject *pyrf__syscall_id(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char *name;
+ int id;
+ int elf_machine = EM_HOST;
+ static char * const kwlist[] = { "name", "elf_machine", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|$i", kwlist, &name, &elf_machine))
+ return NULL;
+
+ id = syscalltbl__id(elf_machine, name);
+ if (id < 0) {
+ PyErr_Format(PyExc_ValueError, "Failed to find syscall %s", name);
+ return NULL;
+ }
+ return PyLong_FromLong(id);
+}
+
static PyMethodDef perf__methods[] = {
{
.ml_name = "metrics",
@@ -2702,6 +2738,18 @@ static PyMethodDef perf__methods[] = {
.ml_flags = METH_NOARGS,
.ml_doc = PyDoc_STR("Returns a sequence of pmus.")
},
+ {
+ .ml_name = "syscall_name",
+ .ml_meth = (PyCFunction) pyrf__syscall_name,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
+ .ml_doc = PyDoc_STR("Turns a syscall number to a string.")
+ },
+ {
+ .ml_name = "syscall_id",
+ .ml_meth = (PyCFunction) pyrf__syscall_id,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
+ .ml_doc = PyDoc_STR("Turns a syscall name to a number.")
+ },
{ .ml_name = NULL, }
};
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
* [PATCH v7 10/59] perf evlist: Add reference count
From: Ian Rogers @ 2026-04-25 22:49 UTC (permalink / raw)
To: acme, adrian.hunter, james.clark, leo.yan, namhyung, tmricht
Cc: alice.mei.rogers, dapeng1.mi, linux-arm-kernel, linux-kernel,
linux-perf-users, mingo, peterz, Ian Rogers
In-Reply-To: <20260425224951.174663-1-irogers@google.com>
This a no-op for most of the perf tool. The reference count is set to
1 at allocation, the put will see the 1, decrement it and perform the
delete. The purpose for adding the reference count is for the python
code. Prior to this change the python code would clone evlists, but
this has issues if events are opened, etc. This change adds a
reference count for the evlists and a later change will add it to
evsels. The combination is needed for the python code to operate
correctly (not hit asserts in the evsel clone), but the changes are
broken apart for the sake of smaller patches.
Assisted-by: Gemini:gemini-3.1-pro-preview
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2: Added evlist__put to pyrf_evlist__init in case init is called more
than once.
I double-checked trace__replay() and confirmed that trace->evlist
is not assigned to session->evlist in that function.
trace__replay creates a new session and uses its own evlist for
processing file events, leaving trace->evlist pointing to the
empty list created at startup. Therefore, the
evlist__put(trace->evlist) call in trace__exit() is safe and
correct to avoid leaking that empty list.
v7:
- Added pyrf_evlist__new to zero-initialize pevlist->evlist to fix
crash on re-initialization.
---
tools/perf/arch/x86/tests/hybrid.c | 2 +-
tools/perf/arch/x86/tests/topdown.c | 2 +-
tools/perf/arch/x86/util/iostat.c | 2 +-
tools/perf/bench/evlist-open-close.c | 18 +-
tools/perf/builtin-ftrace.c | 8 +-
tools/perf/builtin-kvm.c | 4 +-
tools/perf/builtin-lock.c | 2 +-
tools/perf/builtin-record.c | 4 +-
tools/perf/builtin-sched.c | 6 +-
tools/perf/builtin-script.c | 2 +-
tools/perf/builtin-stat.c | 10 +-
tools/perf/builtin-top.c | 52 ++---
tools/perf/builtin-trace.c | 26 +--
tools/perf/tests/backward-ring-buffer.c | 18 +-
tools/perf/tests/code-reading.c | 4 +-
tools/perf/tests/event-times.c | 4 +-
tools/perf/tests/event_update.c | 2 +-
tools/perf/tests/evsel-roundtrip-name.c | 8 +-
tools/perf/tests/expand-cgroup.c | 8 +-
tools/perf/tests/hists_cumulate.c | 2 +-
tools/perf/tests/hists_filter.c | 2 +-
tools/perf/tests/hists_link.c | 2 +-
tools/perf/tests/hists_output.c | 2 +-
tools/perf/tests/hwmon_pmu.c | 2 +-
tools/perf/tests/keep-tracking.c | 2 +-
tools/perf/tests/mmap-basic.c | 18 +-
tools/perf/tests/openat-syscall-tp-fields.c | 18 +-
tools/perf/tests/parse-events.c | 4 +-
tools/perf/tests/parse-metric.c | 4 +-
tools/perf/tests/parse-no-sample-id-all.c | 2 +-
tools/perf/tests/perf-record.c | 18 +-
tools/perf/tests/perf-time-to-tsc.c | 2 +-
tools/perf/tests/pfm.c | 4 +-
tools/perf/tests/pmu-events.c | 6 +-
tools/perf/tests/pmu.c | 4 +-
tools/perf/tests/sw-clock.c | 14 +-
tools/perf/tests/switch-tracking.c | 2 +-
tools/perf/tests/task-exit.c | 14 +-
tools/perf/tests/tool_pmu.c | 2 +-
tools/perf/tests/topology.c | 2 +-
tools/perf/util/cgroup.c | 4 +-
tools/perf/util/data-convert-bt.c | 2 +-
tools/perf/util/evlist.c | 20 +-
tools/perf/util/evlist.h | 7 +-
tools/perf/util/expr.c | 2 +-
| 12 +-
tools/perf/util/metricgroup.c | 6 +-
tools/perf/util/parse-events.c | 4 +-
tools/perf/util/perf_api_probe.c | 2 +-
tools/perf/util/python.c | 212 ++++++++------------
tools/perf/util/record.c | 2 +-
tools/perf/util/session.c | 2 +-
tools/perf/util/sideband_evlist.c | 16 +-
53 files changed, 279 insertions(+), 320 deletions(-)
diff --git a/tools/perf/arch/x86/tests/hybrid.c b/tools/perf/arch/x86/tests/hybrid.c
index e221ea104174..dfb0ffc0d030 100644
--- a/tools/perf/arch/x86/tests/hybrid.c
+++ b/tools/perf/arch/x86/tests/hybrid.c
@@ -268,7 +268,7 @@ static int test_event(const struct evlist_test *e)
ret = e->check(evlist);
}
parse_events_error__exit(&err);
- evlist__delete(evlist);
+ evlist__put(evlist);
return ret;
}
diff --git a/tools/perf/arch/x86/tests/topdown.c b/tools/perf/arch/x86/tests/topdown.c
index 3ee4e5e71be3..2d0ae5b0d76c 100644
--- a/tools/perf/arch/x86/tests/topdown.c
+++ b/tools/perf/arch/x86/tests/topdown.c
@@ -56,7 +56,7 @@ static int event_cb(void *state, struct pmu_event_info *info)
*ret = TEST_FAIL;
}
}
- evlist__delete(evlist);
+ evlist__put(evlist);
return 0;
}
diff --git a/tools/perf/arch/x86/util/iostat.c b/tools/perf/arch/x86/util/iostat.c
index 7442a2cd87ed..e0417552b0cb 100644
--- a/tools/perf/arch/x86/util/iostat.c
+++ b/tools/perf/arch/x86/util/iostat.c
@@ -337,7 +337,7 @@ int iostat_prepare(struct evlist *evlist, struct perf_stat_config *config)
if (evlist->core.nr_entries > 0) {
pr_warning("The -e and -M options are not supported."
"All chosen events/metrics will be dropped\n");
- evlist__delete(evlist);
+ evlist__put(evlist);
evlist = evlist__new();
if (!evlist)
return -ENOMEM;
diff --git a/tools/perf/bench/evlist-open-close.c b/tools/perf/bench/evlist-open-close.c
index faf9c34b4a5d..304929d1f67f 100644
--- a/tools/perf/bench/evlist-open-close.c
+++ b/tools/perf/bench/evlist-open-close.c
@@ -76,7 +76,7 @@ static struct evlist *bench__create_evlist(char *evstr, const char *uid_str)
parse_events_error__exit(&err);
pr_err("Run 'perf list' for a list of valid events\n");
ret = 1;
- goto out_delete_evlist;
+ goto out_put_evlist;
}
parse_events_error__exit(&err);
if (uid_str) {
@@ -85,24 +85,24 @@ static struct evlist *bench__create_evlist(char *evstr, const char *uid_str)
if (uid == UINT_MAX) {
pr_err("Invalid User: %s", uid_str);
ret = -EINVAL;
- goto out_delete_evlist;
+ goto out_put_evlist;
}
ret = parse_uid_filter(evlist, uid);
if (ret)
- goto out_delete_evlist;
+ goto out_put_evlist;
}
ret = evlist__create_maps(evlist, &opts.target);
if (ret < 0) {
pr_err("Not enough memory to create thread/cpu maps\n");
- goto out_delete_evlist;
+ goto out_put_evlist;
}
evlist__config(evlist, &opts, NULL);
return evlist;
-out_delete_evlist:
- evlist__delete(evlist);
+out_put_evlist:
+ evlist__put(evlist);
return NULL;
}
@@ -151,7 +151,7 @@ static int bench_evlist_open_close__run(char *evstr, const char *uid_str)
evlist->core.nr_entries, evlist__count_evsel_fds(evlist));
printf(" Number of iterations:\t%d\n", iterations);
- evlist__delete(evlist);
+ evlist__put(evlist);
for (i = 0; i < iterations; i++) {
pr_debug("Started iteration %d\n", i);
@@ -162,7 +162,7 @@ static int bench_evlist_open_close__run(char *evstr, const char *uid_str)
gettimeofday(&start, NULL);
err = bench__do_evlist_open_close(evlist);
if (err) {
- evlist__delete(evlist);
+ evlist__put(evlist);
return err;
}
@@ -171,7 +171,7 @@ static int bench_evlist_open_close__run(char *evstr, const char *uid_str)
runtime_us = timeval2usec(&diff);
update_stats(&time_stats, runtime_us);
- evlist__delete(evlist);
+ evlist__put(evlist);
pr_debug("Iteration %d took:\t%" PRIu64 "us\n", i, runtime_us);
}
diff --git a/tools/perf/builtin-ftrace.c b/tools/perf/builtin-ftrace.c
index 8a7dbfb14535..676239148b87 100644
--- a/tools/perf/builtin-ftrace.c
+++ b/tools/perf/builtin-ftrace.c
@@ -1999,20 +1999,20 @@ int cmd_ftrace(int argc, const char **argv)
ret = evlist__create_maps(ftrace.evlist, &ftrace.target);
if (ret < 0)
- goto out_delete_evlist;
+ goto out_put_evlist;
if (argc) {
ret = evlist__prepare_workload(ftrace.evlist, &ftrace.target,
argv, false,
ftrace__workload_exec_failed_signal);
if (ret < 0)
- goto out_delete_evlist;
+ goto out_put_evlist;
}
ret = cmd_func(&ftrace);
-out_delete_evlist:
- evlist__delete(ftrace.evlist);
+out_put_evlist:
+ evlist__put(ftrace.evlist);
out_delete_filters:
delete_filter_func(&ftrace.filters);
diff --git a/tools/perf/builtin-kvm.c b/tools/perf/builtin-kvm.c
index 0c5e6b3aac74..d88855e3c7b4 100644
--- a/tools/perf/builtin-kvm.c
+++ b/tools/perf/builtin-kvm.c
@@ -1811,7 +1811,7 @@ static struct evlist *kvm_live_event_list(void)
out:
if (err) {
- evlist__delete(evlist);
+ evlist__put(evlist);
evlist = NULL;
}
@@ -1942,7 +1942,7 @@ static int kvm_events_live(struct perf_kvm_stat *kvm,
out:
perf_session__delete(kvm->session);
kvm->session = NULL;
- evlist__delete(kvm->evlist);
+ evlist__put(kvm->evlist);
return err;
}
diff --git a/tools/perf/builtin-lock.c b/tools/perf/builtin-lock.c
index 5585aeb97684..c40d070f6c36 100644
--- a/tools/perf/builtin-lock.c
+++ b/tools/perf/builtin-lock.c
@@ -2145,7 +2145,7 @@ static int __cmd_contention(int argc, const char **argv)
out_delete:
lock_filter_finish();
- evlist__delete(con.evlist);
+ evlist__put(con.evlist);
lock_contention_finish(&con);
perf_session__delete(session);
perf_env__exit(&host_env);
diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
index 4a5eba498c02..b4fffa936e01 100644
--- a/tools/perf/builtin-record.c
+++ b/tools/perf/builtin-record.c
@@ -4289,7 +4289,7 @@ int cmd_record(int argc, const char **argv)
goto out;
evlist__splice_list_tail(rec->evlist, &def_evlist->core.entries);
- evlist__delete(def_evlist);
+ evlist__put(def_evlist);
}
if (rec->opts.target.tid && !rec->opts.no_inherit_set)
@@ -4397,7 +4397,7 @@ int cmd_record(int argc, const char **argv)
auxtrace_record__free(rec->itr);
out_opts:
evlist__close_control(rec->opts.ctl_fd, rec->opts.ctl_fd_ack, &rec->opts.ctl_fd_close);
- evlist__delete(rec->evlist);
+ evlist__put(rec->evlist);
return err;
}
diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
index 555247568e7a..d683642ab4e0 100644
--- a/tools/perf/builtin-sched.c
+++ b/tools/perf/builtin-sched.c
@@ -3829,7 +3829,7 @@ static int perf_sched__schedstat_record(struct perf_sched *sched,
session = perf_session__new(&data, &sched->tool);
if (IS_ERR(session)) {
pr_err("Perf session creation failed.\n");
- evlist__delete(evlist);
+ evlist__put(evlist);
return PTR_ERR(session);
}
@@ -3925,7 +3925,7 @@ static int perf_sched__schedstat_record(struct perf_sched *sched,
else
fprintf(stderr, "[ perf sched stats: Failed !! ]\n");
- evlist__delete(evlist);
+ evlist__put(evlist);
close(fd);
return err;
}
@@ -4724,7 +4724,7 @@ static int perf_sched__schedstat_live(struct perf_sched *sched,
free_cpu_domain_info(cd_map, sv, nr);
out:
free_schedstat(&cpu_head);
- evlist__delete(evlist);
+ evlist__put(evlist);
return err;
}
diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
index 853b141a0d50..0ead134940d5 100644
--- a/tools/perf/builtin-script.c
+++ b/tools/perf/builtin-script.c
@@ -2269,7 +2269,7 @@ static int script_find_metrics(const struct pmu_metric *pm,
}
pr_debug("Found metric '%s' whose evsels match those of in the perf data\n",
pm->metric_name);
- evlist__delete(metric_evlist);
+ evlist__put(metric_evlist);
out:
return 0;
}
diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
index 99d7db372b48..bfa3512e1686 100644
--- a/tools/perf/builtin-stat.c
+++ b/tools/perf/builtin-stat.c
@@ -2113,7 +2113,7 @@ static int add_default_events(void)
stat_config.user_requested_cpu_list,
stat_config.system_wide,
stat_config.hardware_aware_grouping) < 0) {
- evlist__delete(metric_evlist);
+ evlist__put(metric_evlist);
ret = -1;
break;
}
@@ -2125,7 +2125,7 @@ static int add_default_events(void)
metricgroup__copy_metric_events(evlist, /*cgrp=*/NULL,
&evlist->metric_events,
&metric_evlist->metric_events);
- evlist__delete(metric_evlist);
+ evlist__put(metric_evlist);
}
list_sort(/*priv=*/NULL, &evlist->core.entries, default_evlist_evsel_cmp);
@@ -2146,7 +2146,7 @@ static int add_default_events(void)
metricgroup__copy_metric_events(evsel_list, /*cgrp=*/NULL,
&evsel_list->metric_events,
&evlist->metric_events);
- evlist__delete(evlist);
+ evlist__put(evlist);
return ret;
}
@@ -2381,7 +2381,7 @@ static int __cmd_report(int argc, const char **argv)
perf_stat.session = session;
stat_config.output = stderr;
- evlist__delete(evsel_list);
+ evlist__put(evsel_list);
evsel_list = session->evlist;
ret = perf_session__process_events(session);
@@ -3060,7 +3060,7 @@ int cmd_stat(int argc, const char **argv)
if (smi_cost && smi_reset)
sysfs__write_int(FREEZE_ON_SMI_PATH, 0);
- evlist__delete(evsel_list);
+ evlist__put(evsel_list);
evlist__close_control(stat_config.ctl_fd, stat_config.ctl_fd_ack, &stat_config.ctl_fd_close);
diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c
index f6eb543de537..c509cfef8285 100644
--- a/tools/perf/builtin-top.c
+++ b/tools/perf/builtin-top.c
@@ -1652,14 +1652,14 @@ int cmd_top(int argc, const char **argv)
perf_env__init(&host_env);
status = perf_config(perf_top_config, &top);
if (status)
- goto out_delete_evlist;
+ goto out_put_evlist;
/*
* Since the per arch annotation init routine may need the cpuid, read
* it here, since we are not getting this from the perf.data header.
*/
status = perf_env__set_cmdline(&host_env, argc, argv);
if (status)
- goto out_delete_evlist;
+ goto out_put_evlist;
status = perf_env__read_cpuid(&host_env);
if (status) {
@@ -1680,30 +1680,30 @@ int cmd_top(int argc, const char **argv)
annotate_opts.disassembler_style = strdup(disassembler_style);
if (!annotate_opts.disassembler_style) {
status = -ENOMEM;
- goto out_delete_evlist;
+ goto out_put_evlist;
}
}
if (objdump_path) {
annotate_opts.objdump_path = strdup(objdump_path);
if (!annotate_opts.objdump_path) {
status = -ENOMEM;
- goto out_delete_evlist;
+ goto out_put_evlist;
}
}
if (addr2line_path) {
symbol_conf.addr2line_path = strdup(addr2line_path);
if (!symbol_conf.addr2line_path) {
status = -ENOMEM;
- goto out_delete_evlist;
+ goto out_put_evlist;
}
}
status = symbol__validate_sym_arguments();
if (status)
- goto out_delete_evlist;
+ goto out_put_evlist;
if (annotate_check_args() < 0)
- goto out_delete_evlist;
+ goto out_put_evlist;
status = target__validate(target);
if (status) {
@@ -1718,15 +1718,15 @@ int cmd_top(int argc, const char **argv)
struct evlist *def_evlist = evlist__new_default(target, callchain_param.enabled);
if (!def_evlist)
- goto out_delete_evlist;
+ goto out_put_evlist;
evlist__splice_list_tail(top.evlist, &def_evlist->core.entries);
- evlist__delete(def_evlist);
+ evlist__put(def_evlist);
}
status = evswitch__init(&top.evswitch, top.evlist, stderr);
if (status)
- goto out_delete_evlist;
+ goto out_put_evlist;
if (symbol_conf.report_hierarchy) {
/* disable incompatible options */
@@ -1737,18 +1737,18 @@ int cmd_top(int argc, const char **argv)
pr_err("Error: --hierarchy and --fields options cannot be used together\n");
parse_options_usage(top_usage, options, "fields", 0);
parse_options_usage(NULL, options, "hierarchy", 0);
- goto out_delete_evlist;
+ goto out_put_evlist;
}
}
if (top.stitch_lbr && !(callchain_param.record_mode == CALLCHAIN_LBR)) {
pr_err("Error: --stitch-lbr must be used with --call-graph lbr\n");
- goto out_delete_evlist;
+ goto out_put_evlist;
}
if (nr_cgroups > 0 && opts->record_cgroup) {
pr_err("--cgroup and --all-cgroups cannot be used together\n");
- goto out_delete_evlist;
+ goto out_put_evlist;
}
if (branch_call_mode) {
@@ -1772,7 +1772,7 @@ int cmd_top(int argc, const char **argv)
status = perf_env__read_core_pmu_caps(&host_env);
if (status) {
pr_err("PMU capability data is not available\n");
- goto out_delete_evlist;
+ goto out_put_evlist;
}
}
@@ -1795,7 +1795,7 @@ int cmd_top(int argc, const char **argv)
if (IS_ERR(top.session)) {
status = PTR_ERR(top.session);
top.session = NULL;
- goto out_delete_evlist;
+ goto out_put_evlist;
}
top.evlist->session = top.session;
@@ -1805,7 +1805,7 @@ int cmd_top(int argc, const char **argv)
if (field_order)
parse_options_usage(sort_order ? NULL : top_usage,
options, "fields", 0);
- goto out_delete_evlist;
+ goto out_put_evlist;
}
if (top.uid_str) {
@@ -1814,18 +1814,18 @@ int cmd_top(int argc, const char **argv)
if (uid == UINT_MAX) {
ui__error("Invalid User: %s", top.uid_str);
status = -EINVAL;
- goto out_delete_evlist;
+ goto out_put_evlist;
}
status = parse_uid_filter(top.evlist, uid);
if (status)
- goto out_delete_evlist;
+ goto out_put_evlist;
}
if (evlist__create_maps(top.evlist, target) < 0) {
ui__error("Couldn't create thread/CPU maps: %s\n",
errno == ENOENT ? "No such process" : str_error_r(errno, errbuf, sizeof(errbuf)));
status = -errno;
- goto out_delete_evlist;
+ goto out_put_evlist;
}
if (top.delay_secs < 1)
@@ -1833,7 +1833,7 @@ int cmd_top(int argc, const char **argv)
if (record_opts__config(opts)) {
status = -EINVAL;
- goto out_delete_evlist;
+ goto out_put_evlist;
}
top.sym_evsel = evlist__first(top.evlist);
@@ -1848,14 +1848,14 @@ int cmd_top(int argc, const char **argv)
status = symbol__annotation_init();
if (status < 0)
- goto out_delete_evlist;
+ goto out_put_evlist;
annotation_config__init();
symbol_conf.try_vmlinux_path = (symbol_conf.vmlinux_name == NULL);
status = symbol__init(NULL);
if (status < 0)
- goto out_delete_evlist;
+ goto out_put_evlist;
sort__setup_elide(stdout);
@@ -1875,13 +1875,13 @@ int cmd_top(int argc, const char **argv)
if (top.sb_evlist == NULL) {
pr_err("Couldn't create side band evlist.\n.");
status = -EINVAL;
- goto out_delete_evlist;
+ goto out_put_evlist;
}
if (evlist__add_bpf_sb_event(top.sb_evlist, &host_env)) {
pr_err("Couldn't ask for PERF_RECORD_BPF_EVENT side band events.\n.");
status = -EINVAL;
- goto out_delete_evlist;
+ goto out_put_evlist;
}
}
#endif
@@ -1896,8 +1896,8 @@ int cmd_top(int argc, const char **argv)
if (!opts->no_bpf_event)
evlist__stop_sb_thread(top.sb_evlist);
-out_delete_evlist:
- evlist__delete(top.evlist);
+out_put_evlist:
+ evlist__put(top.evlist);
perf_session__delete(top.session);
annotation_options__exit();
perf_env__exit(&host_env);
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
index e58c49d047a2..da703d762433 100644
--- a/tools/perf/builtin-trace.c
+++ b/tools/perf/builtin-trace.c
@@ -4388,7 +4388,7 @@ static int trace__run(struct trace *trace, int argc, const char **argv)
if (trace->summary_bpf) {
if (trace_prepare_bpf_summary(trace->summary_mode) < 0)
- goto out_delete_evlist;
+ goto out_put_evlist;
if (trace->summary_only)
goto create_maps;
@@ -4456,19 +4456,19 @@ static int trace__run(struct trace *trace, int argc, const char **argv)
err = evlist__create_maps(evlist, &trace->opts.target);
if (err < 0) {
fprintf(trace->output, "Problems parsing the target to trace, check your options!\n");
- goto out_delete_evlist;
+ goto out_put_evlist;
}
err = trace__symbols_init(trace, argc, argv, evlist);
if (err < 0) {
fprintf(trace->output, "Problems initializing symbol libraries!\n");
- goto out_delete_evlist;
+ goto out_put_evlist;
}
if (trace->summary_mode == SUMMARY__BY_TOTAL && !trace->summary_bpf) {
trace->syscall_stats = alloc_syscall_stats();
if (!trace->syscall_stats)
- goto out_delete_evlist;
+ goto out_put_evlist;
}
evlist__config(evlist, &trace->opts, &callchain_param);
@@ -4477,7 +4477,7 @@ static int trace__run(struct trace *trace, int argc, const char **argv)
err = evlist__prepare_workload(evlist, &trace->opts.target, argv, false, NULL);
if (err < 0) {
fprintf(trace->output, "Couldn't run the workload!\n");
- goto out_delete_evlist;
+ goto out_put_evlist;
}
workload_pid = evlist->workload.pid;
}
@@ -4525,7 +4525,7 @@ static int trace__run(struct trace *trace, int argc, const char **argv)
err = trace__expand_filters(trace, &evsel);
if (err)
- goto out_delete_evlist;
+ goto out_put_evlist;
err = evlist__apply_filters(evlist, &evsel, &trace->opts.target);
if (err < 0)
goto out_error_apply_filters;
@@ -4642,12 +4642,12 @@ static int trace__run(struct trace *trace, int argc, const char **argv)
}
}
-out_delete_evlist:
+out_put_evlist:
trace_cleanup_bpf_summary();
delete_syscall_stats(trace->syscall_stats);
trace__symbols__exit(trace);
evlist__free_syscall_tp_fields(evlist);
- evlist__delete(evlist);
+ evlist__put(evlist);
cgroup__put(trace->cgroup);
trace->evlist = NULL;
trace->live = false;
@@ -4672,21 +4672,21 @@ static int trace__run(struct trace *trace, int argc, const char **argv)
out_error:
fprintf(trace->output, "%s\n", errbuf);
- goto out_delete_evlist;
+ goto out_put_evlist;
out_error_apply_filters:
fprintf(trace->output,
"Failed to set filter \"%s\" on event %s: %m\n",
evsel->filter, evsel__name(evsel));
- goto out_delete_evlist;
+ goto out_put_evlist;
}
out_error_mem:
fprintf(trace->output, "Not enough memory to run!\n");
- goto out_delete_evlist;
+ goto out_put_evlist;
out_errno:
fprintf(trace->output, "%m\n");
- goto out_delete_evlist;
+ goto out_put_evlist;
}
static int trace__replay(struct trace *trace)
@@ -5364,7 +5364,7 @@ static void trace__exit(struct trace *trace)
zfree(&trace->syscalls.table);
}
zfree(&trace->perfconfig_events);
- evlist__delete(trace->evlist);
+ evlist__put(trace->evlist);
trace->evlist = NULL;
ordered_events__free(&trace->oe.data);
#ifdef HAVE_LIBBPF_SUPPORT
diff --git a/tools/perf/tests/backward-ring-buffer.c b/tools/perf/tests/backward-ring-buffer.c
index c5e7999f2817..2b49b002d749 100644
--- a/tools/perf/tests/backward-ring-buffer.c
+++ b/tools/perf/tests/backward-ring-buffer.c
@@ -111,7 +111,7 @@ static int test__backward_ring_buffer(struct test_suite *test __maybe_unused, in
err = evlist__create_maps(evlist, &opts.target);
if (err < 0) {
pr_debug("Not enough memory to create thread/cpu maps\n");
- goto out_delete_evlist;
+ goto out_put_evlist;
}
parse_events_error__init(&parse_error);
@@ -124,7 +124,7 @@ static int test__backward_ring_buffer(struct test_suite *test __maybe_unused, in
if (err) {
pr_debug("Failed to parse tracepoint event, try use root\n");
ret = TEST_SKIP;
- goto out_delete_evlist;
+ goto out_put_evlist;
}
evlist__config(evlist, &opts, NULL);
@@ -133,19 +133,19 @@ static int test__backward_ring_buffer(struct test_suite *test __maybe_unused, in
if (err < 0) {
pr_debug("perf_evlist__open: %s\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
- goto out_delete_evlist;
+ goto out_put_evlist;
}
ret = TEST_FAIL;
err = do_test(evlist, opts.mmap_pages, &sample_count,
&comm_count);
if (err != TEST_OK)
- goto out_delete_evlist;
+ goto out_put_evlist;
if ((sample_count != NR_ITERS) || (comm_count != NR_ITERS)) {
pr_err("Unexpected counter: sample_count=%d, comm_count=%d\n",
sample_count, comm_count);
- goto out_delete_evlist;
+ goto out_put_evlist;
}
evlist__close(evlist);
@@ -154,16 +154,16 @@ static int test__backward_ring_buffer(struct test_suite *test __maybe_unused, in
if (err < 0) {
pr_debug("perf_evlist__open: %s\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
- goto out_delete_evlist;
+ goto out_put_evlist;
}
err = do_test(evlist, 1, &sample_count, &comm_count);
if (err != TEST_OK)
- goto out_delete_evlist;
+ goto out_put_evlist;
ret = TEST_OK;
-out_delete_evlist:
- evlist__delete(evlist);
+out_put_evlist:
+ evlist__put(evlist);
return ret;
}
diff --git a/tools/perf/tests/code-reading.c b/tools/perf/tests/code-reading.c
index 47043a3a2fb4..fc65a17f67f7 100644
--- a/tools/perf/tests/code-reading.c
+++ b/tools/perf/tests/code-reading.c
@@ -807,7 +807,7 @@ static int do_test_code_reading(bool try_kcore)
}
perf_evlist__set_maps(&evlist->core, NULL, NULL);
- evlist__delete(evlist);
+ evlist__put(evlist);
evlist = NULL;
continue;
}
@@ -844,7 +844,7 @@ static int do_test_code_reading(bool try_kcore)
out_put:
thread__put(thread);
out_err:
- evlist__delete(evlist);
+ evlist__put(evlist);
perf_cpu_map__put(cpus);
perf_thread_map__put(threads);
machine__delete(machine);
diff --git a/tools/perf/tests/event-times.c b/tools/perf/tests/event-times.c
index ae3b98bb42cf..94ab54ecd3f9 100644
--- a/tools/perf/tests/event-times.c
+++ b/tools/perf/tests/event-times.c
@@ -186,7 +186,7 @@ static int test_times(int (attach)(struct evlist *),
err = attach(evlist);
if (err == TEST_SKIP) {
pr_debug(" SKIP : not enough rights\n");
- evlist__delete(evlist);
+ evlist__put(evlist);
return err;
}
@@ -205,7 +205,7 @@ static int test_times(int (attach)(struct evlist *),
count.ena, count.run);
out_err:
- evlist__delete(evlist);
+ evlist__put(evlist);
return !err ? TEST_OK : TEST_FAIL;
}
diff --git a/tools/perf/tests/event_update.c b/tools/perf/tests/event_update.c
index facc65e29f20..73141b122d2f 100644
--- a/tools/perf/tests/event_update.c
+++ b/tools/perf/tests/event_update.c
@@ -117,7 +117,7 @@ static int test__event_update(struct test_suite *test __maybe_unused, int subtes
TEST_ASSERT_VAL("failed to synthesize attr update cpus",
!perf_event__synthesize_event_update_cpus(&tmp.tool, evsel, process_event_cpus));
- evlist__delete(evlist);
+ evlist__put(evlist);
return 0;
}
diff --git a/tools/perf/tests/evsel-roundtrip-name.c b/tools/perf/tests/evsel-roundtrip-name.c
index 1922cac13a24..6a220634c52f 100644
--- a/tools/perf/tests/evsel-roundtrip-name.c
+++ b/tools/perf/tests/evsel-roundtrip-name.c
@@ -33,7 +33,7 @@ static int perf_evsel__roundtrip_cache_name_test(void)
if (err) {
pr_debug("Failure to parse cache event '%s' possibly as PMUs don't support it",
name);
- evlist__delete(evlist);
+ evlist__put(evlist);
continue;
}
evlist__for_each_entry(evlist, evsel) {
@@ -42,7 +42,7 @@ static int perf_evsel__roundtrip_cache_name_test(void)
ret = TEST_FAIL;
}
}
- evlist__delete(evlist);
+ evlist__put(evlist);
}
}
}
@@ -66,7 +66,7 @@ static int perf_evsel__name_array_test(const char *const names[], int nr_names)
if (err) {
pr_debug("failed to parse event '%s', err %d\n",
names[i], err);
- evlist__delete(evlist);
+ evlist__put(evlist);
ret = TEST_FAIL;
continue;
}
@@ -76,7 +76,7 @@ static int perf_evsel__name_array_test(const char *const names[], int nr_names)
ret = TEST_FAIL;
}
}
- evlist__delete(evlist);
+ evlist__put(evlist);
}
return ret;
}
diff --git a/tools/perf/tests/expand-cgroup.c b/tools/perf/tests/expand-cgroup.c
index dd547f2f77cc..a7a445f12693 100644
--- a/tools/perf/tests/expand-cgroup.c
+++ b/tools/perf/tests/expand-cgroup.c
@@ -106,7 +106,7 @@ static int expand_default_events(void)
TEST_ASSERT_VAL("failed to get evlist", evlist);
ret = test_expand_events(evlist);
- evlist__delete(evlist);
+ evlist__put(evlist);
return ret;
}
@@ -133,7 +133,7 @@ static int expand_group_events(void)
ret = test_expand_events(evlist);
out:
parse_events_error__exit(&err);
- evlist__delete(evlist);
+ evlist__put(evlist);
return ret;
}
@@ -164,7 +164,7 @@ static int expand_libpfm_events(void)
ret = test_expand_events(evlist);
out:
- evlist__delete(evlist);
+ evlist__put(evlist);
return ret;
}
@@ -188,7 +188,7 @@ static int expand_metric_events(void)
ret = test_expand_events(evlist);
out:
- evlist__delete(evlist);
+ evlist__put(evlist);
return ret;
}
diff --git a/tools/perf/tests/hists_cumulate.c b/tools/perf/tests/hists_cumulate.c
index 606aa926a8fc..eca4ecb63ca8 100644
--- a/tools/perf/tests/hists_cumulate.c
+++ b/tools/perf/tests/hists_cumulate.c
@@ -744,7 +744,7 @@ static int test__hists_cumulate(struct test_suite *test __maybe_unused, int subt
out:
/* tear down everything */
- evlist__delete(evlist);
+ evlist__put(evlist);
machines__exit(&machines);
put_fake_samples();
diff --git a/tools/perf/tests/hists_filter.c b/tools/perf/tests/hists_filter.c
index cc6b26e373d1..0d09dc306019 100644
--- a/tools/perf/tests/hists_filter.c
+++ b/tools/perf/tests/hists_filter.c
@@ -332,7 +332,7 @@ static int test__hists_filter(struct test_suite *test __maybe_unused, int subtes
out:
/* tear down everything */
- evlist__delete(evlist);
+ evlist__put(evlist);
reset_output_field();
machines__exit(&machines);
put_fake_samples();
diff --git a/tools/perf/tests/hists_link.c b/tools/perf/tests/hists_link.c
index 996f5f0b3bd1..9646c3b7b4de 100644
--- a/tools/perf/tests/hists_link.c
+++ b/tools/perf/tests/hists_link.c
@@ -352,7 +352,7 @@ static int test__hists_link(struct test_suite *test __maybe_unused, int subtest
out:
/* tear down everything */
- evlist__delete(evlist);
+ evlist__put(evlist);
reset_output_field();
machines__exit(&machines);
put_fake_samples();
diff --git a/tools/perf/tests/hists_output.c b/tools/perf/tests/hists_output.c
index 7818950d786e..3f3bc978553e 100644
--- a/tools/perf/tests/hists_output.c
+++ b/tools/perf/tests/hists_output.c
@@ -631,7 +631,7 @@ static int test__hists_output(struct test_suite *test __maybe_unused, int subtes
out:
/* tear down everything */
- evlist__delete(evlist);
+ evlist__put(evlist);
machines__exit(&machines);
put_fake_samples();
diff --git a/tools/perf/tests/hwmon_pmu.c b/tools/perf/tests/hwmon_pmu.c
index ada6e445c4c4..1b60c3a900f1 100644
--- a/tools/perf/tests/hwmon_pmu.c
+++ b/tools/perf/tests/hwmon_pmu.c
@@ -214,7 +214,7 @@ static int do_test(size_t i, bool with_pmu, bool with_alias)
out:
parse_events_error__exit(&err);
- evlist__delete(evlist);
+ evlist__put(evlist);
return ret;
}
diff --git a/tools/perf/tests/keep-tracking.c b/tools/perf/tests/keep-tracking.c
index 729cc9cc1cb7..51cfd6522867 100644
--- a/tools/perf/tests/keep-tracking.c
+++ b/tools/perf/tests/keep-tracking.c
@@ -153,7 +153,7 @@ static int test__keep_tracking(struct test_suite *test __maybe_unused, int subte
out_err:
if (evlist) {
evlist__disable(evlist);
- evlist__delete(evlist);
+ evlist__put(evlist);
}
perf_cpu_map__put(cpus);
perf_thread_map__put(threads);
diff --git a/tools/perf/tests/mmap-basic.c b/tools/perf/tests/mmap-basic.c
index 8d04f6edb004..e6501791c505 100644
--- a/tools/perf/tests/mmap-basic.c
+++ b/tools/perf/tests/mmap-basic.c
@@ -94,7 +94,7 @@ static int test__basic_mmap(struct test_suite *test __maybe_unused, int subtest
/* Permissions failure, flag the failure as a skip. */
err = TEST_SKIP;
}
- goto out_delete_evlist;
+ goto out_put_evlist;
}
evsels[i]->core.attr.wakeup_events = 1;
@@ -106,7 +106,7 @@ static int test__basic_mmap(struct test_suite *test __maybe_unused, int subtest
pr_debug("failed to open counter: %s, "
"tweak /proc/sys/kernel/perf_event_paranoid?\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
- goto out_delete_evlist;
+ goto out_put_evlist;
}
nr_events[i] = 0;
@@ -116,7 +116,7 @@ static int test__basic_mmap(struct test_suite *test __maybe_unused, int subtest
if (evlist__mmap(evlist, 128) < 0) {
pr_debug("failed to mmap events: %d (%s)\n", errno,
str_error_r(errno, sbuf, sizeof(sbuf)));
- goto out_delete_evlist;
+ goto out_put_evlist;
}
for (i = 0; i < nsyscalls; ++i)
@@ -134,7 +134,7 @@ static int test__basic_mmap(struct test_suite *test __maybe_unused, int subtest
if (event->header.type != PERF_RECORD_SAMPLE) {
pr_debug("unexpected %s event\n",
perf_event__name(event->header.type));
- goto out_delete_evlist;
+ goto out_put_evlist;
}
perf_sample__init(&sample, /*all=*/false);
@@ -142,7 +142,7 @@ static int test__basic_mmap(struct test_suite *test __maybe_unused, int subtest
if (err) {
pr_err("Can't parse sample, err = %d\n", err);
perf_sample__exit(&sample);
- goto out_delete_evlist;
+ goto out_put_evlist;
}
err = -1;
@@ -151,7 +151,7 @@ static int test__basic_mmap(struct test_suite *test __maybe_unused, int subtest
if (evsel == NULL) {
pr_debug("event with id %" PRIu64
" doesn't map to an evsel\n", sample.id);
- goto out_delete_evlist;
+ goto out_put_evlist;
}
nr_events[evsel->core.idx]++;
perf_mmap__consume(&md->core);
@@ -166,12 +166,12 @@ static int test__basic_mmap(struct test_suite *test __maybe_unused, int subtest
expected_nr_events[evsel->core.idx],
evsel__name(evsel), nr_events[evsel->core.idx]);
err = -1;
- goto out_delete_evlist;
+ goto out_put_evlist;
}
}
-out_delete_evlist:
- evlist__delete(evlist);
+out_put_evlist:
+ evlist__put(evlist);
out_free_cpus:
perf_cpu_map__put(cpus);
out_free_threads:
diff --git a/tools/perf/tests/openat-syscall-tp-fields.c b/tools/perf/tests/openat-syscall-tp-fields.c
index 2a139d2781a8..3ff595c7a86a 100644
--- a/tools/perf/tests/openat-syscall-tp-fields.c
+++ b/tools/perf/tests/openat-syscall-tp-fields.c
@@ -51,7 +51,7 @@ static int test__syscall_openat_tp_fields(struct test_suite *test __maybe_unused
if (IS_ERR(evsel)) {
pr_debug("%s: evsel__newtp\n", __func__);
ret = PTR_ERR(evsel) == -EACCES ? TEST_SKIP : TEST_FAIL;
- goto out_delete_evlist;
+ goto out_put_evlist;
}
evlist__add(evlist, evsel);
@@ -59,7 +59,7 @@ static int test__syscall_openat_tp_fields(struct test_suite *test __maybe_unused
err = evlist__create_maps(evlist, &opts.target);
if (err < 0) {
pr_debug("%s: evlist__create_maps\n", __func__);
- goto out_delete_evlist;
+ goto out_put_evlist;
}
evsel__config(evsel, &opts, NULL);
@@ -70,14 +70,14 @@ static int test__syscall_openat_tp_fields(struct test_suite *test __maybe_unused
if (err < 0) {
pr_debug("perf_evlist__open: %s\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
- goto out_delete_evlist;
+ goto out_put_evlist;
}
err = evlist__mmap(evlist, UINT_MAX);
if (err < 0) {
pr_debug("evlist__mmap: %s\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
- goto out_delete_evlist;
+ goto out_put_evlist;
}
evlist__enable(evlist);
@@ -115,7 +115,7 @@ static int test__syscall_openat_tp_fields(struct test_suite *test __maybe_unused
if (err) {
pr_debug("Can't parse sample, err = %d\n", err);
perf_sample__exit(&sample);
- goto out_delete_evlist;
+ goto out_put_evlist;
}
tp_flags = evsel__intval(evsel, &sample, "flags");
@@ -123,7 +123,7 @@ static int test__syscall_openat_tp_fields(struct test_suite *test __maybe_unused
if (flags != tp_flags) {
pr_debug("%s: Expected flags=%#x, got %#x\n",
__func__, flags, tp_flags);
- goto out_delete_evlist;
+ goto out_put_evlist;
}
goto out_ok;
@@ -136,13 +136,13 @@ static int test__syscall_openat_tp_fields(struct test_suite *test __maybe_unused
if (++nr_polls > 5) {
pr_debug("%s: no events!\n", __func__);
- goto out_delete_evlist;
+ goto out_put_evlist;
}
}
out_ok:
ret = TEST_OK;
-out_delete_evlist:
- evlist__delete(evlist);
+out_put_evlist:
+ evlist__put(evlist);
out:
return ret;
}
diff --git a/tools/perf/tests/parse-events.c b/tools/perf/tests/parse-events.c
index 05c3e899b425..19dc7b7475d2 100644
--- a/tools/perf/tests/parse-events.c
+++ b/tools/perf/tests/parse-events.c
@@ -2568,7 +2568,7 @@ static int test_event(const struct evlist_test *e)
ret = e->check(evlist);
}
parse_events_error__exit(&err);
- evlist__delete(evlist);
+ evlist__put(evlist);
return ret;
}
@@ -2594,7 +2594,7 @@ static int test_event_fake_pmu(const char *str)
}
parse_events_error__exit(&err);
- evlist__delete(evlist);
+ evlist__put(evlist);
return ret;
}
diff --git a/tools/perf/tests/parse-metric.c b/tools/perf/tests/parse-metric.c
index 7c7f489a5eb0..3f0ec839c056 100644
--- a/tools/perf/tests/parse-metric.c
+++ b/tools/perf/tests/parse-metric.c
@@ -84,7 +84,7 @@ static int __compute_metric(const char *name, struct value *vals,
cpus = perf_cpu_map__new("0");
if (!cpus) {
- evlist__delete(evlist);
+ evlist__put(evlist);
return -ENOMEM;
}
@@ -113,7 +113,7 @@ static int __compute_metric(const char *name, struct value *vals,
/* ... cleanup. */
evlist__free_stats(evlist);
perf_cpu_map__put(cpus);
- evlist__delete(evlist);
+ evlist__put(evlist);
return err;
}
diff --git a/tools/perf/tests/parse-no-sample-id-all.c b/tools/perf/tests/parse-no-sample-id-all.c
index 50e68b7d43aa..d5a8d065809e 100644
--- a/tools/perf/tests/parse-no-sample-id-all.c
+++ b/tools/perf/tests/parse-no-sample-id-all.c
@@ -49,7 +49,7 @@ static int process_events(union perf_event **events, size_t count)
for (i = 0; i < count && !err; i++)
err = process_event(&evlist, events[i]);
- evlist__delete(evlist);
+ evlist__put(evlist);
return err;
}
diff --git a/tools/perf/tests/perf-record.c b/tools/perf/tests/perf-record.c
index ad44cc68820b..f95752b2ed1c 100644
--- a/tools/perf/tests/perf-record.c
+++ b/tools/perf/tests/perf-record.c
@@ -105,7 +105,7 @@ static int test__PERF_RECORD(struct test_suite *test __maybe_unused, int subtest
err = evlist__create_maps(evlist, &opts.target);
if (err < 0) {
pr_debug("Not enough memory to create thread/cpu maps\n");
- goto out_delete_evlist;
+ goto out_put_evlist;
}
/*
@@ -117,7 +117,7 @@ static int test__PERF_RECORD(struct test_suite *test __maybe_unused, int subtest
err = evlist__prepare_workload(evlist, &opts.target, argv, false, NULL);
if (err < 0) {
pr_debug("Couldn't run the workload!\n");
- goto out_delete_evlist;
+ goto out_put_evlist;
}
/*
@@ -134,7 +134,7 @@ static int test__PERF_RECORD(struct test_suite *test __maybe_unused, int subtest
pr_debug("sched__get_first_possible_cpu: %s\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
evlist__cancel_workload(evlist);
- goto out_delete_evlist;
+ goto out_put_evlist;
}
cpu = err;
@@ -146,7 +146,7 @@ static int test__PERF_RECORD(struct test_suite *test __maybe_unused, int subtest
pr_debug("sched_setaffinity: %s\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
evlist__cancel_workload(evlist);
- goto out_delete_evlist;
+ goto out_put_evlist;
}
/*
@@ -158,7 +158,7 @@ static int test__PERF_RECORD(struct test_suite *test __maybe_unused, int subtest
pr_debug("perf_evlist__open: %s\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
evlist__cancel_workload(evlist);
- goto out_delete_evlist;
+ goto out_put_evlist;
}
/*
@@ -171,7 +171,7 @@ static int test__PERF_RECORD(struct test_suite *test __maybe_unused, int subtest
pr_debug("evlist__mmap: %s\n",
str_error_r(errno, sbuf, sizeof(sbuf)));
evlist__cancel_workload(evlist);
- goto out_delete_evlist;
+ goto out_put_evlist;
}
/*
@@ -209,7 +209,7 @@ static int test__PERF_RECORD(struct test_suite *test __maybe_unused, int subtest
if (verbose > 0)
perf_event__fprintf(event, NULL, stderr);
pr_debug("Couldn't parse sample\n");
- goto out_delete_evlist;
+ goto out_put_evlist;
}
if (verbose > 0) {
@@ -350,9 +350,9 @@ static int test__PERF_RECORD(struct test_suite *test __maybe_unused, int subtest
pr_debug("PERF_RECORD_MMAP for %s missing!\n", "[vdso]");
++errs;
}
-out_delete_evlist:
+out_put_evlist:
CPU_FREE(cpu_mask);
- evlist__delete(evlist);
+ evlist__put(evlist);
out:
perf_sample__exit(&sample);
if (err == -EACCES)
diff --git a/tools/perf/tests/perf-time-to-tsc.c b/tools/perf/tests/perf-time-to-tsc.c
index cca41bd37ae3..d3538fa20af3 100644
--- a/tools/perf/tests/perf-time-to-tsc.c
+++ b/tools/perf/tests/perf-time-to-tsc.c
@@ -201,7 +201,7 @@ static int test__perf_time_to_tsc(struct test_suite *test __maybe_unused, int su
err = TEST_OK;
out_err:
- evlist__delete(evlist);
+ evlist__put(evlist);
perf_cpu_map__put(cpus);
perf_thread_map__put(threads);
return err;
diff --git a/tools/perf/tests/pfm.c b/tools/perf/tests/pfm.c
index fca4a86452df..8d19b1bfecbc 100644
--- a/tools/perf/tests/pfm.c
+++ b/tools/perf/tests/pfm.c
@@ -80,7 +80,7 @@ static int test__pfm_events(struct test_suite *test __maybe_unused,
evlist__nr_groups(evlist),
0);
- evlist__delete(evlist);
+ evlist__put(evlist);
}
return 0;
}
@@ -165,7 +165,7 @@ static int test__pfm_group(struct test_suite *test __maybe_unused,
evlist__nr_groups(evlist),
table[i].nr_groups);
- evlist__delete(evlist);
+ evlist__put(evlist);
}
return 0;
}
diff --git a/tools/perf/tests/pmu-events.c b/tools/perf/tests/pmu-events.c
index a99716862168..236bbbad5773 100644
--- a/tools/perf/tests/pmu-events.c
+++ b/tools/perf/tests/pmu-events.c
@@ -797,7 +797,7 @@ static int check_parse_id(const char *id, struct parse_events_error *error)
/*warn_if_reordered=*/true, /*fake_tp=*/false);
free(dup);
- evlist__delete(evlist);
+ evlist__put(evlist);
return ret;
}
@@ -844,7 +844,7 @@ static int test__parsing_callback(const struct pmu_metric *pm,
cpus = perf_cpu_map__new("0");
if (!cpus) {
- evlist__delete(evlist);
+ evlist__put(evlist);
return -ENOMEM;
}
@@ -899,7 +899,7 @@ static int test__parsing_callback(const struct pmu_metric *pm,
/* ... cleanup. */
evlist__free_stats(evlist);
perf_cpu_map__put(cpus);
- evlist__delete(evlist);
+ evlist__put(evlist);
return err;
}
diff --git a/tools/perf/tests/pmu.c b/tools/perf/tests/pmu.c
index 0ebf2d7b2cb4..3d931c1f99dd 100644
--- a/tools/perf/tests/pmu.c
+++ b/tools/perf/tests/pmu.c
@@ -287,7 +287,7 @@ static int test__pmu_usr_chgs(struct test_suite *test __maybe_unused, int subtes
ret = TEST_OK;
err_out:
parse_events_terms__exit(&terms);
- evlist__delete(evlist);
+ evlist__put(evlist);
test_pmu_put(dir, pmu);
return ret;
}
@@ -339,7 +339,7 @@ static int test__pmu_events(struct test_suite *test __maybe_unused, int subtest
ret = TEST_OK;
err_out:
parse_events_error__exit(&err);
- evlist__delete(evlist);
+ evlist__put(evlist);
test_pmu_put(dir, pmu);
return ret;
}
diff --git a/tools/perf/tests/sw-clock.c b/tools/perf/tests/sw-clock.c
index b6e46975379c..bb6b62cf51d1 100644
--- a/tools/perf/tests/sw-clock.c
+++ b/tools/perf/tests/sw-clock.c
@@ -59,7 +59,7 @@ static int __test__sw_clock_freq(enum perf_sw_ids clock_id)
evsel = evsel__new(&attr);
if (evsel == NULL) {
pr_debug("evsel__new\n");
- goto out_delete_evlist;
+ goto out_put_evlist;
}
evlist__add(evlist, evsel);
@@ -68,7 +68,7 @@ static int __test__sw_clock_freq(enum perf_sw_ids clock_id)
if (!cpus || !threads) {
err = -ENOMEM;
pr_debug("Not enough memory to create thread/cpu maps\n");
- goto out_delete_evlist;
+ goto out_put_evlist;
}
perf_evlist__set_maps(&evlist->core, cpus, threads);
@@ -80,14 +80,14 @@ static int __test__sw_clock_freq(enum perf_sw_ids clock_id)
pr_debug("Couldn't open evlist: %s\nHint: check %s, using %" PRIu64 " in this test.\n",
str_error_r(errno, sbuf, sizeof(sbuf)),
knob, (u64)attr.sample_freq);
- goto out_delete_evlist;
+ goto out_put_evlist;
}
err = evlist__mmap(evlist, 128);
if (err < 0) {
pr_debug("failed to mmap event: %d (%s)\n", errno,
str_error_r(errno, sbuf, sizeof(sbuf)));
- goto out_delete_evlist;
+ goto out_put_evlist;
}
evlist__enable(evlist);
@@ -113,7 +113,7 @@ static int __test__sw_clock_freq(enum perf_sw_ids clock_id)
if (err < 0) {
pr_debug("Error during parse sample\n");
perf_sample__exit(&sample);
- goto out_delete_evlist;
+ goto out_put_evlist;
}
total_periods += sample.period;
@@ -131,10 +131,10 @@ static int __test__sw_clock_freq(enum perf_sw_ids clock_id)
err = -1;
}
-out_delete_evlist:
+out_put_evlist:
perf_cpu_map__put(cpus);
perf_thread_map__put(threads);
- evlist__delete(evlist);
+ evlist__put(evlist);
return err;
}
diff --git a/tools/perf/tests/switch-tracking.c b/tools/perf/tests/switch-tracking.c
index 72a8289e846d..306151c83af8 100644
--- a/tools/perf/tests/switch-tracking.c
+++ b/tools/perf/tests/switch-tracking.c
@@ -579,7 +579,7 @@ static int test__switch_tracking(struct test_suite *test __maybe_unused, int sub
out:
if (evlist) {
evlist__disable(evlist);
- evlist__delete(evlist);
+ evlist__put(evlist);
}
perf_cpu_map__put(cpus);
perf_thread_map__put(threads);
diff --git a/tools/perf/tests/task-exit.c b/tools/perf/tests/task-exit.c
index 4053ff2813bb..a46650b10689 100644
--- a/tools/perf/tests/task-exit.c
+++ b/tools/perf/tests/task-exit.c
@@ -74,7 +74,7 @@ static int test__task_exit(struct test_suite *test __maybe_unused, int subtest _
if (!cpus || !threads) {
err = -ENOMEM;
pr_debug("Not enough memory to create thread/cpu maps\n");
- goto out_delete_evlist;
+ goto out_put_evlist;
}
perf_evlist__set_maps(&evlist->core, cpus, threads);
@@ -82,7 +82,7 @@ static int test__task_exit(struct test_suite *test __maybe_unused, int subtest _
err = evlist__prepare_workload(evlist, &target, argv, false, workload_exec_failed_signal);
if (err < 0) {
pr_debug("Couldn't run the workload!\n");
- goto out_delete_evlist;
+ goto out_put_evlist;
}
evsel = evlist__first(evlist);
@@ -101,14 +101,14 @@ static int test__task_exit(struct test_suite *test __maybe_unused, int subtest _
if (err < 0) {
pr_debug("Couldn't open the evlist: %s\n",
str_error_r(-err, sbuf, sizeof(sbuf)));
- goto out_delete_evlist;
+ goto out_put_evlist;
}
if (evlist__mmap(evlist, 128) < 0) {
pr_debug("failed to mmap events: %d (%s)\n", errno,
str_error_r(errno, sbuf, sizeof(sbuf)));
err = -1;
- goto out_delete_evlist;
+ goto out_put_evlist;
}
evlist__start_workload(evlist);
@@ -133,7 +133,7 @@ static int test__task_exit(struct test_suite *test __maybe_unused, int subtest _
if (retry_count++ > 1000) {
pr_debug("Failed after retrying 1000 times\n");
err = -1;
- goto out_delete_evlist;
+ goto out_put_evlist;
}
goto retry;
@@ -144,10 +144,10 @@ static int test__task_exit(struct test_suite *test __maybe_unused, int subtest _
err = -1;
}
-out_delete_evlist:
+out_put_evlist:
perf_cpu_map__put(cpus);
perf_thread_map__put(threads);
- evlist__delete(evlist);
+ evlist__put(evlist);
return err;
}
diff --git a/tools/perf/tests/tool_pmu.c b/tools/perf/tests/tool_pmu.c
index 1e900ef92e37..e78ff9dcea97 100644
--- a/tools/perf/tests/tool_pmu.c
+++ b/tools/perf/tests/tool_pmu.c
@@ -67,7 +67,7 @@ static int do_test(enum tool_pmu_event ev, bool with_pmu)
out:
parse_events_error__exit(&err);
- evlist__delete(evlist);
+ evlist__put(evlist);
return ret;
}
diff --git a/tools/perf/tests/topology.c b/tools/perf/tests/topology.c
index f54502ebef4b..4ecf5d750313 100644
--- a/tools/perf/tests/topology.c
+++ b/tools/perf/tests/topology.c
@@ -57,7 +57,7 @@ static int session_write_header(char *path)
!perf_session__write_header(session, session->evlist,
perf_data__fd(&data), true));
- evlist__delete(session->evlist);
+ evlist__put(session->evlist);
perf_session__delete(session);
return 0;
diff --git a/tools/perf/util/cgroup.c b/tools/perf/util/cgroup.c
index 1b5664d1481f..652a45aac828 100644
--- a/tools/perf/util/cgroup.c
+++ b/tools/perf/util/cgroup.c
@@ -520,8 +520,8 @@ int evlist__expand_cgroup(struct evlist *evlist, const char *str, bool open_cgro
cgrp_event_expanded = true;
out_err:
- evlist__delete(orig_list);
- evlist__delete(tmp_list);
+ evlist__put(orig_list);
+ evlist__put(tmp_list);
metricgroup__rblist_exit(&orig_metric_events);
release_cgroup_list();
diff --git a/tools/perf/util/data-convert-bt.c b/tools/perf/util/data-convert-bt.c
index 3b8f2df823a9..a85ae53db7c5 100644
--- a/tools/perf/util/data-convert-bt.c
+++ b/tools/perf/util/data-convert-bt.c
@@ -1363,7 +1363,7 @@ static void cleanup_events(struct perf_session *session)
zfree(&evsel->priv);
}
- evlist__delete(evlist);
+ evlist__put(evlist);
session->evlist = NULL;
}
diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c
index 35d65fe50e06..b5a7895debf5 100644
--- a/tools/perf/util/evlist.c
+++ b/tools/perf/util/evlist.c
@@ -75,7 +75,7 @@ int sigqueue(pid_t pid, int sig, const union sigval value);
#define FD(e, x, y) (*(int *)xyarray__entry(e->core.fd, x, y))
#define SID(e, x, y) xyarray__entry(e->core.sample_id, x, y)
-void evlist__init(struct evlist *evlist, struct perf_cpu_map *cpus,
+static void evlist__init(struct evlist *evlist, struct perf_cpu_map *cpus,
struct perf_thread_map *threads)
{
perf_evlist__init(&evlist->core);
@@ -88,6 +88,7 @@ void evlist__init(struct evlist *evlist, struct perf_cpu_map *cpus,
evlist->nr_br_cntr = -1;
metricgroup__rblist_init(&evlist->metric_events);
INIT_LIST_HEAD(&evlist->deferred_samples);
+ refcount_set(&evlist->refcnt, 1);
}
struct evlist *evlist__new(void)
@@ -139,7 +140,7 @@ struct evlist *evlist__new_default(const struct target *target, bool sample_call
return evlist;
out_err:
- evlist__delete(evlist);
+ evlist__put(evlist);
return NULL;
}
@@ -148,13 +149,19 @@ struct evlist *evlist__new_dummy(void)
struct evlist *evlist = evlist__new();
if (evlist && evlist__add_dummy(evlist)) {
- evlist__delete(evlist);
+ evlist__put(evlist);
evlist = NULL;
}
return evlist;
}
+struct evlist *evlist__get(struct evlist *evlist)
+{
+ refcount_inc(&evlist->refcnt);
+ return evlist;
+}
+
/**
* evlist__set_id_pos - set the positions of event ids.
* @evlist: selected event list
@@ -193,7 +200,7 @@ static void evlist__purge(struct evlist *evlist)
evlist->core.nr_entries = 0;
}
-void evlist__exit(struct evlist *evlist)
+static void evlist__exit(struct evlist *evlist)
{
metricgroup__rblist_exit(&evlist->metric_events);
event_enable_timer__exit(&evlist->eet);
@@ -202,11 +209,14 @@ void evlist__exit(struct evlist *evlist)
perf_evlist__exit(&evlist->core);
}
-void evlist__delete(struct evlist *evlist)
+void evlist__put(struct evlist *evlist)
{
if (evlist == NULL)
return;
+ if (!refcount_dec_and_test(&evlist->refcnt))
+ return;
+
evlist__free_stats(evlist);
evlist__munmap(evlist);
evlist__close(evlist);
diff --git a/tools/perf/util/evlist.h b/tools/perf/util/evlist.h
index e54761c670b6..a9820a6aad5b 100644
--- a/tools/perf/util/evlist.h
+++ b/tools/perf/util/evlist.h
@@ -61,6 +61,7 @@ struct event_enable_timer;
struct evlist {
struct perf_evlist core;
+ refcount_t refcnt;
bool enabled;
bool no_affinity;
int id_pos;
@@ -109,10 +110,8 @@ struct evsel_str_handler {
struct evlist *evlist__new(void);
struct evlist *evlist__new_default(const struct target *target, bool sample_callchains);
struct evlist *evlist__new_dummy(void);
-void evlist__init(struct evlist *evlist, struct perf_cpu_map *cpus,
- struct perf_thread_map *threads);
-void evlist__exit(struct evlist *evlist);
-void evlist__delete(struct evlist *evlist);
+struct evlist *evlist__get(struct evlist *evlist);
+void evlist__put(struct evlist *evlist);
void evlist__add(struct evlist *evlist, struct evsel *entry);
void evlist__remove(struct evlist *evlist, struct evsel *evsel);
diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c
index 644769e92708..cf54bbbc8ddc 100644
--- a/tools/perf/util/expr.c
+++ b/tools/perf/util/expr.c
@@ -450,7 +450,7 @@ double expr__has_event(const struct expr_parse_ctx *ctx, bool compute_ids, const
ret = parse_event(tmp, id) ? 0 : 1;
}
out:
- evlist__delete(tmp);
+ evlist__put(tmp);
return ret;
}
--git a/tools/perf/util/header.c b/tools/perf/util/header.c
index f30e48eb3fc3..f9887d2fc8ed 100644
--- a/tools/perf/util/header.c
+++ b/tools/perf/util/header.c
@@ -4909,12 +4909,12 @@ int perf_session__read_header(struct perf_session *session)
evsel = evsel__new(&f_attr.attr);
if (evsel == NULL)
- goto out_delete_evlist;
+ goto out_put_evlist;
evsel->needs_swap = header->needs_swap;
/*
* Do it before so that if perf_evsel__alloc_id fails, this
- * entry gets purged too at evlist__delete().
+ * entry gets purged too at evlist__put().
*/
evlist__add(session->evlist, evsel);
@@ -4925,7 +4925,7 @@ int perf_session__read_header(struct perf_session *session)
* hattr->ids threads.
*/
if (perf_evsel__alloc_id(&evsel->core, 1, nr_ids))
- goto out_delete_evlist;
+ goto out_put_evlist;
lseek(fd, f_attr.ids.offset, SEEK_SET);
@@ -4944,7 +4944,7 @@ int perf_session__read_header(struct perf_session *session)
perf_file_section__process);
if (evlist__prepare_tracepoint_events(session->evlist, session->tevent.pevent))
- goto out_delete_evlist;
+ goto out_put_evlist;
#else
perf_header__process_sections(header, fd, NULL, perf_file_section__process);
#endif
@@ -4953,8 +4953,8 @@ int perf_session__read_header(struct perf_session *session)
out_errno:
return -errno;
-out_delete_evlist:
- evlist__delete(session->evlist);
+out_put_evlist:
+ evlist__put(session->evlist);
session->evlist = NULL;
return -ENOMEM;
}
diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index 4db9578efd81..191ec2d8a250 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -214,7 +214,7 @@ static void metric__free(struct metric *m)
zfree(&m->metric_refs);
expr__ctx_free(m->pctx);
zfree(&m->modifier);
- evlist__delete(m->evlist);
+ evlist__put(m->evlist);
free(m);
}
@@ -1335,7 +1335,7 @@ static int parse_ids(bool metric_no_merge, bool fake_pmu,
parsed_evlist = NULL;
err_out:
parse_events_error__exit(&parse_error);
- evlist__delete(parsed_evlist);
+ evlist__put(parsed_evlist);
strbuf_release(&events);
return ret;
}
@@ -1546,7 +1546,7 @@ static int parse_groups(struct evlist *perf_evlist,
if (combined_evlist) {
evlist__splice_list_tail(perf_evlist, &combined_evlist->core.entries);
- evlist__delete(combined_evlist);
+ evlist__put(combined_evlist);
}
list_for_each_entry(m, &metric_list, nd) {
diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c
index 1497e1f2a08c..f0809be63ad8 100644
--- a/tools/perf/util/parse-events.c
+++ b/tools/perf/util/parse-events.c
@@ -2316,7 +2316,7 @@ int __parse_events(struct evlist *evlist, const char *str, const char *pmu_filte
/*
* There are 2 users - builtin-record and builtin-test objects.
- * Both call evlist__delete in case of error, so we dont
+ * Both call evlist__put in case of error, so we dont
* need to bother.
*/
return ret;
@@ -2519,7 +2519,7 @@ int parse_events_option_new_evlist(const struct option *opt, const char *str, in
}
ret = parse_events_option(opt, str, unset);
if (ret) {
- evlist__delete(*args->evlistp);
+ evlist__put(*args->evlistp);
*args->evlistp = NULL;
}
diff --git a/tools/perf/util/perf_api_probe.c b/tools/perf/util/perf_api_probe.c
index e1904a330b28..f61c4ec52827 100644
--- a/tools/perf/util/perf_api_probe.c
+++ b/tools/perf/util/perf_api_probe.c
@@ -57,7 +57,7 @@ static int perf_do_probe_api(setup_probe_fn_t fn, struct perf_cpu cpu, const cha
err = 0;
out_delete:
- evlist__delete(evlist);
+ evlist__put(evlist);
return err;
}
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index 1e6c99efff90..aeecdb497fac 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -1272,7 +1272,7 @@ static int pyrf_evsel__setup_types(void)
struct pyrf_evlist {
PyObject_HEAD
- struct evlist evlist;
+ struct evlist *evlist;
};
static int pyrf_evlist__init(struct pyrf_evlist *pevlist,
@@ -1285,15 +1285,22 @@ static int pyrf_evlist__init(struct pyrf_evlist *pevlist,
if (!PyArg_ParseTuple(args, "OO", &pcpus, &pthreads))
return -1;
+ evlist__put(pevlist->evlist);
+ pevlist->evlist = evlist__new();
+ if (!pevlist->evlist) {
+ PyErr_NoMemory();
+ return -1;
+ }
threads = ((struct pyrf_thread_map *)pthreads)->threads;
cpus = ((struct pyrf_cpu_map *)pcpus)->cpus;
- evlist__init(&pevlist->evlist, cpus, threads);
+ perf_evlist__set_maps(&pevlist->evlist->core, cpus, threads);
+
return 0;
}
static void pyrf_evlist__delete(struct pyrf_evlist *pevlist)
{
- evlist__exit(&pevlist->evlist);
+ evlist__put(pevlist->evlist);
Py_TYPE(pevlist)->tp_free((PyObject*)pevlist);
}
@@ -1302,7 +1309,7 @@ static PyObject *pyrf_evlist__all_cpus(struct pyrf_evlist *pevlist)
struct pyrf_cpu_map *pcpu_map = PyObject_New(struct pyrf_cpu_map, &pyrf_cpu_map__type);
if (pcpu_map)
- pcpu_map->cpus = perf_cpu_map__get(pevlist->evlist.core.all_cpus);
+ pcpu_map->cpus = perf_cpu_map__get(pevlist->evlist->core.all_cpus);
return (PyObject *)pcpu_map;
}
@@ -1315,7 +1322,7 @@ static PyObject *pyrf_evlist__metrics(struct pyrf_evlist *pevlist)
if (!list)
return NULL;
- for (node = rb_first_cached(&pevlist->evlist.metric_events.entries); node;
+ for (node = rb_first_cached(&pevlist->evlist->metric_events.entries); node;
node = rb_next(node)) {
struct metric_event *me = container_of(node, struct metric_event, nd);
struct list_head *pos;
@@ -1421,7 +1428,7 @@ static PyObject *pyrf_evlist__compute_metric(struct pyrf_evlist *pevlist,
if (!PyArg_ParseTuple(args, "sii", &metric, &cpu, &thread))
return NULL;
- for (node = rb_first_cached(&pevlist->evlist.metric_events.entries);
+ for (node = rb_first_cached(&pevlist->evlist->metric_events.entries);
mexp == NULL && node;
node = rb_next(node)) {
struct metric_event *me = container_of(node, struct metric_event, nd);
@@ -1437,7 +1444,7 @@ static PyObject *pyrf_evlist__compute_metric(struct pyrf_evlist *pevlist,
if (e->metric_events[0] == NULL)
continue;
- evlist__for_each_entry(&pevlist->evlist, pos2) {
+ evlist__for_each_entry(pevlist->evlist, pos2) {
if (pos2->metric_leader != e->metric_events[0])
continue;
cpu_idx = perf_cpu_map__idx(pos2->core.cpus,
@@ -1482,7 +1489,7 @@ static PyObject *pyrf_evlist__compute_metric(struct pyrf_evlist *pevlist,
static PyObject *pyrf_evlist__mmap(struct pyrf_evlist *pevlist,
PyObject *args, PyObject *kwargs)
{
- struct evlist *evlist = &pevlist->evlist;
+ struct evlist *evlist = pevlist->evlist;
static char *kwlist[] = { "pages", "overwrite", NULL };
int pages = 128, overwrite = false;
@@ -1502,7 +1509,7 @@ static PyObject *pyrf_evlist__mmap(struct pyrf_evlist *pevlist,
static PyObject *pyrf_evlist__poll(struct pyrf_evlist *pevlist,
PyObject *args, PyObject *kwargs)
{
- struct evlist *evlist = &pevlist->evlist;
+ struct evlist *evlist = pevlist->evlist;
static char *kwlist[] = { "timeout", NULL };
int timeout = -1, n;
@@ -1522,7 +1529,7 @@ static PyObject *pyrf_evlist__get_pollfd(struct pyrf_evlist *pevlist,
PyObject *args __maybe_unused,
PyObject *kwargs __maybe_unused)
{
- struct evlist *evlist = &pevlist->evlist;
+ struct evlist *evlist = pevlist->evlist;
PyObject *list = PyList_New(0);
int i;
@@ -1551,7 +1558,7 @@ static PyObject *pyrf_evlist__add(struct pyrf_evlist *pevlist,
PyObject *args,
PyObject *kwargs __maybe_unused)
{
- struct evlist *evlist = &pevlist->evlist;
+ struct evlist *evlist = pevlist->evlist;
PyObject *pevsel;
struct evsel *evsel;
@@ -1583,7 +1590,7 @@ static struct mmap *get_md(struct evlist *evlist, int cpu)
static PyObject *pyrf_evlist__read_on_cpu(struct pyrf_evlist *pevlist,
PyObject *args, PyObject *kwargs)
{
- struct evlist *evlist = &pevlist->evlist;
+ struct evlist *evlist = pevlist->evlist;
union perf_event *event;
int sample_id_all = 1, cpu;
static char *kwlist[] = { "cpu", "sample_id_all", NULL };
@@ -1640,7 +1647,7 @@ static PyObject *pyrf_evlist__read_on_cpu(struct pyrf_evlist *pevlist,
static PyObject *pyrf_evlist__open(struct pyrf_evlist *pevlist,
PyObject *args, PyObject *kwargs)
{
- struct evlist *evlist = &pevlist->evlist;
+ struct evlist *evlist = pevlist->evlist;
if (evlist__open(evlist) < 0) {
PyErr_SetFromErrno(PyExc_OSError);
@@ -1653,7 +1660,7 @@ static PyObject *pyrf_evlist__open(struct pyrf_evlist *pevlist,
static PyObject *pyrf_evlist__close(struct pyrf_evlist *pevlist)
{
- struct evlist *evlist = &pevlist->evlist;
+ struct evlist *evlist = pevlist->evlist;
evlist__close(evlist);
@@ -1679,7 +1686,7 @@ static PyObject *pyrf_evlist__config(struct pyrf_evlist *pevlist)
.no_buffering = true,
.no_inherit = true,
};
- struct evlist *evlist = &pevlist->evlist;
+ struct evlist *evlist = pevlist->evlist;
evlist__config(evlist, &opts, &callchain_param);
Py_INCREF(Py_None);
@@ -1688,14 +1695,14 @@ static PyObject *pyrf_evlist__config(struct pyrf_evlist *pevlist)
static PyObject *pyrf_evlist__disable(struct pyrf_evlist *pevlist)
{
- evlist__disable(&pevlist->evlist);
+ evlist__disable(pevlist->evlist);
Py_INCREF(Py_None);
return Py_None;
}
static PyObject *pyrf_evlist__enable(struct pyrf_evlist *pevlist)
{
- evlist__enable(&pevlist->evlist);
+ evlist__enable(pevlist->evlist);
Py_INCREF(Py_None);
return Py_None;
}
@@ -1786,7 +1793,23 @@ static Py_ssize_t pyrf_evlist__length(PyObject *obj)
{
struct pyrf_evlist *pevlist = (void *)obj;
- return pevlist->evlist.core.nr_entries;
+ return pevlist->evlist->core.nr_entries;
+}
+
+static PyObject *pyrf_evsel__from_evsel(struct evsel *evsel)
+{
+ struct pyrf_evsel *pevsel = PyObject_New(struct pyrf_evsel, &pyrf_evsel__type);
+
+ if (!pevsel)
+ return NULL;
+
+ memset(&pevsel->evsel, 0, sizeof(pevsel->evsel));
+ evsel__init(&pevsel->evsel, &evsel->core.attr, evsel->core.idx);
+
+ evsel__clone(&pevsel->evsel, evsel);
+ if (evsel__is_group_leader(evsel))
+ evsel__set_leader(&pevsel->evsel, &pevsel->evsel);
+ return (PyObject *)pevsel;
}
static PyObject *pyrf_evlist__item(PyObject *obj, Py_ssize_t i)
@@ -1794,17 +1817,16 @@ static PyObject *pyrf_evlist__item(PyObject *obj, Py_ssize_t i)
struct pyrf_evlist *pevlist = (void *)obj;
struct evsel *pos;
- if (i >= pevlist->evlist.core.nr_entries) {
+ if (i >= pevlist->evlist->core.nr_entries) {
PyErr_SetString(PyExc_IndexError, "Index out of range");
return NULL;
}
- evlist__for_each_entry(&pevlist->evlist, pos) {
+ evlist__for_each_entry(pevlist->evlist, pos) {
if (i-- == 0)
break;
}
-
- return Py_BuildValue("O", container_of(pos, struct pyrf_evsel, evsel));
+ return pyrf_evsel__from_evsel(pos);
}
static PyObject *pyrf_evlist__str(PyObject *self)
@@ -1816,7 +1838,7 @@ static PyObject *pyrf_evlist__str(PyObject *self)
PyObject *result;
strbuf_addstr(&sb, "evlist([");
- evlist__for_each_entry(&pevlist->evlist, pos) {
+ evlist__for_each_entry(pevlist->evlist, pos) {
if (!first)
strbuf_addch(&sb, ',');
if (!pos->pmu)
@@ -1852,9 +1874,19 @@ static PyTypeObject pyrf_evlist__type = {
.tp_str = pyrf_evlist__str,
};
+static PyObject *pyrf_evlist__new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+ struct pyrf_evlist *pevlist;
+
+ pevlist = (struct pyrf_evlist *)PyType_GenericNew(type, args, kwargs);
+ if (pevlist)
+ pevlist->evlist = NULL;
+ return (PyObject *)pevlist;
+}
+
static int pyrf_evlist__setup_types(void)
{
- pyrf_evlist__type.tp_new = PyType_GenericNew;
+ pyrf_evlist__type.tp_new = pyrf_evlist__new;
return PyType_Ready(&pyrf_evlist__type);
}
@@ -1957,157 +1989,74 @@ static PyObject *pyrf__tracepoint(struct pyrf_evsel *pevsel,
return PyLong_FromLong(tp_pmu__id(sys, name));
}
-static PyObject *pyrf_evsel__from_evsel(struct evsel *evsel)
-{
- struct pyrf_evsel *pevsel = PyObject_New(struct pyrf_evsel, &pyrf_evsel__type);
-
- if (!pevsel)
- return NULL;
-
- memset(&pevsel->evsel, 0, sizeof(pevsel->evsel));
- evsel__init(&pevsel->evsel, &evsel->core.attr, evsel->core.idx);
-
- evsel__clone(&pevsel->evsel, evsel);
- if (evsel__is_group_leader(evsel))
- evsel__set_leader(&pevsel->evsel, &pevsel->evsel);
- return (PyObject *)pevsel;
-}
-
-static int evlist__pos(struct evlist *evlist, struct evsel *evsel)
-{
- struct evsel *pos;
- int idx = 0;
-
- evlist__for_each_entry(evlist, pos) {
- if (evsel == pos)
- return idx;
- idx++;
- }
- return -1;
-}
-
-static struct evsel *evlist__at(struct evlist *evlist, int idx)
-{
- struct evsel *pos;
- int idx2 = 0;
-
- evlist__for_each_entry(evlist, pos) {
- if (idx == idx2)
- return pos;
- idx2++;
- }
- return NULL;
-}
-
static PyObject *pyrf_evlist__from_evlist(struct evlist *evlist)
{
struct pyrf_evlist *pevlist = PyObject_New(struct pyrf_evlist, &pyrf_evlist__type);
- struct evsel *pos;
- struct rb_node *node;
if (!pevlist)
return NULL;
- memset(&pevlist->evlist, 0, sizeof(pevlist->evlist));
- evlist__init(&pevlist->evlist, evlist->core.all_cpus, evlist->core.threads);
- evlist__for_each_entry(evlist, pos) {
- struct pyrf_evsel *pevsel = (void *)pyrf_evsel__from_evsel(pos);
-
- evlist__add(&pevlist->evlist, &pevsel->evsel);
- }
- evlist__for_each_entry(&pevlist->evlist, pos) {
- struct evsel *leader = evsel__leader(pos);
-
- if (pos != leader) {
- int idx = evlist__pos(evlist, leader);
-
- if (idx >= 0)
- evsel__set_leader(pos, evlist__at(&pevlist->evlist, idx));
- else if (leader == NULL)
- evsel__set_leader(pos, pos);
- }
-
- leader = pos->metric_leader;
-
- if (pos != leader) {
- int idx = evlist__pos(evlist, leader);
-
- if (idx >= 0)
- pos->metric_leader = evlist__at(&pevlist->evlist, idx);
- else if (leader == NULL)
- pos->metric_leader = pos;
- }
- }
- metricgroup__copy_metric_events(&pevlist->evlist, /*cgrp=*/NULL,
- &pevlist->evlist.metric_events,
- &evlist->metric_events);
- for (node = rb_first_cached(&pevlist->evlist.metric_events.entries); node;
- node = rb_next(node)) {
- struct metric_event *me = container_of(node, struct metric_event, nd);
- struct list_head *mpos;
- int idx = evlist__pos(evlist, me->evsel);
-
- if (idx >= 0)
- me->evsel = evlist__at(&pevlist->evlist, idx);
- list_for_each(mpos, &me->head) {
- struct metric_expr *e = container_of(mpos, struct metric_expr, nd);
-
- for (int j = 0; e->metric_events[j]; j++) {
- idx = evlist__pos(evlist, e->metric_events[j]);
- if (idx >= 0)
- e->metric_events[j] = evlist__at(&pevlist->evlist, idx);
- }
- }
- }
+ pevlist->evlist = evlist__get(evlist);
return (PyObject *)pevlist;
}
static PyObject *pyrf__parse_events(PyObject *self, PyObject *args)
{
const char *input;
- struct evlist evlist = {};
+ struct evlist *evlist = evlist__new();
struct parse_events_error err;
PyObject *result;
PyObject *pcpus = NULL, *pthreads = NULL;
struct perf_cpu_map *cpus;
struct perf_thread_map *threads;
- if (!PyArg_ParseTuple(args, "s|OO", &input, &pcpus, &pthreads))
+ if (!evlist)
+ return PyErr_NoMemory();
+
+ if (!PyArg_ParseTuple(args, "s|OO", &input, &pcpus, &pthreads)) {
+ evlist__put(evlist);
return NULL;
+ }
threads = pthreads ? ((struct pyrf_thread_map *)pthreads)->threads : NULL;
cpus = pcpus ? ((struct pyrf_cpu_map *)pcpus)->cpus : NULL;
parse_events_error__init(&err);
- evlist__init(&evlist, cpus, threads);
- if (parse_events(&evlist, input, &err)) {
+ perf_evlist__set_maps(&evlist->core, cpus, threads);
+ if (parse_events(evlist, input, &err)) {
parse_events_error__print(&err, input);
PyErr_SetFromErrno(PyExc_OSError);
+ evlist__put(evlist);
return NULL;
}
- result = pyrf_evlist__from_evlist(&evlist);
- evlist__exit(&evlist);
+ result = pyrf_evlist__from_evlist(evlist);
+ evlist__put(evlist);
return result;
}
static PyObject *pyrf__parse_metrics(PyObject *self, PyObject *args)
{
const char *input, *pmu = NULL;
- struct evlist evlist = {};
+ struct evlist *evlist = evlist__new();
PyObject *result;
PyObject *pcpus = NULL, *pthreads = NULL;
struct perf_cpu_map *cpus;
struct perf_thread_map *threads;
int ret;
- if (!PyArg_ParseTuple(args, "s|sOO", &input, &pmu, &pcpus, &pthreads))
+ if (!evlist)
+ return PyErr_NoMemory();
+
+ if (!PyArg_ParseTuple(args, "s|sOO", &input, &pmu, &pcpus, &pthreads)) {
+ evlist__put(evlist);
return NULL;
+ }
threads = pthreads ? ((struct pyrf_thread_map *)pthreads)->threads : NULL;
cpus = pcpus ? ((struct pyrf_cpu_map *)pcpus)->cpus : NULL;
- evlist__init(&evlist, cpus, threads);
- ret = metricgroup__parse_groups(&evlist, pmu ?: "all", input,
+ perf_evlist__set_maps(&evlist->core, cpus, threads);
+ ret = metricgroup__parse_groups(evlist, pmu ?: "all", input,
/*metric_no_group=*/ false,
/*metric_no_merge=*/ false,
/*metric_no_threshold=*/ true,
@@ -2115,12 +2064,13 @@ static PyObject *pyrf__parse_metrics(PyObject *self, PyObject *args)
/*system_wide=*/true,
/*hardware_aware_grouping=*/ false);
if (ret) {
+ evlist__put(evlist);
errno = -ret;
PyErr_SetFromErrno(PyExc_OSError);
return NULL;
}
- result = pyrf_evlist__from_evlist(&evlist);
- evlist__exit(&evlist);
+ result = pyrf_evlist__from_evlist(evlist);
+ evlist__put(evlist);
return result;
}
diff --git a/tools/perf/util/record.c b/tools/perf/util/record.c
index e867de8ddaaa..8a5fc7d5e43c 100644
--- a/tools/perf/util/record.c
+++ b/tools/perf/util/record.c
@@ -264,7 +264,7 @@ bool evlist__can_select_event(struct evlist *evlist, const char *str)
ret = true;
out_delete:
- evlist__delete(temp_evlist);
+ evlist__put(temp_evlist);
return ret;
}
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
index fe0de2a0277f..1ac6cd43c38b 100644
--- a/tools/perf/util/session.c
+++ b/tools/perf/util/session.c
@@ -264,7 +264,7 @@ void perf_session__delete(struct perf_session *session)
machines__exit(&session->machines);
if (session->data) {
if (perf_data__is_read(session->data))
- evlist__delete(session->evlist);
+ evlist__put(session->evlist);
perf_data__close(session->data);
}
#ifdef HAVE_LIBTRACEEVENT
diff --git a/tools/perf/util/sideband_evlist.c b/tools/perf/util/sideband_evlist.c
index 388846f17bc1..b84a5463e039 100644
--- a/tools/perf/util/sideband_evlist.c
+++ b/tools/perf/util/sideband_evlist.c
@@ -102,7 +102,7 @@ int evlist__start_sb_thread(struct evlist *evlist, struct target *target)
return 0;
if (evlist__create_maps(evlist, target))
- goto out_delete_evlist;
+ goto out_put_evlist;
if (evlist->core.nr_entries > 1) {
bool can_sample_identifier = perf_can_sample_identifier();
@@ -116,25 +116,25 @@ int evlist__start_sb_thread(struct evlist *evlist, struct target *target)
evlist__for_each_entry(evlist, counter) {
if (evsel__open(counter, evlist->core.user_requested_cpus,
evlist->core.threads) < 0)
- goto out_delete_evlist;
+ goto out_put_evlist;
}
if (evlist__mmap(evlist, UINT_MAX))
- goto out_delete_evlist;
+ goto out_put_evlist;
evlist__for_each_entry(evlist, counter) {
if (evsel__enable(counter))
- goto out_delete_evlist;
+ goto out_put_evlist;
}
evlist->thread.done = 0;
if (pthread_create(&evlist->thread.th, NULL, perf_evlist__poll_thread, evlist))
- goto out_delete_evlist;
+ goto out_put_evlist;
return 0;
-out_delete_evlist:
- evlist__delete(evlist);
+out_put_evlist:
+ evlist__put(evlist);
evlist = NULL;
return -1;
}
@@ -145,5 +145,5 @@ void evlist__stop_sb_thread(struct evlist *evlist)
return;
evlist->thread.done = 1;
pthread_join(evlist->thread.th, NULL);
- evlist__delete(evlist);
+ evlist__put(evlist);
}
--
2.54.0.545.g6539524ca2-goog
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox