Skip to content

Commit 1964a5b

Browse files
committed
impl(design): value-capture
1 parent 0c0993e commit 1964a5b

File tree

6 files changed

+1096
-165
lines changed

6 files changed

+1096
-165
lines changed

codetracer-python-recorder/src/code_object.rs

Lines changed: 41 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
use dashmap::DashMap;
12
use once_cell::sync::OnceCell;
23
use pyo3::prelude::*;
34
use pyo3::types::PyCode;
4-
use dashmap::DashMap;
55
use std::sync::Arc;
66

77
/// A wrapper around Python `code` objects providing cached access to
@@ -50,73 +50,73 @@ impl CodeObjectWrapper {
5050
}
5151

5252
pub fn filename<'py>(&'py self, py: Python<'py>) -> PyResult<&'py str> {
53-
let value = self.cache.filename.get_or_try_init(|| -> PyResult<String> {
54-
let s: String = self
55-
.as_bound(py)
56-
.getattr("co_filename")?
57-
.extract()?;
58-
Ok(s)
59-
})?;
53+
let value = self
54+
.cache
55+
.filename
56+
.get_or_try_init(|| -> PyResult<String> {
57+
let s: String = self.as_bound(py).getattr("co_filename")?.extract()?;
58+
Ok(s)
59+
})?;
6060
Ok(value.as_str())
6161
}
6262

6363
pub fn qualname<'py>(&'py self, py: Python<'py>) -> PyResult<&'py str> {
64-
let value = self.cache.qualname.get_or_try_init(|| -> PyResult<String> {
65-
let s: String = self
66-
.as_bound(py)
67-
.getattr("co_qualname")?
68-
.extract()?;
69-
Ok(s)
70-
})?;
64+
let value = self
65+
.cache
66+
.qualname
67+
.get_or_try_init(|| -> PyResult<String> {
68+
let s: String = self.as_bound(py).getattr("co_qualname")?.extract()?;
69+
Ok(s)
70+
})?;
7171
Ok(value.as_str())
7272
}
7373

7474
pub fn first_line(&self, py: Python<'_>) -> PyResult<u32> {
75-
let value = *self.cache.firstlineno.get_or_try_init(|| -> PyResult<u32> {
76-
let v: u32 = self
77-
.as_bound(py)
78-
.getattr("co_firstlineno")?
79-
.extract()?;
80-
Ok(v)
81-
})?;
75+
let value = *self
76+
.cache
77+
.firstlineno
78+
.get_or_try_init(|| -> PyResult<u32> {
79+
let v: u32 = self.as_bound(py).getattr("co_firstlineno")?.extract()?;
80+
Ok(v)
81+
})?;
8282
Ok(value)
8383
}
8484

8585
pub fn arg_count(&self, py: Python<'_>) -> PyResult<u16> {
8686
let value = *self.cache.argcount.get_or_try_init(|| -> PyResult<u16> {
87-
let v: u16 = self
88-
.as_bound(py)
89-
.getattr("co_argcount")?
90-
.extract()?;
87+
let v: u16 = self.as_bound(py).getattr("co_argcount")?.extract()?;
9188
Ok(v)
9289
})?;
9390
Ok(value)
9491
}
9592

9693
pub fn flags(&self, py: Python<'_>) -> PyResult<u32> {
9794
let value = *self.cache.flags.get_or_try_init(|| -> PyResult<u32> {
98-
let v: u32 = self
99-
.as_bound(py)
100-
.getattr("co_flags")?
101-
.extract()?;
95+
let v: u32 = self.as_bound(py).getattr("co_flags")?.extract()?;
10296
Ok(v)
10397
})?;
10498
Ok(value)
10599
}
106100

107101
fn lines<'py>(&'py self, py: Python<'py>) -> PyResult<&'py [LineEntry]> {
108-
let vec = self.cache.lines.get_or_try_init(|| -> PyResult<Vec<LineEntry>> {
109-
let mut entries = Vec::new();
110-
let iter = self.as_bound(py).call_method0("co_lines")?;
111-
let iter = iter.try_iter()?;
112-
for item in iter {
113-
let (start, _end, line): (u32, u32, Option<u32>) = item?.extract()?;
114-
if let Some(line) = line {
115-
entries.push(LineEntry { offset: start, line });
102+
let vec = self
103+
.cache
104+
.lines
105+
.get_or_try_init(|| -> PyResult<Vec<LineEntry>> {
106+
let mut entries = Vec::new();
107+
let iter = self.as_bound(py).call_method0("co_lines")?;
108+
let iter = iter.try_iter()?;
109+
for item in iter {
110+
let (start, _end, line): (u32, u32, Option<u32>) = item?.extract()?;
111+
if let Some(line) = line {
112+
entries.push(LineEntry {
113+
offset: start,
114+
line,
115+
});
116+
}
116117
}
117-
}
118-
Ok(entries)
119-
})?;
118+
Ok(entries)
119+
})?;
120120
Ok(vec.as_slice())
121121
}
122122

@@ -161,4 +161,3 @@ impl CodeObjectRegistry {
161161
self.map.clear();
162162
}
163163
}
164-

codetracer-python-recorder/src/lib.rs

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::fs;
2-
use std::path::{PathBuf, Path};
2+
use std::path::{Path, PathBuf};
33
use std::sync::atomic::{AtomicBool, Ordering};
44
use std::sync::Once;
55

@@ -8,8 +8,8 @@ use pyo3::prelude::*;
88
use std::fmt;
99

1010
pub mod code_object;
11-
pub mod tracer;
1211
mod runtime_tracer;
12+
pub mod tracer;
1313
pub use crate::code_object::{CodeObjectRegistry, CodeObjectWrapper};
1414
pub use crate::tracer::{install_tracer, uninstall_tracer, EventSet, Tracer};
1515

@@ -25,20 +25,14 @@ fn init_rust_logging_with_default(default_filter: &str) {
2525
let env = env_logger::Env::default().default_filter_or(default_filter);
2626
// Use a compact format with timestamps and targets to aid debugging.
2727
let mut builder = env_logger::Builder::from_env(env);
28-
builder
29-
.format_timestamp_micros()
30-
.format_target(true);
28+
builder.format_timestamp_micros().format_target(true);
3129
let _ = builder.try_init();
3230
});
3331
}
3432

3533
/// Start tracing using sys.monitoring and runtime_tracing writer.
3634
#[pyfunction]
37-
fn start_tracing(
38-
path: &str,
39-
format: &str,
40-
activation_path: Option<&str>,
41-
) -> PyResult<()> {
35+
fn start_tracing(path: &str, format: &str, activation_path: Option<&str>) -> PyResult<()> {
4236
// Ensure logging is ready before any tracer logs might be emitted.
4337
// Default only our crate to debug to avoid excessive verbosity from deps.
4438
init_rust_logging_with_default("codetracer_python_recorder=debug");
@@ -49,26 +43,31 @@ fn start_tracing(
4943
// Interpret `path` as a directory where trace files will be written.
5044
let out_dir = Path::new(path);
5145
if out_dir.exists() && !out_dir.is_dir() {
52-
return Err(PyRuntimeError::new_err("trace path exists and is not a directory"));
46+
return Err(PyRuntimeError::new_err(
47+
"trace path exists and is not a directory",
48+
));
5349
}
5450
if !out_dir.exists() {
5551
// Best-effort create the directory tree
56-
fs::create_dir_all(&out_dir)
57-
.map_err(|e| PyRuntimeError::new_err(format!("failed to create trace directory: {}", e)))?;
52+
fs::create_dir_all(&out_dir).map_err(|e| {
53+
PyRuntimeError::new_err(format!("failed to create trace directory: {}", e))
54+
})?;
5855
}
5956

6057
// Map format string to enum
6158
let fmt = match format.to_lowercase().as_str() {
6259
"json" => runtime_tracing::TraceEventsFileFormat::Json,
6360
// Use BinaryV0 for "binary" to avoid streaming writer here.
64-
"binary" | "binaryv0" | "binary_v0" | "b0" => runtime_tracing::TraceEventsFileFormat::BinaryV0,
61+
"binary" | "binaryv0" | "binary_v0" | "b0" => {
62+
runtime_tracing::TraceEventsFileFormat::BinaryV0
63+
}
6564
//TODO AI! We need to assert! that the format is among the known values.
6665
other => {
6766
eprintln!("Unknown format '{}', defaulting to binary (v0)", other);
6867
runtime_tracing::TraceEventsFileFormat::BinaryV0
6968
}
7069
};
71-
70+
7271
// Build output file paths inside the directory.
7372
let (events_path, meta_path, paths_path) = match fmt {
7473
runtime_tracing::TraceEventsFileFormat::Json => (
@@ -90,17 +89,10 @@ fn start_tracing(
9089
// Program and args: keep minimal; Python-side API stores full session info if needed
9190
let sys = py.import("sys")?;
9291
let argv = sys.getattr("argv")?;
93-
let program: String = argv
94-
.get_item(0)?
95-
.extract::<String>()?;
92+
let program: String = argv.get_item(0)?.extract::<String>()?;
9693
//TODO: Error-handling. What to do if argv is empty? Does this ever happen?
9794

98-
let mut tracer = runtime_tracer::RuntimeTracer::new(
99-
&program,
100-
&[],
101-
fmt,
102-
activation_path,
103-
);
95+
let mut tracer = runtime_tracer::RuntimeTracer::new(&program, &[], fmt, activation_path);
10496

10597
// Start location: prefer activation path, otherwise best-effort argv[0]
10698
let start_path: &Path = activation_path.unwrap_or(Path::new(&program));

0 commit comments

Comments
 (0)