Add user service discovery to systemd collector

- Use systemctl --user commands to discover user-level services
- Include both user unit files and loaded user units
- Gracefully handle cases where user commands fail (no user session)
- Treat user services same as system services in filtering
- Enables monitoring of user-level Docker, development servers, etc.
This commit is contained in:
Christoffer Martinsson 2025-10-20 23:11:11 +02:00
parent 1cc31ec26a
commit 1e8da8c187

View File

@ -128,12 +128,45 @@ impl SystemdCollector {
.arg("--plain")
.output()?;
// Also get user unit files (user-level services)
let user_unit_files_output = Command::new("systemctl")
.arg("--user")
.arg("list-unit-files")
.arg("--type=service")
.arg("--no-pager")
.arg("--plain")
.output()?;
// And user loaded units
let user_units_output = Command::new("systemctl")
.arg("--user")
.arg("list-units")
.arg("--type=service")
.arg("--all")
.arg("--no-pager")
.arg("--plain")
.output()?;
if !unit_files_output.status.success() || !units_output.status.success() {
return Err(anyhow::anyhow!("systemctl command failed"));
return Err(anyhow::anyhow!("systemctl system command failed"));
}
// User commands might fail if no user session, so check individually
let user_unit_files_success = user_unit_files_output.status.success();
let user_units_success = user_units_output.status.success();
let unit_files_str = String::from_utf8(unit_files_output.stdout)?;
let units_str = String::from_utf8(units_output.stdout)?;
let user_unit_files_str = if user_unit_files_success {
String::from_utf8(user_unit_files_output.stdout).ok()
} else {
None
};
let user_units_str = if user_units_success {
String::from_utf8(user_units_output.stdout).ok()
} else {
None
};
let mut services = Vec::new();
// Skip setup/certificate services that don't need monitoring (from legacy)
@ -220,6 +253,28 @@ impl SystemdCollector {
}
}
// Parse user unit files if available
if let Some(user_unit_files_str) = &user_unit_files_str {
for line in user_unit_files_str.lines() {
let fields: Vec<&str> = line.split_whitespace().collect();
if fields.len() >= 2 && fields[0].ends_with(".service") {
let service_name = fields[0].trim_end_matches(".service");
all_service_names.insert(service_name.to_string());
}
}
}
// Parse user loaded units if available
if let Some(user_units_str) = &user_units_str {
for line in user_units_str.lines() {
let fields: Vec<&str> = line.split_whitespace().collect();
if fields.len() >= 4 && fields[0].ends_with(".service") {
let service_name = fields[0].trim_end_matches(".service");
all_service_names.insert(service_name.to_string());
}
}
}
// Now process all discovered services
for service_name in &all_service_names {
debug!("Processing service: '{}'", service_name);