use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct MetricsDuration {
nanos: u64,
}
impl MetricsDuration {
pub const ZERO: Self = Self { nanos: 0 };
#[inline]
pub fn from_nanos(nanos: u64) -> Self {
Self { nanos }
}
#[inline]
pub fn from_micros(micros: u64) -> Self {
Self {
nanos: micros.saturating_mul(1_000),
}
}
#[inline]
pub fn from_millis(millis: u64) -> Self {
Self {
nanos: millis.saturating_mul(1_000_000),
}
}
#[inline]
pub fn from_secs(secs: u64) -> Self {
Self {
nanos: secs.saturating_mul(1_000_000_000),
}
}
#[inline]
pub fn from_secs_f64(secs: f64) -> Self {
let nanos = (secs * 1_000_000_000.0) as u64;
Self { nanos }
}
#[inline]
pub fn from_std(duration: std::time::Duration) -> Self {
Self {
nanos: duration.as_nanos() as u64,
}
}
#[inline]
pub fn from_instant_elapsed(start: std::time::Instant) -> Self {
Self::from_std(start.elapsed())
}
#[inline]
pub fn as_nanos(&self) -> u64 {
self.nanos
}
#[inline]
pub fn as_micros(&self) -> u64 {
self.nanos / 1_000
}
#[inline]
pub fn as_millis(&self) -> u64 {
self.nanos / 1_000_000
}
#[inline]
pub fn as_secs(&self) -> u64 {
self.nanos / 1_000_000_000
}
#[inline]
pub fn as_secs_f64(&self) -> f64 {
self.nanos as f64 / 1_000_000_000.0
}
#[inline]
pub fn to_std(&self) -> std::time::Duration {
std::time::Duration::from_nanos(self.nanos)
}
#[inline]
pub fn is_zero(&self) -> bool {
self.nanos == 0
}
pub fn checked_add(&self, rhs: MetricsDuration) -> Option<MetricsDuration> {
self.nanos
.checked_add(rhs.nanos)
.map(|nanos| MetricsDuration { nanos })
}
pub fn saturating_add(&self, rhs: MetricsDuration) -> MetricsDuration {
MetricsDuration {
nanos: self.nanos.saturating_add(rhs.nanos),
}
}
}
impl Default for MetricsDuration {
fn default() -> Self {
Self::ZERO
}
}
impl std::fmt::Display for MetricsDuration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.nanos == 0 {
write!(f, "0s")
} else if self.nanos < 1_000 {
write!(f, "{}ns", self.nanos)
} else if self.nanos < 1_000_000 {
write!(f, "{:.1}μs", self.as_nanos() as f64 / 1_000.0)
} else if self.nanos < 1_000_000_000 {
write!(f, "{:.1}ms", self.as_nanos() as f64 / 1_000_000.0)
} else {
write!(f, "{:.2}s", self.as_secs_f64())
}
}
}
impl From<std::time::Duration> for MetricsDuration {
fn from(d: std::time::Duration) -> Self {
Self::from_std(d)
}
}
impl From<MetricsDuration> for std::time::Duration {
fn from(d: MetricsDuration) -> Self {
d.to_std()
}
}
impl std::ops::Add for MetricsDuration {
type Output = MetricsDuration;
fn add(self, rhs: MetricsDuration) -> MetricsDuration {
MetricsDuration {
nanos: self.nanos + rhs.nanos,
}
}
}
impl std::ops::Sub for MetricsDuration {
type Output = MetricsDuration;
fn sub(self, rhs: MetricsDuration) -> MetricsDuration {
MetricsDuration {
nanos: self.nanos - rhs.nanos,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_duration_conversions() {
assert_eq!(MetricsDuration::from_nanos(1_000).as_micros(), 1);
assert_eq!(MetricsDuration::from_micros(1_000).as_millis(), 1);
assert_eq!(MetricsDuration::from_millis(1_000).as_secs(), 1);
assert_eq!(MetricsDuration::from_secs(1).as_millis(), 1_000);
}
#[test]
fn test_duration_display() {
assert_eq!(MetricsDuration::from_nanos(0).to_string(), "0s");
assert_eq!(MetricsDuration::from_nanos(500).to_string(), "500ns");
assert_eq!(MetricsDuration::from_nanos(1_500).to_string(), "1.5μs");
assert_eq!(MetricsDuration::from_micros(1_500).to_string(), "1.5ms");
assert_eq!(MetricsDuration::from_millis(1_500).to_string(), "1.50s");
}
#[test]
fn test_duration_arithmetic() {
let d1 = MetricsDuration::from_millis(100);
let d2 = MetricsDuration::from_millis(50);
assert_eq!((d1 + d2).as_millis(), 150);
assert_eq!((d1 - d2).as_millis(), 50);
}
#[test]
fn test_duration_ordering() {
let d1 = MetricsDuration::from_millis(100);
let d2 = MetricsDuration::from_millis(200);
assert!(d1 < d2);
assert!(d2 > d1);
assert_eq!(d1, MetricsDuration::from_micros(100_000));
}
#[test]
fn test_std_conversion() {
let std_dur = std::time::Duration::from_millis(100);
let our_dur = MetricsDuration::from_std(std_dur);
assert_eq!(our_dur.as_millis(), 100);
assert_eq!(our_dur.to_std(), std_dur);
}
}