pf2 0.5.1 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +4 -4
- data/ext/pf2/src/lib.rs +1 -0
- data/ext/pf2/src/profile.rs +7 -3
- data/ext/pf2/src/profile_serializer.rs +1 -1
- data/ext/pf2/src/serialization/profile.rs +46 -0
- data/ext/pf2/src/serialization/serializer.rs +146 -0
- data/ext/pf2/src/serialization.rs +2 -0
- data/ext/pf2/src/session/configuration.rs +9 -1
- data/ext/pf2/src/session/ruby_object.rs +7 -3
- data/ext/pf2/src/session.rs +23 -7
- data/ext/pf2/src/signal_scheduler.rs +8 -1
- data/ext/pf2/src/timer_thread_scheduler.rs +7 -1
- data/lib/pf2/cli.rb +1 -1
- data/lib/pf2/reporter/firefox_profiler.rb +395 -0
- data/lib/pf2/reporter.rb +2 -390
- data/lib/pf2/serve.rb +1 -1
- data/lib/pf2/version.rb +1 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1aa1dd1d3caa1dd9327382e7de0a44b2c0ff4555a9dff8fdc7244f61c3a13405
|
4
|
+
data.tar.gz: c50895ea4d8285cc9bb223d575cbec7042aacd119a1390b836b818232ef7e214
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b5db496a58016e11203334b71e80e7a4c0d7d0dce6b2cb288884da4aecd8820d8915fd0b9f4ebd546802c314d621f69da23c474d254bff17ec3931cb8f7604b
|
7
|
+
data.tar.gz: 5e3a8fcf25be2dbf28cf8c36e03839cbeed13aa8d944e43991672a39f06fe16f6eae07aab5cd282c3237171bfff94b39a8abb176153a091452c3177266eb1364
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.6.0] - 2024-07-15
|
4
|
+
|
5
|
+
### Changed
|
6
|
+
|
7
|
+
- The default sampling interval is now 9 ms (was originally 49 ms).
|
8
|
+
- It is intentional that the default is not 10 (or 50) ms - this is to avoid lockstep sampling.
|
9
|
+
- BREAKING: `Pf2::Reporter` has been moved to `Pf2::Reporter::FirefoxProfiler`.
|
10
|
+
- This is to make space for other planned reporters.
|
11
|
+
|
12
|
+
|
13
|
+
## [0.5.2] - 2024-07-13
|
14
|
+
|
15
|
+
### Fixed
|
16
|
+
|
17
|
+
- Properly default to TimerThread scheduler on non-Linux environments.
|
18
|
+
|
3
19
|
|
4
20
|
## [0.5.1] - 2024-03-25
|
5
21
|
|
data/README.md
CHANGED
@@ -58,7 +58,7 @@ File.write("my_program.pf2profile", profile)
|
|
58
58
|
Profiles can be visualized using the [Firefox Profiler](https://profiler.firefox.com/).
|
59
59
|
|
60
60
|
```console
|
61
|
-
$ pf2 -o report.json my_program.pf2profile
|
61
|
+
$ pf2 report -o report.json my_program.pf2profile
|
62
62
|
```
|
63
63
|
|
64
64
|
### Configuration
|
@@ -67,7 +67,7 @@ Pf2 accepts the following configuration keys:
|
|
67
67
|
|
68
68
|
```rb
|
69
69
|
Pf2.start(
|
70
|
-
interval_ms:
|
70
|
+
interval_ms: 9, # Integer: The sampling interval in milliseconds (default: 9)
|
71
71
|
time_mode: :cpu, # `:cpu` or `:wall`: The sampling timer's mode
|
72
72
|
# (default: `:cpu` for SignalScheduler, `:wall` for TimerThreadScheduler)
|
73
73
|
threads: [th1, th2], # `Array<Thread>` | `:all`: A list of Ruby Threads to be tracked.
|
@@ -108,9 +108,9 @@ Schedulers determine when to execute sample collection, based on configuration (
|
|
108
108
|
|
109
109
|
#### SignalScheduler (Linux-only)
|
110
110
|
|
111
|
-
The first is the `SignalScheduler`, based on POSIX timers. Pf2 will use this scheduler when possible. SignalScheduler creates a POSIX timer for each Ruby Thread (the underlying pthread to be more accurate) using `timer_create(
|
111
|
+
The first is the `SignalScheduler`, based on POSIX timers. Pf2 will use this scheduler when possible. SignalScheduler creates a POSIX timer for each Ruby Thread (the underlying pthread to be more accurate) using `timer_create(2)`. This leaves the actual time-keeping to the OS, which is capable of tracking accurate per-thread CPU time usage.
|
112
112
|
|
113
|
-
When the specified interval has arrived (the timer has _expired_), the OS delivers us a SIGALRM (note: Unlike `setitimer(2)`, `timer_create(
|
113
|
+
When the specified interval has arrived (the timer has _expired_), the OS delivers us a SIGALRM (note: Unlike `setitimer(2)`, `timer_create(2)` allows us to choose which signal to be delivered, and Pf2 uses SIGALRM regardless of time mode). This is why the scheduler is named SignalScheduler.
|
114
114
|
|
115
115
|
Signals are directed to Ruby Threads' underlying pthread, effectively "pausing" the Thread's activity. This routing is done using `SIGEV_THREAD_ID`, which is a Linux-only feature. Sample collection is done in the signal handler, which is expected to be more _accurate_, capturing the paused Thread's activity.
|
116
116
|
|
data/ext/pf2/src/lib.rs
CHANGED
data/ext/pf2/src/profile.rs
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
use std::time::Instant;
|
1
|
+
use std::time::{Instant, SystemTime};
|
2
2
|
use std::{collections::HashSet, ptr::null_mut};
|
3
3
|
|
4
4
|
use rb_sys::*;
|
@@ -15,7 +15,9 @@ const DEFAULT_RINGBUFFER_CAPACITY: usize = 320;
|
|
15
15
|
|
16
16
|
#[derive(Debug)]
|
17
17
|
pub struct Profile {
|
18
|
-
pub start_timestamp:
|
18
|
+
pub start_timestamp: SystemTime,
|
19
|
+
pub start_instant: Instant,
|
20
|
+
pub end_instant: Option<Instant>,
|
19
21
|
pub samples: Vec<Sample>,
|
20
22
|
pub temporary_sample_buffer: Ringbuffer,
|
21
23
|
pub backtrace_state: BacktraceState,
|
@@ -35,7 +37,9 @@ impl Profile {
|
|
35
37
|
};
|
36
38
|
|
37
39
|
Self {
|
38
|
-
start_timestamp:
|
40
|
+
start_timestamp: SystemTime::now(),
|
41
|
+
start_instant: Instant::now(),
|
42
|
+
end_instant: None,
|
39
43
|
samples: vec![],
|
40
44
|
temporary_sample_buffer: Ringbuffer::new(DEFAULT_RINGBUFFER_CAPACITY),
|
41
45
|
backtrace_state,
|
@@ -221,7 +221,7 @@ impl ProfileSerializer {
|
|
221
221
|
|
222
222
|
if merged_stack.is_empty() {
|
223
223
|
// This is the leaf node, record a Sample
|
224
|
-
let elapsed_ns = (sample.timestamp - profile.
|
224
|
+
let elapsed_ns = (sample.timestamp - profile.start_instant).as_nanos();
|
225
225
|
thread_serializer.samples.push(ProfileSample {
|
226
226
|
elapsed_ns,
|
227
227
|
stack_tree_id: stack_tree.node_id,
|
@@ -0,0 +1,46 @@
|
|
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 ruby_thread_id: Option<u64>,
|
20
|
+
}
|
21
|
+
|
22
|
+
/// Location represents a location (line) in the source code when a sample was captured.
|
23
|
+
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
24
|
+
pub struct Location {
|
25
|
+
pub function_index: FunctionIndex,
|
26
|
+
pub lineno: i32,
|
27
|
+
pub address: Option<usize>,
|
28
|
+
}
|
29
|
+
|
30
|
+
/// Function represents a Ruby method or a C function in the profile.
|
31
|
+
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
32
|
+
pub struct Function {
|
33
|
+
pub implementation: FunctionImplementation,
|
34
|
+
pub name: Option<String>, // unique key
|
35
|
+
pub filename: Option<String>,
|
36
|
+
/// The first line number in the method/function definition.
|
37
|
+
/// For the actual location (line) which was hit during sample capture, refer to `Location.lineno`.
|
38
|
+
pub start_lineno: Option<i32>,
|
39
|
+
pub start_address: Option<usize>,
|
40
|
+
}
|
41
|
+
|
42
|
+
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
43
|
+
pub enum FunctionImplementation {
|
44
|
+
Ruby,
|
45
|
+
C,
|
46
|
+
}
|
@@ -0,0 +1,146 @@
|
|
1
|
+
use std::ffi::CStr;
|
2
|
+
|
3
|
+
use rb_sys::*;
|
4
|
+
|
5
|
+
use super::profile::{Function, FunctionImplementation, Location, LocationIndex, Profile, Sample};
|
6
|
+
use crate::util::RTEST;
|
7
|
+
|
8
|
+
pub struct ProfileSerializer2 {
|
9
|
+
profile: Profile,
|
10
|
+
}
|
11
|
+
|
12
|
+
impl ProfileSerializer2 {
|
13
|
+
pub fn new() -> ProfileSerializer2 {
|
14
|
+
ProfileSerializer2 {
|
15
|
+
profile: Profile {
|
16
|
+
start_timestamp_ns: 0,
|
17
|
+
duration_ns: 0,
|
18
|
+
samples: vec![],
|
19
|
+
locations: vec![],
|
20
|
+
functions: vec![],
|
21
|
+
},
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
pub fn serialize(&mut self, source: &crate::profile::Profile) -> String {
|
26
|
+
// Fill in meta fields
|
27
|
+
self.profile.start_timestamp_ns = source
|
28
|
+
.start_timestamp
|
29
|
+
.duration_since(std::time::UNIX_EPOCH)
|
30
|
+
.unwrap()
|
31
|
+
.as_nanos();
|
32
|
+
self.profile.duration_ns = source
|
33
|
+
.end_instant
|
34
|
+
.unwrap()
|
35
|
+
.duration_since(source.start_instant)
|
36
|
+
.as_nanos();
|
37
|
+
|
38
|
+
// Create a Sample for each sample collected
|
39
|
+
for sample in source.samples.iter() {
|
40
|
+
let mut stack: Vec<LocationIndex> = vec![];
|
41
|
+
|
42
|
+
// Iterate over the Ruby stack
|
43
|
+
let ruby_stack_depth = sample.line_count;
|
44
|
+
for i in 0..ruby_stack_depth {
|
45
|
+
let frame: VALUE = sample.frames[i as usize];
|
46
|
+
let lineno: i32 = sample.linenos[i as usize];
|
47
|
+
|
48
|
+
// Get the Location corresponding to the frame.
|
49
|
+
let location_index = self.process_ruby_frame(frame, lineno);
|
50
|
+
|
51
|
+
stack.push(location_index);
|
52
|
+
}
|
53
|
+
|
54
|
+
self.profile.samples.push(Sample {
|
55
|
+
stack,
|
56
|
+
ruby_thread_id: Some(sample.ruby_thread),
|
57
|
+
});
|
58
|
+
}
|
59
|
+
|
60
|
+
serde_json::to_string(&self.profile).unwrap()
|
61
|
+
}
|
62
|
+
|
63
|
+
/// Process a collected Ruby frame.
|
64
|
+
/// Calling this method will modify `self.profile` in place.
|
65
|
+
///
|
66
|
+
/// Returns the index of the location in `locations`.
|
67
|
+
fn process_ruby_frame(&mut self, frame: VALUE, lineno: i32) -> LocationIndex {
|
68
|
+
// Build a Function corresponding to the frame, and get the index in `functions`
|
69
|
+
let function = Self::extract_function_from_frame(frame);
|
70
|
+
let function_index = match self
|
71
|
+
.profile
|
72
|
+
.functions
|
73
|
+
.iter_mut()
|
74
|
+
.position(|f| *f == function)
|
75
|
+
{
|
76
|
+
Some(index) => index,
|
77
|
+
None => {
|
78
|
+
self.profile.functions.push(function);
|
79
|
+
self.profile.functions.len() - 1
|
80
|
+
}
|
81
|
+
};
|
82
|
+
|
83
|
+
// Build a Location based on (1) the Function and (2) the actual line hit during sampling.
|
84
|
+
let location = Location {
|
85
|
+
function_index,
|
86
|
+
lineno,
|
87
|
+
address: None,
|
88
|
+
};
|
89
|
+
// Get the index of the location in `locations`
|
90
|
+
match self
|
91
|
+
.profile
|
92
|
+
.locations
|
93
|
+
.iter_mut()
|
94
|
+
.position(|l| *l == location)
|
95
|
+
{
|
96
|
+
Some(index) => index,
|
97
|
+
None => {
|
98
|
+
self.profile.locations.push(location);
|
99
|
+
self.profile.locations.len() - 1
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}
|
103
|
+
|
104
|
+
fn extract_function_from_frame(frame: VALUE) -> Function {
|
105
|
+
unsafe {
|
106
|
+
let mut frame_full_label: VALUE = rb_profile_frame_full_label(frame);
|
107
|
+
let frame_full_label: Option<String> = if RTEST(frame_full_label) {
|
108
|
+
Some(
|
109
|
+
CStr::from_ptr(rb_string_value_cstr(&mut frame_full_label))
|
110
|
+
.to_str()
|
111
|
+
.unwrap()
|
112
|
+
.to_owned(),
|
113
|
+
)
|
114
|
+
} else {
|
115
|
+
None
|
116
|
+
};
|
117
|
+
|
118
|
+
let mut frame_path: VALUE = rb_profile_frame_path(frame);
|
119
|
+
let frame_path: Option<String> = if RTEST(frame_path) {
|
120
|
+
Some(
|
121
|
+
CStr::from_ptr(rb_string_value_cstr(&mut frame_path))
|
122
|
+
.to_str()
|
123
|
+
.unwrap()
|
124
|
+
.to_owned(),
|
125
|
+
)
|
126
|
+
} else {
|
127
|
+
None
|
128
|
+
};
|
129
|
+
|
130
|
+
let frame_first_lineno: VALUE = rb_profile_frame_first_lineno(frame);
|
131
|
+
let frame_first_lineno: Option<i32> = if RTEST(frame_first_lineno) {
|
132
|
+
Some(rb_num2int(frame_first_lineno).try_into().unwrap())
|
133
|
+
} else {
|
134
|
+
None
|
135
|
+
};
|
136
|
+
|
137
|
+
Function {
|
138
|
+
implementation: FunctionImplementation::Ruby,
|
139
|
+
name: frame_full_label,
|
140
|
+
filename: frame_path,
|
141
|
+
start_lineno: frame_first_lineno,
|
142
|
+
start_address: None,
|
143
|
+
}
|
144
|
+
}
|
145
|
+
}
|
146
|
+
}
|
@@ -6,9 +6,16 @@ use rb_sys::*;
|
|
6
6
|
|
7
7
|
use crate::util::cstr;
|
8
8
|
|
9
|
+
#[cfg(target_os = "linux")]
|
9
10
|
pub const DEFAULT_SCHEDULER: Scheduler = Scheduler::Signal;
|
10
|
-
|
11
|
+
#[cfg(target_os = "linux")]
|
11
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);
|
12
19
|
|
13
20
|
#[derive(Clone, Debug)]
|
14
21
|
pub struct Configuration {
|
@@ -16,6 +23,7 @@ pub struct Configuration {
|
|
16
23
|
pub interval: Duration,
|
17
24
|
pub time_mode: TimeMode,
|
18
25
|
pub target_ruby_threads: Threads,
|
26
|
+
pub use_experimental_serializer: bool,
|
19
27
|
}
|
20
28
|
|
21
29
|
#[derive(Clone, Debug, PartialEq)]
|
@@ -1,7 +1,7 @@
|
|
1
1
|
use std::ffi::{c_int, c_void};
|
2
2
|
use std::mem;
|
3
3
|
use std::mem::ManuallyDrop;
|
4
|
-
use std::ptr::null_mut;
|
4
|
+
use std::ptr::{addr_of, null_mut};
|
5
5
|
|
6
6
|
use rb_sys::*;
|
7
7
|
|
@@ -43,7 +43,7 @@ impl SessionRubyObject {
|
|
43
43
|
// Extract the SessionRubyObject struct from a Ruby object
|
44
44
|
unsafe fn get_struct_from(obj: VALUE) -> ManuallyDrop<Box<Self>> {
|
45
45
|
unsafe {
|
46
|
-
let ptr = rb_check_typeddata(obj,
|
46
|
+
let ptr = rb_check_typeddata(obj, addr_of!(RBDATA));
|
47
47
|
ManuallyDrop::new(Box::from_raw(ptr as *mut SessionRubyObject))
|
48
48
|
}
|
49
49
|
}
|
@@ -55,7 +55,11 @@ impl SessionRubyObject {
|
|
55
55
|
let rb_mPf2: VALUE = rb_define_module(cstr!("Pf2"));
|
56
56
|
let rb_cSession = rb_define_class_under(rb_mPf2, cstr!("Session"), rb_cObject);
|
57
57
|
// Wrap the struct into a Ruby object
|
58
|
-
rb_data_typed_object_wrap(
|
58
|
+
rb_data_typed_object_wrap(
|
59
|
+
rb_cSession,
|
60
|
+
Box::into_raw(obj) as *mut c_void,
|
61
|
+
addr_of!(RBDATA),
|
62
|
+
)
|
59
63
|
}
|
60
64
|
|
61
65
|
unsafe extern "C" fn dmark(ptr: *mut c_void) {
|
data/ext/pf2/src/session.rs
CHANGED
@@ -38,7 +38,7 @@ impl Session {
|
|
38
38
|
unsafe {
|
39
39
|
rb_scan_args(argc, argv, cstr!(":"), &kwargs);
|
40
40
|
};
|
41
|
-
let mut kwargs_values: [VALUE;
|
41
|
+
let mut kwargs_values: [VALUE; 5] = [Qnil.into(); 5];
|
42
42
|
unsafe {
|
43
43
|
rb_get_kwargs(
|
44
44
|
kwargs,
|
@@ -47,10 +47,11 @@ impl Session {
|
|
47
47
|
rb_intern(cstr!("threads")),
|
48
48
|
rb_intern(cstr!("time_mode")),
|
49
49
|
rb_intern(cstr!("scheduler")),
|
50
|
+
rb_intern(cstr!("use_experimental_serializer")),
|
50
51
|
]
|
51
52
|
.as_mut_ptr(),
|
52
53
|
0,
|
53
|
-
|
54
|
+
5,
|
54
55
|
kwargs_values.as_mut_ptr(),
|
55
56
|
);
|
56
57
|
};
|
@@ -59,12 +60,15 @@ impl Session {
|
|
59
60
|
let threads = Self::parse_option_threads(kwargs_values[1]);
|
60
61
|
let time_mode = Self::parse_option_time_mode(kwargs_values[2]);
|
61
62
|
let scheduler = Self::parse_option_scheduler(kwargs_values[3]);
|
63
|
+
let use_experimental_serializer =
|
64
|
+
Self::parse_option_use_experimental_serializer(kwargs_values[4]);
|
62
65
|
|
63
66
|
let configuration = Configuration {
|
64
67
|
scheduler,
|
65
68
|
interval,
|
66
69
|
target_ruby_threads: threads.clone(),
|
67
70
|
time_mode,
|
71
|
+
use_experimental_serializer,
|
68
72
|
};
|
69
73
|
|
70
74
|
match configuration.validate() {
|
@@ -93,12 +97,17 @@ impl Session {
|
|
93
97
|
)),
|
94
98
|
};
|
95
99
|
|
100
|
+
let running = Arc::new(AtomicBool::new(false));
|
101
|
+
|
96
102
|
let new_thread_watcher = match threads {
|
97
103
|
configuration::Threads::All => {
|
98
104
|
let scheduler = Arc::clone(&scheduler);
|
105
|
+
let running = Arc::clone(&running);
|
99
106
|
Some(NewThreadWatcher::watch(move |thread: VALUE| {
|
100
|
-
|
101
|
-
|
107
|
+
if running.load(Ordering::Relaxed) {
|
108
|
+
log::debug!("New Ruby thread detected: {:?}", thread);
|
109
|
+
scheduler.on_new_thread(thread);
|
110
|
+
}
|
102
111
|
}))
|
103
112
|
}
|
104
113
|
configuration::Threads::Targeted(_) => None,
|
@@ -108,7 +117,7 @@ impl Session {
|
|
108
117
|
configuration,
|
109
118
|
scheduler,
|
110
119
|
profile,
|
111
|
-
running
|
120
|
+
running,
|
112
121
|
new_thread_watcher,
|
113
122
|
}
|
114
123
|
}
|
@@ -122,10 +131,10 @@ impl Session {
|
|
122
131
|
let interval_ms = unsafe { rb_num2long(value) };
|
123
132
|
Duration::from_millis(interval_ms.try_into().unwrap_or_else(|_| {
|
124
133
|
eprintln!(
|
125
|
-
"[Pf2] Warning: Specified interval ({}) is not valid. Using default value (
|
134
|
+
"[Pf2] Warning: Specified interval ({}) is not valid. Using default value (9ms).",
|
126
135
|
interval_ms
|
127
136
|
);
|
128
|
-
|
137
|
+
9
|
129
138
|
}))
|
130
139
|
}
|
131
140
|
|
@@ -202,6 +211,13 @@ impl Session {
|
|
202
211
|
scheduler
|
203
212
|
}
|
204
213
|
|
214
|
+
fn parse_option_use_experimental_serializer(value: VALUE) -> bool {
|
215
|
+
if value == Qundef as VALUE {
|
216
|
+
return false;
|
217
|
+
}
|
218
|
+
RTEST(value)
|
219
|
+
}
|
220
|
+
|
205
221
|
pub fn start(&mut self) -> VALUE {
|
206
222
|
self.running.store(true, Ordering::Relaxed);
|
207
223
|
self.start_profile_buffer_flusher_thread();
|
@@ -5,6 +5,7 @@ use crate::profile_serializer::ProfileSerializer;
|
|
5
5
|
use crate::ruby_internal_apis::rb_thread_getcpuclockid;
|
6
6
|
use crate::sample::Sample;
|
7
7
|
use crate::scheduler::Scheduler;
|
8
|
+
use crate::serialization::serializer::ProfileSerializer2;
|
8
9
|
use crate::session::configuration::{self, Configuration};
|
9
10
|
|
10
11
|
use core::panic;
|
@@ -46,6 +47,7 @@ impl Scheduler for SignalScheduler {
|
|
46
47
|
match self.profile.try_write() {
|
47
48
|
Ok(mut profile) => {
|
48
49
|
profile.flush_temporary_sample_buffer();
|
50
|
+
profile.end_instant = Some(std::time::Instant::now());
|
49
51
|
}
|
50
52
|
Err(_) => {
|
51
53
|
println!("[pf2 ERROR] stop: Failed to acquire profile lock.");
|
@@ -56,7 +58,11 @@ impl Scheduler for SignalScheduler {
|
|
56
58
|
let profile = self.profile.try_read().unwrap();
|
57
59
|
log::debug!("Number of samples: {}", profile.samples.len());
|
58
60
|
|
59
|
-
let serialized =
|
61
|
+
let serialized = if self.configuration.use_experimental_serializer {
|
62
|
+
ProfileSerializer2::new().serialize(&profile)
|
63
|
+
} else {
|
64
|
+
ProfileSerializer::serialize(&profile)
|
65
|
+
};
|
60
66
|
let serialized = CString::new(serialized).unwrap();
|
61
67
|
unsafe { rb_str_new_cstr(serialized.as_ptr()) }
|
62
68
|
}
|
@@ -103,6 +109,7 @@ impl SignalScheduler {
|
|
103
109
|
if err != 0 {
|
104
110
|
panic!("sigaction failed: {}", err);
|
105
111
|
}
|
112
|
+
log::debug!("Signal handler installed");
|
106
113
|
}
|
107
114
|
|
108
115
|
// Respond to the signal and collect a sample.
|
@@ -12,6 +12,7 @@ use crate::profile::Profile;
|
|
12
12
|
use crate::profile_serializer::ProfileSerializer;
|
13
13
|
use crate::sample::Sample;
|
14
14
|
use crate::scheduler::Scheduler;
|
15
|
+
use crate::serialization::serializer::ProfileSerializer2;
|
15
16
|
use crate::session::configuration::{self, Configuration};
|
16
17
|
use crate::util::*;
|
17
18
|
|
@@ -61,6 +62,7 @@ impl Scheduler for TimerThreadScheduler {
|
|
61
62
|
match self.profile.try_write() {
|
62
63
|
Ok(mut profile) => {
|
63
64
|
profile.flush_temporary_sample_buffer();
|
65
|
+
profile.end_instant = Some(std::time::Instant::now());
|
64
66
|
}
|
65
67
|
Err(_) => {
|
66
68
|
println!("[pf2 ERROR] stop: Failed to acquire profile lock.");
|
@@ -71,7 +73,11 @@ impl Scheduler for TimerThreadScheduler {
|
|
71
73
|
let profile = self.profile.try_read().unwrap();
|
72
74
|
log::debug!("Number of samples: {}", profile.samples.len());
|
73
75
|
|
74
|
-
let serialized =
|
76
|
+
let serialized = if self.configuration.use_experimental_serializer {
|
77
|
+
ProfileSerializer2::new().serialize(&profile)
|
78
|
+
} else {
|
79
|
+
ProfileSerializer::serialize(&profile)
|
80
|
+
};
|
75
81
|
let serialized = CString::new(serialized).unwrap();
|
76
82
|
unsafe { rb_str_new_cstr(serialized.as_ptr()) }
|
77
83
|
}
|
data/lib/pf2/cli.rb
CHANGED
@@ -55,7 +55,7 @@ module Pf2
|
|
55
55
|
option_parser.parse!(argv)
|
56
56
|
|
57
57
|
profile = JSON.parse(File.read(argv[0]), symbolize_names: true, max_nesting: false)
|
58
|
-
report = JSON.generate(Pf2::Reporter.new(profile).emit)
|
58
|
+
report = JSON.generate(Pf2::Reporter::FirefoxProfiler.new(profile).emit)
|
59
59
|
|
60
60
|
if options[:output_file]
|
61
61
|
File.write(options[:output_file], report)
|