Skip to main content

max / makenotwork

4.8 KB · 139 lines History Blame Raw
1 //! `pom status`: one-shot status summary across all configured targets.
2
3 use pom::checks::drift;
4 use pom::config::Config;
5 use pom::db;
6 use pom::display;
7 use pom::error::Result;
8 use pom::types::{LatencyStats, TestStaleness};
9
10 pub(crate) async fn cmd_status(pool: &sqlx::SqlitePool, config: &Config, json: bool) -> Result<()> {
11 let mut target_statuses = Vec::new();
12
13 for name in config.target_names() {
14 let target = config.get_target(&name).unwrap();
15 let health = db::get_latest_health(pool, &name).await?;
16 let tls_check = db::get_latest_tls_check(pool, &name).await?;
17 let route_checks = db::get_latest_route_checks(pool, &name).await?;
18 let dns_checks = db::get_latest_dns_checks(pool, &name).await?;
19 let whois_check = db::get_latest_whois_check(pool, &name).await?;
20 let test = db::get_latest_test_run(pool, &name).await?;
21 let incident = db::get_open_incident(pool, &name).await?;
22
23 // Compute 24h latency stats
24 let latency_24h = {
25 let cutoff = (chrono::Utc::now() - chrono::Duration::hours(24)).to_rfc3339();
26 let times = db::get_response_times(pool, &name, &cutoff)
27 .await
28 .unwrap_or_default();
29 let operational_times: Vec<i64> = times
30 .iter()
31 .filter(|(_, ms)| *ms > 0)
32 .map(|(_, ms)| *ms)
33 .collect();
34 LatencyStats::from_times(&operational_times)
35 };
36
37 // Compute test staleness
38 let staleness: Option<TestStaleness> = if let Some(tests_config) = &target.tests {
39 let current_version = health
40 .as_ref()
41 .and_then(|h| h.details.as_ref())
42 .and_then(|d| d.version.clone());
43
44 let tested_version = if let Some(ref t) = test {
45 db::get_version_at_time(pool, &name, &t.started_at)
46 .await
47 .unwrap_or(None)
48 } else {
49 None
50 };
51
52 Some(drift::compute_test_staleness(
53 current_version.as_deref(),
54 tested_version.as_deref(),
55 test.as_ref().map(|t| t.started_at.as_str()),
56 tests_config.staleness_days,
57 ))
58 } else {
59 None
60 };
61
62 // Compute test duration trend
63 let test_durations = if target.tests.is_some() {
64 db::get_test_durations(pool, &name, 13)
65 .await
66 .unwrap_or_default()
67 } else {
68 vec![]
69 };
70 let duration_drift = if test_durations.is_empty() {
71 None
72 } else {
73 drift::detect_test_duration_drift(&test_durations, 10, 3, 1.5)
74 };
75
76 if json {
77 target_statuses.push(serde_json::json!({
78 "target": name,
79 "label": target.label,
80 "health": health,
81 "tls": tls_check,
82 "latency_24h": latency_24h,
83 "dns": dns_checks,
84 "whois": whois_check,
85 "last_test": test.map(|t| serde_json::json!({
86 "passed": t.passed,
87 "exit_code": t.exit_code,
88 "duration_secs": t.duration_secs,
89 "started_at": t.started_at,
90 "summary": t.summary,
91 })),
92 "test_staleness": staleness,
93 "test_duration_drift": duration_drift,
94 "incident": incident,
95 }));
96 } else {
97 let route_slice = if route_checks.is_empty() {
98 None
99 } else {
100 Some(route_checks.as_slice())
101 };
102 let dns_slice = if dns_checks.is_empty() {
103 None
104 } else {
105 Some(dns_checks.as_slice())
106 };
107 print!(
108 "{}",
109 display::format_status_target(
110 &name,
111 &target.label,
112 health.as_ref(),
113 latency_24h.as_ref(),
114 tls_check.as_ref(),
115 route_slice,
116 dns_slice,
117 whois_check.as_ref(),
118 test.as_ref(),
119 staleness.as_ref(),
120 incident.as_ref(),
121 )
122 );
123 if !test_durations.is_empty() {
124 let recent_5: Vec<(String, i64)> = test_durations.iter().take(5).cloned().collect();
125 print!(
126 "{}",
127 display::format_test_duration_trend(&recent_5, duration_drift.as_deref())
128 );
129 }
130 }
131 }
132
133 if json {
134 println!("{}", serde_json::to_string_pretty(&target_statuses)?);
135 }
136
137 Ok(())
138 }
139