|
| 1 | +# Listing 003 |
| 2 | + |
| 3 | +This listing examines the build pipeline and initial Rust implementation of the native tracer. We review the `extconf.rb` script, `build.rs`, the crate's `Cargo.toml`, and the opening portions of `src/lib.rs` that set up symbol lookups, recorder state, and early helper functions. |
| 4 | + |
| 5 | +**Invoke mkmf and rb_sys to generate a Makefile for the Rust extension.** |
| 6 | +```ruby |
| 7 | +require 'mkmf' |
| 8 | +require 'rb_sys/mkmf' |
| 9 | + |
| 10 | +create_rust_makefile('codetracer_ruby_recorder') |
| 11 | +``` |
| 12 | + |
| 13 | +**Activate rb_sys environment variables during Cargo build.** |
| 14 | +```rust |
| 15 | +fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 16 | + rb_sys_env::activate()?; |
| 17 | + Ok(()) |
| 18 | +} |
| 19 | +``` |
| 20 | + |
| 21 | +**Define package metadata, library type, dependencies, and build helpers.** |
| 22 | +```toml |
| 23 | +[package] |
| 24 | +name = "codetracer_ruby_recorder" |
| 25 | +description = "Native Ruby module for generating CodeTracer trace files" |
| 26 | +version = "0.1.0" |
| 27 | +edition = "2021" |
| 28 | +build = "build.rs" |
| 29 | + |
| 30 | +[lib] |
| 31 | +crate-type = ["cdylib"] |
| 32 | + |
| 33 | +[dependencies] |
| 34 | +rb-sys = "0.9" |
| 35 | +runtime_tracing = "0.14.0" |
| 36 | + |
| 37 | +[build-dependencies] |
| 38 | +rb-sys-env = "0.2" |
| 39 | + |
| 40 | +[profile.release] |
| 41 | +codegen-units = 1 |
| 42 | +lto = "thin" |
| 43 | +opt-level = 3 |
| 44 | +``` |
| 45 | + |
| 46 | +**Allow missing safety docs and import standard, Ruby, and tracing crates.** |
| 47 | +```rust |
| 48 | +#![allow(clippy::missing_safety_doc)] |
| 49 | + |
| 50 | +use std::{ |
| 51 | + collections::HashMap, |
| 52 | + ffi::CStr, |
| 53 | + mem::transmute, |
| 54 | + os::raw::{c_char, c_int, c_void}, |
| 55 | + path::Path, |
| 56 | + ptr, |
| 57 | +}; |
| 58 | + |
| 59 | +use rb_sys::{ |
| 60 | + rb_cObject, rb_define_alloc_func, rb_define_class, rb_define_method, rb_eIOError, |
| 61 | + rb_event_flag_t, rb_funcall, rb_id2name, rb_id2sym, rb_intern, rb_num2long, rb_obj_classname, |
| 62 | + rb_raise, rb_sym2id, ID, RUBY_EVENT_CALL, RUBY_EVENT_LINE, RUBY_EVENT_RAISE, RUBY_EVENT_RETURN, |
| 63 | + VALUE, |
| 64 | +}; |
| 65 | +use rb_sys::{ |
| 66 | + rb_protect, NIL_P, RARRAY_CONST_PTR, RARRAY_LEN, RB_FLOAT_TYPE_P, RB_INTEGER_TYPE_P, |
| 67 | + RB_SYMBOL_P, RB_TYPE_P, RSTRING_LEN, RSTRING_PTR, |
| 68 | +}; |
| 69 | +use rb_sys::{Qfalse, Qnil, Qtrue}; |
| 70 | +use runtime_tracing::{ |
| 71 | + create_trace_writer, CallRecord, EventLogKind, FieldTypeRecord, FullValueRecord, Line, |
| 72 | + TraceEventsFileFormat, TraceLowLevelEvent, TraceWriter, TypeKind, TypeRecord, TypeSpecificInfo, |
| 73 | + ValueRecord, |
| 74 | +}; |
| 75 | +``` |
| 76 | + |
| 77 | +**Declare event hook type and import flag enum and additional binding functions.** |
| 78 | +```rust |
| 79 | +// Event hook function type from Ruby debug.h |
| 80 | +type rb_event_hook_func_t = Option<unsafe extern "C" fn(rb_event_flag_t, VALUE, VALUE, ID, VALUE)>; |
| 81 | + |
| 82 | +// Use event hook flags enum from rb_sys |
| 83 | +use rb_sys::rb_event_hook_flag_t; |
| 84 | + |
| 85 | +// Types from rb_sys bindings |
| 86 | +use rb_sys::{ |
| 87 | + rb_add_event_hook2, rb_cRange, rb_cRegexp, rb_cStruct, rb_cTime, rb_check_typeddata, |
| 88 | + rb_const_defined, rb_const_get, rb_data_type_struct__bindgen_ty_1, rb_data_type_t, |
| 89 | + rb_data_typed_object_wrap, rb_method_boundp, rb_num2dbl, rb_obj_is_kind_of, |
| 90 | + rb_remove_event_hook_with_data, rb_trace_arg_t, rb_tracearg_binding, rb_tracearg_callee_id, |
| 91 | + rb_tracearg_event_flag, rb_tracearg_lineno, rb_tracearg_path, rb_tracearg_raised_exception, |
| 92 | + rb_tracearg_return_value, rb_tracearg_self, |
| 93 | +}; |
| 94 | +``` |
| 95 | + |
| 96 | +**Collect frequently used Ruby method identifiers for efficient lookup.** |
| 97 | +```rust |
| 98 | +struct InternedSymbols { |
| 99 | + to_s: ID, |
| 100 | + local_variables: ID, |
| 101 | + local_variable_get: ID, |
| 102 | + instance_method: ID, |
| 103 | + parameters: ID, |
| 104 | + class: ID, |
| 105 | + to_a: ID, |
| 106 | + begin: ID, |
| 107 | + end: ID, |
| 108 | + to_i: ID, |
| 109 | + nsec: ID, |
| 110 | + source: ID, |
| 111 | + options: ID, |
| 112 | + members: ID, |
| 113 | + values: ID, |
| 114 | + to_h: ID, |
| 115 | + instance_variables: ID, |
| 116 | + instance_variable_get: ID, |
| 117 | + set_const: ID, |
| 118 | + open_struct_const: ID, |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +**Construct the symbol table by interning method names.** |
| 123 | +```rust |
| 124 | +impl InternedSymbols { |
| 125 | + unsafe fn new() -> InternedSymbols { |
| 126 | + InternedSymbols { |
| 127 | + to_s: rb_intern!("to_s"), |
| 128 | + local_variables: rb_intern!("local_variables"), |
| 129 | + local_variable_get: rb_intern!("local_variable_get"), |
| 130 | + instance_method: rb_intern!("instance_method"), |
| 131 | + parameters: rb_intern!("parameters"), |
| 132 | + class: rb_intern!("class"), |
| 133 | + to_a: rb_intern!("to_a"), |
| 134 | + begin: rb_intern!("begin"), |
| 135 | + end: rb_intern!("end"), |
| 136 | + to_i: rb_intern!("to_i"), |
| 137 | + nsec: rb_intern!("nsec"), |
| 138 | + source: rb_intern!("source"), |
| 139 | + options: rb_intern!("options"), |
| 140 | + members: rb_intern!("members"), |
| 141 | + values: rb_intern!("values"), |
| 142 | + to_h: rb_intern!("to_h"), |
| 143 | + instance_variables: rb_intern!("instance_variables"), |
| 144 | + instance_variable_get: rb_intern!("instance_variable_get"), |
| 145 | + set_const: rb_intern!("Set"), |
| 146 | + open_struct_const: rb_intern!("OpenStruct"), |
| 147 | + } |
| 148 | + } |
| 149 | +} |
| 150 | +``` |
| 151 | + |
| 152 | +**Define the recorder state with tracing backend, flags, and cached type IDs.** |
| 153 | +```rust |
| 154 | +struct Recorder { |
| 155 | + tracer: Box<dyn TraceWriter>, |
| 156 | + active: bool, |
| 157 | + id: InternedSymbols, |
| 158 | + set_class: VALUE, |
| 159 | + open_struct_class: VALUE, |
| 160 | + struct_type_versions: HashMap<String, usize>, |
| 161 | + int_type_id: runtime_tracing::TypeId, |
| 162 | + float_type_id: runtime_tracing::TypeId, |
| 163 | + bool_type_id: runtime_tracing::TypeId, |
| 164 | + string_type_id: runtime_tracing::TypeId, |
| 165 | + symbol_type_id: runtime_tracing::TypeId, |
| 166 | + error_type_id: runtime_tracing::TypeId, |
| 167 | +} |
| 168 | +``` |
| 169 | + |
| 170 | +**Skip instrumentation for internal or library paths to reduce noise.** |
| 171 | +```rust |
| 172 | +fn should_ignore_path(path: &str) -> bool { |
| 173 | + const PATTERNS: [&str; 5] = [ |
| 174 | + "codetracer_ruby_recorder.rb", |
| 175 | + "lib/ruby", |
| 176 | + "recorder.rb", |
| 177 | + "codetracer_pure_ruby_recorder.rb", |
| 178 | + "gems/", |
| 179 | + ]; |
| 180 | + if path.starts_with("<internal:") { |
| 181 | + return true; |
| 182 | + } |
| 183 | + PATTERNS.iter().any(|p| path.contains(p)) |
| 184 | +} |
| 185 | +``` |
| 186 | + |
| 187 | +**Retrieve the type ID embedded within a `ValueRecord` variant.** |
| 188 | +```rust |
| 189 | +fn value_type_id(val: &ValueRecord) -> runtime_tracing::TypeId { |
| 190 | + use ValueRecord::*; |
| 191 | + match val { |
| 192 | + Int { type_id, .. } |
| 193 | + | Float { type_id, .. } |
| 194 | + | Bool { type_id, .. } |
| 195 | + | String { type_id, .. } |
| 196 | + | Sequence { type_id, .. } |
| 197 | + | Tuple { type_id, .. } |
| 198 | + | Struct { type_id, .. } |
| 199 | + | Variant { type_id, .. } |
| 200 | + | Reference { type_id, .. } |
| 201 | + | Raw { type_id, .. } |
| 202 | + | Error { type_id, .. } |
| 203 | + | BigInt { type_id, .. } |
| 204 | + | None { type_id } => *type_id, |
| 205 | + Cell { .. } => runtime_tracing::NONE_TYPE_ID, |
| 206 | + } |
| 207 | +} |
| 208 | +``` |
0 commit comments