use crate::models::Stage;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct JobTiming {
pub(crate) total_ms: i64,
pub(crate) processing_ms: i64,
pub(crate) waiting_ms: i64,
}
pub(crate) fn calculate(
previous_stages: &[Stage],
current_stage: &Stage,
now: chrono::DateTime<chrono::Utc>,
) -> JobTiming {
let started_at = previous_stages.first().unwrap_or(current_stage).date();
let finished_at = if current_stage.is_terminal() {
current_stage.date()
} else {
&now
};
let total_ms = elapsed_ms(started_at, finished_at);
let mut processing_ms = 0_i64;
for (index, stage) in previous_stages.iter().enumerate() {
if let Stage::Running(running) = stage {
let next_stage = previous_stages.get(index + 1).unwrap_or(current_stage);
processing_ms =
processing_ms.saturating_add(elapsed_ms(&running.date, next_stage.date()));
}
}
if let Stage::Running(running) = current_stage {
processing_ms = processing_ms.saturating_add(elapsed_ms(&running.date, &now));
}
processing_ms = processing_ms.min(total_ms);
JobTiming {
total_ms,
processing_ms,
waiting_ms: total_ms.saturating_sub(processing_ms),
}
}
fn elapsed_ms(
started_at: &chrono::DateTime<chrono::Utc>,
finished_at: &chrono::DateTime<chrono::Utc>,
) -> i64 {
finished_at
.signed_duration_since(*started_at)
.num_milliseconds()
.max(0)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::{EnqueuedStage, RequeuedStage, RunningStage, SuccessStage};
fn at(second: i64) -> chrono::DateTime<chrono::Utc> {
chrono::DateTime::from_timestamp(second, 0).unwrap_or_default()
}
#[test]
fn separates_processing_from_queue_and_retry_waits() {
let previous_stages = vec![
Stage::Enqueued(EnqueuedStage { date: at(0) }),
Stage::Running(RunningStage { date: at(2) }),
Stage::Requeued(RequeuedStage {
date: at(5),
requeue_count: 1,
not_before: Some(at(8)),
}),
Stage::Enqueued(EnqueuedStage { date: at(8) }),
Stage::Running(RunningStage { date: at(10) }),
];
let current_stage = Stage::Success(SuccessStage { date: at(14) });
assert_eq!(
calculate(&previous_stages, ¤t_stage, at(30)),
JobTiming {
total_ms: 14_000,
processing_ms: 7_000,
waiting_ms: 7_000,
}
);
}
#[test]
fn active_job_duration_uses_now_and_never_becomes_negative() {
assert_eq!(
calculate(&[], &Stage::Running(RunningStage { date: at(20) }), at(25),),
JobTiming {
total_ms: 5_000,
processing_ms: 5_000,
waiting_ms: 0,
}
);
assert_eq!(
calculate(
&[],
&Stage::Enqueued(EnqueuedStage { date: at(20) }),
at(10),
),
JobTiming {
total_ms: 0,
processing_ms: 0,
waiting_ms: 0,
}
);
}
}