Skip to main content

fxt/
fxt_builder.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.
4use thiserror::Error;
5use zerocopy::IntoBytes;
6
7#[derive(Clone)]
8pub struct FxtBuilder<H> {
9    header: H,
10    buf: Vec<u64>,
11}
12
13#[derive(Error, Debug)]
14pub enum SerializeError {
15    #[error("Encountered Empty StringRefs when serializing argument's name")]
16    MissingArgName,
17}
18
19impl<H: crate::header::TraceHeader> FxtBuilder<H> {
20    /// Start a new fxt record with a typed header. The header should be completely configured for
21    /// the corresponding record except for its size in words which will be updated by the builder.
22    pub fn new(mut header: H) -> Self {
23        // Make space for our header word before anything gets added.
24        let buf = vec![0];
25
26        // Set an initial size, we'll update as we go.
27        header.set_size_words(1);
28
29        Self { header, buf }
30    }
31
32    pub fn atom(mut self, atom: impl AsRef<[u8]>) -> Self {
33        let atom = atom.as_ref();
34        let start = self.buf.len() * 8;
35        let size_words = self.buf.len() + atom.len().div_ceil(8);
36        assert!(
37            size_words <= crate::MAX_RECORD_SIZE_WORDS,
38            "maximum record size is {} words",
39            crate::MAX_RECORD_SIZE_WORDS
40        );
41        self.buf.resize(size_words, 0);
42        self.buf.as_mut_slice().as_mut_bytes()[start..start + atom.len()].copy_from_slice(atom);
43        self.header.set_size_words(
44            size_words.try_into().expect("trace records size in words must fit in a u16"),
45        );
46        self
47    }
48
49    /// Append an already serialized record without copying it to a byte vector.
50    pub fn atom_words(self, words: impl AsRef<[u64]>) -> Self {
51        self.atom(words.as_ref().as_bytes())
52    }
53
54    /// Return the words of a possibly-valid FXT record with the header in place.
55    pub fn build(mut self) -> Vec<u64> {
56        self.buf[0] = u64::from_ne_bytes(self.header.to_le_bytes());
57        self.buf
58    }
59}
60
61impl<H: std::fmt::Debug> std::fmt::Debug for FxtBuilder<H> {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        // Print in word-aligned chunks, exclude the zeroes we keep for the header.
64        let chunks = self.buf.iter().skip(1).collect::<Vec<_>>();
65        f.debug_struct("FxtBuilder").field("header", &self.header).field("buf", &chunks).finish()
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[derive(Clone, Copy)]
74    struct TestHeader(u64);
75
76    impl crate::header::TraceHeader for TestHeader {
77        fn set_size_words(&mut self, n: u16) {
78            self.0 = n.into();
79        }
80
81        fn to_le_bytes(&self) -> [u8; 8] {
82            self.0.to_le_bytes()
83        }
84    }
85
86    #[test]
87    fn builder_preserves_record_bytes_and_allocation() {
88        let first_atom = [1, 2, 3];
89        let second_atom = [4, 5, 6, 7, 8, 9, 10, 11, 12];
90        let builder = FxtBuilder::new(TestHeader(0)).atom(first_atom).atom(second_atom);
91        let backing = builder.buf.as_ptr();
92        let words: Vec<u64> = builder.build();
93        // Building must keep the aligned allocation rather than copy the whole record.
94        assert_eq!(words.as_ptr(), backing);
95        assert_eq!((words.as_ptr() as usize) % std::mem::align_of::<u64>(), 0);
96        let bytes = words.as_slice().as_bytes();
97        let expected = [
98            4, 0, 0, 0, 0, 0, 0, 0, // header: four words
99            1, 2, 3, 0, 0, 0, 0, 0, // first atom and padding
100            4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0, 0, 0, 0, 0, // second atom and padding
101        ];
102        assert_eq!(bytes, expected);
103    }
104
105    #[test]
106    fn serialized_words_can_be_parsed_as_session_bytes() {
107        let magic = [0x10, 0x00, 0x04, 0x46, 0x78, 0x54, 0x16, 0x00];
108        let mut header = crate::event::EventHeader::empty();
109        header.set_thread_ref(1);
110        let event = FxtBuilder::new(header).atom(42u64.to_le_bytes()).build();
111        let mut session = magic.to_vec();
112        session.extend_from_slice(event.as_bytes());
113
114        let mut parser = crate::SessionParser::new(std::io::Cursor::new(session));
115        assert!(matches!(parser.next(), Some(Ok(crate::TraceRecord::Event(_)))));
116    }
117}