Skip to content

Commit 58386a3

Browse files
authored
Catch incorrect uses of Profile::add_allocation_sample. (#629)
Jeff was running into wrong stacks because he was calling Profile::add_allocation_sample for a thread which was not the main thread, and then we were using stack handles which aren't valid on the main thread, and the existing assertions were insufficient to catch this. Change the API to accept a process instead of a thread, and widen the assertions.
2 parents 52395c4 + 724fec4 commit 58386a3

File tree

2 files changed

+34
-25
lines changed

2 files changed

+34
-25
lines changed

‎fxprof-processed-profile/src/process.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub struct Process {
1515
pid: String,
1616
name: String,
1717
threads: Vec<ThreadHandle>,
18+
main_thread: Option<ThreadHandle>,
1819
start_time: Timestamp,
1920
end_time: Option<Timestamp>,
2021
libs: LibMappings<LibraryHandle>,
@@ -25,6 +26,7 @@ impl Process {
2526
Self {
2627
pid,
2728
threads: Vec::new(),
29+
main_thread: None,
2830
libs: LibMappings::new(),
2931
start_time,
3032
end_time: None,
@@ -33,7 +35,7 @@ impl Process {
3335
}
3436

3537
pub fn thread_handle_for_allocations(&self) -> Option<ThreadHandle> {
36-
self.threads.first().cloned()
38+
self.main_thread
3739
}
3840

3941
pub fn set_start_time(&mut self, start_time: Timestamp) {
@@ -60,8 +62,11 @@ impl Process {
6062
&self.name
6163
}
6264

63-
pub fn add_thread(&mut self, thread: ThreadHandle) {
65+
pub fn add_thread(&mut self, thread: ThreadHandle, is_main: bool) {
6466
self.threads.push(thread);
67+
if is_main && self.main_thread.is_none() {
68+
self.main_thread = Some(thread)
69+
}
6570
}
6671

6772
pub fn pid(&self) -> &str {

‎fxprof-processed-profile/src/profile.rs

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ impl Profile {
481481
let handle = ThreadHandle(self.threads.len());
482482
self.threads
483483
.push(Thread::new(process, tid, start_time, is_main));
484-
self.processes[process.0].add_thread(handle);
484+
self.processes[process.0].add_thread(handle, is_main);
485485
handle
486486
}
487487

@@ -926,8 +926,12 @@ impl Profile {
926926
self.threads[thread.0].add_sample_same_stack_zero_cpu(timestamp, weight);
927927
}
928928

929-
/// Add an allocation or deallocation sample to the given thread. This is used
930-
/// to collect stacks showing where allocations and deallocations happened.
929+
/// Add an allocation or deallocation sample to the *main* thread of the given
930+
/// process. This is used to collect stacks showing where allocations and
931+
/// deallocations happened.
932+
///
933+
/// CANNOT BE CALLED BEFORE THE MAIN THREAD FOR `process` HAS BEEN CREATED.
934+
/// `stack` MUST BE A STACK HANDLE WHICH IS VALID FOR THAT MAIN THREAD.
931935
///
932936
/// When loading profiles with allocation samples in the Firefox Profiler, the
933937
/// UI will display a dropdown above the call tree to switch between regular
@@ -951,40 +955,40 @@ impl Profile {
951955
///
952956
/// To get the stack handle, you can use [`Profile::handle_for_stack`] or
953957
/// [`Profile::handle_for_stack_frames`].
958+
///
959+
/// ## Main thread requirement
960+
///
961+
/// Allocations are per-process, because you can allocate something one one thread
962+
/// and then free it on a different thread, and you'll still want those two operations
963+
/// to be matched up in a view that shows the retained memory.
964+
///
965+
/// Unfortunately, `StackHandle` is currently per-thread. This method will become more
966+
/// ergonomic once the profile format has changed so that `StackHandle`s can be used
967+
/// across all threads of a profile. In the meantime, unfortunately you must manually
968+
/// ensure that you create the stack handle for the main thread of the given process.
954969
pub fn add_allocation_sample(
955970
&mut self,
956-
thread: ThreadHandle,
971+
process: ProcessHandle,
957972
timestamp: Timestamp,
958973
stack: Option<StackHandle>,
959974
allocation_address: u64,
960975
allocation_size: i64,
961976
) {
962-
// The profile format strictly separates sample data from different threads.
963-
// For allocation samples, this separation is a bit unfortunate, especially
964-
// when it comes to the "Retained Memory" panel which shows allocation stacks
965-
// for just objects that haven't been deallocated yet. This panel is per-thread,
966-
// and it needs to know about deallocations even if they happened on a different
967-
// thread from the allocation.
968-
// To resolve this conundrum, for now, we will put all allocation and deallocation
969-
// samples on a single thread per process, regardless of what thread they actually
970-
// happened on.
971-
// The Gecko profiler puts all allocation samples on the main thread, for example.
972-
// Here in fxprof-processed-profile, we just deem the first thread of each process
973-
// as the processes "allocation thread".
974-
let process_handle = self.threads[thread.0].process();
975-
let process = &self.processes[process_handle.0];
976-
let allocation_thread_handle = process.thread_handle_for_allocations().unwrap();
977+
let process = &self.processes[process.0];
978+
let Some(allocation_thread) = process.thread_handle_for_allocations() else {
979+
panic!("Profile::add_allocation_sample called for a thread whose process does not have a main thread");
980+
};
977981
let stack_index = match stack {
978-
Some(StackHandle(stack_thread_handle, stack_index)) => {
982+
Some(StackHandle(stack_thread, stack_index)) => {
979983
assert_eq!(
980-
stack_thread_handle, thread,
981-
"StackHandle from different thread passed to Profile::add_sample"
984+
stack_thread, allocation_thread,
985+
"StackHandle from different thread passed to Profile::add_allocation_sample"
982986
);
983987
Some(stack_index)
984988
}
985989
None => None,
986990
};
987-
self.threads[allocation_thread_handle.0].add_allocation_sample(
991+
self.threads[allocation_thread.0].add_allocation_sample(
988992
timestamp,
989993
stack_index,
990994
allocation_address,

0 commit comments

Comments
 (0)