pf2 0.2.0 → 0.3.0

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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -2
  3. data/Cargo.lock +186 -17
  4. data/Cargo.toml +1 -1
  5. data/README.md +17 -6
  6. data/Rakefile +8 -0
  7. data/crates/backtrace-sys2/.gitignore +1 -0
  8. data/crates/backtrace-sys2/Cargo.toml +9 -0
  9. data/crates/backtrace-sys2/build.rs +48 -0
  10. data/crates/backtrace-sys2/src/lib.rs +5 -0
  11. data/crates/backtrace-sys2/src/libbacktrace/.gitignore +15 -0
  12. data/crates/backtrace-sys2/src/libbacktrace/Isaac.Newton-Opticks.txt +9286 -0
  13. data/crates/backtrace-sys2/src/libbacktrace/LICENSE +29 -0
  14. data/crates/backtrace-sys2/src/libbacktrace/Makefile.am +623 -0
  15. data/crates/backtrace-sys2/src/libbacktrace/Makefile.in +2666 -0
  16. data/crates/backtrace-sys2/src/libbacktrace/README.md +36 -0
  17. data/crates/backtrace-sys2/src/libbacktrace/aclocal.m4 +864 -0
  18. data/crates/backtrace-sys2/src/libbacktrace/alloc.c +167 -0
  19. data/crates/backtrace-sys2/src/libbacktrace/allocfail.c +136 -0
  20. data/crates/backtrace-sys2/src/libbacktrace/allocfail.sh +104 -0
  21. data/crates/backtrace-sys2/src/libbacktrace/atomic.c +113 -0
  22. data/crates/backtrace-sys2/src/libbacktrace/backtrace-supported.h.in +66 -0
  23. data/crates/backtrace-sys2/src/libbacktrace/backtrace.c +129 -0
  24. data/crates/backtrace-sys2/src/libbacktrace/backtrace.h +189 -0
  25. data/crates/backtrace-sys2/src/libbacktrace/btest.c +501 -0
  26. data/crates/backtrace-sys2/src/libbacktrace/compile +348 -0
  27. data/crates/backtrace-sys2/src/libbacktrace/config/enable.m4 +38 -0
  28. data/crates/backtrace-sys2/src/libbacktrace/config/lead-dot.m4 +31 -0
  29. data/crates/backtrace-sys2/src/libbacktrace/config/libtool.m4 +7436 -0
  30. data/crates/backtrace-sys2/src/libbacktrace/config/ltoptions.m4 +369 -0
  31. data/crates/backtrace-sys2/src/libbacktrace/config/ltsugar.m4 +123 -0
  32. data/crates/backtrace-sys2/src/libbacktrace/config/ltversion.m4 +23 -0
  33. data/crates/backtrace-sys2/src/libbacktrace/config/lt~obsolete.m4 +98 -0
  34. data/crates/backtrace-sys2/src/libbacktrace/config/multi.m4 +68 -0
  35. data/crates/backtrace-sys2/src/libbacktrace/config/override.m4 +117 -0
  36. data/crates/backtrace-sys2/src/libbacktrace/config/unwind_ipinfo.m4 +37 -0
  37. data/crates/backtrace-sys2/src/libbacktrace/config/warnings.m4 +227 -0
  38. data/crates/backtrace-sys2/src/libbacktrace/config.guess +1700 -0
  39. data/crates/backtrace-sys2/src/libbacktrace/config.h.in +182 -0
  40. data/crates/backtrace-sys2/src/libbacktrace/config.sub +1885 -0
  41. data/crates/backtrace-sys2/src/libbacktrace/configure +15740 -0
  42. data/crates/backtrace-sys2/src/libbacktrace/configure.ac +613 -0
  43. data/crates/backtrace-sys2/src/libbacktrace/dwarf.c +4402 -0
  44. data/crates/backtrace-sys2/src/libbacktrace/edtest.c +120 -0
  45. data/crates/backtrace-sys2/src/libbacktrace/edtest2.c +43 -0
  46. data/crates/backtrace-sys2/src/libbacktrace/elf.c +7443 -0
  47. data/crates/backtrace-sys2/src/libbacktrace/fileline.c +407 -0
  48. data/crates/backtrace-sys2/src/libbacktrace/filenames.h +52 -0
  49. data/crates/backtrace-sys2/src/libbacktrace/filetype.awk +13 -0
  50. data/crates/backtrace-sys2/src/libbacktrace/install-debuginfo-for-buildid.sh.in +65 -0
  51. data/crates/backtrace-sys2/src/libbacktrace/install-sh +501 -0
  52. data/crates/backtrace-sys2/src/libbacktrace/instrumented_alloc.c +114 -0
  53. data/crates/backtrace-sys2/src/libbacktrace/internal.h +389 -0
  54. data/crates/backtrace-sys2/src/libbacktrace/libtool.m4 +7436 -0
  55. data/crates/backtrace-sys2/src/libbacktrace/ltmain.sh +8636 -0
  56. data/crates/backtrace-sys2/src/libbacktrace/ltoptions.m4 +369 -0
  57. data/crates/backtrace-sys2/src/libbacktrace/ltsugar.m4 +123 -0
  58. data/crates/backtrace-sys2/src/libbacktrace/ltversion.m4 +23 -0
  59. data/crates/backtrace-sys2/src/libbacktrace/lt~obsolete.m4 +98 -0
  60. data/crates/backtrace-sys2/src/libbacktrace/macho.c +1355 -0
  61. data/crates/backtrace-sys2/src/libbacktrace/missing +215 -0
  62. data/crates/backtrace-sys2/src/libbacktrace/mmap.c +331 -0
  63. data/crates/backtrace-sys2/src/libbacktrace/mmapio.c +110 -0
  64. data/crates/backtrace-sys2/src/libbacktrace/move-if-change +83 -0
  65. data/crates/backtrace-sys2/src/libbacktrace/mtest.c +410 -0
  66. data/crates/backtrace-sys2/src/libbacktrace/nounwind.c +66 -0
  67. data/crates/backtrace-sys2/src/libbacktrace/pecoff.c +957 -0
  68. data/crates/backtrace-sys2/src/libbacktrace/posix.c +104 -0
  69. data/crates/backtrace-sys2/src/libbacktrace/print.c +92 -0
  70. data/crates/backtrace-sys2/src/libbacktrace/read.c +110 -0
  71. data/crates/backtrace-sys2/src/libbacktrace/simple.c +108 -0
  72. data/crates/backtrace-sys2/src/libbacktrace/sort.c +108 -0
  73. data/crates/backtrace-sys2/src/libbacktrace/state.c +72 -0
  74. data/crates/backtrace-sys2/src/libbacktrace/stest.c +137 -0
  75. data/crates/backtrace-sys2/src/libbacktrace/test-driver +148 -0
  76. data/crates/backtrace-sys2/src/libbacktrace/test_format.c +55 -0
  77. data/crates/backtrace-sys2/src/libbacktrace/testlib.c +234 -0
  78. data/crates/backtrace-sys2/src/libbacktrace/testlib.h +110 -0
  79. data/crates/backtrace-sys2/src/libbacktrace/ttest.c +161 -0
  80. data/crates/backtrace-sys2/src/libbacktrace/unittest.c +92 -0
  81. data/crates/backtrace-sys2/src/libbacktrace/unknown.c +65 -0
  82. data/crates/backtrace-sys2/src/libbacktrace/xcoff.c +1606 -0
  83. data/crates/backtrace-sys2/src/libbacktrace/xztest.c +508 -0
  84. data/crates/backtrace-sys2/src/libbacktrace/zstdtest.c +523 -0
  85. data/crates/backtrace-sys2/src/libbacktrace/ztest.c +541 -0
  86. data/ext/pf2/Cargo.toml +1 -0
  87. data/ext/pf2/src/backtrace.rs +126 -0
  88. data/ext/pf2/src/lib.rs +1 -0
  89. data/ext/pf2/src/profile.rs +16 -1
  90. data/ext/pf2/src/profile_serializer.rs +95 -21
  91. data/ext/pf2/src/ringbuffer.rs +7 -0
  92. data/ext/pf2/src/ruby_init.rs +18 -6
  93. data/ext/pf2/src/sample.rs +22 -1
  94. data/ext/pf2/src/signal_scheduler/configuration.rs +7 -0
  95. data/ext/pf2/src/signal_scheduler/timer_installer.rs +34 -27
  96. data/ext/pf2/src/signal_scheduler.rs +95 -26
  97. data/ext/pf2/src/timer_thread_scheduler.rs +88 -12
  98. data/ext/pf2/src/util.rs +2 -2
  99. data/lib/pf2/reporter.rb +12 -5
  100. data/lib/pf2/version.rb +1 -1
  101. data/lib/pf2.rb +3 -6
  102. metadata +96 -2
@@ -1,8 +1,11 @@
1
- use std::collections::HashSet;
2
1
  use std::time::Instant;
2
+ use std::{collections::HashSet, ptr::null_mut};
3
3
 
4
4
  use rb_sys::*;
5
5
 
6
+ use backtrace_sys2::backtrace_create_state;
7
+
8
+ use super::backtrace::{Backtrace, BacktraceState};
6
9
  use super::ringbuffer::Ringbuffer;
7
10
  use super::sample::Sample;
8
11
 
@@ -15,15 +18,27 @@ pub struct Profile {
15
18
  pub start_timestamp: Instant,
16
19
  pub samples: Vec<Sample>,
17
20
  pub temporary_sample_buffer: Ringbuffer,
21
+ pub backtrace_state: BacktraceState,
18
22
  known_values: HashSet<VALUE>,
19
23
  }
20
24
 
21
25
  impl Profile {
22
26
  pub fn new() -> Self {
27
+ let backtrace_state = unsafe {
28
+ let ptr = backtrace_create_state(
29
+ null_mut(),
30
+ 1,
31
+ Some(Backtrace::backtrace_error_callback),
32
+ null_mut(),
33
+ );
34
+ BacktraceState::new(ptr)
35
+ };
36
+
23
37
  Self {
24
38
  start_timestamp: Instant::now(),
25
39
  samples: vec![],
26
40
  temporary_sample_buffer: Ringbuffer::new(DEFAULT_RINGBUFFER_CAPACITY),
41
+ backtrace_state,
27
42
  known_values: HashSet::new(),
28
43
  }
29
44
  }
@@ -1,7 +1,10 @@
1
- use std::{collections::HashMap, ffi::CStr};
1
+ use std::collections::HashMap;
2
+ use std::ffi::{c_char, CStr};
3
+ use std::hash::Hasher;
2
4
 
3
5
  use rb_sys::*;
4
6
 
7
+ use crate::backtrace::Backtrace;
5
8
  use crate::profile::Profile;
6
9
 
7
10
  #[derive(Debug, Deserialize, Serialize)]
@@ -56,9 +59,17 @@ struct StackTreeNode {
56
59
 
57
60
  #[derive(Debug, Deserialize, Serialize)]
58
61
  struct FrameTableEntry {
62
+ id: FrameTableId,
63
+ entry_type: FrameTableEntryType,
59
64
  full_label: String,
60
65
  }
61
66
 
67
+ #[derive(Debug, Deserialize, Serialize)]
68
+ enum FrameTableEntryType {
69
+ Ruby,
70
+ Native,
71
+ }
72
+
62
73
  // Represents leaf (末端)
63
74
  #[derive(Debug, Deserialize, Serialize)]
64
75
  struct ProfileSample {
@@ -77,6 +88,73 @@ impl ProfileSerializer {
77
88
  unsafe {
78
89
  // Process each sample
79
90
  for sample in profile.samples.iter() {
91
+ let mut merged_stack: Vec<FrameTableEntry> = vec![];
92
+
93
+ // Process C-level stack
94
+
95
+ // A vec to keep the "programmer's" C stack trace.
96
+ // A single PC may be mapped to multiple inlined frames,
97
+ // so we keep the expanded stack frame in this Vec.
98
+ let mut c_stack: Vec<String> = vec![];
99
+ for i in 0..sample.c_backtrace_pcs[0] {
100
+ let pc = sample.c_backtrace_pcs[i + 1];
101
+ Backtrace::backtrace_syminfo(
102
+ &profile.backtrace_state,
103
+ pc,
104
+ |_pc: usize, symname: *const c_char, _symval: usize, _symsize: usize| {
105
+ if symname.is_null() {
106
+ c_stack.push("(no symbol information)".to_owned());
107
+ } else {
108
+ c_stack.push(CStr::from_ptr(symname).to_str().unwrap().to_owned());
109
+ }
110
+ },
111
+ Some(Backtrace::backtrace_error_callback),
112
+ );
113
+ }
114
+
115
+ // Strip the C stack trace:
116
+ // - Remove Pf2-related frames which are always captured
117
+ // - Remove frames below rb_vm_exec
118
+ let mut reached_ruby = false;
119
+ c_stack.retain(|frame| {
120
+ if reached_ruby {
121
+ return false;
122
+ }
123
+ if frame.contains("pf2") {
124
+ return false;
125
+ }
126
+ if frame.contains("rb_vm_exec") || frame.contains("vm_call_cfunc_with_frame") {
127
+ reached_ruby = true;
128
+ return false;
129
+ }
130
+ true
131
+ });
132
+
133
+ for frame in c_stack.iter() {
134
+ merged_stack.push(FrameTableEntry {
135
+ id: calculate_id_for_c_frame(frame),
136
+ entry_type: FrameTableEntryType::Native,
137
+ full_label: frame.to_string(),
138
+ });
139
+ }
140
+
141
+ // Process Ruby-level stack
142
+
143
+ let ruby_stack_depth = sample.line_count;
144
+ for i in 0..ruby_stack_depth {
145
+ let frame: VALUE = sample.frames[i as usize];
146
+ merged_stack.push(FrameTableEntry {
147
+ id: frame,
148
+ entry_type: FrameTableEntryType::Ruby,
149
+ full_label: CStr::from_ptr(rb_string_value_cstr(
150
+ &mut rb_profile_frame_full_label(frame),
151
+ ))
152
+ .to_str()
153
+ .unwrap()
154
+ .to_owned(),
155
+ });
156
+ }
157
+
80
158
  // Find the Thread profile for this sample
81
159
  let thread_serializer = serializer
82
160
  .threads
@@ -86,34 +164,18 @@ impl ProfileSerializer {
86
164
  // Stack frames, shallow to deep
87
165
  let mut stack_tree = &mut thread_serializer.stack_tree;
88
166
 
89
- for i in (0..(sample.line_count - 1)).rev() {
90
- let frame = sample.frames[i as usize];
91
-
92
- // Register frame metadata to frame table, if not registered yet
93
- let frame_table_id: FrameTableId = frame;
94
- thread_serializer
95
- .frame_table
96
- .entry(frame_table_id)
97
- .or_insert(FrameTableEntry {
98
- full_label: CStr::from_ptr(rb_string_value_cstr(
99
- &mut rb_profile_frame_full_label(frame),
100
- ))
101
- .to_str()
102
- .unwrap()
103
- .to_string(),
104
- });
105
-
106
- stack_tree = stack_tree.children.entry(frame_table_id).or_insert({
167
+ while let Some(frame_table_entry) = merged_stack.pop() {
168
+ stack_tree = stack_tree.children.entry(frame_table_entry.id).or_insert({
107
169
  let node = StackTreeNode {
108
170
  children: HashMap::new(),
109
171
  node_id: sequence,
110
- frame_id: frame_table_id,
172
+ frame_id: frame_table_entry.id,
111
173
  };
112
174
  sequence += 1;
113
175
  node
114
176
  });
115
177
 
116
- if i == 0 {
178
+ if merged_stack.is_empty() {
117
179
  // This is the leaf node, record a Sample
118
180
  let elapsed_ns = (sample.timestamp - profile.start_timestamp).as_nanos();
119
181
  thread_serializer.samples.push(ProfileSample {
@@ -121,6 +183,12 @@ impl ProfileSerializer {
121
183
  stack_tree_id: stack_tree.node_id,
122
184
  });
123
185
  }
186
+
187
+ // Register frame metadata to frame table, if not registered yet
188
+ thread_serializer
189
+ .frame_table
190
+ .entry(frame_table_entry.id)
191
+ .or_insert(frame_table_entry);
124
192
  }
125
193
  }
126
194
  }
@@ -128,3 +196,9 @@ impl ProfileSerializer {
128
196
  serde_json::to_string(&serializer).unwrap()
129
197
  }
130
198
  }
199
+
200
+ fn calculate_id_for_c_frame<T: std::hash::Hash>(t: &T) -> FrameTableId {
201
+ let mut s = std::collections::hash_map::DefaultHasher::new();
202
+ t.hash(&mut s);
203
+ s.finish()
204
+ }
@@ -71,6 +71,7 @@ mod tests {
71
71
  line_count: 0,
72
72
  frames: [0; 500],
73
73
  linenos: [0; 500],
74
+ c_backtrace_pcs: [0; 1001],
74
75
  };
75
76
  let sample2 = Sample {
76
77
  ruby_thread: 2,
@@ -78,6 +79,7 @@ mod tests {
78
79
  line_count: 0,
79
80
  frames: [0; 500],
80
81
  linenos: [0; 500],
82
+ c_backtrace_pcs: [0; 1001],
81
83
  };
82
84
 
83
85
  ringbuffer.push(sample1).unwrap();
@@ -97,6 +99,7 @@ mod tests {
97
99
  line_count: 0,
98
100
  frames: [0; 500],
99
101
  linenos: [0; 500],
102
+ c_backtrace_pcs: [0; 1001],
100
103
  };
101
104
  let sample2 = Sample {
102
105
  ruby_thread: 2,
@@ -104,6 +107,7 @@ mod tests {
104
107
  line_count: 0,
105
108
  frames: [0; 500],
106
109
  linenos: [0; 500],
110
+ c_backtrace_pcs: [0; 1001],
107
111
  };
108
112
 
109
113
  ringbuffer.push(sample1).unwrap();
@@ -119,6 +123,7 @@ mod tests {
119
123
  line_count: 0,
120
124
  frames: [0; 500],
121
125
  linenos: [0; 500],
126
+ c_backtrace_pcs: [0; 1001],
122
127
  };
123
128
  let sample2 = Sample {
124
129
  ruby_thread: 2,
@@ -126,6 +131,7 @@ mod tests {
126
131
  line_count: 0,
127
132
  frames: [0; 500],
128
133
  linenos: [0; 500],
134
+ c_backtrace_pcs: [0; 1001],
129
135
  };
130
136
  let sample3 = Sample {
131
137
  ruby_thread: 3,
@@ -133,6 +139,7 @@ mod tests {
133
139
  line_count: 0,
134
140
  frames: [0; 500],
135
141
  linenos: [0; 500],
142
+ c_backtrace_pcs: [0; 1001],
136
143
  };
137
144
 
138
145
  ringbuffer.push(sample1).unwrap();
@@ -26,16 +26,22 @@ extern "C" fn Init_pf2() {
26
26
  let rb_mPf2_SignalScheduler =
27
27
  rb_define_class_under(rb_mPf2, cstr!("SignalScheduler"), rb_cObject);
28
28
  rb_define_alloc_func(rb_mPf2_SignalScheduler, Some(SignalScheduler::rb_alloc));
29
+ rb_define_method(
30
+ rb_mPf2_SignalScheduler,
31
+ cstr!("initialize"),
32
+ Some(to_ruby_cfunc_with_args(SignalScheduler::rb_initialize)),
33
+ -1,
34
+ );
29
35
  rb_define_method(
30
36
  rb_mPf2_SignalScheduler,
31
37
  cstr!("start"),
32
- Some(to_ruby_cfunc3(SignalScheduler::rb_start)),
33
- 2,
38
+ Some(to_ruby_cfunc_with_no_args(SignalScheduler::rb_start)),
39
+ 0,
34
40
  );
35
41
  rb_define_method(
36
42
  rb_mPf2_SignalScheduler,
37
43
  cstr!("stop"),
38
- Some(to_ruby_cfunc1(SignalScheduler::rb_stop)),
44
+ Some(to_ruby_cfunc_with_no_args(SignalScheduler::rb_stop)),
39
45
  0,
40
46
  );
41
47
  }
@@ -46,16 +52,22 @@ extern "C" fn Init_pf2() {
46
52
  rb_mPf2_TimerThreadScheduler,
47
53
  Some(TimerThreadScheduler::rb_alloc),
48
54
  );
55
+ rb_define_method(
56
+ rb_mPf2_TimerThreadScheduler,
57
+ cstr!("initialize"),
58
+ Some(to_ruby_cfunc_with_args(TimerThreadScheduler::rb_initialize)),
59
+ -1,
60
+ );
49
61
  rb_define_method(
50
62
  rb_mPf2_TimerThreadScheduler,
51
63
  cstr!("start"),
52
- Some(to_ruby_cfunc3(TimerThreadScheduler::rb_start)),
53
- 2,
64
+ Some(to_ruby_cfunc_with_no_args(TimerThreadScheduler::rb_start)),
65
+ 0,
54
66
  );
55
67
  rb_define_method(
56
68
  rb_mPf2_TimerThreadScheduler,
57
69
  cstr!("stop"),
58
- Some(to_ruby_cfunc1(TimerThreadScheduler::rb_stop)),
70
+ Some(to_ruby_cfunc_with_no_args(TimerThreadScheduler::rb_stop)),
59
71
  0,
60
72
  );
61
73
  }
@@ -2,7 +2,10 @@ use std::time::Instant;
2
2
 
3
3
  use rb_sys::*;
4
4
 
5
+ use crate::backtrace::{Backtrace, BacktraceState};
6
+
5
7
  const MAX_STACK_DEPTH: usize = 500;
8
+ const MAX_C_STACK_DEPTH: usize = 1000;
6
9
 
7
10
  #[derive(Debug, PartialEq)]
8
11
  pub struct Sample {
@@ -11,18 +14,36 @@ pub struct Sample {
11
14
  pub line_count: i32,
12
15
  pub frames: [VALUE; MAX_STACK_DEPTH],
13
16
  pub linenos: [i32; MAX_STACK_DEPTH],
17
+ pub c_backtrace_pcs: [usize; MAX_C_STACK_DEPTH + 1],
14
18
  }
15
19
 
16
20
  impl Sample {
17
21
  // Nearly async-signal-safe
18
22
  // (rb_profile_thread_frames isn't defined as a-s-s)
19
- pub fn capture(ruby_thread: VALUE) -> Self {
23
+ pub fn capture(ruby_thread: VALUE, backtrace_state: &BacktraceState) -> Self {
24
+ let mut c_backtrace_pcs = [0; MAX_C_STACK_DEPTH + 1];
25
+
26
+ Backtrace::backtrace_simple(
27
+ backtrace_state,
28
+ 0,
29
+ |pc: usize| -> i32 {
30
+ if c_backtrace_pcs[0] >= MAX_C_STACK_DEPTH {
31
+ return 1;
32
+ }
33
+ c_backtrace_pcs[0] += 1;
34
+ c_backtrace_pcs[c_backtrace_pcs[0]] = pc;
35
+ 0
36
+ },
37
+ Some(Backtrace::backtrace_error_callback),
38
+ );
39
+
20
40
  let mut sample = Sample {
21
41
  ruby_thread,
22
42
  timestamp: Instant::now(),
23
43
  line_count: 0,
24
44
  frames: [0; MAX_STACK_DEPTH],
25
45
  linenos: [0; MAX_STACK_DEPTH],
46
+ c_backtrace_pcs,
26
47
  };
27
48
  unsafe {
28
49
  sample.line_count = rb_profile_thread_frames(
@@ -1,8 +1,15 @@
1
+ use std::collections::HashSet;
1
2
  use std::str::FromStr;
3
+ use std::time::Duration;
4
+
5
+ use rb_sys::VALUE;
2
6
 
3
7
  #[derive(Clone, Debug)]
4
8
  pub struct Configuration {
9
+ pub interval: Duration,
5
10
  pub time_mode: TimeMode,
11
+ pub target_ruby_threads: HashSet<VALUE>,
12
+ pub track_new_threads: bool,
6
13
  }
7
14
 
8
15
  #[derive(Clone, Debug)]
@@ -23,7 +23,6 @@ pub struct TimerInstaller {
23
23
 
24
24
  struct Internal {
25
25
  configuration: Configuration,
26
- target_ruby_threads: HashSet<VALUE>,
27
26
  registered_pthread_ids: HashSet<libc::pthread_t>,
28
27
  kernel_thread_id_to_ruby_thread_map: HashMap<libc::pid_t, VALUE>,
29
28
  profile: Arc<RwLock<Profile>>,
@@ -34,14 +33,11 @@ impl TimerInstaller {
34
33
  // The callback should create a timer for the thread.
35
34
  pub fn install_timer_to_ruby_threads(
36
35
  configuration: Configuration,
37
- ruby_threads: &HashSet<VALUE>,
38
36
  profile: Arc<RwLock<Profile>>,
39
- track_new_threads: bool,
40
37
  ) {
41
38
  let registrar = Self {
42
39
  internal: Box::new(Mutex::new(Internal {
43
- configuration,
44
- target_ruby_threads: ruby_threads.clone(),
40
+ configuration: configuration.clone(),
45
41
  registered_pthread_ids: HashSet::new(),
46
42
  kernel_thread_id_to_ruby_thread_map: HashMap::new(),
47
43
  profile,
@@ -60,7 +56,7 @@ impl TimerInstaller {
60
56
  rb_thread_create(Some(Self::do_nothing), null_mut());
61
57
  };
62
58
 
63
- if track_new_threads {
59
+ if configuration.track_new_threads {
64
60
  unsafe {
65
61
  rb_internal_thread_add_event_hook(
66
62
  Some(Self::on_thread_start),
@@ -88,7 +84,11 @@ impl TimerInstaller {
88
84
 
89
85
  // Check if the current thread is a target Ruby Thread
90
86
  let current_ruby_thread: VALUE = unsafe { (*data).thread };
91
- if !internal.target_ruby_threads.contains(&current_ruby_thread) {
87
+ if !internal
88
+ .configuration
89
+ .target_ruby_threads
90
+ .contains(&current_ruby_thread)
91
+ {
92
92
  return;
93
93
  }
94
94
 
@@ -129,7 +129,10 @@ impl TimerInstaller {
129
129
  let mut internal = internal.lock().unwrap();
130
130
 
131
131
  let current_ruby_thread: VALUE = unsafe { (*data).thread };
132
- internal.target_ruby_threads.insert(current_ruby_thread);
132
+ internal
133
+ .configuration
134
+ .target_ruby_threads
135
+ .insert(current_ruby_thread);
133
136
  }
134
137
 
135
138
  // Creates a new POSIX timer which invocates sampling for the thread that called this function.
@@ -162,31 +165,35 @@ impl TimerInstaller {
162
165
  // Pass required args to the signal handler
163
166
  sigevent.sigev_value.sival_ptr = Box::into_raw(signal_handler_args) as *mut c_void;
164
167
 
165
- // Create and configure timer to fire every 10 ms of CPU time
168
+ // Create and configure timer to fire every _interval_ ms of CPU time
166
169
  let mut timer: libc::timer_t = unsafe { mem::zeroed() };
167
- match configuration.time_mode {
168
- crate::signal_scheduler::TimeMode::CpuTime => {
169
- let err = unsafe {
170
- libc::timer_create(libc::CLOCK_THREAD_CPUTIME_ID, &mut sigevent, &mut timer)
171
- };
172
- if err != 0 {
173
- panic!("timer_create failed: {}", err);
174
- }
175
- }
176
- crate::signal_scheduler::TimeMode::WallTime => {
177
- todo!("WallTime is not supported yet");
178
- }
170
+ let clockid = match configuration.time_mode {
171
+ crate::signal_scheduler::TimeMode::CpuTime => libc::CLOCK_THREAD_CPUTIME_ID,
172
+ crate::signal_scheduler::TimeMode::WallTime => libc::CLOCK_MONOTONIC,
179
173
  };
180
- let mut its: libc::itimerspec = unsafe { mem::zeroed() };
181
- its.it_interval.tv_sec = 0;
182
- its.it_interval.tv_nsec = 10_000_000; // 10 ms
183
- its.it_value.tv_sec = 0;
184
- its.it_value.tv_nsec = 10_000_000;
185
- let err = unsafe { libc::timer_settime(timer, 0, &its, null_mut()) };
174
+ let err = unsafe { libc::timer_create(clockid, &mut sigevent, &mut timer) };
175
+ if err != 0 {
176
+ panic!("timer_create failed: {}", err);
177
+ }
178
+ let itimerspec = Self::duration_to_itimerspec(&configuration.interval);
179
+ let err = unsafe { libc::timer_settime(timer, 0, &itimerspec, null_mut()) };
186
180
  if err != 0 {
187
181
  panic!("timer_settime failed: {}", err);
188
182
  }
189
183
 
190
184
  log::debug!("timer registered for thread {}", current_pthread_id);
191
185
  }
186
+
187
+ fn duration_to_itimerspec(duration: &std::time::Duration) -> libc::itimerspec {
188
+ let nanos = duration.as_nanos();
189
+ let seconds_part: i64 = (nanos / 1_000_000_000).try_into().unwrap();
190
+ let nanos_part: i64 = (nanos % 1_000_000_000).try_into().unwrap();
191
+
192
+ let mut its: libc::itimerspec = unsafe { mem::zeroed() };
193
+ its.it_interval.tv_sec = seconds_part;
194
+ its.it_interval.tv_nsec = nanos_part;
195
+ its.it_value.tv_sec = seconds_part;
196
+ its.it_value.tv_nsec = nanos_part;
197
+ its
198
+ }
192
199
  }
@@ -11,8 +11,9 @@ use crate::sample::Sample;
11
11
 
12
12
  use core::panic;
13
13
  use std::collections::HashSet;
14
- use std::ffi::{c_int, c_void, CString};
14
+ use std::ffi::{c_int, c_void, CStr, CString};
15
15
  use std::mem::ManuallyDrop;
16
+ use std::str::FromStr;
16
17
  use std::sync::{Arc, RwLock};
17
18
  use std::thread;
18
19
  use std::time::Duration;
@@ -24,7 +25,7 @@ use crate::util::*;
24
25
 
25
26
  #[derive(Debug)]
26
27
  pub struct SignalScheduler {
27
- configuration: configuration::Configuration,
28
+ configuration: Option<configuration::Configuration>,
28
29
  profile: Option<Arc<RwLock<Profile>>>,
29
30
  }
30
31
 
@@ -36,37 +37,100 @@ pub struct SignalHandlerArgs {
36
37
  impl SignalScheduler {
37
38
  fn new() -> Self {
38
39
  Self {
39
- configuration: Configuration {
40
- time_mode: TimeMode::CpuTime,
41
- },
40
+ configuration: None,
42
41
  profile: None,
43
42
  }
44
43
  }
45
44
 
46
- fn start(
47
- &mut self,
48
- _rbself: VALUE,
49
- ruby_threads_rary: VALUE,
50
- track_new_threads: VALUE,
51
- ) -> VALUE {
52
- let track_new_threads = RTEST(track_new_threads);
53
-
54
- let profile = Arc::new(RwLock::new(Profile::new()));
55
- self.start_profile_buffer_flusher_thread(&profile);
56
- self.install_signal_handler();
45
+ fn initialize(&mut self, argc: c_int, argv: *const VALUE, _rbself: VALUE) -> VALUE {
46
+ // Parse arguments
47
+ let kwargs: VALUE = Qnil.into();
48
+ unsafe {
49
+ rb_scan_args(argc, argv, cstr!(":"), &kwargs);
50
+ };
51
+ let mut kwargs_values: [VALUE; 4] = [Qnil.into(); 4];
52
+ unsafe {
53
+ rb_get_kwargs(
54
+ kwargs,
55
+ [
56
+ rb_intern(cstr!("interval_ms")),
57
+ rb_intern(cstr!("threads")),
58
+ rb_intern(cstr!("time_mode")),
59
+ rb_intern(cstr!("track_new_threads")),
60
+ ]
61
+ .as_mut_ptr(),
62
+ 0,
63
+ 4,
64
+ kwargs_values.as_mut_ptr(),
65
+ );
66
+ };
67
+ let interval: Duration = if kwargs_values[0] != Qundef as VALUE {
68
+ let interval_ms = unsafe { rb_num2long(kwargs_values[0]) };
69
+ Duration::from_millis(interval_ms.try_into().unwrap_or_else(|_| {
70
+ eprintln!(
71
+ "[Pf2] Warning: Specified interval ({}) is not valid. Using default value (49ms).",
72
+ interval_ms
73
+ );
74
+ 49
75
+ }))
76
+ } else {
77
+ Duration::from_millis(49)
78
+ };
79
+ let threads: VALUE = if kwargs_values[1] != Qundef as VALUE {
80
+ kwargs_values[1]
81
+ } else {
82
+ unsafe { rb_funcall(rb_cThread, rb_intern(cstr!("list")), 0) }
83
+ };
84
+ let time_mode: configuration::TimeMode = if kwargs_values[2] != Qundef as VALUE {
85
+ let specified_mode = unsafe {
86
+ let mut str = rb_funcall(kwargs_values[2], rb_intern(cstr!("to_s")), 0);
87
+ let ptr = rb_string_value_ptr(&mut str);
88
+ CStr::from_ptr(ptr).to_str().unwrap()
89
+ };
90
+ configuration::TimeMode::from_str(specified_mode).unwrap_or_else(|_| {
91
+ // Raise an ArgumentError
92
+ unsafe {
93
+ rb_raise(
94
+ rb_eArgError,
95
+ cstr!("Invalid time mode. Valid values are 'cpu' and 'wall'."),
96
+ )
97
+ }
98
+ })
99
+ } else {
100
+ configuration::TimeMode::CpuTime
101
+ };
102
+ let track_new_threads: bool = if kwargs_values[3] != Qundef as VALUE {
103
+ RTEST(kwargs_values[3])
104
+ } else {
105
+ false
106
+ };
57
107
 
58
108
  let mut target_ruby_threads = HashSet::new();
59
109
  unsafe {
60
- for i in 0..RARRAY_LEN(ruby_threads_rary) {
61
- let ruby_thread: VALUE = rb_ary_entry(ruby_threads_rary, i);
110
+ for i in 0..RARRAY_LEN(threads) {
111
+ let ruby_thread: VALUE = rb_ary_entry(threads, i);
62
112
  target_ruby_threads.insert(ruby_thread);
63
113
  }
64
114
  }
115
+
116
+ self.configuration = Some(Configuration {
117
+ interval,
118
+ target_ruby_threads,
119
+ time_mode,
120
+ track_new_threads,
121
+ });
122
+
123
+ Qnil.into()
124
+ }
125
+
126
+ fn start(&mut self, _rbself: VALUE) -> VALUE {
127
+ let profile = Arc::new(RwLock::new(Profile::new()));
128
+ self.start_profile_buffer_flusher_thread(&profile);
129
+ self.install_signal_handler();
130
+
65
131
  TimerInstaller::install_timer_to_ruby_threads(
66
- self.configuration.clone(),
67
- &target_ruby_threads,
132
+ self.configuration.as_ref().unwrap().clone(), // FIXME: don't clone
68
133
  Arc::clone(&profile),
69
- track_new_threads,
70
134
  );
71
135
 
72
136
  self.profile = Some(profile);
@@ -132,7 +196,7 @@ impl SignalScheduler {
132
196
  }
133
197
  };
134
198
 
135
- let sample = Sample::capture(args.context_ruby_thread); // NOT async-signal-safe
199
+ let sample = Sample::capture(args.context_ruby_thread, &profile.backtrace_state); // NOT async-signal-safe
136
200
  if profile.temporary_sample_buffer.push(sample).is_err() {
137
201
  log::debug!("Temporary sample buffer full. Dropping sample.");
138
202
  }
@@ -156,13 +220,18 @@ impl SignalScheduler {
156
220
 
157
221
  // Ruby Methods
158
222
 
159
- pub unsafe extern "C" fn rb_start(
223
+ pub unsafe extern "C" fn rb_initialize(
224
+ argc: c_int,
225
+ argv: *const VALUE,
160
226
  rbself: VALUE,
161
- ruby_threads: VALUE,
162
- track_new_threads: VALUE,
163
227
  ) -> VALUE {
164
228
  let mut collector = unsafe { Self::get_struct_from(rbself) };
165
- collector.start(rbself, ruby_threads, track_new_threads)
229
+ collector.initialize(argc, argv, rbself)
230
+ }
231
+
232
+ pub unsafe extern "C" fn rb_start(rbself: VALUE) -> VALUE {
233
+ let mut collector = unsafe { Self::get_struct_from(rbself) };
234
+ collector.start(rbself)
166
235
  }
167
236
 
168
237
  pub unsafe extern "C" fn rb_stop(rbself: VALUE) -> VALUE {