中文 | 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.
- 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
pip install peeka # CLI only (Python 3.8+)
pip install peeka[tui] # CLI + TUI (Python 3.9+)# 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
peekaAll 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| 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 |
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
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()
peeka-cli stack <pattern> [options]| Option | Description | Default |
|---|---|---|
-n, --times |
Number of captures (-1 = infinite) | -1 |
--condition |
Filter expression | — |
--depth |
Stack depth limit | 10 |
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 # ArithmeticSecurity: Only safe operations (comparisons, arithmetic, logic) are allowed. eval, exec, __import__, open, compile and reflection via __class__/__subclasses__ are all blocked.
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 | 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.
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 gdbdocker run --cap-add=SYS_PTRACE your-image# 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- Ensure same UID or
CAP_SYS_PTRACE - Check
ptrace_scope(see above) - For GDB fallback: install debug symbols if "no symbol" error appears
- Verify function name (use fully qualified name:
module.Class.method) - Confirm the function is actually being called
- Check if condition expression is too restrictive
# 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>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()
Apache License 2.0
- Inspired by Alibaba Arthas
- Safe evaluation: simpleeval
- Remote debugging protocol: PEP 768