You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CLAUDE.md
+44Lines changed: 44 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -36,6 +36,50 @@ tests/ — All tests (unittest)
36
36
utils.py — Shared helpers and base test class
37
37
```
38
38
39
+
## How it works
40
+
41
+
### Hook mechanism
42
+
`yappi.start()` calls `sys.setprofile(_profile_thread_callback)` on the current thread. When a new thread is encountered, yappi transplants its own `c_profilefunc` into the new thread's `PyThreadState` — this is how it profiles all threads without each one needing an explicit `setprofile` call.
43
+
44
+
Every `call`/`return`/`c_call`/`c_return` event from the interpreter is delivered to `_yappi._profile_event()` (C function).
45
+
46
+
### Core data structures (C layer)
47
+
```
48
+
contexts (global htab)
49
+
└── context_id → _ctx
50
+
├── cs — call stack (_cstack), tracks the current call chain
51
+
├── rec_levels — htab tracking recursion depth per function
52
+
├── t0 — profiling start tick
53
+
├── sched_cnt — how many times this thread was scheduled
54
+
└── tags (htab)
55
+
└── tag_id → pits (htab)
56
+
└── code_obj / m_ml → _pit (profile item)
57
+
├── callcount, nonrecursive_callcount
58
+
├── ttotal — total time including children
59
+
├── tsubtotal — self time (excluding children)
60
+
├── children — linked list of _pit_children_info (callee timing per caller-callee pair)
61
+
└── coroutines — linked list of _coro (active coroutine frames + start tick)
62
+
```
63
+
64
+
### Contexts
65
+
A *context* maps to a thread by default. Context identity is stored as `_yappi_tid` in `ThreadState.dict` — a monotonic counter rather than the OS tid (which can be recycled). This design allows alternative context backends: for **greenlets**, a `context_id_callback` returns a per-greenlet ID so multiple greenlets sharing one OS thread appear as separate contexts.
66
+
67
+
### Tags
68
+
An optional `tag_callback` returns an integer per call event. Stats are bucketed per `(context, tag)`, allowing you to segregate profiling data (e.g. by request, task type, etc.) without separate profiling sessions.
69
+
70
+
### Coroutines
71
+
Each `_pit` (function) holds a linked list of `_coro` entries — one per concurrently suspended coroutine frame. When a coroutine is suspended (`FRAME_SUSPENDED`), its elapsed time is accumulated into the `_coro` entry without closing the `_pit`. On resumption, timing continues from where it left off. This correctly handles multiple concurrent coroutines calling the same function.
72
+
73
+
### Stat collection (Python layer)
74
+
`get_func_stats()` / `get_thread_stats()` enumerate the C-side hash tables and materialize them as Python objects:
75
+
76
+
| C struct | Python wrapper | Collection |
77
+
|----------|---------------|------------|
78
+
|`_pit`|`YFuncStat`|`YFuncStats`|
79
+
|`_ctx`|`YThreadStat`|`YThreadStats`|
80
+
81
+
`YFuncStat.children` is a `YChildFuncStats` collection (from `_pit_children_info`) representing direct callees with per-pair timing. Export formats (callgrind, pstat) are produced by converting these collections in Python.
82
+
39
83
## Key constraints
40
84
-**Don't assume GIL protection in callbacks**: profiler callbacks can fire on any thread; C code must be thread-safe
41
85
-**clear_stats() sequence**: pause → wait for in-flight callbacks → clear. Never free memory while callbacks may still be running
0 commit comments