Skip to content

Commit 106aad2

Browse files
committed
Run the recorder as a script python -m codetracer_python_recorder [args] script.py [script args]
codetracer-python-recorder/codetracer_python_recorder/__init__.py: codetracer-python-recorder/codetracer_python_recorder/__main__.py: hello.py: trace.json: trace.metadata.json: trace.paths.json: Signed-off-by: Tzanko Matev <[email protected]>
1 parent ddf3653 commit 106aad2

File tree

2 files changed

+104
-3
lines changed

2 files changed

+104
-3
lines changed

codetracer-python-recorder/codetracer_python_recorder/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
maintains placeholder state and performs no actual tracing.
88
"""
99

10-
from .api import *
11-
12-
__all__ = api.__all__
10+
from . import api as _api
11+
from .api import * # re-export public API symbols
1312

13+
__all__ = _api.__all__
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"""CLI to record a trace while running a Python script.
2+
3+
Usage:
4+
python -m codetracer_python_recorder [codetracer options] <script.py> [script args...]
5+
6+
Codetracer options (must appear before the script path):
7+
--codetracer-trace PATH Output events file (default: trace.bin or trace.json)
8+
--codetracer-format {binary,json} Output format (default: binary)
9+
--codetracer-capture-values BOOL Whether to capture values (default: true)
10+
11+
Examples:
12+
python -m codetracer_python_recorder --codetracer-format=json app.py --flag=1
13+
python -m codetracer_python_recorder --codetracer-trace=out.bin script.py --x=2
14+
python -m codetracer_python_recorder --codetracer-capture-values=false script.py
15+
"""
16+
from __future__ import annotations
17+
18+
import runpy
19+
import sys
20+
from pathlib import Path
21+
22+
from . import DEFAULT_FORMAT, start, stop
23+
import argparse
24+
25+
26+
def _default_trace_path(fmt: str) -> Path:
27+
# Keep a simple filename; Rust side derives sidecars (metadata/paths)
28+
if fmt == "json":
29+
return Path.cwd() / "trace.json"
30+
return Path.cwd() / "trace.bin"
31+
32+
33+
def main(argv: list[str] | None = None) -> int:
34+
if argv is None:
35+
argv = sys.argv[1:]
36+
37+
parser = argparse.ArgumentParser(add_help=False, allow_abbrev=False)
38+
parser.add_argument("--codetracer-trace", dest="trace", default=None)
39+
parser.add_argument(
40+
"--codetracer-format",
41+
dest="format",
42+
choices=["binary", "json"],
43+
default=DEFAULT_FORMAT,
44+
)
45+
# Only parse our options; leave script and script args in unknown
46+
ns, unknown = parser.parse_known_args(argv)
47+
48+
# If unknown contains codetracer-prefixed options, they are invalid
49+
for tok in unknown:
50+
if isinstance(tok, str) and tok.startswith("--codetracer-"):
51+
sys.stderr.write(f"error: unknown codetracer option: {tok}\n")
52+
return 2
53+
54+
if not unknown:
55+
sys.stderr.write("Usage: python -m codetracer_python_recorder [codetracer options] <script.py> [args...]\n")
56+
return 2
57+
58+
script_path = Path(unknown[0])
59+
script_args = unknown[1:]
60+
61+
if not script_path.exists():
62+
sys.stderr.write(f"error: script not found: {script_path}\n")
63+
return 2
64+
65+
def _parse_bool(s: str) -> bool:
66+
v = s.strip().lower()
67+
if v in {"1", "true", "yes", "on"}:
68+
return True
69+
if v in {"0", "false", "no", "off"}:
70+
return False
71+
raise ValueError(f"invalid boolean: {s}")
72+
73+
fmt = ns.format or DEFAULT_FORMAT
74+
trace_path = Path(ns.trace) if ns.trace else _default_trace_path(fmt)
75+
76+
old_argv = sys.argv
77+
sys.argv = [str(script_path)] + script_args
78+
# Activate tracing only after entering the target script file.
79+
session = start(
80+
trace_path,
81+
format=fmt,
82+
start_on_enter=script_path,
83+
)
84+
try:
85+
runpy.run_path(str(script_path.resolve()), run_name="__main__")
86+
return 0
87+
except SystemExit as e:
88+
# Preserve script's exit code
89+
code = e.code if isinstance(e.code, int) else 1
90+
return code
91+
finally:
92+
# Ensure tracer stops and files are flushed
93+
try:
94+
session.flush()
95+
finally:
96+
stop()
97+
sys.argv = old_argv
98+
99+
100+
if __name__ == "__main__": # pragma: no cover
101+
raise SystemExit(main())

0 commit comments

Comments
 (0)