| #!/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 | |
| """ | |
| from __future__ import annotations | |
| 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", | |
| } | |
| _GFP_DMA = 1 << 0 | |
| _GFP_HIGHMEM = 1 << 1 | |
| _GFP_DMA32 = 1 << 2 | |
| _GFP_MOVABLE = 1 << 3 | |
| _GFP_RECLAIMABLE = 1 << 4 | |
| _GFP_HIGH = 1 << 5 | |
| _GFP_IO = 1 << 6 | |
| _GFP_FS = 1 << 7 | |
| _GFP_ZERO = 1 << 8 | |
| _GFP_DIRECT_RECLAIM = 1 << 10 | |
| _GFP_KSWAPD_RECLAIM = 1 << 11 | |
| _GFP_WRITE = 1 << 12 | |
| _GFP_NOWARN = 1 << 13 | |
| _GFP_RETRY_MAYFAIL = 1 << 14 | |
| _GFP_NOFAIL = 1 << 15 | |
| _GFP_NORETRY = 1 << 16 | |
| _GFP_MEMALLOC = 1 << 17 | |
| _GFP_COMP = 1 << 18 | |
| _GFP_NOMEMALLOC = 1 << 19 | |
| _GFP_HARDWALL = 1 << 20 | |
| _GFP_THISNODE = 1 << 21 | |
| _GFP_ACCOUNT = 1 << 22 | |
| _GFP_ZEROTAGS = 1 << 23 | |
| _GFP_RECLAIM = _GFP_DIRECT_RECLAIM | _GFP_KSWAPD_RECLAIM | |
| _GFP_KERNEL = _GFP_RECLAIM | _GFP_IO | _GFP_FS | |
| _GFP_USER = _GFP_KERNEL | _GFP_HARDWALL | |
| _GFP_HIGHUSER = _GFP_USER | _GFP_HIGHMEM | |
| _GFP_HIGHUSER_MOVABLE = _GFP_HIGHUSER | _GFP_MOVABLE | |
| _GFP_TRANSHUGE_LIGHT = ( | |
| _GFP_HIGHUSER_MOVABLE | _GFP_COMP | _GFP_NOMEMALLOC | _GFP_NOWARN | |
| ) & ~_GFP_RECLAIM | |
| _GFP_TRANSHUGE = _GFP_TRANSHUGE_LIGHT | _GFP_DIRECT_RECLAIM | |
| GFP_FLAG_NAMES = [ | |
| (_GFP_TRANSHUGE, "GFP_TRANSHUGE"), | |
| (_GFP_TRANSHUGE_LIGHT, "GFP_TRANSHUGE_LIGHT"), | |
| (_GFP_HIGHUSER_MOVABLE, "GFP_HIGHUSER_MOVABLE"), | |
| (_GFP_HIGHUSER, "GFP_HIGHUSER"), | |
| (_GFP_USER, "GFP_USER"), | |
| (_GFP_KERNEL | _GFP_ACCOUNT, "GFP_KERNEL_ACCOUNT"), | |
| (_GFP_KERNEL, "GFP_KERNEL"), | |
| (_GFP_RECLAIM | _GFP_IO, "GFP_NOFS"), | |
| (_GFP_HIGH | _GFP_KSWAPD_RECLAIM, "GFP_ATOMIC"), | |
| (_GFP_RECLAIM, "GFP_NOIO"), | |
| (_GFP_KSWAPD_RECLAIM | _GFP_NOWARN, "GFP_NOWAIT"), | |
| (_GFP_DMA, "GFP_DMA"), | |
| (_GFP_DMA32, "GFP_DMA32"), | |
| (_GFP_RECLAIM, "__GFP_RECLAIM"), | |
| (_GFP_DMA, "__GFP_DMA"), | |
| (_GFP_HIGHMEM, "__GFP_HIGHMEM"), | |
| (_GFP_DMA32, "__GFP_DMA32"), | |
| (_GFP_MOVABLE, "__GFP_MOVABLE"), | |
| (_GFP_RECLAIMABLE, "__GFP_RECLAIMABLE"), | |
| (_GFP_HIGH, "__GFP_HIGH"), | |
| (_GFP_IO, "__GFP_IO"), | |
| (_GFP_FS, "__GFP_FS"), | |
| (_GFP_ZERO, "__GFP_ZERO"), | |
| (_GFP_DIRECT_RECLAIM, "__GFP_DIRECT_RECLAIM"), | |
| (_GFP_KSWAPD_RECLAIM, "__GFP_KSWAPD_RECLAIM"), | |
| (_GFP_WRITE, "__GFP_WRITE"), | |
| (_GFP_NOWARN, "__GFP_NOWARN"), | |
| (_GFP_RETRY_MAYFAIL, "__GFP_RETRY_MAYFAIL"), | |
| (_GFP_NOFAIL, "__GFP_NOFAIL"), | |
| (_GFP_NORETRY, "__GFP_NORETRY"), | |
| (_GFP_MEMALLOC, "__GFP_MEMALLOC"), | |
| (_GFP_COMP, "__GFP_COMP"), | |
| (_GFP_NOMEMALLOC, "__GFP_NOMEMALLOC"), | |
| (_GFP_HARDWALL, "__GFP_HARDWALL"), | |
| (_GFP_THISNODE, "__GFP_THISNODE"), | |
| (_GFP_ACCOUNT, "__GFP_ACCOUNT"), | |
| (_GFP_ZEROTAGS, "__GFP_ZEROTAGS"), | |
| ] | |
| def trace_begin() -> None: | |
| """Called at the start of trace processing.""" | |
| print("in trace_begin") | |
| def trace_end() -> None: | |
| """Called at the end of trace processing.""" | |
| print_unhandled() | |
| print("in trace_end") | |
| def symbol_str(event_name: str, field_name: str, value: int) -> str: | |
| """Resolves symbol values to strings.""" | |
| # Note: The standalone Python API currently lacks dynamic libtraceevent | |
| # formatting (equivalent to _perf_trace_context.symbol_str()) | |
| 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.""" | |
| # Note: The standalone Python API currently lacks dynamic libtraceevent | |
| # formatting (equivalent to _perf_trace_context.flag_str()) | |
| if event_name == "kmem__kmalloc" and field_name == "gfp_flags": | |
| if value == 0: | |
| return "none" | |
| names = [] | |
| rem = value | |
| for mask, name in GFP_FLAG_NAMES: | |
| if (rem & mask) == mask: | |
| names.append(name) | |
| rem &= ~mask | |
| if rem: | |
| names.append(f"0x{rem:x}") | |
| return "|".join(names) | |
| 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 = "[unknown]" | |
| try: | |
| if session: | |
| thread = session.find_thread(sample.sample_pid, sample.sample_tid) | |
| if thread: | |
| comm = thread.comm() or "[unknown]" | |
| except (TypeError, AttributeError): | |
| pass | |
| print(f"{event_name:<20} {sample.sample_cpu:5} {secs:05}.{nsecs:09} " | |
| f"{sample.sample_tid: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}, ", end='') | |
| 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', getattr(sample, 'vec', 0))}") | |
| def kmem__kmalloc(sample: perf.sample_event) -> None: | |
| """Handles kmem:kmalloc events.""" | |
| print_header("kmem__kmalloc", sample) | |
| print_uncommon(sample) | |
| print(f"call_site={getattr(sample, 'call_site', 0):#x}, " | |
| f"ptr={getattr(sample, 'ptr', 0):#x}, " | |
| f"bytes_req={getattr(sample, 'bytes_req', 0):d}, " | |
| f"bytes_alloc={getattr(sample, 'bytes_alloc', 0):d}, " | |
| f"gfp_flags={flag_str('kmem__kmalloc', 'gfp_flags', getattr(sample, 'gfp_flags', 0))}") | |
| 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.startswith("evsel(") and event_name.endswith(")"): | |
| event_name = event_name[6:-1] | |
| if event_name.startswith("irq:softirq_entry"): | |
| irq__softirq_entry(sample) | |
| elif event_name.startswith("kmem:kmalloc"): | |
| 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() | |
| try: | |
| session = perf.session(perf.data(args.input), sample=process_event) | |
| try: | |
| session.process_events() | |
| except KeyboardInterrupt: | |
| pass | |
| trace_end() | |
| finally: | |
| # Break the reference cycle between the global session and the | |
| # process_event callback so the C perf.session object is freed. | |
| session = None |