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::{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                // Remember which bytes within self.parsed_bytes represent the record we're parsing.
73                self.record_range = self.parsed_bytes.len()..self.parsed_bytes.len() + bytes.len();
74                self.parsed_bytes.extend(bytes);
75
76                // Make sure the first record we encounter is the magic number record.
77                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                // Resolve the record to end our borrow on the buffer before rotating it.
86                let resolve_res = TraceRecord::resolve(&mut self.resolver, raw_record);
87                // Update our buffer based on how much was unused to parse this record.
88                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                    // Updated resolution state but don't have any logical records to return,
95                    // try again.
96                    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                    // Fall back to asking for the max trace record size if we don't know
108                    // how much we want.
109                    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
124// We use a macro here because it's difficult to abstract over sync vs. async for read callbacks.
125macro_rules! fill_buffer {
126    ($self:ident, $original_len:ident, $needed:ident, $bytes_read:expr) => {{
127        if $self.reader_is_eof {
128            // We already reached the end of the reader and still failed to parse, so
129            // this iterator is done.
130            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        // Clear out previously parsed bytes
147        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        // Bounce the records through a channel to avoid a Stream impl and all the pinning.
175        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                    // The listener has disconnected, don't need to keep parsing.
190                    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        // Clear out previously parsed bytes
202        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            // No action to take on the magic number.
276            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(&current_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        // Send tiny chunks to shake out any incorrect streaming parser code.
408        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        // Add the magic record from the simple trace before we add our bogus record so we don't
421        // error on an invalid first record.
422        session.extend(&SIMPLE_TRACE_FXT[..8]);
423
424        // Add our bogus record with an unknown type, expecting to skip over it.
425        let mut header = crate::BaseTraceHeader::empty();
426        header.set_raw_type(14); // not currently a valid ordinal
427        session.extend(FxtBuilder::new(header).atom(&(0u8..27u8).collect::<Vec<u8>>()).build());
428
429        // Add the rest of the simple trace.
430        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        // Add the magic record from the simple trace before we add our bogus record so we don't
443        // error on an invalid first record.
444        session.extend(&SIMPLE_TRACE_FXT[..8]);
445        // This error is not valid because it's not UTF-8
446        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        // We want to test that even we seeing an invalid record, we still parse the rest of the
463        // session.
464        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        // Make a 2 word header with some arbitrary values.
475        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()) // timestamp
484            .atom(512u64.to_le_bytes()) // process
485            .atom(513u64.to_le_bytes()) // thread
486            .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            // There should be no warnings produced from these tests.
689            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            // Ensure that if we define the same string index multiple times, it's
700            // disambiguated per provider
701            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, // Instant
705                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, // Instant
720                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            // Back to the first provider
729            RawTraceRecord::Metadata(MetadataRecord::ProviderSection(
730                ProviderSectionMetadataRecord { provider_id: 1 },
731            )),
732            RawTraceRecord::Event(RawEventRecord {
733                event_type: 0, // Instant
734                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}