io_wrapper_statistics/
lib.rs

1#![doc(html_root_url = "https://docs.rs/io_wrapper_statistics/0.1.1")]
2
3use std::io::{Read, Write, Seek, SeekFrom};
4use std::io::Result as IOResult;
5use std::io::ErrorKind;
6use std::io::{IoSlice, IoSliceMut};
7#[rustversion::nightly]
8#[cfg(feature = "read_initializer")]
9use std::io::Initializer;
10
11use std::convert::TryFrom;
12
13use std::iter::Extend;
14
15use num_traits::{PrimInt, Unsigned, Signed};
16
17pub use success_failure_ctr::SuccessFailureCounter;
18
19pub mod success_failure_ctr {
20    use num_traits::{PrimInt, Unsigned};
21
22    #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
23    /// A struct for counting successful and failed attempts.
24    pub struct SuccessFailureCounter<T: PrimInt + Unsigned> {
25        success_ctr: T,
26        failure_ctr: T
27    }
28    impl<T: PrimInt + Unsigned> SuccessFailureCounter<T> {
29        pub fn increment_success(&mut self) {
30            self.success_ctr = self.success_ctr + T::one();
31        }
32        pub fn add_successes(&mut self, amount: T) {
33            self.success_ctr = self.success_ctr + amount;
34        }
35        pub fn success_ctr(&self) -> T {
36            self.success_ctr
37        }
38        pub fn increment_failure(&mut self) {
39            self.failure_ctr = self.failure_ctr + T::one();
40        }
41        pub fn add_failures(&mut self, amount: T) {
42            self.failure_ctr = self.failure_ctr + amount;
43        }
44        pub fn failure_ctr(&self) -> T {
45            self.failure_ctr
46        }
47        pub fn attempt_ctr(&self) -> T {
48            self.success_ctr + self.failure_ctr
49        }
50    }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54enum SignedAbsResult<T: PrimInt + Unsigned> {
55    Negative(T),
56    Zero,
57    Positive(T)
58}
59/// Returns the absolute value of a signed number, along with the original sign.
60/// `abs()` returns a signed value and `abs(i*::MIN)` is still negative,
61/// so we handle this specifically and add typed inputs/outputs
62fn abs_sign_tuple<S, U>(signed_number: S) -> SignedAbsResult<U>
63where
64    S: PrimInt + Signed,
65    U: PrimInt + Unsigned
66{
67    // Did not assert sizeof(S)==sizeof(U) because that expands to unsafe
68    if signed_number.signum() == S::one() {
69        SignedAbsResult::Positive(U::from(signed_number.abs()).unwrap())
70    } else if signed_number.signum() == S::zero() {
71        SignedAbsResult::Zero
72    } else if signed_number.signum() == -S::one() {
73        if signed_number == S::min_value() {
74            // .abs would be borked-do manually
75            // Primitive integer types guaranteed to be two's complement
76            SignedAbsResult::Negative(U::from(S::max_value()).unwrap()+U::one())
77        } else {
78            SignedAbsResult::Negative(U::from(signed_number.abs()).unwrap())
79        }
80    } else {
81        unreachable!()
82    }
83}
84
85#[derive(Debug, Clone, Copy)]
86/// Types of IO Operations.
87pub enum IopActions {
88    /// Attempted read of the given size.
89    Read(usize),
90    /// Attempted seek to the given position.
91    Seek(SeekFrom),
92    /// Attempted write of the given size.
93    Write(usize),
94    /// Attempted flush of a writer.
95    Flush
96}
97#[derive(Debug, Clone, Copy)]
98/// Results of IO Operations.
99///
100/// We store only [`std::io::ErrorKind`] because [`std::io::Result`] is not clonable and `Arc<std::io::Error>` would be messy with lifetimes.
101pub enum IopResults {
102    /// Result of a read operation.
103    Read(Result<usize, ErrorKind>),
104    /// Result of a seek operation.
105    Seek(Result<u64, ErrorKind>),
106    /// Result of a write operation.
107    Write(Result<usize, ErrorKind>),
108    /// Result of a flush operation.
109    Flush(Result<(), ErrorKind>)
110}
111pub type IopInfoPair = (IopActions, IopResults);
112
113#[derive(Debug)]
114/// A wrapper around an IO object that tracks operations and statistics.
115pub struct IOStatWrapper<T, C> {
116    inner_io: T,
117    iop_log: C,
118    read_call_counter: SuccessFailureCounter<u64>,
119    read_byte_counter: usize,
120    seek_call_counter: SuccessFailureCounter<u64>,
121    seek_pos: u64, // Meaningless unless T: Seek
122    write_call_counter: SuccessFailureCounter<u64>,
123    write_flush_counter: SuccessFailureCounter<u64>,
124    write_byte_counter: usize
125}
126
127impl<T, C> IOStatWrapper<T, C>
128where
129    C: Default + Extend<IopInfoPair>
130{
131    /// Create a new IOStatWrapper with a manually given seek position.
132    /// Detecting the seek position automatically is not possible without specialization.
133    pub fn new(obj: T, start_seek_pos: u64) -> IOStatWrapper<T, C> {
134        IOStatWrapper {
135            inner_io: obj,
136            iop_log: C::default(),
137            read_call_counter: SuccessFailureCounter::default(),
138            read_byte_counter: 0,
139            seek_call_counter: SuccessFailureCounter::default(),
140            seek_pos: start_seek_pos,
141            write_call_counter: SuccessFailureCounter::default(),
142            write_flush_counter: SuccessFailureCounter::default(),
143            write_byte_counter: 0
144        }
145    }
146    /// Extract the original I/O object.
147    pub fn into_inner(self) -> T {
148        self.inner_io
149    }
150    /// Get the I/O operation log containing operations and their results.
151    pub fn iop_log(&self) -> &C {
152        &self.iop_log
153    }
154}
155
156impl<T: Read, C: Extend<IopInfoPair>> Read for IOStatWrapper<T, C> {
157    //! We wrap most methods of [`Read`], including provided ones, and pass calls through to the inner I/O object.
158    //! The I/O operation log and statistics are only explicitly updated in the [`Read::read()`] function, as it is expected that the other methods are implemented with it.
159    //! Notably, we do not passthrough [`Read::bytes()`], [`Read::chain()`], and [`Read::take()`] as the structs they return have private implementation details that we need to see to have correct type generics. However, for this reason, we do not expect other [`Read`] implementations to have their own implementations either, so this shouldn't be an issue.
160    fn read(&mut self, buf: &mut [u8]) -> IOResult<usize> {
161        //! Passthrough for the `inner_io` read call that increments a call counter and appends a [`IopResults::Read`] object to the log.
162        let read_result = self.inner_io.read(buf);
163        let extend_item: [IopInfoPair; 1] = match read_result {
164            Ok(n) => {
165                self.read_call_counter.increment_success();
166                self.read_byte_counter += n;
167                self.seek_pos += u64::try_from(n).unwrap();
168                [(IopActions::Read(buf.len()),
169                    IopResults::Read(Ok(n)))]
170            },
171            Err(ref e) => {
172                self.read_call_counter.increment_failure();
173                [(IopActions::Read(buf.len()),
174                    IopResults::Read(Err(e.kind())))]
175            }
176        };
177        self.iop_log.extend(extend_item);
178        read_result
179    }
180
181    #[rustversion::since(1.36)]
182    fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> IOResult<usize> {
183        self.inner_io.read_vectored(bufs)
184    }
185    #[rustversion::nightly]
186    #[cfg(feature = "can_vector")]
187    fn is_read_vectored(&self) -> bool {
188        self.inner_io.is_read_vectored()
189    }
190    #[rustversion::nightly]
191    #[inline]
192    #[cfg(feature = "read_initializer")]
193    unsafe fn initializer(&self) -> Initializer {
194        self.inner_io.initializer()
195    }
196    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> IOResult<usize> {
197        self.inner_io.read_to_end(buf)
198    }
199    fn read_to_string(&mut self, buf: &mut String) -> IOResult<usize> {
200        self.inner_io.read_to_string(buf)
201    }
202    #[rustversion::since(1.6)]
203    fn read_exact(&mut self, buf: &mut [u8]) -> IOResult<()> {
204        self.inner_io.read_exact(buf)
205    }
206    fn by_ref(&mut self) -> &mut Self
207    where
208        Self: Sized,
209    {
210        // Do not pass this one through to the inner_io object
211        self
212    }
213    // Missing: bytes, chain, and take, as the struct fields are private
214    // Issues arise if default impls are overriden, but this is unlikely
215
216    /*fn bytes(self) -> Bytes<Self>
217    where
218        Self: Sized,
219    {
220        Bytes{inner: self}
221    }
222    fn chain<R: Read>(self, next: R) -> Chain<Self, R>
223    where
224        Self: Sized,
225    {
226        Chain{first: self, second: next, done_first: false}
227    }
228    fn take(self, limit: u64) -> Take<Self>
229    where
230        Self: Sized,
231    {
232        Take{inner: self, limit}
233    }*/
234}
235impl<T: Read, C> IOStatWrapper<T, C> {
236    /// Returns the number of times [`Read::read()`] was invoked.
237    pub fn read_call_counter(&self) -> &SuccessFailureCounter<u64> {
238        &self.read_call_counter
239    }
240    /// Returns the total number of bytes read.
241    pub fn read_byte_counter(&self) -> usize {
242        self.read_byte_counter
243    }
244}
245
246impl<T: Seek, C: Extend<IopInfoPair>> Seek for IOStatWrapper<T, C> {
247    //! We wrap all methods of [`Seek`], including provided ones, and pass calls through to the inner I/O object.
248    //! The I/O operation log and statistics are only explicitly updated in the [`Seek::seek()`] function, as it is expected that the other methods are implemented with it.
249    fn seek(&mut self, pos: SeekFrom) -> IOResult<u64> {
250        //! Passthrough for the `inner_io` seek call that increments a call counter and appends a [`IopResults::Seek`] object to the log.
251        let old_pos = self.seek_pos;
252        let seek_result = self.inner_io.seek(pos);
253        let extend_item: [IopInfoPair; 1] = match seek_result {
254            Ok(n) => {
255                self.seek_call_counter.increment_success();
256                self.seek_pos = n;
257                if let SeekFrom::Current(offset) = pos {
258                    match abs_sign_tuple::<i64, u64>(offset) {
259                        SignedAbsResult::Zero => {
260                            debug_assert_eq!(old_pos, n);
261                        },
262                        SignedAbsResult::Positive(a) => {
263                            debug_assert_eq!(old_pos+a, n)
264                        },
265                        SignedAbsResult::Negative(a) => {
266                            debug_assert_eq!(old_pos-a, n)
267                        }
268                    }
269                };
270                [(IopActions::Seek(pos),
271                    IopResults::Seek(Ok(n)))]
272            },
273            Err(ref e) => {
274                self.seek_call_counter.increment_failure();
275                [(IopActions::Seek(pos),
276                    IopResults::Seek(Err(e.kind())))]
277            }
278        };
279        self.iop_log.extend(extend_item);
280        seek_result
281    }
282    #[rustversion::since(1.55)]
283    fn rewind(&mut self) -> IOResult<()> {
284        self.inner_io.rewind()
285    }
286    #[rustversion::nightly]
287    #[cfg(feature = "seek_stream_len")]
288    fn stream_len(&mut self) -> IOResult<u64> {
289        self.inner_io.stream_len()
290    }
291    #[rustversion::since(1.51)]
292    fn stream_position(&mut self) -> IOResult<u64> {
293        self.inner_io.stream_position()
294    }
295}
296impl<T: Seek, C> IOStatWrapper<T, C> {
297    /// Returns the number of times [`Seek::seek()`] was invoked.
298    pub fn seek_call_counter(&self) -> &SuccessFailureCounter<u64> {
299        &self.seek_call_counter
300    }
301    /// Get the current seek position without doing an actual seek operation.
302    ///
303    /// This is accomplished by storing a separate position integer.
304    /// When debug assertions are on we assert after every seek operation that the cursor is where we expect it to be.
305    pub fn seek_pos(&self) -> u64 {
306        self.seek_pos
307    }
308}
309
310impl<T: Write, C: Extend<IopInfoPair>> Write for IOStatWrapper<T, C> {
311    //! We wrap all methods of [`Write`], including provided ones, and pass calls through to the inner I/O object.
312    //! The I/O operation log and statistics are explicitly updated in the [`Write::write()`] and [`Write::flush()`] functions, as it is expected that the other methods are implemented with them.
313    fn write(&mut self, buf: &[u8]) -> IOResult<usize> {
314        //! Passthrough for the `inner_io` write call that increments a call counter and appends a [`IopResults::Write`] object to the log.
315        let write_result = self.inner_io.write(buf);
316        let extend_item: [IopInfoPair; 1] = match write_result {
317            Ok(n) => {
318                self.write_call_counter.increment_success();
319                self.write_byte_counter += n;
320                self.seek_pos += u64::try_from(n).unwrap();
321                [(IopActions::Write(buf.len()),
322                    IopResults::Write(Ok(n)))]
323            },
324            Err(ref e) => {
325                self.write_call_counter.increment_failure();
326                [(IopActions::Write(buf.len()),
327                    IopResults::Write(Err(e.kind())))]
328            }
329        };
330        self.iop_log.extend(extend_item);
331        write_result
332    }
333    fn flush(&mut self) -> IOResult<()> {
334        //! Passthrough for the `inner_io` write call that increments a call counter and appends a [`IopResults::Flush`] object to the log.
335        let flush_result = self.inner_io.flush();
336        let extend_item: [IopInfoPair; 1] = match flush_result {
337            Ok(()) => {
338                self.write_flush_counter.increment_success();
339                [(IopActions::Flush, IopResults::Flush(Ok(())))]
340            },
341            Err(ref e) => {
342                self.write_flush_counter.increment_failure();
343                [(IopActions::Flush,
344                    IopResults::Flush(Err(e.kind())))]
345            }
346        };
347        self.iop_log.extend(extend_item);
348        flush_result
349    }
350
351    #[rustversion::since(1.36.0)]
352    fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> IOResult<usize> {
353        self.inner_io.write_vectored(bufs)
354    }
355    #[rustversion::nightly]
356    #[cfg(feature = "can_vector")]
357    fn is_write_vectored(&self) -> bool {
358        self.inner_io.is_write_vectored()
359    }
360    // Keep the original declaration even if mut is unneeded here
361    #[allow(unused_mut)]
362    fn write_all(&mut self, mut buf: &[u8]) -> IOResult<()> {
363        self.inner_io.write_all(buf)
364    }
365    #[rustversion::nightly]
366    #[cfg(feature = "write_all_vectored")]
367    fn write_all_vectored(&mut self, mut bufs: &mut [IoSlice<'_>]) -> IOResult<()> {
368        self.inner_io.write_all_vectored(bufs)
369    }
370    fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> IOResult<()> {
371        self.inner_io.write_fmt(fmt)
372    }
373    fn by_ref(&mut self) -> &mut Self
374    where
375        Self: Sized,
376    {
377        // Do not pass this one through to the inner_io object
378        self
379    }
380}
381impl<T: Write, C> IOStatWrapper<T, C> {
382    /// Returns the number of times [`Write::write()`] was invoked.
383    pub fn write_call_counter(&self) -> &SuccessFailureCounter<u64> {
384        &self.write_call_counter
385    }
386    /// Returns the number of times [`Write::flush()`] was invoked.
387    pub fn write_flush_counter(&self) -> &SuccessFailureCounter<u64> {
388        &self.write_flush_counter
389    }
390    pub fn write_byte_counter(&self) -> usize {
391        self.write_byte_counter
392    }
393}