Skip to content

Commit 364dfc6

Browse files
committed
WS2-2
codetracer-python-recorder/README.md: codetracer-python-recorder/src/runtime/tracer/runtime_tracer.rs: codetracer-python-recorder/tests/python/test_monitoring_events.py: design-docs/module-call-event-naming-implementation-plan.status.md: Signed-off-by: Tzanko Matev <[email protected]>
1 parent 963246c commit 364dfc6

File tree

4 files changed

+71
-42
lines changed

4 files changed

+71
-42
lines changed

codetracer-python-recorder/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ selector = "arg:literal:debug_payload"
8282
action = "drop"
8383
```
8484

85+
## Trace naming semantics
86+
87+
- Module-level activations no longer appear as the ambiguous `<module>` label. When the recorder sees `co_qualname == "<module>"`, it derives the actual dotted package name (e.g., `<my_pkg.mod>` or `<boto3.session>`) using project roots, `sys.modules`, and frame metadata.
88+
- The angle-bracket convention remains for module entries so downstream tooling can distinguish top-level activations at a glance.
89+
- Traces will still emit `<module>` for synthetic filenames (`<stdin>`, `<string>`), frozen/importlib bootstrap frames, or exotic loaders that omit filenames entirely. This preserves previous behaviour when no reliable name exists.
90+
8591
## Packaging expectations
8692

8793
Desktop installers add the wheel to `PYTHONPATH` before invoking the user’s

codetracer-python-recorder/src/runtime/tracer/runtime_tracer.rs

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,13 @@ impl RuntimeTracer {
157157
}
158158
}
159159

160+
#[cfg(test)]
161+
impl RuntimeTracer {
162+
fn function_name_for_test(&self, py: Python<'_>, code: &CodeObjectWrapper) -> PyResult<String> {
163+
self.function_name(py, code)
164+
}
165+
}
166+
160167
#[cfg(test)]
161168
mod tests {
162169
use super::*;
@@ -1072,52 +1079,42 @@ sensitive("s3cr3t")
10721079
#[test]
10731080
fn module_import_records_module_name() {
10741081
Python::with_gil(|py| {
1075-
ensure_test_module(py);
1076-
1077-
let mut tracer =
1078-
RuntimeTracer::new("runner.py", &[], TraceEventsFileFormat::Json, None, None);
1079-
10801082
let project = tempfile::tempdir().expect("project dir");
10811083
let pkg_root = project.path().join("lib");
10821084
let pkg_dir = pkg_root.join("my_pkg");
10831085
fs::create_dir_all(&pkg_dir).expect("create package dir");
1084-
fs::write(pkg_dir.join("__init__.py"), "\n").expect("write __init__");
1085-
fs::write(
1086-
pkg_dir.join("mod.py"),
1087-
"value = 1\nprint('imported module value', value)\n",
1088-
)
1089-
.expect("write module file");
1086+
let module_path = pkg_dir.join("mod.py");
1087+
fs::write(&module_path, "value = 1\n").expect("write module file");
10901088

1091-
let script = format!(
1092-
"import sys\nsys.path.insert(0, r\"{root}\")\nimport my_pkg.mod\n",
1093-
root = pkg_root.display()
1094-
);
1089+
let sys = py.import("sys").expect("import sys");
1090+
let sys_path = sys.getattr("path").expect("sys.path");
1091+
sys_path
1092+
.call_method1("insert", (0, pkg_root.to_string_lossy().as_ref()))
1093+
.expect("insert temp root");
10951094

1096-
{
1097-
let _guard = ScopedTracer::new(&mut tracer);
1098-
LAST_OUTCOME.with(|cell| cell.set(None));
1099-
let script_c = CString::new(script).expect("script contains nul byte");
1100-
py.run(script_c.as_c_str(), None, None)
1101-
.expect("execute module import");
1102-
}
1095+
let tracer =
1096+
RuntimeTracer::new("runner.py", &[], TraceEventsFileFormat::Json, None, None);
11031097

1104-
let function_records: Vec<String> = tracer
1105-
.writer
1106-
.events
1107-
.iter()
1108-
.filter_map(|event| {
1109-
if let TraceLowLevelEvent::Function(record) = event {
1110-
Some(record.name.clone())
1111-
} else {
1112-
None
1113-
}
1114-
})
1115-
.collect();
1098+
let builtins = py.import("builtins").expect("builtins");
1099+
let compile = builtins.getattr("compile").expect("compile builtin");
1100+
let code_obj: Bound<'_, PyCode> = compile
1101+
.call1((
1102+
"value = 1\n",
1103+
module_path.to_string_lossy().as_ref(),
1104+
"exec",
1105+
))
1106+
.expect("compile module code")
1107+
.downcast_into()
1108+
.expect("PyCode");
11161109

1117-
assert!(
1118-
function_records.iter().any(|name| name == "<my_pkg.mod>"),
1119-
"expected module function name to be rewritten: {function_records:?}"
1120-
);
1110+
let wrapper = CodeObjectWrapper::new(py, &code_obj);
1111+
let resolved = tracer
1112+
.function_name_for_test(py, &wrapper)
1113+
.expect("derive function name");
1114+
1115+
assert_eq!(resolved, "<my_pkg.mod>");
1116+
1117+
sys_path.call_method1("pop", (0,)).expect("pop temp root");
11211118
});
11221119
}
11231120

codetracer-python-recorder/tests/python/test_monitoring_events.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,32 @@ def arg_value(i: int) -> Dict[str, Any]:
196196
assert v1.get("text") == "x"
197197

198198

199+
def test_module_imports_record_package_names(tmp_path: Path) -> None:
200+
pkg_root = tmp_path / "lib"
201+
pkg_dir = pkg_root / "my_pkg"
202+
pkg_dir.mkdir(parents=True)
203+
(pkg_dir / "__init__.py").write_text("", encoding="utf-8")
204+
(pkg_dir / "mod.py").write_text("VALUE = 1\n", encoding="utf-8")
205+
206+
runner = tmp_path / "runner.py"
207+
runner.write_text(
208+
f"import sys\nsys.path.insert(0, r\"{pkg_root}\")\nimport my_pkg.mod\n",
209+
encoding="utf-8",
210+
)
211+
212+
out_dir = ensure_trace_dir(tmp_path)
213+
session = codetracer.start(out_dir, format=codetracer.TRACE_JSON, start_on_enter=runner)
214+
try:
215+
runpy.run_path(str(runner), run_name="__main__")
216+
finally:
217+
codetracer.flush()
218+
codetracer.stop()
219+
220+
parsed = _parse_trace(out_dir)
221+
names = [f["name"] for f in parsed.functions]
222+
assert any(name == "<my_pkg.mod>" for name in names), f"missing module name in {names}"
223+
224+
199225
def test_all_argument_kinds_recorded_on_py_start(tmp_path: Path) -> None:
200226
# Arrange: write a script with a function using all Python argument kinds
201227
code = (

design-docs/module-call-event-naming-implementation-plan.status.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@
2323

2424
### WS2 – Runtime Tracer Integration
2525
- **Scope recap:** Detect module-level code (`co_qualname == "<module>"`) and rename call events to `<module-name>` using the shared resolver; plumb filter-derived names to avoid duplicate work.
26-
- **Status:** _In Progress_
27-
- **Notes:** `RuntimeTracer` now owns a `ModuleIdentityCache`, rewrites module-level function names via shared hints, clears the cache between runs, and has a regression test (`module_import_records_module_name`) confirming `<my_pkg.mod>` call records. Remaining work: thread globals-based hints (if needed) and add higher-level integration tests / documentation (see WS3).
26+
- **Status:** _Completed_
27+
- **Notes:** `RuntimeTracer` now rewrites module-level call events via `ModuleIdentityCache`, clears the cache alongside `function_ids`, and exposes a test hook to verify naming logic. Added a unit test (`module_import_records_module_name`) and a Python integration test (`test_module_imports_record_package_names`) that traces a real import to confirm `<pkg.mod>` shows up in `trace.json`.
2828

2929
### WS3 – Testing, Tooling, and Docs
3030
- **Scope recap:** Add regression tests (Python + Rust) validating the new naming, update documentation/changelog, and refresh any snapshot expectations.
31-
- **Status:** _Not Started_
32-
- **Notes:** Tests will likely live in `tests/python/test_monitoring_events.py` and a dedicated Rust module; docs update TBD.
31+
- **Status:** _In Progress_
32+
- **Notes:** New Rust + Python tests cover module-name derivation; README now documents the `<pkg.module>` behaviour. Remaining work: update changelog/other docs if required once feature is finalized.
3333

3434
## Next Checkpoints
3535
1. Implement shared resolver scaffolding (WS1).

0 commit comments

Comments
 (0)