Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,13 @@ pub async fn metrics(
// gone by the time the rows are built. Additive, so a theme unaware of it
// continues to work.
let (ping, loss) = if want_ping { app.db.ping_records(id, span)? } else { (vec![], json!({})) };
anyhow::Ok(json!({"metrics": metrics, "ping": ping, "probes": probes, "loss": loss}))
// `step` is the seconds each point spans, against which a row's `minutes`
// is read; inferred from the stamps instead, it would be missing for a
// window holding a single point. The last point is the bucket still in
// progress and holds only the minutes elapsed.
anyhow::Ok(json!({
"metrics": metrics, "ping": ping, "probes": probes, "loss": loss, "step": span.step,
}))
})
.await;
match built.map_err(|e| anyhow::anyhow!(e)).and_then(|r| r) {
Expand All @@ -418,7 +424,8 @@ pub async fn metrics(
}
}

/// The window a chart request is answered with, `hours` wide.
/// The window a chart request is answered with: `hours` back from `now`,
/// widened to the point boundary at or before that.
///
/// The width is capped by the retention window alone, the same for every
/// caller, because no width costs more than the week: up to `DETAIL_DAYS` a
Expand Down Expand Up @@ -450,7 +457,10 @@ fn span(hours: i64, points: Option<i64>, now: i64) -> db::Span {
// evenly would keep the finer step and exceed it. `i64::div_ceil` is still
// unstable, and both operands are positive here.
let step = unit * ((hours * 3_600 / unit + budget - 1) / budget).max(1);
db::Span { since: now - hours * 3_600, step, hourly }
// Rows are bucketed by `ts / step` from the epoch, and a window opening
// partway through a bucket would leave its first point short of the
// `step / 60` minutes a whole one holds.
db::Span { since: (now - hours * 3_600).div_euclid(step) * step, step, hourly }
}

/// Guards a per-node read: the panel sees everything, while the public page sees
Expand Down Expand Up @@ -2448,11 +2458,15 @@ mod tests {
);
// Thinned, but neither empty nor reaching outside the window.
assert!(!metrics.is_empty() && !ping.is_empty(), "{hours}h returned nothing");
// A bucket the window opens partway through begins before it.
assert!(
metrics.iter().all(|m| m["ts"].as_i64().unwrap() >= span.since - span.step),
metrics.iter().all(|m| m["ts"].as_i64().unwrap() >= span.since),
"{hours}h reached back too far"
);
// Begun on a point boundary, so the first point is a whole one where
// the history reaches past it: this node reported every minute.
if hours < 720 {
assert_eq!(metrics[0]["minutes"], span.step / 60, "{hours}h opened partway through a point");
}
}

// A day returns every minute it holds: thinning exists only for what the
Expand Down Expand Up @@ -3427,6 +3441,7 @@ mod tests {
};
let rows =
|body: &[u8]| serde_json::from_slice::<Value>(body).unwrap()["metrics"].as_array().unwrap().len();
let step = |body: &[u8]| serde_json::from_slice::<Value>(body).unwrap()["step"].clone();

// Before the first rollup nothing is folded, and a window past the week
// reads the newest week of minute rows rather than every one.
Expand All @@ -3437,11 +3452,14 @@ mod tests {
app.db.set("retention_days", "7").unwrap();
let week = ask(168).await;
assert_eq!(rows(&week), 8, "a week reaches back seven days");
assert_eq!(step(&week), 420, "in seven-minute points, a week of minutes at the 1,440-point budget");
assert_eq!(ask(2_160).await, week, "a window past the retention window is narrowed to it");

// Past the week, from the hourly tier and the minute rows after it.
app.db.set("retention_days", "30").unwrap();
assert_eq!(rows(&ask(2_160).await), 30, "a month reaches back thirty days");
let month = ask(2_160).await;
assert_eq!(rows(&month), 30, "a month reaches back thirty days");
assert_eq!(step(&month), 3_600, "in whole hours, as no hourly row may straddle two points");
}

#[tokio::test]
Expand Down
Loading