pf2 0.8.0 → 1.0.0.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +20 -4
  4. data/Rakefile +1 -0
  5. data/doc/development.md +17 -0
  6. data/examples/mandelbrot.rb +69 -0
  7. data/examples/mandelbrot_ractor.rb +77 -0
  8. data/ext/pf2/backtrace_state.c +10 -0
  9. data/ext/pf2/backtrace_state.h +10 -0
  10. data/ext/pf2/configuration.c +90 -0
  11. data/ext/pf2/configuration.h +23 -0
  12. data/ext/pf2/debug.h +12 -0
  13. data/ext/pf2/extconf.rb +23 -6
  14. data/ext/pf2/pf2.c +17 -0
  15. data/ext/pf2/pf2.h +8 -0
  16. data/ext/pf2/ringbuffer.c +74 -0
  17. data/ext/pf2/ringbuffer.h +24 -0
  18. data/ext/pf2/sample.c +76 -0
  19. data/ext/pf2/sample.h +26 -0
  20. data/ext/pf2/serializer.c +377 -0
  21. data/ext/pf2/serializer.h +58 -0
  22. data/ext/pf2/session.c +394 -0
  23. data/ext/pf2/session.h +56 -0
  24. data/lib/pf2/cli.rb +25 -11
  25. data/lib/pf2/reporter/annotate.rb +101 -0
  26. data/lib/pf2/reporter/firefox_profiler_ser2.rb +17 -13
  27. data/lib/pf2/reporter/stack_weaver.rb +8 -0
  28. data/lib/pf2/reporter.rb +1 -1
  29. data/lib/pf2/version.rb +1 -1
  30. data/lib/pf2.rb +1 -1
  31. data/vendor/libbacktrace/.gitignore +5 -0
  32. data/{crates/backtrace-sys2/src → vendor}/libbacktrace/README.md +1 -1
  33. data/{crates/backtrace-sys2/src → vendor}/libbacktrace/configure +23 -0
  34. data/{crates/backtrace-sys2/src → vendor}/libbacktrace/configure.ac +10 -0
  35. data/{crates/backtrace-sys2/src → vendor}/libbacktrace/dwarf.c +199 -15
  36. data/{crates/backtrace-sys2/src → vendor}/libbacktrace/elf.c +20 -14
  37. data/{crates/backtrace-sys2/src → vendor}/libbacktrace/fileline.c +2 -2
  38. data/{crates/backtrace-sys2/src → vendor}/libbacktrace/macho.c +2 -2
  39. data/{crates/backtrace-sys2/src → vendor}/libbacktrace/pecoff.c +2 -2
  40. metadata +111 -111
  41. data/Cargo.lock +0 -630
  42. data/Cargo.toml +0 -3
  43. data/crates/backtrace-sys2/.gitignore +0 -1
  44. data/crates/backtrace-sys2/Cargo.toml +0 -9
  45. data/crates/backtrace-sys2/build.rs +0 -45
  46. data/crates/backtrace-sys2/src/lib.rs +0 -5
  47. data/crates/backtrace-sys2/src/libbacktrace/.gitignore +0 -15
  48. data/ext/pf2/Cargo.toml +0 -25
  49. data/ext/pf2/build.rs +0 -10
  50. data/ext/pf2/src/backtrace.rs +0 -127
  51. data/ext/pf2/src/lib.rs +0 -22
  52. data/ext/pf2/src/profile.rs +0 -69
  53. data/ext/pf2/src/profile_serializer.rs +0 -241
  54. data/ext/pf2/src/ringbuffer.rs +0 -150
  55. data/ext/pf2/src/ruby_c_api_helper.c +0 -6
  56. data/ext/pf2/src/ruby_init.rs +0 -40
  57. data/ext/pf2/src/ruby_internal_apis.rs +0 -77
  58. data/ext/pf2/src/sample.rs +0 -67
  59. data/ext/pf2/src/scheduler.rs +0 -10
  60. data/ext/pf2/src/serialization/profile.rs +0 -48
  61. data/ext/pf2/src/serialization/serializer.rs +0 -329
  62. data/ext/pf2/src/serialization.rs +0 -2
  63. data/ext/pf2/src/session/configuration.rs +0 -114
  64. data/ext/pf2/src/session/new_thread_watcher.rs +0 -80
  65. data/ext/pf2/src/session/ruby_object.rs +0 -90
  66. data/ext/pf2/src/session.rs +0 -248
  67. data/ext/pf2/src/siginfo_t.c +0 -5
  68. data/ext/pf2/src/signal_scheduler.rs +0 -201
  69. data/ext/pf2/src/signal_scheduler_unsupported_platform.rs +0 -39
  70. data/ext/pf2/src/timer_thread_scheduler.rs +0 -179
  71. data/ext/pf2/src/util.rs +0 -31
  72. data/lib/pf2/reporter/firefox_profiler.rb +0 -397
  73. data/rust-toolchain.toml +0 -2
  74. data/rustfmt.toml +0 -1
  75. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/Isaac.Newton-Opticks.txt +0 -0
  76. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/LICENSE +0 -0
  77. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/Makefile.am +0 -0
  78. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/Makefile.in +0 -0
  79. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/aclocal.m4 +0 -0
  80. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/alloc.c +0 -0
  81. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/allocfail.c +0 -0
  82. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/allocfail.sh +0 -0
  83. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/atomic.c +0 -0
  84. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/backtrace-supported.h.in +0 -0
  85. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/backtrace.c +0 -0
  86. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/backtrace.h +0 -0
  87. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/btest.c +0 -0
  88. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/compile +0 -0
  89. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/enable.m4 +0 -0
  90. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/lead-dot.m4 +0 -0
  91. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/libtool.m4 +0 -0
  92. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/ltoptions.m4 +0 -0
  93. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/ltsugar.m4 +0 -0
  94. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/ltversion.m4 +0 -0
  95. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/lt~obsolete.m4 +0 -0
  96. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/multi.m4 +0 -0
  97. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/override.m4 +0 -0
  98. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/unwind_ipinfo.m4 +0 -0
  99. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/warnings.m4 +0 -0
  100. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config.guess +0 -0
  101. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config.h.in +0 -0
  102. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config.sub +0 -0
  103. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/edtest.c +0 -0
  104. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/edtest2.c +0 -0
  105. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/filenames.h +0 -0
  106. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/filetype.awk +0 -0
  107. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/install-debuginfo-for-buildid.sh.in +0 -0
  108. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/install-sh +0 -0
  109. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/instrumented_alloc.c +0 -0
  110. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/internal.h +0 -0
  111. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/ltmain.sh +0 -0
  112. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/missing +0 -0
  113. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/mmap.c +0 -0
  114. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/mmapio.c +0 -0
  115. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/move-if-change +0 -0
  116. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/mtest.c +0 -0
  117. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/nounwind.c +0 -0
  118. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/posix.c +0 -0
  119. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/print.c +0 -0
  120. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/read.c +0 -0
  121. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/simple.c +0 -0
  122. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/sort.c +0 -0
  123. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/state.c +0 -0
  124. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/stest.c +0 -0
  125. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/test-driver +0 -0
  126. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/test_format.c +0 -0
  127. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/testlib.c +0 -0
  128. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/testlib.h +0 -0
  129. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/ttest.c +0 -0
  130. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/unittest.c +0 -0
  131. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/unknown.c +0 -0
  132. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/xcoff.c +0 -0
  133. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/xztest.c +0 -0
  134. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/zstdtest.c +0 -0
  135. /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/ztest.c +0 -0
@@ -1,40 +0,0 @@
1
- #![deny(unsafe_op_in_unsafe_fn)]
2
-
3
- use rb_sys::*;
4
-
5
- use crate::session::ruby_object::SessionRubyObject;
6
- use crate::util::*;
7
-
8
- #[allow(non_snake_case)]
9
- #[no_mangle]
10
- extern "C" fn Init_pf2() {
11
- #[cfg(feature = "debug")]
12
- {
13
- env_logger::builder().format_timestamp(None).format_module_path(false).init();
14
- }
15
-
16
- unsafe {
17
- let rb_mPf2: VALUE = rb_define_module(cstr!("Pf2"));
18
-
19
- let rb_mPf2_Session = rb_define_class_under(rb_mPf2, cstr!("Session"), rb_cObject);
20
- rb_define_alloc_func(rb_mPf2_Session, Some(SessionRubyObject::rb_alloc));
21
- rb_define_method(
22
- rb_mPf2_Session,
23
- cstr!("initialize"),
24
- Some(to_ruby_cfunc_with_args(SessionRubyObject::rb_initialize)),
25
- -1,
26
- );
27
- rb_define_method(
28
- rb_mPf2_Session,
29
- cstr!("start"),
30
- Some(to_ruby_cfunc_with_no_args(SessionRubyObject::rb_start)),
31
- 0,
32
- );
33
- rb_define_method(
34
- rb_mPf2_Session,
35
- cstr!("stop"),
36
- Some(to_ruby_cfunc_with_no_args(SessionRubyObject::rb_stop)),
37
- 0,
38
- );
39
- }
40
- }
@@ -1,77 +0,0 @@
1
- #![allow(non_snake_case)]
2
- #![allow(non_camel_case_types)]
3
-
4
- use libc::{clockid_t, pthread_t};
5
- use rb_sys::{rb_check_typeddata, rb_data_type_struct, RTypedData, VALUE};
6
- use std::ffi::{c_char, c_int, c_void};
7
- use std::mem::MaybeUninit;
8
-
9
- #[cfg(target_os = "linux")]
10
- use libc::pthread_getcpuclockid;
11
-
12
- #[cfg(not(target_os = "linux"))]
13
- pub unsafe fn pthread_getcpuclockid(thread: pthread_t, clk_id: *mut clockid_t) -> c_int {
14
- unimplemented!()
15
- }
16
-
17
- // Types and structs from Ruby 3.4.0.
18
- #[repr(C)]
19
- pub struct rb_callable_method_entry_struct {
20
- /* same fields with rb_method_entry_t */
21
- pub flags: VALUE,
22
- _padding_defined_class: VALUE,
23
- pub def: *mut rb_method_definition_struct,
24
- // ...
25
- }
26
-
27
- #[repr(C)]
28
- pub struct rb_method_definition_struct {
29
- pub type_: c_int,
30
- _padding: [c_char; 4],
31
- pub cfunc: rb_method_cfunc_struct,
32
- // ...
33
- }
34
-
35
- #[repr(C)]
36
- pub struct rb_method_cfunc_struct {
37
- pub func: *mut c_void,
38
- // ...
39
- }
40
-
41
- type rb_nativethread_id_t = libc::pthread_t;
42
-
43
- #[repr(C)]
44
- struct rb_native_thread {
45
- _padding_serial: [c_char; 4], // rb_atomic_t
46
- _padding_vm: *mut c_int, // struct rb_vm_struct
47
- thread_id: rb_nativethread_id_t,
48
- // ...
49
- }
50
-
51
- #[repr(C)]
52
- struct rb_thread_struct {
53
- _padding_lt_node: [c_char; 16], // struct ccan_list_node
54
- _padding_self: VALUE,
55
- _padding_ractor: *mut c_int, // rb_ractor_t
56
- _padding_vm: *mut c_int, // rb_vm_t
57
- nt: *mut rb_native_thread,
58
- // ...
59
- }
60
- type rb_thread_t = rb_thread_struct;
61
-
62
- /// Reimplementation of the internal RTYPEDDATA_TYPE macro.
63
- unsafe fn RTYPEDDATA_TYPE(obj: VALUE) -> *const rb_data_type_struct {
64
- let typed: *mut RTypedData = obj as *mut RTypedData;
65
- (*typed).type_
66
- }
67
-
68
- unsafe fn rb_thread_ptr(thread: VALUE) -> *mut rb_thread_t {
69
- unsafe { rb_check_typeddata(thread, RTYPEDDATA_TYPE(thread)) as *mut rb_thread_t }
70
- }
71
-
72
- pub unsafe fn rb_thread_getcpuclockid(thread: VALUE) -> clockid_t {
73
- let mut cid: clockid_t = MaybeUninit::zeroed().assume_init();
74
- let pthread_id: pthread_t = (*(*rb_thread_ptr(thread)).nt).thread_id;
75
- pthread_getcpuclockid(pthread_id, &mut cid as *mut clockid_t);
76
- cid
77
- }
@@ -1,67 +0,0 @@
1
- use std::time::Instant;
2
-
3
- use rb_sys::*;
4
-
5
- use crate::backtrace::{Backtrace, BacktraceState};
6
-
7
- const MAX_STACK_DEPTH: usize = 500;
8
- const MAX_C_STACK_DEPTH: usize = 1000;
9
-
10
- #[derive(Debug, PartialEq)]
11
- pub struct Sample {
12
- pub ruby_thread: VALUE,
13
- pub timestamp: Instant,
14
- pub line_count: i32,
15
- pub frames: [VALUE; MAX_STACK_DEPTH],
16
- pub linenos: [i32; MAX_STACK_DEPTH],
17
- /// First element represents the backtrace depth.
18
- pub c_backtrace_pcs: [usize; MAX_C_STACK_DEPTH + 1],
19
- }
20
-
21
- impl Sample {
22
- // Nearly async-signal-safe
23
- // (rb_profile_thread_frames isn't defined as a-s-s)
24
- pub fn capture(ruby_thread: VALUE, backtrace_state: &BacktraceState) -> Self {
25
- let mut c_backtrace_pcs = [0; MAX_C_STACK_DEPTH + 1];
26
-
27
- Backtrace::backtrace_simple(
28
- backtrace_state,
29
- 0,
30
- |pc: usize| -> i32 {
31
- if c_backtrace_pcs[0] >= MAX_C_STACK_DEPTH {
32
- return 1;
33
- }
34
- c_backtrace_pcs[0] += 1;
35
- c_backtrace_pcs[c_backtrace_pcs[0]] = pc;
36
- 0
37
- },
38
- Some(Backtrace::backtrace_error_callback),
39
- );
40
-
41
- let mut sample = Sample {
42
- ruby_thread,
43
- timestamp: Instant::now(),
44
- line_count: 0,
45
- frames: [0; MAX_STACK_DEPTH],
46
- linenos: [0; MAX_STACK_DEPTH],
47
- c_backtrace_pcs,
48
- };
49
- unsafe {
50
- sample.line_count = rb_profile_thread_frames(
51
- ruby_thread,
52
- 0,
53
- 2000,
54
- sample.frames.as_mut_ptr(),
55
- sample.linenos.as_mut_ptr(),
56
- );
57
- };
58
- sample
59
- }
60
-
61
- pub unsafe fn dmark(&self) {
62
- rb_gc_mark(self.ruby_thread);
63
- for frame in self.frames.iter() {
64
- rb_gc_mark(*frame);
65
- }
66
- }
67
- }
@@ -1,10 +0,0 @@
1
- use rb_sys::{size_t, VALUE};
2
-
3
- pub trait Scheduler {
4
- fn start(&self) -> VALUE;
5
- fn stop(&self) -> VALUE;
6
- fn on_new_thread(&self, thread: VALUE);
7
- fn dmark(&self);
8
- fn dfree(&self);
9
- fn dsize(&self) -> size_t;
10
- }
@@ -1,48 +0,0 @@
1
- #[derive(Clone, Deserialize, Serialize)]
2
- pub struct Profile {
3
- pub samples: Vec<Sample>,
4
- pub locations: Vec<Location>,
5
- pub functions: Vec<Function>,
6
- pub start_timestamp_ns: u128,
7
- pub duration_ns: u128,
8
- }
9
-
10
- pub type LocationIndex = usize;
11
- pub type FunctionIndex = usize;
12
-
13
- /// Sample
14
- #[derive(Clone, Serialize, Deserialize)]
15
- pub struct Sample {
16
- /// The stack leading to this sample.
17
- /// The leaf node will be stored at `stack[0]`.
18
- pub stack: Vec<LocationIndex>,
19
- pub native_stack: Vec<LocationIndex>,
20
- pub ruby_thread_id: Option<u64>,
21
- pub elapsed_ns: u64,
22
- }
23
-
24
- /// Location represents a location (line) in the source code when a sample was captured.
25
- #[derive(Clone, PartialEq, Serialize, Deserialize)]
26
- pub struct Location {
27
- pub function_index: FunctionIndex,
28
- pub lineno: i32,
29
- pub address: Option<usize>,
30
- }
31
-
32
- /// Function represents a Ruby method or a C function in the profile.
33
- #[derive(Clone, PartialEq, Serialize, Deserialize)]
34
- pub struct Function {
35
- pub implementation: FunctionImplementation,
36
- pub name: Option<String>, // unique key
37
- pub filename: Option<String>,
38
- /// The first line number in the method/function definition.
39
- /// For the actual location (line) which was hit during sample capture, refer to `Location.lineno`.
40
- pub start_lineno: Option<i32>,
41
- pub start_address: Option<usize>,
42
- }
43
-
44
- #[derive(Clone, PartialEq, Serialize, Deserialize)]
45
- pub enum FunctionImplementation {
46
- Ruby,
47
- Native,
48
- }
@@ -1,329 +0,0 @@
1
- use std::ffi::{c_char, CStr, CString};
2
-
3
- use rb_sys::*;
4
-
5
- use super::profile::{
6
- Function, FunctionImplementation, FunctionIndex, Location, LocationIndex, Profile, Sample,
7
- };
8
- use crate::backtrace::Backtrace;
9
- use crate::util::{cstr, RTEST};
10
-
11
- pub struct ProfileSerializer2 {
12
- profile: Profile,
13
- }
14
-
15
- impl ProfileSerializer2 {
16
- pub fn new() -> ProfileSerializer2 {
17
- ProfileSerializer2 {
18
- profile: Profile {
19
- start_timestamp_ns: 0,
20
- duration_ns: 0,
21
- samples: vec![],
22
- locations: vec![],
23
- functions: vec![],
24
- },
25
- }
26
- }
27
-
28
- pub fn serialize(&mut self, source: &crate::profile::Profile) {
29
- // Fill in meta fields
30
- self.profile.start_timestamp_ns =
31
- source.start_timestamp.duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos();
32
- self.profile.duration_ns =
33
- source.end_instant.unwrap().duration_since(source.start_instant).as_nanos();
34
-
35
- // Create a Sample for each sample collected
36
- for sample in source.samples.iter() {
37
- // Iterate over the Ruby stack
38
- let mut stack: Vec<LocationIndex> = vec![];
39
- let ruby_stack_depth = sample.line_count;
40
- for i in 0..ruby_stack_depth {
41
- let frame: VALUE = sample.frames[i as usize];
42
- let lineno: i32 = sample.linenos[i as usize];
43
- let function = Self::extract_function_from_ruby_frame(frame);
44
-
45
- let function_index = self.function_index_for(function);
46
- let location_index = self.location_index_for(function_index, lineno);
47
- stack.push(location_index);
48
- }
49
-
50
- // Iterate over the native stack
51
- let mut native_stack: Vec<LocationIndex> = vec![];
52
- let native_stack_depth = sample.c_backtrace_pcs[0];
53
- for i in 1..(native_stack_depth - 1) {
54
- let pc = sample.c_backtrace_pcs[i];
55
- let function = Self::extract_function_from_native_pc(pc, source);
56
-
57
- let function_index = self.function_index_for(function);
58
- let location_index = self.location_index_for(function_index, 0);
59
- native_stack.push(location_index);
60
- }
61
-
62
- self.profile.samples.push(Sample {
63
- stack,
64
- native_stack,
65
- ruby_thread_id: Some(sample.ruby_thread),
66
- elapsed_ns: sample.timestamp.duration_since(source.start_instant).as_nanos() as u64,
67
- });
68
- }
69
- }
70
-
71
- /// Returns the index of the function in `functions`.
72
- /// Calling this method will modify `self.profile` in place.
73
- fn function_index_for(&mut self, function: Function) -> FunctionIndex {
74
- match self.profile.functions.iter_mut().position(|f| *f == function) {
75
- Some(index) => index,
76
- None => {
77
- self.profile.functions.push(function);
78
- self.profile.functions.len() - 1
79
- }
80
- }
81
- }
82
-
83
- /// Returns the index of the location in `locations`.
84
- /// Calling this method will modify `self.profile` in place.
85
- fn location_index_for(&mut self, function_index: FunctionIndex, lineno: i32) -> LocationIndex {
86
- // Build a Location based on (1) the Function and (2) the actual line hit during sampling.
87
- let location = Location { function_index, lineno, address: None };
88
- match self.profile.locations.iter_mut().position(|l| *l == location) {
89
- Some(index) => index,
90
- None => {
91
- self.profile.locations.push(location);
92
- self.profile.locations.len() - 1
93
- }
94
- }
95
- }
96
-
97
- /// Build a Function from a Ruby frame.
98
- fn extract_function_from_ruby_frame(frame: VALUE) -> Function {
99
- unsafe {
100
- let mut frame_full_label: VALUE = rb_profile_frame_full_label(frame);
101
- let frame_full_label: Option<String> = if RTEST(frame_full_label) {
102
- Some(
103
- CStr::from_ptr(rb_string_value_cstr(&mut frame_full_label))
104
- .to_str()
105
- .unwrap()
106
- .to_owned(),
107
- )
108
- } else {
109
- None
110
- };
111
-
112
- let mut frame_path: VALUE = rb_profile_frame_path(frame);
113
- let frame_path: Option<String> = if RTEST(frame_path) {
114
- Some(
115
- CStr::from_ptr(rb_string_value_cstr(&mut frame_path))
116
- .to_str()
117
- .unwrap()
118
- .to_owned(),
119
- )
120
- } else {
121
- None
122
- };
123
-
124
- let frame_first_lineno: VALUE = rb_profile_frame_first_lineno(frame);
125
- let frame_first_lineno: Option<i32> = if RTEST(frame_first_lineno) {
126
- Some(rb_num2int(frame_first_lineno).try_into().unwrap())
127
- } else {
128
- None
129
- };
130
-
131
- let start_address = Self::get_underlying_c_function_address(frame);
132
-
133
- Function {
134
- implementation: FunctionImplementation::Ruby,
135
- name: frame_full_label,
136
- filename: frame_path,
137
- start_lineno: frame_first_lineno,
138
- start_address,
139
- }
140
- }
141
- }
142
-
143
- fn get_underlying_c_function_address(frame: VALUE) -> Option<usize> {
144
- unsafe {
145
- let cme = frame as *mut crate::ruby_internal_apis::rb_callable_method_entry_struct;
146
- let cme = &*cme; // *mut to reference
147
-
148
- if (*(cme.def)).type_ == 1 {
149
- // The cme is a Cfunc
150
- Some((*(cme.def)).cfunc.func as usize)
151
- } else {
152
- // The cme is an ISeq (Ruby code) or some other type
153
- None
154
- }
155
- }
156
- }
157
-
158
- /// Build a Function from a PC (program counter) obtained by libbacktrace.
159
- fn extract_function_from_native_pc(pc: usize, source: &crate::profile::Profile) -> Function {
160
- // Obtain the function name and address using libbacktrace
161
- let mut function: Option<Function> = None;
162
- Backtrace::backtrace_syminfo(
163
- &source.backtrace_state,
164
- pc,
165
- |_pc: usize, symname: *const c_char, symval: usize, _symsize: usize| unsafe {
166
- function = Some(Function {
167
- implementation: FunctionImplementation::Native,
168
- name: if symname.is_null() {
169
- None
170
- } else {
171
- Some(CStr::from_ptr(symname).to_str().unwrap().to_owned())
172
- },
173
- filename: None,
174
- start_lineno: None,
175
- start_address: Some(symval),
176
- });
177
- },
178
- Some(Backtrace::backtrace_error_callback),
179
- );
180
- function.unwrap()
181
- }
182
-
183
- pub fn to_ruby_hash(&self) -> VALUE {
184
- unsafe {
185
- let hash: VALUE = rb_hash_new();
186
-
187
- // profile[:start_timestamp_ns]
188
- rb_hash_aset(
189
- hash,
190
- rb_id2sym(rb_intern(cstr!("start_timestamp_ns"))),
191
- rb_int2inum(self.profile.start_timestamp_ns as isize),
192
- );
193
- // profile[:duration_ns]
194
- rb_hash_aset(
195
- hash,
196
- rb_id2sym(rb_intern(cstr!("duration_ns"))),
197
- rb_int2inum(self.profile.duration_ns as isize),
198
- );
199
-
200
- // profile[:samples]
201
- let samples: VALUE = rb_ary_new();
202
- for sample in self.profile.samples.iter() {
203
- // sample[:stack]
204
- let stack: VALUE = rb_ary_new();
205
- for &location_index in sample.stack.iter() {
206
- rb_ary_push(stack, rb_int2inum(location_index as isize));
207
- }
208
- // sample[:native_stack]
209
- let native_stack: VALUE = rb_ary_new();
210
- for &location_index in sample.native_stack.iter() {
211
- rb_ary_push(native_stack, rb_int2inum(location_index as isize));
212
- }
213
- // sample[:ruby_thread_id]
214
- let ruby_thread_id = if let Some(ruby_thread_id) = sample.ruby_thread_id {
215
- rb_int2inum(ruby_thread_id as isize)
216
- } else {
217
- Qnil as VALUE
218
- };
219
- // sample[:elapsed_ns]
220
- let elapsed_ns = rb_ull2inum(sample.elapsed_ns);
221
-
222
- let sample_hash: VALUE = rb_hash_new();
223
- rb_hash_aset(sample_hash, rb_id2sym(rb_intern(cstr!("stack"))), stack);
224
- rb_hash_aset(
225
- sample_hash,
226
- rb_id2sym(rb_intern(cstr!("native_stack"))),
227
- native_stack,
228
- );
229
- rb_hash_aset(
230
- sample_hash,
231
- rb_id2sym(rb_intern(cstr!("ruby_thread_id"))),
232
- ruby_thread_id,
233
- );
234
- rb_hash_aset(sample_hash, rb_id2sym(rb_intern(cstr!("elapsed_ns"))), elapsed_ns);
235
-
236
- rb_ary_push(samples, sample_hash);
237
- }
238
- rb_hash_aset(hash, rb_id2sym(rb_intern(cstr!("samples"))), samples);
239
-
240
- // profile[:locations]
241
- let locations = rb_ary_new();
242
- for location in self.profile.locations.iter() {
243
- let location_hash: VALUE = rb_hash_new();
244
- // location[:function_index]
245
- rb_hash_aset(
246
- location_hash,
247
- rb_id2sym(rb_intern(cstr!("function_index"))),
248
- rb_int2inum(location.function_index as isize),
249
- );
250
- // location[:lineno]
251
- rb_hash_aset(
252
- location_hash,
253
- rb_id2sym(rb_intern(cstr!("lineno"))),
254
- rb_int2inum(location.lineno as isize),
255
- );
256
- // location[:address]
257
- rb_hash_aset(
258
- location_hash,
259
- rb_id2sym(rb_intern(cstr!("address"))),
260
- if let Some(address) = location.address {
261
- rb_int2inum(address as isize)
262
- } else {
263
- Qnil as VALUE
264
- },
265
- );
266
- rb_ary_push(locations, location_hash);
267
- }
268
- rb_hash_aset(hash, rb_id2sym(rb_intern(cstr!("locations"))), locations);
269
-
270
- // profile[:functions]
271
- let functions = rb_ary_new();
272
- for function in self.profile.functions.iter() {
273
- let function_hash: VALUE = rb_hash_new();
274
- // function[:implementation]
275
- rb_hash_aset(
276
- function_hash,
277
- rb_id2sym(rb_intern(cstr!("implementation"))),
278
- match function.implementation {
279
- FunctionImplementation::Ruby => rb_id2sym(rb_intern(cstr!("ruby"))),
280
- FunctionImplementation::Native => rb_id2sym(rb_intern(cstr!("native"))),
281
- },
282
- );
283
-
284
- // function[:name]
285
- let name: VALUE = match &function.name {
286
- Some(name) => {
287
- let cstring = CString::new(name.as_str()).unwrap();
288
- rb_str_new_cstr(cstring.as_ptr())
289
- }
290
- None => Qnil as VALUE,
291
- };
292
- rb_hash_aset(function_hash, rb_id2sym(rb_intern(cstr!("name"))), name);
293
- // function[:filename]
294
- let filename: VALUE = match &function.filename {
295
- Some(filename) => {
296
- let cstring = CString::new(filename.as_str()).unwrap();
297
- rb_str_new_cstr(cstring.as_ptr())
298
- }
299
- None => Qnil as VALUE,
300
- };
301
- rb_hash_aset(function_hash, rb_id2sym(rb_intern(cstr!("filename"))), filename);
302
- // function[:start_lineno]
303
- rb_hash_aset(
304
- function_hash,
305
- rb_id2sym(rb_intern(cstr!("start_lineno"))),
306
- if let Some(start_lineno) = function.start_lineno {
307
- rb_int2inum(start_lineno as isize)
308
- } else {
309
- Qnil as VALUE
310
- },
311
- );
312
- // function[:start_address]
313
- rb_hash_aset(
314
- function_hash,
315
- rb_id2sym(rb_intern(cstr!("start_address"))),
316
- if let Some(start_address) = function.start_address {
317
- rb_int2inum(start_address as isize)
318
- } else {
319
- Qnil as VALUE
320
- },
321
- );
322
- rb_ary_push(functions, function_hash);
323
- }
324
- rb_hash_aset(hash, rb_id2sym(rb_intern(cstr!("functions"))), functions);
325
-
326
- hash
327
- }
328
- }
329
- }
@@ -1,2 +0,0 @@
1
- pub mod profile;
2
- pub mod serializer;
@@ -1,114 +0,0 @@
1
- use std::collections::HashSet;
2
- use std::str::FromStr;
3
- use std::time::Duration;
4
-
5
- use rb_sys::*;
6
-
7
- use crate::util::cstr;
8
-
9
- #[cfg(target_os = "linux")]
10
- pub const DEFAULT_SCHEDULER: Scheduler = Scheduler::Signal;
11
- #[cfg(target_os = "linux")]
12
- pub const DEFAULT_TIME_MODE: TimeMode = TimeMode::CpuTime;
13
- #[cfg(not(target_os = "linux"))]
14
- pub const DEFAULT_SCHEDULER: Scheduler = Scheduler::TimerThread;
15
- #[cfg(not(target_os = "linux"))]
16
- pub const DEFAULT_TIME_MODE: TimeMode = TimeMode::WallTime;
17
-
18
- pub const DEFAULT_INTERVAL: Duration = Duration::from_millis(9);
19
-
20
- #[derive(Clone, Debug)]
21
- pub struct Configuration {
22
- pub scheduler: Scheduler,
23
- pub interval: Duration,
24
- pub time_mode: TimeMode,
25
- pub target_ruby_threads: Threads,
26
- pub use_experimental_serializer: bool,
27
- }
28
-
29
- #[derive(Clone, Debug, PartialEq)]
30
- pub enum Scheduler {
31
- Signal,
32
- TimerThread,
33
- }
34
-
35
- impl FromStr for Scheduler {
36
- type Err = ();
37
-
38
- fn from_str(s: &str) -> Result<Self, Self::Err> {
39
- match s {
40
- "signal" => Ok(Self::Signal),
41
- "timer_thread" => Ok(Self::TimerThread),
42
- _ => Err(()),
43
- }
44
- }
45
- }
46
-
47
- #[derive(Clone, Debug, PartialEq)]
48
- pub enum TimeMode {
49
- CpuTime,
50
- WallTime,
51
- }
52
-
53
- impl FromStr for TimeMode {
54
- type Err = ();
55
-
56
- fn from_str(s: &str) -> Result<Self, Self::Err> {
57
- match s {
58
- "cpu" => Ok(Self::CpuTime),
59
- "wall" => Ok(Self::WallTime),
60
- _ => Err(()),
61
- }
62
- }
63
- }
64
-
65
- #[derive(Clone, Debug, PartialEq)]
66
- pub enum Threads {
67
- All,
68
- Targeted(HashSet<VALUE>),
69
- }
70
-
71
- impl Configuration {
72
- pub fn validate(&self) -> Result<(), String> {
73
- if self.scheduler == Scheduler::TimerThread && self.time_mode == TimeMode::CpuTime {
74
- return Err("TimerThread scheduler does not support `time_mode: :cpu`.".to_owned());
75
- }
76
- if self.scheduler == Scheduler::TimerThread && self.target_ruby_threads == Threads::All {
77
- return Err(concat!(
78
- "TimerThread scheduler does not support `threads: :all` at the moment. ",
79
- "Consider using `threads: Thread.list` for watching all threads at profiler start."
80
- )
81
- .to_owned());
82
- }
83
-
84
- Ok(())
85
- }
86
-
87
- pub fn to_rb_hash(&self) -> VALUE {
88
- let hash: VALUE = unsafe { rb_hash_new() };
89
- unsafe {
90
- rb_hash_aset(
91
- hash,
92
- rb_id2sym(rb_intern(cstr!("scheduler"))),
93
- rb_id2sym(rb_intern(match self.scheduler {
94
- Scheduler::Signal => cstr!("signal"),
95
- Scheduler::TimerThread => cstr!("timer_thread"),
96
- })),
97
- );
98
- rb_hash_aset(
99
- hash,
100
- rb_id2sym(rb_intern(cstr!("interval_ms"))),
101
- rb_int2inum(self.interval.as_millis().try_into().unwrap()),
102
- );
103
- rb_hash_aset(
104
- hash,
105
- rb_id2sym(rb_intern(cstr!("time_mode"))),
106
- rb_id2sym(rb_intern(match self.time_mode {
107
- TimeMode::CpuTime => cstr!("cpu"),
108
- TimeMode::WallTime => cstr!("wall"),
109
- })),
110
- );
111
- }
112
- hash
113
- }
114
- }