Skip to content

Commit 33c06d4

Browse files
committed
docs: add listing-006 covering parameter helpers
- document helper functions for extracting and registering parameters in the Rust tracer - explain initialization, flushing, and custom event APIs
1 parent c1688cd commit 33c06d4

File tree

1 file changed

+275
-0
lines changed

1 file changed

+275
-0
lines changed

listing-006.md

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
# Listing 006
2+
3+
This listing continues in `gems/codetracer-ruby-recorder/ext/native_tracer/src/lib.rs`, covering helper routines that extract method parameters, register them with the tracer, and expose C-callable APIs for initialization, flushing, and emitting custom events.
4+
5+
**Prepare to collect parameters by defining the function signature and required arguments.**
6+
```rust
7+
unsafe fn collect_parameter_values(
8+
recorder: &mut Recorder,
9+
binding: VALUE,
10+
defined_class: VALUE,
11+
mid: ID,
12+
) -> Vec<(String, ValueRecord)> {
13+
```
14+
15+
**Convert the method ID to a Ruby symbol and bail out if the method isn't found.**
16+
```rust
17+
let method_sym = rb_id2sym(mid);
18+
if rb_method_boundp(defined_class, mid, 0) == 0 {
19+
return Vec::new();
20+
}
21+
```
22+
23+
**Fetch the `Method` object and query its parameter metadata, ensuring an array is returned.**
24+
```rust
25+
let method_obj = rb_funcall(defined_class, recorder.id.instance_method, 1, method_sym);
26+
let params_ary = rb_funcall(method_obj, recorder.id.parameters, 0);
27+
if !RB_TYPE_P(params_ary, rb_sys::ruby_value_type::RUBY_T_ARRAY) {
28+
return Vec::new();
29+
}
30+
```
31+
32+
**Determine how many parameters exist and prime a results vector of matching capacity.**
33+
```rust
34+
let params_len = RARRAY_LEN(params_ary) as usize;
35+
let params_ptr = RARRAY_CONST_PTR(params_ary);
36+
let mut result = Vec::with_capacity(params_len);
37+
```
38+
39+
**Iterate through each parameter description, skipping malformed entries.**
40+
```rust
41+
for i in 0..params_len {
42+
let pair = *params_ptr.add(i);
43+
if !RB_TYPE_P(pair, rb_sys::ruby_value_type::RUBY_T_ARRAY) || RARRAY_LEN(pair) < 2 {
44+
continue;
45+
}
46+
let pair_ptr = RARRAY_CONST_PTR(pair);
47+
```
48+
49+
**Extract the parameter's name symbol, ignoring `nil` placeholders.**
50+
```rust
51+
let name_sym = *pair_ptr.add(1);
52+
if NIL_P(name_sym) {
53+
continue;
54+
}
55+
```
56+
57+
**Convert the symbol to a C string; if conversion fails, skip the parameter.**
58+
```rust
59+
let name_id = rb_sym2id(name_sym);
60+
let name_c = rb_id2name(name_id);
61+
if name_c.is_null() {
62+
continue;
63+
}
64+
let name = CStr::from_ptr(name_c).to_str().unwrap_or("").to_string();
65+
```
66+
67+
**Read the argument's value from the binding and turn it into a `ValueRecord`.**
68+
```rust
69+
let value = rb_funcall(binding, recorder.id.local_variable_get, 1, name_sym);
70+
let val_rec = to_value(recorder, value, 10);
71+
result.push((name, val_rec));
72+
}
73+
result
74+
}
75+
```
76+
77+
**Define `register_parameter_values` to persist parameters and their values with the tracer.**
78+
```rust
79+
unsafe fn register_parameter_values(
80+
recorder: &mut Recorder,
81+
params: Vec<(String, ValueRecord)>,
82+
) -> Vec<FullValueRecord> {
83+
```
84+
85+
**Allocate space for the returned records and walk through each `(name, value)` pair.**
86+
```rust
87+
let mut result = Vec::with_capacity(params.len());
88+
for (name, val_rec) in params {
89+
```
90+
91+
**Record the variable and ensure it has a stable ID in the trace.**
92+
```rust
93+
TraceWriter::register_variable_with_full_value(
94+
&mut *recorder.tracer,
95+
&name,
96+
val_rec.clone(),
97+
);
98+
let var_id = TraceWriter::ensure_variable_id(&mut *recorder.tracer, &name);
99+
```
100+
101+
**Store the final `FullValueRecord` in the results vector and finish.**
102+
```rust
103+
result.push(FullValueRecord {
104+
variable_id: var_id,
105+
value: val_rec,
106+
});
107+
}
108+
result
109+
}
110+
```
111+
112+
**`record_event` logs an arbitrary string event at a given file path and line.**
113+
```rust
114+
unsafe fn record_event(tracer: &mut dyn TraceWriter, path: &str, line: i64, content: String) {
115+
TraceWriter::register_step(tracer, Path::new(path), Line(line));
116+
TraceWriter::register_special_event(tracer, EventLogKind::Write, &content)
117+
}
118+
```
119+
120+
**Begin the C-facing `initialize` function, pulling pointers from Ruby strings.**
121+
```rust
122+
unsafe extern "C" fn initialize(self_val: VALUE, out_dir: VALUE, format: VALUE) -> VALUE {
123+
let recorder_ptr = get_recorder(self_val);
124+
let recorder = &mut *recorder_ptr;
125+
let ptr = RSTRING_PTR(out_dir) as *const u8;
126+
let len = RSTRING_LEN(out_dir) as usize;
127+
let slice = std::slice::from_raw_parts(ptr, len);
128+
```
129+
130+
**Determine the output file format, defaulting to JSON when no symbol is supplied.**
131+
```rust
132+
let fmt = if !NIL_P(format) && RB_SYMBOL_P(format) {
133+
let id = rb_sym2id(format);
134+
match CStr::from_ptr(rb_id2name(id)).to_str().unwrap_or("") {
135+
"binaryv0" => runtime_tracing::TraceEventsFileFormat::BinaryV0,
136+
"binary" | "bin" => runtime_tracing::TraceEventsFileFormat::Binary,
137+
"json" => runtime_tracing::TraceEventsFileFormat::Json,
138+
_ => rb_raise(rb_eIOError, b"Unknown format\0".as_ptr() as *const c_char),
139+
}
140+
} else {
141+
runtime_tracing::TraceEventsFileFormat::Json
142+
};
143+
```
144+
145+
**Attempt to start tracing, pre-registering common Ruby types on success.**
146+
```rust
147+
match std::str::from_utf8(slice) {
148+
Ok(path_str) => {
149+
match begin_trace(Path::new(path_str), fmt) {
150+
Ok(t) => {
151+
recorder.tracer = t;
152+
// pre-register common types to match the pure Ruby tracer
153+
recorder.int_type_id = TraceWriter::ensure_type_id(
154+
&mut *recorder.tracer,
155+
TypeKind::Int,
156+
"Integer",
157+
);
158+
recorder.string_type_id = TraceWriter::ensure_type_id(
159+
&mut *recorder.tracer,
160+
TypeKind::String,
161+
"String",
162+
);
163+
recorder.bool_type_id =
164+
TraceWriter::ensure_type_id(&mut *recorder.tracer, TypeKind::Bool, "Bool");
165+
recorder.float_type_id = runtime_tracing::NONE_TYPE_ID;
166+
recorder.symbol_type_id = TraceWriter::ensure_type_id(
167+
&mut *recorder.tracer,
168+
TypeKind::String,
169+
"Symbol",
170+
);
171+
recorder.error_type_id = TraceWriter::ensure_type_id(
172+
&mut *recorder.tracer,
173+
TypeKind::Error,
174+
"No type",
175+
);
176+
let path = Path::new("");
177+
let func_id = TraceWriter::ensure_function_id(
178+
&mut *recorder.tracer,
179+
"<top-level>",
180+
path,
181+
Line(1),
182+
);
183+
TraceWriter::add_event(
184+
&mut *recorder.tracer,
185+
TraceLowLevelEvent::Call(CallRecord {
186+
function_id: func_id,
187+
args: vec![],
188+
}),
189+
);
190+
}
191+
Err(e) => {
192+
let msg = std::ffi::CString::new(e.to_string())
193+
.unwrap_or_else(|_| std::ffi::CString::new("unknown error").unwrap());
194+
rb_raise(
195+
rb_eIOError,
196+
b"Failed to flush trace: %s\0".as_ptr() as *const c_char,
197+
msg.as_ptr(),
198+
);
199+
}
200+
}
201+
}
202+
Err(e) => {
203+
let msg = std::ffi::CString::new(e.to_string())
204+
.unwrap_or_else(|_| std::ffi::CString::new("invalid utf8").unwrap());
205+
rb_raise(
206+
rb_eIOError,
207+
b"Invalid UTF-8 in path: %s\0".as_ptr() as *const c_char,
208+
msg.as_ptr(),
209+
)
210+
}
211+
}
212+
```
213+
214+
**Finalize initialization by returning Ruby `nil`.**
215+
```rust
216+
Qnil.into()
217+
}
218+
```
219+
220+
**Expose a `flush_trace` function for Ruby to write pending events to disk.**
221+
```rust
222+
unsafe extern "C" fn flush_trace(self_val: VALUE) -> VALUE {
223+
let recorder_ptr = get_recorder(self_val);
224+
let recorder = &mut *recorder_ptr;
225+
```
226+
227+
**Attempt the flush and surface I/O errors back to Ruby.**
228+
```rust
229+
if let Err(e) = flush_to_dir(&mut *recorder.tracer) {
230+
let msg = std::ffi::CString::new(e.to_string())
231+
.unwrap_or_else(|_| std::ffi::CString::new("unknown error").unwrap());
232+
rb_raise(
233+
rb_eIOError,
234+
b"Failed to flush trace: %s\0".as_ptr() as *const c_char,
235+
msg.as_ptr(),
236+
);
237+
}
238+
```
239+
240+
**Return `nil` to signal success.**
241+
```rust
242+
Qnil.into()
243+
}
244+
```
245+
246+
**`record_event_api` lets Ruby code log custom events with a path and line number.**
247+
```rust
248+
unsafe extern "C" fn record_event_api(
249+
self_val: VALUE,
250+
path: VALUE,
251+
line: VALUE,
252+
content: VALUE,
253+
) -> VALUE {
254+
```
255+
256+
**Retrieve the recorder and decode the optional path string from Ruby.**
257+
```rust
258+
let recorder = &mut *get_recorder(self_val);
259+
let path_slice = if NIL_P(path) {
260+
""
261+
} else {
262+
let ptr = RSTRING_PTR(path);
263+
let len = RSTRING_LEN(path) as usize;
264+
std::str::from_utf8(std::slice::from_raw_parts(ptr as *const u8, len)).unwrap_or("")
265+
};
266+
```
267+
268+
**Convert the line number and content, then dispatch the event.**
269+
```rust
270+
let line_num = rb_num2long(line) as i64;
271+
let content_str = value_to_string(recorder, content);
272+
record_event(&mut *recorder.tracer, path_slice, line_num, content_str);
273+
Qnil.into()
274+
}
275+
```

0 commit comments

Comments
 (0)