py-tt-debug 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_tt_debug-0.3.0/CHANGELOG.md +201 -0
- py_tt_debug-0.3.0/LICENSE +21 -0
- py_tt_debug-0.3.0/MANIFEST.in +7 -0
- py_tt_debug-0.3.0/PKG-INFO +193 -0
- py_tt_debug-0.3.0/README.md +163 -0
- py_tt_debug-0.3.0/ext/checkpoint.c +470 -0
- py_tt_debug-0.3.0/ext/checkpoint.h +26 -0
- py_tt_debug-0.3.0/ext/checkpoint_store.c +255 -0
- py_tt_debug-0.3.0/ext/checkpoint_store.h +55 -0
- py_tt_debug-0.3.0/ext/frame_event.h +18 -0
- py_tt_debug-0.3.0/ext/iohook.c +515 -0
- py_tt_debug-0.3.0/ext/iohook.h +11 -0
- py_tt_debug-0.3.0/ext/platform.h +41 -0
- py_tt_debug-0.3.0/ext/pyttd_native.c +52 -0
- py_tt_debug-0.3.0/ext/recorder.c +1373 -0
- py_tt_debug-0.3.0/ext/recorder.h +51 -0
- py_tt_debug-0.3.0/ext/replay.c +176 -0
- py_tt_debug-0.3.0/ext/replay.h +7 -0
- py_tt_debug-0.3.0/ext/ringbuf.c +366 -0
- py_tt_debug-0.3.0/ext/ringbuf.h +90 -0
- py_tt_debug-0.3.0/py_tt_debug.egg-info/PKG-INFO +193 -0
- py_tt_debug-0.3.0/py_tt_debug.egg-info/SOURCES.txt +69 -0
- py_tt_debug-0.3.0/py_tt_debug.egg-info/dependency_links.txt +1 -0
- py_tt_debug-0.3.0/py_tt_debug.egg-info/entry_points.txt +2 -0
- py_tt_debug-0.3.0/py_tt_debug.egg-info/requires.txt +6 -0
- py_tt_debug-0.3.0/py_tt_debug.egg-info/top_level.txt +2 -0
- py_tt_debug-0.3.0/pyproject.toml +56 -0
- py_tt_debug-0.3.0/pyttd/__init__.py +5 -0
- py_tt_debug-0.3.0/pyttd/__main__.py +2 -0
- py_tt_debug-0.3.0/pyttd/cli.py +198 -0
- py_tt_debug-0.3.0/pyttd/config.py +17 -0
- py_tt_debug-0.3.0/pyttd/errors.py +6 -0
- py_tt_debug-0.3.0/pyttd/main.py +25 -0
- py_tt_debug-0.3.0/pyttd/models/__init__.py +6 -0
- py_tt_debug-0.3.0/pyttd/models/base.py +8 -0
- py_tt_debug-0.3.0/pyttd/models/checkpoints.py +15 -0
- py_tt_debug-0.3.0/pyttd/models/constants.py +8 -0
- py_tt_debug-0.3.0/pyttd/models/frames.py +27 -0
- py_tt_debug-0.3.0/pyttd/models/io_events.py +17 -0
- py_tt_debug-0.3.0/pyttd/models/runs.py +11 -0
- py_tt_debug-0.3.0/pyttd/models/storage.py +40 -0
- py_tt_debug-0.3.0/pyttd/models/timeline.py +80 -0
- py_tt_debug-0.3.0/pyttd/protocol.py +86 -0
- py_tt_debug-0.3.0/pyttd/py.typed +0 -0
- py_tt_debug-0.3.0/pyttd/query.py +29 -0
- py_tt_debug-0.3.0/pyttd/recorder.py +121 -0
- py_tt_debug-0.3.0/pyttd/replay.py +58 -0
- py_tt_debug-0.3.0/pyttd/runner.py +43 -0
- py_tt_debug-0.3.0/pyttd/server.py +603 -0
- py_tt_debug-0.3.0/pyttd/session.py +688 -0
- py_tt_debug-0.3.0/pyttd/tracing/__init__.py +0 -0
- py_tt_debug-0.3.0/pyttd/tracing/constants.py +23 -0
- py_tt_debug-0.3.0/pyttd/tracing/enums.py +8 -0
- py_tt_debug-0.3.0/setup.cfg +4 -0
- py_tt_debug-0.3.0/setup.py +28 -0
- py_tt_debug-0.3.0/tests/__init__.py +0 -0
- py_tt_debug-0.3.0/tests/conftest.py +72 -0
- py_tt_debug-0.3.0/tests/test_checkpoint.py +183 -0
- py_tt_debug-0.3.0/tests/test_iohook.py +159 -0
- py_tt_debug-0.3.0/tests/test_models.py +82 -0
- py_tt_debug-0.3.0/tests/test_multithread.py +324 -0
- py_tt_debug-0.3.0/tests/test_native_stub.py +35 -0
- py_tt_debug-0.3.0/tests/test_phase6.py +299 -0
- py_tt_debug-0.3.0/tests/test_phase7.py +348 -0
- py_tt_debug-0.3.0/tests/test_recorder.py +175 -0
- py_tt_debug-0.3.0/tests/test_replay.py +145 -0
- py_tt_debug-0.3.0/tests/test_reverse_nav.py +298 -0
- py_tt_debug-0.3.0/tests/test_ringbuf.py +32 -0
- py_tt_debug-0.3.0/tests/test_server.py +454 -0
- py_tt_debug-0.3.0/tests/test_session.py +377 -0
- py_tt_debug-0.3.0/tests/test_timeline.py +234 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to pyttd are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
|
|
7
|
+
## [0.3.0] - 2026-03-17
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
#### Multi-Thread Recording
|
|
12
|
+
- All Python threads are now recorded with per-thread call stacks and global sequence ordering
|
|
13
|
+
- Per-thread SPSC ring buffers with lazy allocation on first frame entry
|
|
14
|
+
- Atomic sequence counter (`atomic_fetch_add`) for globally ordered events across threads
|
|
15
|
+
- Thread-local storage (TLS) for `call_depth` and `inside_repr` guards
|
|
16
|
+
- `ExecutionFrames.thread_id` field stores actual OS thread ID (`BigIntegerField`)
|
|
17
|
+
- Thread-aware navigation: `step_over`/`step_out` stay on current thread; `step_into`/`step_back` follow global sequence
|
|
18
|
+
- `Session.get_threads()` returns all threads seen during recording with names
|
|
19
|
+
- Stack reconstruction filters by target thread's ID
|
|
20
|
+
- Checkpoint skip guard: checkpoints are not created when multiple threads are active (fork is unsafe with threads)
|
|
21
|
+
- pthread key destructor marks orphaned thread buffers for flush thread cleanup
|
|
22
|
+
|
|
23
|
+
#### Tests
|
|
24
|
+
- 12 new multi-thread tests covering per-thread recording, stacks, thread-aware navigation
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- Ring buffer system redesigned: per-thread SPSC buffers instead of single global buffer
|
|
28
|
+
- Main thread gets 8MB string pools; secondary threads get 2MB pools
|
|
29
|
+
- `g_sequence_counter` changed from static to atomic global for cross-thread visibility
|
|
30
|
+
- `g_call_depth` and `g_inside_repr` changed from static to TLS (`PYTTD_THREAD_LOCAL`)
|
|
31
|
+
|
|
32
|
+
## [0.2.0] - 2026-03-15
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
#### Fork-Based Checkpointing (Phase 2)
|
|
37
|
+
- `fork()` creates full-process snapshots for cold navigation
|
|
38
|
+
- Checkpoint store with static array of 32 entries and smallest-gap thinning eviction
|
|
39
|
+
- Pipe-based IPC protocol: 9-byte commands (RESUME/STEP/DIE) with length-prefixed JSON results
|
|
40
|
+
- Fast-forward mode in checkpoint children (counts sequence numbers without serialization)
|
|
41
|
+
- `PyOS_BeforeFork`/`PyOS_AfterFork_Child`/`PyOS_AfterFork_Parent` for Python 3.13+ compatibility
|
|
42
|
+
- Pre-fork flush thread synchronization via condvar protocol
|
|
43
|
+
- `Checkpoint` Peewee model for tracking checkpoint metadata
|
|
44
|
+
- `ReplayController` with `goto_frame` (cold) and `warm_goto_frame` (warm-only) methods
|
|
45
|
+
- `pyttd replay --last-run --goto-frame N` CLI subcommand
|
|
46
|
+
|
|
47
|
+
#### JSON-RPC Server & Debug Adapter (Phase 3)
|
|
48
|
+
- `pyttd serve --script s.py` starts JSON-RPC server over TCP (localhost only)
|
|
49
|
+
- Content-Length framed JSON-RPC protocol (`JsonRpcConnection` class)
|
|
50
|
+
- Two-thread server model: RPC thread (selector-based event loop) + recording thread
|
|
51
|
+
- Stdout/stderr capture via `os.pipe()` + `os.dup2()`
|
|
52
|
+
- Port handshake: server writes `PYTTD_PORT:<port>` to stdout
|
|
53
|
+
- Session navigation: `step_into`, `step_over`, `step_out`, `continue_forward` with breakpoints and exception filters
|
|
54
|
+
- Stack reconstruction from frame events with push/pop on call/return
|
|
55
|
+
- Variable queries (`get_variables_at`) with type inference from `repr()` values
|
|
56
|
+
- Expression evaluation (`evaluate_at`) for hover/watch/repl contexts
|
|
57
|
+
- VSCode extension with inline debug adapter (`DebugAdapterInlineImplementation`)
|
|
58
|
+
- Full DAP handler implementations for forward navigation
|
|
59
|
+
- Backend connection with spawn, TCP connect, JSON-RPC request/response correlation
|
|
60
|
+
|
|
61
|
+
#### Time-Travel Navigation (Phase 4)
|
|
62
|
+
- `step_back` — previous `line` event (always warm, sub-ms)
|
|
63
|
+
- `reverse_continue` — backward scan with breakpoint and exception filter matching
|
|
64
|
+
- `goto_frame` — jump to any frame by sequence number with line-snapping
|
|
65
|
+
- `goto_targets` — find all executions at a file:line (capped at 1000)
|
|
66
|
+
- `restart_frame` — jump to first line of a function containing a given frame
|
|
67
|
+
- Stack cache optimization for large backward jumps
|
|
68
|
+
- I/O hooks for deterministic cold replay: `time.time`, `time.monotonic`, `time.perf_counter`, `random.random`, `random.randint`, `os.urandom`
|
|
69
|
+
- Type-specific I/O serialization: IEEE 754 doubles for floats, length-prefixed for ints/bytes
|
|
70
|
+
- I/O replay mode in checkpoint children with pre-loaded event cursor
|
|
71
|
+
- `IOEvent` Peewee model for I/O event storage
|
|
72
|
+
- DAP `supportsStepBack`, `supportsGotoTargetsRequest`, `supportsRestartFrame` capabilities
|
|
73
|
+
|
|
74
|
+
#### Timeline Scrubber (Phase 5)
|
|
75
|
+
- Canvas-based timeline webview in the Debug sidebar
|
|
76
|
+
- SQL bucket aggregation with configurable bucket count for timeline summary queries
|
|
77
|
+
- Vertical bars scaled by call depth, color-coded (blue=normal, red=exception, orange=breakpoint)
|
|
78
|
+
- Yellow cursor line with triangle marker for current position
|
|
79
|
+
- Click to navigate, drag with 150ms throttle, mousewheel zoom
|
|
80
|
+
- Keyboard shortcuts: arrows=step, Home/End=bounds, PageUp/PageDown=zoom
|
|
81
|
+
- Zoom cache (max 4 entries) for responsive interaction
|
|
82
|
+
- DPR-aware canvas rendering with `ResizeObserver`
|
|
83
|
+
- Custom DAP events: `pyttd/timelineData` (bucket data), `pyttd/positionChanged` (cursor sync)
|
|
84
|
+
- Breakpoint markers update immediately on `setBreakPointsRequest`
|
|
85
|
+
|
|
86
|
+
#### CodeLens, Inline Values, Call History (Phase 6)
|
|
87
|
+
- CodeLens annotations above traced functions showing "TTD: N calls | M exceptions"
|
|
88
|
+
- Click CodeLens to navigate to first execution of a function
|
|
89
|
+
- `InlineValuesProvider` displays variable values inline during stepping
|
|
90
|
+
- Call history tree in Debug sidebar with lazy-loaded nesting via `get_call_children`
|
|
91
|
+
- Exception icons and incomplete call markers in call history
|
|
92
|
+
- `get_traced_files` RPC — distinct filenames from recording
|
|
93
|
+
- `get_execution_stats` RPC — per-function call/exception counts via GROUP BY with CASE WHEN
|
|
94
|
+
- `get_call_children` RPC — call/return pairing at target depth for tree loading
|
|
95
|
+
|
|
96
|
+
#### Polish, Packaging, CI (Phase 7)
|
|
97
|
+
- `pyttd --version` prints version
|
|
98
|
+
- `pyttd -v` / `--verbose` enables debug logging
|
|
99
|
+
- `pyttd serve --db path.pyttd.db` replay-only mode (no recording phase)
|
|
100
|
+
- `pyttd record` and `pyttd serve --script` validate script exists before starting
|
|
101
|
+
- `PYTTD_RECORDING=1` environment variable set during recording, cleared after stop
|
|
102
|
+
- Protocol robustness: 1 MB header accumulation limit, 10 MB Content-Length limit, non-ASCII header rejection
|
|
103
|
+
- PyPI packaging: pyproject.toml with classifiers, URLs, readme, cibuildwheel config
|
|
104
|
+
- `MANIFEST.in` for source distribution (headers, py.typed, tests)
|
|
105
|
+
- `py.typed` marker (PEP 561) for type checking support
|
|
106
|
+
- GitHub Actions CI: test matrix (Python 3.12/3.13, Linux/macOS), ASAN build, sdist smoke test
|
|
107
|
+
|
|
108
|
+
#### Tests
|
|
109
|
+
- Test count grew from 26 (v0.1.0) to 147 across 14 test files
|
|
110
|
+
- 70 VSCode extension Mocha tests (backendConnection, debugSession, providers)
|
|
111
|
+
|
|
112
|
+
### Fixed
|
|
113
|
+
|
|
114
|
+
These fixes were developed between v0.1.0 and v0.2.0:
|
|
115
|
+
|
|
116
|
+
#### Critical (crash/data corruption prevention)
|
|
117
|
+
|
|
118
|
+
- **Data race on string pool `producer_idx`** — `ringbuf_pool_swap()` and `ringbuf_pool_reset_consumer()` were called outside the GIL in `flush_batch()`, racing with the producer thread. Moved both operations inside the GIL-protected section to serialize access. (`ext/recorder.c`)
|
|
119
|
+
- **NULL dereference in `flush_batch` on OOM** — `Py_DECREF(NULL)` (undefined behavior) if any `PyLong_From*`/`PyFloat_From*`/`PyUnicode_FromString` returned NULL. Added NULL checks for all 8 allocations per event; on failure, skips the event gracefully. (`ext/recorder.c`)
|
|
120
|
+
- **NULL dereference from `PyUnicode_AsUTF8`** — eval hook and all three trace function cases (`PyTrace_LINE`, `PyTrace_RETURN`, `PyTrace_EXCEPTION`) passed potentially-NULL strings to `should_ignore()`, `strstr()`, and `ringbuf_push()`. Added NULL checks; on failure, clears the error and skips the frame. (`ext/recorder.c`)
|
|
121
|
+
- **`strdup` NULL stored in ignore filters** — if `strdup()` returned NULL (OOM), the NULL pointer was stored and later passed to `strstr()`/`strcmp()` (undefined behavior). Now only stores and increments count on successful `strdup`. (`ext/recorder.c`)
|
|
122
|
+
- **`request_stop` interrupted the flush thread** — the stop-request check in the eval hook fired before the main-thread check, so the flush thread's Python calls (GIL-acquired imports, `db.close()`) would receive a spurious `KeyboardInterrupt`. Stop check is now gated on `g_main_thread_id`. (`ext/recorder.c`)
|
|
123
|
+
|
|
124
|
+
#### Correctness
|
|
125
|
+
|
|
126
|
+
- **`flush_interval_ms` parameter was ignored** — the parameter was parsed by `start_recording` but the flush thread hardcoded 10ms. Added `g_flush_interval_ms` global; the flush thread now uses the configured value on both POSIX and Windows. (`ext/recorder.c`)
|
|
127
|
+
- **Version-gated macros used dead `#elif defined()` branch** — `defined(_PyInterpreterState_SetEvalFrameFunc)` never matches (it's a function declaration, not a macro), making the `#elif` and `#else` branches identical dead code. Replaced with a clean `PY_VERSION_HEX` range check: `>= 0x030F0000` for 3.15+ and `#else` for 3.12-3.14. (`ext/recorder.c`)
|
|
128
|
+
- **`_cmd_query` created empty DB if file didn't exist** — `get_last_run()` called `connect_to_db()` which silently creates a new database, then `Runs.select().get()` raised an unhelpful `DoesNotExist`. Added `os.path.exists()` check with a clear error message before connecting. (`pyttd/cli.py`)
|
|
129
|
+
- **`sys.path[0]` restoration could raise `IndexError`** — if a user script cleared `sys.path`, the `finally` block's `sys.path[0] = old_path0` would crash. Now checks `len(sys.path) > 0` first and falls back to `sys.path.insert(0, ...)`. (`pyttd/runner.py`)
|
|
130
|
+
- **Negative `buffer_size` caused huge allocation** — a negative `int` silently cast to a large `uint32_t`. Added validation that rejects negative values with `ValueError`. (`ext/recorder.c`)
|
|
131
|
+
- **`PyDict_SetItemString` return values unchecked in `flush_batch`** — failures were silently ignored. Now checks return values; on error, clears the exception and continues. (`ext/recorder.c`)
|
|
132
|
+
- **`pyttd_get_recording_stats` had no NULL checks** — the 5 `PyLong_From*`/`PyFloat_From*` calls could return NULL on OOM, leading to `PyDict_SetItemString(dict, key, NULL)` and `Py_DECREF(NULL)`. Added NULL check with proper cleanup. (`ext/recorder.c`)
|
|
133
|
+
- **Windows `\` path separators not detected in ignore patterns** — `strchr(pattern, '/')` missed backslash-separated paths. Added `strchr(pattern, '\\')` check. (`ext/recorder.c`)
|
|
134
|
+
|
|
135
|
+
### Removed
|
|
136
|
+
|
|
137
|
+
- **Dead code: `pyttd/performance/clock.py`** — `Clock` class was unused (holdover from pre-C-extension era).
|
|
138
|
+
- **Dead code: `pyttd/performance/performance.py`** — `TracePerformance` class was unused; also had a logic bug (`total_samples` initialized to 1 instead of 0).
|
|
139
|
+
|
|
140
|
+
### Changed
|
|
141
|
+
|
|
142
|
+
- Updated project documentation: `exception_unwind` line_no limitation, interrupt mechanism (main-thread-only gating), pool swap/reset inside GIL, recorder.c description.
|
|
143
|
+
- Fixed misleading "Thread-local" comment on `g_locals_buf` — it is a static global, safe only because Phase 1 records the main thread exclusively. (`ext/recorder.c`)
|
|
144
|
+
- Added documentation comments for `call` event not capturing locals (frame may not be initialized) and `exception_unwind` not capturing locals (internal frame may not be valid after eval). (`ext/recorder.c`)
|
|
145
|
+
|
|
146
|
+
## [0.1.0] - 2026-03-04
|
|
147
|
+
|
|
148
|
+
Initial implementation of Phases 0 and 1.
|
|
149
|
+
|
|
150
|
+
### Added
|
|
151
|
+
|
|
152
|
+
#### C Extension (`pyttd_native`)
|
|
153
|
+
- PEP 523 frame eval hook for intercepting frame entry (`call` events)
|
|
154
|
+
- C-level trace function (`PyEval_SetTrace`) for `line`, `return`, `exception` events
|
|
155
|
+
- `exception_unwind` event recorded by eval hook when frame exits via exception propagation
|
|
156
|
+
- Lock-free SPSC ring buffer (C11 `<stdatomic.h>`, power-of-2 capacity, default 65536 slots)
|
|
157
|
+
- Double-buffered 8MB string pools with drop-on-full semantics
|
|
158
|
+
- JSON escaping helper (`json_escape_string()`) for locals serialization
|
|
159
|
+
- `call_depth` tracking in the eval hook (not trace function)
|
|
160
|
+
- Monotonic timestamp recording relative to recording start
|
|
161
|
+
- Flush thread with configurable interval, GIL management, and DB connection cleanup on exit
|
|
162
|
+
- `request_stop()` — atomic stop flag checked by eval hook, raises `KeyboardInterrupt`
|
|
163
|
+
- `set_ignore_patterns()` — substring (directory) and exact-match (basename/function) filtering
|
|
164
|
+
- `get_recording_stats()` — frame count, dropped frames, elapsed time, flush count, pool overflows
|
|
165
|
+
- Platform detection macros (`PYTTD_HAS_FORK`, Windows/POSIX)
|
|
166
|
+
- Stub functions for Phase 2+ (`create_checkpoint`, `restore_checkpoint`, `kill_all_checkpoints`, `install_io_hooks`, `remove_io_hooks`)
|
|
167
|
+
|
|
168
|
+
#### Python Backend
|
|
169
|
+
- `pyttd record script.py` — records all user-code frame events to `<script>.pyttd.db`
|
|
170
|
+
- `pyttd record --module pkg.mod` — records module execution
|
|
171
|
+
- `pyttd query --last-run --frames` — dumps recorded frames with source lines
|
|
172
|
+
- `@ttdbg` decorator for recording function execution
|
|
173
|
+
- `Recorder` class — Python wrapper around C recorder with run lifecycle management
|
|
174
|
+
- `Runner` class — user script/module execution via `runpy`
|
|
175
|
+
- `PyttdConfig` dataclass for configuration
|
|
176
|
+
- Custom exception hierarchy (`PyttdError`, `RecordingError`, `ReplayError`, `ServerError`)
|
|
177
|
+
- Peewee ORM models with deferred database pattern (`SqliteDatabase(None)`)
|
|
178
|
+
- `Runs` — run metadata (UUID PK, script path, timestamps, frame count)
|
|
179
|
+
- `ExecutionFrames` — frame events with indexes on `(run_id, sequence_no)`, `(run_id, filename, line_no)`, etc.
|
|
180
|
+
- Storage utilities: `connect_to_db`, `close_db`, `delete_db_files`, `initialize_schema`
|
|
181
|
+
- Query API: `get_last_run`, `get_frames`, `get_frame_at_seq`, `get_line_code`
|
|
182
|
+
- WAL mode, `busy_timeout: 5000`, batch insert (500 per batch)
|
|
183
|
+
- Stub subcommands for `replay` (Phase 2) and `serve` (Phase 3)
|
|
184
|
+
|
|
185
|
+
#### VSCode Extension (skeleton)
|
|
186
|
+
- TypeScript project structure with `package.json`, `tsconfig.json`
|
|
187
|
+
- DAP handler stubs in `pyttdDebugSession.ts`
|
|
188
|
+
- Backend connection stub in `backendConnection.ts`
|
|
189
|
+
|
|
190
|
+
#### Tests
|
|
191
|
+
- 26 tests passing across 4 test files
|
|
192
|
+
- `test_models.py` (7) — model creation, batch insert, WAL mode
|
|
193
|
+
- `test_native_stub.py` (7) — import check, stub functions raise `NotImplementedError`
|
|
194
|
+
- `test_recorder.py` (9) — recording, sequence monotonicity, events, locals, call depth
|
|
195
|
+
- `test_ringbuf.py` (3) — flush pipeline, dict keys, recording stats
|
|
196
|
+
- All tests use `tmp_path` fixture for DB isolation
|
|
197
|
+
|
|
198
|
+
#### Build System
|
|
199
|
+
- `pyproject.toml` — project metadata, dependencies, build system config
|
|
200
|
+
- `setup.py` — C extension build (`ext_modules`)
|
|
201
|
+
- Editable install via `pip install -e ".[dev]"`
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Parham Moini
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: py-tt-debug
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Python Time-Travel Debugger — record, replay, and step backward through program execution
|
|
5
|
+
Author: pyttd contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/pyttd/pyttd
|
|
8
|
+
Project-URL: Repository, https://github.com/pyttd/pyttd
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/pyttd/pyttd/issues
|
|
10
|
+
Project-URL: Documentation, https://github.com/pyttd/pyttd/tree/main/docs
|
|
11
|
+
Keywords: debugger,time-travel,debugging,replay,tracing
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: C
|
|
17
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Operating System :: MacOS
|
|
20
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
21
|
+
Requires-Python: >=3.12
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: peewee>=3.17
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-benchmark>=4.0; extra == "dev"
|
|
28
|
+
Requires-Dist: rich; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# pyttd
|
|
32
|
+
|
|
33
|
+
[](https://github.com/pyttd/pyttd/actions/workflows/ci.yml)
|
|
34
|
+
[](LICENSE)
|
|
35
|
+
[](https://www.python.org/downloads/)
|
|
36
|
+
|
|
37
|
+
**pyttd** (Python Time-Travel Debugger) is an open-source time-travel debugger for Python with full VSCode integration. It records complete program execution at the C level, then lets you step backward and forward, jump to any point in the trace, and visually scrub through a timeline — all from within your editor.
|
|
38
|
+
|
|
39
|
+
## What is Time-Travel Debugging?
|
|
40
|
+
|
|
41
|
+
Traditional debuggers only move forward. If you step past a bug, you start over. pyttd records every execution frame during a single run, then drops you into a **replay session** where you can navigate freely in both directions:
|
|
42
|
+
|
|
43
|
+
- **Step backward** through execution to see exactly how state evolved
|
|
44
|
+
- **Reverse continue** to find the last time a breakpoint was hit
|
|
45
|
+
- **Jump to any frame** in the entire recording
|
|
46
|
+
- **Scrub a visual timeline** to navigate through call depth, exceptions, and execution flow
|
|
47
|
+
- **Inspect variables** at any point without re-running the program
|
|
48
|
+
|
|
49
|
+
pyttd is a **post-mortem replay debugger** — your script runs to completion, and then you debug the recorded trace.
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
> **Requires Python 3.12+** — uses CPython C API features introduced in 3.12.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install pyttd
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Or from source:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
git clone https://github.com/pyttd/pyttd.git
|
|
63
|
+
cd pyttd
|
|
64
|
+
python3 -m venv .venv
|
|
65
|
+
.venv/bin/pip install -e ".[dev]"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Quick Start
|
|
69
|
+
|
|
70
|
+
### CLI
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Record the included example
|
|
74
|
+
pyttd record examples/hello.py
|
|
75
|
+
|
|
76
|
+
# Query the recording
|
|
77
|
+
pyttd query --last-run --frames
|
|
78
|
+
|
|
79
|
+
# Replay and jump to a specific frame
|
|
80
|
+
pyttd replay --last-run --goto-frame 750
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### VSCode
|
|
84
|
+
|
|
85
|
+
1. Build the extension: `cd vscode-pyttd && npm install && npm run package`
|
|
86
|
+
2. In VSCode: Extensions sidebar → `...` → **Install from VSIX** → select `pyttd-*.vsix`
|
|
87
|
+
3. Add to `.vscode/launch.json`:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"type": "pyttd",
|
|
92
|
+
"request": "launch",
|
|
93
|
+
"name": "Time-Travel Debug",
|
|
94
|
+
"program": "${file}"
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
4. Press **F5** — your script records, then you navigate freely
|
|
99
|
+
|
|
100
|
+
### Python API
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from pyttd import ttdbg
|
|
104
|
+
|
|
105
|
+
@ttdbg
|
|
106
|
+
def my_function():
|
|
107
|
+
x = 42
|
|
108
|
+
return x * 2
|
|
109
|
+
|
|
110
|
+
my_function() # Records to <file>.pyttd.db
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Features
|
|
114
|
+
|
|
115
|
+
### Recording
|
|
116
|
+
- C extension recorder using PEP 523 frame eval hooks (not `sys.settrace`)
|
|
117
|
+
- Lock-free per-thread SPSC ring buffers with background flush
|
|
118
|
+
- Fork-based checkpointing for fast cold navigation (Linux/macOS)
|
|
119
|
+
- Multi-thread recording with globally ordered sequence numbers
|
|
120
|
+
- I/O hooks for deterministic checkpoint replay (`time.time`, `random.random`, `os.urandom`)
|
|
121
|
+
|
|
122
|
+
### Navigation
|
|
123
|
+
- Forward: step into/over/out, continue with breakpoints
|
|
124
|
+
- Reverse: step back, reverse continue with breakpoints and exception filters
|
|
125
|
+
- Jump: goto frame, goto targets (all executions of a line), restart frame
|
|
126
|
+
- Warm navigation (SQLite, sub-ms) for stepping; cold navigation (checkpoint restore, 50-300ms) for jumps
|
|
127
|
+
|
|
128
|
+
### VSCode Extension
|
|
129
|
+
- Full DAP implementation with step-back and reverse-continue
|
|
130
|
+
- Canvas-based timeline scrubber with click/drag/zoom
|
|
131
|
+
- CodeLens annotations showing call and exception counts per function
|
|
132
|
+
- Inline variable values during stepping
|
|
133
|
+
- Call history tree with lazy-loaded nesting and exception markers
|
|
134
|
+
- Exception breakpoint filters (uncaught, all raised)
|
|
135
|
+
|
|
136
|
+
## Architecture
|
|
137
|
+
|
|
138
|
+
Three-layer system:
|
|
139
|
+
|
|
140
|
+
| Layer | Technology | Responsibility |
|
|
141
|
+
|-------|-----------|----------------|
|
|
142
|
+
| C Extension (`pyttd_native`) | C, CPython API | Frame recording, ring buffer, checkpoints, I/O hooks |
|
|
143
|
+
| Python Backend (`pyttd/`) | Python, Peewee, SQLite | JSON-RPC server, session navigation, query API |
|
|
144
|
+
| VSCode Extension (`vscode-pyttd/`) | TypeScript | DAP handlers, timeline webview, CodeLens, inline values |
|
|
145
|
+
|
|
146
|
+
See [docs/architecture.md](docs/architecture.md) for the full design.
|
|
147
|
+
|
|
148
|
+
## Platform Support
|
|
149
|
+
|
|
150
|
+
| Platform | Recording | Warm Navigation | Cold Navigation | Multi-Thread |
|
|
151
|
+
|----------|-----------|-----------------|-----------------|--------------|
|
|
152
|
+
| Linux | Full | Full | Full | Full |
|
|
153
|
+
| macOS | Full | Full | Partial* | Full |
|
|
154
|
+
| Windows | Full | Full | None | Full |
|
|
155
|
+
|
|
156
|
+
\* macOS: checkpoints skip when multiple threads are active.
|
|
157
|
+
|
|
158
|
+
## Requirements
|
|
159
|
+
|
|
160
|
+
- **Python >= 3.12** (required for `PyUnstable_InterpreterFrame_*` C API)
|
|
161
|
+
- **C compiler** (GCC, Clang, or MSVC)
|
|
162
|
+
- **VSCode** (for the extension; CLI works standalone)
|
|
163
|
+
|
|
164
|
+
## Known Limitations
|
|
165
|
+
|
|
166
|
+
- Variables are `repr()` snapshots — flat strings, not expandable objects
|
|
167
|
+
- Expression evaluation operates on recorded snapshots, not live values
|
|
168
|
+
- C extension internals are opaque (third-party C extension objects may have uninformative `repr()`)
|
|
169
|
+
- Windows: no cold navigation (no `fork()`)
|
|
170
|
+
- `exception_unwind` line number is from function entry, not the exception site
|
|
171
|
+
- Variable repr strings are capped at 256 characters
|
|
172
|
+
|
|
173
|
+
## Documentation
|
|
174
|
+
|
|
175
|
+
- **[Getting Started](docs/getting-started.md)** — first recording walkthrough
|
|
176
|
+
- **[CLI Reference](docs/cli-reference.md)** — all commands and flags
|
|
177
|
+
- **[VSCode Guide](docs/vscode-guide.md)** — extension features and configuration
|
|
178
|
+
- **[API Reference](docs/api-reference.md)** — Python programmatic API
|
|
179
|
+
- **[Architecture](docs/architecture.md)** — system design and data flow
|
|
180
|
+
- **[Troubleshooting](docs/troubleshooting.md)** — common issues
|
|
181
|
+
- **[FAQ](docs/faq.md)** — frequently asked questions
|
|
182
|
+
- **[Contributing](CONTRIBUTING.md)** — how to contribute
|
|
183
|
+
- **[Changelog](CHANGELOG.md)** — version history
|
|
184
|
+
|
|
185
|
+
Development guides: [Building](docs/development/building.md) | [Testing](docs/development/testing.md) | [C Extension](docs/development/c-extension.md) | [Protocol](docs/development/protocol.md) | [Releasing](docs/development/releasing.md)
|
|
186
|
+
|
|
187
|
+
## Contributing
|
|
188
|
+
|
|
189
|
+
Contributions welcome across C, Python, and TypeScript. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and guidelines.
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
MIT License. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# pyttd
|
|
2
|
+
|
|
3
|
+
[](https://github.com/pyttd/pyttd/actions/workflows/ci.yml)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
6
|
+
|
|
7
|
+
**pyttd** (Python Time-Travel Debugger) is an open-source time-travel debugger for Python with full VSCode integration. It records complete program execution at the C level, then lets you step backward and forward, jump to any point in the trace, and visually scrub through a timeline — all from within your editor.
|
|
8
|
+
|
|
9
|
+
## What is Time-Travel Debugging?
|
|
10
|
+
|
|
11
|
+
Traditional debuggers only move forward. If you step past a bug, you start over. pyttd records every execution frame during a single run, then drops you into a **replay session** where you can navigate freely in both directions:
|
|
12
|
+
|
|
13
|
+
- **Step backward** through execution to see exactly how state evolved
|
|
14
|
+
- **Reverse continue** to find the last time a breakpoint was hit
|
|
15
|
+
- **Jump to any frame** in the entire recording
|
|
16
|
+
- **Scrub a visual timeline** to navigate through call depth, exceptions, and execution flow
|
|
17
|
+
- **Inspect variables** at any point without re-running the program
|
|
18
|
+
|
|
19
|
+
pyttd is a **post-mortem replay debugger** — your script runs to completion, and then you debug the recorded trace.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
> **Requires Python 3.12+** — uses CPython C API features introduced in 3.12.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install pyttd
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or from source:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
git clone https://github.com/pyttd/pyttd.git
|
|
33
|
+
cd pyttd
|
|
34
|
+
python3 -m venv .venv
|
|
35
|
+
.venv/bin/pip install -e ".[dev]"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
### CLI
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Record the included example
|
|
44
|
+
pyttd record examples/hello.py
|
|
45
|
+
|
|
46
|
+
# Query the recording
|
|
47
|
+
pyttd query --last-run --frames
|
|
48
|
+
|
|
49
|
+
# Replay and jump to a specific frame
|
|
50
|
+
pyttd replay --last-run --goto-frame 750
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### VSCode
|
|
54
|
+
|
|
55
|
+
1. Build the extension: `cd vscode-pyttd && npm install && npm run package`
|
|
56
|
+
2. In VSCode: Extensions sidebar → `...` → **Install from VSIX** → select `pyttd-*.vsix`
|
|
57
|
+
3. Add to `.vscode/launch.json`:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"type": "pyttd",
|
|
62
|
+
"request": "launch",
|
|
63
|
+
"name": "Time-Travel Debug",
|
|
64
|
+
"program": "${file}"
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
4. Press **F5** — your script records, then you navigate freely
|
|
69
|
+
|
|
70
|
+
### Python API
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from pyttd import ttdbg
|
|
74
|
+
|
|
75
|
+
@ttdbg
|
|
76
|
+
def my_function():
|
|
77
|
+
x = 42
|
|
78
|
+
return x * 2
|
|
79
|
+
|
|
80
|
+
my_function() # Records to <file>.pyttd.db
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Features
|
|
84
|
+
|
|
85
|
+
### Recording
|
|
86
|
+
- C extension recorder using PEP 523 frame eval hooks (not `sys.settrace`)
|
|
87
|
+
- Lock-free per-thread SPSC ring buffers with background flush
|
|
88
|
+
- Fork-based checkpointing for fast cold navigation (Linux/macOS)
|
|
89
|
+
- Multi-thread recording with globally ordered sequence numbers
|
|
90
|
+
- I/O hooks for deterministic checkpoint replay (`time.time`, `random.random`, `os.urandom`)
|
|
91
|
+
|
|
92
|
+
### Navigation
|
|
93
|
+
- Forward: step into/over/out, continue with breakpoints
|
|
94
|
+
- Reverse: step back, reverse continue with breakpoints and exception filters
|
|
95
|
+
- Jump: goto frame, goto targets (all executions of a line), restart frame
|
|
96
|
+
- Warm navigation (SQLite, sub-ms) for stepping; cold navigation (checkpoint restore, 50-300ms) for jumps
|
|
97
|
+
|
|
98
|
+
### VSCode Extension
|
|
99
|
+
- Full DAP implementation with step-back and reverse-continue
|
|
100
|
+
- Canvas-based timeline scrubber with click/drag/zoom
|
|
101
|
+
- CodeLens annotations showing call and exception counts per function
|
|
102
|
+
- Inline variable values during stepping
|
|
103
|
+
- Call history tree with lazy-loaded nesting and exception markers
|
|
104
|
+
- Exception breakpoint filters (uncaught, all raised)
|
|
105
|
+
|
|
106
|
+
## Architecture
|
|
107
|
+
|
|
108
|
+
Three-layer system:
|
|
109
|
+
|
|
110
|
+
| Layer | Technology | Responsibility |
|
|
111
|
+
|-------|-----------|----------------|
|
|
112
|
+
| C Extension (`pyttd_native`) | C, CPython API | Frame recording, ring buffer, checkpoints, I/O hooks |
|
|
113
|
+
| Python Backend (`pyttd/`) | Python, Peewee, SQLite | JSON-RPC server, session navigation, query API |
|
|
114
|
+
| VSCode Extension (`vscode-pyttd/`) | TypeScript | DAP handlers, timeline webview, CodeLens, inline values |
|
|
115
|
+
|
|
116
|
+
See [docs/architecture.md](docs/architecture.md) for the full design.
|
|
117
|
+
|
|
118
|
+
## Platform Support
|
|
119
|
+
|
|
120
|
+
| Platform | Recording | Warm Navigation | Cold Navigation | Multi-Thread |
|
|
121
|
+
|----------|-----------|-----------------|-----------------|--------------|
|
|
122
|
+
| Linux | Full | Full | Full | Full |
|
|
123
|
+
| macOS | Full | Full | Partial* | Full |
|
|
124
|
+
| Windows | Full | Full | None | Full |
|
|
125
|
+
|
|
126
|
+
\* macOS: checkpoints skip when multiple threads are active.
|
|
127
|
+
|
|
128
|
+
## Requirements
|
|
129
|
+
|
|
130
|
+
- **Python >= 3.12** (required for `PyUnstable_InterpreterFrame_*` C API)
|
|
131
|
+
- **C compiler** (GCC, Clang, or MSVC)
|
|
132
|
+
- **VSCode** (for the extension; CLI works standalone)
|
|
133
|
+
|
|
134
|
+
## Known Limitations
|
|
135
|
+
|
|
136
|
+
- Variables are `repr()` snapshots — flat strings, not expandable objects
|
|
137
|
+
- Expression evaluation operates on recorded snapshots, not live values
|
|
138
|
+
- C extension internals are opaque (third-party C extension objects may have uninformative `repr()`)
|
|
139
|
+
- Windows: no cold navigation (no `fork()`)
|
|
140
|
+
- `exception_unwind` line number is from function entry, not the exception site
|
|
141
|
+
- Variable repr strings are capped at 256 characters
|
|
142
|
+
|
|
143
|
+
## Documentation
|
|
144
|
+
|
|
145
|
+
- **[Getting Started](docs/getting-started.md)** — first recording walkthrough
|
|
146
|
+
- **[CLI Reference](docs/cli-reference.md)** — all commands and flags
|
|
147
|
+
- **[VSCode Guide](docs/vscode-guide.md)** — extension features and configuration
|
|
148
|
+
- **[API Reference](docs/api-reference.md)** — Python programmatic API
|
|
149
|
+
- **[Architecture](docs/architecture.md)** — system design and data flow
|
|
150
|
+
- **[Troubleshooting](docs/troubleshooting.md)** — common issues
|
|
151
|
+
- **[FAQ](docs/faq.md)** — frequently asked questions
|
|
152
|
+
- **[Contributing](CONTRIBUTING.md)** — how to contribute
|
|
153
|
+
- **[Changelog](CHANGELOG.md)** — version history
|
|
154
|
+
|
|
155
|
+
Development guides: [Building](docs/development/building.md) | [Testing](docs/development/testing.md) | [C Extension](docs/development/c-extension.md) | [Protocol](docs/development/protocol.md) | [Releasing](docs/development/releasing.md)
|
|
156
|
+
|
|
157
|
+
## Contributing
|
|
158
|
+
|
|
159
|
+
Contributions welcome across C, Python, and TypeScript. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and guidelines.
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT License. See [LICENSE](LICENSE) for details.
|