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,179 +0,0 @@
1
- #![deny(unsafe_op_in_unsafe_fn)]
2
-
3
- use std::ffi::{c_void, CString};
4
- use std::mem::ManuallyDrop;
5
- use std::sync::atomic::{AtomicBool, Ordering};
6
- use std::sync::{Arc, RwLock};
7
- use std::thread;
8
-
9
- use rb_sys::*;
10
-
11
- use crate::profile::Profile;
12
- use crate::profile_serializer::ProfileSerializer;
13
- use crate::sample::Sample;
14
- use crate::scheduler::Scheduler;
15
- use crate::serialization::serializer::ProfileSerializer2;
16
- use crate::session::configuration::{self, Configuration};
17
- use crate::util::*;
18
-
19
- #[derive(Clone, Debug)]
20
- pub struct TimerThreadScheduler {
21
- configuration: Arc<Configuration>,
22
- profile: Arc<RwLock<Profile>>,
23
- stop_requested: Arc<AtomicBool>,
24
- }
25
-
26
- #[derive(Debug)]
27
- struct PostponedJobArgs {
28
- configuration: Arc<Configuration>,
29
- profile: Arc<RwLock<Profile>>,
30
- }
31
-
32
- impl Scheduler for TimerThreadScheduler {
33
- fn start(&self) -> VALUE {
34
- // Register the Postponed Job which does the actual work of collecting samples
35
- let postponed_job_args: Box<PostponedJobArgs> = Box::new(PostponedJobArgs {
36
- configuration: Arc::clone(&self.configuration),
37
- profile: Arc::clone(&self.profile),
38
- });
39
- let postponed_job_handle: rb_postponed_job_handle_t = unsafe {
40
- rb_postponed_job_preregister(
41
- 0,
42
- Some(Self::postponed_job),
43
- Box::into_raw(postponed_job_args) as *mut c_void, // FIXME: leak
44
- )
45
- };
46
-
47
- // Start a timer thread that periodically triggers postponed jobs based on configuration
48
- let configuration = Arc::clone(&self.configuration);
49
- let stop_requested = Arc::clone(&self.stop_requested);
50
- thread::spawn(move || {
51
- Self::thread_main_loop(configuration, stop_requested, postponed_job_handle)
52
- });
53
-
54
- Qtrue.into()
55
- }
56
-
57
- fn stop(&self) -> VALUE {
58
- // Stop the collector thread
59
- self.stop_requested.store(true, Ordering::Relaxed);
60
-
61
- // Finalize
62
- match self.profile.try_write() {
63
- Ok(mut profile) => {
64
- profile.flush_temporary_sample_buffer();
65
- profile.end_instant = Some(std::time::Instant::now());
66
- }
67
- Err(_) => {
68
- println!("[pf2 ERROR] stop: Failed to acquire profile lock.");
69
- return Qfalse.into();
70
- }
71
- }
72
-
73
- let profile = self.profile.try_read().unwrap();
74
- log::debug!("Number of samples: {}", profile.samples.len());
75
-
76
- if self.configuration.use_experimental_serializer {
77
- let mut ser = ProfileSerializer2::new();
78
- ser.serialize(&profile);
79
- ser.to_ruby_hash()
80
- } else {
81
- let serialized = ProfileSerializer::serialize(&profile);
82
- let string = CString::new(serialized).unwrap();
83
- unsafe { rb_str_new_cstr(string.as_ptr()) }
84
- }
85
- }
86
-
87
- fn on_new_thread(&self, _thread: VALUE) {
88
- todo!();
89
- }
90
-
91
- fn dmark(&self) {
92
- match self.profile.read() {
93
- Ok(profile) => unsafe {
94
- profile.dmark();
95
- },
96
- Err(_) => {
97
- panic!("[pf2 FATAL] dmark: Failed to acquire profile lock.");
98
- }
99
- }
100
- }
101
-
102
- fn dfree(&self) {
103
- // No-op
104
- }
105
-
106
- fn dsize(&self) -> size_t {
107
- // FIXME: Report something better
108
- std::mem::size_of::<TimerThreadScheduler>() as size_t
109
- }
110
- }
111
-
112
- impl TimerThreadScheduler {
113
- pub fn new(configuration: &Configuration, profile: Arc<RwLock<Profile>>) -> Self {
114
- Self {
115
- configuration: Arc::new(configuration.clone()),
116
- profile,
117
- stop_requested: Arc::new(AtomicBool::new(false)),
118
- }
119
-
120
- // cstr!("TimerThreadScheduler only supports :wall mode."),
121
- }
122
-
123
- fn thread_main_loop(
124
- configuration: Arc<Configuration>,
125
- stop_requested: Arc<AtomicBool>,
126
- postponed_job_handle: rb_postponed_job_handle_t,
127
- ) {
128
- loop {
129
- if stop_requested.fetch_and(true, Ordering::Relaxed) {
130
- break;
131
- }
132
- unsafe {
133
- log::trace!("Triggering postponed job");
134
- rb_postponed_job_trigger(postponed_job_handle);
135
- }
136
-
137
- thread::sleep(configuration.interval);
138
- }
139
- }
140
-
141
- unsafe extern "C" fn postponed_job(ptr: *mut c_void) {
142
- unsafe {
143
- rb_gc_disable();
144
- }
145
- let args = unsafe { ManuallyDrop::new(Box::from_raw(ptr as *mut PostponedJobArgs)) };
146
-
147
- let mut profile = match args.profile.try_write() {
148
- Ok(profile) => profile,
149
- Err(_) => {
150
- // FIXME: Do we want to properly collect GC samples? I don't know yet.
151
- log::trace!("Failed to acquire profile lock (garbage collection possibly in progress). Dropping sample.");
152
- return;
153
- }
154
- };
155
-
156
- // Collect stack information from specified Ruby Threads
157
- match &args.configuration.target_ruby_threads {
158
- configuration::Threads::All => todo!(),
159
- configuration::Threads::Targeted(threads) => {
160
- for ruby_thread in threads.iter() {
161
- // Check if the thread is still alive
162
- if unsafe { rb_funcall(*ruby_thread, rb_intern(cstr!("status")), 0) }
163
- == Qfalse as u64
164
- {
165
- continue;
166
- }
167
-
168
- let sample = Sample::capture(*ruby_thread, &profile.backtrace_state);
169
- if profile.temporary_sample_buffer.push(sample).is_err() {
170
- log::debug!("Temporary sample buffer full. Dropping sample.");
171
- }
172
- }
173
- }
174
- }
175
- unsafe {
176
- rb_gc_enable();
177
- }
178
- }
179
- }
data/ext/pf2/src/util.rs DELETED
@@ -1,31 +0,0 @@
1
- use core::mem::transmute;
2
- use rb_sys::*;
3
- use std::{ffi::c_void, u128};
4
-
5
- // Convert str literal to C string literal
6
- macro_rules! cstr {
7
- ($s:expr) => {
8
- concat!($s, "\0").as_ptr() as *const std::ffi::c_char
9
- };
10
- }
11
- pub(crate) use cstr;
12
-
13
- pub type RubyCFunc = unsafe extern "C" fn() -> VALUE;
14
-
15
- // TODO: rewrite as macro
16
- pub fn to_ruby_cfunc_with_no_args<T>(f: unsafe extern "C" fn(T) -> VALUE) -> RubyCFunc {
17
- unsafe { transmute::<unsafe extern "C" fn(T) -> VALUE, RubyCFunc>(f) }
18
- }
19
- pub fn to_ruby_cfunc_with_args<T, U, V>(f: unsafe extern "C" fn(T, U, V) -> VALUE) -> RubyCFunc {
20
- unsafe { transmute::<unsafe extern "C" fn(T, U, V) -> VALUE, RubyCFunc>(f) }
21
- }
22
-
23
- #[allow(non_snake_case)]
24
- pub fn RTEST(v: VALUE) -> bool {
25
- v != Qfalse as VALUE && v != Qnil as VALUE
26
- }
27
-
28
- extern "C" {
29
- pub fn extract_si_value_sival_ptr(info: *mut libc::siginfo_t) -> *mut c_void;
30
- pub fn rb_ull2num(n: u128) -> VALUE;
31
- }
@@ -1,397 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- module Pf2
6
- module Reporter
7
- # Generates Firefox Profiler's "processed profile format"
8
- # https://github.com/firefox-devtools/profiler/blob/main/docs-developer/processed-profile-format.md
9
- class FirefoxProfiler
10
- def initialize(profile)
11
- @profile = FirefoxProfiler.deep_intize_keys(profile)
12
- end
13
-
14
- def inspect
15
- "#<#{self.class.name}>" # TODO: add sample count etc.
16
- end
17
-
18
- def emit
19
- report = {
20
- meta: {
21
- interval: 10, # ms; TODO: replace with actual interval
22
- start_time: 0,
23
- process_type: 0,
24
- product: 'ruby',
25
- stackwalk: 0,
26
- version: 28,
27
- preprocessed_profile_version: 47,
28
- symbolicated: true,
29
- categories: [
30
- {
31
- name: "Logs",
32
- color: "grey",
33
- subcategories: ["Unused"],
34
- },
35
- {
36
- name: "Ruby",
37
- color: "red",
38
- subcategories: ["Code"],
39
- },
40
- {
41
- name: "Native",
42
- color: "blue",
43
- subcategories: ["Code"],
44
- },
45
- {
46
- name: "Native",
47
- color: "lightblue",
48
- subcategories: ["Code"],
49
- },
50
- ],
51
- marker_schema: [],
52
- },
53
- libs: [],
54
- counters: [],
55
- threads: @profile[:threads].values.map {|th| ThreadReport.new(th).emit }
56
- }
57
- FirefoxProfiler.deep_camelize_keys(report)
58
- end
59
-
60
- class ThreadReport
61
- def initialize(thread)
62
- @thread = thread
63
-
64
- # Populated in other methods
65
- @func_id_map = {}
66
- @frame_id_map = {}
67
- @stack_tree_id_map = {}
68
-
69
- @string_table = {}
70
- end
71
-
72
- def inspect
73
- "" # TODO: provide something better
74
- end
75
-
76
- def emit
77
- x = weave_native_stack(@thread[:stack_tree])
78
- @thread[:stack_tree] = x
79
- func_table = build_func_table
80
- frame_table = build_frame_table
81
- stack_table = build_stack_table(func_table, frame_table)
82
- samples = build_samples
83
-
84
- string_table = build_string_table
85
-
86
- {
87
- process_type: 'default',
88
- process_name: 'ruby',
89
- process_startup_time: 0,
90
- process_shutdown_time: nil,
91
- register_time: 0,
92
- unregister_time: nil,
93
- paused_ranges: [],
94
- name: "Thread (tid: #{@thread[:thread_id]})",
95
- is_main_thread: true,
96
- is_js_tracer: true,
97
- # FIXME: We can fill the correct PID only after we correctly fill is_main_thread
98
- # (only one thread could be marked as is_main_thread in a single process)
99
- pid: @thread[:thread_id],
100
- tid: @thread[:thread_id],
101
- samples: samples,
102
- markers: markers,
103
- stack_table: stack_table,
104
- frame_table: frame_table,
105
- string_array: build_string_table,
106
- func_table: func_table,
107
- resource_table: {
108
- lib: [],
109
- name: [],
110
- host: [],
111
- type: [],
112
- length: 0,
113
- },
114
- native_symbols: [],
115
- }
116
- end
117
-
118
- def build_samples
119
- ret = {
120
- event_delay: [],
121
- stack: [],
122
- time: [],
123
- duration: [],
124
- # weight: nil,
125
- # weight_type: 'samples',
126
- }
127
-
128
- @thread[:samples].each do |sample|
129
- ret[:stack] << @stack_tree_id_map[sample[:stack_tree_id]]
130
- ret[:time] << sample[:elapsed_ns] / 1000000 # ns -> ms
131
- ret[:duration] << 1
132
- ret[:event_delay] << 0
133
- end
134
-
135
- ret[:length] = ret[:stack].length
136
- ret
137
- end
138
-
139
- def build_frame_table
140
- ret = {
141
- address: [],
142
- category: [],
143
- subcategory: [],
144
- func: [],
145
- inner_window_id: [],
146
- implementation: [],
147
- line: [],
148
- column: [],
149
- optimizations: [],
150
- inline_depth: [],
151
- native_symbol: [],
152
- }
153
-
154
- @thread[:frames].each.with_index do |(id, frame), i|
155
- ret[:address] << frame[:address].to_s
156
- ret[:category] << 1
157
- ret[:subcategory] << 1
158
- ret[:func] << i # TODO
159
- ret[:inner_window_id] << nil
160
- ret[:implementation] << nil
161
- ret[:line] << frame[:callsite_lineno]
162
- ret[:column] << nil
163
- ret[:optimizations] << nil
164
- ret[:inline_depth] << 0
165
- ret[:native_symbol] << nil
166
-
167
- @frame_id_map[id] = i
168
- end
169
-
170
- ret[:length] = ret[:address].length
171
- ret
172
- end
173
-
174
- def build_func_table
175
- ret = {
176
- name: [],
177
- is_js: [],
178
- relevant_for_js: [],
179
- resource: [],
180
- file_name: [],
181
- line_number: [],
182
- column_number: [],
183
- }
184
-
185
- @thread[:frames].each.with_index do |(id, frame), i|
186
- native = (frame[:entry_type] == 'Native')
187
- label = "#{native ? "Native: " : ""}#{frame[:full_label]}"
188
- ret[:name] << string_id(label)
189
- ret[:is_js] << !native
190
- ret[:relevant_for_js] << false
191
- ret[:resource] << -1
192
- ret[:file_name] << string_id(frame[:file_name])
193
- ret[:line_number] << frame[:function_first_lineno]
194
- ret[:column_number] << nil
195
-
196
- @func_id_map[id] = i
197
- end
198
-
199
- ret[:length] = ret[:name].length
200
- ret
201
- end
202
-
203
- # "Weave" the native stack into the Ruby stack.
204
- #
205
- # Strategy:
206
- # - Split the stack into Ruby and Native parts
207
- # - Start from the root of the Native stack
208
- # - Dig in to the native stack until we hit a rb_vm_exec(), which marks a call into Ruby code
209
- # - Switch to Ruby stack. Keep digging until we hit a Cfunc call, then switch back to Native stack
210
- # - Repeat until we consume the entire stack
211
- def weave_native_stack(stack_tree)
212
- collected_paths = []
213
- tree_to_array_of_paths(stack_tree, @thread[:frames], [], collected_paths)
214
- collected_paths = collected_paths.map do |path|
215
- next if path.size == 0
216
-
217
- new_path = []
218
- new_path << path.shift # root
219
-
220
- # Split the stack into Ruby and Native parts
221
- native_path, ruby_path = path.partition do |frame|
222
- frame_id = frame[:frame_id]
223
- @thread[:frames][frame_id][:entry_type] == 'Native'
224
- end
225
-
226
- mode = :native
227
-
228
- loop do
229
- break if ruby_path.size == 0 && native_path.size == 0
230
-
231
- case mode
232
- when :ruby
233
- if ruby_path.size == 0
234
- mode = :native
235
- next
236
- end
237
-
238
- next_node = ruby_path[0]
239
- new_path << ruby_path.shift
240
- next_node_frame = @thread[:frames][next_node[:frame_id]]
241
- if native_path.size > 0
242
- # Search the remainder of the native stack for the same address
243
- # Note: This isn't a very efficient way for the job... but it still works
244
- ruby_addr = next_node_frame[:address]
245
- native_path[0..].each do |native_node|
246
- native_addr = @thread[:frames][native_node[:frame_id]][:address]
247
- if ruby_addr && native_addr && ruby_addr == native_addr
248
- # A match has been found. Switch to native mode
249
- mode = :native
250
- break
251
- end
252
- end
253
- end
254
- when :native
255
- if native_path.size == 0
256
- mode = :ruby
257
- next
258
- end
259
-
260
- # Dig until we meet a rb_vm_exec
261
- next_node = native_path[0]
262
- new_path << native_path.shift
263
- if @thread[:frames][next_node[:frame_id]][:full_label] =~ /vm_exec_core/ # VM_EXEC in vm_exec.h
264
- mode = :ruby
265
- end
266
- end
267
- end
268
-
269
- new_path
270
- end
271
-
272
- # reconstruct stack_tree
273
- new_stack_tree = array_of_paths_to_tree(collected_paths)
274
- new_stack_tree
275
- end
276
-
277
- def tree_to_array_of_paths(stack_tree, frames, path, collected_paths)
278
- new_path = path + [{ frame_id: stack_tree[:frame_id], node_id: stack_tree[:node_id] }]
279
- if stack_tree[:children].empty?
280
- collected_paths << new_path
281
- else
282
- stack_tree[:children].each do |frame_id, child|
283
- tree_to_array_of_paths(child, frames, new_path, collected_paths)
284
- end
285
- end
286
- end
287
-
288
- def array_of_paths_to_tree(paths)
289
- new_stack_tree = { children: {}, node_id: 0, frame_id: 0 }
290
- paths.each do |path|
291
- current = new_stack_tree
292
- path[1..].each do |frame|
293
- frame_id = frame[:frame_id]
294
- node_id = frame[:node_id]
295
- current[:children][frame_id] ||= { children: {}, node_id: node_id, frame_id: frame_id }
296
- current = current[:children][frame_id]
297
- end
298
- end
299
- new_stack_tree
300
- end
301
-
302
- def build_stack_table(func_table, frame_table)
303
- ret = {
304
- frame: [],
305
- category: [],
306
- subcategory: [],
307
- prefix: [],
308
- }
309
-
310
- queue = []
311
-
312
- @thread[:stack_tree][:children].each {|_, c| queue << [nil, c] }
313
-
314
- loop do
315
- break if queue.size == 0
316
-
317
- prefix, node = queue.shift
318
- ret[:frame] << @frame_id_map[node[:frame_id]]
319
- ret[:category] << (build_string_table[func_table[:name][frame_table[:func][@frame_id_map[node[:frame_id]]]]].start_with?('Native:') ? 2 : 1)
320
- ret[:subcategory] << nil
321
- ret[:prefix] << prefix
322
-
323
- # The index of this frame - children can refer to this frame using this index as prefix
324
- frame_index = ret[:frame].length - 1
325
- @stack_tree_id_map[node[:node_id]] = frame_index
326
-
327
- # Enqueue children nodes
328
- node[:children].each {|_, c| queue << [frame_index, c] }
329
- end
330
-
331
- ret[:length] = ret[:frame].length
332
- ret
333
- end
334
-
335
- def build_string_table
336
- @string_table.sort_by {|_, v| v}.map {|s| s[0] }
337
- end
338
-
339
- def string_id(str)
340
- return @string_table[str] if @string_table.has_key?(str)
341
- @string_table[str] = @string_table.length
342
- @string_table[str]
343
- end
344
-
345
- def markers
346
- {
347
- data: [],
348
- name: [],
349
- time: [],
350
- start_time: [],
351
- end_time: [],
352
- phase: [],
353
- category: [],
354
- length: 0
355
- }
356
- end
357
- end
358
-
359
- # Util functions
360
- class << self
361
- def snake_to_camel(s)
362
- return "isJS" if s == "is_js"
363
- return "relevantForJS" if s == "relevant_for_js"
364
- return "innerWindowID" if s == "inner_window_id"
365
- s.split('_').inject([]) {|buffer, p| buffer.push(buffer.size == 0 ? p : p.capitalize) }.join
366
- end
367
-
368
- def deep_transform_keys(value, &block)
369
- case value
370
- when Array
371
- value.map {|v| deep_transform_keys(v, &block) }
372
- when Hash
373
- Hash[value.map {|k, v| [yield(k), deep_transform_keys(v, &block)] }]
374
- else
375
- value
376
- end
377
- end
378
-
379
- def deep_camelize_keys(value)
380
- deep_transform_keys(value) do |key|
381
- snake_to_camel(key.to_s).to_sym
382
- end
383
- end
384
-
385
- def deep_intize_keys(value)
386
- deep_transform_keys(value) do |key|
387
- if key.to_s.to_i.to_s == key.to_s
388
- key.to_s.to_i
389
- else
390
- key
391
- end
392
- end
393
- end
394
- end
395
- end
396
- end
397
- end
data/rust-toolchain.toml DELETED
@@ -1,2 +0,0 @@
1
- [toolchain]
2
- channel = "1.75.0"
data/rustfmt.toml DELETED
@@ -1 +0,0 @@
1
- use_small_heuristics = "Max"