blob: c233a29eb8f96e29bd1f10dc6378fbbda4d4bfe2 [file]
#!/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
"""
from __future__ import annotations
import argparse
from typing import Dict, List, Tuple
import contextlib
from ctypes import addressof, create_string_buffer
import io
import os
import struct
import sys
from typing import Any, Optional
import typing
import perf
try:
from libxed import LibXED as _LibXED
LibXED: Optional[type[_LibXED]] = _LibXED
except ImportError:
LibXED = None
def sample_flags_to_name(flags: int) -> str:
"""Implement perf's sample_flags_to_name."""
if not isinstance(flags, int) or flags == 0:
return "".ljust(21)
# PERF_IP_FLAG_* bit positions (from tools/perf/util/event.h):
# bit 0: PERF_IP_FLAG_BRANCH
# bit 1: PERF_IP_FLAG_CALL
# bit 2: PERF_IP_FLAG_RETURN
# bit 3: PERF_IP_FLAG_CONDITIONAL
# bit 4: PERF_IP_FLAG_SYSCALLRET
# bit 5: PERF_IP_FLAG_ASYNC
# bit 6: PERF_IP_FLAG_INTERRUPT
# bit 7: PERF_IP_FLAG_TX_ABORT
# bit 8: PERF_IP_FLAG_TRACE_BEGIN
# bit 9: PERF_IP_FLAG_TRACE_END
# bit 10: PERF_IP_FLAG_IN_TX
# bit 11: PERF_IP_FLAG_VMENTRY
# bit 12: PERF_IP_FLAG_VMEXIT
# bit 13: PERF_IP_FLAG_INTR_DISABLE
# bit 14: PERF_IP_FLAG_INTR_TOGGLE
# bit 15: PERF_IP_FLAG_BRANCH_MISS
# bit 16: PERF_IP_FLAG_NOT_TAKEN
sample_flags = [
((1 << 0) | (1 << 1), "call"),
((1 << 0) | (1 << 2), "return"),
((1 << 0) | (1 << 3), "jcc"),
((1 << 0), "jmp"),
((1 << 0) | (1 << 1) | (1 << 6), "int"),
((1 << 0) | (1 << 2) | (1 << 6), "iret"),
((1 << 0) | (1 << 1) | (1 << 4), "syscall"),
((1 << 0) | (1 << 2) | (1 << 4), "sysret"),
((1 << 0) | (1 << 5), "async"),
((1 << 0) | (1 << 1) | (1 << 5) | (1 << 6), "hw int"),
((1 << 0) | (1 << 7), "tx abrt"),
((1 << 0) | (1 << 8), "tr strt"),
((1 << 0) | (1 << 9), "tr end"),
((1 << 0) | (1 << 1) | (1 << 11), "vmentry"),
((1 << 0) | (1 << 1) | (1 << 12), "vmexit"),
]
additional_mask = (1 << 10) | (1 << 13) | (1 << 14)
branch_event_mask = (1 << 15) | (1 << 16)
xf = flags & additional_mask
rem_flags = flags & ~additional_mask
if rem_flags & (1 << 8):
prefix = "tr strt "
elif rem_flags & (1 << 9):
prefix = "tr end "
else:
prefix = ""
rem_flags &= ~((1 << 8) | (1 << 9))
types = rem_flags & ~branch_event_mask
type_name = ""
for f_mask, f_name in sample_flags:
if f_mask == types:
type_name = f_name
break
s = prefix + type_name
ev_parts = []
if rem_flags & (1 << 15):
ev_parts.append("miss")
if rem_flags & (1 << 16):
ev_parts.append("not_taken")
if ev_parts:
s += "/" + ",".join(ev_parts) + "/"
if xf:
xs = "("
if xf & (1 << 10):
xs += "x"
if xf & (1 << 13):
xs += "D"
if xf & (1 << 14):
xs += "t"
xs += ")"
if len(s) + len(xs) < 21:
s = s + xs.rjust(21 - len(s))
else:
s = s + " " + xs
return s.ljust(21)
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
self.inst: Any = None
if self.insn and LibXED is not None:
try:
self.disassembler = LibXED()
self.inst = self.disassembler.instruction()
except (OSError, ValueError, KeyError, RuntimeError, TypeError, AttributeError,
ImportError) as e:
print(f"Failed to initialize LibXED: {e}")
self.disassembler = None
self.inst = None
def print_ptwrite(self, raw_buf: bytes) -> None:
"""Print PTWRITE data."""
if not raw_buf:
return
try:
data = struct.unpack_from("<IQ", raw_buf)
flags = data[0]
payload = data[1]
except struct.error:
return
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
try:
data = struct.unpack_from("<BBBBII", raw_buf)
except struct.error:
return
cbr = data[0]
f = (data[4] + 500) // 1000
if data[2] == 0:
return
p = ((cbr * 1000 // data[2]) + 5) // 10
print(f"{cbr:3d} freq: {f:4d} MHz ({p:3d}%)", end=' ')
def print_mwait(self, raw_buf: bytes) -> None:
"""Print MWAIT data."""
try:
data = struct.unpack_from("<IQ", raw_buf)
except struct.error:
return
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."""
try:
data = struct.unpack_from("<IQ", raw_buf)
except struct.error:
return
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."""
try:
data = struct.unpack_from("<I", raw_buf)
except struct.error:
return
flags = data[0]
exact_ip = flags & 1
print(f"IP: {exact_ip}", end=' ')
def print_pwrx(self, raw_buf: bytes) -> None:
"""Print PWRX data."""
try:
data = struct.unpack_from("<IQ", raw_buf)
except struct.error:
return
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."""
try:
data = struct.unpack_from("<IQ", raw_buf)
except struct.error:
return
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
try:
data = struct.unpack_from("<BBH", raw_buf)
except struct.error:
return
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
try:
data = struct.unpack_from("<QQ", raw_buf, pos)
except struct.error:
return
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."""
try:
data = struct.unpack_from("<IQ", raw_buf)
except struct.error:
return
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.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:5d}/{tid:<5d} "
f"[{cpu:03d}] {ts // 1000000000:9d}.{ts % 1000000000:09d} ")
return (f"{comm:>16s} {pid:5d}/{tid:<5d} [{cpu:03d}] "
f"{ts // 1000000000:9d}.{ts % 1000000000:09d} ")
def print_common_start(self, comm: str, sample: perf.sample_event, name: str) -> None:
"""Print common start info."""
flags_disp = sample_flags_to_name(getattr(sample, "flags", 0))
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."""
raw_flags = getattr(sample, "flags", 0)
if isinstance(raw_flags, int) and (raw_flags & (1 << 10)):
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.inst if self.inst is not None else self.disassembler.instruction()
is_64_bit = getattr(self.session, "is_64_bit", True) if self.session else True
self.disassembler.set_mode(inst, 0 if is_64_bit else 1)
buf = create_string_buffer(insn)
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.sym_offset:#x}" if getattr(sample, "sym_offset", None) is not None else ""
cyc_cnt = getattr(sample, "sample_cyc_count", 0)
if cyc_cnt:
insn_cnt = getattr(sample, "sample_insn_count", 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=' ')
else:
print(f"{ip:16x}", end=' ')
print(f"{symbol}{offs} ({dso})", end=' ')
else:
print(f"{ip:16x} {symbol}{offs} ({dso})", end=' ')
addr = getattr(sample, "sample_addr", getattr(sample, "addr", 0))
addr_correlates_sym = (
bool(addr)
or getattr(sample, "addr_symbol", None) is not None
or getattr(sample, "addr_dso", None) is not None
)
if addr_correlates_sym:
addr_dso = (sample.addr_dso or '[unknown]')
addr_symbol = (sample.addr_symbol or '[unknown]')
addr_offs = (f"+{sample.addr_sym_offset:#x}"
if getattr(sample, "addr_sym_offset", None) is not None 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.sym_offset:#x}"
if getattr(sample, "sym_offset", None) is not None 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)
source_file_name: typing.Any = None
line_number: typing.Any = 0
source_line: typing.Any = None
try:
res_srcc = sample.srccode()
if res_srcc:
source_file_name, line_number, source_line = res_srcc
except (AttributeError, ValueError, TypeError):
pass
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."""
cpu = getattr(sample, "sample_cpu", getattr(sample, "cpu", 0))
if cpu in self.switch_str:
print(self.switch_str[cpu])
del self.switch_str[cpu]
comm = "Unknown"
if hasattr(self, 'session') and self.session:
try:
thread = self.session.find_thread(sample.sample_pid, sample.sample_tid)
if thread:
comm = thread.comm() or "Unknown"
except (OSError, ValueError, KeyError, RuntimeError, TypeError, AttributeError):
pass
# Python < 3.9 compatibility
name = str(sample.evsel)
if name.startswith("evsel("):
name = name[6:-1]
dso = (sample.dso or '[unknown]')
symbol = (sample.symbol or '[unknown]')
raw_buf = getattr(sample, 'raw_buf', b'') or 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_context_switch(self, event: perf.switch_event) -> None:
"""Process context switch."""
cpu = getattr(event, "sample_cpu", getattr(event, "cpu", 0))
pid = getattr(event, "sample_pid", getattr(event, "pid", 0))
tid = getattr(event, "sample_tid", getattr(event, "tid", 0))
ts = getattr(event, "sample_time", getattr(event, "time", 0))
np_pid = getattr(event, "next_prev_pid", None)
np_tid = getattr(event, "next_prev_tid", None)
machine_pid = getattr(event, "machine_pid", -1)
vcpu = getattr(event, "vcpu", -1)
misc = getattr(event, "misc", 0)
out = bool(misc & (1 << 13))
out_preempt = bool(misc & (1 << 14))
if self.args.interleave:
self.flush_stashed_output()
if out:
out_str = "Switch out "
else:
out_str = "Switch In "
preempt_str = "preempt" if out_preempt else ""
if machine_pid == -1:
machine_str = ""
elif vcpu == -1:
machine_str = f"machine PID {machine_pid}"
else:
machine_str = f"machine PID {machine_pid} VCPU {vcpu}"
np_str = f"{np_pid:5d}/{np_tid:<5d} " if np_pid is not None and np_tid is not None else ""
c_str = (f"{out_str:>16s} {pid:5d}/{tid:<5d} [{cpu:03d}] "
f"{ts // 1000000000:9d}.{ts % 1000000000:09d} "
f"{np_str}{machine_str} {preempt_str}")
if self.args.all_switch_events:
print(c_str)
else:
self.switch_str[cpu] = c_str
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)
ap.add_argument("--itrace", default=os.environ.get("PERF_ITRACE"), help="itrace options")
args = ap.parse_args()
if args.itrace is None:
if args.insn_trace or args.src_trace:
args.itrace = "i0nsepwxI"
else:
args.itrace = "bepwxI"
analyzer = IntelPTAnalyzer(args)
try:
# Note: Python API currently lacks auxtrace_error callbacks affecting
# chronological interleaving
try:
analyzer.session = perf.session(
perf.data(args.input),
sample=analyzer.process_event,
context_switch=analyzer.process_context_switch,
itrace=args.itrace
)
try:
analyzer.session.process_events()
except KeyboardInterrupt:
pass
finally:
# Break the reference cycle between analyzer.session and the bound
# analyzer.process_event / process_context_switch callbacks so the
# underlying C perf.session object is freed.
analyzer.session = None
if args.interleave:
analyzer.flush_stashed_output()
print("End")
except BrokenPipeError:
sys.exit(0)
except (OSError, ValueError, KeyError, RuntimeError, TypeError, AttributeError):
import traceback
traceback.print_exc()
sys.exit(1)