use serde::{Deserialize, Serialize};
use super::{ConsoleMetricsReport, ServiceHealth};
use crate::host_metrics::HostMetrics;
pub const MACHINE_STATUS_SCHEMA_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceSummary {
pub service_id: String,
pub display_name: String,
pub version: String,
pub status: ServiceHealth,
pub metrics_schema_version: u32,
pub collected_at_unix: Option<u64>,
}
impl From<&ConsoleMetricsReport> for ServiceSummary {
fn from(r: &ConsoleMetricsReport) -> Self {
Self {
service_id: r.service_id.clone(),
display_name: r.display_name.clone(),
version: r.version.clone(),
status: r.status.clone(),
metrics_schema_version: r.metrics_schema_version,
collected_at_unix: r.collected_at_unix,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceRollup {
pub total: usize,
pub ok: usize,
pub degraded: usize,
pub error: usize,
pub services: Vec<ServiceSummary>,
}
impl ServiceRollup {
#[must_use]
pub fn from_reports(reports: &[ConsoleMetricsReport]) -> Self {
let (mut ok, mut degraded, mut error) = (0usize, 0usize, 0usize);
let mut services = Vec::with_capacity(reports.len());
for r in reports {
match r.status {
ServiceHealth::Ok => ok += 1,
ServiceHealth::Degraded => degraded += 1,
ServiceHealth::Error => error += 1,
}
services.push(ServiceSummary::from(r));
}
Self {
total: reports.len(),
ok,
degraded,
error,
services,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MachineStatus {
pub host: HostMetrics,
pub services: ServiceRollup,
pub schema_version: u32,
pub assembled_at_unix: Option<u64>,
}
impl MachineStatus {
#[must_use]
pub fn assemble(host: HostMetrics, reports: &[ConsoleMetricsReport]) -> Self {
Self {
host,
services: ServiceRollup::from_reports(reports),
schema_version: MACHINE_STATUS_SCHEMA_VERSION,
assembled_at_unix: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.ok()
.map(|d| d.as_secs()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::console_metrics::make_report;
use serde_json::json;
fn sample_host() -> HostMetrics {
crate::host_metrics::HostSampler::new().sample()
}
#[test]
fn assemble_counts_by_health() {
let reports = vec![
make_report("a", "A", "1.0.0", ServiceHealth::Ok, json!({}), 1),
make_report("b", "B", "1.0.0", ServiceHealth::Degraded, json!({}), 1),
make_report("c", "C", "1.0.0", ServiceHealth::Error, json!({}), 1),
];
let status = MachineStatus::assemble(sample_host(), &reports);
assert_eq!(status.services.total, 3);
assert_eq!(status.services.ok, 1);
assert_eq!(status.services.degraded, 1);
assert_eq!(status.services.error, 1);
assert_eq!(status.schema_version, MACHINE_STATUS_SCHEMA_VERSION);
}
#[test]
fn rollup_drops_opaque_metrics() {
let reports = vec![make_report(
"trusty-search",
"Search",
"0.24.1",
ServiceHealth::Ok,
json!({ "index_count": 42, "big": [1, 2, 3, 4, 5] }),
3,
)];
let rollup = ServiceRollup::from_reports(&reports);
assert_eq!(rollup.services.len(), 1);
let summary = &rollup.services[0];
assert_eq!(summary.service_id, "trusty-search");
assert_eq!(summary.metrics_schema_version, 3);
let value = serde_json::to_value(summary).unwrap();
assert!(
value.get("metrics").is_none(),
"ServiceSummary must not carry the opaque metrics payload"
);
}
#[test]
fn machine_status_serde_round_trip() {
let reports = vec![make_report(
"trusty-memory",
"Memory",
"0.46.5",
ServiceHealth::Ok,
json!(null),
1,
)];
let status = MachineStatus::assemble(sample_host(), &reports);
let s = serde_json::to_string(&status).expect("serialise MachineStatus");
let back: MachineStatus = serde_json::from_str(&s).expect("deserialise MachineStatus");
assert_eq!(back.services.total, 1);
assert_eq!(back.services.ok, 1);
assert_eq!(back.schema_version, status.schema_version);
assert_eq!(back.host.cpu.logical_cores, status.host.cpu.logical_cores);
}
}