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/Cargo.toml
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
[package]
|
2
|
-
name = "pf2"
|
3
|
-
version = "0.1.0"
|
4
|
-
edition = "2021"
|
5
|
-
authors = ["Daisuke Aritomo <osyoyu@osyoyu.com>"]
|
6
|
-
publish = false
|
7
|
-
|
8
|
-
[lib]
|
9
|
-
crate-type = ["cdylib"]
|
10
|
-
|
11
|
-
[dependencies]
|
12
|
-
backtrace-sys2 = { path = "../../crates/backtrace-sys2" }
|
13
|
-
env_logger = { version = "0.11.0", optional = true }
|
14
|
-
libc = "0.2.149"
|
15
|
-
log = "0.4.20"
|
16
|
-
rb-sys = { version = "0.9.105", features = ["stable-api"] }
|
17
|
-
serde = "1.0.189"
|
18
|
-
serde_derive = "1.0.189"
|
19
|
-
serde_json = "1.0.107"
|
20
|
-
|
21
|
-
[build-dependencies]
|
22
|
-
cc = "1.0.83"
|
23
|
-
|
24
|
-
[features]
|
25
|
-
debug = ["env_logger"]
|
data/ext/pf2/build.rs
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
use std::env;
|
2
|
-
|
3
|
-
fn main() {
|
4
|
-
cc::Build::new().file("src/siginfo_t.c").compile("ccode");
|
5
|
-
cc::Build::new()
|
6
|
-
.flag(format!("-I{}", env::var("DEP_RB_RBCONFIG_RUBYHDRDIR").unwrap()).as_str())
|
7
|
-
.flag(format!("-I{}", env::var("DEP_RB_RBCONFIG_RUBYARCHHDRDIR").unwrap()).as_str())
|
8
|
-
.file("src/ruby_c_api_helper.c")
|
9
|
-
.compile("rubyhelper");
|
10
|
-
}
|
data/ext/pf2/src/backtrace.rs
DELETED
@@ -1,127 +0,0 @@
|
|
1
|
-
use std::ffi::{c_char, c_int, CStr};
|
2
|
-
|
3
|
-
use libc::c_void;
|
4
|
-
|
5
|
-
#[derive(Debug)]
|
6
|
-
pub struct BacktraceState {
|
7
|
-
ptr: *mut backtrace_sys2::backtrace_state,
|
8
|
-
}
|
9
|
-
|
10
|
-
unsafe impl Send for BacktraceState {}
|
11
|
-
unsafe impl Sync for BacktraceState {}
|
12
|
-
|
13
|
-
impl BacktraceState {
|
14
|
-
pub unsafe fn new(ptr: *mut backtrace_sys2::backtrace_state) -> Self {
|
15
|
-
Self { ptr }
|
16
|
-
}
|
17
|
-
|
18
|
-
pub fn as_mut_ptr(&self) -> *mut backtrace_sys2::backtrace_state {
|
19
|
-
self.ptr
|
20
|
-
}
|
21
|
-
}
|
22
|
-
|
23
|
-
pub struct Backtrace {}
|
24
|
-
|
25
|
-
impl Backtrace {
|
26
|
-
pub fn backtrace_simple<F>(
|
27
|
-
state: &BacktraceState,
|
28
|
-
skip: i32,
|
29
|
-
mut on_ok: F,
|
30
|
-
on_error: backtrace_sys2::backtrace_error_callback,
|
31
|
-
) where
|
32
|
-
F: FnMut(usize) -> c_int,
|
33
|
-
{
|
34
|
-
unsafe {
|
35
|
-
backtrace_sys2::backtrace_simple(
|
36
|
-
state.as_mut_ptr(),
|
37
|
-
skip,
|
38
|
-
Some(Self::backtrace_simple_trampoline::<F>),
|
39
|
-
on_error,
|
40
|
-
&mut on_ok as *mut _ as *mut c_void,
|
41
|
-
);
|
42
|
-
}
|
43
|
-
}
|
44
|
-
|
45
|
-
#[allow(dead_code)] // TODO: Remove this
|
46
|
-
pub fn backtrace_pcinfo<F>(
|
47
|
-
state: &BacktraceState,
|
48
|
-
pc: usize,
|
49
|
-
mut on_ok: F,
|
50
|
-
on_error: backtrace_sys2::backtrace_error_callback,
|
51
|
-
) where
|
52
|
-
F: FnMut(usize, *const c_char, c_int, *const c_char) -> c_int,
|
53
|
-
{
|
54
|
-
unsafe {
|
55
|
-
backtrace_sys2::backtrace_pcinfo(
|
56
|
-
state.as_mut_ptr(),
|
57
|
-
pc,
|
58
|
-
Some(Self::backtrace_full_trampoline::<F>),
|
59
|
-
on_error,
|
60
|
-
&mut on_ok as *mut _ as *mut c_void,
|
61
|
-
);
|
62
|
-
}
|
63
|
-
}
|
64
|
-
|
65
|
-
pub fn backtrace_syminfo<F>(
|
66
|
-
state: &BacktraceState,
|
67
|
-
pc: usize,
|
68
|
-
mut on_ok: F,
|
69
|
-
on_error: backtrace_sys2::backtrace_error_callback,
|
70
|
-
) where
|
71
|
-
F: FnMut(usize, *const c_char, usize, usize),
|
72
|
-
{
|
73
|
-
unsafe {
|
74
|
-
backtrace_sys2::backtrace_syminfo(
|
75
|
-
state.as_mut_ptr(),
|
76
|
-
pc,
|
77
|
-
Some(Self::backtrace_syminfo_trampoline::<F>),
|
78
|
-
on_error,
|
79
|
-
&mut on_ok as *mut _ as *mut c_void,
|
80
|
-
);
|
81
|
-
}
|
82
|
-
}
|
83
|
-
|
84
|
-
unsafe extern "C" fn backtrace_full_trampoline<F>(
|
85
|
-
user_data: *mut c_void,
|
86
|
-
pc: usize,
|
87
|
-
filename: *const c_char,
|
88
|
-
lineno: c_int,
|
89
|
-
function: *const c_char,
|
90
|
-
) -> c_int
|
91
|
-
where
|
92
|
-
F: FnMut(usize, *const c_char, c_int, *const c_char) -> c_int,
|
93
|
-
{
|
94
|
-
let user_data = &mut *(user_data as *mut F);
|
95
|
-
user_data(pc, filename, lineno, function)
|
96
|
-
}
|
97
|
-
|
98
|
-
unsafe extern "C" fn backtrace_simple_trampoline<F>(user_data: *mut c_void, pc: usize) -> c_int
|
99
|
-
where
|
100
|
-
F: FnMut(usize) -> c_int,
|
101
|
-
{
|
102
|
-
let user_data = &mut *(user_data as *mut F);
|
103
|
-
user_data(pc)
|
104
|
-
}
|
105
|
-
|
106
|
-
unsafe extern "C" fn backtrace_syminfo_trampoline<F>(
|
107
|
-
user_data: *mut c_void,
|
108
|
-
pc: usize,
|
109
|
-
symname: *const c_char,
|
110
|
-
symval: usize,
|
111
|
-
symsize: usize,
|
112
|
-
) where
|
113
|
-
F: FnMut(usize, *const c_char, usize, usize),
|
114
|
-
{
|
115
|
-
let user_data = &mut *(user_data as *mut F);
|
116
|
-
user_data(pc, symname, symval, symsize);
|
117
|
-
}
|
118
|
-
|
119
|
-
pub unsafe extern "C" fn backtrace_error_callback(
|
120
|
-
_data: *mut c_void,
|
121
|
-
msg: *const c_char,
|
122
|
-
errnum: c_int,
|
123
|
-
) {
|
124
|
-
let msg = unsafe { CStr::from_ptr(msg) };
|
125
|
-
log::debug!("backtrace error: {:?} ({})", msg, errnum);
|
126
|
-
}
|
127
|
-
}
|
data/ext/pf2/src/lib.rs
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
extern crate serde;
|
2
|
-
#[macro_use]
|
3
|
-
extern crate serde_derive;
|
4
|
-
|
5
|
-
mod ruby_init;
|
6
|
-
|
7
|
-
mod backtrace;
|
8
|
-
mod profile;
|
9
|
-
mod profile_serializer;
|
10
|
-
mod ringbuffer;
|
11
|
-
mod sample;
|
12
|
-
mod scheduler;
|
13
|
-
mod serialization;
|
14
|
-
mod session;
|
15
|
-
#[cfg(target_os = "linux")]
|
16
|
-
mod signal_scheduler;
|
17
|
-
#[cfg(not(target_os = "linux"))]
|
18
|
-
mod signal_scheduler_unsupported_platform;
|
19
|
-
mod timer_thread_scheduler;
|
20
|
-
mod util;
|
21
|
-
|
22
|
-
mod ruby_internal_apis;
|
data/ext/pf2/src/profile.rs
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
use std::time::{Instant, SystemTime};
|
2
|
-
use std::{collections::HashSet, ptr::null_mut};
|
3
|
-
|
4
|
-
use rb_sys::*;
|
5
|
-
|
6
|
-
use backtrace_sys2::backtrace_create_state;
|
7
|
-
|
8
|
-
use super::backtrace::{Backtrace, BacktraceState};
|
9
|
-
use super::ringbuffer::Ringbuffer;
|
10
|
-
use super::sample::Sample;
|
11
|
-
|
12
|
-
// Capacity large enough to hold 1 second worth of samples for 16 threads
|
13
|
-
// 16 threads * 20 samples per second * 1 second = 320
|
14
|
-
const DEFAULT_RINGBUFFER_CAPACITY: usize = 320;
|
15
|
-
|
16
|
-
#[derive(Debug)]
|
17
|
-
pub struct Profile {
|
18
|
-
pub start_timestamp: SystemTime,
|
19
|
-
pub start_instant: Instant,
|
20
|
-
pub end_instant: Option<Instant>,
|
21
|
-
pub samples: Vec<Sample>,
|
22
|
-
pub temporary_sample_buffer: Ringbuffer,
|
23
|
-
pub backtrace_state: BacktraceState,
|
24
|
-
known_values: HashSet<VALUE>,
|
25
|
-
}
|
26
|
-
|
27
|
-
impl Profile {
|
28
|
-
pub fn new() -> Self {
|
29
|
-
let backtrace_state = unsafe {
|
30
|
-
let ptr = backtrace_create_state(
|
31
|
-
null_mut(),
|
32
|
-
1,
|
33
|
-
Some(Backtrace::backtrace_error_callback),
|
34
|
-
null_mut(),
|
35
|
-
);
|
36
|
-
BacktraceState::new(ptr)
|
37
|
-
};
|
38
|
-
|
39
|
-
Self {
|
40
|
-
start_timestamp: SystemTime::now(),
|
41
|
-
start_instant: Instant::now(),
|
42
|
-
end_instant: None,
|
43
|
-
samples: vec![],
|
44
|
-
temporary_sample_buffer: Ringbuffer::new(DEFAULT_RINGBUFFER_CAPACITY),
|
45
|
-
backtrace_state,
|
46
|
-
known_values: HashSet::new(),
|
47
|
-
}
|
48
|
-
}
|
49
|
-
|
50
|
-
pub fn flush_temporary_sample_buffer(&mut self) {
|
51
|
-
while let Some(sample) = self.temporary_sample_buffer.pop() {
|
52
|
-
self.known_values.insert(sample.ruby_thread);
|
53
|
-
for frame in sample.frames.iter() {
|
54
|
-
if frame == &0 {
|
55
|
-
break;
|
56
|
-
}
|
57
|
-
self.known_values.insert(*frame);
|
58
|
-
}
|
59
|
-
self.samples.push(sample);
|
60
|
-
}
|
61
|
-
}
|
62
|
-
|
63
|
-
pub unsafe fn dmark(&self) {
|
64
|
-
for value in self.known_values.iter() {
|
65
|
-
rb_gc_mark(*value);
|
66
|
-
}
|
67
|
-
self.temporary_sample_buffer.dmark();
|
68
|
-
}
|
69
|
-
}
|
@@ -1,241 +0,0 @@
|
|
1
|
-
use std::collections::HashMap;
|
2
|
-
use std::ffi::{c_char, CStr};
|
3
|
-
use std::hash::Hasher;
|
4
|
-
|
5
|
-
use rb_sys::*;
|
6
|
-
|
7
|
-
use crate::backtrace::Backtrace;
|
8
|
-
use crate::profile::Profile;
|
9
|
-
use crate::util::RTEST;
|
10
|
-
|
11
|
-
#[derive(Debug, Deserialize, Serialize)]
|
12
|
-
pub struct ProfileSerializer {
|
13
|
-
threads: HashMap<ThreadId, ThreadProfile>,
|
14
|
-
}
|
15
|
-
|
16
|
-
type ThreadId = VALUE;
|
17
|
-
|
18
|
-
#[derive(Debug, Deserialize, Serialize)]
|
19
|
-
struct ThreadProfile {
|
20
|
-
thread_id: ThreadId,
|
21
|
-
stack_tree: StackTreeNode,
|
22
|
-
#[serde(rename = "frames")]
|
23
|
-
frame_table: HashMap<FrameTableId, FrameTableEntry>,
|
24
|
-
samples: Vec<ProfileSample>,
|
25
|
-
}
|
26
|
-
|
27
|
-
impl ThreadProfile {
|
28
|
-
fn new(thread_id: ThreadId) -> ThreadProfile {
|
29
|
-
ThreadProfile {
|
30
|
-
thread_id,
|
31
|
-
// The root node
|
32
|
-
stack_tree: StackTreeNode { children: HashMap::new(), node_id: 0, frame_id: 0 },
|
33
|
-
frame_table: HashMap::new(),
|
34
|
-
samples: vec![],
|
35
|
-
}
|
36
|
-
}
|
37
|
-
}
|
38
|
-
|
39
|
-
type StackTreeNodeId = i32;
|
40
|
-
|
41
|
-
// Arbitary value which is used inside StackTreeNode.
|
42
|
-
// This VALUE should not be dereferenced as a pointer; we're merely using its pointer as a unique value.
|
43
|
-
// (Probably should be reconsidered)
|
44
|
-
type FrameTableId = VALUE;
|
45
|
-
|
46
|
-
#[derive(Debug, Deserialize, Serialize)]
|
47
|
-
struct StackTreeNode {
|
48
|
-
// TODO: Maybe a Vec<StackTreeNode> is enough?
|
49
|
-
// There's no particular meaning in using FrameTableId as key
|
50
|
-
children: HashMap<FrameTableId, StackTreeNode>,
|
51
|
-
// An arbitary ID (no particular meaning)
|
52
|
-
node_id: StackTreeNodeId,
|
53
|
-
// ?
|
54
|
-
frame_id: FrameTableId,
|
55
|
-
}
|
56
|
-
|
57
|
-
#[derive(Debug, Deserialize, Serialize)]
|
58
|
-
struct FrameTableEntry {
|
59
|
-
id: FrameTableId,
|
60
|
-
entry_type: FrameTableEntryType,
|
61
|
-
full_label: String,
|
62
|
-
file_name: Option<String>,
|
63
|
-
function_first_lineno: Option<i32>,
|
64
|
-
callsite_lineno: Option<i32>,
|
65
|
-
address: Option<usize>,
|
66
|
-
}
|
67
|
-
|
68
|
-
#[derive(Debug, Deserialize, Serialize)]
|
69
|
-
enum FrameTableEntryType {
|
70
|
-
Ruby,
|
71
|
-
Native,
|
72
|
-
}
|
73
|
-
|
74
|
-
// Represents leaf (末端)
|
75
|
-
#[derive(Debug, Deserialize, Serialize)]
|
76
|
-
struct ProfileSample {
|
77
|
-
elapsed_ns: u128,
|
78
|
-
stack_tree_id: StackTreeNodeId,
|
79
|
-
}
|
80
|
-
|
81
|
-
struct NativeFunctionFrame {
|
82
|
-
pub symbol_name: String,
|
83
|
-
pub address: Option<usize>,
|
84
|
-
}
|
85
|
-
|
86
|
-
impl ProfileSerializer {
|
87
|
-
pub fn serialize(profile: &Profile) -> String {
|
88
|
-
let mut sequence = 1;
|
89
|
-
|
90
|
-
let mut serializer = ProfileSerializer { threads: HashMap::new() };
|
91
|
-
|
92
|
-
unsafe {
|
93
|
-
// Process each sample
|
94
|
-
for sample in profile.samples.iter() {
|
95
|
-
let mut merged_stack: Vec<FrameTableEntry> = vec![];
|
96
|
-
|
97
|
-
// Process C-level stack
|
98
|
-
|
99
|
-
let mut c_stack: Vec<NativeFunctionFrame> = vec![];
|
100
|
-
// Rebuild the original backtrace (including inlined functions) from the PC.
|
101
|
-
for i in 0..sample.c_backtrace_pcs[0] {
|
102
|
-
let pc = sample.c_backtrace_pcs[i + 1];
|
103
|
-
Backtrace::backtrace_syminfo(
|
104
|
-
&profile.backtrace_state,
|
105
|
-
pc,
|
106
|
-
|_pc: usize, symname: *const c_char, symval: usize, _symsize: usize| {
|
107
|
-
if symname.is_null() {
|
108
|
-
c_stack.push(NativeFunctionFrame {
|
109
|
-
symbol_name: "(no symbol information)".to_owned(),
|
110
|
-
address: None,
|
111
|
-
});
|
112
|
-
} else {
|
113
|
-
c_stack.push(NativeFunctionFrame {
|
114
|
-
symbol_name: CStr::from_ptr(symname)
|
115
|
-
.to_str()
|
116
|
-
.unwrap()
|
117
|
-
.to_owned(),
|
118
|
-
address: Some(symval),
|
119
|
-
});
|
120
|
-
}
|
121
|
-
},
|
122
|
-
Some(Backtrace::backtrace_error_callback),
|
123
|
-
);
|
124
|
-
}
|
125
|
-
for frame in c_stack.iter() {
|
126
|
-
if frame.symbol_name.contains("pf2") {
|
127
|
-
// Skip Pf2-related frames
|
128
|
-
continue;
|
129
|
-
}
|
130
|
-
|
131
|
-
merged_stack.push(FrameTableEntry {
|
132
|
-
id: calculate_id_for_c_frame(&frame.symbol_name),
|
133
|
-
entry_type: FrameTableEntryType::Native,
|
134
|
-
full_label: frame.symbol_name.clone(),
|
135
|
-
file_name: None,
|
136
|
-
function_first_lineno: None,
|
137
|
-
callsite_lineno: None,
|
138
|
-
address: frame.address,
|
139
|
-
});
|
140
|
-
}
|
141
|
-
|
142
|
-
// Process Ruby-level stack
|
143
|
-
|
144
|
-
let ruby_stack_depth = sample.line_count;
|
145
|
-
for i in 0..ruby_stack_depth {
|
146
|
-
let frame: VALUE = sample.frames[i as usize];
|
147
|
-
let lineno: i32 = sample.linenos[i as usize];
|
148
|
-
let address: Option<usize> = {
|
149
|
-
let cme = frame
|
150
|
-
as *mut crate::ruby_internal_apis::rb_callable_method_entry_struct;
|
151
|
-
let cme = &*cme;
|
152
|
-
|
153
|
-
if (*(cme.def)).type_ == 1 {
|
154
|
-
// The cme is a Cfunc
|
155
|
-
Some((*(cme.def)).cfunc.func as usize)
|
156
|
-
} else {
|
157
|
-
// The cme is an ISeq (Ruby code) or some other type
|
158
|
-
None
|
159
|
-
}
|
160
|
-
};
|
161
|
-
let mut frame_full_label: VALUE = rb_profile_frame_full_label(frame);
|
162
|
-
let frame_full_label: String = if RTEST(frame_full_label) {
|
163
|
-
CStr::from_ptr(rb_string_value_cstr(&mut frame_full_label))
|
164
|
-
.to_str()
|
165
|
-
.unwrap()
|
166
|
-
.to_owned()
|
167
|
-
} else {
|
168
|
-
"(unknown)".to_owned()
|
169
|
-
};
|
170
|
-
let mut frame_path: VALUE = rb_profile_frame_path(frame);
|
171
|
-
let frame_path: String = if RTEST(frame_path) {
|
172
|
-
CStr::from_ptr(rb_string_value_cstr(&mut frame_path))
|
173
|
-
.to_str()
|
174
|
-
.unwrap()
|
175
|
-
.to_owned()
|
176
|
-
} else {
|
177
|
-
"(unknown)".to_owned()
|
178
|
-
};
|
179
|
-
let frame_first_lineno: VALUE = rb_profile_frame_first_lineno(frame);
|
180
|
-
let frame_first_lineno: Option<i32> = if RTEST(frame_first_lineno) {
|
181
|
-
Some(rb_num2int(frame_first_lineno).try_into().unwrap())
|
182
|
-
} else {
|
183
|
-
None
|
184
|
-
};
|
185
|
-
merged_stack.push(FrameTableEntry {
|
186
|
-
id: frame,
|
187
|
-
entry_type: FrameTableEntryType::Ruby,
|
188
|
-
full_label: frame_full_label,
|
189
|
-
file_name: Some(frame_path),
|
190
|
-
function_first_lineno: frame_first_lineno,
|
191
|
-
callsite_lineno: Some(lineno),
|
192
|
-
address,
|
193
|
-
});
|
194
|
-
}
|
195
|
-
|
196
|
-
// Find the Thread profile for this sample
|
197
|
-
let thread_serializer = serializer
|
198
|
-
.threads
|
199
|
-
.entry(sample.ruby_thread)
|
200
|
-
.or_insert(ThreadProfile::new(sample.ruby_thread));
|
201
|
-
|
202
|
-
// Stack frames, shallow to deep
|
203
|
-
let mut stack_tree = &mut thread_serializer.stack_tree;
|
204
|
-
|
205
|
-
while let Some(frame_table_entry) = merged_stack.pop() {
|
206
|
-
stack_tree = stack_tree.children.entry(frame_table_entry.id).or_insert({
|
207
|
-
let node = StackTreeNode {
|
208
|
-
children: HashMap::new(),
|
209
|
-
node_id: sequence,
|
210
|
-
frame_id: frame_table_entry.id,
|
211
|
-
};
|
212
|
-
sequence += 1;
|
213
|
-
node
|
214
|
-
});
|
215
|
-
|
216
|
-
if merged_stack.is_empty() {
|
217
|
-
// This is the leaf node, record a Sample
|
218
|
-
let elapsed_ns = (sample.timestamp - profile.start_instant).as_nanos();
|
219
|
-
thread_serializer
|
220
|
-
.samples
|
221
|
-
.push(ProfileSample { elapsed_ns, stack_tree_id: stack_tree.node_id });
|
222
|
-
}
|
223
|
-
|
224
|
-
// Register frame metadata to frame table, if not registered yet
|
225
|
-
thread_serializer
|
226
|
-
.frame_table
|
227
|
-
.entry(frame_table_entry.id)
|
228
|
-
.or_insert(frame_table_entry);
|
229
|
-
}
|
230
|
-
}
|
231
|
-
}
|
232
|
-
|
233
|
-
serde_json::to_string(&serializer).unwrap()
|
234
|
-
}
|
235
|
-
}
|
236
|
-
|
237
|
-
fn calculate_id_for_c_frame<T: std::hash::Hash>(t: &T) -> FrameTableId {
|
238
|
-
let mut s = std::collections::hash_map::DefaultHasher::new();
|
239
|
-
t.hash(&mut s);
|
240
|
-
s.finish()
|
241
|
-
}
|
data/ext/pf2/src/ringbuffer.rs
DELETED
@@ -1,150 +0,0 @@
|
|
1
|
-
use crate::sample::Sample;
|
2
|
-
|
3
|
-
#[derive(Debug)]
|
4
|
-
pub struct Ringbuffer {
|
5
|
-
capacity: usize,
|
6
|
-
buffer: Vec<Option<Sample>>,
|
7
|
-
read_index: usize,
|
8
|
-
write_index: usize,
|
9
|
-
}
|
10
|
-
|
11
|
-
#[derive(Debug, PartialEq)]
|
12
|
-
pub enum RingbufferError {
|
13
|
-
Full,
|
14
|
-
}
|
15
|
-
|
16
|
-
impl Ringbuffer {
|
17
|
-
pub fn new(capacity: usize) -> Self {
|
18
|
-
Self {
|
19
|
-
capacity,
|
20
|
-
buffer: std::iter::repeat_with(|| None).take(capacity + 1).collect::<Vec<_>>(),
|
21
|
-
read_index: 0,
|
22
|
-
write_index: 0,
|
23
|
-
}
|
24
|
-
}
|
25
|
-
|
26
|
-
// async-signal-safe
|
27
|
-
pub fn push(&mut self, sample: Sample) -> Result<(), RingbufferError> {
|
28
|
-
let next = (self.write_index + 1) % (self.capacity + 1);
|
29
|
-
if next == self.read_index {
|
30
|
-
return Err(RingbufferError::Full);
|
31
|
-
}
|
32
|
-
self.buffer[self.write_index] = Some(sample);
|
33
|
-
self.write_index = next;
|
34
|
-
Ok(())
|
35
|
-
}
|
36
|
-
|
37
|
-
pub fn pop(&mut self) -> Option<Sample> {
|
38
|
-
if self.read_index == self.write_index {
|
39
|
-
return None;
|
40
|
-
}
|
41
|
-
let sample = self.buffer[self.read_index].take();
|
42
|
-
self.read_index = (self.read_index + 1) % (self.capacity + 1);
|
43
|
-
sample
|
44
|
-
}
|
45
|
-
|
46
|
-
// This will call rb_gc_mark() for capacity * Sample::MAX_STACK_DEPTH * 2 times, which is a lot!
|
47
|
-
pub fn dmark(&self) {
|
48
|
-
for sample in self.buffer.iter().flatten() {
|
49
|
-
unsafe {
|
50
|
-
sample.dmark();
|
51
|
-
}
|
52
|
-
}
|
53
|
-
}
|
54
|
-
}
|
55
|
-
|
56
|
-
#[cfg(test)]
|
57
|
-
mod tests {
|
58
|
-
use super::*;
|
59
|
-
use std::time::Instant;
|
60
|
-
|
61
|
-
#[test]
|
62
|
-
fn test_ringbuffer() {
|
63
|
-
let mut ringbuffer = Ringbuffer::new(2);
|
64
|
-
assert_eq!(ringbuffer.pop(), None);
|
65
|
-
|
66
|
-
let sample1 = Sample {
|
67
|
-
ruby_thread: 1,
|
68
|
-
timestamp: Instant::now(),
|
69
|
-
line_count: 0,
|
70
|
-
frames: [0; 500],
|
71
|
-
linenos: [0; 500],
|
72
|
-
c_backtrace_pcs: [0; 1001],
|
73
|
-
};
|
74
|
-
let sample2 = Sample {
|
75
|
-
ruby_thread: 2,
|
76
|
-
timestamp: Instant::now(),
|
77
|
-
line_count: 0,
|
78
|
-
frames: [0; 500],
|
79
|
-
linenos: [0; 500],
|
80
|
-
c_backtrace_pcs: [0; 1001],
|
81
|
-
};
|
82
|
-
|
83
|
-
ringbuffer.push(sample1).unwrap();
|
84
|
-
ringbuffer.push(sample2).unwrap();
|
85
|
-
|
86
|
-
assert_eq!(ringbuffer.pop().unwrap().ruby_thread, 1);
|
87
|
-
assert_eq!(ringbuffer.pop().unwrap().ruby_thread, 2);
|
88
|
-
assert_eq!(ringbuffer.pop(), None);
|
89
|
-
}
|
90
|
-
|
91
|
-
#[test]
|
92
|
-
fn test_ringbuffer_full() {
|
93
|
-
let mut ringbuffer = Ringbuffer::new(1);
|
94
|
-
let sample1 = Sample {
|
95
|
-
ruby_thread: 1,
|
96
|
-
timestamp: Instant::now(),
|
97
|
-
line_count: 0,
|
98
|
-
frames: [0; 500],
|
99
|
-
linenos: [0; 500],
|
100
|
-
c_backtrace_pcs: [0; 1001],
|
101
|
-
};
|
102
|
-
let sample2 = Sample {
|
103
|
-
ruby_thread: 2,
|
104
|
-
timestamp: Instant::now(),
|
105
|
-
line_count: 0,
|
106
|
-
frames: [0; 500],
|
107
|
-
linenos: [0; 500],
|
108
|
-
c_backtrace_pcs: [0; 1001],
|
109
|
-
};
|
110
|
-
|
111
|
-
ringbuffer.push(sample1).unwrap();
|
112
|
-
assert_eq!(ringbuffer.push(sample2), Err(RingbufferError::Full));
|
113
|
-
}
|
114
|
-
|
115
|
-
#[test]
|
116
|
-
fn test_ringbuffer_write_a_lot() {
|
117
|
-
let mut ringbuffer = Ringbuffer::new(2);
|
118
|
-
let sample1 = Sample {
|
119
|
-
ruby_thread: 1,
|
120
|
-
timestamp: Instant::now(),
|
121
|
-
line_count: 0,
|
122
|
-
frames: [0; 500],
|
123
|
-
linenos: [0; 500],
|
124
|
-
c_backtrace_pcs: [0; 1001],
|
125
|
-
};
|
126
|
-
let sample2 = Sample {
|
127
|
-
ruby_thread: 2,
|
128
|
-
timestamp: Instant::now(),
|
129
|
-
line_count: 0,
|
130
|
-
frames: [0; 500],
|
131
|
-
linenos: [0; 500],
|
132
|
-
c_backtrace_pcs: [0; 1001],
|
133
|
-
};
|
134
|
-
let sample3 = Sample {
|
135
|
-
ruby_thread: 3,
|
136
|
-
timestamp: Instant::now(),
|
137
|
-
line_count: 0,
|
138
|
-
frames: [0; 500],
|
139
|
-
linenos: [0; 500],
|
140
|
-
c_backtrace_pcs: [0; 1001],
|
141
|
-
};
|
142
|
-
|
143
|
-
ringbuffer.push(sample1).unwrap();
|
144
|
-
ringbuffer.pop().unwrap();
|
145
|
-
ringbuffer.push(sample2).unwrap();
|
146
|
-
ringbuffer.pop().unwrap();
|
147
|
-
ringbuffer.push(sample3).unwrap();
|
148
|
-
assert_eq!(ringbuffer.pop().unwrap().ruby_thread, 3);
|
149
|
-
}
|
150
|
-
}
|