fxprof_processed_profile/
thread.rs

1use std::borrow::Cow;
2use std::cmp::Ordering;
3
4use serde::ser::{SerializeMap, Serializer};
5use serde_json::json;
6
7use crate::category::{Category, CategoryPairHandle};
8use crate::cpu_delta::CpuDelta;
9use crate::frame_table::{FrameTable, InternalFrame};
10use crate::func_table::FuncTable;
11use crate::global_lib_table::GlobalLibTable;
12use crate::marker_table::MarkerTable;
13use crate::native_symbols::NativeSymbols;
14use crate::resource_table::ResourceTable;
15use crate::sample_table::SampleTable;
16use crate::stack_table::StackTable;
17use crate::string_table::{GlobalStringIndex, GlobalStringTable};
18use crate::thread_string_table::{ThreadInternalStringIndex, ThreadStringTable};
19use crate::{MarkerTiming, ProfilerMarker, Timestamp};
20
21/// A process. Can be created with [`Profile::add_process`](crate::Profile::add_process).
22#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
23pub struct ProcessHandle(pub(crate) usize);
24
25#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
26pub struct CounterHandle(pub(crate) usize);
27
28#[derive(Debug)]
29pub struct Thread {
30    process: ProcessHandle,
31    tid: String,
32    name: Option<String>,
33    start_time: Timestamp,
34    end_time: Option<Timestamp>,
35    is_main: bool,
36    stack_table: StackTable,
37    frame_table: FrameTable,
38    func_table: FuncTable,
39    samples: SampleTable,
40    markers: MarkerTable,
41    resources: ResourceTable,
42    native_symbols: NativeSymbols,
43    string_table: ThreadStringTable,
44    last_sample_stack: Option<usize>,
45    last_sample_was_zero_cpu: bool,
46}
47
48impl Thread {
49    pub fn new(process: ProcessHandle, tid: String, start_time: Timestamp, is_main: bool) -> Self {
50        Self {
51            process,
52            tid,
53            name: None,
54            start_time,
55            end_time: None,
56            is_main,
57            stack_table: StackTable::new(),
58            frame_table: FrameTable::new(),
59            func_table: FuncTable::new(),
60            samples: SampleTable::new(),
61            markers: MarkerTable::new(),
62            resources: ResourceTable::new(),
63            native_symbols: NativeSymbols::new(),
64            string_table: ThreadStringTable::new(),
65            last_sample_stack: None,
66            last_sample_was_zero_cpu: 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 process(&self) -> ProcessHandle {
83        self.process
84    }
85
86    pub fn convert_string_index(
87        &mut self,
88        global_table: &GlobalStringTable,
89        index: GlobalStringIndex,
90    ) -> ThreadInternalStringIndex {
91        self.string_table
92            .index_for_global_string(index, global_table)
93    }
94
95    pub fn frame_index_for_frame(
96        &mut self,
97        frame: InternalFrame,
98        global_libs: &GlobalLibTable,
99    ) -> usize {
100        self.frame_table.index_for_frame(
101            &mut self.string_table,
102            &mut self.resources,
103            &mut self.func_table,
104            &mut self.native_symbols,
105            global_libs,
106            frame,
107        )
108    }
109
110    pub fn stack_index_for_stack(
111        &mut self,
112        prefix: Option<usize>,
113        frame: usize,
114        category_pair: CategoryPairHandle,
115    ) -> usize {
116        self.stack_table
117            .index_for_stack(prefix, frame, category_pair)
118    }
119
120    pub fn add_sample(
121        &mut self,
122        timestamp: Timestamp,
123        stack_index: Option<usize>,
124        cpu_delta: CpuDelta,
125        weight: i32,
126    ) {
127        self.samples
128            .add_sample(timestamp, stack_index, cpu_delta, weight);
129        self.last_sample_stack = stack_index;
130        self.last_sample_was_zero_cpu = cpu_delta == CpuDelta::ZERO;
131    }
132
133    pub fn add_sample_same_stack_zero_cpu(&mut self, timestamp: Timestamp, weight: i32) {
134        if self.last_sample_was_zero_cpu {
135            self.samples.modify_last_sample(timestamp, weight);
136        } else {
137            let stack_index = self.last_sample_stack;
138            self.samples
139                .add_sample(timestamp, stack_index, CpuDelta::ZERO, weight);
140            self.last_sample_was_zero_cpu = true;
141        }
142    }
143
144    pub fn add_marker<T: ProfilerMarker>(
145        &mut self,
146        name: &str,
147        marker: T,
148        timing: MarkerTiming,
149        stack_index: Option<usize>,
150    ) {
151        let name_string_index = self.string_table.index_for_string(name);
152        let mut data = marker.json_marker_data();
153        if let Some(stack_index) = stack_index {
154            if let Some(obj) = data.as_object_mut() {
155                obj.insert("cause".to_string(), json!({ "stack": stack_index }));
156            }
157        }
158        self.markers.add_marker(name_string_index, timing, data);
159    }
160
161    pub fn contains_js_function(&self) -> bool {
162        self.func_table.contains_js_function()
163    }
164
165    pub fn cmp_for_json_order(&self, other: &Thread) -> Ordering {
166        let ordering = (!self.is_main).cmp(&(!other.is_main));
167        if ordering != Ordering::Equal {
168            return ordering;
169        }
170        if let Some(ordering) = self.start_time.partial_cmp(&other.start_time) {
171            if ordering != Ordering::Equal {
172                return ordering;
173            }
174        }
175        let ordering = self.name.cmp(&other.name);
176        if ordering != Ordering::Equal {
177            return ordering;
178        }
179        self.tid.cmp(&other.tid)
180    }
181
182    pub fn serialize_with<S: Serializer>(
183        &self,
184        serializer: S,
185        categories: &[Category],
186        process_start_time: Timestamp,
187        process_end_time: Option<Timestamp>,
188        process_name: &str,
189        pid: &str,
190    ) -> Result<S::Ok, S::Error> {
191        let thread_name: Cow<str> = match (self.is_main, &self.name) {
192            (true, _) => process_name.into(),
193            (false, Some(name)) => name.into(),
194            (false, None) => format!("Thread <{}>", self.tid).into(),
195        };
196
197        let thread_register_time = self.start_time;
198        let thread_unregister_time = self.end_time;
199
200        let mut map = serializer.serialize_map(None)?;
201        map.serialize_entry("frameTable", &self.frame_table.as_serializable(categories))?;
202        map.serialize_entry("funcTable", &self.func_table)?;
203        map.serialize_entry("markers", &self.markers)?;
204        map.serialize_entry("name", &thread_name)?;
205        map.serialize_entry("isMainThread", &self.is_main)?;
206        map.serialize_entry("nativeSymbols", &self.native_symbols)?;
207        map.serialize_entry("pausedRanges", &[] as &[()])?;
208        map.serialize_entry("pid", &pid)?;
209        map.serialize_entry("processName", process_name)?;
210        map.serialize_entry("processShutdownTime", &process_end_time)?;
211        map.serialize_entry("processStartupTime", &process_start_time)?;
212        map.serialize_entry("processType", &"default")?;
213        map.serialize_entry("registerTime", &thread_register_time)?;
214        map.serialize_entry("resourceTable", &self.resources)?;
215        map.serialize_entry("samples", &self.samples)?;
216        map.serialize_entry(
217            "stackTable",
218            &self.stack_table.serialize_with_categories(categories),
219        )?;
220        map.serialize_entry("stringArray", &self.string_table)?;
221        map.serialize_entry("tid", &self.tid)?;
222        map.serialize_entry("unregisterTime", &thread_unregister_time)?;
223        map.end()
224    }
225}