GitHub - logerrors/peeka: Runtime diagnostic tool for Python applications
中文 | English
Peek-a-boo! — The name comes from the children's game. A diagnostic tool finding a hidden bug feels just like that moment of surprise when someone is found in hide-and-seek.
Runtime diagnostic tool for Python applications, inspired by Alibaba Arthas. Non-invasive function observation with zero code changes.
Uses PEP 768 (sys.remote_exec) on Python 3.14+, with a GDB + ptrace fallback for Python 3.8–3.13.
Key Features
- Non-invasive — Inject observation logic at runtime, fully restored on detach
- Real-time streaming — Millisecond-latency data via Unix domain sockets
- Production-safe — < 5% overhead, fixed-size memory buffers, graceful error recovery
- Secure filtering — simpleeval-based condition expressions, blocks all code injection
- Dual interface — CLI (JSONL output, pipe-friendly) and interactive TUI
Quick Start
Install
pip install peeka # CLI only (Python 3.8+) pip install peeka[tui] # CLI + TUI (Python 3.9+)
Basic Usage
# 1. Attach to a running Python process peeka-cli attach <pid> # 2. Watch function calls peeka-cli watch "module.Class.method" -n 5 # 3. Watch with condition filter peeka-cli watch "module.func" --condition "params[0] > 100" # 4. Trace call tree with timing peeka-cli trace "module.func" -d 3 # 5. Capture call stack peeka-cli stack "module.func" -n 3 # 6. Launch TUI peeka
Pipe-Friendly Output (JSONL)
All CLI output is JSONL — one JSON object per line with a type field:
# Filter observations with jq peeka-cli watch "module.func" | jq 'select(.type == "observation")' # Find slow calls peeka-cli watch "module.func" | jq 'select(.type == "observation" and .data.duration_ms > 100)' # Save to file peeka-cli watch "module.func" > observations.jsonl
Commands
| Command | Description |
|---|---|
attach |
Attach to a running Python process |
watch |
Observe function calls (args, return, time) |
trace |
Trace call tree with timing breakdown |
stack |
Capture call stack at function entry |
monitor |
Periodic performance statistics |
logger |
Adjust log levels at runtime |
memory |
Memory usage analysis |
inspect |
Runtime object inspection |
sc/sm |
Search classes / search methods |
thread |
Thread analysis and diagnostics |
top |
Function-level performance sampling |
reset |
Remove all injected enhancements |
detach |
Detach from target process |
watch
peeka-cli watch <pattern> [options]
| Option | Description | Default |
|---|---|---|
-x, --depth |
Output depth for nested objects | 2 |
-n, --times |
Number of observations (-1 = infinite) | -1 |
--condition |
Filter expression (supports cost var) |
— |
-b, --before |
Observe at function entry | false |
-s, --success |
Observe on success only | false |
-e, --exception |
Observe on exception only | false |
-f, --finish |
Observe on both success and exception | true |
Pattern format: module.Class.method or module.function
trace
peeka-cli trace <pattern> [options]
| Option | Description | Default |
|---|---|---|
-d, --depth |
Max call tree depth | 3 |
-n, --times |
Number of observations (-1 = infinite) | -1 |
--condition |
Filter expression (supports cost var) |
— |
--skip-builtin |
Skip stdlib/built-in functions | true |
--min-duration |
Minimum duration filter (ms) | 0 |
Output example:
`---[125.3ms] calculator.Calculator.calculate()
+---[2.1ms] calculator.Calculator._validate()
+---[98.2ms] calculator.Calculator._compute()
| `---[95.1ms] math.sqrt()
`---[15.7ms] calculator.Logger.info()
stack
peeka-cli stack <pattern> [options]
| Option | Description | Default |
|---|---|---|
-n, --times |
Number of captures (-1 = infinite) | -1 |
--condition |
Filter expression | — |
--depth |
Stack depth limit | 10 |
Condition Expressions
Powered by simpleeval for safe evaluation:
params[0] > 100 # Positional argument check len(params) > 2 # Argument count kwargs.get('debug') == True # Keyword argument check cost > 50 # Execution time in ms (watch/trace) str(x).startswith('prefix') # String operations x + y > 10 # Arithmetic
Security: Only safe operations (comparisons, arithmetic, logic) are allowed. eval, exec, __import__, open, compile and reflection via __class__/__subclasses__ are all blocked.
Output Format
Every line is a JSON object with a type field:
| Type | Description | Commands |
|---|---|---|
status |
Progress/info messages | attach |
success |
Command completed successfully | attach, detach |
error |
Command failed | all |
event |
Control events (started/stopped) | watch, stack, monitor |
observation |
Real-time observation data | watch, stack, monitor |
result |
Query results (non-streaming) | logger, memory, sc, sm |
Output examples
{"type": "status", "level": "info", "message": "Attaching to process 12345"}
{"type": "success", "command": "attach", "data": {"pid": 12345, "socket": "/tmp/peeka_xxx.sock"}}
{"type": "event", "event": "watch_started", "data": {"watch_id": "watch_001", "pattern": "module.func"}}
{
"type": "observation",
"watch_id": "watch_001",
"timestamp": 1705586200.123,
"func_name": "demo.Calculator.add",
"args": [1, 2],
"kwargs": {},
"result": 3,
"success": true,
"duration_ms": 0.123,
"count": 1
}
{"type": "error", "command": "watch", "error": "Cannot find target: invalid.pattern"}Python Version Support
| Python Version | CLI | TUI | Attach Mechanism | Requirements |
|---|---|---|---|---|
| 3.14+ | ✅ | ✅ | PEP 768 sys.remote_exec() |
Same UID or CAP_SYS_PTRACE |
| 3.9–3.13 | ✅ | ✅ | GDB + ptrace fallback | GDB 7.3+, ptrace_scope ≤ 1 |
| 3.8 | ✅ | ❌ | GDB + ptrace fallback | GDB 7.3+, ptrace_scope ≤ 1 |
TUI requires Textual which needs Python ≥ 3.9.
Python < 3.14 Setup
GDB is required. Debug symbols are strongly recommended — some distros include them by default, but if GDB reports "no symbol" errors, install them:
# Debian/Ubuntu sudo apt-get install gdb python3-dbg # RHEL/Fedora sudo yum install gdb python3-debuginfo # Arch sudo pacman -S gdb
Docker
docker run --cap-add=SYS_PTRACE your-image
ptrace Restrictions
# Check current setting cat /proc/sys/kernel/yama/ptrace_scope # Temporarily allow (for testing) echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope # SELinux (Fedora/RHEL) sudo setsebool -P deny_ptrace=off
Troubleshooting
Attach fails (permission denied)
- Ensure same UID or
CAP_SYS_PTRACE - Check
ptrace_scope(see above) - For GDB fallback: install debug symbols if "no symbol" error appears
No observation data
- Verify function name (use fully qualified name:
module.Class.method) - Confirm the function is actually being called
- Check if condition expression is too restrictive
Target process behaving abnormally
# Remove observation on specific function peeka-cli reset "module.func" # Remove all injected enhancements peeka-cli reset --all # If issues persist, detach and restart target process peeka-cli detach <pid>
Architecture
CLI/TUI → AgentClient → Unix Socket → PeekaAgent (injected in target)
├─ _register_handlers() → BaseCommand subclasses
├─ DecoratorInjector (function wrapping)
└─ ObservationManager (buffered streaming)
- Attach: PEP 768
sys.remote_exec()on 3.14+, GDB + ptrace on 3.8–3.13 - Observe: Decorator injection wraps target functions, captures args/return/exceptions/timing
- Stream: Real-time observation data via Unix domain socket (length-prefixed JSON)
- Commands: Modular
BaseCommandsubclasses, registered inPeekaAgent._register_handlers()
License
Apache License 2.0
Acknowledgments
- Inspired by Alibaba Arthas
- Safe evaluation: simpleeval
- Remote debugging protocol: PEP 768