Skip to main content

fs_core/
counting_device.rs

1//! A device that counts what a driver asks of it.
2//!
3//! # Why this is in the shared crate rather than a test file
4//!
5//! Every driver in this family is about to be measured and then made
6//! faster, and a measurement is only worth having if the drivers can be
7//! compared against each other and against themselves later. Two
8//! drivers each counting reads with their own wrapper would produce two
9//! numbers that look alike and are not: one might count a read of a
10//! whole extent as one, the other as one per block, and nothing in
11//! either number would say so.
12//!
13//! One instrument, in the crate every driver already depends on. It
14//! wraps a [`BlockRead`] and forwards every call, so a driver mounted
15//! on it behaves exactly as it would otherwise.
16//!
17//! # What the numbers mean
18//!
19//! - **reads** — calls to [`BlockRead::read_at`]. This is the number a
20//!   cache moves: a metadata block read twice is two reads here and one
21//!   after a cache is put underneath.
22//! - **bytes** — the total of every buffer those calls filled. This is
23//!   what the *device* moves, which is not the same thing: a driver
24//!   that reads a 4 KiB block to look at 8 bytes of it moves 4 KiB, and
25//!   only the read count will show the waste.
26//!
27//! Both are worth having. A change that halves reads and doubles bytes
28//! is a readahead that guessed wrong, and one number alone would call
29//! it a win.
30
31use crate::block::BlockRead;
32use crate::error::Result;
33use std::sync::atomic::{AtomicU64, Ordering};
34use std::sync::Arc;
35
36/// Wraps a device and counts the reads passing through it.
37///
38/// The counters are atomic and the type is `Sync`, so a driver reading
39/// from several threads is measured correctly rather than approximately.
40pub struct CountingDevice {
41    inner: Arc<dyn BlockRead>,
42    reads: AtomicU64,
43    bytes: AtomicU64,
44}
45
46impl CountingDevice {
47    /// Wrap `inner`, counting from zero.
48    pub fn new(inner: Arc<dyn BlockRead>) -> Self {
49        CountingDevice {
50            inner,
51            reads: AtomicU64::new(0),
52            bytes: AtomicU64::new(0),
53        }
54    }
55
56    /// How many times the driver called `read_at`.
57    pub fn reads(&self) -> u64 {
58        self.reads.load(Ordering::Relaxed)
59    }
60
61    /// How many bytes those calls asked for.
62    pub fn bytes(&self) -> u64 {
63        self.bytes.load(Ordering::Relaxed)
64    }
65
66    /// Start counting again from zero.
67    ///
68    /// A mount reads a superblock and headers before the work being
69    /// measured begins, and counting that in makes a small operation
70    /// look like a large one. Reset after mounting, measure the
71    /// operation, read the counters.
72    pub fn reset(&self) {
73        self.reads.store(0, Ordering::Relaxed);
74        self.bytes.store(0, Ordering::Relaxed);
75    }
76}
77
78impl BlockRead for CountingDevice {
79    fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
80        self.reads.fetch_add(1, Ordering::Relaxed);
81        self.bytes.fetch_add(buf.len() as u64, Ordering::Relaxed);
82        self.inner.read_at(offset, buf)
83    }
84
85    fn size_bytes(&self) -> u64 {
86        self.inner.size_bytes()
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use crate::test_device::Bytes;
94
95    fn device(len: usize) -> Arc<CountingDevice> {
96        Arc::new(CountingDevice::new(Arc::new(Bytes::new(vec![7u8; len]))))
97    }
98
99    /// One call is one read, whatever it asked for, and the bytes are
100    /// what the buffer wanted rather than what the device holds.
101    #[test]
102    fn it_counts_calls_and_the_bytes_they_asked_for() {
103        let dev = device(4096);
104        let mut small = [0u8; 8];
105        let mut block = [0u8; 512];
106
107        dev.read_at(0, &mut small).expect("read");
108        assert_eq!((dev.reads(), dev.bytes()), (1, 8));
109
110        dev.read_at(1024, &mut block).expect("read");
111        assert_eq!(
112            (dev.reads(), dev.bytes()),
113            (2, 520),
114            "two calls, and the bytes are the sum of both buffers"
115        );
116    }
117
118    /// A read the device refuses is still a read the driver made.
119    ///
120    /// The point of the count is what the driver ASKED for, so a failed
121    /// call belongs in it: a driver looping on an out-of-range offset is
122    /// exactly the shape this is here to make visible.
123    #[test]
124    fn a_failed_read_still_counts() {
125        let dev = device(16);
126        let mut buf = [0u8; 64];
127        assert!(dev.read_at(0, &mut buf).is_err(), "past the end");
128        assert_eq!(dev.reads(), 1, "the driver asked, so it counts");
129    }
130
131    /// Resetting drops the mount's own reads, which is the whole reason
132    /// it exists: an operation measured with them included is measured
133    /// against a constant that has nothing to do with it.
134    #[test]
135    fn resetting_starts_the_measurement_where_the_work_does() {
136        let dev = device(4096);
137        let mut buf = [0u8; 64];
138        dev.read_at(0, &mut buf).expect("the mount's own reads");
139        dev.reset();
140        assert_eq!((dev.reads(), dev.bytes()), (0, 0));
141
142        dev.read_at(64, &mut buf).expect("the work being measured");
143        assert_eq!((dev.reads(), dev.bytes()), (1, 64));
144    }
145}