use std::fmt;
use std::time::{Duration, Instant};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum WritePhase {
Open,
Write,
BuildId,
Commit,
}
impl WritePhase {
pub const ALL: [Self; 4] = [Self::Open, Self::Write, Self::BuildId, Self::Commit];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Open => "open",
Self::Write => "write",
Self::BuildId => "build-id",
Self::Commit => "commit",
}
}
const fn index(self) -> usize {
match self {
Self::Open => 0,
Self::Write => 1,
Self::BuildId => 2,
Self::Commit => 3,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Backing {
Mapped,
Written,
Buffered,
#[default]
Memory,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct WriteStats {
pub bytes: u64,
pub backing: Backing,
pub background_release: bool,
elapsed: [Duration; 4],
}
impl WriteStats {
#[must_use]
pub fn new(bytes: u64, backing: Backing) -> Self {
Self {
bytes,
backing,
..Self::default()
}
}
#[must_use]
pub fn elapsed(&self, phase: WritePhase) -> Duration {
self.elapsed[phase.index()]
}
pub fn record(&mut self, phase: WritePhase, duration: Duration) {
let slot = &mut self.elapsed[phase.index()];
*slot = slot.saturating_add(duration);
}
pub fn time<T>(&mut self, phase: WritePhase, f: impl FnOnce() -> T) -> T {
let start = Instant::now();
let result = f();
self.record(phase, start.elapsed());
result
}
#[must_use]
pub fn total(&self) -> Duration {
self.elapsed
.iter()
.fold(Duration::ZERO, |acc, d| acc.saturating_add(*d))
}
}
impl fmt::Display for WriteStats {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} bytes ({:?})", self.bytes, self.backing)?;
for phase in WritePhase::ALL {
write!(f, ", {} {:?}", phase.name(), self.elapsed(phase))?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn records_and_sums() {
let mut stats = WriteStats::new(10, Backing::Mapped);
stats.record(WritePhase::Open, Duration::from_millis(2));
stats.record(WritePhase::Open, Duration::from_millis(3));
stats.record(WritePhase::Commit, Duration::from_millis(1));
let value = stats.time(WritePhase::Write, || 7);
assert_eq!(value, 7);
assert_eq!(stats.elapsed(WritePhase::Open), Duration::from_millis(5));
assert!(stats.total() >= Duration::from_millis(6));
assert!(stats.to_string().starts_with("10 bytes (Mapped), open"));
}
}