[PATCH v1 02/49] perf python: Update callchain stubs and session thread lookup

From: Ian Rogers

Date: Sun Sep 20 2026 - 01:23:29 EST


Update pyrf_session__find_thread() in util/python.c to accept an
optional tid parameter (defaulting to pid) and return None instead of
raising TypeError when a thread is not found in the session machines.

Update perf.pyi to match:
- Set session.find_thread() signature to (pid: int, tid: int = -1) ->
Optional[thread].
- Update callchain_node.symbol and callchain_node.dso to str (the C
getters return "[unknown]" fallback strings rather than None) and add
__iter__() to callchain.

Also update treport.py to handle Optional[thread] returned by
session.find_thread().

Assisted-by: Antigravity:gemini-3.1-pro
Signed-off-by: Ian Rogers <irogers@xxxxxxxxxx>
---
tools/perf/python/perf.pyi | 7 ++++---
tools/perf/python/treport.py | 2 +-
tools/perf/util/python.c | 19 +++++++++++--------
3 files changed, 16 insertions(+), 12 deletions(-)

diff --git a/tools/perf/python/perf.pyi b/tools/perf/python/perf.pyi
index e737eb21567c..e5259e9b61dd 100644
--- a/tools/perf/python/perf.pyi
+++ b/tools/perf/python/perf.pyi
@@ -320,13 +320,14 @@ class branch_stack:
class callchain_node:
"""Represents a frame in the callchain."""
ip: int
- symbol: Optional[str]
- dso: Optional[str]
+ symbol: str
+ dso: str

class callchain:
"""Sequence of callchain frames."""
def __len__(self) -> int: ...
def __getitem__(self, index: int) -> callchain_node: ...
+ def __iter__(self) -> Iterator[callchain_node]: ...

class stat_event(_sample_members):
"""Represents a stat event from perf."""
@@ -441,7 +442,7 @@ class session:
def process_events(self) -> None:
"""Process all events in the session."""
...
- def find_thread(self, pid: int) -> thread:
+ def find_thread(self, pid: int, tid: int = -1) -> Optional[thread]:
"""Returns the thread associated with a pid."""
...

diff --git a/tools/perf/python/treport.py b/tools/perf/python/treport.py
index 43542599a884..786b852f471c 100755
--- a/tools/perf/python/treport.py
+++ b/tools/perf/python/treport.py
@@ -92,7 +92,7 @@ class ProfileNode:
try:
assert session
thread = session.find_thread(sample.sample_tid)
- comm = thread.comm()
+ comm = (thread.comm() if thread else None) or f"unknown ({pid})"
except Exception:
comm = f"unknown ({pid})"

diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
index a3ffaf4d0850..fbfa71b1c4b6 100644
--- a/tools/perf/util/python.c
+++ b/tools/perf/util/python.c
@@ -3912,26 +3912,29 @@ static PyObject *pyrf_session__find_thread(struct pyrf_session *psession, PyObje
struct machine *machine;
struct thread *thread = NULL;
PyObject *result;
- int pid;
+ int pid, tid = -1;

CHECK_INITIALIZED(psession->session, "session");

- if (!PyArg_ParseTuple(args, "i", &pid))
+ if (!PyArg_ParseTuple(args, "i|i", &pid, &tid))
return NULL;

+ if (tid == -1)
+ tid = pid;
+
+ /* Look up the thread in the host machine first, then fall back to guest machines. */
machine = &psession->session->machines.host;
- thread = machine__find_thread(machine, pid, pid);
+ thread = machine__find_thread(machine, pid, tid);

if (!thread) {
machine = perf_session__find_machine(psession->session, pid);
if (machine)
- thread = machine__find_thread(machine, pid, pid);
+ thread = machine__find_thread(machine, pid, tid);
}

- if (!thread) {
- PyErr_Format(PyExc_TypeError, "Failed to find thread %d", pid);
- return NULL;
- }
+ /* Return None rather than raising TypeError when a PID/TID is not known. */
+ if (!thread)
+ Py_RETURN_NONE;
result = pyrf_thread__from_thread(thread);
thread__put(thread);
return result;
--
2.55.0.1082.g2b9226bbc0-goog