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
412    static SIMPLE_TRACE_FXT: &[u8] =
413        include_bytes!("../../../../trace2json/test_data/simple_trace.fxt");
414
415    #[test]
416    fn test_parse_full_session() {
417        let session = parse_full_session(SIMPLE_TRACE_FXT).unwrap();
418        assert_eq!(session, expected_simple_trace_records());
419    }
420
421    #[fuchsia::test]
422    async fn test_async_parse() {
423        let (mut send_chunks, recv_chunks) = futures::channel::mpsc::unbounded();
424
425        let parse_trace_session = fuchsia_async::Task::spawn(async move {
426            let (records, parse_task) = SessionParser::new_async(recv_chunks.into_async_read());
427            let records = records.map(|res| res.unwrap()).collect::<Vec<_>>().await;
428            (records, parse_task.await)
429        });
430
431        // Send tiny chunks to shake out any incorrect streaming parser code.
432        for chunk in SIMPLE_TRACE_FXT.chunks(1) {
433            send_chunks.send(Ok(chunk)).await.unwrap();
434        }
435        drop(send_chunks);
436
437        assert_eq!(parse_trace_session.await, expected_simple_trace_records());
438    }
439
440    #[fuchsia::test]
441    fn session_with_unknown_record_in_middle() {
442        let mut session = vec![];
443
444        // Add the magic record from the simple trace before we add our bogus record so we don't
445        // error on an invalid first record.
446        session.extend(&SIMPLE_TRACE_FXT[..8]);
447
448        // Add our bogus record with an unknown type, expecting to skip over it.
449        let mut header = crate::BaseTraceHeader::empty();
450        header.set_raw_type(14); // not currently a valid ordinal
451        session.extend(FxtBuilder::new(header).atom(&(0u8..27u8).collect::<Vec<u8>>()).build());
452
453        // Add the rest of the simple trace.
454        session.extend(&SIMPLE_TRACE_FXT[8..]);
455
456        let (observed_parsed, observed_warnings) = parse_full_session(&session).unwrap();
457        let (expected_parsed, expected_warnings) =
458            (expected_simple_trace_records().0, vec![ParseWarning::UnknownTraceRecordType(14)]);
459        assert_eq!(observed_parsed, expected_parsed);
460        assert_eq!(observed_warnings, expected_warnings);
461    }
462
463    #[fuchsia::test]
464    fn session_with_non_utf8_kernel_object_name() {
465        let mut session = vec![];
466        session.extend(&SIMPLE_TRACE_FXT[..8]);
467
468        let mut header = crate::objects::KernelObjHeader::empty();
469        header.set_kernel_obj_type(zx_types::ZX_OBJ_TYPE_PROCESS);
470        let name_bytes = b"binder:936_D\xff\xff\xff";
471        header.set_name_ref(fxt_layout::StringRefHeader::inline(name_bytes.len() as u16).bits());
472        header.set_num_args(0);
473
474        let kobj_record =
475            FxtBuilder::new(header).atom(1234u64.to_le_bytes()).atom(name_bytes).build();
476        session.extend(kobj_record);
477
478        let mut parser = SessionParser::new(std::io::Cursor::new(session));
479        let mut records = vec![];
480        while let Some(record) = parser.next() {
481            if let Ok(r) = record {
482                records.push(r);
483            }
484        }
485        let kobj = records
486            .iter()
487            .find_map(|r| match r {
488                TraceRecord::KernelObj(kobj) => Some(kobj),
489                _ => None,
490            })
491            .expect("expected KernelObj record in parsed records");
492        assert_eq!(kobj.name, FlyByteStr::new(b"binder:936_D\xff\xff\xff"));
493    }
494
495    #[fuchsia::test]
496    fn session_with_invalid_record_in_middle() {
497        let mut session = vec![];
498        // Add the magic record from the simple trace before we add our bogus record so we don't
499        // error on an invalid first record.
500        session.extend(&SIMPLE_TRACE_FXT[..8]);
501        // A record with 0 size is invalid according to FXT spec
502        let invalid_record = vec![0u8; 8];
503        session.extend(invalid_record);
504        session.extend(&SIMPLE_TRACE_FXT[8..]);
505        let mut parser = SessionParser::new(std::io::Cursor::new(session));
506        let mut records = vec![];
507        let mut had_error_record = false;
508        while let Some(record) = parser.next() {
509            match record {
510                Ok(record) => records.push(record),
511                Err(_) => had_error_record = true,
512            }
513        }
514        // We want to test that even after seeing an invalid record, we still parse the rest of the
515        // session.
516        assert_eq!(records, expected_simple_trace_records().0);
517        assert_eq!(had_error_record, true);
518    }
519
520    #[fuchsia::test]
521    fn session_with_incomplete_trailing_record() {
522        let mut session = SIMPLE_TRACE_FXT.to_vec();
523
524        // Make a 2 word header with some arbitrary values.
525        let category = "test_category";
526        let name = "test_instant";
527        let mut header = crate::event::EventHeader::empty();
528        header.set_category_ref(fxt_layout::StringRefHeader::inline(category.len() as u16).bits());
529        header.set_name_ref(fxt_layout::StringRefHeader::inline(name.len() as u16).bits());
530        header.set_event_type(crate::event::INSTANT_EVENT_TYPE);
531
532        let mut final_record = FxtBuilder::new(header)
533            .atom(2048u64.to_le_bytes()) // timestamp
534            .atom(512u64.to_le_bytes()) // process
535            .atom(513u64.to_le_bytes()) // thread
536            .atom(category)
537            .atom(name)
538            .build();
539        let byte_to_make_valid = final_record.pop().unwrap();
540
541        for byte in final_record {
542            session.push(byte);
543            assert_eq!(
544                parse_full_session(&session).expect("should parse without final incomplete record"),
545                expected_simple_trace_records(),
546            );
547        }
548
549        let (mut expected_with_final_record, expected_warnings) = expected_simple_trace_records();
550        expected_with_final_record.push(TraceRecord::Event(EventRecord {
551            provider: Some(Provider { id: 1, name: "test_provider".into() }),
552            timestamp: 85333,
553            process: ProcessKoid(512),
554            thread: ThreadKoid(513),
555            category: category.into(),
556            name: name.into(),
557            args: vec![],
558            payload: EventPayload::Instant,
559        }));
560
561        session.push(byte_to_make_valid);
562        assert_eq!(
563            parse_full_session(&session).unwrap(),
564            (expected_with_final_record, expected_warnings)
565        );
566    }
567
568    fn expected_simple_trace_records() -> (Vec<TraceRecord>, Vec<ParseWarning>) {
569        (
570            vec![
571                TraceRecord::Scheduling(SchedulingRecord::LegacyContextSwitch(
572                    LegacyContextSwitchEvent {
573                        provider: Some(Provider { id: 1, name: "test_provider".into() }),
574                        timestamp: 41,
575                        cpu_id: 0,
576                        outgoing_thread_state: ThreadState::Suspended,
577                        outgoing_process: ProcessKoid(4660),
578                        outgoing_thread: ThreadKoid(17185),
579                        outgoing_thread_priority: 0,
580                        incoming_process: ProcessKoid(1000),
581                        incoming_thread: ThreadKoid(1001),
582                        incoming_thread_priority: 20,
583                    },
584                )),
585                TraceRecord::Event(EventRecord {
586                    provider: Some(Provider { id: 1, name: "test_provider".into() }),
587                    timestamp: 0,
588                    process: ProcessKoid(1000),
589                    thread: ThreadKoid(1001),
590                    category: "test".into(),
591                    name: "begin_end_ref".into(),
592                    args: vec![],
593                    payload: EventPayload::DurationBegin,
594                }),
595                TraceRecord::Event(EventRecord {
596                    provider: Some(Provider { id: 1, name: "test_provider".into() }),
597                    timestamp: 110000000,
598                    process: ProcessKoid(1000),
599                    thread: ThreadKoid(1001),
600                    category: "test".into(),
601                    name: "complete_inline".into(),
602                    args: vec![],
603                    payload: EventPayload::DurationComplete { end_timestamp: 150000000 },
604                }),
605                TraceRecord::Event(EventRecord {
606                    provider: Some(Provider { id: 1, name: "test_provider".into() }),
607                    timestamp: 200000000,
608                    process: ProcessKoid(1000),
609                    thread: ThreadKoid(1001),
610                    category: "test".into(),
611                    name: "begin_end_inline".into(),
612                    args: vec![],
613                    payload: EventPayload::DurationBegin,
614                }),
615                TraceRecord::Event(EventRecord {
616                    provider: Some(Provider { id: 1, name: "test_provider".into() }),
617                    timestamp: 450000000,
618                    process: ProcessKoid(1000),
619                    thread: ThreadKoid(1001),
620                    category: "test".into(),
621                    name: "begin_end_inline".into(),
622                    args: vec![],
623                    payload: EventPayload::DurationEnd,
624                }),
625                TraceRecord::Event(EventRecord {
626                    provider: Some(Provider { id: 1, name: "test_provider".into() }),
627                    timestamp: 100000000,
628                    process: ProcessKoid(1000),
629                    thread: ThreadKoid(1001),
630                    category: "test".into(),
631                    name: "complete_ref".into(),
632                    args: vec![],
633                    payload: EventPayload::DurationComplete { end_timestamp: 500000000 },
634                }),
635                TraceRecord::Event(EventRecord {
636                    provider: Some(Provider { id: 1, name: "test_provider".into() }),
637                    timestamp: 500000208,
638                    process: ProcessKoid(1000),
639                    thread: ThreadKoid(1001),
640                    category: "test".into(),
641                    name: "async".into(),
642                    args: vec![],
643                    payload: EventPayload::AsyncBegin { id: 1 },
644                }),
645                TraceRecord::Scheduling(SchedulingRecord::LegacyContextSwitch(
646                    LegacyContextSwitchEvent {
647                        provider: Some(Provider { id: 1, name: "test_provider".into() }),
648                        timestamp: 500000416,
649                        cpu_id: 0,
650                        outgoing_thread_state: ThreadState::Suspended,
651                        outgoing_process: ProcessKoid(1000),
652                        outgoing_thread: ThreadKoid(1001),
653                        outgoing_thread_priority: 20,
654                        incoming_process: ProcessKoid(1000),
655                        incoming_thread: ThreadKoid(1002),
656                        incoming_thread_priority: 20,
657                    },
658                )),
659                TraceRecord::Event(EventRecord {
660                    provider: Some(Provider { id: 1, name: "test_provider".into() }),
661                    timestamp: 500000458,
662                    process: ProcessKoid(1000),
663                    thread: ThreadKoid(1002),
664                    category: "test".into(),
665                    name: "complete_ref".into(),
666                    args: vec![],
667                    payload: EventPayload::DurationComplete { end_timestamp: 600000000 },
668                }),
669                TraceRecord::Scheduling(SchedulingRecord::LegacyContextSwitch(
670                    LegacyContextSwitchEvent {
671                        provider: Some(Provider { id: 1, name: "test_provider".into() }),
672                        timestamp: 600010666,
673                        cpu_id: 0,
674                        outgoing_thread_state: ThreadState::Suspended,
675                        outgoing_process: ProcessKoid(1000),
676                        outgoing_thread: ThreadKoid(1002),
677                        outgoing_thread_priority: 20,
678                        incoming_process: ProcessKoid(1000),
679                        incoming_thread: ThreadKoid(1001),
680                        incoming_thread_priority: 20,
681                    },
682                )),
683                TraceRecord::Event(EventRecord {
684                    provider: Some(Provider { id: 1, name: "test_provider".into() }),
685                    timestamp: 600016000,
686                    process: ProcessKoid(1000),
687                    thread: ThreadKoid(1001),
688                    category: "test".into(),
689                    name: "async".into(),
690                    args: vec![],
691                    payload: EventPayload::AsyncEnd { id: 1 },
692                }),
693                TraceRecord::Event(EventRecord {
694                    provider: Some(Provider { id: 1, name: "test_provider".into() }),
695                    timestamp: 630000000,
696                    process: ProcessKoid(1000),
697                    thread: ThreadKoid(1001),
698                    category: "test".into(),
699                    name: "begin_end_ref".into(),
700                    args: vec![],
701                    payload: EventPayload::DurationBegin,
702                }),
703                TraceRecord::Event(EventRecord {
704                    provider: Some(Provider { id: 1, name: "test_provider".into() }),
705                    timestamp: 950000000,
706                    process: ProcessKoid(1000),
707                    thread: ThreadKoid(1001),
708                    category: "test".into(),
709                    name: "begin_end_ref".into(),
710                    args: vec![],
711                    payload: EventPayload::DurationEnd,
712                }),
713                TraceRecord::Event(EventRecord {
714                    provider: Some(Provider { id: 1, name: "test_provider".into() }),
715                    timestamp: 1000000000,
716                    process: ProcessKoid(1000),
717                    thread: ThreadKoid(1001),
718                    category: "test".into(),
719                    name: "begin_end_ref".into(),
720                    args: vec![],
721                    payload: EventPayload::DurationEnd,
722                }),
723                TraceRecord::Scheduling(SchedulingRecord::LegacyContextSwitch(
724                    LegacyContextSwitchEvent {
725                        provider: Some(Provider { id: 1, name: "test_provider".into() }),
726                        timestamp: 1000000666,
727                        cpu_id: 0,
728                        outgoing_thread_state: ThreadState::Suspended,
729                        outgoing_process: ProcessKoid(1000),
730                        outgoing_thread: ThreadKoid(1001),
731                        outgoing_thread_priority: 20,
732                        incoming_process: ProcessKoid(4660),
733                        incoming_thread: ThreadKoid(17185),
734                        incoming_thread_priority: 0,
735                    },
736                )),
737            ],
738            // There should be no warnings produced from these tests.
739            vec![],
740        )
741    }
742
743    #[fuchsia::test]
744    fn per_provider_strings() {
745        let raw_records = vec![
746            RawTraceRecord::Metadata(MetadataRecord::ProviderSection(
747                ProviderSectionMetadataRecord { provider_id: 1 },
748            )),
749            // Ensure that if we define the same string index multiple times, it's
750            // disambiguated per provider
751            RawTraceRecord::String(StringRecord { index: 1, value: "cat_1_1" }),
752            RawTraceRecord::String(StringRecord { index: 2, value: "name_1_2" }),
753            RawTraceRecord::Event(RawEventRecord {
754                event_type: 0, // Instant
755                ticks: Ticks(1),
756                process: ProcessRef::Inline(ProcessKoid(1)),
757                thread: ThreadRef::Inline(ThreadKoid(2)),
758                category: StringRef::Index(NonZero::new(1).unwrap()),
759                name: StringRef::Index(NonZero::new(2).unwrap()),
760                args: vec![],
761                payload: EventPayload::Instant,
762            }),
763            RawTraceRecord::Metadata(MetadataRecord::ProviderSection(
764                ProviderSectionMetadataRecord { provider_id: 2 },
765            )),
766            RawTraceRecord::String(StringRecord { index: 1, value: "cat_2_1" }),
767            RawTraceRecord::String(StringRecord { index: 2, value: "name_2_2" }),
768            RawTraceRecord::Event(RawEventRecord {
769                event_type: 0, // Instant
770                ticks: Ticks(2),
771                process: ProcessRef::Inline(ProcessKoid(3)),
772                thread: ThreadRef::Inline(ThreadKoid(4)),
773                category: StringRef::Index(NonZero::new(1).unwrap()),
774                name: StringRef::Index(NonZero::new(2).unwrap()),
775                args: vec![],
776                payload: EventPayload::Instant,
777            }),
778            // Back to the first provider
779            RawTraceRecord::Metadata(MetadataRecord::ProviderSection(
780                ProviderSectionMetadataRecord { provider_id: 1 },
781            )),
782            RawTraceRecord::Event(RawEventRecord {
783                event_type: 0, // Instant
784                ticks: Ticks(3),
785                process: ProcessRef::Inline(ProcessKoid(1)),
786                thread: ThreadRef::Inline(ThreadKoid(2)),
787                category: StringRef::Index(NonZero::new(1).unwrap()),
788                name: StringRef::Index(NonZero::new(2).unwrap()),
789                args: vec![],
790                payload: EventPayload::Instant,
791            }),
792        ];
793        let mut resolve_ctx = ResolveCtx::new();
794        let resolved: Vec<_> =
795            raw_records.into_iter().map(|r| TraceRecord::resolve(&mut resolve_ctx, r)).collect();
796        let event1 = resolved[3]
797            .as_ref()
798            .expect("Failed to parse")
799            .as_ref()
800            .expect("Expected ResolvedRecord");
801        let event2 = resolved[7]
802            .as_ref()
803            .expect("Failed to parse")
804            .as_ref()
805            .expect("Expected ResolvedRecord");
806        let event3 = resolved[9]
807            .as_ref()
808            .expect("Failed to parse")
809            .as_ref()
810            .expect("Expected ResolvedRecord");
811        match event1 {
812            TraceRecord::Event(event_record) => {
813                assert_eq!(event_record.category, "cat_1_1");
814                assert_eq!(event_record.name, "name_1_2");
815            }
816            _ => assert!(false),
817        };
818        match event2 {
819            TraceRecord::Event(event_record) => {
820                assert_eq!(event_record.category, "cat_2_1");
821                assert_eq!(event_record.name, "name_2_2");
822            }
823            _ => assert!(false),
824        };
825        match event3 {
826            TraceRecord::Event(event_record) => {
827                assert_eq!(event_record.category, "cat_1_1");
828                assert_eq!(event_record.name, "name_1_2");
829            }
830            _ => assert!(false),
831        };
832    }
833}