fxprof_processed_profile/
thread.rs

1use std::borrow::Cow;
2use std::cmp::Ordering;
3
4use serde::ser::{SerializeMap, Serializer};
5
6use crate::cpu_delta::CpuDelta;
7use crate::frame_table::{FrameTable, InternalFrame};
8use crate::func_table::FuncTable;
9use crate::global_lib_table::GlobalLibTable;
10use crate::marker_table::MarkerTable;
11use crate::markers::InternalMarkerSchema;
12use crate::native_symbols::NativeSymbols;
13use crate::resource_table::ResourceTable;
14use crate::sample_table::{NativeAllocationsTable, SampleTable, WeightType};
15use crate::stack_table::StackTable;
16use crate::string_table::{GlobalStringIndex, GlobalStringTable};
17use crate::thread_string_table::{ThreadInternalStringIndex, ThreadStringTable};
18use crate::{CategoryHandle, Marker, MarkerHandle, MarkerTiming, MarkerTypeHandle, Timestamp};
19
20/// A process. Can be created with [`Profile::add_process`](crate::Profile::add_process).
21#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
22pub struct ProcessHandle(pub(crate) usize);
23
24#[derive(Debug)]
25pub struct Thread {
26    process: ProcessHandle,
27    tid: String,
28    name: Option<String>,
29    start_time: Timestamp,
30    end_time: Option<Timestamp>,
31    is_main: bool,
32    stack_table: StackTable,
33    frame_table: FrameTable,
34    func_table: FuncTable,
35    samples: SampleTable,
36    native_allocations: Option<NativeAllocationsTable>,
37    markers: MarkerTable,
38    resources: ResourceTable,
39    native_symbols: NativeSymbols,
40    string_table: ThreadStringTable,
41    last_sample_stack: Option<usize>,
42    last_sample_was_zero_cpu: bool,
43    show_markers_in_timeline: bool,
44}
45
46impl Thread {
47    pub fn new(process: ProcessHandle, tid: String, start_time: Timestamp, is_main: bool) -> Self {
48        Self {
49            process,
50            tid,
51            name: None,
52            start_time,
53            end_time: None,
54            is_main,
55            stack_table: StackTable::new(),
56            frame_table: FrameTable::new(),
57            func_table: FuncTable::new(),
58            samples: SampleTable::new(),
59            native_allocations: None,
60            markers: MarkerTable::new(),
61            resources: ResourceTable::new(),
62            native_symbols: NativeSymbols::new(),
63            string_table: ThreadStringTable::new(),
64            last_sample_stack: None,
65            last_sample_was_zero_cpu: false,
66            show_markers_in_timeline: false,
67        }
68    }
69
70    pub fn set_name(&mut self, name: &str) {
71        self.name = Some(name.to_string());
72    }
73
74    pub fn set_start_time(&mut self, start_time: Timestamp) {
75        self.start_time = start_time;
76    }
77
78    pub fn set_end_time(&mut self, end_time: Timestamp) {
79        self.end_time = Some(end_time);
80    }
81
82    pub fn set_tid(&mut self, tid: String) {
83        self.tid = tid;
84    }
85
86    pub fn set_show_markers_in_timeline(&mut self, v: bool) {
87        self.show_markers_in_timeline = v;
88    }
89
90    pub fn process(&self) -> ProcessHandle {
91        self.process
92    }
93
94    pub fn convert_string_index(
95        &mut self,
96        global_table: &GlobalStringTable,
97        index: GlobalStringIndex,
98    ) -> ThreadInternalStringIndex {
99        self.string_table
100            .index_for_global_string(index, global_table)
101    }
102
103    pub fn frame_index_for_frame(
104        &mut self,
105        frame: InternalFrame,
106        global_libs: &mut GlobalLibTable,
107    ) -> usize {
108        self.frame_table.index_for_frame(
109            &mut self.string_table,
110            &mut self.resources,
111            &mut self.func_table,
112            &mut self.native_symbols,
113            global_libs,
114            frame,
115        )
116    }
117
118    pub fn stack_index_for_stack(&mut self, prefix: Option<usize>, frame: usize) -> usize {
119        self.stack_table.index_for_stack(prefix, frame)
120    }
121
122    pub fn add_sample(
123        &mut self,
124        timestamp: Timestamp,
125        stack_index: Option<usize>,
126        cpu_delta: CpuDelta,
127        weight: i32,
128    ) {
129        self.samples
130            .add_sample(timestamp, stack_index, cpu_delta, weight);
131        self.last_sample_stack = stack_index;
132        self.last_sample_was_zero_cpu = cpu_delta == CpuDelta::ZERO;
133    }
134
135    pub fn add_allocation_sample(
136        &mut self,
137        timestamp: Timestamp,
138        stack_index: Option<usize>,
139        allocation_address: u64,
140        allocation_size: i64,
141    ) {
142        // Create allocations table, if it doesn't exist yet.
143        let allocations = self.native_allocations.get_or_insert_with(Default::default);
144
145        // Add the allocation sample.
146        allocations.add_sample(timestamp, stack_index, allocation_address, allocation_size);
147    }
148
149    pub fn add_sample_same_stack_zero_cpu(&mut self, timestamp: Timestamp, weight: i32) {
150        if self.last_sample_was_zero_cpu {
151            self.samples.modify_last_sample(timestamp, weight);
152        } else {
153            let stack_index = self.last_sample_stack;
154            self.samples
155                .add_sample(timestamp, stack_index, CpuDelta::ZERO, weight);
156            self.last_sample_was_zero_cpu = true;
157        }
158    }
159
160    pub fn set_samples_weight_type(&mut self, t: WeightType) {
161        self.samples.set_weight_type(t);
162    }
163
164    #[allow(clippy::too_many_arguments)]
165    pub fn add_marker<T: Marker>(
166        &mut self,
167        name_string_index: ThreadInternalStringIndex,
168        marker_type_handle: MarkerTypeHandle,
169        schema: &InternalMarkerSchema,
170        marker: T,
171        timing: MarkerTiming,
172        category: CategoryHandle,
173        global_string_table: &mut GlobalStringTable,
174    ) -> MarkerHandle {
175        self.markers.add_marker(
176            name_string_index,
177            marker_type_handle,
178            schema,
179            marker,
180            timing,
181            category,
182            &mut self.string_table,
183            global_string_table,
184        )
185    }
186
187    pub fn set_marker_stack(&mut self, marker: MarkerHandle, stack_index: Option<usize>) {
188        self.markers.set_marker_stack(marker, stack_index);
189    }
190
191    pub fn contains_js_function(&self) -> bool {
192        self.func_table.contains_js_function()
193    }
194
195    pub fn cmp_for_json_order(&self, other: &Thread) -> Ordering {
196        let ordering = (!self.is_main).cmp(&(!other.is_main));
197        if ordering != Ordering::Equal {
198            return ordering;
199        }
200        if let Some(ordering) = self.start_time.partial_cmp(&other.start_time) {
201            if ordering != Ordering::Equal {
202                return ordering;
203            }
204        }
205        let ordering = self.name.cmp(&other.name);
206        if ordering != Ordering::Equal {
207            return ordering;
208        }
209        self.tid.cmp(&other.tid)
210    }
211
212    #[allow(clippy::too_many_arguments)]
213    pub fn serialize_with<S: Serializer>(
214        &self,
215        serializer: S,
216        process_start_time: Timestamp,
217        process_end_time: Option<Timestamp>,
218        process_name: &str,
219        pid: &str,
220        marker_schemas: &[InternalMarkerSchema],
221        global_string_table: &GlobalStringTable,
222    ) -> Result<S::Ok, S::Error> {
223        let thread_name: Cow<str> = match (self.is_main, &self.name) {
224            (true, _) => process_name.into(),
225            (false, Some(name)) => name.into(),
226            (false, None) => format!("Thread <{}>", self.tid).into(),
227        };
228
229        let thread_register_time = self.start_time;
230        let thread_unregister_time = self.end_time;
231
232        let mut map = serializer.serialize_map(None)?;
233        map.serialize_entry("frameTable", &self.frame_table)?;
234        map.serialize_entry("funcTable", &self.func_table)?;
235        map.serialize_entry(
236            "markers",
237            &self
238                .markers
239                .as_serializable(marker_schemas, global_string_table),
240        )?;
241        map.serialize_entry("name", &thread_name)?;
242        map.serialize_entry("isMainThread", &self.is_main)?;
243        map.serialize_entry("nativeSymbols", &self.native_symbols)?;
244        map.serialize_entry("pausedRanges", &[] as &[()])?;
245        map.serialize_entry("pid", &pid)?;
246        map.serialize_entry("processName", process_name)?;
247        map.serialize_entry("processShutdownTime", &process_end_time)?;
248        map.serialize_entry("processStartupTime", &process_start_time)?;
249        map.serialize_entry("processType", &"default")?;
250        map.serialize_entry("registerTime", &thread_register_time)?;
251        map.serialize_entry("resourceTable", &self.resources)?;
252        map.serialize_entry("samples", &self.samples)?;
253        if let Some(allocations) = &self.native_allocations {
254            map.serialize_entry("nativeAllocations", &allocations)?;
255        }
256        map.serialize_entry("stackTable", &self.stack_table)?;
257        map.serialize_entry("stringArray", &self.string_table)?;
258        map.serialize_entry("tid", &self.tid)?;
259        map.serialize_entry("unregisterTime", &thread_unregister_time)?;
260        map.serialize_entry("showMarkersInTimeline", &self.show_markers_in_timeline)?;
261        map.end()
262    }
263}