Skip to main content

blktrace_rs/
event.rs

1//! Parsed trace events and the record reader.
2
3use std::io::Read;
4
5use crate::error::BlktraceError;
6use crate::uapi::{
7    blk_io_trace, blktrace_cat_BLK_TC_DISCARD, blktrace_cat_BLK_TC_READ, blktrace_cat_BLK_TC_WRITE,
8    BLK_IO_TRACE_MAGIC, BLK_IO_TRACE_VERSION, BLK_TC_SHIFT,
9};
10
11/// One parsed `blk_io_trace` record together with its trailing pdu data.
12pub struct BlkTraceEvent {
13    /// The raw kernel record; field names match the UAPI header.
14    pub event: blk_io_trace,
15    /// The variable-length pdu data following the record.
16    pub data: Vec<u8>,
17}
18
19impl std::fmt::Debug for BlkTraceEvent {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        f.debug_struct("BlkTraceEvent")
22            .field("event", &self.event)
23            .field("data_len", &self.data.len())
24            .finish()
25    }
26}
27
28impl BlkTraceEvent {
29    /// Whether the event is a read.
30    pub fn is_read(&self) -> bool {
31        ((self.event.action >> BLK_TC_SHIFT) & blktrace_cat_BLK_TC_READ) == blktrace_cat_BLK_TC_READ
32    }
33
34    /// Whether the event is a write.
35    pub fn is_write(&self) -> bool {
36        ((self.event.action >> BLK_TC_SHIFT) & blktrace_cat_BLK_TC_WRITE)
37            == blktrace_cat_BLK_TC_WRITE
38    }
39
40    /// Whether the event is a discard.
41    pub fn is_discard(&self) -> bool {
42        ((self.event.action >> BLK_TC_SHIFT) & blktrace_cat_BLK_TC_DISCARD)
43            == blktrace_cat_BLK_TC_DISCARD
44    }
45}
46
47/// Reads one record (header plus pdu data) from `reader`.
48///
49/// At EOF this returns `BlktraceError::Io` with
50/// [`std::io::ErrorKind::UnexpectedEof`]; callers retry while their trace
51/// session is still live, because the kernel appends records to the relay
52/// channel as they happen.
53pub(crate) fn read_event(reader: &mut impl Read) -> Result<BlkTraceEvent, BlktraceError> {
54    // blk_io_trace is plain old data, so the on-disk form is the in-memory
55    // form; read the bytes straight into it.
56    let mut blk_event = std::mem::MaybeUninit::<blk_io_trace>::uninit();
57    let ptr = unsafe {
58        &mut *blk_event
59            .as_mut_ptr()
60            .cast::<[u8; std::mem::size_of::<blk_io_trace>()]>()
61    };
62    reader.read_exact(ptr)?;
63
64    let blk_event = unsafe { blk_event.assume_init() };
65    if blk_event.magic != BLK_IO_TRACE_MAGIC + BLK_IO_TRACE_VERSION {
66        return Err(BlktraceError::MagicMismatch);
67    }
68
69    let mut data = vec![0; blk_event.pdu_len as usize];
70    reader.read_exact(&mut data)?;
71
72    Ok(BlkTraceEvent {
73        event: blk_event,
74        data,
75    })
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    fn trace(action: u32, pdu_len: u16, magic: u32) -> blk_io_trace {
83        blk_io_trace {
84            magic,
85            action,
86            pdu_len,
87            ..Default::default()
88        }
89    }
90
91    fn valid_trace(action: u32, pdu_len: u16) -> blk_io_trace {
92        trace(action, pdu_len, BLK_IO_TRACE_MAGIC + BLK_IO_TRACE_VERSION)
93    }
94
95    // The wire format is the raw C struct, so serialize it back the same way
96    // the reader deserializes it: a plain byte view of the repr(C) value.
97    fn to_bytes(trace: &blk_io_trace, pdu: &[u8]) -> Vec<u8> {
98        let header = unsafe {
99            std::slice::from_raw_parts(
100                trace as *const _ as *const u8,
101                std::mem::size_of::<blk_io_trace>(),
102            )
103        };
104        let mut bytes = header.to_vec();
105        bytes.extend_from_slice(pdu);
106        bytes
107    }
108
109    #[test]
110    fn read_event_parses_header_and_pdu() {
111        let read_action = blktrace_cat_BLK_TC_READ << BLK_TC_SHIFT;
112        let bytes = to_bytes(&valid_trace(read_action, 3), b"xyz");
113        let mut reader = &bytes[..];
114
115        let event = read_event(&mut reader).unwrap();
116        assert_eq!(event.data, b"xyz");
117        assert!(event.is_read());
118        assert!(!event.is_write());
119        assert!(!event.is_discard());
120        // The pdu data is fully consumed.
121        assert_eq!(reader.len(), 0);
122    }
123
124    #[test]
125    fn read_event_rejects_bad_magic() {
126        let bytes = to_bytes(&trace(0, 0, 0), b"");
127        let mut reader = &bytes[..];
128
129        assert!(matches!(
130            read_event(&mut reader),
131            Err(BlktraceError::MagicMismatch)
132        ));
133    }
134
135    #[test]
136    fn read_event_reports_eof_on_empty_input() {
137        let mut reader = &b""[..];
138
139        let err = read_event(&mut reader).unwrap_err();
140        let kind = match &err {
141            BlktraceError::Io(e) => e.kind(),
142            _ => panic!("expected an io error, got {err:?}"),
143        };
144        assert_eq!(kind, std::io::ErrorKind::UnexpectedEof);
145    }
146
147    #[test]
148    fn read_event_reports_eof_on_truncated_pdu() {
149        let bytes = to_bytes(&valid_trace(0, 4), b"xy");
150        let mut reader = &bytes[..];
151
152        let err = read_event(&mut reader).unwrap_err();
153        let kind = match &err {
154            BlktraceError::Io(e) => e.kind(),
155            _ => panic!("expected an io error, got {err:?}"),
156        };
157        assert_eq!(kind, std::io::ErrorKind::UnexpectedEof);
158    }
159
160    #[test]
161    fn event_classification_by_action_bits() {
162        let read = blk_io_trace {
163            action: blktrace_cat_BLK_TC_READ << BLK_TC_SHIFT,
164            ..Default::default()
165        };
166        let write = blk_io_trace {
167            action: blktrace_cat_BLK_TC_WRITE << BLK_TC_SHIFT,
168            ..Default::default()
169        };
170        let discard = blk_io_trace {
171            action: blktrace_cat_BLK_TC_DISCARD << BLK_TC_SHIFT,
172            ..Default::default()
173        };
174
175        assert!(BlkTraceEvent {
176            event: read,
177            data: vec![]
178        }
179        .is_read());
180        assert!(BlkTraceEvent {
181            event: write,
182            data: vec![]
183        }
184        .is_write());
185        assert!(BlkTraceEvent {
186            event: discard,
187            data: vec![]
188        }
189        .is_discard());
190    }
191}