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"
|
rand = "0.8"
|
||||||
gethostname = "0.4"
|
gethostname = "0.4"
|
||||||
lettre = { version = "0.11", default-features = false, features = ["smtp-transport", "builder"] }
|
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::Serialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
use std::time::Duration;
|
use std::time::{Duration, Instant};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
@ -129,6 +129,7 @@ impl ServiceCollector {
|
|||||||
is_sandbox_excluded,
|
is_sandbox_excluded,
|
||||||
description,
|
description,
|
||||||
sub_service: None,
|
sub_service: None,
|
||||||
|
latency_ms: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -831,6 +832,40 @@ impl ServiceCollector {
|
|||||||
std::env::var("UID").unwrap_or_default() == "0"
|
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>> {
|
async fn get_nginx_sites(&self) -> Option<Vec<String>> {
|
||||||
|
|
||||||
// Get the actual nginx config file path from systemd (NixOS uses custom config)
|
// 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
|
// Add nginx sites as individual sub-services
|
||||||
if let Some(sites) = self.get_nginx_sites().await {
|
if let Some(sites) = self.get_nginx_sites().await {
|
||||||
for site in sites.iter() {
|
for site in sites.iter() {
|
||||||
|
// Measure latency for this site
|
||||||
|
let latency = self.measure_site_latency(site).await;
|
||||||
|
|
||||||
services.push(ServiceData {
|
services.push(ServiceData {
|
||||||
name: site.clone(),
|
name: site.clone(),
|
||||||
status: ServiceStatus::Running, // Assume sites are running if nginx is running
|
status: ServiceStatus::Running, // Assume sites are running if nginx is running
|
||||||
@ -1333,6 +1371,7 @@ impl Collector for ServiceCollector {
|
|||||||
is_sandbox_excluded: false,
|
is_sandbox_excluded: false,
|
||||||
description: None,
|
description: None,
|
||||||
sub_service: Some("nginx".to_string()),
|
sub_service: Some("nginx".to_string()),
|
||||||
|
latency_ms: latency,
|
||||||
});
|
});
|
||||||
healthy += 1;
|
healthy += 1;
|
||||||
}
|
}
|
||||||
@ -1361,6 +1400,7 @@ impl Collector for ServiceCollector {
|
|||||||
is_sandbox_excluded: false,
|
is_sandbox_excluded: false,
|
||||||
description: None,
|
description: None,
|
||||||
sub_service: Some("docker".to_string()),
|
sub_service: Some("docker".to_string()),
|
||||||
|
latency_ms: None,
|
||||||
});
|
});
|
||||||
healthy += 1;
|
healthy += 1;
|
||||||
}
|
}
|
||||||
@ -1385,6 +1425,7 @@ impl Collector for ServiceCollector {
|
|||||||
is_sandbox_excluded: false,
|
is_sandbox_excluded: false,
|
||||||
description: None,
|
description: None,
|
||||||
sub_service: None,
|
sub_service: None,
|
||||||
|
latency_ms: None,
|
||||||
});
|
});
|
||||||
tracing::warn!("Failed to collect metrics for service {}: {}", service, e);
|
tracing::warn!("Failed to collect metrics for service {}: {}", service, e);
|
||||||
}
|
}
|
||||||
@ -1449,6 +1490,8 @@ struct ServiceData {
|
|||||||
description: Option<Vec<String>>,
|
description: Option<Vec<String>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
sub_service: Option<String>,
|
sub_service: Option<String>,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
latency_ms: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
|||||||
@ -128,6 +128,8 @@ pub struct ServiceInfo {
|
|||||||
pub description: Option<Vec<String>>,
|
pub description: Option<Vec<String>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub sub_service: Option<String>,
|
pub sub_service: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub latency_ms: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|||||||
@ -105,12 +105,23 @@ fn render_metrics(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if svc.sub_service.is_some() {
|
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(
|
data.add_row_with_sub_service(
|
||||||
Some(WidgetStatus::new(status_level)),
|
Some(WidgetStatus::new(status_level)),
|
||||||
description,
|
description,
|
||||||
vec![
|
vec![
|
||||||
svc.name.clone(),
|
service_name_with_latency,
|
||||||
"".to_string(),
|
"".to_string(),
|
||||||
"".to_string(),
|
"".to_string(),
|
||||||
"".to_string(),
|
"".to_string(),
|
||||||
@ -139,7 +150,7 @@ fn render_metrics(
|
|||||||
|
|
||||||
fn format_bytes(mb: f32) -> String {
|
fn format_bytes(mb: f32) -> String {
|
||||||
if mb < 0.1 {
|
if mb < 0.1 {
|
||||||
"0".to_string()
|
"<1MB".to_string()
|
||||||
} else if mb < 1.0 {
|
} else if mb < 1.0 {
|
||||||
format!("{:.0}kB", mb * 1000.0)
|
format!("{:.0}kB", mb * 1000.0)
|
||||||
} else if mb < 1000.0 {
|
} else if mb < 1000.0 {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user