Skip to main content

fxt/
session.rs

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