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}