Add ARK game servers to systemd service monitoring

This commit is contained in:
Christoffer Martinsson 2025-10-19 19:23:51 +02:00
parent ca160c9627
commit f67779be9d

View File

@ -67,8 +67,11 @@ impl SystemdCollector {
Ok(services) => { Ok(services) => {
state.monitored_services = services; state.monitored_services = services;
state.last_discovery_time = Some(Instant::now()); state.last_discovery_time = Some(Instant::now());
debug!("Auto-discovered {} services to monitor: {:?}", debug!(
state.monitored_services.len(), state.monitored_services); "Auto-discovered {} services to monitor: {:?}",
state.monitored_services.len(),
state.monitored_services
);
} }
Err(e) => { Err(e) => {
debug!("Failed to discover services, using cached list: {}", e); debug!("Failed to discover services, using cached list: {}", e);
@ -96,7 +99,10 @@ impl SystemdCollector {
if needs_refresh { if needs_refresh {
// Only check nginx sites if nginx service is active // Only check nginx sites if nginx service is active
if state.monitored_services.iter().any(|s| s.contains("nginx")) { if state.monitored_services.iter().any(|s| s.contains("nginx")) {
debug!("Refreshing nginx site latency metrics (interval: {}s)", state.nginx_check_interval_seconds); debug!(
"Refreshing nginx site latency metrics (interval: {}s)",
state.nginx_check_interval_seconds
);
let fresh_metrics = self.get_nginx_sites(); let fresh_metrics = self.get_nginx_sites();
state.nginx_site_metrics = fresh_metrics; state.nginx_site_metrics = fresh_metrics;
state.last_nginx_check_time = Some(Instant::now()); state.last_nginx_check_time = Some(Instant::now());
@ -176,6 +182,8 @@ impl SystemdCollector {
"haasp", "haasp",
// Backup services // Backup services
"backup", "backup",
// Game servers
"ark",
]; ];
for line in output_str.lines() { for line in output_str.lines() {
@ -188,7 +196,10 @@ impl SystemdCollector {
let mut is_excluded = false; let mut is_excluded = false;
for excluded in &excluded_services { for excluded in &excluded_services {
if service_name.contains(excluded) { if service_name.contains(excluded) {
debug!("EXCLUDING service '{}' because it matches pattern '{}'", service_name, excluded); debug!(
"EXCLUDING service '{}' because it matches pattern '{}'",
service_name, excluded
);
is_excluded = true; is_excluded = true;
break; break;
} }
@ -202,7 +213,10 @@ impl SystemdCollector {
// Check if this service matches our interesting patterns // Check if this service matches our interesting patterns
for pattern in &interesting_services { for pattern in &interesting_services {
if service_name.contains(pattern) || pattern.contains(service_name) { if service_name.contains(pattern) || pattern.contains(service_name) {
debug!("INCLUDING service '{}' because it matches pattern '{}'", service_name, pattern); debug!(
"INCLUDING service '{}' because it matches pattern '{}'",
service_name, pattern
);
services.push(service_name.to_string()); services.push(service_name.to_string());
break; break;
} }
@ -214,7 +228,8 @@ impl SystemdCollector {
if !services.iter().any(|s| s.contains("ssh")) { if !services.iter().any(|s| s.contains("ssh")) {
for line in output_str.lines() { for line in output_str.lines() {
let fields: Vec<&str> = line.split_whitespace().collect(); let fields: Vec<&str> = line.split_whitespace().collect();
if fields.len() >= 4 && (fields[0] == "sshd.service" || fields[0] == "ssh.service") { if fields.len() >= 4 && (fields[0] == "sshd.service" || fields[0] == "ssh.service")
{
let service_name = fields[0].trim_end_matches(".service"); let service_name = fields[0].trim_end_matches(".service");
services.push(service_name.to_string()); services.push(service_name.to_string());
break; break;
@ -276,7 +291,6 @@ impl SystemdCollector {
None None
} }
/// Get service disk usage by examining service working directory /// Get service disk usage by examining service working directory
fn get_service_disk_usage(&self, service: &str) -> Option<f32> { fn get_service_disk_usage(&self, service: &str) -> Option<f32> {
// Try to get working directory from systemctl // Try to get working directory from systemctl
@ -303,10 +317,16 @@ impl SystemdCollector {
s if s.contains("docker") => vec!["/var/lib/docker", "/var/lib/docker/containers"], s if s.contains("docker") => vec!["/var/lib/docker", "/var/lib/docker/containers"],
// Web services and applications // Web services and applications
s if s.contains("gitea") => vec!["/var/lib/gitea", "/opt/gitea", "/home/git", "/data/gitea"], s if s.contains("gitea") => {
vec!["/var/lib/gitea", "/opt/gitea", "/home/git", "/data/gitea"]
}
s if s.contains("nginx") => vec!["/var/log/nginx", "/var/www", "/usr/share/nginx"], s if s.contains("nginx") => vec!["/var/log/nginx", "/var/www", "/usr/share/nginx"],
s if s.contains("apache") || s.contains("httpd") => vec!["/var/log/apache2", "/var/www", "/etc/apache2"], s if s.contains("apache") || s.contains("httpd") => {
s if s.contains("immich") => vec!["/var/lib/immich", "/opt/immich", "/usr/src/app/upload"], vec!["/var/log/apache2", "/var/www", "/etc/apache2"]
}
s if s.contains("immich") => {
vec!["/var/lib/immich", "/opt/immich", "/usr/src/app/upload"]
}
s if s.contains("nextcloud") => vec!["/var/www/nextcloud", "/var/nextcloud"], s if s.contains("nextcloud") => vec!["/var/www/nextcloud", "/var/nextcloud"],
s if s.contains("owncloud") => vec!["/var/www/owncloud", "/var/owncloud"], s if s.contains("owncloud") => vec!["/var/www/owncloud", "/var/owncloud"],
s if s.contains("plex") => vec!["/var/lib/plexmediaserver", "/opt/plex"], s if s.contains("plex") => vec!["/var/lib/plexmediaserver", "/opt/plex"],
@ -321,7 +341,9 @@ impl SystemdCollector {
s if s.contains("mysql") => vec!["/var/lib/mysql"], s if s.contains("mysql") => vec!["/var/lib/mysql"],
s if s.contains("mariadb") => vec!["/var/lib/mysql", "/var/lib/mariadb"], s if s.contains("mariadb") => vec!["/var/lib/mysql", "/var/lib/mariadb"],
s if s.contains("redis") => vec!["/var/lib/redis", "/var/redis"], s if s.contains("redis") => vec!["/var/lib/redis", "/var/redis"],
s if s.contains("mongodb") || s.contains("mongo") => vec!["/var/lib/mongodb", "/var/lib/mongo"], s if s.contains("mongodb") || s.contains("mongo") => {
vec!["/var/lib/mongodb", "/var/lib/mongo"]
}
// Message queues and communication // Message queues and communication
s if s.contains("mosquitto") => vec!["/var/lib/mosquitto", "/etc/mosquitto"], s if s.contains("mosquitto") => vec!["/var/lib/mosquitto", "/etc/mosquitto"],
@ -329,7 +351,9 @@ impl SystemdCollector {
s if s.contains("ssh") => vec!["/var/log/auth.log", "/etc/ssh"], s if s.contains("ssh") => vec!["/var/log/auth.log", "/etc/ssh"],
// Download and sync services // Download and sync services
s if s.contains("transmission") => vec!["/var/lib/transmission-daemon", "/var/transmission"], s if s.contains("transmission") => {
vec!["/var/lib/transmission-daemon", "/var/transmission"]
}
s if s.contains("syncthing") => vec!["/var/lib/syncthing", "/home/syncthing"], s if s.contains("syncthing") => vec!["/var/lib/syncthing", "/home/syncthing"],
// System services - check logs and config // System services - check logs and config
@ -365,14 +389,9 @@ impl SystemdCollector {
None None
} }
/// Get directory size in GB with permission-aware logging /// Get directory size in GB with permission-aware logging
fn get_directory_size(&self, dir: &str) -> Option<f32> { fn get_directory_size(&self, dir: &str) -> Option<f32> {
let output = Command::new("du") let output = Command::new("du").arg("-sb").arg(dir).output().ok()?;
.arg("-sb")
.arg(dir)
.output()
.ok()?;
if !output.status.success() { if !output.status.success() {
// Log permission errors for debugging but don't spam logs // Log permission errors for debugging but don't spam logs
@ -449,9 +468,13 @@ impl SystemdCollector {
// Try service-specific known directories // Try service-specific known directories
let service_dirs = match service { let service_dirs = match service {
s if s.contains("docker") => vec!["/var/lib/docker", "/var/lib/docker/containers"], s if s.contains("docker") => vec!["/var/lib/docker", "/var/lib/docker/containers"],
s if s.contains("gitea") => vec!["/var/lib/gitea", "/opt/gitea", "/home/git", "/data/gitea"], s if s.contains("gitea") => {
vec!["/var/lib/gitea", "/opt/gitea", "/home/git", "/data/gitea"]
}
s if s.contains("nginx") => vec!["/var/log/nginx", "/var/www", "/usr/share/nginx"], s if s.contains("nginx") => vec!["/var/log/nginx", "/var/www", "/usr/share/nginx"],
s if s.contains("immich") => vec!["/var/lib/immich", "/opt/immich", "/usr/src/app/upload"], s if s.contains("immich") => {
vec!["/var/lib/immich", "/opt/immich", "/usr/src/app/upload"]
}
s if s.contains("postgres") => vec!["/var/lib/postgresql", "/var/lib/postgres"], s if s.contains("postgres") => vec!["/var/lib/postgresql", "/var/lib/postgres"],
s if s.contains("mysql") => vec!["/var/lib/mysql"], s if s.contains("mysql") => vec!["/var/lib/mysql"],
s if s.contains("redis") => vec!["/var/lib/redis", "/var/redis"], s if s.contains("redis") => vec!["/var/lib/redis", "/var/redis"],
@ -626,9 +649,10 @@ impl SystemdCollector {
if let Ok(link) = std::fs::read_link(entry.path()) { if let Ok(link) = std::fs::read_link(entry.path()) {
if let Some(path_str) = link.to_str() { if let Some(path_str) = link.to_str() {
// Skip special files, focus on regular files // Skip special files, focus on regular files
if !path_str.starts_with("/dev/") && if !path_str.starts_with("/dev/")
!path_str.starts_with("/proc/") && && !path_str.starts_with("/proc/")
!path_str.starts_with("[") { && !path_str.starts_with("[")
{
if let Ok(metadata) = std::fs::metadata(&link) { if let Ok(metadata) = std::fs::metadata(&link) {
total_size += metadata.len(); total_size += metadata.len();
found_any = true; found_any = true;
@ -656,10 +680,10 @@ impl SystemdCollector {
// Database services typically have significant disk usage // Database services typically have significant disk usage
s if s.contains("mysql") || s.contains("postgres") || s.contains("redis") => { s if s.contains("mysql") || s.contains("postgres") || s.contains("redis") => {
(memory_mb / 100.0).max(0.1) // Estimate based on memory (memory_mb / 100.0).max(0.1) // Estimate based on memory
}, }
// Web services and applications // Web services and applications
s if s.contains("nginx") || s.contains("apache") => 0.05, // ~50MB for configs/logs s if s.contains("nginx") || s.contains("apache") => 0.05, // ~50MB for configs/logs
s if s.contains("gitea") => (memory_mb / 50.0).max(0.5), // Code repositories s if s.contains("gitea") => (memory_mb / 50.0).max(0.5), // Code repositories
s if s.contains("docker") => 1.0, // Docker has significant overhead s if s.contains("docker") => 1.0, // Docker has significant overhead
// System services // System services
s if s.contains("ssh") || s.contains("postfix") => 0.01, // ~10MB for configs/logs s if s.contains("ssh") || s.contains("postfix") => 0.01, // ~10MB for configs/logs
@ -669,8 +693,6 @@ impl SystemdCollector {
Some(estimated_gb) Some(estimated_gb)
} }
} }
#[async_trait] #[async_trait]
@ -750,8 +772,11 @@ impl Collector for SystemdCollector {
} }
let collection_time = start_time.elapsed(); let collection_time = start_time.elapsed();
debug!("Systemd collection completed in {:?} with {} individual service metrics", debug!(
collection_time, metrics.len()); "Systemd collection completed in {:?} with {} individual service metrics",
collection_time,
metrics.len()
);
Ok(metrics) Ok(metrics)
} }
@ -863,8 +888,8 @@ impl SystemdCollector {
/// Check site latency using HTTP GET requests /// Check site latency using HTTP GET requests
fn check_site_latency(&self, url: &str) -> Result<f32, Box<dyn std::error::Error>> { fn check_site_latency(&self, url: &str) -> Result<f32, Box<dyn std::error::Error>> {
use std::time::Instant;
use std::time::Duration; use std::time::Duration;
use std::time::Instant;
let start = Instant::now(); let start = Instant::now();
@ -883,7 +908,12 @@ impl SystemdCollector {
if response.status().is_success() || response.status().is_redirection() { if response.status().is_success() || response.status().is_redirection() {
Ok(latency) Ok(latency)
} else { } else {
Err(format!("HTTP request failed for {} with status: {}", url, response.status()).into()) Err(format!(
"HTTP request failed for {} with status: {}",
url,
response.status()
)
.into())
} }
} }
@ -1045,7 +1075,11 @@ impl SystemdCollector {
if let Some(names_part) = trimmed.strip_prefix("server_name") { if let Some(names_part) = trimmed.strip_prefix("server_name") {
let names_clean = names_part.trim().trim_end_matches(';'); let names_clean = names_part.trim().trim_end_matches(';');
for name in names_clean.split_whitespace() { for name in names_clean.split_whitespace() {
if name != "_" && !name.is_empty() && name.contains('.') && !name.starts_with('$') { if name != "_"
&& !name.is_empty()
&& name.contains('.')
&& !name.starts_with('$')
{
server_names.push(name.to_string()); server_names.push(name.to_string());
debug!("Found server_name in block: {}", name); debug!("Found server_name in block: {}", name);
} }