1use std::sync::Arc;
2use std::time::Duration;
3
4use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer};
5use serde_json::json;
6
7use crate::category::{Category, CategoryHandle, CategoryPairHandle};
8use crate::category_color::CategoryColor;
9use crate::counters::{Counter, CounterHandle};
10use crate::cpu_delta::CpuDelta;
11use crate::fast_hash_map::FastHashMap;
12use crate::frame::{Frame, FrameInfo};
13use crate::frame_table::{InternalFrame, InternalFrameLocation};
14use crate::global_lib_table::{GlobalLibTable, LibraryHandle};
15use crate::lib_mappings::LibMappings;
16use crate::library_info::LibraryInfo;
17use crate::process::{Process, ThreadHandle};
18use crate::reference_timestamp::ReferenceTimestamp;
19use crate::string_table::{GlobalStringIndex, GlobalStringTable};
20use crate::thread::{ProcessHandle, Thread};
21use crate::{MarkerSchema, MarkerTiming, ProfilerMarker, SymbolTable, Timestamp};
22
23#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
31pub struct SamplingInterval {
32 nanos: u64,
33}
34
35impl SamplingInterval {
36 pub fn from_hz(samples_per_second: f32) -> Self {
40 assert!(samples_per_second > 0.0);
41 let nanos = (1_000_000_000.0 / samples_per_second) as u64;
42 Self::from_nanos(nanos)
43 }
44
45 pub fn from_millis(millis: u64) -> Self {
47 Self::from_nanos(millis * 1_000_000)
48 }
49
50 pub fn from_nanos(nanos: u64) -> Self {
52 Self { nanos }
53 }
54
55 pub fn nanos(&self) -> u64 {
57 self.nanos
58 }
59
60 pub fn as_secs_f64(&self) -> f64 {
62 self.nanos as f64 / 1_000_000_000.0
63 }
64}
65
66impl From<Duration> for SamplingInterval {
67 fn from(duration: Duration) -> Self {
68 Self::from_nanos(duration.as_nanos() as u64)
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
74pub struct StringHandle(GlobalStringIndex);
75
76#[derive(Debug)]
102pub struct Profile {
103 pub(crate) product: String,
104 pub(crate) interval: SamplingInterval,
105 pub(crate) global_libs: GlobalLibTable,
106 pub(crate) kernel_libs: LibMappings<LibraryHandle>,
107 pub(crate) categories: Vec<Category>, pub(crate) processes: Vec<Process>, pub(crate) counters: Vec<Counter>,
110 pub(crate) threads: Vec<Thread>, pub(crate) reference_timestamp: ReferenceTimestamp,
112 pub(crate) string_table: GlobalStringTable,
113 pub(crate) marker_schemas: FastHashMap<&'static str, MarkerSchema>,
114 used_pids: FastHashMap<u32, u32>,
115 used_tids: FastHashMap<u32, u32>,
116}
117
118impl Profile {
119 pub fn new(
126 product: &str,
127 reference_timestamp: ReferenceTimestamp,
128 interval: SamplingInterval,
129 ) -> Self {
130 Profile {
131 interval,
132 product: product.to_string(),
133 threads: Vec::new(),
134 global_libs: GlobalLibTable::new(),
135 kernel_libs: LibMappings::new(),
136 reference_timestamp,
137 processes: Vec::new(),
138 string_table: GlobalStringTable::new(),
139 marker_schemas: FastHashMap::default(),
140 categories: vec![Category {
141 name: "Other".to_string(),
142 color: CategoryColor::Gray,
143 subcategories: Vec::new(),
144 }],
145 used_pids: FastHashMap::default(),
146 used_tids: FastHashMap::default(),
147 counters: Vec::new(),
148 }
149 }
150
151 pub fn set_interval(&mut self, interval: SamplingInterval) {
153 self.interval = interval;
154 }
155
156 pub fn set_reference_timestamp(&mut self, reference_timestamp: ReferenceTimestamp) {
158 self.reference_timestamp = reference_timestamp;
159 }
160
161 pub fn set_product(&mut self, product: &str) {
163 self.product = product.to_string();
164 }
165
166 pub fn add_category(&mut self, name: &str, color: CategoryColor) -> CategoryHandle {
170 let handle = CategoryHandle(self.categories.len() as u16);
171 self.categories.push(Category {
172 name: name.to_string(),
173 color,
174 subcategories: Vec::new(),
175 });
176 handle
177 }
178
179 pub fn add_subcategory(&mut self, category: CategoryHandle, name: &str) -> CategoryPairHandle {
181 let subcategory = self.categories[category.0 as usize].add_subcategory(name.into());
182 CategoryPairHandle(category, Some(subcategory))
183 }
184
185 pub fn add_process(&mut self, name: &str, pid: u32, start_time: Timestamp) -> ProcessHandle {
188 let pid = self.make_unique_pid(pid);
189 let handle = ProcessHandle(self.processes.len());
190 self.processes.push(Process::new(name, pid, start_time));
191 handle
192 }
193
194 fn make_unique_pid(&mut self, pid: u32) -> String {
195 Self::make_unique_pid_or_tid(&mut self.used_pids, pid)
196 }
197
198 fn make_unique_tid(&mut self, tid: u32) -> String {
199 Self::make_unique_pid_or_tid(&mut self.used_tids, tid)
200 }
201
202 fn make_unique_pid_or_tid(map: &mut FastHashMap<u32, u32>, id: u32) -> String {
207 match map.entry(id) {
208 std::collections::hash_map::Entry::Occupied(mut entry) => {
209 let suffix = *entry.get();
210 *entry.get_mut() += 1;
211 format!("{id}.{suffix}")
212 }
213 std::collections::hash_map::Entry::Vacant(entry) => {
214 entry.insert(1);
215 format!("{id}")
216 }
217 }
218 }
219
220 pub fn add_counter(
237 &mut self,
238 process: ProcessHandle,
239 name: &str,
240 category: &str,
241 description: &str,
242 ) -> CounterHandle {
243 let handle = CounterHandle(self.counters.len());
244 self.counters.push(Counter::new(
245 name,
246 category,
247 description,
248 process,
249 self.processes[process.0].pid(),
250 ));
251 handle
252 }
253
254 pub fn set_process_start_time(&mut self, process: ProcessHandle, start_time: Timestamp) {
256 self.processes[process.0].set_start_time(start_time);
257 }
258
259 pub fn set_process_end_time(&mut self, process: ProcessHandle, end_time: Timestamp) {
261 self.processes[process.0].set_end_time(end_time);
262 }
263
264 pub fn set_process_name(&mut self, process: ProcessHandle, name: &str) {
266 self.processes[process.0].set_name(name);
267 }
268
269 pub fn add_lib(&mut self, library: LibraryInfo) -> LibraryHandle {
275 self.global_libs.handle_for_lib(library)
276 }
277
278 pub fn set_lib_symbol_table(&mut self, library: LibraryHandle, symbol_table: Arc<SymbolTable>) {
288 self.global_libs.set_lib_symbol_table(library, symbol_table);
289 }
290
291 pub fn add_lib_mapping(
301 &mut self,
302 process: ProcessHandle,
303 lib: LibraryHandle,
304 start_avma: u64,
305 end_avma: u64,
306 relative_address_at_start: u32,
307 ) {
308 self.processes[process.0].add_lib_mapping(
309 lib,
310 start_avma,
311 end_avma,
312 relative_address_at_start,
313 );
314 }
315
316 pub fn remove_lib_mapping(&mut self, process: ProcessHandle, start_avma: u64) {
319 self.processes[process.0].remove_lib_mapping(start_avma);
320 }
321
322 pub fn clear_process_lib_mappings(&mut self, process: ProcessHandle) {
324 self.processes[process.0].remove_all_lib_mappings();
325 }
326
327 pub fn add_kernel_lib_mapping(
335 &mut self,
336 lib: LibraryHandle,
337 start_avma: u64,
338 end_avma: u64,
339 relative_address_at_start: u32,
340 ) {
341 self.kernel_libs
342 .add_mapping(start_avma, end_avma, relative_address_at_start, lib);
343 }
344
345 pub fn remove_kernel_lib_mapping(&mut self, start_avma: u64) {
348 self.kernel_libs.remove_mapping(start_avma);
349 }
350
351 pub fn add_thread(
353 &mut self,
354 process: ProcessHandle,
355 tid: u32,
356 start_time: Timestamp,
357 is_main: bool,
358 ) -> ThreadHandle {
359 let tid = self.make_unique_tid(tid);
360 let handle = ThreadHandle(self.threads.len());
361 self.threads
362 .push(Thread::new(process, tid, start_time, is_main));
363 self.processes[process.0].add_thread(handle);
364 handle
365 }
366
367 pub fn set_thread_name(&mut self, thread: ThreadHandle, name: &str) {
369 self.threads[thread.0].set_name(name);
370 }
371
372 pub fn set_thread_start_time(&mut self, thread: ThreadHandle, start_time: Timestamp) {
374 self.threads[thread.0].set_start_time(start_time);
375 }
376
377 pub fn set_thread_end_time(&mut self, thread: ThreadHandle, end_time: Timestamp) {
379 self.threads[thread.0].set_end_time(end_time);
380 }
381
382 pub fn intern_string(&mut self, s: &str) -> StringHandle {
384 StringHandle(self.string_table.index_for_string(s))
385 }
386
387 pub fn get_string(&self, handle: StringHandle) -> &str {
392 self.string_table.get_string(handle.0).unwrap()
393 }
394
395 pub fn add_sample(
419 &mut self,
420 thread: ThreadHandle,
421 timestamp: Timestamp,
422 frames: impl Iterator<Item = FrameInfo>,
423 cpu_delta: CpuDelta,
424 weight: i32,
425 ) {
426 let stack_index = self.stack_index_for_frames(thread, frames);
427 self.threads[thread.0].add_sample(timestamp, stack_index, cpu_delta, weight);
428 }
429
430 pub fn add_sample_same_stack_zero_cpu(
434 &mut self,
435 thread: ThreadHandle,
436 timestamp: Timestamp,
437 weight: i32,
438 ) {
439 self.threads[thread.0].add_sample_same_stack_zero_cpu(timestamp, weight);
440 }
441
442 pub fn add_marker<T: ProfilerMarker>(
444 &mut self,
445 thread: ThreadHandle,
446 name: &str,
447 marker: T,
448 timing: MarkerTiming,
449 ) {
450 self.marker_schemas
451 .entry(T::MARKER_TYPE_NAME)
452 .or_insert_with(T::schema);
453 self.threads[thread.0].add_marker(name, marker, timing, None);
454 }
455
456 pub fn add_marker_with_stack<T: ProfilerMarker>(
458 &mut self,
459 thread: ThreadHandle,
460 name: &str,
461 marker: T,
462 timing: MarkerTiming,
463 stack_frames: impl Iterator<Item = FrameInfo>,
464 ) {
465 self.marker_schemas
466 .entry(T::MARKER_TYPE_NAME)
467 .or_insert_with(T::schema);
468 let stack_index = self.stack_index_for_frames(thread, stack_frames);
469 self.threads[thread.0].add_marker(name, marker, timing, stack_index);
470 }
471
472 pub fn add_counter_sample(
484 &mut self,
485 counter: CounterHandle,
486 timestamp: Timestamp,
487 value_delta: f64,
488 number_of_operations_delta: u32,
489 ) {
490 self.counters[counter.0].add_sample(timestamp, value_delta, number_of_operations_delta)
491 }
492
493 fn stack_index_for_frames(
495 &mut self,
496 thread: ThreadHandle,
497 frames: impl Iterator<Item = FrameInfo>,
498 ) -> Option<usize> {
499 let thread = &mut self.threads[thread.0];
500 let process = &mut self.processes[thread.process().0];
501 let mut prefix = None;
502 for frame_info in frames {
503 let location = match frame_info.frame {
504 Frame::InstructionPointer(ip) => {
505 process.convert_address(&mut self.global_libs, &mut self.kernel_libs, ip)
506 }
507 Frame::ReturnAddress(ra) => process.convert_address(
508 &mut self.global_libs,
509 &mut self.kernel_libs,
510 ra.saturating_sub(1),
511 ),
512 Frame::RelativeAddressFromInstructionPointer(lib_handle, relative_address) => {
513 let global_lib_index = self.global_libs.index_for_used_lib(lib_handle);
514 InternalFrameLocation::AddressInLib(relative_address, global_lib_index)
515 }
516 Frame::RelativeAddressFromReturnAddress(lib_handle, relative_address) => {
517 let global_lib_index = self.global_libs.index_for_used_lib(lib_handle);
518 let nudged_relative_address = relative_address.saturating_sub(1);
519 InternalFrameLocation::AddressInLib(nudged_relative_address, global_lib_index)
520 }
521 Frame::Label(string_index) => {
522 let thread_string_index =
523 thread.convert_string_index(&self.string_table, string_index.0);
524 InternalFrameLocation::Label(thread_string_index)
525 }
526 };
527 let internal_frame = InternalFrame {
528 location,
529 flags: frame_info.flags,
530 category_pair: frame_info.category_pair,
531 };
532 let frame_index = thread.frame_index_for_frame(internal_frame, &self.global_libs);
533 prefix =
534 Some(thread.stack_index_for_stack(prefix, frame_index, frame_info.category_pair));
535 }
536 prefix
537 }
538
539 fn sorted_threads(&self) -> (Vec<ThreadHandle>, Vec<usize>) {
545 let mut sorted_threads = Vec::with_capacity(self.threads.len());
546 let mut first_thread_index_per_process = Vec::with_capacity(self.processes.len());
547
548 let mut sorted_processes: Vec<_> = (0..self.processes.len()).map(ProcessHandle).collect();
549 sorted_processes.sort_by(|a_handle, b_handle| {
550 let a = &self.processes[a_handle.0];
551 let b = &self.processes[b_handle.0];
552 a.cmp_for_json_order(b)
553 });
554
555 for process in sorted_processes {
556 let prev_len = sorted_threads.len();
557 first_thread_index_per_process.push(prev_len);
558 sorted_threads.extend_from_slice(self.processes[process.0].threads());
559
560 let sorted_threads_for_this_process = &mut sorted_threads[prev_len..];
561 sorted_threads_for_this_process.sort_by(|a_handle, b_handle| {
562 let a = &self.threads[a_handle.0];
563 let b = &self.threads[b_handle.0];
564 a.cmp_for_json_order(b)
565 });
566 }
567
568 (sorted_threads, first_thread_index_per_process)
569 }
570
571 fn serializable_threads<'a>(
572 &'a self,
573 sorted_threads: &'a [ThreadHandle],
574 ) -> SerializableProfileThreadsProperty<'a> {
575 SerializableProfileThreadsProperty {
576 threads: &self.threads,
577 processes: &self.processes,
578 categories: &self.categories,
579 sorted_threads,
580 }
581 }
582
583 fn serializable_counters<'a>(
584 &'a self,
585 first_thread_index_per_process: &'a [usize],
586 ) -> SerializableProfileCountersProperty<'a> {
587 SerializableProfileCountersProperty {
588 counters: &self.counters,
589 first_thread_index_per_process,
590 }
591 }
592
593 fn contains_js_function(&self) -> bool {
594 self.threads.iter().any(|t| t.contains_js_function())
595 }
596}
597
598impl Serialize for Profile {
599 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
600 let (sorted_threads, first_thread_index_per_process) = self.sorted_threads();
601 let mut map = serializer.serialize_map(None)?;
602 map.serialize_entry("meta", &SerializableProfileMeta(self))?;
603 map.serialize_entry("libs", &self.global_libs)?;
604 map.serialize_entry("threads", &self.serializable_threads(&sorted_threads))?;
605 map.serialize_entry("pages", &[] as &[()])?;
606 map.serialize_entry("profilerOverhead", &[] as &[()])?;
607 map.serialize_entry(
608 "counters",
609 &self.serializable_counters(&first_thread_index_per_process),
610 )?;
611 map.end()
612 }
613}
614
615struct SerializableProfileMeta<'a>(&'a Profile);
616
617impl<'a> Serialize for SerializableProfileMeta<'a> {
618 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
619 let mut map = serializer.serialize_map(None)?;
620 map.serialize_entry("categories", &self.0.categories)?;
621 map.serialize_entry("debug", &false)?;
622 map.serialize_entry(
623 "extensions",
624 &json!({
625 "length": 0,
626 "baseURL": [],
627 "id": [],
628 "name": [],
629 }),
630 )?;
631 map.serialize_entry("interval", &(self.0.interval.as_secs_f64() * 1000.0))?;
632 map.serialize_entry("preprocessedProfileVersion", &46)?;
633 map.serialize_entry("processType", &0)?;
634 map.serialize_entry("product", &self.0.product)?;
635 map.serialize_entry(
636 "sampleUnits",
637 &json!({
638 "time": "ms",
639 "eventDelay": "ms",
640 "threadCPUDelta": "µs",
641 }),
642 )?;
643 map.serialize_entry("startTime", &self.0.reference_timestamp)?;
644 map.serialize_entry("symbolicated", &false)?;
645 map.serialize_entry("pausedRanges", &[] as &[()])?;
646 map.serialize_entry("version", &24)?;
647 map.serialize_entry("usesOnlyOneStackType", &(!self.0.contains_js_function()))?;
648 map.serialize_entry("doesNotUseFrameImplementation", &true)?;
649 map.serialize_entry("sourceCodeIsNotOnSearchfox", &true)?;
650
651 let mut marker_schemas: Vec<MarkerSchema> =
652 self.0.marker_schemas.values().cloned().collect();
653 marker_schemas.sort_by_key(|schema| schema.type_name);
654 map.serialize_entry("markerSchema", &marker_schemas)?;
655
656 map.end()
657 }
658}
659
660struct SerializableProfileThreadsProperty<'a> {
661 threads: &'a [Thread],
662 processes: &'a [Process],
663 categories: &'a [Category],
664 sorted_threads: &'a [ThreadHandle],
665}
666
667impl<'a> Serialize for SerializableProfileThreadsProperty<'a> {
668 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
669 let mut seq = serializer.serialize_seq(Some(self.threads.len()))?;
670
671 for thread in self.sorted_threads {
672 let categories = &self.categories;
673 let thread = &self.threads[thread.0];
674 let process = &self.processes[thread.process().0];
675 seq.serialize_element(&SerializableProfileThread(process, thread, categories))?;
676 }
677
678 seq.end()
679 }
680}
681
682struct SerializableProfileCountersProperty<'a> {
683 counters: &'a [Counter],
684 first_thread_index_per_process: &'a [usize],
685}
686
687impl<'a> Serialize for SerializableProfileCountersProperty<'a> {
688 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
689 let mut seq = serializer.serialize_seq(Some(self.counters.len()))?;
690
691 for counter in self.counters {
692 let main_thread_index = self.first_thread_index_per_process[counter.process().0];
693 seq.serialize_element(&counter.as_serializable(main_thread_index))?;
694 }
695
696 seq.end()
697 }
698}
699
700struct SerializableProfileThread<'a>(&'a Process, &'a Thread, &'a [Category]);
701
702impl<'a> Serialize for SerializableProfileThread<'a> {
703 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
704 let SerializableProfileThread(process, thread, categories) = self;
705 let process_start_time = process.start_time();
706 let process_end_time = process.end_time();
707 let process_name = process.name();
708 let pid = process.pid();
709 thread.serialize_with(
710 serializer,
711 categories,
712 process_start_time,
713 process_end_time,
714 process_name,
715 pid,
716 )
717 }
718}