From 3f1fe48a36b0b6722dc3fd421d93512bac138e9a Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Fri, 2 Oct 2026 12:17:24 -0700 Subject: Merge tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux Pull io_uring fixes from Jens Axboe: - Fix a task_work add use-after-free with SQPOLL. The sqpoll thread could pop and complete the last request while io_req_normal_work_add() was still looking at them after the mpscq push. Use the same approach as DEFER_TASKRUN to protect from that, holding an RCU read lock across the add, and have exit wait for an RCU grace period for SQPOLL rings as well. - CQE32 ring fixes: correct the free entry check for 32b CQEs, zero the big_cqe for aux CQEs, and only post the dummy skip CQE on CQE_MIXED rings - Mark the source filter table as COW when cloning bpf filters, so registering another filter on the source doesn't modify the shared table in place - Initialize the task context before running the BPF loop - Requeue zcrx multishot receives stopped by a local resource - End a TX_TIMESTAMP multishot cmd when the CQ is full (lollipopkit) * tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux: io_uring: fix task_work add use-after-free with SQPOLL io_uring/cmd_net: end TX_TIMESTAMP multishot when the CQ is full io_uring/zcrx: requeue multishot receives stopped by a local resource io_uring: initialize task context before running the BPF loop io_uring: zero big_cqe for aux CQEs on CQE32 rings io_uring: fix free entry check for 32b CQEs on CQE32 rings io_uring: only post the dummy skip CQE on CQE_MIXED rings io_uring/bpf_filter: mark source as COW when cloning filters --- tools/perf/python/counting.py | 36 +++ tools/perf/python/ilist.py | 515 ++++++++++++++++++++++++++++++ tools/perf/python/perf.pyi | 672 ++++++++++++++++++++++++++++++++++++++++ tools/perf/python/perf_live.py | 59 ++++ tools/perf/python/tracepoint.py | 55 ++++ tools/perf/python/twatch.py | 61 ++++ 6 files changed, 1398 insertions(+) create mode 100755 tools/perf/python/counting.py create mode 100755 tools/perf/python/ilist.py create mode 100644 tools/perf/python/perf.pyi create mode 100755 tools/perf/python/perf_live.py create mode 100755 tools/perf/python/tracepoint.py create mode 100755 tools/perf/python/twatch.py (limited to 'tools/perf/python') diff --git a/tools/perf/python/counting.py b/tools/perf/python/counting.py new file mode 100755 index 000000000..02121d2bb --- /dev/null +++ b/tools/perf/python/counting.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# -*- python -*- +# -*- coding: utf-8 -*- + +import argparse +import perf + +def main(event: str): + evlist = perf.parse_events(event) + + for evsel in evlist: + evsel.read_format = perf.FORMAT_TOTAL_TIME_ENABLED | perf.FORMAT_TOTAL_TIME_RUNNING + + evlist.open() + evlist.enable() + + count = 100000 + while count > 0: + count -= 1 + + evlist.disable() + + for evsel in evlist: + for cpu in evsel.cpus(): + for thread in evsel.threads(): + counts = evsel.read(cpu, thread) + print(f"For {evsel} val: {counts.val} enable: {counts.ena} run: {counts.run}") + + evlist.close() + +if __name__ == '__main__': + ap = argparse.ArgumentParser() + ap.add_argument('-e', '--event', help="Events to open", default="cpu-clock,task-clock") + args = ap.parse_args() + main(args.event) diff --git a/tools/perf/python/ilist.py b/tools/perf/python/ilist.py new file mode 100755 index 000000000..0d757ddb4 --- /dev/null +++ b/tools/perf/python/ilist.py @@ -0,0 +1,515 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +"""Interactive perf list.""" + +from abc import ABC, abstractmethod +import argparse +from dataclasses import dataclass +import math +from typing import Any, Dict, Optional, Tuple +import perf +from textual import on +from textual.app import App, ComposeResult +from textual.binding import Binding +from textual.containers import Horizontal, HorizontalGroup, Vertical, VerticalScroll +from textual.css.query import NoMatches +from textual.command import SearchIcon +from textual.screen import ModalScreen +from textual.widgets import Button, Footer, Header, Input, Label, Sparkline, Static, Tree +from textual.widgets.tree import TreeNode + + +def get_info(info: Dict[str, str], key: str): + return (info[key] + "\n") if key in info else "" + + +class TreeValue(ABC): + """Abstraction for the data of value in the tree.""" + + @abstractmethod + def name(self) -> str: + pass + + @abstractmethod + def description(self) -> str: + pass + + @abstractmethod + def matches(self, query: str) -> bool: + pass + + @abstractmethod + def parse(self) -> perf.evlist: + pass + + @abstractmethod + def value(self, evlist: perf.evlist, evsel: perf.evsel, cpu: int, thread: int) -> float: + pass + + +@dataclass +class Metric(TreeValue): + """A metric in the tree.""" + metric_name: str + metric_pmu: str + + def name(self) -> str: + return self.metric_name + + def description(self) -> str: + """Find and format metric description.""" + for metric in perf.metrics(): + if metric["MetricName"] != self.metric_name: + continue + if self.metric_pmu and metric["PMU"] != self.metric_pmu: + continue + desc = get_info(metric, "BriefDescription") + desc += get_info(metric, "PublicDescription") + desc += get_info(metric, "MetricExpr") + desc += get_info(metric, "MetricThreshold") + return desc + return "description" + + def matches(self, query: str) -> bool: + return query in self.metric_name + + def parse(self) -> perf.evlist: + return perf.parse_metrics(self.metric_name, self.metric_pmu) + + def value(self, evlist: perf.evlist, evsel: perf.evsel, cpu: int, thread: int) -> float: + try: + val = evlist.compute_metric(self.metric_name, cpu, thread) + return 0 if math.isnan(val) else val + except: + # Be tolerant of failures to compute metrics on particular CPUs/threads. + return 0 + + +@dataclass +class PmuEvent(TreeValue): + """A PMU and event within the tree.""" + pmu: str + event: str + + def name(self) -> str: + if self.event.startswith(self.pmu) or ':' in self.event: + return self.event + else: + return f"{self.pmu}/{self.event}/" + + def description(self) -> str: + """Find and format event description for {pmu}/{event}/.""" + for p in perf.pmus(): + if p.name() != self.pmu: + continue + for info in p.events(): + if "name" not in info or info["name"] != self.event: + continue + + desc = get_info(info, "topic") + desc += get_info(info, "event_type_desc") + desc += get_info(info, "desc") + desc += get_info(info, "long_desc") + desc += get_info(info, "encoding_desc") + return desc + return "description" + + def matches(self, query: str) -> bool: + return query in self.pmu or query in self.event + + def parse(self) -> perf.evlist: + return perf.parse_events(self.name()) + + def value(self, evlist: perf.evlist, evsel: perf.evsel, cpu: int, thread: int) -> float: + return evsel.read(cpu, thread).val + + +class ErrorScreen(ModalScreen[bool]): + """Pop up dialog for errors.""" + + CSS = """ + ErrorScreen { + align: center middle; + } + """ + + def __init__(self, error: str): + self.error = error + super().__init__() + + def compose(self) -> ComposeResult: + yield Button(f"Error: {self.error}", variant="primary", id="error") + + def on_button_pressed(self, event: Button.Pressed) -> None: + self.dismiss(True) + + +class SearchScreen(ModalScreen[str]): + """Pop up dialog for search.""" + + CSS = """ + SearchScreen Horizontal { + align: center middle; + margin-top: 1; + } + SearchScreen Input { + width: 1fr; + } + """ + + def compose(self) -> ComposeResult: + yield Horizontal(SearchIcon(), Input(placeholder="Event name")) + + def on_input_submitted(self, event: Input.Submitted) -> None: + """Handle the user pressing Enter in the input field.""" + self.dismiss(event.value) + + +class Counter(HorizontalGroup): + """Two labels for a CPU and its counter value.""" + + CSS = """ + Label { + gutter: 1; + } + """ + + def __init__(self, cpu: int) -> None: + self.cpu = cpu + super().__init__() + + def compose(self) -> ComposeResult: + label = f"cpu{self.cpu}" if self.cpu >= 0 else "total" + yield Label(label + " ") + yield Label("0", id=f"counter_{label}") + + +class CounterSparkline(HorizontalGroup): + """A Sparkline for a performance counter.""" + + def __init__(self, cpu: int) -> None: + self.cpu = cpu + super().__init__() + + def compose(self) -> ComposeResult: + label = f"cpu{self.cpu}" if self.cpu >= 0 else "total" + yield Label(label) + yield Sparkline([], summary_function=max, id=f"sparkline_{label}") + + +class IListApp(App): + TITLE = "Interactive Perf List" + + BINDINGS = [ + Binding(key="s", action="search", description="Search", + tooltip="Search events and PMUs"), + Binding(key="n", action="next", description="Next", + tooltip="Next search result or item"), + Binding(key="p", action="prev", description="Previous", + tooltip="Previous search result or item"), + Binding(key="c", action="collapse", description="Collapse", + tooltip="Collapse the current PMU"), + Binding(key="^q", action="quit", description="Quit", + tooltip="Quit the app"), + ] + + CSS = """ + /* Make the 'total' sparkline a different color. */ + #sparkline_total > .sparkline--min-color { + color: $accent; + } + #sparkline_total > .sparkline--max-color { + color: $accent 30%; + } + /* + * Make the active_search initially not displayed with the text in + * the middle of the line. + */ + #active_search { + display: none; + width: 100%; + text-align: center; + } + """ + + def __init__(self, interval: float) -> None: + self.interval = interval + self.evlist = None + self.selected: Optional[TreeValue] = None + self.search_results: list[TreeNode[TreeValue]] = [] + self.cur_search_result: TreeNode[TreeValue] | None = None + super().__init__() + + def expand_and_select(self, node: TreeNode[Any]) -> None: + """Expand select a node in the tree.""" + if node.parent: + node.parent.expand() + if node.parent.parent: + node.parent.parent.expand() + node.expand() + node.tree.select_node(node) + node.tree.scroll_to_node(node) + + def set_searched_tree_node(self, previous: bool) -> None: + """Set the cur_search_result node to either the next or previous.""" + l = len(self.search_results) + + if l < 1: + tree: Tree[TreeValue] = self.query_one("#root", Tree) + if previous: + tree.action_cursor_up() + else: + tree.action_cursor_down() + return + + if self.cur_search_result: + idx = self.search_results.index(self.cur_search_result) + if previous: + idx = idx - 1 if idx > 0 else l - 1 + else: + idx = idx + 1 if idx < l - 1 else 0 + else: + idx = l - 1 if previous else 0 + + node = self.search_results[idx] + if node == self.cur_search_result: + return + + self.cur_search_result = node + self.expand_and_select(node) + + def action_search(self) -> None: + """Search was chosen.""" + def set_initial_focus(event: str | None) -> None: + """Sets the focus after the SearchScreen is dismissed.""" + + search_label = self.query_one("#active_search", Label) + search_label.display = True if event else False + if not event: + return + event = event.lower() + search_label.update(f'Searching for events matching "{event}"') + + tree: Tree[str] = self.query_one("#root", Tree) + + def find_search_results(event: str, node: TreeNode[str], + cursor_seen: bool = False, + match_after_cursor: Optional[TreeNode[str]] = None + ) -> Tuple[bool, Optional[TreeNode[str]]]: + """Find nodes that match the search remembering the one after the cursor.""" + if not cursor_seen and node == tree.cursor_node: + cursor_seen = True + if node.data and node.data.matches(event): + if cursor_seen and not match_after_cursor: + match_after_cursor = node + self.search_results.append(node) + + if node.children: + for child in node.children: + (cursor_seen, match_after_cursor) = \ + find_search_results(event, child, cursor_seen, match_after_cursor) + return (cursor_seen, match_after_cursor) + + self.search_results.clear() + (_, self.cur_search_result) = find_search_results(event, tree.root) + if len(self.search_results) < 1: + self.push_screen(ErrorScreen(f"Failed to find pmu/event or metric {event}")) + search_label.display = False + elif self.cur_search_result: + self.expand_and_select(self.cur_search_result) + else: + self.set_searched_tree_node(previous=False) + + self.push_screen(SearchScreen(), set_initial_focus) + + def action_next(self) -> None: + """Next was chosen.""" + self.set_searched_tree_node(previous=False) + + def action_prev(self) -> None: + """Previous was chosen.""" + self.set_searched_tree_node(previous=True) + + def action_collapse(self) -> None: + """Collapse the part of the tree currently on.""" + tree: Tree[str] = self.query_one("#root", Tree) + node = tree.cursor_node + if node and node.parent: + node.parent.collapse_all() + node.tree.scroll_to_node(node.parent) + + def update_counts(self) -> None: + """Called every interval to update counts.""" + if not self.selected or not self.evlist: + return + + def update_count(cpu: int, count: int): + # Update the raw count display. + counter: Label = self.query(f"#counter_cpu{cpu}" if cpu >= 0 else "#counter_total") + if not counter: + return + counter = counter.first(Label) + counter.update(str(count)) + + # Update the sparkline. + line: Sparkline = self.query(f"#sparkline_cpu{cpu}" if cpu >= 0 else "#sparkline_total") + if not line: + return + line = line.first(Sparkline) + # If there are more events than the width, remove the front event. + if len(line.data) > line.size.width: + line.data.pop(0) + line.data.append(count) + line.mutate_reactive(Sparkline.data) + + # Update the total and each CPU counts, assume there's just 1 evsel. + total = 0 + self.evlist.disable() + for evsel in self.evlist: + for cpu in evsel.cpus(): + aggr = 0 + for thread in evsel.threads(): + aggr += self.selected.value(self.evlist, evsel, cpu, thread) + update_count(cpu, aggr) + total += aggr + update_count(-1, total) + self.evlist.enable() + + def on_mount(self) -> None: + """When App starts set up periodic event updating.""" + self.update_counts() + self.set_interval(self.interval, self.update_counts) + + def set_selected(self, value: TreeValue) -> None: + """Updates the event/description and starts the counters.""" + try: + label_name = self.query_one("#event_name", Label) + event_description = self.query_one("#event_description", Static) + lines = self.query_one("#lines") + except NoMatches: + # A race with rendering, ignore the update as we can't + # mount the assumed output widgets. + return + + self.selected = value + + # Remove previous event information. + if self.evlist: + self.evlist.disable() + self.evlist.close() + old_lines = self.query(CounterSparkline) + for line in old_lines: + line.remove() + old_counters = self.query(Counter) + for counter in old_counters: + counter.remove() + + # Update event/metric text and description. + label_name.update(value.name()) + event_description.update(value.description()) + + # Open the event. + try: + self.evlist = value.parse() + if self.evlist: + self.evlist.open() + self.evlist.enable() + except: + self.evlist = None + + if not self.evlist: + self.push_screen(ErrorScreen(f"Failed to open {value.name()}")) + return + + # Add spark lines for all the CPUs. Note, must be done after + # open so that the evlist CPUs have been computed by propagate + # maps. + line = CounterSparkline(cpu=-1) + lines.mount(line) + for cpu in self.evlist.all_cpus(): + line = CounterSparkline(cpu) + lines.mount(line) + line = Counter(cpu=-1) + lines.mount(line) + for cpu in self.evlist.all_cpus(): + line = Counter(cpu) + lines.mount(line) + + def compose(self) -> ComposeResult: + """Draws the app.""" + def metric_event_tree() -> Tree: + """Create tree of PMUs and metricgroups with events or metrics under.""" + tree: Tree[TreeValue] = Tree("Root", id="root") + pmus = tree.root.add("PMUs") + for pmu in perf.pmus(): + pmu_name = pmu.name().lower() + pmu_node = pmus.add(pmu_name) + try: + for event in sorted(pmu.events(), key=lambda x: x["name"]): + if "deprecated" in event: + continue + if "name" in event: + e = event["name"].lower() + if "alias" in event: + pmu_node.add_leaf(f'{e} ({event["alias"]})', + data=PmuEvent(pmu_name, e)) + else: + pmu_node.add_leaf(e, data=PmuEvent(pmu_name, e)) + except: + # Reading events may fail with EPERM, ignore. + pass + metrics = tree.root.add("Metrics") + groups = set() + for metric in perf.metrics(): + groups.update(metric["MetricGroup"]) + + def add_metrics_to_tree(node: TreeNode[TreeValue], parent: str, pmu: str = None): + for metric in sorted(perf.metrics(), key=lambda x: x["MetricName"]): + metric_pmu = metric.get('PMU') + if pmu and metric_pmu and metric_pmu != pmu: + continue + if parent in metric["MetricGroup"]: + name = metric["MetricName"] + display_name = name + if metric_pmu: + display_name += f" ({metric_pmu})" + node.add_leaf(display_name, data=Metric(name, metric_pmu)) + child_group_name = f'{name}_group' + if child_group_name in groups: + display_child_group_name = child_group_name + if metric_pmu: + display_child_group_name += f" ({metric_pmu})" + add_metrics_to_tree(node.add(display_child_group_name), + child_group_name, + metric_pmu) + + for group in sorted(groups): + if group.endswith('_group'): + continue + add_metrics_to_tree(metrics.add(group), group) + + tree.root.expand() + return tree + + yield Header(id="header") + yield Horizontal(Vertical(metric_event_tree(), id="events"), + Vertical(Label("event name", id="event_name"), + Static("description", markup=False, id="event_description"), + )) + yield Label(id="active_search") + yield VerticalScroll(id="lines") + yield Footer(id="footer") + + @on(Tree.NodeSelected) + def on_tree_node_selected(self, event: Tree.NodeSelected[TreeValue]) -> None: + """Called when a tree node is selected, selecting the event.""" + if event.node.data: + self.set_selected(event.node.data) + + +if __name__ == "__main__": + ap = argparse.ArgumentParser() + ap.add_argument('-I', '--interval', help="Counter update interval in seconds", default=0.1) + args = ap.parse_args() + app = IListApp(float(args.interval)) + app.run() diff --git a/tools/perf/python/perf.pyi b/tools/perf/python/perf.pyi new file mode 100644 index 000000000..58b0b3ed8 --- /dev/null +++ b/tools/perf/python/perf.pyi @@ -0,0 +1,672 @@ +"""Type stubs for the perf Python module.""" +from typing import Callable, Dict, List, Optional, Any, Iterator, Union + +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, Union[str, List[str]]]]: + """Get a list of available metrics. + + Returns: + A list of dictionaries, each describing a metric. + """ + ... + +def syscall_name(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['thread_map'] = 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, + pmu: Optional[str] = None, + cpus: Optional[cpu_map] = None, + threads: Optional['thread_map'] = None +) -> 'evlist': + """Parse a string of metrics or metric groups and return an evlist.""" + ... + +def tracepoint(sys: str, name: str) -> int: + """Returns the tracepoint ID for a given system and name.""" + ... + +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.""" + ... + pid: int + tid: int + ppid: int + cpu: int + +class counts_values: + """Raw counter values.""" + id: int + val: int + ena: int + run: int + lost: int + values: List[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 = ..., + inherit_stat: bool = ..., + enable_on_exec: bool = ..., + task: bool = ..., + watermark: int = ..., + precise_ip: int = ..., + mmap_data: bool = ..., + sample_id_all: bool = ..., + wakeup_events: int = ..., + bp_type: int = ..., + bp_addr: int = ..., + bp_len: int = ..., + 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.""" + ... + tracking: bool + config: int + read_format: int + sample_period: int + sample_type: int + size: int + type: int + wakeup_events: int + + +class _sample_members: + sample_pid: int + sample_tid: int + sample_time: int + sample_id: int + sample_stream_id: int + sample_period: int + sample_cpu: int + +class sample_event(_sample_members): + """Represents a sample event from perf.""" + evsel: evsel + sample_ip: int + sample_addr: int + sample_phys_addr: int + sample_weight: int + sample_data_src: int + sample_insn_count: int + sample_cyc_count: int + type: int + raw_buf: bytes + dso: str + dso_long_name: str + dso_bid: Optional[bytes] + map_start: int + map_end: int + map_pgoff: int + symbol: str + sym_start: int + sym_end: int + brstack: Optional['branch_stack'] + callchain: Optional['callchain'] + def srccode(self) -> str: ... + def insn(self) -> str: ... + def __getattr__(self, name: str) -> Any: ... + +class mmap_event(_sample_members): + """Represents a mmap event from perf.""" + type: int + misc: int + pid: int + tid: int + start: int + len: int + pgoff: int + filename: str + evsel: Optional['evsel'] + +class mmap2_event(_sample_members): + """Represents a mmap2 event from perf.""" + type: int + misc: int + pid: int + tid: int + start: int + len: int + pgoff: int + prot: int + flags: int + filename: str + maj: Optional[int] + min: Optional[int] + ino: Optional[int] + ino_generation: Optional[int] + build_id: Optional[bytes] + evsel: Optional['evsel'] + +class lost_event(_sample_members): + """Represents a lost events record.""" + type: int + id: int + lost: int + evsel: Optional['evsel'] + +class comm_event(_sample_members): + """Represents a COMM record.""" + type: int + pid: int + tid: int + comm: str + evsel: Optional['evsel'] + +class task_event(_sample_members): + """Represents an EXIT or FORK record.""" + type: int + pid: int + ppid: int + tid: int + ptid: int + time: int + evsel: Optional['evsel'] + +class throttle_event(_sample_members): + """Represents a THROTTLE or UNTHROTTLE record.""" + type: int + time: int + id: int + stream_id: int + evsel: Optional['evsel'] + +class read_event(_sample_members): + """Represents a READ record.""" + type: int + pid: int + tid: int + evsel: Optional['evsel'] + +class switch_event(_sample_members): + """Represents a SWITCH or SWITCH_CPU_WIDE record.""" + type: int + next_prev_pid: int + next_prev_tid: int + evsel: Optional['evsel'] + +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: + """Sequence of branch entries in the branch stack.""" + def __len__(self) -> int: ... + def __getitem__(self, index: int) -> branch_entry: ... + +class callchain_node: + """Represents a frame in the callchain.""" + ip: int + symbol: Optional[str] + dso: Optional[str] + +class callchain: + """Sequence of callchain frames.""" + def __len__(self) -> int: ... + def __getitem__(self, index: int) -> callchain_node: ... + +class stat_event(_sample_members): + """Represents a stat event from perf.""" + type: int + id: int + cpu: int + thread: int + val: int + ena: int + run: int + evsel: Optional['evsel'] + +class stat_round_event(_sample_members): + """Represents a stat round event from perf.""" + type: int + time: int + stat_round_type: int + evsel: Optional['evsel'] + +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[Any]: + """Read a sample event from a specific CPU. + + Args: + cpu: The CPU number. + + Returns: + A sample_event or other event type 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) -> None: + """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.""" diff --git a/tools/perf/python/perf_live.py b/tools/perf/python/perf_live.py new file mode 100755 index 000000000..616b5f657 --- /dev/null +++ b/tools/perf/python/perf_live.py @@ -0,0 +1,59 @@ +# 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.""" + try: + self.evlist.open() + 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: + for _ in range(1000): # Limit to 1000 events per CPU per poll to prevent starvation + try: + event = self.evlist.read_on_cpu(cpu) + except TypeError as e: + if "Unknown CPU" in str(e): + # CPU might be unmapped or offline, wait for mmap event + break + if "Unexpected header type" in str(e): + # Ignore valid but unsupported event types + continue + raise + + if event is None: + break + + if event.type == perf.RECORD_SAMPLE: + self.sample_callback(event) + except KeyboardInterrupt: + pass + finally: + self.evlist.close() diff --git a/tools/perf/python/tracepoint.py b/tools/perf/python/tracepoint.py new file mode 100755 index 000000000..15b0c8268 --- /dev/null +++ b/tools/perf/python/tracepoint.py @@ -0,0 +1,55 @@ +#! /usr/bin/env python +# SPDX-License-Identifier: GPL-2.0 +# -*- python -*- +# -*- coding: utf-8 -*- + +import perf + +def change_proctitle(): + try: + import setproctitle + setproctitle.setproctitle("tracepoint.py") + except: + print("Install the setproctitle python package to help with top and friends") + +def main(): + change_proctitle() + cpus = perf.cpu_map() + threads = perf.thread_map(-1) + evlist = perf.parse_events("sched:sched_switch", cpus, threads) + # Disable tracking of mmaps and similar that are unnecessary. + for ev in evlist: + ev.tracking = False + # Configure evsels with default record options. + evlist.config() + # Simplify the sample_type and read_format of evsels + for ev in evlist: + ev.sample_type = ev.sample_type & ~perf.SAMPLE_IP + ev.read_format = 0 + + evlist.open() + evlist.mmap() + evlist.enable(); + + while True: + evlist.poll(timeout = -1) + for cpu in cpus: + event = evlist.read_on_cpu(cpu) + if not event: + continue + + if not isinstance(event, perf.sample_event): + continue + + print("time %u prev_comm=%s prev_pid=%d prev_prio=%d prev_state=0x%x ==> next_comm=%s next_pid=%d next_prio=%d" % ( + event.sample_time, + event.prev_comm, + event.prev_pid, + event.prev_prio, + event.prev_state, + event.next_comm, + event.next_pid, + event.next_prio)) + +if __name__ == '__main__': + main() diff --git a/tools/perf/python/twatch.py b/tools/perf/python/twatch.py new file mode 100755 index 000000000..04f3db29b --- /dev/null +++ b/tools/perf/python/twatch.py @@ -0,0 +1,61 @@ +#! /usr/bin/env python +# SPDX-License-Identifier: GPL-2.0-only +# -*- python -*- +# -*- coding: utf-8 -*- +# twatch - Experimental use of the perf python interface +# Copyright (C) 2011 Arnaldo Carvalho de Melo +# + +import perf + +def main(context_switch = 0, thread = -1): + cpus = perf.cpu_map() + threads = perf.thread_map(thread) + evsel = perf.evsel(type = perf.TYPE_SOFTWARE, + config = perf.COUNT_SW_DUMMY, + task = 1, comm = 1, mmap = 0, freq = 0, + wakeup_events = 1, watermark = 1, + sample_id_all = 1, context_switch = context_switch, + sample_type = perf.SAMPLE_PERIOD | perf.SAMPLE_TID | perf.SAMPLE_CPU) + + """What we want are just the PERF_RECORD_ lifetime events for threads, + using the default, PERF_TYPE_HARDWARE + PERF_COUNT_HW_CYCLES & freq=1 + (the default), makes perf reenable irq_vectors:local_timer_entry, when + disabling nohz, not good for some use cases where all we want is to get + threads comes and goes... So use (perf.TYPE_SOFTWARE, perf_COUNT_SW_DUMMY, + freq=0) instead.""" + + evsel.open(cpus = cpus, threads = threads); + evlist = perf.evlist(cpus, threads) + evlist.add(evsel) + evlist.mmap() + while True: + evlist.poll(timeout = -1) + for cpu in cpus: + event = evlist.read_on_cpu(cpu) + if not event: + continue + print("cpu: {0}, pid: {1}, tid: {2} {3}".format(event.sample_cpu, + event.sample_pid, + event.sample_tid, + event)) + +if __name__ == '__main__': + """ + To test the PERF_RECORD_SWITCH record, pick a pid and replace + in the following line. + + Example output: + +cpu: 3, pid: 31463, tid: 31593 { type: context_switch, next_prev_pid: 31463, next_prev_tid: 31593, switch_out: 1 } +cpu: 1, pid: 31463, tid: 31489 { type: context_switch, next_prev_pid: 31463, next_prev_tid: 31489, switch_out: 1 } +cpu: 2, pid: 31463, tid: 31496 { type: context_switch, next_prev_pid: 31463, next_prev_tid: 31496, switch_out: 1 } +cpu: 3, pid: 31463, tid: 31491 { type: context_switch, next_prev_pid: 31463, next_prev_tid: 31491, switch_out: 0 } + + It is possible as well to use event.misc & perf.PERF_RECORD_MISC_SWITCH_OUT + to figure out if this is a context switch in or out of the monitored threads. + + If bored, please add command line option parsing support for these options :-) + """ + # main(context_switch = 1, thread = 31463) + main() -- cgit v1.3.1