summaryrefslogtreecommitdiffstats
path: root/tools/perf/python
diff options
context:
space:
mode:
Diffstat (limited to 'tools/perf/python')
-rwxr-xr-xtools/perf/python/counting.py36
-rwxr-xr-xtools/perf/python/ilist.py515
-rw-r--r--tools/perf/python/perf.pyi672
-rwxr-xr-xtools/perf/python/perf_live.py59
-rwxr-xr-xtools/perf/python/tracepoint.py55
-rwxr-xr-xtools/perf/python/twatch.py61
6 files changed, 1398 insertions, 0 deletions
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 <acme@redhat.com>
+#
+
+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()