use std::fmt;
use std::num::NonZeroUsize;
use std::time::Instant;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HistorySize(NonZeroUsize);
impl HistorySize {
pub const DEFAULT: Self = Self(NonZeroUsize::new(60).unwrap());
#[must_use]
pub const fn new(size: usize) -> Self {
match NonZeroUsize::new(size) {
Some(non_zero) => Self(non_zero),
None => panic!("HistorySize must be non-zero"),
}
}
#[must_use]
#[inline]
pub const fn get(&self) -> usize {
self.0.get()
}
}
impl Default for HistorySize {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)]
pub struct Throughput(f64);
impl Throughput {
#[must_use]
#[inline]
pub const fn new(bps: f64) -> Self {
Self(bps)
}
#[must_use]
#[inline]
pub const fn zero() -> Self {
Self(0.0)
}
#[must_use]
#[inline]
pub const fn get(&self) -> f64 {
self.0
}
#[must_use]
pub fn format(&self) -> String {
const KIB: f64 = 1_024.0;
const MIB: f64 = KIB * 1_024.0;
const GIB: f64 = MIB * 1_024.0;
const TIB: f64 = GIB * 1_024.0;
const PIB: f64 = TIB * 1_024.0;
if self.0 >= PIB {
format!("{:.2} PiB/s", self.0 / PIB)
} else if self.0 >= TIB {
format!("{:.2} TiB/s", self.0 / TIB)
} else if self.0 >= GIB {
format!("{:.2} GiB/s", self.0 / GIB)
} else if self.0 >= MIB {
format!("{:.2} MiB/s", self.0 / MIB)
} else if self.0 >= KIB {
format!("{:.2} KiB/s", self.0 / KIB)
} else {
format!("{:.0} B/s", self.0)
}
}
}
impl Default for Throughput {
fn default() -> Self {
Self::zero()
}
}
impl fmt::Display for Throughput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.format())
}
}
impl From<f64> for Throughput {
fn from(bps: f64) -> Self {
Self::new(bps)
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)]
pub struct CommandsPerSecond(f64);
impl CommandsPerSecond {
#[must_use]
#[inline]
pub const fn new(cps: f64) -> Self {
Self(cps)
}
#[must_use]
#[inline]
pub const fn zero() -> Self {
Self(0.0)
}
#[must_use]
#[inline]
pub const fn get(&self) -> f64 {
self.0
}
}
impl Default for CommandsPerSecond {
fn default() -> Self {
Self::zero()
}
}
impl fmt::Display for CommandsPerSecond {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.1}", self.0)
}
}
impl From<f64> for CommandsPerSecond {
fn from(cps: f64) -> Self {
Self::new(cps)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Timestamp(Instant);
impl Timestamp {
#[must_use]
#[inline]
pub const fn new(instant: Instant) -> Self {
Self(instant)
}
#[must_use]
#[inline]
pub fn now() -> Self {
Self(Instant::now())
}
#[must_use]
#[inline]
pub const fn into_inner(self) -> Instant {
self.0
}
#[must_use]
#[inline]
pub fn elapsed(&self) -> std::time::Duration {
self.0.elapsed()
}
#[must_use]
#[inline]
pub fn duration_since(&self, earlier: Self) -> std::time::Duration {
self.0.duration_since(earlier.0)
}
}
impl From<Instant> for Timestamp {
fn from(instant: Instant) -> Self {
Self::new(instant)
}
}
impl From<Timestamp> for Instant {
fn from(ts: Timestamp) -> Self {
ts.into_inner()
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)] mod tests {
use super::*;
#[test]
fn test_history_size() {
let size = HistorySize::new(60);
assert_eq!(size.get(), 60);
assert_eq!(HistorySize::DEFAULT.get(), 60);
assert_eq!(HistorySize::default().get(), 60);
}
#[test]
#[should_panic(expected = "HistorySize must be non-zero")]
fn test_history_size_zero_panics() {
let _ = HistorySize::new(0);
}
#[test]
fn test_history_size_custom_values() {
assert_eq!(HistorySize::new(100).get(), 100);
assert_eq!(HistorySize::new(1).get(), 1);
assert_eq!(HistorySize::new(1000).get(), 1000);
}
#[test]
fn test_bytes_per_second() {
let bps = Throughput::new(1_500_000.0);
assert_eq!(bps.format(), "1.43 MiB/s");
let bps2 = Throughput::new(2_500.0);
assert_eq!(bps2.format(), "2.44 KiB/s");
let bps3 = Throughput::new(500.0);
assert_eq!(bps3.format(), "500 B/s");
assert_eq!(Throughput::zero().get(), 0.0);
}
#[test]
fn test_bytes_per_second_format_boundaries() {
assert_eq!(Throughput::new(1_048_577.0).format(), "1.00 MiB/s");
assert_eq!(Throughput::new(1_048_575.0).format(), "1024.00 KiB/s");
assert_eq!(Throughput::new(1_025.0).format(), "1.00 KiB/s");
assert_eq!(Throughput::new(999.0).format(), "999 B/s");
assert_eq!(Throughput::zero().format(), "0 B/s");
}
#[test]
fn test_bytes_per_second_default() {
let bps = Throughput::default();
assert_eq!(bps.get(), 0.0);
}
#[test]
fn test_bytes_per_second_display() {
assert_eq!(Throughput::new(1_500_000.0).to_string(), "1.43 MiB/s");
assert_eq!(Throughput::new(2_500.0).to_string(), "2.44 KiB/s");
assert_eq!(Throughput::new(500.0).to_string(), "500 B/s");
}
#[test]
fn test_bytes_per_second_from_f64() {
let bps = Throughput::from(1234.5);
assert_eq!(bps.get(), 1234.5);
}
#[test]
fn test_commands_per_second() {
let cps = CommandsPerSecond::new(123.456);
assert_eq!(cps.to_string(), "123.5");
assert_eq!(CommandsPerSecond::zero().get(), 0.0);
}
#[test]
fn test_commands_per_second_default() {
let cps = CommandsPerSecond::default();
assert_eq!(cps.get(), 0.0);
}
#[test]
fn test_commands_per_second_display() {
assert_eq!(CommandsPerSecond::new(0.0).to_string(), "0.0");
assert_eq!(CommandsPerSecond::new(1.5).to_string(), "1.5");
assert_eq!(CommandsPerSecond::new(99.99).to_string(), "100.0");
}
#[test]
fn test_commands_per_second_from_f64() {
let cps = CommandsPerSecond::from(42.7);
assert_eq!(cps.get(), 42.7);
}
#[test]
fn test_timestamp() {
let ts = Timestamp::now();
std::thread::sleep(std::time::Duration::from_millis(10));
assert!(ts.elapsed() >= std::time::Duration::from_millis(10));
}
#[test]
fn test_timestamp_duration_since() {
let ts1 = Timestamp::now();
std::thread::sleep(std::time::Duration::from_millis(10));
let ts2 = Timestamp::now();
let duration = ts2.duration_since(ts1);
assert!(duration >= std::time::Duration::from_millis(10));
}
#[test]
fn test_timestamp_from_instant() {
let instant = Instant::now();
let ts = Timestamp::from(instant);
assert_eq!(ts.into_inner(), instant);
}
#[test]
fn test_timestamp_into_instant() {
let instant = Instant::now();
let ts = Timestamp::new(instant);
let instant2: Instant = ts.into();
assert_eq!(instant, instant2);
}
}