xentrace_parser/
trace.rs

1use std::{fs, io, ops::Deref, path::Path};
2
3use self::parse::parse_trace;
4use crate::{record::Record, Error, Result};
5
6/// Represents a parsed XenTrace binary file.
7///
8/// The trace is truncated to the last readable record,
9/// returning no errors.
10///
11/// # Examples
12///
13/// ```
14/// use xentrace_parser::{Result, Trace};
15///
16/// fn function() -> Result<()> {
17///     let trace = Trace::from_file("/path/to/xentrace.bin")?;
18///
19///     // Alternatively, you can create a trace from a reader:
20///     // let file = std::fs::File::open("/path/to/xentrace.bin")?;
21///     // let bufreader = std::io::BufReader::new(file);
22///     // let trace = Trace::from_reader(bufreader);
23///
24///     for record in trace.iter() {
25///         println!("{:?}", record);
26///     }
27///
28///     Ok(())
29/// }
30/// ```
31#[derive(Debug)]
32pub struct Trace {
33    records: Box<[Record]>,
34    cpu_count: u32,
35}
36
37impl Trace {
38    /// Constructs a `Trace` from a file specified by its path.
39    ///
40    /// # Errors
41    ///
42    /// This function will return an error if it fails to open
43    /// the trace file or if it fails to parse the file contents.
44    ///
45    /// # Examples
46    ///
47    /// ```
48    /// use xentrace_parser::{Result, Trace};
49    ///
50    /// fn main() -> Result<()> {
51    ///     let trace = Trace::from_file("/path/to/xentrace.bin")?;
52    ///     println!("{:?}", trace);
53    ///     Ok(())
54    /// }
55    /// ```
56    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
57        fs::File::open(path)
58            .map_err(|e| Error::io_error("Failed to open trace file", e))
59            .map(io::BufReader::new)
60            .and_then(parse_trace)
61    }
62
63    /// Constructs a `Trace` from a byte slice.
64    ///
65    /// # Errors
66    ///
67    /// This function will return an error if it fails to parse the trace data.
68    ///
69    /// # Examples
70    ///
71    /// ```
72    /// use xentrace_parser::{Trace, Result};
73    ///
74    /// fn main() -> Result<()> {
75    ///     let bytes: Vec<u8> = vec![/* byte data */];
76    ///     let trace = Trace::from_bytes(&bytes)?;
77    ///     println!("{:?}", trace);
78    ///     Ok(())
79    /// }
80    /// ```
81    pub fn from_bytes<B: AsRef<[u8]>>(bytes: B) -> Result<Self> {
82        let reader = io::Cursor::new(bytes);
83        parse_trace(reader)
84    }
85
86    /// Constructs a `Trace` from any type that implements `io::Read`.
87    ///
88    /// # Errors
89    ///
90    /// This function will return an error if it fails to parse the trace data.
91    ///
92    /// # Examples
93    ///
94    /// ```
95    /// use std::fs::File;
96    /// use std::io::BufReader;
97    /// use xentrace_parser::{Trace, Result};
98    ///
99    /// fn main() -> Result<()> {
100    ///     let file = File::open("/path/to/xentrace.bin")?;
101    ///     let bufreader = BufReader::new(file);
102    ///     let trace = Trace::from_reader(bufreader)?;
103    ///     println!("{:?}", trace);
104    ///     Ok(())
105    /// }
106    /// ```
107    pub fn from_reader<R: io::Read>(reader: R) -> Result<Self> {
108        parse_trace(reader)
109    }
110
111    /// Returns the number of [Records](crate::record::Record) parsed from the trace file.
112    ///
113    /// # Examples
114    ///
115    /// ```
116    /// use xentrace_parser::{Trace, Result};
117    ///
118    /// fn main() -> Result<()> {
119    ///     let trace = Trace::from_file("/path/to/xentrace.bin")?;
120    ///     let record_count = trace.record_count();
121    ///     println!("Record count: {}", record_count);
122    ///     Ok(())
123    /// }
124    /// ```
125    pub fn record_count(&self) -> usize {
126        self.records.len()
127    }
128
129    /// Returns the count of CPUs used in the trace file.
130    ///
131    /// **Note:** The value is calculated based on the
132    /// number of unique CPUs found in the trace data.
133    ///
134    /// # Examples
135    ///
136    /// ```
137    /// use xentrace_parser::{Trace, Result};
138    ///
139    /// fn main() -> Result<()> {
140    ///     let trace = Trace::from_file("/path/to/xentrace.bin")?;
141    ///     let cpu_count = trace.cpu_count();
142    ///     println!("CPU count: {}", cpu_count);
143    ///     Ok(())
144    /// }
145    /// ```
146    pub fn cpu_count(&self) -> u32 {
147        self.cpu_count
148    }
149}
150
151impl Deref for Trace {
152    type Target = [Record];
153
154    fn deref(&self) -> &Self::Target {
155        &self.records
156    }
157}
158
159// Functions for trace parsing logic
160mod parse {
161    use std::collections::HashMap;
162
163    use fxhash::FxBuildHasher;
164
165    use super::*;
166    use crate::{
167        record::{Domain, Event, EventCode, EVENT_EXTRA_CAPACITY},
168        util::IoReadUtil,
169    };
170
171    const TRC_TRACE_CPU_CHANGE: u32 = 0x0001F003;
172    const TRC_SCHED_TO_RUN: u32 = 0x00021F0F;
173
174    struct ParserData {
175        domains: HashMap<u32, Domain, FxBuildHasher>,
176        last_cpu: u32,
177        records: Vec<Record>,
178        last_tsc: u64,
179    }
180
181    pub(super) fn parse_trace<R: io::Read>(mut rdr: R) -> Result<Trace> {
182        let mut data = ParserData {
183            domains: HashMap::with_capacity_and_hasher(
184                u16::BITS as usize,
185                FxBuildHasher::default(),
186            ),
187            last_cpu: 0,
188            records: Vec::with_capacity((u16::MAX / 2) as usize),
189            last_tsc: 0,
190        };
191
192        while let Some(record) = next_record(&mut rdr, &mut data)? {
193            if record.event.code == TRC_TRACE_CPU_CHANGE {
194                data.last_cpu = record.event.extra[0].unwrap_or(0);
195                continue;
196            }
197
198            data.records.push(record);
199        }
200
201        let records = {
202            data.records.sort();
203            data.records.into_boxed_slice()
204        };
205
206        match data.domains.len().try_into() {
207            Ok(cpu_count) => Ok(Trace { records, cpu_count }),
208            Err(_) => Err(Error::new(format_args!(
209                "Failed to set host CPU count: {} > u32::MAX",
210                data.domains.len()
211            ))),
212        }
213    }
214
215    fn next_record<R: io::Read>(rdr: &mut R, data: &mut ParserData) -> Result<Option<Record>> {
216        fn read_event<R: io::Read>(rdr: &mut R, last_tsc: &mut u64) -> Result<Option<Event>> {
217            // Truncate the reader at the first misread header
218            let Some(header) = rdr.read_ne_u32().ok() else {
219                return Ok(None);
220            };
221
222            let code = EventCode::from(header & 0x0FFFFFF);
223
224            let tsc = {
225                // has "tsc" value ?
226                if header & (1 << 31) > 0 {
227                    *last_tsc = rdr
228                        .read_ne_u64()
229                        .map_err(|e| Error::io_error("Failed to read tsc value", e))?;
230                }
231
232                *last_tsc
233            };
234
235            let extra = {
236                let len = ((header >> 28) as usize) & EVENT_EXTRA_CAPACITY;
237                let mut extra = [None; EVENT_EXTRA_CAPACITY];
238
239                for entry in extra.iter_mut().take(len) {
240                    *entry = rdr
241                        .read_ne_u32()
242                        .map(Some)
243                        .map_err(|e| Error::io_error("Failed to read extra value", e))?;
244                }
245
246                extra
247            };
248
249            Ok(Some(Event { code, tsc, extra }))
250        }
251
252        // "next_record" function
253        let Some(event) = read_event(rdr, &mut data.last_tsc)? else {
254            return Ok(None);
255        };
256
257        let cpu = data.last_cpu;
258        let domain = if event.code == (event.code & TRC_SCHED_TO_RUN) {
259            let extra_0 = event.extra[0].unwrap_or(0);
260            let domain = Domain::from(extra_0);
261            data.domains.insert(cpu, domain);
262            domain
263        } else {
264            data.domains.get(&cpu).copied().unwrap_or_default()
265        };
266
267        Ok(Some(Record { cpu, domain, event }))
268    }
269}