1use crate::{
2 NanoSecond, Reader, Result, Scope, ScopeCollection, ScopeId, Stream, ThreadInfo,
3 UnpackedFrameData,
4};
5use std::{collections::BTreeMap, hash::Hash};
6
7#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
9struct MergeId<'s> {
10 id: ScopeId,
11 data: &'s str,
12}
13
14#[derive(Default)]
16struct MergeNode<'s> {
17 pieces: Vec<MergePiece<'s>>,
20
21 children: BTreeMap<MergeId<'s>, MergeNode<'s>>,
23}
24#[derive(Clone, Copy, Debug, PartialEq)]
25struct MergePiece<'s> {
26 pub relative_start_ns: NanoSecond,
28 pub scope: Scope<'s>,
30}
31
32#[derive(Clone, Debug, PartialEq)]
34pub struct MergeScope<'s> {
35 pub relative_start_ns: NanoSecond,
37 pub total_duration_ns: NanoSecond,
39 pub duration_per_frame_ns: NanoSecond,
41 pub max_duration_ns: NanoSecond,
43 pub num_pieces: usize,
45 pub id: ScopeId,
47 pub data: std::borrow::Cow<'s, str>,
49 pub children: Vec<MergeScope<'s>>,
51}
52
53impl MergeScope<'_> {
54 pub fn into_owned(self) -> MergeScope<'static> {
56 MergeScope::<'static> {
57 relative_start_ns: self.relative_start_ns,
58 total_duration_ns: self.total_duration_ns,
59 duration_per_frame_ns: self.duration_per_frame_ns,
60 max_duration_ns: self.max_duration_ns,
61 num_pieces: self.num_pieces,
62 id: self.id,
63 data: std::borrow::Cow::Owned(self.data.into_owned()),
64 children: self.children.into_iter().map(Self::into_owned).collect(),
65 }
66 }
67}
68
69impl<'s> MergeNode<'s> {
70 fn add<'slf>(&'slf mut self, stream: &'s Stream, piece: MergePiece<'s>) -> Result<()> {
71 self.pieces.push(piece);
72
73 for child in Reader::with_offset(stream, piece.scope.child_begin_position)? {
74 let child = child?;
75
76 self.children
77 .entry(MergeId {
78 id: child.id,
79 data: child.record.data,
80 })
81 .or_default()
82 .add(
83 stream,
84 MergePiece {
85 relative_start_ns: child.record.start_ns - piece.scope.record.start_ns,
86 scope: child,
87 },
88 )?;
89 }
90
91 Ok(())
92 }
93
94 fn build(self, scope_collection: &ScopeCollection, num_frames: i64) -> MergeScope<'s> {
95 assert!(!self.pieces.is_empty());
96 let mut relative_start_ns = self.pieces[0].relative_start_ns;
97 let mut total_duration_ns = 0;
98 let mut slowest_ns = 0;
99 let num_pieces = self.pieces.len();
100 let id = self.pieces[0].scope.id;
101 let mut data = self.pieces[0].scope.record.data;
102
103 for piece in &self.pieces {
104 relative_start_ns = relative_start_ns.min(piece.relative_start_ns);
106 total_duration_ns += piece.scope.record.duration_ns;
107 slowest_ns = slowest_ns.max(piece.scope.record.duration_ns);
108
109 assert_eq!(id, piece.scope.id);
110 if data != piece.scope.record.data {
111 data = ""; }
113 }
114
115 MergeScope {
116 relative_start_ns,
117 total_duration_ns,
118 duration_per_frame_ns: total_duration_ns / num_frames,
119 max_duration_ns: slowest_ns,
120 num_pieces,
121 id,
122 data: data.into(),
123 children: build(scope_collection, self.children, num_frames),
124 }
125 }
126}
127
128fn build<'s>(
129 scope_collection: &ScopeCollection,
130 nodes: BTreeMap<MergeId<'s>, MergeNode<'s>>,
131 num_frames: i64,
132) -> Vec<MergeScope<'s>> {
133 let mut scopes: Vec<_> = nodes
134 .into_values()
135 .map(|node| node.build(scope_collection, num_frames))
136 .collect();
137
138 scopes.sort_by_key(|scope| scope.relative_start_ns);
140
141 let mut relative_ns = 0;
143 for scope in &mut scopes {
144 scope.relative_start_ns = scope.relative_start_ns.max(relative_ns);
145 relative_ns = scope.relative_start_ns + scope.duration_per_frame_ns;
146 }
147
148 scopes
149}
150
151pub fn merge_scopes_for_thread<'s>(
153 scope_collection: &ScopeCollection,
154 frames: &'s [std::sync::Arc<UnpackedFrameData>],
155 thread_info: &ThreadInfo,
156) -> Result<Vec<MergeScope<'s>>> {
157 let mut top_nodes: BTreeMap<MergeId<'s>, MergeNode<'s>> = Default::default();
158
159 for frame in frames {
160 if let Some(stream_info) = frame.thread_streams.get(thread_info) {
161 let offset_ns = frame.meta.range_ns.0 - frames[0].meta.range_ns.0; let top_scopes = Reader::from_start(&stream_info.stream).read_top_scopes()?;
164 for scope in top_scopes {
165 top_nodes
166 .entry(MergeId {
167 id: scope.id,
168 data: scope.record.data,
169 })
170 .or_default()
171 .add(
172 &stream_info.stream,
173 MergePiece {
174 relative_start_ns: scope.record.start_ns - offset_ns,
175 scope,
176 },
177 )?;
178 }
179 }
180 }
181
182 Ok(build(scope_collection, top_nodes, frames.len() as _))
183}
184
185#[cfg(test)]
186mod tests {
187 use std::{collections::BTreeMap, sync::Arc};
188
189 #[test]
190 fn test_merge() {
191 use crate::*;
192
193 let mut scope_collection = ScopeCollection::default();
194 scope_collection.insert(Arc::new(
196 ScopeDetails::from_scope_id(ScopeId::new(1)).with_function_name("a"),
197 ));
198 scope_collection.insert(Arc::new(
199 ScopeDetails::from_scope_id(ScopeId::new(2)).with_function_name("b"),
200 ));
201
202 scope_collection.insert(Arc::new(
204 ScopeDetails::from_scope_id(ScopeId::new(3)).with_function_name("ba"),
205 ));
206 scope_collection.insert(Arc::new(
207 ScopeDetails::from_scope_id(ScopeId::new(4)).with_function_name("bb"),
208 ));
209 scope_collection.insert(Arc::new(
210 ScopeDetails::from_scope_id(ScopeId::new(5)).with_function_name("bba"),
211 ));
212
213 let stream = {
214 let mut stream = Stream::default();
215
216 for i in 0..2 {
217 let ns = 1000 * i;
218
219 let (a, _) = stream.begin_scope(|| ns + 100, ScopeId::new(1), "");
220 stream.end_scope(a, ns + 200);
221
222 let (b, _) = stream.begin_scope(|| ns + 200, ScopeId::new(2), "");
223
224 let (ba, _) = stream.begin_scope(|| ns + 400, ScopeId::new(3), "");
225 stream.end_scope(ba, ns + 600);
226
227 let (bb, _) = stream.begin_scope(|| ns + 600, ScopeId::new(4), "");
228 let (bba, _) = stream.begin_scope(|| ns + 600, ScopeId::new(5), "");
229 stream.end_scope(bba, ns + 700);
230 stream.end_scope(bb, ns + 800);
231 stream.end_scope(b, ns + 900);
232 }
233
234 stream
235 };
236
237 let stream_info = StreamInfo::parse(stream).unwrap();
238 let mut thread_streams = BTreeMap::new();
239 let thread_info = ThreadInfo {
240 start_time_ns: Some(0),
241 name: "main".to_owned(),
242 };
243 thread_streams.insert(thread_info.clone(), stream_info);
244 let frame = UnpackedFrameData::new(0, thread_streams).unwrap();
245 let frames = [Arc::new(frame)];
246 let merged = merge_scopes_for_thread(&scope_collection, &frames, &thread_info).unwrap();
247
248 let expected = vec![
249 MergeScope {
250 relative_start_ns: 100,
251 total_duration_ns: 2 * 100,
252 duration_per_frame_ns: 2 * 100,
253 max_duration_ns: 100,
254 num_pieces: 2,
255 id: ScopeId::new(1),
256 data: "".into(),
257 children: vec![],
258 },
259 MergeScope {
260 relative_start_ns: 300, total_duration_ns: 2 * 700,
262 duration_per_frame_ns: 2 * 700,
263 max_duration_ns: 700,
264 num_pieces: 2,
265 id: ScopeId::new(2),
266 data: "".into(),
267 children: vec![
268 MergeScope {
269 relative_start_ns: 200,
270 total_duration_ns: 2 * 200,
271 duration_per_frame_ns: 2 * 200,
272 max_duration_ns: 200,
273 num_pieces: 2,
274 id: ScopeId::new(3),
275 data: "".into(),
276 children: vec![],
277 },
278 MergeScope {
279 relative_start_ns: 600,
280 total_duration_ns: 2 * 200,
281 duration_per_frame_ns: 2 * 200,
282 max_duration_ns: 200,
283 num_pieces: 2,
284 id: ScopeId::new(4),
285 data: "".into(),
286 children: vec![MergeScope {
287 relative_start_ns: 0,
288 total_duration_ns: 2 * 100,
289 duration_per_frame_ns: 2 * 100,
290 max_duration_ns: 100,
291 num_pieces: 2,
292 id: ScopeId::new(5),
293 data: "".into(),
294 children: vec![],
295 }],
296 },
297 ],
298 },
299 ];
300
301 assert_eq!(
302 merged, expected,
303 "\nGot:\n{merged:#?}\n\n!=\nExpected:\n{expected:#?}",
304 );
305 }
306}