1use std::{fmt::Display, sync::Arc};
6
7use ahash::HashMap;
8use smallvec::SmallVec;
9use tracing_tape::{
10 intro::Intro,
11 record::{
12 field_type, record_kind, CallsiteFieldRecord, CallsiteRecord, EventRecord,
13 EventValueRecord, RecordHeader, SpanCloseRecord, SpanEnterRecord, SpanExitRecord,
14 SpanOpenRecord, SpanOpenRecord2, SpanValueRecord,
15 },
16};
17use zerocopy::FromBytes;
18
19#[derive(Debug)]
20pub enum Value {
21 Bool(bool),
22 I64(i64),
23 U64(u64),
24 I128(i128),
25 U128(u128),
26 F64(f64),
27 String(Arc<str>),
28 Error(Arc<str>),
29}
30
31impl Value {
32 fn parse(kind: u8, data: &[u8]) -> Self {
33 match kind {
34 field_type::BOOL => Value::Bool(data[0] != 0),
35 field_type::I64 => Value::I64(i64::from_le_bytes(data.try_into().unwrap())),
36 field_type::U64 => Value::U64(u64::from_le_bytes(data.try_into().unwrap())),
37 field_type::I128 => Value::I128(i128::from_le_bytes(data.try_into().unwrap())),
38 field_type::U128 => Value::U128(u128::from_le_bytes(data.try_into().unwrap())),
39 field_type::F64 => Value::F64(f64::from_le_bytes(data.try_into().unwrap())),
40 field_type::STR => {
41 let value = Arc::from(String::from_utf8_lossy(data));
42 Value::String(value)
43 }
44 field_type::ERROR => {
45 let value = Arc::from(String::from_utf8_lossy(data));
46 Value::Error(value)
47 }
48 _ => {
49 panic!("unknown field type: {}", kind);
50 }
51 }
52 }
53}
54
55impl Display for Value {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 match self {
58 Value::Bool(value) => value.fmt(f),
59 Value::I64(value) => value.fmt(f),
60 Value::U64(value) => value.fmt(f),
61 Value::I128(value) => value.fmt(f),
62 Value::U128(value) => value.fmt(f),
63 Value::F64(value) => value.fmt(f),
64 Value::String(value) => value.fmt(f),
65 Value::Error(value) => value.fmt(f),
66 }
67 }
68}
69
70#[derive(Debug)]
71struct Intermediate {
72 min_timestamp: i64,
73 max_timestamp: i64,
74
75 intermediate_callsites: HashMap<u64, IntermediateCallsite>,
77
78 callsites: Vec<IntermediateCallsite>,
80
81 intermediate_events: HashMap<u64, IntermediateEvent>,
85
86 events: Vec<IntermediateEvent>,
88
89 span_graph:
90 petgraph::stable_graph::StableGraph<IntermediateSpan, (), petgraph::Directed, usize>,
91 root_nodes: Vec<petgraph::stable_graph::NodeIndex<usize>>,
92 opened_spans: HashMap<u64, petgraph::stable_graph::NodeIndex<usize>>,
93 context: HashMap<u64, Vec<petgraph::stable_graph::NodeIndex<usize>>>,
94 threads: HashMap<u64, Option<String>>,
95}
96
97impl Default for Intermediate {
98 fn default() -> Self {
99 Self {
100 min_timestamp: i64::MAX,
101 max_timestamp: i64::MIN,
102
103 intermediate_callsites: HashMap::default(),
104 callsites: Vec::new(),
105
106 intermediate_events: HashMap::default(),
107 events: Vec::new(),
108
109 span_graph: Default::default(),
110 root_nodes: Vec::new(),
111 opened_spans: HashMap::default(),
112 threads: HashMap::default(),
113 context: HashMap::default(),
114 }
115 }
116}
117
118impl Intermediate {
119 fn callsite<'a>(&mut self, slice: &'a [u8]) -> &'a [u8] {
120 let (callsite, remaining) = IntermediateCallsite::parse(slice);
121
122 if callsite.fields.capacity() == 0 {
123 self.callsites.push(callsite);
124 } else {
125 self.intermediate_callsites.insert(callsite.id, callsite);
126 }
127 remaining
128 }
129
130 fn callsite_field<'a>(&mut self, slice: &'a [u8]) -> &'a [u8] {
131 let callsite_field_record = CallsiteFieldRecord::ref_from_prefix(slice).unwrap();
132
133 let name_len = callsite_field_record.field_name_len.get() as usize;
134 let offset = std::mem::size_of::<CallsiteFieldRecord>();
135 let name = &slice[offset..offset + name_len];
136 let name = Arc::from(String::from_utf8_lossy(name));
137
138 let callsite_id = callsite_field_record.callsite_id.get();
139 let mut callsite = self.intermediate_callsites.remove(&callsite_id).unwrap();
140 callsite.fields.push(Field {
141 name,
142 id: callsite_field_record.field_id.get(),
143 });
144 if callsite.fields.len() == callsite.fields.capacity() {
145 self.callsites.push(callsite);
146 } else {
147 self.intermediate_callsites.insert(callsite_id, callsite);
148 }
149
150 &slice[callsite_field_record.header.len.get() as usize..]
151 }
152
153 fn event<'a>(&mut self, slice: &'a [u8]) -> &'a [u8] {
154 let event_record = EventRecord::ref_from_prefix(slice).unwrap();
155
156 self.threads
158 .entry(event_record.thread_id.get())
159 .or_insert(None);
160
161 let event = IntermediateEvent {
162 timestamp: event_record.timestamp.get(),
163 callsite_id: event_record.callsite_id.get(),
164 values: Vec::with_capacity(event_record.value_count.get() as usize),
165 };
166
167 let thread_id = event_record.thread_id.get();
168 assert!(!self.intermediate_events.contains_key(&thread_id));
169
170 if event.values.capacity() == 0 {
171 self.events.push(event);
172 } else {
173 self.intermediate_events.insert(thread_id, event);
174 }
175
176 self.min_timestamp = self.min_timestamp.min(event_record.timestamp.get());
177 self.max_timestamp = self.max_timestamp.max(event_record.timestamp.get());
178
179 &slice[event_record.header.len.get() as usize..]
180 }
181
182 fn event_value<'a>(&mut self, slice: &'a [u8]) -> &'a [u8] {
183 let event_value_record = EventValueRecord::ref_from_prefix(slice).unwrap();
184
185 let value_len =
186 event_value_record.header.len.get() as usize - std::mem::size_of::<EventValueRecord>();
187 let value = &slice[std::mem::size_of::<EventValueRecord>()..][..value_len];
188
189 let kind = event_value_record.kind;
190 let value = Value::parse(kind, value);
191
192 let thread_id = event_value_record.thread_id.get();
193 let mut event = self.intermediate_events.remove(&thread_id).unwrap();
194
195 event.values.push(IntermediateValue {
197 value,
198 field_id: event_value_record.field_id.get(),
199 });
200
201 if event.values.len() == event.values.capacity() {
202 self.events.push(event);
203 } else {
204 self.intermediate_events.insert(thread_id, event);
205 }
206
207 &slice[event_value_record.header.len.get() as usize..]
208 }
209
210 fn open_span<'a>(&mut self, slice: &'a [u8]) -> &'a [u8] {
211 let span_record = if slice.len() >= std::mem::size_of::<SpanOpenRecord2>() {
212 SpanOpenRecord2::ref_from_prefix(slice).unwrap().clone()
213 } else if slice.len() >= std::mem::size_of::<SpanOpenRecord>() {
214 SpanOpenRecord::ref_from_prefix(slice)
215 .unwrap()
216 .clone()
217 .into()
218 } else {
219 panic!("invalid span record");
220 };
221
222 let span = IntermediateSpan {
223 id: span_record.span_open_record.id.get(),
224 opened: span_record.span_open_record.timestamp.get(),
225 closed: 0,
226 entrances: SmallVec::new(),
227 callsite_id: span_record.span_open_record.callsite_id.get(),
228 parent: Parent::Contextual,
229 values: HashMap::default(),
230 };
231
232 self.min_timestamp = self
233 .min_timestamp
234 .min(span_record.span_open_record.timestamp.get());
235 self.max_timestamp = self
236 .max_timestamp
237 .max(span_record.span_open_record.timestamp.get());
238
239 let span_id = span.id;
240 let index = self.span_graph.add_node(span);
241 self.opened_spans.insert(span_id, index);
242
243 &slice[span_record.span_open_record.header.len.get() as usize..]
244 }
245
246 fn enter_span<'a>(&mut self, slice: &'a [u8]) -> &'a [u8] {
247 let span_enter_record = SpanEnterRecord::ref_from_prefix(slice).unwrap();
248
249 self.threads
250 .entry(span_enter_record.thread_id.get())
251 .or_insert(None);
252
253 let index = self.opened_spans[&span_enter_record.id.get()];
254 let span = &mut self.span_graph[index];
255 let thread_id = span_enter_record.thread_id.get();
256 span.entrances.push(SpanEntrance {
257 entered: span_enter_record.timestamp.get(),
258 exited: 0,
259 thread_id,
260 });
261 self.context
262 .entry(thread_id)
263 .or_insert_with(Vec::new)
264 .push(index);
265
266 &slice[span_enter_record.header.len.get() as usize..]
267 }
268
269 fn exit_span<'a>(&mut self, slice: &'a [u8]) -> &'a [u8] {
270 let span_exit_record = SpanExitRecord::ref_from_prefix(slice).unwrap();
271
272 let index = self.opened_spans[&span_exit_record.id.get()];
273 let span = &mut self.span_graph[index];
274 let last_entrance = span.entrances.last_mut().unwrap();
275 last_entrance.exited = span_exit_record.timestamp.get();
276
277 assert_eq!(
278 self.context
279 .get_mut(&last_entrance.thread_id)
280 .unwrap()
281 .pop(),
282 Some(index)
283 );
284
285 &slice[span_exit_record.header.len.get() as usize..]
286 }
287
288 fn close_span<'a>(&mut self, slice: &'a [u8]) -> &'a [u8] {
289 let span_record = SpanCloseRecord::ref_from_prefix(slice).unwrap();
290
291 self.min_timestamp = self.min_timestamp.min(span_record.timestamp.get());
292 self.max_timestamp = self.max_timestamp.max(span_record.timestamp.get());
293
294 let span_index = self.opened_spans.remove(&span_record.id.get()).unwrap();
295 let span = &mut self.span_graph[span_index];
296 span.closed = span_record.timestamp.get();
297
298 if matches!(span.parent, Parent::Root) {
299 self.root_nodes.push(span_index);
300 } else if let Some(last_entrance) = span.entrances.last() {
301 let thread_id = last_entrance.thread_id;
302 if let Some(parent_index) = self.context[&thread_id].last() {
303 self.span_graph.add_edge(*parent_index, span_index, ());
304 } else {
305 self.root_nodes.push(span_index);
306 }
307 } else {
308 self.root_nodes.push(span_index);
309 }
310
311 &slice[span_record.header.len.get() as usize..]
312 }
313
314 fn span_value<'a>(&mut self, slice: &'a [u8]) -> &'a [u8] {
315 let span_value_record = SpanValueRecord::ref_from_prefix(slice).unwrap();
316
317 let value_len =
318 span_value_record.header.len.get() as usize - std::mem::size_of::<SpanValueRecord>();
319 let value = &slice[std::mem::size_of::<SpanValueRecord>()..][..value_len];
320
321 let kind = span_value_record.kind;
322 let value = Value::parse(kind, value);
323
324 let span_id = span_value_record.span_id.get();
325 let index = self.opened_spans[&span_id];
326 let span = &mut self.span_graph[index];
327 span.values.insert(span_value_record.field_id.get(), value);
328
329 &slice[span_value_record.header.len.get() as usize..]
330 }
331
332 fn parse(&mut self, mut data: &[u8]) -> Result<(), u8> {
333 while !data.is_empty() {
334 let record_kind = data[0];
335
336 match record_kind {
337 record_kind::NOOP => {
338 data = &data[1..];
339 }
340 record_kind::CALLSITE => {
341 data = self.callsite(data);
342 }
343 record_kind::CALLSITE_FIELD => {
344 data = self.callsite_field(data);
345 }
346 record_kind::SPAN_OPEN => {
347 data = self.open_span(data);
348 }
349 record_kind::SPAN_ENTER => {
350 data = self.enter_span(data);
351 }
352 record_kind::SPAN_EXIT => {
353 data = self.exit_span(data);
354 }
355 record_kind::SPAN_CLOSE => {
356 data = self.close_span(data);
357 }
358 record_kind::SPAN_VALUE => {
359 data = self.span_value(data);
360 }
361 record_kind::EVENT => {
362 data = self.event(data);
363 }
364 record_kind::EVENT_VALUE => {
365 data = self.event_value(data);
366 }
367 _ => {
368 let header = RecordHeader::ref_from_prefix(data).unwrap();
369 data = &data[header.len.get() as usize..];
370 }
371 }
372 }
373
374 Ok(())
375 }
376}
377
378#[derive(Debug)]
379struct IntermediateValue {
380 value: Value,
381 field_id: u64,
382}
383
384#[derive(Debug)]
385struct IntermediateEvent {
386 timestamp: i64,
387 callsite_id: u64,
388 values: Vec<IntermediateValue>,
389}
390
391#[derive(Debug)]
392enum Parent {
393 #[allow(dead_code)]
394 Root,
395 #[allow(dead_code)]
396 Explicit(u64),
397 Contextual,
398}
399
400#[derive(Debug)]
401struct IntermediateSpan {
402 id: u64,
403 opened: i64,
404 closed: i64,
405 entrances: SmallVec<[SpanEntrance; 1]>,
406 callsite_id: u64,
407 parent: Parent,
408 values: HashMap<u64, Value>,
409}
410
411#[derive(Debug)]
412pub struct SpanEntrance {
413 pub entered: i64,
414 pub exited: i64,
415 pub thread_id: u64,
416}
417
418#[derive(Debug)]
419pub struct Span {
420 pub opened: i64,
421 pub closed: i64,
422 pub callsite_index: usize,
423 pub entrances: Arc<[SpanEntrance]>,
424 pub values: Arc<[Value]>,
425}
426
427#[derive(Debug)]
428pub struct Event {
429 pub timestamp: i64,
430 pub callsite_index: usize,
431 pub values: Arc<[Value]>,
432}
433
434#[derive(Debug, Hash, PartialEq, Eq, Clone)]
435pub struct Field {
436 name: Arc<str>,
437 id: u64,
438}
439
440#[derive(Debug)]
441struct IntermediateCallsite {
442 id: u64,
443 kind: tracing::metadata::Kind,
444 level: tracing::Level,
445 name: Arc<str>,
446 target: Arc<str>,
447 module_path: Arc<str>,
448 file: Option<Arc<str>>,
449 line: Option<u32>,
450 fields: Vec<Field>,
451}
452
453#[derive(Debug, Hash, PartialEq, Eq, Clone)]
454pub struct Metadata {
455 pub level: tracing::Level,
456 pub name: Arc<str>,
457 pub target: Arc<str>,
458 pub module_path: Arc<str>,
459 pub file: Option<Arc<str>>,
460 pub line: Option<u32>,
461 pub fields: Arc<[Arc<str>]>,
462}
463
464impl From<IntermediateCallsite> for Metadata {
465 fn from(value: IntermediateCallsite) -> Self {
466 Self {
467 level: value.level,
468 name: value.name,
469 target: value.target,
470 module_path: value.module_path,
471 file: value.file,
472 line: value.line,
473 fields: value
474 .fields
475 .into_iter()
476 .map(|field| field.name)
477 .collect::<Vec<_>>()
478 .into(),
479 }
480 }
481}
482
483#[derive(Debug, Hash, PartialEq, Eq, Clone)]
484pub enum Callsite {
485 Event(Metadata),
486 Span(Metadata),
487}
488
489impl Callsite {
490 pub fn metadata(&self) -> &Metadata {
491 match self {
492 Self::Event(metadata) => metadata,
493 Self::Span(metadata) => metadata,
494 }
495 }
496
497 pub fn kind(&self) -> tracing::metadata::Kind {
498 match self {
499 Self::Event(_) => tracing::metadata::Kind::EVENT,
500 Self::Span(_) => tracing::metadata::Kind::SPAN,
501 }
502 }
503
504 pub fn level(&self) -> tracing::Level {
505 self.metadata().level
506 }
507
508 pub fn name(&self) -> &str {
509 &self.metadata().name
510 }
511
512 pub fn target(&self) -> &str {
513 &self.metadata().target
514 }
515
516 pub fn module_path(&self) -> &str {
517 &self.metadata().module_path
518 }
519
520 pub fn file(&self) -> Option<&str> {
521 self.metadata().file.as_deref()
522 }
523
524 pub fn line(&self) -> Option<u32> {
525 self.metadata().line
526 }
527
528 pub fn fields(&self) -> &[Arc<str>] {
529 &self.metadata().fields
530 }
531}
532
533impl From<IntermediateCallsite> for Callsite {
534 fn from(value: IntermediateCallsite) -> Self {
535 if value.kind == tracing::metadata::Kind::SPAN {
536 Self::Span(value.into())
537 } else {
538 Self::Event(value.into())
539 }
540 }
541}
542
543impl IntermediateCallsite {
544 fn parse(slice: &[u8]) -> (Self, &[u8]) {
545 let callsite_record = CallsiteRecord::ref_from_prefix(slice).unwrap();
546
547 let remaining = &slice[callsite_record.header.len.get() as usize..];
548
549 let slice = &slice[std::mem::size_of::<CallsiteRecord>()..];
550 let (name, slice) = slice.split_at(callsite_record.name_len.get() as usize);
551 let (target, slice) = slice.split_at(callsite_record.target_len.get() as usize);
552 let (module_path, slice) = slice.split_at(callsite_record.module_path_len.get() as usize);
553 let (file, _) = slice.split_at(callsite_record.file_len.get() as usize);
554
555 let name = Arc::from(String::from_utf8_lossy(name));
556 let target = Arc::from(String::from_utf8_lossy(target));
557 let module_path = Arc::from(String::from_utf8_lossy(module_path));
558 let file = if file.is_empty() {
559 None
560 } else {
561 Some(Arc::from(String::from_utf8_lossy(file)))
562 };
563 let line = if callsite_record.line.get() == 0 {
564 None
565 } else {
566 Some(callsite_record.line.get())
567 };
568
569 let callsite = Self {
570 id: callsite_record.id.get(),
571 kind: callsite_record.info.kind().expect("invalid kind"),
572 level: callsite_record.info.level().expect("invalid level"),
573 name,
574 target,
575 module_path,
576 file,
577 line,
578 fields: Vec::with_capacity(callsite_record.field_count.get() as usize),
579 };
580
581 (callsite, remaining)
582 }
583}
584
585#[derive(Debug)]
586pub struct TapeData {
587 min_timestamp: i64,
588 max_timestamp: i64,
589 callsites: Vec<Callsite>,
590 events: Vec<Event>,
591 spans: petgraph::graph::Graph<Span, (), petgraph::Directed, usize>,
592 root_spans: Vec<petgraph::graph::NodeIndex<usize>>,
593 threads: HashMap<u64, Option<String>>,
594}
595
596impl TapeData {
597 fn new(intermediate: Intermediate) -> Self {
598 let mut callsite_map = HashMap::default();
599 let mut callsite_field_map = HashMap::default();
600 let callsites = intermediate
601 .callsites
602 .into_iter()
603 .enumerate()
604 .map(|(index, callsite)| {
605 callsite_map.insert(callsite.id, index);
606
607 for (index, field) in callsite.fields.iter().enumerate() {
608 callsite_field_map.insert((callsite.id, field.id), index);
609 }
610
611 callsite.into()
612 })
613 .collect::<Vec<_>>();
614
615 let mut events = intermediate.events;
616 events.sort_by_key(|event| event.timestamp);
617 let events = events
618 .into_iter()
619 .map(|event| {
620 let mut values = event.values;
621 values.sort_by_cached_key(|value| {
622 callsite_field_map[&(event.callsite_id, value.field_id)]
623 });
624 let values = values
625 .into_iter()
626 .map(|value| value.value)
627 .collect::<Vec<_>>();
628
629 Event {
630 timestamp: event.timestamp,
631 callsite_index: callsite_map[&event.callsite_id],
632 values: Arc::from(values.into_boxed_slice()),
633 }
634 })
635 .collect();
636
637 struct SpanMapping {
638 old_children: Vec<petgraph::stable_graph::NodeIndex<usize>>,
639 new_parent: petgraph::graph::NodeIndex<usize>,
640 }
641
642 let mut root_nodes = vec![];
643 let mut intermediate_graph = intermediate.span_graph;
644 let mut spans = petgraph::Graph::with_capacity(
645 intermediate_graph.node_count(),
646 intermediate_graph.edge_count(),
647 );
648 let mut nodes_to_process = Vec::new();
649
650 for node in intermediate.root_nodes {
651 let children = intermediate_graph.neighbors(node).collect::<Vec<_>>();
652 let intermediate_span = intermediate_graph.remove_node(node).unwrap();
653
654 let callsite_index = callsite_map[&intermediate_span.callsite_id];
659 let mut values = intermediate_span.values.into_iter().collect::<Vec<_>>();
660 values.sort_by_cached_key(|(field_id, _)| {
661 callsite_field_map[&(intermediate_span.callsite_id, *field_id)]
662 });
663 let value = values
664 .into_iter()
665 .map(|(_, value)| value)
666 .collect::<Vec<_>>();
667
668 let span = Span {
669 callsite_index,
670 opened: intermediate_span.opened,
671 closed: intermediate_span.closed,
672 entrances: Arc::from(intermediate_span.entrances.into_boxed_slice()),
673 values: Arc::from(value.into_boxed_slice()),
674 };
675
676 let span_node = spans.add_node(span);
677 root_nodes.push(span_node);
678 if !children.is_empty() {
679 nodes_to_process.push(SpanMapping {
680 old_children: children,
681 new_parent: span_node,
682 });
683 }
684 }
685
686 while let Some(mapping) = nodes_to_process.pop() {
687 let children = mapping.old_children;
688 let parent = mapping.new_parent;
689
690 for child in children {
691 let children = intermediate_graph.neighbors(child).collect::<Vec<_>>();
692 let intermediate_span = intermediate_graph.remove_node(child).unwrap();
693
694 let callsite_index = callsite_map[&intermediate_span.callsite_id];
699 let mut values = intermediate_span.values.into_iter().collect::<Vec<_>>();
700 values.sort_by_cached_key(|(field_id, _)| {
701 callsite_field_map[&(intermediate_span.callsite_id, *field_id)]
702 });
703 let value = values
704 .into_iter()
705 .map(|(_, value)| value)
706 .collect::<Vec<_>>();
707
708 let span = Span {
709 callsite_index,
710 opened: intermediate_span.opened,
711 closed: intermediate_span.closed,
712 entrances: Arc::from(intermediate_span.entrances.into_boxed_slice()),
713 values: Arc::from(value.into_boxed_slice()),
714 };
715
716 let span_node = spans.add_node(span);
717 spans.add_edge(parent, span_node, ());
718 if !children.is_empty() {
719 nodes_to_process.push(SpanMapping {
720 old_children: children,
721 new_parent: span_node,
722 });
723 }
724 }
725 }
726
727 Self {
728 min_timestamp: intermediate.min_timestamp,
729 max_timestamp: intermediate.max_timestamp,
730 callsites,
731 events,
732 spans,
733 root_spans: root_nodes,
734 threads: intermediate.threads,
735 }
736 }
737}
738
739#[derive(Debug)]
740pub struct Tape {
741 intro: Intro,
742 data: TapeData,
743}
744
745impl Tape {
746 pub fn parse(data: &[u8]) -> Self {
747 let intro = Intro::read_from_prefix(data).unwrap();
748
749 let mut intermediate = Intermediate::default();
750 intermediate
751 .parse(&data[std::mem::size_of::<Intro>()..])
752 .unwrap();
753
754 let data = TapeData::new(intermediate);
755
756 Self { intro, data }
757 }
758
759 pub fn time_range(&self) -> std::ops::RangeInclusive<i128> {
760 let start = self.intro.timestamp_base.get();
761 let end = start + self.data.max_timestamp as i128;
762 start..=end
763 }
764
765 pub fn timestamp_range(&self) -> std::ops::RangeInclusive<i64> {
766 self.data.min_timestamp..=self.data.max_timestamp
767 }
768
769 pub fn events(&self) -> &[Event] {
770 &self.data.events
771 }
772
773 pub fn callsites(&self) -> &[Callsite] {
774 &self.data.callsites
775 }
776
777 pub fn root_spans(&self) -> &[petgraph::graph::NodeIndex<usize>] {
778 &self.data.root_spans
779 }
780
781 pub fn spans(&self) -> &petgraph::graph::Graph<Span, (), petgraph::Directed, usize> {
782 &self.data.spans
783 }
784
785 pub fn threads(&self) -> &HashMap<u64, Option<String>> {
786 &self.data.threads
787 }
788}