1use crate::error::ParseWarning;
6use crate::init::{InitRecord, Ticks};
7use crate::metadata::{
8 MetadataRecord, Provider, ProviderEventMetadataRecord, ProviderInfoMetadataRecord,
9 ProviderSectionMetadataRecord, TraceInfoMetadataRecord,
10};
11use crate::string::{StringRecord, StringRef};
12use crate::thread::{ProcessKoid, ProcessRef, ThreadKoid, ThreadRecord, ThreadRef};
13use crate::{ParseError, ParsedWithOriginalBytes, RawTraceRecord, TraceRecord};
14use flyweights::FlyStr;
15use fuchsia_sync::Mutex;
16use futures::{AsyncRead, AsyncReadExt, SinkExt, Stream};
17use std::collections::BTreeMap;
18use std::num::{NonZeroU8, NonZeroU16};
19
20pub fn parse_full_session<'a>(
21 buf: &'a [u8],
22) -> Result<(Vec<TraceRecord>, Vec<ParseWarning>), ParseError> {
23 let mut parser = SessionParser::new(std::io::Cursor::new(buf));
24 let mut records = vec![];
25 while let Some(record) = parser.next() {
26 records.push(record?);
27 }
28 Ok((records, parser.take_warnings()))
29}
30
31#[derive(Debug)]
32pub struct SessionParser<R> {
33 buffer: Vec<u8>,
34 reader: R,
35 resolver: ResolveCtx,
36 reader_is_eof: bool,
37 have_seen_magic_number: bool,
38 parsed_bytes: Vec<u8>,
39 record_range: std::ops::Range<usize>,
40}
41
42impl<R: std::io::Read> SessionParser<R> {
43 pub fn new(reader: R) -> Self {
44 Self {
45 buffer: vec![],
46 reader,
47 resolver: ResolveCtx::new(),
48 reader_is_eof: false,
49 have_seen_magic_number: false,
50 parsed_bytes: vec![],
51 record_range: Default::default(),
52 }
53 }
54}
55
56impl<R> SessionParser<R> {
57 pub fn take_warnings(&self) -> Vec<ParseWarning> {
58 self.resolver.take_warnings()
59 }
60
61 pub fn parsed_bytes(&self) -> &[u8] {
62 return &self.parsed_bytes;
63 }
64
65 pub fn record_range(&self) -> std::ops::Range<usize> {
66 self.record_range.clone()
67 }
68
69 fn parse_next(&mut self) -> ParseOutcome {
70 match RawTraceRecord::parse(&self.buffer) {
71 Ok((rem, ParsedWithOriginalBytes { parsed: raw_record, bytes })) => {
72 self.record_range = self.parsed_bytes.len()..self.parsed_bytes.len() + bytes.len();
74 self.parsed_bytes.extend(bytes);
75
76 if raw_record.is_magic_number() {
78 self.have_seen_magic_number = true;
79 } else {
80 if !self.have_seen_magic_number {
81 return ParseOutcome::Error(ParseError::MissingMagicNumber);
82 }
83 }
84
85 let resolve_res = TraceRecord::resolve(&mut self.resolver, raw_record);
87 let unused_len = rem.len();
89 let parsed_len = self.buffer.len() - unused_len;
90 self.buffer.copy_within(parsed_len.., 0);
91 self.buffer.truncate(unused_len);
92
93 match resolve_res {
94 Ok(None) => ParseOutcome::Continue,
97 Ok(Some(resolved)) => ParseOutcome::GotRecord(resolved),
98 Err(e) => ParseOutcome::Error(e),
99 }
100 }
101 Err(nom::Err::Error(e) | nom::Err::Failure(e)) => {
102 self.buffer = vec![];
103 ParseOutcome::Error(e)
104 }
105 Err(nom::Err::Incomplete(needed)) => {
106 ParseOutcome::NeedMoreBytes(match needed {
107 nom::Needed::Unknown => 32768,
110 nom::Needed::Size(n) => n.into(),
111 })
112 }
113 }
114 }
115}
116
117enum ParseOutcome {
118 GotRecord(TraceRecord),
119 Continue,
120 Error(ParseError),
121 NeedMoreBytes(usize),
122}
123
124macro_rules! fill_buffer {
126 ($self:ident, $original_len:ident, $needed:ident, $bytes_read:expr) => {{
127 if $self.reader_is_eof {
128 return None;
131 } else {
132 let $original_len = $self.buffer.len();
133 $self.buffer.resize($original_len + $needed, 0);
134 let bytes_read = $bytes_read;
135 if bytes_read == 0 {
136 $self.reader_is_eof = true;
137 }
138 $self.buffer.truncate($original_len + bytes_read);
139 }
140 }};
141}
142
143impl<R: std::io::Read> Iterator for SessionParser<R> {
144 type Item = Result<TraceRecord, ParseError>;
145 fn next(&mut self) -> Option<Self::Item> {
146 self.parsed_bytes.clear();
148 loop {
149 match self.parse_next() {
150 ParseOutcome::GotRecord(r) => return Some(Ok(r)),
151 ParseOutcome::Error(e) => return Some(Err(e)),
152 ParseOutcome::Continue => continue,
153 ParseOutcome::NeedMoreBytes(needed) => {
154 fill_buffer!(
155 self,
156 original_len,
157 needed,
158 match self.reader.read(&mut self.buffer[original_len..]) {
159 Ok(b) => b,
160 Err(e) => return Some(Err(ParseError::Io(e))),
161 }
162 );
163 }
164 }
165 }
166 }
167}
168
169impl<R: AsyncRead + Send + Unpin + 'static> SessionParser<R> {
170 pub fn new_async(
171 reader: R,
172 ) -> (impl Stream<Item = Result<TraceRecord, ParseError>>, fuchsia_async::Task<Vec<ParseWarning>>)
173 {
174 let (mut send, recv) = futures::channel::mpsc::channel(1);
176 let pump_task = fuchsia_async::Task::spawn(async move {
177 let mut parser = Self {
178 buffer: vec![],
179 reader,
180 resolver: ResolveCtx::new(),
181 reader_is_eof: false,
182 have_seen_magic_number: false,
183 parsed_bytes: vec![],
184 record_range: Default::default(),
185 };
186
187 while let Some(next) = parser.next_async().await {
188 if send.send(next).await.is_err() {
189 break;
191 }
192 }
193
194 parser.take_warnings()
195 });
196
197 (recv, pump_task)
198 }
199
200 pub async fn next_async(&mut self) -> Option<Result<TraceRecord, ParseError>> {
201 self.parsed_bytes.clear();
203 loop {
204 match self.parse_next() {
205 ParseOutcome::GotRecord(r) => return Some(Ok(r)),
206 ParseOutcome::Error(e) => return Some(Err(e)),
207 ParseOutcome::Continue => continue,
208 ParseOutcome::NeedMoreBytes(needed) => {
209 fill_buffer!(
210 self,
211 original_len,
212 needed,
213 match self.reader.read(&mut self.buffer[original_len..]).await {
214 Ok(b) => b,
215 Err(e) => return Some(Err(ParseError::Io(e))),
216 }
217 );
218 }
219 }
220 }
221 }
222}
223
224#[derive(Debug)]
225pub(crate) struct ResolveCtx {
226 ticks_per_second: u64,
227 current_provider: Option<Provider>,
228 providers: BTreeMap<u32, FlyStr>,
229 strings: BTreeMap<u32, BTreeMap<NonZeroU16, FlyStr>>,
230 threads: BTreeMap<NonZeroU8, (ProcessKoid, ThreadKoid)>,
231 warnings: Mutex<Vec<ParseWarning>>,
232}
233
234impl ResolveCtx {
235 pub fn new() -> Self {
236 Self {
237 ticks_per_second: 1,
238 current_provider: None,
239 providers: Default::default(),
240 strings: Default::default(),
241 threads: Default::default(),
242 warnings: Default::default(),
243 }
244 }
245
246 pub fn add_warning(&self, warning: ParseWarning) {
247 self.warnings.lock().push(warning);
248 }
249
250 pub fn take_warnings(&self) -> Vec<ParseWarning> {
251 let mut guard = self.warnings.lock();
252 std::mem::replace(&mut *guard, Vec::new())
253 }
254
255 pub fn current_provider(&self) -> Option<Provider> {
256 self.current_provider.clone()
257 }
258
259 pub fn get_provider(&mut self, id: u32) -> Result<Provider, ParseError> {
260 let name = if let Some(name) = self.providers.get(&id).cloned() {
261 name
262 } else {
263 self.add_warning(ParseWarning::UnknownProviderId(id));
264 "<unknown>".into()
265 };
266
267 Ok(Provider { id, name })
268 }
269
270 pub fn on_metadata_record(
271 &mut self,
272 m: MetadataRecord,
273 ) -> Result<Option<TraceRecord>, ParseError> {
274 Ok(match m {
275 MetadataRecord::TraceInfo(TraceInfoMetadataRecord::MagicNumber) => None,
277
278 MetadataRecord::ProviderInfo(ProviderInfoMetadataRecord { provider_id, name }) => {
279 self.providers.insert(provider_id, name.clone());
280 self.current_provider = Some(Provider { id: provider_id, name: name });
281 None
282 }
283 MetadataRecord::ProviderSection(ProviderSectionMetadataRecord { provider_id }) => {
284 let new_provider = self.get_provider(provider_id)?;
285 self.current_provider = Some(new_provider);
286 None
287 }
288 MetadataRecord::ProviderEvent(ProviderEventMetadataRecord { provider_id, event }) => {
289 Some(TraceRecord::ProviderEvent {
290 provider: self.get_provider(provider_id)?,
291 event,
292 })
293 }
294 MetadataRecord::Unknown { raw_type } => {
295 self.add_warning(ParseWarning::UnknownMetadataRecordType(raw_type));
296 None
297 }
298 })
299 }
300
301 pub fn on_init_record(&mut self, InitRecord { ticks_per_second }: InitRecord) {
302 self.ticks_per_second = ticks_per_second;
303 }
304
305 pub fn on_string_record(&mut self, s: StringRecord<'_>) {
306 let Some(ref current_provider) = self.current_provider else {
307 self.add_warning(ParseWarning::MissingProviderId);
308 return;
309 };
310 if let Some(idx) = NonZeroU16::new(s.index) {
311 self.strings.entry(current_provider.id).or_default().insert(idx, s.value.into());
312 } else {
313 self.add_warning(ParseWarning::RecordForZeroStringId);
314 }
315 }
316
317 pub fn on_thread_record(&mut self, t: ThreadRecord) {
318 self.threads.insert(t.index, (t.process_koid, t.thread_koid));
319 }
320
321 pub fn resolve_str(&self, s: StringRef<'_>) -> FlyStr {
322 match s {
323 StringRef::Empty => FlyStr::default(),
324 StringRef::Inline(inline) => FlyStr::from(inline),
325 StringRef::Index(id) => {
326 let Some(ref current_provider) = self.current_provider else {
327 self.add_warning(ParseWarning::MissingProviderId);
328 return "<unknown>".into();
329 };
330 let Some(ref string_table) = self.strings.get(¤t_provider.id) else {
331 self.add_warning(ParseWarning::UnknownStringId(id));
332 return "<unknown>".into();
333 };
334 if let Some(s) = string_table.get(&id).cloned() {
335 s
336 } else {
337 self.add_warning(ParseWarning::UnknownStringId(id));
338 "<unknown>".into()
339 }
340 }
341 }
342 }
343
344 pub fn resolve_process(&self, p: ProcessRef) -> ProcessKoid {
345 match p {
346 ProcessRef::Index(id) => {
347 if let Some(process) = self.threads.get(&id).map(|(process, _thread)| *process) {
348 process
349 } else {
350 self.add_warning(ParseWarning::UnknownProcessRef(p));
351 ProcessKoid(u64::MAX)
352 }
353 }
354 ProcessRef::Inline(inline) => inline,
355 }
356 }
357
358 pub fn resolve_thread(&self, t: ThreadRef) -> ThreadKoid {
359 match t {
360 ThreadRef::Index(id) => {
361 if let Some(thread) = self.threads.get(&id).map(|(_process, thread)| *thread) {
362 thread
363 } else {
364 self.add_warning(ParseWarning::UnknownThreadRef(t));
365 ThreadKoid(u64::MAX)
366 }
367 }
368 ThreadRef::Inline(inline) => inline,
369 }
370 }
371
372 pub fn resolve_ticks(&self, t: Ticks) -> i64 {
373 t.scale(self.ticks_per_second)
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use std::num::NonZero;
380
381 use super::*;
382 use crate::event::{EventPayload, EventRecord};
383 use crate::fxt_builder::FxtBuilder;
384 use crate::scheduling::{LegacyContextSwitchEvent, SchedulingRecord, ThreadState};
385 use crate::{RawEventRecord, RawTraceRecord};
386 use futures::{StreamExt, TryStreamExt};
387
388 static SIMPLE_TRACE_FXT: &[u8] =
389 include_bytes!("../../../../trace2json/test_data/simple_trace.fxt");
390
391 #[test]
392 fn test_parse_full_session() {
393 let session = parse_full_session(SIMPLE_TRACE_FXT).unwrap();
394 assert_eq!(session, expected_simple_trace_records());
395 }
396
397 #[fuchsia::test]
398 async fn test_async_parse() {
399 let (mut send_chunks, recv_chunks) = futures::channel::mpsc::unbounded();
400
401 let parse_trace_session = fuchsia_async::Task::spawn(async move {
402 let (records, parse_task) = SessionParser::new_async(recv_chunks.into_async_read());
403 let records = records.map(|res| res.unwrap()).collect::<Vec<_>>().await;
404 (records, parse_task.await)
405 });
406
407 for chunk in SIMPLE_TRACE_FXT.chunks(1) {
409 send_chunks.send(Ok(chunk)).await.unwrap();
410 }
411 drop(send_chunks);
412
413 assert_eq!(parse_trace_session.await, expected_simple_trace_records());
414 }
415
416 #[fuchsia::test]
417 fn session_with_unknown_record_in_middle() {
418 let mut session = vec![];
419
420 session.extend(&SIMPLE_TRACE_FXT[..8]);
423
424 let mut header = crate::BaseTraceHeader::empty();
426 header.set_raw_type(14); session.extend(FxtBuilder::new(header).atom(&(0u8..27u8).collect::<Vec<u8>>()).build());
428
429 session.extend(&SIMPLE_TRACE_FXT[8..]);
431
432 let (observed_parsed, observed_warnings) = parse_full_session(&session).unwrap();
433 let (expected_parsed, expected_warnings) =
434 (expected_simple_trace_records().0, vec![ParseWarning::UnknownTraceRecordType(14)]);
435 assert_eq!(observed_parsed, expected_parsed);
436 assert_eq!(observed_warnings, expected_warnings);
437 }
438
439 #[fuchsia::test]
440 fn session_with_invalid_record_in_middle() {
441 let mut session = vec![];
442 session.extend(&SIMPLE_TRACE_FXT[..8]);
445 let invalid_record = vec![
447 103, 0, 2, 15, 128, 1, 0, 0, 229, 253, 9, 0, 0, 0, 0, 0, 98, 105, 110, 100, 101, 114,
448 58, 57, 51, 54, 95, 68, 255, 255, 255, 0, 40, 0, 166, 0, 0, 0, 0, 0, 125, 125, 4, 0, 0,
449 0, 0, 0,
450 ];
451 session.extend(invalid_record);
452 session.extend(&SIMPLE_TRACE_FXT[8..]);
453 let mut parser = SessionParser::new(std::io::Cursor::new(session));
454 let mut records = vec![];
455 let mut had_error_record = false;
456 while let Some(record) = parser.next() {
457 match record {
458 Ok(record) => records.push(record),
459 Err(_) => had_error_record = true,
460 }
461 }
462 assert_eq!(records, expected_simple_trace_records().0);
465 assert_eq!(had_error_record, true);
466 }
467
468 #[fuchsia::test]
469 fn sessioninvalid_recordwith_incomplete_trailing_record() {
470 use crate::string::STRING_REF_INLINE_BIT;
471
472 let mut session = SIMPLE_TRACE_FXT.to_vec();
473
474 let category = "test_category";
476 let name = "test_instant";
477 let mut header = crate::event::EventHeader::empty();
478 header.set_category_ref(category.len() as u16 | STRING_REF_INLINE_BIT);
479 header.set_name_ref(name.len() as u16 | STRING_REF_INLINE_BIT);
480 header.set_event_type(crate::event::INSTANT_EVENT_TYPE);
481
482 let mut final_record = FxtBuilder::new(header)
483 .atom(2048u64.to_le_bytes()) .atom(512u64.to_le_bytes()) .atom(513u64.to_le_bytes()) .atom(category)
487 .atom(name)
488 .build();
489 let byte_to_make_valid = final_record.pop().unwrap();
490
491 for byte in final_record {
492 session.push(byte);
493 assert_eq!(
494 parse_full_session(&session).expect("should parse without final incomplete record"),
495 expected_simple_trace_records(),
496 );
497 }
498
499 let (mut expected_with_final_record, expected_warnings) = expected_simple_trace_records();
500 expected_with_final_record.push(TraceRecord::Event(EventRecord {
501 provider: Some(Provider { id: 1, name: "test_provider".into() }),
502 timestamp: 85333,
503 process: ProcessKoid(512),
504 thread: ThreadKoid(513),
505 category: category.into(),
506 name: name.into(),
507 args: vec![],
508 payload: EventPayload::Instant,
509 }));
510
511 session.push(byte_to_make_valid);
512 assert_eq!(
513 parse_full_session(&session).unwrap(),
514 (expected_with_final_record, expected_warnings)
515 );
516 }
517
518 fn expected_simple_trace_records() -> (Vec<TraceRecord>, Vec<ParseWarning>) {
519 (
520 vec![
521 TraceRecord::Scheduling(SchedulingRecord::LegacyContextSwitch(
522 LegacyContextSwitchEvent {
523 provider: Some(Provider { id: 1, name: "test_provider".into() }),
524 timestamp: 41,
525 cpu_id: 0,
526 outgoing_thread_state: ThreadState::Suspended,
527 outgoing_process: ProcessKoid(4660),
528 outgoing_thread: ThreadKoid(17185),
529 outgoing_thread_priority: 0,
530 incoming_process: ProcessKoid(1000),
531 incoming_thread: ThreadKoid(1001),
532 incoming_thread_priority: 20,
533 },
534 )),
535 TraceRecord::Event(EventRecord {
536 provider: Some(Provider { id: 1, name: "test_provider".into() }),
537 timestamp: 0,
538 process: ProcessKoid(1000),
539 thread: ThreadKoid(1001),
540 category: "test".into(),
541 name: "begin_end_ref".into(),
542 args: vec![],
543 payload: EventPayload::DurationBegin,
544 }),
545 TraceRecord::Event(EventRecord {
546 provider: Some(Provider { id: 1, name: "test_provider".into() }),
547 timestamp: 110000000,
548 process: ProcessKoid(1000),
549 thread: ThreadKoid(1001),
550 category: "test".into(),
551 name: "complete_inline".into(),
552 args: vec![],
553 payload: EventPayload::DurationComplete { end_timestamp: 150000000 },
554 }),
555 TraceRecord::Event(EventRecord {
556 provider: Some(Provider { id: 1, name: "test_provider".into() }),
557 timestamp: 200000000,
558 process: ProcessKoid(1000),
559 thread: ThreadKoid(1001),
560 category: "test".into(),
561 name: "begin_end_inline".into(),
562 args: vec![],
563 payload: EventPayload::DurationBegin,
564 }),
565 TraceRecord::Event(EventRecord {
566 provider: Some(Provider { id: 1, name: "test_provider".into() }),
567 timestamp: 450000000,
568 process: ProcessKoid(1000),
569 thread: ThreadKoid(1001),
570 category: "test".into(),
571 name: "begin_end_inline".into(),
572 args: vec![],
573 payload: EventPayload::DurationEnd,
574 }),
575 TraceRecord::Event(EventRecord {
576 provider: Some(Provider { id: 1, name: "test_provider".into() }),
577 timestamp: 100000000,
578 process: ProcessKoid(1000),
579 thread: ThreadKoid(1001),
580 category: "test".into(),
581 name: "complete_ref".into(),
582 args: vec![],
583 payload: EventPayload::DurationComplete { end_timestamp: 500000000 },
584 }),
585 TraceRecord::Event(EventRecord {
586 provider: Some(Provider { id: 1, name: "test_provider".into() }),
587 timestamp: 500000208,
588 process: ProcessKoid(1000),
589 thread: ThreadKoid(1001),
590 category: "test".into(),
591 name: "async".into(),
592 args: vec![],
593 payload: EventPayload::AsyncBegin { id: 1 },
594 }),
595 TraceRecord::Scheduling(SchedulingRecord::LegacyContextSwitch(
596 LegacyContextSwitchEvent {
597 provider: Some(Provider { id: 1, name: "test_provider".into() }),
598 timestamp: 500000416,
599 cpu_id: 0,
600 outgoing_thread_state: ThreadState::Suspended,
601 outgoing_process: ProcessKoid(1000),
602 outgoing_thread: ThreadKoid(1001),
603 outgoing_thread_priority: 20,
604 incoming_process: ProcessKoid(1000),
605 incoming_thread: ThreadKoid(1002),
606 incoming_thread_priority: 20,
607 },
608 )),
609 TraceRecord::Event(EventRecord {
610 provider: Some(Provider { id: 1, name: "test_provider".into() }),
611 timestamp: 500000458,
612 process: ProcessKoid(1000),
613 thread: ThreadKoid(1002),
614 category: "test".into(),
615 name: "complete_ref".into(),
616 args: vec![],
617 payload: EventPayload::DurationComplete { end_timestamp: 600000000 },
618 }),
619 TraceRecord::Scheduling(SchedulingRecord::LegacyContextSwitch(
620 LegacyContextSwitchEvent {
621 provider: Some(Provider { id: 1, name: "test_provider".into() }),
622 timestamp: 600010666,
623 cpu_id: 0,
624 outgoing_thread_state: ThreadState::Suspended,
625 outgoing_process: ProcessKoid(1000),
626 outgoing_thread: ThreadKoid(1002),
627 outgoing_thread_priority: 20,
628 incoming_process: ProcessKoid(1000),
629 incoming_thread: ThreadKoid(1001),
630 incoming_thread_priority: 20,
631 },
632 )),
633 TraceRecord::Event(EventRecord {
634 provider: Some(Provider { id: 1, name: "test_provider".into() }),
635 timestamp: 600016000,
636 process: ProcessKoid(1000),
637 thread: ThreadKoid(1001),
638 category: "test".into(),
639 name: "async".into(),
640 args: vec![],
641 payload: EventPayload::AsyncEnd { id: 1 },
642 }),
643 TraceRecord::Event(EventRecord {
644 provider: Some(Provider { id: 1, name: "test_provider".into() }),
645 timestamp: 630000000,
646 process: ProcessKoid(1000),
647 thread: ThreadKoid(1001),
648 category: "test".into(),
649 name: "begin_end_ref".into(),
650 args: vec![],
651 payload: EventPayload::DurationBegin,
652 }),
653 TraceRecord::Event(EventRecord {
654 provider: Some(Provider { id: 1, name: "test_provider".into() }),
655 timestamp: 950000000,
656 process: ProcessKoid(1000),
657 thread: ThreadKoid(1001),
658 category: "test".into(),
659 name: "begin_end_ref".into(),
660 args: vec![],
661 payload: EventPayload::DurationEnd,
662 }),
663 TraceRecord::Event(EventRecord {
664 provider: Some(Provider { id: 1, name: "test_provider".into() }),
665 timestamp: 1000000000,
666 process: ProcessKoid(1000),
667 thread: ThreadKoid(1001),
668 category: "test".into(),
669 name: "begin_end_ref".into(),
670 args: vec![],
671 payload: EventPayload::DurationEnd,
672 }),
673 TraceRecord::Scheduling(SchedulingRecord::LegacyContextSwitch(
674 LegacyContextSwitchEvent {
675 provider: Some(Provider { id: 1, name: "test_provider".into() }),
676 timestamp: 1000000666,
677 cpu_id: 0,
678 outgoing_thread_state: ThreadState::Suspended,
679 outgoing_process: ProcessKoid(1000),
680 outgoing_thread: ThreadKoid(1001),
681 outgoing_thread_priority: 20,
682 incoming_process: ProcessKoid(4660),
683 incoming_thread: ThreadKoid(17185),
684 incoming_thread_priority: 0,
685 },
686 )),
687 ],
688 vec![],
690 )
691 }
692
693 #[fuchsia::test]
694 fn per_provider_strings() {
695 let raw_records = vec![
696 RawTraceRecord::Metadata(MetadataRecord::ProviderSection(
697 ProviderSectionMetadataRecord { provider_id: 1 },
698 )),
699 RawTraceRecord::String(StringRecord { index: 1, value: "cat_1_1" }),
702 RawTraceRecord::String(StringRecord { index: 2, value: "name_1_2" }),
703 RawTraceRecord::Event(RawEventRecord {
704 event_type: 0, ticks: Ticks(1),
706 process: ProcessRef::Inline(ProcessKoid(1)),
707 thread: ThreadRef::Inline(ThreadKoid(2)),
708 category: StringRef::Index(NonZero::new(1).unwrap()),
709 name: StringRef::Index(NonZero::new(2).unwrap()),
710 args: vec![],
711 payload: EventPayload::Instant,
712 }),
713 RawTraceRecord::Metadata(MetadataRecord::ProviderSection(
714 ProviderSectionMetadataRecord { provider_id: 2 },
715 )),
716 RawTraceRecord::String(StringRecord { index: 1, value: "cat_2_1" }),
717 RawTraceRecord::String(StringRecord { index: 2, value: "name_2_2" }),
718 RawTraceRecord::Event(RawEventRecord {
719 event_type: 0, ticks: Ticks(2),
721 process: ProcessRef::Inline(ProcessKoid(3)),
722 thread: ThreadRef::Inline(ThreadKoid(4)),
723 category: StringRef::Index(NonZero::new(1).unwrap()),
724 name: StringRef::Index(NonZero::new(2).unwrap()),
725 args: vec![],
726 payload: EventPayload::Instant,
727 }),
728 RawTraceRecord::Metadata(MetadataRecord::ProviderSection(
730 ProviderSectionMetadataRecord { provider_id: 1 },
731 )),
732 RawTraceRecord::Event(RawEventRecord {
733 event_type: 0, ticks: Ticks(3),
735 process: ProcessRef::Inline(ProcessKoid(1)),
736 thread: ThreadRef::Inline(ThreadKoid(2)),
737 category: StringRef::Index(NonZero::new(1).unwrap()),
738 name: StringRef::Index(NonZero::new(2).unwrap()),
739 args: vec![],
740 payload: EventPayload::Instant,
741 }),
742 ];
743 let mut resolve_ctx = ResolveCtx::new();
744 let resolved: Vec<_> =
745 raw_records.into_iter().map(|r| TraceRecord::resolve(&mut resolve_ctx, r)).collect();
746 let event1 = resolved[3]
747 .as_ref()
748 .expect("Failed to parse")
749 .as_ref()
750 .expect("Expected ResolvedRecord");
751 let event2 = resolved[7]
752 .as_ref()
753 .expect("Failed to parse")
754 .as_ref()
755 .expect("Expected ResolvedRecord");
756 let event3 = resolved[9]
757 .as_ref()
758 .expect("Failed to parse")
759 .as_ref()
760 .expect("Expected ResolvedRecord");
761 match event1 {
762 TraceRecord::Event(event_record) => {
763 assert_eq!(event_record.category, "cat_1_1");
764 assert_eq!(event_record.name, "name_1_2");
765 }
766 _ => assert!(false),
767 };
768 match event2 {
769 TraceRecord::Event(event_record) => {
770 assert_eq!(event_record.category, "cat_2_1");
771 assert_eq!(event_record.name, "name_2_2");
772 }
773 _ => assert!(false),
774 };
775 match event3 {
776 TraceRecord::Event(event_record) => {
777 assert_eq!(event_record.category, "cat_1_1");
778 assert_eq!(event_record.name, "name_1_2");
779 }
780 _ => assert!(false),
781 };
782 }
783}