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