Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
20c2d1b
Add private Windows ETW thread spawn tracer prototype
cursoragent Jul 10, 2026
8ab188d
Add Windows ETW integration tests and drop mock-heavy unit tests
cursoragent Jul 10, 2026
38a1202
Derive native pool spawn expectations from threadpool_info
cursoragent Jul 10, 2026
b0aa8d7
Fix black formatting and address review feedback
cursoragent Jul 10, 2026
3a8b0ed
Format integration tests with black 25.1.0 (CI pin)
cursoragent Jul 10, 2026
6201324
Fix package layout conflict with flit single-module structure
cursoragent Jul 10, 2026
c31174a
Fix Windows ETW consumer segfault in integration tests
cursoragent Jul 10, 2026
a30a5c1
Stabilize Windows ETW integration tests on CI
cursoragent Jul 10, 2026
c5fb7f4
Fix indentation of traced subprocess scripts
cursoragent Jul 10, 2026
1aa8ae7
Temporarily disable flexiblas CI matrix jobs for faster iteration
cursoragent Jul 10, 2026
8d91f9a
Return stats from traced child helper; assert exact spawn counts in t…
cursoragent Jul 10, 2026
c67ca9e
Account for child startup threads in strict spawn assertions
cursoragent Jul 10, 2026
077951e
Derive Python thread expectations from measured noop baseline
cursoragent Jul 10, 2026
bb262fe
Add integration test for existing-thread DCStart rundown events
cursoragent Jul 10, 2026
b3418c1
Keep noop child alive for existing-thread baseline measurement
cursoragent Jul 10, 2026
9d952b0
Address review feedback and re-enable flexiblas CI jobs
cursoragent Jul 10, 2026
a0a0f61
Allow importing the Windows tracer module on non-Windows platforms
cursoragent Jul 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions _thread_tracer/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Private experimental thread spawn tracer (not part of the public API)."""

from _thread_tracer._types import ThreadSpawnStats, ThreadTracerError
from _thread_tracer._windows_etw import WindowsThreadSpawnTracer

__all__ = [
"ThreadSpawnStats",
"ThreadTracerError",
"WindowsThreadSpawnTracer",
]
68 changes: 68 additions & 0 deletions _thread_tracer/_parsing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""Pure-Python helpers to classify kernel thread ETW events."""

import ctypes as ct
import struct

# Kernel thread event opcodes (EVENT_TRACE_TYPE_* for the Thread task).
EVENT_TRACE_TYPE_START = 1
EVENT_TRACE_TYPE_END = 2
EVENT_TRACE_TYPE_DCSTART = 3
EVENT_TRACE_TYPE_DCEND = 4

_MIN_USER_DATA_LENGTH = 8


def parse_kernel_thread_payload(user_data, user_data_length):
"""Extract ``(process_id, thread_id)`` from a kernel Thread event payload.

The first two fields are stable across Thread event schema versions. Pointer
fields that follow are ignored because only the identifiers are needed.
"""
if user_data is None or user_data_length < _MIN_USER_DATA_LENGTH:
return None

if isinstance(user_data, (bytes, bytearray)):
raw = user_data[:user_data_length]
else:
address = user_data
if not isinstance(address, int):
address = ct.cast(user_data, ct.c_void_p).value or 0
if not address:
return None
raw = ct.string_at(address, user_data_length)

if len(raw) < _MIN_USER_DATA_LENGTH:
return None
process_id, thread_id = struct.unpack_from("<II", raw, 0)
return process_id, thread_id


def classify_kernel_thread_event(opcode, user_data, user_data_length, target_pid):
"""Return a spawn classification for a kernel Thread event.

Returns one of:
- ``"spawn"`` for a thread start event in ``target_pid``
- ``"existing"`` for a DCStart rundown event in ``target_pid``
- ``None`` if the event should be ignored
"""
if opcode not in (
EVENT_TRACE_TYPE_START,
EVENT_TRACE_TYPE_END,
EVENT_TRACE_TYPE_DCSTART,
EVENT_TRACE_TYPE_DCEND,
):
return None

parsed = parse_kernel_thread_payload(user_data, user_data_length)
if parsed is None:
return None

process_id, _thread_id = parsed
if process_id != target_pid:
return None

if opcode == EVENT_TRACE_TYPE_START:
return "spawn"
if opcode == EVENT_TRACE_TYPE_DCSTART:
return "existing"
return None
32 changes: 32 additions & 0 deletions _thread_tracer/_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Private types for the experimental thread spawn tracer."""


class ThreadSpawnStats(object):
"""Statistics collected during a thread tracing window."""

__slots__ = ("spawn_count", "existing_thread_count", "thread_ids")

def __init__(self, spawn_count=0, existing_thread_count=0, thread_ids=None):
self.spawn_count = spawn_count
self.existing_thread_count = existing_thread_count
self.thread_ids = frozenset(thread_ids or ())

def __eq__(self, other):
if not isinstance(other, ThreadSpawnStats):
return NotImplemented
return (
self.spawn_count == other.spawn_count
and self.existing_thread_count == other.existing_thread_count
and self.thread_ids == other.thread_ids
)

def __repr__(self):
return (
"ThreadSpawnStats(spawn_count={0.spawn_count}, "
"existing_thread_count={0.existing_thread_count}, "
"thread_ids={0.thread_ids!r})"
).format(self)


class ThreadTracerError(OSError):
"""Raised when the platform tracer cannot start or stop cleanly."""
Loading
Loading