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#[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 let allocations = self.native_allocations.get_or_insert_with(Default::default);
144
145 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}