* [PATCH v1] perf python: Fix ilist TreeNode string type drift
@ 2026-09-10 17:44 Ian Rogers
2026-09-10 21:15 ` [PATCH v2] " Ian Rogers
0 siblings, 1 reply; 3+ messages in thread
From: Ian Rogers @ 2026-09-10 17:44 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Updates Tree type hints to correctly use TreeNode[TreeValue]
across all assignments preventing mypy strictness mismatches.
Address some other minor stylistic issues reported by mypy, pylint and
Sashiko.
Assisted-by: Antigravity:gemini-3.1-pro
Signed-off-by: Ian Rogers <irogers@google.com>
---
tools/perf/python/ilist.py | 70 ++++++++++++++++++++------------------
1 file changed, 37 insertions(+), 33 deletions(-)
diff --git a/tools/perf/python/ilist.py b/tools/perf/python/ilist.py
index 0d757ddb4795..4797818ef42e 100755
--- a/tools/perf/python/ilist.py
+++ b/tools/perf/python/ilist.py
@@ -6,7 +6,7 @@ from abc import ABC, abstractmethod
import argparse
from dataclasses import dataclass
import math
-from typing import Any, Dict, Optional, Tuple
+from typing import Any, Dict, List, Optional, Set, Tuple
import perf
from textual import on
from textual.app import App, ComposeResult
@@ -19,7 +19,7 @@ from textual.widgets import Button, Footer, Header, Input, Label, Sparkline, Sta
from textual.widgets.tree import TreeNode
-def get_info(info: Dict[str, str], key: str):
+def get_info(info: Dict[str, Any], key: str):
return (info[key] + "\n") if key in info else ""
@@ -234,7 +234,7 @@ class IListApp(App):
def __init__(self, interval: float) -> None:
self.interval = interval
- self.evlist = None
+ self.evlist: Optional[perf.evlist] = None
self.selected: Optional[TreeValue] = None
self.search_results: list[TreeNode[TreeValue]] = []
self.cur_search_result: TreeNode[TreeValue] | None = None
@@ -290,12 +290,12 @@ class IListApp(App):
event = event.lower()
search_label.update(f'Searching for events matching "{event}"')
- tree: Tree[str] = self.query_one("#root", Tree)
+ tree: Tree[TreeValue] = self.query_one("#root", Tree)
- def find_search_results(event: str, node: TreeNode[str],
+ def find_search_results(event: str, node: TreeNode[TreeValue],
cursor_seen: bool = False,
- match_after_cursor: Optional[TreeNode[str]] = None
- ) -> Tuple[bool, Optional[TreeNode[str]]]:
+ match_after_cursor: Optional[TreeNode[TreeValue]] = None
+ ) -> Tuple[bool, Optional[TreeNode[TreeValue]]]:
"""Find nodes that match the search remembering the one after the cursor."""
if not cursor_seen and node == tree.cursor_node:
cursor_seen = True
@@ -332,7 +332,7 @@ class IListApp(App):
def action_collapse(self) -> None:
"""Collapse the part of the tree currently on."""
- tree: Tree[str] = self.query_one("#root", Tree)
+ tree: Tree[TreeValue] = self.query_one("#root", Tree)
node = tree.cursor_node
if node and node.parent:
node.parent.collapse_all()
@@ -343,31 +343,34 @@ class IListApp(App):
if not self.selected or not self.evlist:
return
- def update_count(cpu: int, count: int):
+ def update_count(cpu: int, count: int | float):
# Update the raw count display.
- counter: Label = self.query(f"#counter_cpu{cpu}" if cpu >= 0 else "#counter_total")
- if not counter:
+ counter_query = self.query(f"#counter_cpu{cpu}" if cpu >= 0 else "#counter_total")
+ if not counter_query:
return
- counter = counter.first(Label)
+ counter: Label = counter_query.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:
+ line_query = self.query(f"#sparkline_cpu{cpu}" if cpu >= 0 else "#sparkline_total")
+ if not line_query:
return
- line = line.first(Sparkline)
+ line: Sparkline = line_query.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)
+ if line.data is not None:
+ if len(line.data) > line.size.width:
+ line.data = line.data[1:]
+ line.data = list(line.data) + [float(count)]
+ else:
+ line.data = [float(count)]
+ line.refresh()
# Update the total and each CPU counts, assume there's just 1 evsel.
- total = 0
+ total: float = 0.0
self.evlist.disable()
for evsel in self.evlist:
for cpu in evsel.cpus():
- aggr = 0
+ aggr: float = 0.0
for thread in evsel.threads():
aggr += self.selected.value(self.evlist, evsel, cpu, thread)
update_count(cpu, aggr)
@@ -424,16 +427,16 @@ class IListApp(App):
# 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)
+ line_sp = CounterSparkline(cpu=-1)
+ lines.mount(line_sp)
for cpu in self.evlist.all_cpus():
- line = CounterSparkline(cpu)
- lines.mount(line)
- line = Counter(cpu=-1)
- lines.mount(line)
+ c_sp = CounterSparkline(cpu)
+ lines.mount(c_sp)
+ c_val = Counter(cpu=-1)
+ lines.mount(c_val)
for cpu in self.evlist.all_cpus():
- line = Counter(cpu)
- lines.mount(line)
+ c_val2 = Counter(cpu)
+ lines.mount(c_val2)
def compose(self) -> ComposeResult:
"""Draws the app."""
@@ -459,17 +462,18 @@ class IListApp(App):
# Reading events may fail with EPERM, ignore.
pass
metrics = tree.root.add("Metrics")
- groups = set()
+ groups: Set[str] = set()
for metric in perf.metrics():
groups.update(metric["MetricGroup"])
- def add_metrics_to_tree(node: TreeNode[TreeValue], parent: str, pmu: str = None):
+ def add_metrics_to_tree(node: TreeNode[TreeValue], parent: str, pmu: Optional[str] = None):
for metric in sorted(perf.metrics(), key=lambda x: x["MetricName"]):
- metric_pmu = metric.get('PMU')
+ metric_pmu_raw = metric.get('PMU')
+ metric_pmu = str(metric_pmu_raw) if metric_pmu_raw else ''
if pmu and metric_pmu and metric_pmu != pmu:
continue
if parent in metric["MetricGroup"]:
- name = metric["MetricName"]
+ name = str(metric["MetricName"])
display_name = name
if metric_pmu:
display_name += f" ({metric_pmu})"
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH v2] perf python: Fix ilist TreeNode string type drift
2026-09-10 17:44 [PATCH v1] perf python: Fix ilist TreeNode string type drift Ian Rogers
@ 2026-09-10 21:15 ` Ian Rogers
2026-09-13 21:40 ` Arnaldo Carvalho de Melo
0 siblings, 1 reply; 3+ messages in thread
From: Ian Rogers @ 2026-09-10 21:15 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Arnaldo Carvalho de Melo,
Namhyung Kim, Jiri Olsa, Ian Rogers, Adrian Hunter, James Clark,
linux-perf-users, linux-kernel
Updates Tree type hints to correctly use TreeNode[TreeValue]
across all assignments preventing mypy strictness mismatches.
Address some other minor stylistic issues reported by mypy, pylint and
Sashiko.
Assisted-by: Antigravity:gemini-3.1-pro
Signed-off-by: Ian Rogers <irogers@google.com>
---
v2: Tweak the imports and type annotations for better 3.9
compatibility and to be more pythonic.
---
tools/perf/python/ilist.py | 78 ++++++++++++++++++++------------------
1 file changed, 42 insertions(+), 36 deletions(-)
diff --git a/tools/perf/python/ilist.py b/tools/perf/python/ilist.py
index 0d757ddb4795..ebff0a843b7a 100755
--- a/tools/perf/python/ilist.py
+++ b/tools/perf/python/ilist.py
@@ -2,11 +2,13 @@
# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
"""Interactive perf list."""
-from abc import ABC, abstractmethod
+from __future__ import annotations
+
import argparse
+from abc import ABC, abstractmethod
from dataclasses import dataclass
import math
-from typing import Any, Dict, Optional, Tuple
+from typing import Any, Optional
import perf
from textual import on
from textual.app import App, ComposeResult
@@ -19,7 +21,7 @@ from textual.widgets import Button, Footer, Header, Input, Label, Sparkline, Sta
from textual.widgets.tree import TreeNode
-def get_info(info: Dict[str, str], key: str):
+def get_info(info: dict[str, Any], key: str):
return (info[key] + "\n") if key in info else ""
@@ -140,7 +142,7 @@ class ErrorScreen(ModalScreen[bool]):
def compose(self) -> ComposeResult:
yield Button(f"Error: {self.error}", variant="primary", id="error")
- def on_button_pressed(self, event: Button.Pressed) -> None:
+ def on_button_pressed(self) -> None:
self.dismiss(True)
@@ -234,7 +236,7 @@ class IListApp(App):
def __init__(self, interval: float) -> None:
self.interval = interval
- self.evlist = None
+ self.evlist: Optional[perf.evlist] = None
self.selected: Optional[TreeValue] = None
self.search_results: list[TreeNode[TreeValue]] = []
self.cur_search_result: TreeNode[TreeValue] | None = None
@@ -284,18 +286,18 @@ class IListApp(App):
"""Sets the focus after the SearchScreen is dismissed."""
search_label = self.query_one("#active_search", Label)
- search_label.display = True if event else False
+ search_label.display = bool(event)
if not event:
return
event = event.lower()
search_label.update(f'Searching for events matching "{event}"')
- tree: Tree[str] = self.query_one("#root", Tree)
+ tree: Tree[TreeValue] = self.query_one("#root", Tree)
- def find_search_results(event: str, node: TreeNode[str],
+ def find_search_results(event: str, node: TreeNode[TreeValue],
cursor_seen: bool = False,
- match_after_cursor: Optional[TreeNode[str]] = None
- ) -> Tuple[bool, Optional[TreeNode[str]]]:
+ match_after_cursor: Optional[TreeNode[TreeValue]] = None
+ ) -> tuple[bool, Optional[TreeNode[TreeValue]]]:
"""Find nodes that match the search remembering the one after the cursor."""
if not cursor_seen and node == tree.cursor_node:
cursor_seen = True
@@ -332,7 +334,7 @@ class IListApp(App):
def action_collapse(self) -> None:
"""Collapse the part of the tree currently on."""
- tree: Tree[str] = self.query_one("#root", Tree)
+ tree: Tree[TreeValue] = self.query_one("#root", Tree)
node = tree.cursor_node
if node and node.parent:
node.parent.collapse_all()
@@ -343,31 +345,34 @@ class IListApp(App):
if not self.selected or not self.evlist:
return
- def update_count(cpu: int, count: int):
+ def update_count(cpu: int, count: int | float):
# Update the raw count display.
- counter: Label = self.query(f"#counter_cpu{cpu}" if cpu >= 0 else "#counter_total")
- if not counter:
+ counter_query = self.query(f"#counter_cpu{cpu}" if cpu >= 0 else "#counter_total")
+ if not counter_query:
return
- counter = counter.first(Label)
+ counter: Label = counter_query.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:
+ line_query = self.query(f"#sparkline_cpu{cpu}" if cpu >= 0 else "#sparkline_total")
+ if not line_query:
return
- line = line.first(Sparkline)
+ line: Sparkline = line_query.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)
+ if line.data is not None:
+ if len(line.data) > line.size.width:
+ line.data = line.data[1:]
+ line.data = list(line.data) + [float(count)]
+ else:
+ line.data = [float(count)]
+ line.refresh()
# Update the total and each CPU counts, assume there's just 1 evsel.
- total = 0
+ total: float = 0.0
self.evlist.disable()
for evsel in self.evlist:
for cpu in evsel.cpus():
- aggr = 0
+ aggr: float = 0.0
for thread in evsel.threads():
aggr += self.selected.value(self.evlist, evsel, cpu, thread)
update_count(cpu, aggr)
@@ -424,16 +429,16 @@ class IListApp(App):
# 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)
+ line_sp = CounterSparkline(cpu=-1)
+ lines.mount(line_sp)
for cpu in self.evlist.all_cpus():
- line = CounterSparkline(cpu)
- lines.mount(line)
- line = Counter(cpu=-1)
- lines.mount(line)
+ c_sp = CounterSparkline(cpu)
+ lines.mount(c_sp)
+ c_val = Counter(cpu=-1)
+ lines.mount(c_val)
for cpu in self.evlist.all_cpus():
- line = Counter(cpu)
- lines.mount(line)
+ c_val2 = Counter(cpu)
+ lines.mount(c_val2)
def compose(self) -> ComposeResult:
"""Draws the app."""
@@ -459,17 +464,18 @@ class IListApp(App):
# Reading events may fail with EPERM, ignore.
pass
metrics = tree.root.add("Metrics")
- groups = set()
+ groups: set[str] = set()
for metric in perf.metrics():
groups.update(metric["MetricGroup"])
- def add_metrics_to_tree(node: TreeNode[TreeValue], parent: str, pmu: str = None):
+ def add_metrics_to_tree(node: TreeNode[TreeValue], parent: str, pmu: Optional[str] = None):
for metric in sorted(perf.metrics(), key=lambda x: x["MetricName"]):
- metric_pmu = metric.get('PMU')
+ metric_pmu_raw = metric.get('PMU')
+ metric_pmu = str(metric_pmu_raw) if metric_pmu_raw else ''
if pmu and metric_pmu and metric_pmu != pmu:
continue
if parent in metric["MetricGroup"]:
- name = metric["MetricName"]
+ name = str(metric["MetricName"])
display_name = name
if metric_pmu:
display_name += f" ({metric_pmu})"
--
2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v2] perf python: Fix ilist TreeNode string type drift
2026-09-10 21:15 ` [PATCH v2] " Ian Rogers
@ 2026-09-13 21:40 ` Arnaldo Carvalho de Melo
0 siblings, 0 replies; 3+ messages in thread
From: Arnaldo Carvalho de Melo @ 2026-09-13 21:40 UTC (permalink / raw)
To: Ian Rogers
Cc: Peter Zijlstra, Ingo Molnar, Namhyung Kim, Jiri Olsa,
Adrian Hunter, James Clark, linux-perf-users, linux-kernel
On Thu, Sep 10, 2026 at 02:15:39PM -0700, Ian Rogers wrote:
> Updates Tree type hints to correctly use TreeNode[TreeValue]
> across all assignments preventing mypy strictness mismatches.
>
> Address some other minor stylistic issues reported by mypy, pylint and
> Sashiko.
Thanks, applied to perf-tools-next, for v7.4.
- Arnaldo
> Assisted-by: Antigravity:gemini-3.1-pro
> Signed-off-by: Ian Rogers <irogers@google.com>
> ---
> v2: Tweak the imports and type annotations for better 3.9
> compatibility and to be more pythonic.
> ---
> tools/perf/python/ilist.py | 78 ++++++++++++++++++++------------------
> 1 file changed, 42 insertions(+), 36 deletions(-)
>
> diff --git a/tools/perf/python/ilist.py b/tools/perf/python/ilist.py
> index 0d757ddb4795..ebff0a843b7a 100755
> --- a/tools/perf/python/ilist.py
> +++ b/tools/perf/python/ilist.py
> @@ -2,11 +2,13 @@
> # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
> """Interactive perf list."""
>
> -from abc import ABC, abstractmethod
> +from __future__ import annotations
> +
> import argparse
> +from abc import ABC, abstractmethod
> from dataclasses import dataclass
> import math
> -from typing import Any, Dict, Optional, Tuple
> +from typing import Any, Optional
> import perf
> from textual import on
> from textual.app import App, ComposeResult
> @@ -19,7 +21,7 @@ from textual.widgets import Button, Footer, Header, Input, Label, Sparkline, Sta
> from textual.widgets.tree import TreeNode
>
>
> -def get_info(info: Dict[str, str], key: str):
> +def get_info(info: dict[str, Any], key: str):
> return (info[key] + "\n") if key in info else ""
>
>
> @@ -140,7 +142,7 @@ class ErrorScreen(ModalScreen[bool]):
> def compose(self) -> ComposeResult:
> yield Button(f"Error: {self.error}", variant="primary", id="error")
>
> - def on_button_pressed(self, event: Button.Pressed) -> None:
> + def on_button_pressed(self) -> None:
> self.dismiss(True)
>
>
> @@ -234,7 +236,7 @@ class IListApp(App):
>
> def __init__(self, interval: float) -> None:
> self.interval = interval
> - self.evlist = None
> + self.evlist: Optional[perf.evlist] = None
> self.selected: Optional[TreeValue] = None
> self.search_results: list[TreeNode[TreeValue]] = []
> self.cur_search_result: TreeNode[TreeValue] | None = None
> @@ -284,18 +286,18 @@ class IListApp(App):
> """Sets the focus after the SearchScreen is dismissed."""
>
> search_label = self.query_one("#active_search", Label)
> - search_label.display = True if event else False
> + search_label.display = bool(event)
> if not event:
> return
> event = event.lower()
> search_label.update(f'Searching for events matching "{event}"')
>
> - tree: Tree[str] = self.query_one("#root", Tree)
> + tree: Tree[TreeValue] = self.query_one("#root", Tree)
>
> - def find_search_results(event: str, node: TreeNode[str],
> + def find_search_results(event: str, node: TreeNode[TreeValue],
> cursor_seen: bool = False,
> - match_after_cursor: Optional[TreeNode[str]] = None
> - ) -> Tuple[bool, Optional[TreeNode[str]]]:
> + match_after_cursor: Optional[TreeNode[TreeValue]] = None
> + ) -> tuple[bool, Optional[TreeNode[TreeValue]]]:
> """Find nodes that match the search remembering the one after the cursor."""
> if not cursor_seen and node == tree.cursor_node:
> cursor_seen = True
> @@ -332,7 +334,7 @@ class IListApp(App):
>
> def action_collapse(self) -> None:
> """Collapse the part of the tree currently on."""
> - tree: Tree[str] = self.query_one("#root", Tree)
> + tree: Tree[TreeValue] = self.query_one("#root", Tree)
> node = tree.cursor_node
> if node and node.parent:
> node.parent.collapse_all()
> @@ -343,31 +345,34 @@ class IListApp(App):
> if not self.selected or not self.evlist:
> return
>
> - def update_count(cpu: int, count: int):
> + def update_count(cpu: int, count: int | float):
> # Update the raw count display.
> - counter: Label = self.query(f"#counter_cpu{cpu}" if cpu >= 0 else "#counter_total")
> - if not counter:
> + counter_query = self.query(f"#counter_cpu{cpu}" if cpu >= 0 else "#counter_total")
> + if not counter_query:
> return
> - counter = counter.first(Label)
> + counter: Label = counter_query.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:
> + line_query = self.query(f"#sparkline_cpu{cpu}" if cpu >= 0 else "#sparkline_total")
> + if not line_query:
> return
> - line = line.first(Sparkline)
> + line: Sparkline = line_query.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)
> + if line.data is not None:
> + if len(line.data) > line.size.width:
> + line.data = line.data[1:]
> + line.data = list(line.data) + [float(count)]
> + else:
> + line.data = [float(count)]
> + line.refresh()
>
> # Update the total and each CPU counts, assume there's just 1 evsel.
> - total = 0
> + total: float = 0.0
> self.evlist.disable()
> for evsel in self.evlist:
> for cpu in evsel.cpus():
> - aggr = 0
> + aggr: float = 0.0
> for thread in evsel.threads():
> aggr += self.selected.value(self.evlist, evsel, cpu, thread)
> update_count(cpu, aggr)
> @@ -424,16 +429,16 @@ class IListApp(App):
> # 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)
> + line_sp = CounterSparkline(cpu=-1)
> + lines.mount(line_sp)
> for cpu in self.evlist.all_cpus():
> - line = CounterSparkline(cpu)
> - lines.mount(line)
> - line = Counter(cpu=-1)
> - lines.mount(line)
> + c_sp = CounterSparkline(cpu)
> + lines.mount(c_sp)
> + c_val = Counter(cpu=-1)
> + lines.mount(c_val)
> for cpu in self.evlist.all_cpus():
> - line = Counter(cpu)
> - lines.mount(line)
> + c_val2 = Counter(cpu)
> + lines.mount(c_val2)
>
> def compose(self) -> ComposeResult:
> """Draws the app."""
> @@ -459,17 +464,18 @@ class IListApp(App):
> # Reading events may fail with EPERM, ignore.
> pass
> metrics = tree.root.add("Metrics")
> - groups = set()
> + groups: set[str] = set()
> for metric in perf.metrics():
> groups.update(metric["MetricGroup"])
>
> - def add_metrics_to_tree(node: TreeNode[TreeValue], parent: str, pmu: str = None):
> + def add_metrics_to_tree(node: TreeNode[TreeValue], parent: str, pmu: Optional[str] = None):
> for metric in sorted(perf.metrics(), key=lambda x: x["MetricName"]):
> - metric_pmu = metric.get('PMU')
> + metric_pmu_raw = metric.get('PMU')
> + metric_pmu = str(metric_pmu_raw) if metric_pmu_raw else ''
> if pmu and metric_pmu and metric_pmu != pmu:
> continue
> if parent in metric["MetricGroup"]:
> - name = metric["MetricName"]
> + name = str(metric["MetricName"])
> display_name = name
> if metric_pmu:
> display_name += f" ({metric_pmu})"
> --
> 2.55.0.1007.g17ff1f9808-goog
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-09-13 21:40 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-10 17:44 [PATCH v1] perf python: Fix ilist TreeNode string type drift Ian Rogers
2026-09-10 21:15 ` [PATCH v2] " Ian Rogers
2026-09-13 21:40 ` Arnaldo Carvalho de Melo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®