1use 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 self.record_range = self.parsed_bytes.len()..self.parsed_bytes.len() + bytes.len();
75 self.parsed_bytes.extend(bytes);
76
77 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 let resolve_res = TraceRecord::resolve(&mut self.resolver, raw_record);
88 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 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 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
125macro_rules! fill_buffer {
127 ($self:ident, $original_len:ident, $needed:ident, $bytes_read:expr) => {{
128 if $self.reader_is_eof {
129 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 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 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 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 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 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(¤t_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(¤t_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 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 session.extend(&SIMPLE_TRACE_FXT[..8]);
447
448 let mut header = crate::BaseTraceHeader::empty();
450 header.set_raw_type(14); session.extend(FxtBuilder::new(header).atom(&(0u8..27u8).collect::<Vec<u8>>()).build());
452
453 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 session.extend(&SIMPLE_TRACE_FXT[..8]);
501 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 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 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()) .atom(512u64.to_le_bytes()) .atom(513u64.to_le_bytes()) .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 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 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, 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, 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 RawTraceRecord::Metadata(MetadataRecord::ProviderSection(
780 ProviderSectionMetadataRecord { provider_id: 1 },
781 )),
782 RawTraceRecord::Event(RawEventRecord {
783 event_type: 0, 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}