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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +20 -4
- data/Rakefile +1 -0
- data/doc/development.md +17 -0
- data/examples/mandelbrot.rb +69 -0
- data/examples/mandelbrot_ractor.rb +77 -0
- data/ext/pf2/backtrace_state.c +10 -0
- data/ext/pf2/backtrace_state.h +10 -0
- data/ext/pf2/configuration.c +90 -0
- data/ext/pf2/configuration.h +23 -0
- data/ext/pf2/debug.h +12 -0
- data/ext/pf2/extconf.rb +23 -6
- data/ext/pf2/pf2.c +17 -0
- data/ext/pf2/pf2.h +8 -0
- data/ext/pf2/ringbuffer.c +74 -0
- data/ext/pf2/ringbuffer.h +24 -0
- data/ext/pf2/sample.c +76 -0
- data/ext/pf2/sample.h +26 -0
- data/ext/pf2/serializer.c +377 -0
- data/ext/pf2/serializer.h +58 -0
- data/ext/pf2/session.c +394 -0
- data/ext/pf2/session.h +56 -0
- data/lib/pf2/cli.rb +25 -11
- data/lib/pf2/reporter/annotate.rb +101 -0
- data/lib/pf2/reporter/firefox_profiler_ser2.rb +17 -13
- data/lib/pf2/reporter/stack_weaver.rb +8 -0
- data/lib/pf2/reporter.rb +1 -1
- data/lib/pf2/version.rb +1 -1
- data/lib/pf2.rb +1 -1
- data/vendor/libbacktrace/.gitignore +5 -0
- data/{crates/backtrace-sys2/src → vendor}/libbacktrace/README.md +1 -1
- data/{crates/backtrace-sys2/src → vendor}/libbacktrace/configure +23 -0
- data/{crates/backtrace-sys2/src → vendor}/libbacktrace/configure.ac +10 -0
- data/{crates/backtrace-sys2/src → vendor}/libbacktrace/dwarf.c +199 -15
- data/{crates/backtrace-sys2/src → vendor}/libbacktrace/elf.c +20 -14
- data/{crates/backtrace-sys2/src → vendor}/libbacktrace/fileline.c +2 -2
- data/{crates/backtrace-sys2/src → vendor}/libbacktrace/macho.c +2 -2
- data/{crates/backtrace-sys2/src → vendor}/libbacktrace/pecoff.c +2 -2
- metadata +111 -111
- data/Cargo.lock +0 -630
- data/Cargo.toml +0 -3
- data/crates/backtrace-sys2/.gitignore +0 -1
- data/crates/backtrace-sys2/Cargo.toml +0 -9
- data/crates/backtrace-sys2/build.rs +0 -45
- data/crates/backtrace-sys2/src/lib.rs +0 -5
- data/crates/backtrace-sys2/src/libbacktrace/.gitignore +0 -15
- data/ext/pf2/Cargo.toml +0 -25
- data/ext/pf2/build.rs +0 -10
- data/ext/pf2/src/backtrace.rs +0 -127
- data/ext/pf2/src/lib.rs +0 -22
- data/ext/pf2/src/profile.rs +0 -69
- data/ext/pf2/src/profile_serializer.rs +0 -241
- data/ext/pf2/src/ringbuffer.rs +0 -150
- data/ext/pf2/src/ruby_c_api_helper.c +0 -6
- data/ext/pf2/src/ruby_init.rs +0 -40
- data/ext/pf2/src/ruby_internal_apis.rs +0 -77
- data/ext/pf2/src/sample.rs +0 -67
- data/ext/pf2/src/scheduler.rs +0 -10
- data/ext/pf2/src/serialization/profile.rs +0 -48
- data/ext/pf2/src/serialization/serializer.rs +0 -329
- data/ext/pf2/src/serialization.rs +0 -2
- data/ext/pf2/src/session/configuration.rs +0 -114
- data/ext/pf2/src/session/new_thread_watcher.rs +0 -80
- data/ext/pf2/src/session/ruby_object.rs +0 -90
- data/ext/pf2/src/session.rs +0 -248
- data/ext/pf2/src/siginfo_t.c +0 -5
- data/ext/pf2/src/signal_scheduler.rs +0 -201
- data/ext/pf2/src/signal_scheduler_unsupported_platform.rs +0 -39
- data/ext/pf2/src/timer_thread_scheduler.rs +0 -179
- data/ext/pf2/src/util.rs +0 -31
- data/lib/pf2/reporter/firefox_profiler.rb +0 -397
- data/rust-toolchain.toml +0 -2
- data/rustfmt.toml +0 -1
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/Isaac.Newton-Opticks.txt +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/LICENSE +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/Makefile.am +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/Makefile.in +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/aclocal.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/alloc.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/allocfail.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/allocfail.sh +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/atomic.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/backtrace-supported.h.in +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/backtrace.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/backtrace.h +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/btest.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/compile +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/enable.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/lead-dot.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/libtool.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/ltoptions.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/ltsugar.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/ltversion.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/lt~obsolete.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/multi.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/override.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/unwind_ipinfo.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config/warnings.m4 +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config.guess +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config.h.in +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/config.sub +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/edtest.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/edtest2.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/filenames.h +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/filetype.awk +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/install-debuginfo-for-buildid.sh.in +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/install-sh +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/instrumented_alloc.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/internal.h +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/ltmain.sh +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/missing +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/mmap.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/mmapio.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/move-if-change +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/mtest.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/nounwind.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/posix.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/print.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/read.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/simple.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/sort.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/state.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/stest.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/test-driver +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/test_format.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/testlib.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/testlib.h +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/ttest.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/unittest.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/unknown.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/xcoff.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/xztest.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/zstdtest.c +0 -0
- /data/{crates/backtrace-sys2/src → vendor}/libbacktrace/ztest.c +0 -0
data/ext/pf2/src/ruby_init.rs
DELETED
@@ -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
|
-
}
|
data/ext/pf2/src/sample.rs
DELETED
@@ -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
|
-
}
|
data/ext/pf2/src/scheduler.rs
DELETED
@@ -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,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
|
-
}
|