1use crate::function_trace::*;
2use ahash::{HashMap, HashSet, RandomState};
3use color_eyre::eyre::{Result, WrapErr};
4use indexmap::set::IndexSet;
5use owo_colors::OwoColorize;
6use serde::Serialize;
7use serde_repr::Serialize_repr;
8
9use std::borrow::Cow;
10use std::fs::File;
11use std::io::BufWriter;
12use std::num::NonZeroU32;
13use std::path::PathBuf;
14use std::sync::atomic::Ordering;
15use std::time::{Duration, SystemTime};
16
17type HighResFirefoxTime = f64;
28type FirefoxTime = f32;
29
30#[derive(Serialize, Debug)]
32enum Unused {}
33
34type StringIndex = usize;
35
36type ThreadId = u32;
38
39pub struct FirefoxProfileThreadId {
42 pub tid: ThreadId,
44
45 start_duration: Duration,
48}
49
50#[derive(Serialize, Debug)]
55pub struct FirefoxProfile {
56 meta: Metadata,
57 counters: Vec<Unused>,
58 threads: Vec<FirefoxThread>,
59 libs: Vec<Unused>,
60
61 #[serde(skip_serializing)]
66 thread_count: u32,
67}
68
69#[derive(Hash, Debug, PartialEq, Eq)]
72enum FunctionCall<'a> {
73 Native {
74 function_name: Cow<'a, str>,
75 module: Cow<'a, str>,
76 },
77 Python {
78 function_name: Cow<'a, str>,
79 module: Cow<'a, str>,
80 line: NonZeroU32,
81 },
82}
83
84#[derive(Serialize, Debug)]
89#[serde(rename_all = "lowercase")]
90#[allow(dead_code)]
91enum Color {
92 Transparent,
93 Purple,
94 Green,
95 Orange,
96 Yellow,
97 LightBlue,
98 Grey,
99 Blue,
100 Brown,
101}
102
103#[derive(Serialize, Debug)]
104struct Category {
105 name: &'static str,
106 color: Color,
107 subcategories: [&'static str; 1],
108}
109#[derive(Serialize_repr, Debug, Clone, Copy)]
110#[repr(u32)]
111enum CategoryIndex {
112 Logs,
113 Python,
114 Native,
115 Exceptions,
116 Imports,
117 GarbageCollection,
118 Length,
119}
120
121const CATEGORIES: [Category; CategoryIndex::Length as usize] = [
122 Category {
123 name: "Logs",
124 color: Color::Grey,
125 subcategories: ["Unused"],
126 },
127 Category {
128 name: "Python",
129 color: Color::Orange,
130 subcategories: ["Code"],
131 },
132 Category {
133 name: "Native",
134 color: Color::LightBlue,
135 subcategories: ["Code"],
136 },
137 Category {
138 name: "Exceptions",
139 color: Color::Brown,
140 subcategories: ["Unused"],
141 },
142 Category {
143 name: "Imports",
144 color: Color::Grey,
145 subcategories: ["Unused"],
146 },
147 Category {
148 name: "GarbageCollection",
149 color: Color::Purple,
150 subcategories: ["Unused"],
151 },
152];
153
154macro_rules! struct_table {
162 (struct $struct_name: ident {
163 $($element: ident: $ty: ty),+
164 } $($additional_field:ident: $additional_type: ty),*) => {
165 paste::item! {
166 #[derive(Serialize, Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
167 struct [<$struct_name Index>] (u32);
172
173 #[derive(Serialize, Debug, Default)]
174 #[serde(rename_all = "camelCase")]
175 struct [<$struct_name Table>] {
176 $($element: Vec<$ty>,)*
177 $($additional_field: $additional_type,)*
178 length: usize
179 }
180
181 struct $struct_name {
182 $($element: $ty),*,
183 }
184
185 impl [<$struct_name Table>] {
186 #[allow(dead_code)]
187 fn new($([<$additional_field _arg>]: $additional_type,)*) -> Self {
188 Self {
189 $($element: Vec::new()),*,
190 $($additional_field: [<$additional_field _arg>],)*
191 length: 0
192 }
193 }
194
195 #[allow(dead_code)]
196 fn add(&mut self, arg: $struct_name) -> [<$struct_name Index>] {
197 let length = self.length;
198 $(self.$element.push(arg.$element);)*
199 self.length += 1;
200
201 [<$struct_name Index>](length as u32)
202 }
203
204 #[allow(dead_code)]
206 fn is_empty(&self) -> bool {
207 self.length == 0
208 }
209
210 $(
211 #[allow(dead_code)]
213 fn [<get_ $element>](&self, index: [<$struct_name Index>]) -> &$ty {
214 &self.$element[index.0 as usize]
215 })*
216 }
217 }
218 }
219}
220
221struct_table! {
222 struct Sample {
223 stack: StackIndex,
224 time: FirefoxTime,
225 weight: Option<FirefoxTime>
226 }
230 weight_type: &'static str
231}
232
233struct_table! {
234 struct Stack {
235 frame: FrameIndex,
236 category: CategoryIndex,
237 subcategory: u8,
238 prefix: Option<StackIndex>
239 }
240}
241struct_table! {
242 struct Frame {
243 func: FunctionIndex,
244 inlineDepth: u8,
245 category: CategoryIndex,
246 subcategory: u8,
247 address: Option<Unused>,
248 nativeSymbol: Option<Unused>,
249 innerWindowID: Option<Unused>,
250 implementation: Option<Unused>,
251 line: Option<Unused>,
253 column: Option<Unused>
254 }
255}
256struct_table! {
257 struct Function {
258 isJS: bool,
259 name: StringIndex,
260 resource: i32,
261 relevantForJS: bool,
262 file_name: StringIndex, line_number: Option<NonZeroU32>,
264 column_number: Option<Unused>
265 }
266}
267struct_table! {
268 struct NativeAllocation {
269 time: FirefoxTime,
270 weight: isize, stack: Option<StackIndex>,
272 memory_address: usize,
273 thread_id: ThreadId
274 }
275 weight_type: &'static str
276}
277struct_table! {
278 struct NativeSymbol {
279 libIndex: Option<Unused>,
280 address: Option<Unused>,
281 name: StringIndex,
282 functionSize: Option<u32>
283 }
284}
285struct_table! {
286 struct Marker {
287 data: serde_json::Value,
288 name: StringIndex,
289 startTime: FirefoxTime,
290 endTime: Option<FirefoxTime>,
291 phase: u64, category: CategoryIndex
293 }
294}
295struct_table! {
296 struct Resource {
297 lib: Unused,
298 name: Unused,
299 host: Unused,
300 r#type: Unused
301 }
302}
303
304#[derive(Serialize, Debug)]
309#[serde(rename_all = "camelCase")]
310struct MarkerSchema {
311 name: &'static str,
312 tooltip_label: &'static str,
313 table_label: &'static str,
314 chart_label: &'static str,
315 display: &'static [&'static str],
316 data: &'static [MarkerDataSchema],
317}
318
319#[derive(Serialize, Debug)]
320struct MarkerDataSchema {
321 key: &'static str,
322 label: &'static str,
323 format: &'static str,
324 searchable: bool,
325}
326
327const MARKER_DISPLAYS_ALL: &[&str] = &[
328 "marker-chart",
329 "marker-table",
330 "timeline-overview",
331 "stack-chart",
332];
333
334const MARKER_SCHEMAS_SUPPORTED: &[MarkerSchema] = &[
335 MarkerSchema {
336 name: "Log",
337 tooltip_label: "Log event from {marker.data.origin}",
338 chart_label: "{marker.data.origin}",
339 table_label: "{marker.data.origin}: {marker.data.value}",
340 display: MARKER_DISPLAYS_ALL,
341 data: &[
342 MarkerDataSchema {
343 key: "origin",
344 label: "Log Origin",
345 format: "string",
346 searchable: true,
347 },
348 MarkerDataSchema {
349 key: "value",
350 label: "Value",
351 format: "string",
352 searchable: true,
353 },
354 ],
355 },
356 MarkerSchema {
357 name: "Import",
358 tooltip_label: "Imported {marker.data.module}",
359 chart_label: "{marker.data.module}",
360 table_label: "imported {marker.data.module}",
361 display: MARKER_DISPLAYS_ALL,
362 data: &[MarkerDataSchema {
363 key: "module",
364 label: "Imported Module",
365 format: "string",
366 searchable: true,
367 }],
368 },
369 MarkerSchema {
370 name: "Exception",
371 tooltip_label: "Exception {marker.data.exception} in {marker.data.module}",
372 chart_label: "{marker.data.exception}",
373 table_label: "Exception {marker.data.exception}: {marker.data.value}",
374 display: MARKER_DISPLAYS_ALL,
375 data: &[
376 MarkerDataSchema {
377 key: "exception",
378 label: "Exception Type",
379 format: "string",
380 searchable: true,
381 },
382 MarkerDataSchema {
383 key: "value",
384 label: "Exception Data",
385 format: "string",
386 searchable: true,
387 },
388 MarkerDataSchema {
389 key: "module",
390 label: "Module",
391 format: "string",
392 searchable: true,
393 },
394 ],
395 },
396];
397
398#[derive(Serialize, Debug)]
402#[serde(rename_all = "camelCase")]
403struct Metadata {
404 interval: f32,
408 start_time: f64,
410 process_type: u32,
412 categories: &'static [Category],
413 stackwalk: u32,
415 debug: bool,
416 version: u32,
417 preprocessed_profile_version: u32,
418 symbolicated: bool,
419
420 product: String,
421 #[serde(rename = "appBuildID")]
422 app_build_id: String,
423 abi: String,
424 platform: String,
425 misc: String,
426
427 #[serde(rename = "physicalCPUs")]
428 physical_cpus: usize,
429 #[serde(rename = "logicalCPUs")]
430 logical_cpus: usize,
431
432 marker_schema: &'static [MarkerSchema],
433
434 #[serde(skip_serializing)]
439 start_duration: Duration,
440}
441
442#[derive(Serialize, Debug, Default)]
447#[serde(rename_all = "camelCase")]
448pub struct FirefoxThread {
449 process_type: &'static str,
453 process_startup_time: HighResFirefoxTime, process_shutdown_time: FirefoxTime,
455 register_time: HighResFirefoxTime,
456 unregister_time: Option<HighResFirefoxTime>, paused_ranges: Vec<Unused>,
458 name: &'static str,
459 is_main_thread: bool,
460 process_name: String,
461 is_js_tracer: bool,
462 pid: String,
463 tid: ThreadId,
464
465 samples: SampleTable,
466 markers: MarkerTable,
467 stack_table: StackTable,
468 frame_table: FrameTable,
469 func_table: FunctionTable,
470 string_array: IndexSet<String, RandomState>,
471
472 #[serde(skip_serializing_if = "NativeAllocationTable::is_empty")]
474 native_allocations: NativeAllocationTable,
475
476 resource_table: ResourceTable,
480 native_symbols: NativeSymbolTable,
481
482 #[serde(skip_serializing)]
488 calls: Vec<SampleIndex>,
489
490 #[serde(skip_serializing)]
492 functions: HashMap<FunctionCall<'static>, FunctionIndex>,
493
494 #[serde(skip_serializing)]
497 start_duration: Duration,
498
499 #[serde(skip_serializing)]
503 allocations: HashMap<usize, usize>,
504
505 #[serde(skip_serializing)]
508 existing_stacks: HashMap<(FunctionIndex, Option<StackIndex>), StackIndex>,
509}
510
511impl FirefoxProfile {
512 #[must_use]
514 pub fn new(info: TraceInitialization) -> FirefoxProfile {
515 FirefoxProfile {
516 meta: Metadata {
517 start_time: SystemTime::now()
520 .duration_since(SystemTime::UNIX_EPOCH)
521 .map_or(0.0, |n| n.as_secs_f64() * 1000.0),
522 start_duration: info.time,
523 interval: 0.000_001, process_type: 0,
525 categories: &CATEGORIES,
526 stackwalk: 0,
527
528 version: 27,
533 preprocessed_profile_version: 47,
534 debug: false,
535 symbolicated: true,
536 physical_cpus: num_cpus::get_physical(),
537 logical_cpus: num_cpus::get(),
538
539 product: info.program_name,
540 app_build_id: info.program_version,
541 abi: info.lang_version,
542 platform: info.platform,
543 misc: "<git revision>".to_string(),
544
545 marker_schema: MARKER_SCHEMAS_SUPPORTED,
546 },
547 counters: Vec::new(),
548 threads: Vec::new(),
549 libs: Vec::new(),
550
551 thread_count: 0,
552 }
553 }
554
555 pub fn register_thread(&mut self) -> FirefoxProfileThreadId {
558 self.thread_count += 1;
559
560 FirefoxProfileThreadId {
561 tid: self.thread_count,
562 start_duration: self.meta.start_duration,
563 }
564 }
565
566 pub fn finalize_thread(&mut self, thread: FirefoxThread) {
568 self.threads.push(thread);
570 }
571
572 pub fn export(&mut self, output_dir: PathBuf) -> Result<()> {
574 self.threads.sort_by(|x, y| {
578 x.register_time
579 .partial_cmp(&y.register_time)
580 .expect("Registration time is reasonable")
581 });
582
583 let end_time = self
586 .threads
587 .iter()
588 .map(|x| x.process_shutdown_time)
589 .fold(0.0, FirefoxTime::max);
590
591 let mut registered_pids = HashSet::default();
596
597 for thread in &mut self.threads {
599 thread.name = if registered_pids.contains(&thread.pid) {
600 "thread"
601 } else {
602 thread.is_main_thread = true;
603 "GeckoMain"
604 };
605
606 registered_pids.insert(thread.pid.clone());
607
608 if thread.functions.is_empty() {
609 let message = "[WARNING] FunctionTrace didn't log any data for this thread. This may be because the thread crashed or otherwise exited before flushing its trace buffer.";
614 thread.emit_call(
615 message,
616 thread.start_duration + Duration::from_secs_f64(thread.register_time / 1000.0),
617 "FunctionTrace",
618 None,
619 );
620 thread.emit_return(message, end_time);
621 thread.process_shutdown_time = end_time;
622 }
623 }
624
625 let compressed = std::env::var("FUNCTIONTRACE_COMPRESSION")
632 .map(|x| !matches!(x.as_str(), "false" | "no" | "disable"))
633 .unwrap_or(true);
634
635 let output_path = {
637 let now = chrono::Local::now();
638
639 output_dir.join(format!(
640 "functiontrace.{}.json{}",
641 now.format("%F-%T.%3f"),
642 if compressed { ".gz" } else { "" }
643 ))
644 };
645
646 {
648 use spinoff::spinners::{Arrow3, SpinnerFrames};
649
650 let bytes = std::sync::atomic::AtomicUsize::new(0);
652
653 let mut spinner = spinoff::Spinner::new(
654 Arrow3,
655 "Exporting FunctionTrace profile...",
656 spinoff::Color::Blue,
657 );
658 let gzipped_bytes = std::thread::scope(|s| {
659 s.spawn(|| {
662 let spin_interval = {
663 let spinner_frame = SpinnerFrames::from(Arrow3);
664
665 Duration::from_millis(
666 spinner_frame.frames.len() as u64 * spinner_frame.interval as u64,
667 )
668 };
669
670 loop {
671 std::thread::sleep(spin_interval);
672 let written = bytes.load(Ordering::Relaxed);
673
674 if written == usize::MAX {
675 break;
676 }
677
678 spinner.update_text(format!(
679 "Exporting FunctionTrace profile... {}",
680 bytesize::to_string(written as u64, false)
681 ));
682 }
683 });
684
685 let progress_writer = BufWriter::new(progress_streams::ProgressWriter::new(
688 File::create(&output_path)?,
689 |progress: usize| {
690 bytes.fetch_add(progress, Ordering::Relaxed);
691 },
692 ));
693
694 if compressed {
695 let gzip = flate2::write::GzEncoder::new(
696 progress_writer,
697 flate2::Compression::new(4),
700 );
701
702 serde_json::to_writer(gzip, &self)?;
703 } else {
704 serde_json::to_writer(progress_writer, &self)?;
705 }
706
707 Ok::<_, std::io::Error>(bytes.swap(usize::MAX, Ordering::SeqCst))
710 })?;
711
712 spinner.success(&format!(
713 "Exported FunctionTrace profile to {} ({})",
714 output_path.display().bold().italic(),
715 bytesize::to_string(gzipped_bytes as u64, false)
716 ));
717 };
718
719 let symlink = output_dir.join(format!(
722 "functiontrace.latest.json{}",
723 if compressed { ".gz" } else { "" }
724 ));
725 let _ = std::fs::remove_file(&symlink);
726 std::os::unix::fs::symlink(&output_path, &symlink)
727 .wrap_err("Symlinking functiontrace.latest.json.gz failed")?;
728
729 Ok(())
730 }
731}
732
733impl FirefoxThread {
734 #[must_use]
737 pub fn new(registration: ThreadRegistration, thread: &FirefoxProfileThreadId) -> FirefoxThread {
738 FirefoxThread {
739 process_type: "default",
740 process_startup_time: 0.0,
741 process_shutdown_time: 0.0, register_time: (registration.time - thread.start_duration).as_secs_f64() * 1000.0,
743 start_duration: thread.start_duration,
744 unregister_time: None,
745 name: "<filled in at export>",
746 process_name: registration.program_name,
747 is_js_tracer: true,
748 pid: registration.pid.to_string(),
749 tid: thread.tid,
750 samples: SampleTable::new("tracing-ms"),
751 native_allocations: NativeAllocationTable::new("bytes"),
752 ..Default::default()
753 }
754 }
755
756 fn offset_time(&self, time: Duration) -> FirefoxTime {
759 (time - self.start_duration).as_secs_f32() * 1000.0
760 }
761
762 fn unique_string(&mut self, string: &str) -> StringIndex {
765 match self.string_array.get_full(string) {
766 Some((index, _)) => index,
767 None => self.string_array.insert_full(string.to_string()).0,
768 }
769 }
770
771 fn emit_call(
772 &mut self,
773 name: &str,
774 time: Duration,
775 file_or_module: &str,
776 line_number: Option<NonZeroU32>,
777 ) -> FirefoxTime {
778 let (func_uid, category) = match line_number {
780 None => (
782 FunctionCall::Native {
783 function_name: std::borrow::Cow::Borrowed(name),
784 module: std::borrow::Cow::Borrowed(file_or_module),
785 },
786 CategoryIndex::Native,
787 ),
788 Some(line) => (
789 FunctionCall::Python {
790 function_name: std::borrow::Cow::Borrowed(name),
791 module: std::borrow::Cow::Borrowed(file_or_module),
792 line,
793 },
794 CategoryIndex::Python,
795 ),
796 };
797
798 let function_id = match self.functions.get(&func_uid) {
800 Some(&x) => x,
801 None => {
802 let funcname_id = self.unique_string(name);
805 let filename_id = self.unique_string(file_or_module);
806
807 let function_id = self.func_table.add(Function {
810 isJS: false,
811 name: funcname_id,
812 resource: -1,
813 relevantForJS: false,
814 file_name: filename_id,
815 line_number,
816 column_number: None,
817 });
818 self.frame_table.add(Frame {
819 func: function_id,
820 category,
821 subcategory: 0,
822 address: None,
823 inlineDepth: 0,
824 nativeSymbol: None,
825 innerWindowID: None,
826 implementation: None,
827 line: None,
828 column: None,
829 });
830
831 let func_uid = match func_uid {
833 FunctionCall::Native { .. } => FunctionCall::Native {
834 function_name: std::borrow::Cow::Owned(name.to_string()),
835 module: std::borrow::Cow::Owned(file_or_module.to_string()),
836 },
837 FunctionCall::Python { line, .. } => FunctionCall::Python {
838 function_name: std::borrow::Cow::Owned(name.to_string()),
839 module: std::borrow::Cow::Owned(file_or_module.to_string()),
840 line,
841 },
842 };
843
844 self.functions.insert(func_uid, function_id);
845 function_id
846 }
847 };
848 let frame_id = FrameIndex(function_id.0);
850
851 let prefix = self.calls.last().map(|&x| *self.samples.get_stack(x));
854
855 let stack_id = *self
860 .existing_stacks
861 .entry((function_id, prefix))
862 .or_insert_with(|| {
863 self.stack_table.add(Stack {
864 frame: frame_id,
865 subcategory: 0,
866 category,
867 prefix,
868 })
869 });
870
871 let time = self.offset_time(time);
873 let samples_id = self.samples.add(Sample {
874 stack: stack_id,
875 time,
876 weight: None, });
878 self.calls.push(samples_id);
879
880 time
881 }
882
883 fn emit_return(&mut self, func_name: &str, time: FirefoxTime) -> FirefoxTime {
884 while let Some(call_id) = self.calls.pop() {
887 let matches = {
891 let caller_id = *self.func_table.get_name(
893 *self
894 .frame_table
895 .get_func(*self.stack_table.get_frame(*self.samples.get_stack(call_id))),
896 );
897
898 self.string_array
901 .get_full(func_name)
902 .map_or(false, |(id, _)| id == caller_id)
903 };
904
905 self.samples.weight[call_id.0 as usize] = Some(time - self.samples.get_time(call_id));
907
908 if matches {
909 break;
911 }
912
913 log::trace!(
914 "Returning from `{}` which was not the most recently called function",
915 func_name
916 );
917 }
918
919 time
920 }
921
922 pub fn add_trace(&mut self, trace: FunctionTrace) {
925 let event_time = match trace {
926 FunctionTrace::Call {
927 time,
928 func_name,
929 filename,
930 linenumber,
931 } => self.emit_call(&func_name, time, &filename, Some(linenumber)),
932 FunctionTrace::NativeCall {
933 time,
934 func_name,
935 module_name,
936 } => self.emit_call(&func_name, time, &module_name, None),
937 FunctionTrace::Return { time, func_name }
938 | FunctionTrace::NativeReturn { time, func_name } => {
939 self.emit_return(&func_name, self.offset_time(time))
940 }
941 FunctionTrace::Exception {
942 time,
943 exception_type,
944 exception_value,
945 filename,
946 linenumber,
947 } => {
948 let unique_type = self.unique_string(&exception_type);
951 let time = self.offset_time(time);
952
953 self.markers.add(Marker {
954 data: serde_json::json!({
955 "type": "Exception",
956 "exeption": exception_type,
957 "value": exception_value,
958 "module": format!("{}:{}", filename, linenumber)
959 }),
960 name: unique_type,
961 startTime: time,
962 endTime: None,
963 phase: 0,
964 category: CategoryIndex::Exceptions,
965 });
966
967 time
968 }
969 FunctionTrace::Log {
970 time,
971 log_type,
972 log_value,
973 } => {
974 let time = self.offset_time(time);
975 let unique_type = self.unique_string(&log_type);
976
977 self.markers.add(Marker {
978 data: serde_json::json!({
979 "type": "Log",
980 "origin": log_type,
981 "value": log_value,
982 }),
983 name: unique_type,
984 startTime: time,
985 endTime: None,
986 phase: 0,
987 category: CategoryIndex::Logs,
988 });
989
990 time
991 }
992 FunctionTrace::Import { time, module_name } => {
993 let time = self.offset_time(time);
994 let unique_type = self.unique_string("Import");
995
996 self.markers.add(Marker {
997 data: serde_json::json!({
998 "type": "Import",
999 "module": module_name,
1000 }),
1001 name: unique_type,
1002 startTime: time,
1003 endTime: None,
1004 phase: 0,
1005 category: CategoryIndex::Imports,
1006 });
1007
1008 time
1009 }
1010 FunctionTrace::Allocation { time, details } => {
1011 let time = self.offset_time(time);
1012 let stack = self.calls.last().map(|&x| *self.samples.get_stack(x));
1013
1014 match details {
1015 AllocationDetails::Alloc { bytes, addr } => {
1016 self.allocations.insert(addr, bytes);
1017
1018 self.native_allocations.add(NativeAllocation {
1019 time,
1020 weight: bytes as isize,
1021 stack,
1022 memory_address: addr,
1023 thread_id: self.tid,
1024 });
1025 }
1026 AllocationDetails::Realloc {
1027 bytes,
1028 old_addr,
1029 new_addr,
1030 } => {
1031 let old_bytes = self.allocations.remove(&old_addr).unwrap_or(0);
1035 self.native_allocations.add(NativeAllocation {
1036 time,
1037 weight: -(old_bytes as isize),
1038 stack,
1039 memory_address: old_addr,
1040 thread_id: self.tid,
1041 });
1042
1043 self.allocations.insert(new_addr, bytes);
1045 self.native_allocations.add(NativeAllocation {
1046 time,
1047 weight: bytes as isize,
1048 stack,
1049 memory_address: new_addr,
1050 thread_id: self.tid,
1051 });
1052 }
1053 AllocationDetails::Free { old_addr } => {
1054 let bytes = self.allocations.remove(&old_addr).unwrap_or(0);
1057
1058 self.native_allocations.add(NativeAllocation {
1059 time,
1060 weight: -(bytes as isize),
1061 stack,
1062 memory_address: old_addr,
1063 thread_id: self.tid,
1064 });
1065 }
1066 }
1067
1068 time
1069 }
1070 FunctionTrace::RegisterThread(_) => unreachable!(),
1071 };
1072
1073 self.process_shutdown_time = self.process_shutdown_time.max(event_time);
1075 }
1076}