use std::collections::BTreeMap;
use crate::Usage;
pub const DAY_MS: u64 = 86_400_000;
pub fn day_of(unix_ms: u64) -> u64 {
unix_ms / DAY_MS
}
pub fn daily_totals(readings: &[Usage], meter: &str) -> BTreeMap<u64, u64> {
let mut days: BTreeMap<u64, u64> = BTreeMap::new();
for reading in readings {
let Some(value) = reading.meters.get(meter).copied() else {
continue;
};
let day = days.entry(day_of(reading.measured_at_unix_ms)).or_insert(0);
*day = (*day).max(value);
}
days
}
pub fn window_total(readings: &[Usage], meter: &str, since_unix_ms: u64, until_unix_ms: u64) -> u64 {
let (from, to) = (day_of(since_unix_ms), day_of(until_unix_ms));
daily_totals(readings, meter)
.range(from..=to)
.map(|(_, total)| *total)
.fold(0u64, |acc, n| acc.saturating_add(n))
}
pub fn quiet_for(last_seen_unix_ms: Option<u64>, now_unix_ms: u64) -> Quiet {
let Some(last_seen_unix_ms) = last_seen_unix_ms else {
return Quiet::NoData;
};
if last_seen_unix_ms == 0 {
return Quiet::Never;
}
match now_unix_ms.checked_sub(last_seen_unix_ms) {
None => Quiet::For(0),
Some(ms) => Quiet::For(ms),
}
}
pub fn quiet_for_ms(last_seen_unix_ms: Option<u64>, now_unix_ms: u64) -> u64 {
match quiet_for(last_seen_unix_ms, now_unix_ms) {
Quiet::NoData => 0,
Quiet::Never => u64::MAX,
Quiet::For(ms) => ms,
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum Quiet {
NoData,
Never,
For(u64),
}
impl Quiet {
pub fn days(self) -> Option<u64> {
match self {
Quiet::NoData | Quiet::Never => None,
Quiet::For(ms) => Some(ms / DAY_MS),
}
}
pub fn at_least_days(self, days: u64) -> bool {
match self {
Quiet::NoData => false,
Quiet::Never => true,
Quiet::For(ms) => ms >= days.saturating_mul(DAY_MS),
}
}
pub fn is_no_data(self) -> bool {
matches!(self, Quiet::NoData)
}
}
pub fn last_seen(usage: &Usage) -> Option<u64> {
let at = |meter: &str| usage.meters.get(meter).copied();
match (at("last_push_unix_ms"), at("last_fetch_unix_ms")) {
(None, None) => None,
(push, fetch) => Some(push.unwrap_or(0).max(fetch.unwrap_or(0))),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn reading(at: u64, meters: &[(&str, u64)]) -> Usage {
Usage {
meters: meters.iter().map(|(k, v)| ((*k).to_string(), *v)).collect(),
measured_at_unix_ms: at,
}
}
const NOON: u64 = 1_789_041_600_000;
#[test]
fn a_daily_counter_is_read_once_per_day_however_often_it_is_polled() {
let midnight = day_of(NOON) * DAY_MS;
let mut readings = vec![reading(midnight + 8 * 3_600_000, &[("refused_today", 0)])];
for tick in 0..60u64 {
readings.push(reading(
midnight + 9 * 3_600_000 + tick * 900_000,
&[("refused_today", 1)],
));
}
let totals = daily_totals(&readings, "refused_today");
assert_eq!(totals.len(), 1, "one day: {totals:?}");
assert_eq!(
totals[&day_of(NOON)],
1,
"summing the readings would say 60; the counter is per day, not per poll"
);
}
#[test]
fn the_days_total_is_its_peak_and_not_its_last_reading() {
let midnight = day_of(NOON) * DAY_MS;
let readings = vec![
reading(midnight + 3_600_000, &[("refused_today", 4)]),
reading(midnight + 7_200_000, &[("refused_today", 9)]),
reading(midnight + DAY_MS - 1, &[("refused_today", 1)]),
];
assert_eq!(daily_totals(&readings, "refused_today")[&day_of(NOON)], 9);
}
#[test]
fn a_window_adds_the_days_it_names_and_invents_none() {
let day = |n: u64| day_of(NOON) * DAY_MS + n * DAY_MS + 3_600_000;
let readings = vec![
reading(day(0), &[("refused_today", 2)]),
reading(day(1), &[("refused_today", 5)]),
reading(day(3), &[("refused_today", 1)]),
];
let totals = daily_totals(&readings, "refused_today");
assert_eq!(totals.len(), 3, "three days were measured: {totals:?}");
assert!(
!totals.contains_key(&(day_of(NOON) + 2)),
"a day nobody measured must not appear as a day with no refusals"
);
assert_eq!(window_total(&readings, "refused_today", day(0), day(3)), 8);
assert_eq!(window_total(&readings, "refused_today", day(1), day(3)), 6, "the window is honoured");
assert_eq!(window_total(&readings, "refused_today", day(4), day(9)), 0, "a window with nothing in it");
}
#[test]
fn a_meter_that_is_not_reported_is_not_a_zero() {
let readings = vec![reading(NOON, &[("bytes_in_today", 4_096)])];
assert!(
daily_totals(&readings, "refused_today").is_empty(),
"a meter nobody reported must not produce a day of zero refusals"
);
assert_eq!(window_total(&readings, "refused_today", 0, NOON), 0);
}
#[test]
fn never_is_not_nineteen_seventy() {
assert_eq!(quiet_for(Some(0), NOON), Quiet::Never);
assert_eq!(quiet_for(Some(0), NOON).days(), None, "never has no number of days");
assert!(quiet_for(Some(0), NOON).at_least_days(14));
assert_eq!(
quiet_for_ms(Some(0), NOON),
u64::MAX,
"the tenant nobody has ever used must sort above one last seen a year ago"
);
let three_days = NOON - 3 * DAY_MS;
assert_eq!(quiet_for(Some(three_days), NOON), Quiet::For(3 * DAY_MS));
assert_eq!(quiet_for(Some(three_days), NOON).days(), Some(3));
assert!(!quiet_for(Some(three_days), NOON).at_least_days(14));
assert!(quiet_for(Some(NOON - 14 * DAY_MS), NOON).at_least_days(14), "exactly at the threshold is quiet");
}
#[test]
fn a_product_that_reports_no_flow_is_no_data_and_never_a_zero() {
let measured_zero = reading(NOON, &[("last_push_unix_ms", 0), ("last_fetch_unix_ms", 0)]);
let unmeasured = reading(NOON, &[("pack_bytes", 4_096)]);
assert_eq!(last_seen(&measured_zero), Some(0), "reported, and it reads zero");
assert_eq!(last_seen(&unmeasured), None, "no flow meter was reported at all");
assert_ne!(
last_seen(&measured_zero),
last_seen(&unmeasured),
"an absent meter and a meter reading zero must not be the same value"
);
assert_eq!(quiet_for(last_seen(&measured_zero), NOON), Quiet::Never);
assert_eq!(quiet_for(last_seen(&unmeasured), NOON), Quiet::NoData);
assert!(!quiet_for(last_seen(&measured_zero), NOON).is_no_data());
assert!(quiet_for(last_seen(&unmeasured), NOON).is_no_data());
assert!(
quiet_for(last_seen(&measured_zero), NOON).at_least_days(14),
"a tenant measured as never used belongs on the SILENT list"
);
assert!(
!quiet_for(last_seen(&unmeasured), NOON).at_least_days(14),
"a tenant nobody measured must not be published as a silence"
);
assert_eq!(quiet_for_ms(last_seen(&unmeasured), NOON), 0);
assert!(quiet_for_ms(last_seen(&unmeasured), NOON) < quiet_for_ms(last_seen(&measured_zero), NOON));
assert_eq!(quiet_for(last_seen(&measured_zero), NOON).days(), None);
assert_eq!(quiet_for(last_seen(&unmeasured), NOON).days(), None);
}
#[test]
fn a_clock_that_runs_ahead_does_not_make_a_busy_tenant_silent() {
assert_eq!(quiet_for(Some(NOON + DAY_MS), NOON), Quiet::For(0));
assert!(!quiet_for(Some(NOON + DAY_MS), NOON).at_least_days(1));
}
#[test]
fn last_seen_is_the_later_of_the_two_and_a_fetch_counts() {
let only_fetch = reading(NOON, &[("last_push_unix_ms", 0), ("last_fetch_unix_ms", NOON)]);
assert_eq!(last_seen(&only_fetch), Some(NOON));
let older_fetch = reading(
NOON,
&[("last_push_unix_ms", NOON), ("last_fetch_unix_ms", NOON - DAY_MS)],
);
assert_eq!(last_seen(&older_fetch), Some(NOON), "the later of the two");
let push_only = reading(NOON, &[("last_push_unix_ms", NOON - DAY_MS)]);
assert_eq!(last_seen(&push_only), Some(NOON - DAY_MS), "one reported meter is a measurement");
let neither = reading(NOON, &[("pack_bytes", 1)]);
assert_eq!(
last_seen(&neither),
None,
"a product that reports neither has not been MEASURED — it has not `never been seen`"
);
}
}