use std::fmt::Display;
use std::time::Duration;
use sprawling::audience::Audience;
use sprawling::monitor::Sample;
use sprawling::monitor::spread::{Share, Spread, micros, nanos};
use sprawling::monitor::top::Unit;
use sprawling::monitor::tree::TreeReading;
use super::{Host, Run};
const UNMEASURED: &str = "-";
pub(crate) fn city_line(sample: &Sample) -> serde_json::Result<String> {
#[derive(serde::Serialize)]
struct CityLine<'sample> {
line: &'static str,
#[serde(flatten)]
sample: &'sample Sample,
}
serde_json::to_string(&CityLine {
line: "city",
sample,
})
}
pub(super) fn tree_line(at: Duration, reading: &TreeReading, audience: Audience) -> String {
match audience {
Audience::Agent => object(
"tree",
&[
("at_us", micros(at).to_string()),
("processes", reading.processes.to_string()),
("cpu_permille", or_null(reading.cpu_permille)),
("private_bytes", reading.private_bytes.to_string()),
("working_set_bytes", reading.working_set_bytes.to_string()),
("read_bytes", reading.read_bytes.to_string()),
("written_bytes", reading.written_bytes.to_string()),
],
),
Audience::Person => format!(
"{:>10} {} process(es) cpu {} private {} working set {} read {} written {}",
Unit::Nanos.reading(nanos(at)),
reading.processes,
in_unit(Unit::Permille, reading.cpu_permille),
Unit::Bytes.reading(reading.private_bytes),
Unit::Bytes.reading(reading.working_set_bytes),
Unit::Bytes.reading(reading.read_bytes),
Unit::Bytes.reading(reading.written_bytes),
),
}
}
pub(super) fn run_line(run: &Run, audience: Audience) -> String {
let seen = run.watched.seen;
let seen_of = |figure: fn(&sprawling::monitor::tree::Seen) -> u64| seen.as_ref().map(figure);
match audience {
Audience::Agent => object(
"run",
&[
("index", run.index.to_string()),
("exit", or_null(run.exit)),
("wall_us", micros(run.wall).to_string()),
("beats", run.watched.beats.to_string()),
(
"seen_cpu_us",
or_null(seen_of(|seen| seen.cpu_ms.saturating_mul(1_000))),
),
("seen_read_bytes", or_null(seen_of(|seen| seen.read_bytes))),
(
"seen_written_bytes",
or_null(seen_of(|seen| seen.written_bytes)),
),
(
"seen_peak_private_bytes",
or_null(seen_of(|seen| seen.peak_private_bytes)),
),
(
"seen_peak_working_set_bytes",
or_null(seen_of(|seen| seen.peak_working_set_bytes)),
),
("child_peak_private_bytes", or_null(run.child.private_bytes)),
(
"child_peak_working_set_bytes",
or_null(run.child.working_set_bytes),
),
("read_cost_us", micros(run.watched.read_cost).to_string()),
],
),
Audience::Person => format!(
"run {} exit {} {} {} beat(s) cpu {} peak private {} peak working set {} child peak private {} child peak working set {} read cost {}",
run.index,
run.exit
.map_or_else(|| "by signal".to_owned(), |code| code.to_string()),
Unit::Nanos.reading(nanos(run.wall)),
run.watched.beats,
in_unit(
Unit::Nanos,
seen_of(|seen| seen.cpu_ms.saturating_mul(1_000_000))
),
in_unit(Unit::Bytes, seen_of(|seen| seen.peak_private_bytes)),
in_unit(Unit::Bytes, seen_of(|seen| seen.peak_working_set_bytes)),
in_unit(Unit::Bytes, run.child.private_bytes),
in_unit(Unit::Bytes, run.child.working_set_bytes),
Unit::Nanos.reading(nanos(run.watched.read_cost)),
),
}
}
pub(super) fn spread_line(spread: &Spread, failed: u32, host: Host, audience: Audience) -> String {
match audience {
Audience::Agent => object(
"spread",
&[
("samples", spread.samples().to_string()),
("failed", failed.to_string()),
("floor_us", micros(spread.floor()).to_string()),
("p50_us", micros(spread.p(Share::P50)).to_string()),
("p95_us", micros(spread.p(Share::P95)).to_string()),
("p99_us", micros(spread.p(Share::P99)).to_string()),
("peak_us", micros(spread.peak()).to_string()),
("suspicious", spread.suspicious().to_string()),
("cores", host.cores.to_string()),
("physical_bytes", host.physical_bytes.to_string()),
],
),
Audience::Person => format!(
"{} run(s), {failed} failed floor {} p50 {} p95 {} p99 {} peak {} {} suspicious on {} cores, {} memory",
spread.samples(),
Unit::Nanos.reading(nanos(spread.floor())),
Unit::Nanos.reading(nanos(spread.p(Share::P50))),
Unit::Nanos.reading(nanos(spread.p(Share::P95))),
Unit::Nanos.reading(nanos(spread.p(Share::P99))),
Unit::Nanos.reading(nanos(spread.peak())),
spread.suspicious(),
host.cores,
Unit::Bytes.reading(host.physical_bytes),
),
}
}
fn object(line: &str, fields: &[(&str, String)]) -> String {
let body: String = fields
.iter()
.map(|(key, value)| format!(",\"{key}\":{value}"))
.collect();
format!("{{\"line\":\"{line}\"{body}}}")
}
fn or_null(value: Option<impl Display>) -> String {
value.map_or_else(|| "null".to_owned(), |value| value.to_string())
}
fn in_unit(unit: Unit, value: Option<u64>) -> String {
value.map_or_else(|| UNMEASURED.to_owned(), |value| unit.reading(value))
}