Skip to main content

max / makenotwork

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