#![doc(html_root_url = "https://docs.rs/io_wrapper_statistics/0.1.1")] use std::io::{Read, Write, Seek, SeekFrom}; use std::io::Result as IOResult; use std::io::ErrorKind; use std::io::{IoSlice, IoSliceMut}; #[rustversion::nightly] #[cfg(feature = "read_initializer")] use std::io::Initializer; use std::convert::TryFrom; use std::iter::Extend; use num_traits::{PrimInt, Unsigned, Signed}; pub use success_failure_ctr::SuccessFailureCounter; pub mod success_failure_ctr { use num_traits::{PrimInt, Unsigned}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] /// A struct for counting successful and failed attempts. pub struct SuccessFailureCounter { success_ctr: T, failure_ctr: T } impl SuccessFailureCounter { pub fn increment_success(&mut self) { self.success_ctr = self.success_ctr + T::one(); } pub fn add_successes(&mut self, amount: T) { self.success_ctr = self.success_ctr + amount; } pub fn success_ctr(&self) -> T { self.success_ctr } pub fn increment_failure(&mut self) { self.failure_ctr = self.failure_ctr + T::one(); } pub fn add_failures(&mut self, amount: T) { self.failure_ctr = self.failure_ctr + amount; } pub fn failure_ctr(&self) -> T { self.failure_ctr } pub fn attempt_ctr(&self) -> T { self.success_ctr + self.failure_ctr } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum SignedAbsResult { Negative(T), Zero, Positive(T) } /// Returns the absolute value of a signed number, along with the original sign. /// `abs()` returns a signed value and `abs(i*::MIN)` is still negative, /// so we handle this specifically and add typed inputs/outputs fn abs_sign_tuple(signed_number: S) -> SignedAbsResult where S: PrimInt + Signed, U: PrimInt + Unsigned { // Did not assert sizeof(S)==sizeof(U) because that expands to unsafe if signed_number.signum() == S::one() { SignedAbsResult::Positive(U::from(signed_number.abs()).unwrap()) } else if signed_number.signum() == S::zero() { SignedAbsResult::Zero } else if signed_number.signum() == -S::one() { if signed_number == S::min_value() { // .abs would be borked-do manually // Primitive integer types guaranteed to be two's complement SignedAbsResult::Negative(U::from(S::max_value()).unwrap()+U::one()) } else { SignedAbsResult::Negative(U::from(signed_number.abs()).unwrap()) } } else { unreachable!() } } #[derive(Debug, Clone, Copy)] /// Types of IO Operations. pub enum IopActions { /// Attempted read of the given size. Read(usize), /// Attempted seek to the given position. Seek(SeekFrom), /// Attempted write of the given size. Write(usize), /// Attempted flush of a writer. Flush } #[derive(Debug, Clone, Copy)] /// Results of IO Operations. /// /// We store only [`std::io::ErrorKind`] because [`std::io::Result`] is not clonable and `Arc` would be messy with lifetimes. pub enum IopResults { /// Result of a read operation. Read(Result), /// Result of a seek operation. Seek(Result), /// Result of a write operation. Write(Result), /// Result of a flush operation. Flush(Result<(), ErrorKind>) } pub type IopInfoPair = (IopActions, IopResults); #[derive(Debug)] /// A wrapper around an IO object that tracks operations and statistics. pub struct IOStatWrapper { inner_io: T, iop_log: C, read_call_counter: SuccessFailureCounter, read_byte_counter: usize, seek_call_counter: SuccessFailureCounter, seek_pos: u64, // Meaningless unless T: Seek write_call_counter: SuccessFailureCounter, write_flush_counter: SuccessFailureCounter, write_byte_counter: usize } impl IOStatWrapper where C: Default + Extend { /// Create a new IOStatWrapper with a manually given seek position. /// Detecting the seek position automatically is not possible without specialization. pub fn new(obj: T, start_seek_pos: u64) -> IOStatWrapper { IOStatWrapper { inner_io: obj, iop_log: C::default(), read_call_counter: SuccessFailureCounter::default(), read_byte_counter: 0, seek_call_counter: SuccessFailureCounter::default(), seek_pos: start_seek_pos, write_call_counter: SuccessFailureCounter::default(), write_flush_counter: SuccessFailureCounter::default(), write_byte_counter: 0 } } /// Extract the original I/O object. pub fn into_inner(self) -> T { self.inner_io } /// Get the I/O operation log containing operations and their results. pub fn iop_log(&self) -> &C { &self.iop_log } } impl> Read for IOStatWrapper { //! We wrap most methods of [`Read`], including provided ones, and pass calls through to the inner I/O object. //! 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. //! 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. fn read(&mut self, buf: &mut [u8]) -> IOResult { //! Passthrough for the `inner_io` read call that increments a call counter and appends a [`IopResults::Read`] object to the log. let read_result = self.inner_io.read(buf); let extend_item: [IopInfoPair; 1] = match read_result { Ok(n) => { self.read_call_counter.increment_success(); self.read_byte_counter += n; self.seek_pos += u64::try_from(n).unwrap(); [(IopActions::Read(buf.len()), IopResults::Read(Ok(n)))] }, Err(ref e) => { self.read_call_counter.increment_failure(); [(IopActions::Read(buf.len()), IopResults::Read(Err(e.kind())))] } }; self.iop_log.extend(extend_item); read_result } #[rustversion::since(1.36)] fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> IOResult { self.inner_io.read_vectored(bufs) } #[rustversion::nightly] #[cfg(feature = "can_vector")] fn is_read_vectored(&self) -> bool { self.inner_io.is_read_vectored() } #[rustversion::nightly] #[inline] #[cfg(feature = "read_initializer")] unsafe fn initializer(&self) -> Initializer { self.inner_io.initializer() } fn read_to_end(&mut self, buf: &mut Vec) -> IOResult { self.inner_io.read_to_end(buf) } fn read_to_string(&mut self, buf: &mut String) -> IOResult { self.inner_io.read_to_string(buf) } #[rustversion::since(1.6)] fn read_exact(&mut self, buf: &mut [u8]) -> IOResult<()> { self.inner_io.read_exact(buf) } fn by_ref(&mut self) -> &mut Self where Self: Sized, { // Do not pass this one through to the inner_io object self } // Missing: bytes, chain, and take, as the struct fields are private // Issues arise if default impls are overriden, but this is unlikely /*fn bytes(self) -> Bytes where Self: Sized, { Bytes{inner: self} } fn chain(self, next: R) -> Chain where Self: Sized, { Chain{first: self, second: next, done_first: false} } fn take(self, limit: u64) -> Take where Self: Sized, { Take{inner: self, limit} }*/ } impl IOStatWrapper { /// Returns the number of times [`Read::read()`] was invoked. pub fn read_call_counter(&self) -> &SuccessFailureCounter { &self.read_call_counter } /// Returns the total number of bytes read. pub fn read_byte_counter(&self) -> usize { self.read_byte_counter } } impl> Seek for IOStatWrapper { //! We wrap all methods of [`Seek`], including provided ones, and pass calls through to the inner I/O object. //! 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. fn seek(&mut self, pos: SeekFrom) -> IOResult { //! Passthrough for the `inner_io` seek call that increments a call counter and appends a [`IopResults::Seek`] object to the log. let old_pos = self.seek_pos; let seek_result = self.inner_io.seek(pos); let extend_item: [IopInfoPair; 1] = match seek_result { Ok(n) => { self.seek_call_counter.increment_success(); self.seek_pos = n; if let SeekFrom::Current(offset) = pos { match abs_sign_tuple::(offset) { SignedAbsResult::Zero => { debug_assert_eq!(old_pos, n); }, SignedAbsResult::Positive(a) => { debug_assert_eq!(old_pos+a, n) }, SignedAbsResult::Negative(a) => { debug_assert_eq!(old_pos-a, n) } } }; [(IopActions::Seek(pos), IopResults::Seek(Ok(n)))] }, Err(ref e) => { self.seek_call_counter.increment_failure(); [(IopActions::Seek(pos), IopResults::Seek(Err(e.kind())))] } }; self.iop_log.extend(extend_item); seek_result } #[rustversion::since(1.55)] fn rewind(&mut self) -> IOResult<()> { self.inner_io.rewind() } #[rustversion::nightly] #[cfg(feature = "seek_stream_len")] fn stream_len(&mut self) -> IOResult { self.inner_io.stream_len() } #[rustversion::since(1.51)] fn stream_position(&mut self) -> IOResult { self.inner_io.stream_position() } } impl IOStatWrapper { /// Returns the number of times [`Seek::seek()`] was invoked. pub fn seek_call_counter(&self) -> &SuccessFailureCounter { &self.seek_call_counter } /// Get the current seek position without doing an actual seek operation. /// /// This is accomplished by storing a separate position integer. /// When debug assertions are on we assert after every seek operation that the cursor is where we expect it to be. pub fn seek_pos(&self) -> u64 { self.seek_pos } } impl> Write for IOStatWrapper { //! We wrap all methods of [`Write`], including provided ones, and pass calls through to the inner I/O object. //! 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. fn write(&mut self, buf: &[u8]) -> IOResult { //! Passthrough for the `inner_io` write call that increments a call counter and appends a [`IopResults::Write`] object to the log. let write_result = self.inner_io.write(buf); let extend_item: [IopInfoPair; 1] = match write_result { Ok(n) => { self.write_call_counter.increment_success(); self.write_byte_counter += n; self.seek_pos += u64::try_from(n).unwrap(); [(IopActions::Write(buf.len()), IopResults::Write(Ok(n)))] }, Err(ref e) => { self.write_call_counter.increment_failure(); [(IopActions::Write(buf.len()), IopResults::Write(Err(e.kind())))] } }; self.iop_log.extend(extend_item); write_result } fn flush(&mut self) -> IOResult<()> { //! Passthrough for the `inner_io` write call that increments a call counter and appends a [`IopResults::Flush`] object to the log. let flush_result = self.inner_io.flush(); let extend_item: [IopInfoPair; 1] = match flush_result { Ok(()) => { self.write_flush_counter.increment_success(); [(IopActions::Flush, IopResults::Flush(Ok(())))] }, Err(ref e) => { self.write_flush_counter.increment_failure(); [(IopActions::Flush, IopResults::Flush(Err(e.kind())))] } }; self.iop_log.extend(extend_item); flush_result } #[rustversion::since(1.36.0)] fn write_vectored(&mut self, bufs: &[IoSlice<'_>]) -> IOResult { self.inner_io.write_vectored(bufs) } #[rustversion::nightly] #[cfg(feature = "can_vector")] fn is_write_vectored(&self) -> bool { self.inner_io.is_write_vectored() } // Keep the original declaration even if mut is unneeded here #[allow(unused_mut)] fn write_all(&mut self, mut buf: &[u8]) -> IOResult<()> { self.inner_io.write_all(buf) } #[rustversion::nightly] #[cfg(feature = "write_all_vectored")] fn write_all_vectored(&mut self, mut bufs: &mut [IoSlice<'_>]) -> IOResult<()> { self.inner_io.write_all_vectored(bufs) } fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> IOResult<()> { self.inner_io.write_fmt(fmt) } fn by_ref(&mut self) -> &mut Self where Self: Sized, { // Do not pass this one through to the inner_io object self } } impl IOStatWrapper { /// Returns the number of times [`Write::write()`] was invoked. pub fn write_call_counter(&self) -> &SuccessFailureCounter { &self.write_call_counter } /// Returns the number of times [`Write::flush()`] was invoked. pub fn write_flush_counter(&self) -> &SuccessFailureCounter { &self.write_flush_counter } pub fn write_byte_counter(&self) -> usize { self.write_byte_counter } }