11#![ allow( clippy:: missing_safety_doc) ]
22
3- use std:: { ffi:: CStr , mem:: transmute, os:: raw:: c_char} ;
3+ use std:: {
4+ ffi:: { CStr , CString } ,
5+ mem:: transmute,
6+ os:: raw:: { c_char, c_int, c_void} ,
7+ path:: { Path , PathBuf } ,
8+ ptr,
9+ sync:: { Mutex } ,
10+ } ;
411
512use rb_sys:: {
6- // frequently used public items
7- rb_add_event_hook2, rb_event_flag_t,
13+ rb_add_event_hook2, rb_remove_event_hook_with_data, rb_define_class,
14+ rb_define_alloc_func, rb_define_method, rb_obj_alloc,
15+ rb_ivar_set, rb_ivar_get, rb_intern, rb_ull2inum, rb_num2ull,
816 rb_event_hook_flag_t:: RUBY_EVENT_HOOK_FLAG_RAW_ARG ,
9- ID , VALUE , RUBY_EVENT_LINE ,
10-
11- // the raw-trace-API symbols live in the generated `bindings` module
12- bindings:: {
13- rb_trace_arg_t, // struct rb_trace_arg
14- rb_tracearg_event_flag, // event kind helpers
15- rb_tracearg_lineno,
16- rb_tracearg_path,
17- } ,
17+ rb_event_flag_t, rb_trace_arg_t,
18+ rb_tracearg_event_flag, rb_tracearg_lineno, rb_tracearg_path,
19+ rb_cObject, VALUE , ID , RUBY_EVENT_LINE ,
20+ RSTRING_PTR , RSTRING_LEN ,
1821} ;
22+ use runtime_tracing:: { Tracer , Line } ;
23+
24+ struct Recorder {
25+ tracer : Mutex < Tracer > ,
26+ active : bool ,
27+ }
28+
29+ static mut PTR_IVAR : ID = 0 ;
30+
31+ unsafe fn get_recorder ( obj : VALUE ) -> * mut Recorder {
32+ let val = rb_ivar_get ( obj, PTR_IVAR ) ;
33+ rb_num2ull ( val) as * mut Recorder
34+ }
35+
36+ unsafe extern "C" fn ruby_recorder_alloc ( klass : VALUE ) -> VALUE {
37+ let obj = rb_obj_alloc ( klass) ;
38+ let recorder = Box :: new ( Recorder { tracer : Mutex :: new ( Tracer :: new ( "ruby" , & vec ! [ ] ) ) , active : false } ) ;
39+ rb_ivar_set ( obj, PTR_IVAR , rb_ull2inum ( Box :: into_raw ( recorder) as u64 ) ) ;
40+ obj
41+ }
42+
43+ unsafe extern "C" fn ruby_recorder_initialize ( _self : VALUE ) -> VALUE {
44+ // nothing special for now
45+ rb_sys:: Qnil . into ( )
46+ }
47+
48+ unsafe extern "C" fn enable_tracing ( self_val : VALUE ) -> VALUE {
49+ let recorder = & mut * get_recorder ( self_val) ;
50+ if !recorder. active {
51+ let raw_cb: unsafe extern "C" fn ( VALUE , * mut rb_trace_arg_t ) = event_hook_raw;
52+ let cb: unsafe extern "C" fn ( rb_event_flag_t , VALUE , VALUE , ID , VALUE ) = transmute ( raw_cb) ;
53+ rb_add_event_hook2 ( Some ( cb) , RUBY_EVENT_LINE , self_val, RUBY_EVENT_HOOK_FLAG_RAW_ARG ) ;
54+ recorder. active = true ;
55+ }
56+ rb_sys:: Qnil . into ( )
57+ }
58+
59+ unsafe extern "C" fn disable_tracing ( self_val : VALUE ) -> VALUE {
60+ let recorder = & mut * get_recorder ( self_val) ;
61+ if recorder. active {
62+ let raw_cb: unsafe extern "C" fn ( VALUE , * mut rb_trace_arg_t ) = event_hook_raw;
63+ let cb: unsafe extern "C" fn ( rb_event_flag_t , VALUE , VALUE , ID , VALUE ) = transmute ( raw_cb) ;
64+ rb_remove_event_hook_with_data ( Some ( cb) , self_val) ;
65+ recorder. active = false ;
66+ }
67+ rb_sys:: Qnil . into ( )
68+ }
69+
70+ fn flush_to_dir ( tracer : & Tracer , dir : & Path ) {
71+ let _ = std:: fs:: create_dir_all ( dir) ;
72+ let events = dir. join ( "trace.json" ) ;
73+ let metadata = dir. join ( "trace_metadata.json" ) ;
74+ let paths = dir. join ( "trace_paths.json" ) ;
75+ let _ = tracer. store_trace_events ( & events) ;
76+ let _ = tracer. store_trace_metadata ( & metadata) ;
77+ let _ = tracer. store_trace_paths ( & paths) ;
78+ }
79+
80+ unsafe extern "C" fn flush_trace ( self_val : VALUE , out_dir : VALUE ) -> VALUE {
81+ let recorder_ptr = get_recorder ( self_val) ;
82+ let recorder = & mut * recorder_ptr;
83+ let ptr = RSTRING_PTR ( out_dir) as * const u8 ;
84+ let len = RSTRING_LEN ( out_dir) as usize ;
85+ let slice = std:: slice:: from_raw_parts ( ptr, len) ;
86+ if let Ok ( path_str) = std:: str:: from_utf8 ( slice) {
87+ if let Ok ( t) = recorder. tracer . lock ( ) {
88+ flush_to_dir ( & t, Path :: new ( path_str) ) ;
89+ }
90+ }
91+ drop ( Box :: from_raw ( recorder_ptr) ) ;
92+ rb_ivar_set ( self_val, PTR_IVAR , rb_ull2inum ( 0 ) ) ;
93+ rb_sys:: Qnil . into ( )
94+ }
1995
2096/// Raw-argument callback (Ruby will call it when we set
2197/// `RUBY_EVENT_HOOK_FLAG_RAW_ARG`).
@@ -24,41 +100,46 @@ use rb_sys::{
24100/// ```c
25101/// void (*)(VALUE data, rb_trace_arg_t *arg);
26102/// ```
27- unsafe extern "C" fn event_hook_raw ( _data : VALUE , arg : * mut rb_trace_arg_t ) {
103+ unsafe extern "C" fn event_hook_raw ( data : VALUE , arg : * mut rb_trace_arg_t ) {
28104 if arg. is_null ( ) {
29105 return ;
30106 }
31107
108+ let recorder = & mut * get_recorder ( data) ;
109+ if !recorder. active {
110+ return ;
111+ }
112+
32113 let ev: rb_event_flag_t = rb_tracearg_event_flag ( arg) ;
33114 if ( ev & RUBY_EVENT_LINE ) == 0 {
34115 return ;
35116 }
36117
37118 let path_ptr = rb_tracearg_path ( arg) as * const c_char ;
38- let line = rb_tracearg_lineno ( arg) as u32 ;
119+ let line = rb_tracearg_lineno ( arg) as i64 ;
39120
40121 if !path_ptr. is_null ( ) {
41122 if let Ok ( path) = CStr :: from_ptr ( path_ptr) . to_str ( ) {
42- println ! ( "Path: {path}, Line: {line}" ) ;
123+ if let Ok ( mut t) = recorder. tracer . lock ( ) {
124+ t. register_step ( Path :: new ( path) , Line ( line) ) ;
125+ }
43126 }
44127 }
45128}
46129
47130#[ no_mangle]
48131pub extern "C" fn Init_codetracer_ruby_recorder ( ) {
49132 unsafe {
50- // rb_add_event_hook2’s first parameter is a function pointer with the
51- // classic five-argument signature. We cast our raw callback to that
52- // type via an intermediate variable so the sizes match.
53- let raw_cb: unsafe extern "C" fn ( VALUE , * mut rb_trace_arg_t ) = event_hook_raw;
54- let cb: unsafe extern "C" fn ( rb_event_flag_t , VALUE , VALUE , ID , VALUE ) =
55- transmute ( raw_cb) ;
56-
57- rb_add_event_hook2 (
58- Some ( cb) , // callback (now cast)
59- RUBY_EVENT_LINE , // which events
60- 0 , // user data
61- RUBY_EVENT_HOOK_FLAG_RAW_ARG ,
62- ) ;
133+ PTR_IVAR = rb_intern ( b"@ptr\0 " . as_ptr ( ) as * const c_char ) ;
134+ let class = rb_define_class ( b"RubyRecorder\0 " . as_ptr ( ) as * const c_char , rb_cObject) ;
135+ rb_define_alloc_func ( class, Some ( ruby_recorder_alloc) ) ;
136+ let init_cb: unsafe extern "C" fn ( VALUE ) -> VALUE = ruby_recorder_initialize;
137+ let enable_cb: unsafe extern "C" fn ( VALUE ) -> VALUE = enable_tracing;
138+ let disable_cb: unsafe extern "C" fn ( VALUE ) -> VALUE = disable_tracing;
139+ let flush_cb: unsafe extern "C" fn ( VALUE , VALUE ) -> VALUE = flush_trace;
140+ rb_define_method ( class, b"initialize\0 " . as_ptr ( ) as * const c_char , Some ( transmute ( init_cb) ) , 0 ) ;
141+ rb_define_method ( class, b"enable_tracing\0 " . as_ptr ( ) as * const c_char , Some ( transmute ( enable_cb) ) , 0 ) ;
142+ rb_define_method ( class, b"disable_tracing\0 " . as_ptr ( ) as * const c_char , Some ( transmute ( disable_cb) ) , 0 ) ;
143+ rb_define_method ( class, b"flush_trace\0 " . as_ptr ( ) as * const c_char , Some ( transmute ( flush_cb) ) , 1 ) ;
63144 }
64145}
0 commit comments