Implement nginx site latency monitoring and improve disk usage display
Agent improvements: - Add reqwest dependency for HTTP latency testing - Implement measure_site_latency() function for nginx sites - Add latency_ms field to ServiceData structure - Measure response times for nginx sites using HEAD requests - Handle connection failures gracefully with 5-second timeout - Use HTTPS for external sites, HTTP for localhost Dashboard improvements: - Add latency_ms field to ServiceInfo structure - Display latency for nginx sites: "docker.cmtec.se 134ms" - Only show latency for nginx sub-services, not other services - Change disk usage "0" to "<1MB" for better readability The Services widget now shows: - Nginx sites with response times when measurable - Cleaner disk usage formatting for small values - Improved user experience with meaningful latency data
This commit is contained in:
parent
c6e8749ddd
commit
fd8aa0678e
@ -22,3 +22,4 @@ futures = "0.3"
|
||||
rand = "0.8"
|
||||
gethostname = "0.4"
|
||||
lettre = { version = "0.11", default-features = false, features = ["smtp-transport", "builder"] }
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
|
||||
@ -3,7 +3,7 @@ use chrono::Utc;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
use std::process::Stdio;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::fs;
|
||||
use tokio::process::Command;
|
||||
use tokio::time::timeout;
|
||||
@ -129,6 +129,7 @@ impl ServiceCollector {
|
||||
is_sandbox_excluded,
|
||||
description,
|
||||
sub_service: None,
|
||||
latency_ms: None,
|
||||
})
|
||||
}
|
||||
|
||||
@ -831,6 +832,40 @@ impl ServiceCollector {
|
||||
std::env::var("UID").unwrap_or_default() == "0"
|
||||
}
|
||||
|
||||
async fn measure_site_latency(&self, site_name: &str) -> Option<f32> {
|
||||
// Construct URL from site name
|
||||
let url = if site_name.contains("localhost") || site_name.contains("127.0.0.1") {
|
||||
format!("http://{}", site_name)
|
||||
} else {
|
||||
format!("https://{}", site_name)
|
||||
};
|
||||
|
||||
// Create HTTP client with short timeout
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(5))
|
||||
.build()
|
||||
.ok()?;
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
// Make HEAD request to avoid downloading content
|
||||
match client.head(&url).send().await {
|
||||
Ok(response) => {
|
||||
let latency = start.elapsed().as_millis() as f32;
|
||||
if response.status().is_success() || response.status().is_redirection() {
|
||||
Some(latency)
|
||||
} else {
|
||||
// Site is reachable but returned error, still measure latency
|
||||
Some(latency)
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// Connection failed, no latency measurement
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_nginx_sites(&self) -> Option<Vec<String>> {
|
||||
|
||||
// Get the actual nginx config file path from systemd (NixOS uses custom config)
|
||||
@ -1320,6 +1355,9 @@ impl Collector for ServiceCollector {
|
||||
// Add nginx sites as individual sub-services
|
||||
if let Some(sites) = self.get_nginx_sites().await {
|
||||
for site in sites.iter() {
|
||||
// Measure latency for this site
|
||||
let latency = self.measure_site_latency(site).await;
|
||||
|
||||
services.push(ServiceData {
|
||||
name: site.clone(),
|
||||
status: ServiceStatus::Running, // Assume sites are running if nginx is running
|
||||
@ -1333,6 +1371,7 @@ impl Collector for ServiceCollector {
|
||||
is_sandbox_excluded: false,
|
||||
description: None,
|
||||
sub_service: Some("nginx".to_string()),
|
||||
latency_ms: latency,
|
||||
});
|
||||
healthy += 1;
|
||||
}
|
||||
@ -1361,6 +1400,7 @@ impl Collector for ServiceCollector {
|
||||
is_sandbox_excluded: false,
|
||||
description: None,
|
||||
sub_service: Some("docker".to_string()),
|
||||
latency_ms: None,
|
||||
});
|
||||
healthy += 1;
|
||||
}
|
||||
@ -1385,6 +1425,7 @@ impl Collector for ServiceCollector {
|
||||
is_sandbox_excluded: false,
|
||||
description: None,
|
||||
sub_service: None,
|
||||
latency_ms: None,
|
||||
});
|
||||
tracing::warn!("Failed to collect metrics for service {}: {}", service, e);
|
||||
}
|
||||
@ -1449,6 +1490,8 @@ struct ServiceData {
|
||||
description: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
sub_service: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
latency_ms: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
|
||||
@ -128,6 +128,8 @@ pub struct ServiceInfo {
|
||||
pub description: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub sub_service: Option<String>,
|
||||
#[serde(default)]
|
||||
pub latency_ms: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@ -105,12 +105,23 @@ fn render_metrics(
|
||||
};
|
||||
|
||||
if svc.sub_service.is_some() {
|
||||
// Sub-services only show name and status, no memory/CPU/disk/sandbox data
|
||||
// Sub-services (nginx sites) only show name and status, no memory/CPU/disk data
|
||||
// Add latency information for nginx sites if available
|
||||
let service_name_with_latency = if let Some(parent) = &svc.sub_service {
|
||||
if parent == "nginx" && svc.latency_ms.is_some() {
|
||||
format!("{} {:.0}ms", svc.name, svc.latency_ms.unwrap())
|
||||
} else {
|
||||
svc.name.clone()
|
||||
}
|
||||
} else {
|
||||
svc.name.clone()
|
||||
};
|
||||
|
||||
data.add_row_with_sub_service(
|
||||
Some(WidgetStatus::new(status_level)),
|
||||
description,
|
||||
vec![
|
||||
svc.name.clone(),
|
||||
service_name_with_latency,
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
"".to_string(),
|
||||
@ -139,7 +150,7 @@ fn render_metrics(
|
||||
|
||||
fn format_bytes(mb: f32) -> String {
|
||||
if mb < 0.1 {
|
||||
"0".to_string()
|
||||
"<1MB".to_string()
|
||||
} else if mb < 1.0 {
|
||||
format!("{:.0}kB", mb * 1000.0)
|
||||
} else if mb < 1000.0 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user