pf2 0.5.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|