Skip to content
Merged
Show file tree
Hide file tree
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
48 changes: 29 additions & 19 deletions src/agent_ws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,33 +150,35 @@ struct Mark {
const MEAN_FLOAT: [&str; 1] = ["cpu"];
const MEAN_INT: [&str; 6] = ["mem_used", "swap_used", "disk_used", "tcp", "udp", "procs"];

/// Rates a history row also carries at their highest over the minute, each as
/// the agent measured it across one report interval, under the column it is
/// stored in. The row's own rate is the minute's mean, which integrates to the
/// traffic totals and therefore stores a 15-second burst at 286 Mbps as 72 Mbps
/// (measured).
/// Figures a history row also carries at their highest over the minute, each
/// as the agent measured it across one report interval, under the column it is
/// stored in. The row's own figure is the minute's mean, which for a rate
/// integrates to the traffic totals and therefore stores a 15-second burst at
/// 286 Mbps as 72 Mbps (measured); a 15-second CPU spike to 100% is likewise a
/// minute at 25%.
///
/// Taken from the agent rather than derived here from the arrival of two
/// frames: the network bunches frames, and a second of bytes divided by the
/// half second between two arrivals would record twice the rate that ran.
const PEAK: [(&str, &str); 2] = [("net_rx", "net_rx_max"), ("net_tx", "net_tx_max")];
const PEAK_FLOAT: [(&str, &str); 1] = [("cpu", "cpu_max")];
const PEAK_INT: [(&str, &str); 2] = [("net_rx", "net_rx_max"), ("net_tx", "net_tx_max")];

/// Running sums for the minute in progress, one slot per averaged field, and
/// the highest of each [`PEAK`] rate.
/// the highest of each peak figure.
#[derive(Debug, Default)]
struct Minute {
sums: [f64; MEAN_FLOAT.len() + MEAN_INT.len()],
reports: f64,
peaks: [i64; PEAK.len()],
peaks: [f64; PEAK_FLOAT.len() + PEAK_INT.len()],
}

impl Minute {
fn add(&mut self, metrics: &serde_json::Value) {
for (slot, key) in MEAN_FLOAT.iter().chain(&MEAN_INT).enumerate() {
self.sums[slot] += metrics.get(key).and_then(|v| v.as_f64()).unwrap_or(0.0);
}
for (peak, (key, _)) in self.peaks.iter_mut().zip(PEAK) {
*peak = (*peak).max(metrics.get(key).and_then(|v| v.as_i64()).unwrap_or(0));
for (peak, (key, _)) in self.peaks.iter_mut().zip(PEAK_FLOAT.iter().chain(&PEAK_INT)) {
*peak = peak.max(metrics.get(key).and_then(|v| v.as_f64()).unwrap_or(0.0));
}
self.reports += 1.0;
}
Expand All @@ -199,8 +201,11 @@ impl Minute {
}
// Written whatever the report carried, so an agent sending these keys
// itself cannot choose the stored value.
for (peak, (_, column)) in self.peaks.iter().zip(PEAK) {
obj.insert(column.to_owned(), json!(peak));
for (slot, (peak, (_, column))) in
self.peaks.iter().zip(PEAK_FLOAT.iter().chain(&PEAK_INT)).enumerate()
{
let peak = if slot < PEAK_FLOAT.len() { json!(peak) } else { json!(*peak as i64) };
obj.insert((*column).to_owned(), peak);
}
}
}
Expand Down Expand Up @@ -838,7 +843,7 @@ mod tests {
use chrono::TimeZone;

use super::*;
use crate::db::{Node, PingTask};
use crate::db::{Node, PingTask, Span};

fn app() -> App {
App::for_test(Db::open(":memory:").unwrap())
Expand Down Expand Up @@ -912,7 +917,7 @@ mod tests {
fn results(app: &App, id: i64) -> Vec<(i64, i64)> {
let mut seen: Vec<(i64, i64)> = app
.db
.ping_records(id, 0, 60)
.ping_records(id, Span::minutes(0, 60))
.unwrap()
.0
.iter()
Expand Down Expand Up @@ -1047,15 +1052,15 @@ mod tests {
assert_eq!(app.agents.read().unwrap()[&id].metrics["net_rx_total"], 6_900, "the live view follows");
assert_eq!(total_rx(&app, id), 0, "within the minute nothing past the baseline is booked");
assert!(
app.db.metrics(id, 0, 60).unwrap().is_empty(),
app.db.metrics(id, Span::minutes(0, 60)).unwrap().is_empty(),
"a session writes no row for its first minute"
);

send(&app, id, &mut session, 60, &report_json("boot-a", 7_000, 0)).unwrap();
// The minute's last reading, 59 s in, is what the boundary books; the
// reading that crossed it waits for the next.
assert_eq!(total_rx(&app, id), 5_900);
let rows = app.db.metrics(id, 0, 60).unwrap();
let rows = app.db.metrics(id, Span::minutes(0, 60)).unwrap();
assert_eq!(rows.len(), 1, "a minute of reports is one row");
// History rows are keyed by (node, ts), so counting them proves nothing on
// its own: reports a second apart collapse onto one row with or without
Expand Down Expand Up @@ -1109,13 +1114,14 @@ mod tests {
send(&app, id, &mut session, 30, &burst(1_000 + 45_000_000, 3_000_000, 50.0, 151)).unwrap();
send(&app, id, &mut session, 60, &burst(1_000 + 60_000_000, 0, 0.0, 201)).unwrap();

let row = &app.db.metrics(id, 0, 60).unwrap()[0];
let row = &app.db.metrics(id, Span::minutes(0, 60)).unwrap()[0];
assert_eq!(
row["net_rx"], 1_008_403,
"60 MB over 59.5 s, not the agent's 0 nor over 59 whole seconds"
);
assert_eq!(row["net_rx_max"], 3_000_000, "the busiest second survives the mean");
assert_eq!(row["cpu"], 50.0, "the mean of the minute, not the idle second it ended on");
assert_eq!(row["cpu_max"], 100.0, "and its busiest second of CPU survives it too");
// Integers remain integral: the column is read with as_i64, which returns
// nothing for the 150.67 the raw mean would produce.
assert_eq!(row["mem_used"], 151);
Expand All @@ -1135,7 +1141,7 @@ mod tests {
let mut session = Session::default();
send(&app, id, &mut session, 0, &report_json("boot-a", 1_000, 500)).unwrap();
send(&app, id, &mut session, 60, &report_json("boot-a", 2_000, 500)).unwrap();
let before = app.db.metrics(id, 0, 60).unwrap();
let before = app.db.metrics(id, Span::minutes(0, 60)).unwrap();
assert_eq!(before.len(), 1, "the running session wrote the row for this minute");

// The socket drops and the agent returns within the same minute.
Expand All @@ -1146,7 +1152,11 @@ mod tests {
"net_tx_total": 4_500}})
.to_string();
send(&app, id, &mut Session::default(), 70, &loud).unwrap();
assert_eq!(app.db.metrics(id, 0, 60).unwrap(), before, "the row keeps the minute it described");
assert_eq!(
app.db.metrics(id, Span::minutes(0, 60)).unwrap(),
before,
"the row keeps the minute it described"
);
}

/// Booking about once a minute must leave exactly what booking every report
Expand Down
Loading
Loading