#![cfg_attr(windows, warn(dead_code))]
use std::path::Path;
use super::{ServiceError, ServiceStatus};
pub const SERVICE_DISPLAY_NAME: &str = "all-smi GPU/NPU Metrics Exporter";
pub const SERVICE_DESCRIPTION: &str = "Exports GPU, NPU, CPU, memory, and chassis metrics in \
Prometheus format on the configured port. Part of all-smi \
(https://github.com/lablup/all-smi).";
pub const LAUNCH_ARGUMENTS: &[&str] = &["service", "run"];
pub const RESTART_DELAY_SECS: u64 = 5;
pub const FAILURE_RESET_PERIOD_SECS: u64 = 86_400;
pub const TRANSITION_WAIT_HINT_SECS: u64 = 10;
pub const STOP_TIMEOUT_SECS: u64 = 30;
pub const LOG_DIR_NAME: &str = "logs";
pub const LOG_FILE_PREFIX: &str = "all-smi";
pub const LOG_FILE_SUFFIX: &str = "log";
pub const LOG_RETENTION_FILES: usize = 14;
pub mod state {
pub const STOPPED: u32 = 1;
pub const START_PENDING: u32 = 2;
pub const STOP_PENDING: u32 = 3;
pub const RUNNING: u32 = 4;
pub const CONTINUE_PENDING: u32 = 5;
pub const PAUSE_PENDING: u32 = 6;
pub const PAUSED: u32 = 7;
}
pub mod start_type {
pub const BOOT_START: u32 = 0;
pub const SYSTEM_START: u32 = 1;
pub const AUTO_START: u32 = 2;
pub const DEMAND_START: u32 = 3;
pub const DISABLED: u32 = 4;
}
pub mod error_code {
pub const ACCESS_DENIED: i32 = 5;
pub const FAILED_SERVICE_CONTROLLER_CONNECT: i32 = 1063;
pub const SERVICE_DOES_NOT_EXIST: i32 = 1060;
pub const SERVICE_ALREADY_RUNNING: i32 = 1056;
pub const SERVICE_NOT_ACTIVE: i32 = 1062;
pub const SERVICE_EXISTS: i32 = 1073;
pub const SERVICE_MARKED_FOR_DELETE: i32 = 1072;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct RawScmStatus {
pub current_state: u32,
pub process_id: Option<u32>,
pub start_type: Option<u32>,
}
pub fn describe_state(current_state: u32) -> String {
match current_state {
state::STOPPED => "stopped".to_string(),
state::START_PENDING => "start pending".to_string(),
state::STOP_PENDING => "stop pending".to_string(),
state::RUNNING => "running".to_string(),
state::CONTINUE_PENDING => "continue pending".to_string(),
state::PAUSE_PENDING => "pause pending".to_string(),
state::PAUSED => "paused".to_string(),
other => format!("unknown state ({other})"),
}
}
pub fn maps_to_enabled(start_type: Option<u32>) -> Option<bool> {
match start_type {
Some(start_type::BOOT_START | start_type::SYSTEM_START | start_type::AUTO_START) => {
Some(true)
}
Some(start_type::DEMAND_START | start_type::DISABLED) => Some(false),
_ => None,
}
}
pub fn map_status(raw: RawScmStatus) -> ServiceStatus {
let running = raw.current_state == state::RUNNING;
ServiceStatus {
installed: true,
enabled: maps_to_enabled(raw.start_type),
running,
pid: if running {
raw.process_id.filter(|p| *p != 0)
} else {
None
},
detail: describe_state(raw.current_state),
}
}
pub fn not_installed_status() -> ServiceStatus {
ServiceStatus {
installed: false,
enabled: None,
running: false,
pid: None,
detail: "not installed".to_string(),
}
}
pub fn elevation_message(verb: &str) -> String {
format!(
"service {verb} requires Administrator rights on Windows; re-run it from an elevated \
terminal (right-click Command Prompt, Windows Terminal, or PowerShell and choose \
\"Run as administrator\")"
)
}
pub fn user_scope_unsupported() -> ServiceError {
ServiceError::NotSupported(
"--user is not supported on Windows: the Service Control Manager has no per-user service \
scope, so there is nothing for all-smi to install into. Drop --user and run the install \
from an elevated terminal, or register a per-user startup task with Task Scheduler \
instead, for example: schtasks /create /tn all-smi /tr \"<path>\\all-smi.exe api\" /sc \
onlogon"
.to_string(),
)
}
pub fn map_os_error(verb: &str, code: Option<i32>, detail: &str) -> ServiceError {
match code {
Some(error_code::ACCESS_DENIED) => ServiceError::NeedsElevation(elevation_message(verb)),
Some(error_code::SERVICE_DOES_NOT_EXIST) => ServiceError::NotInstalled,
Some(error_code::SERVICE_MARKED_FOR_DELETE) => ServiceError::Conflict(format!(
"the all-smi service is queued for deletion and cannot be {verb}ed until every open \
handle to it closes. Close services.msc if it is open, then retry."
)),
Some(error_code::SERVICE_EXISTS) => ServiceError::Conflict(
"a Windows service named `all-smi` was registered by another process while this \
install was running. Re-run the install to reconfigure it, or pass --force."
.to_string(),
),
_ => ServiceError::CommandFailed {
cmd: format!("Service Control Manager: {verb}"),
stderr: detail.to_string(),
},
}
}
pub fn is_benign_lifecycle_error(code: Option<i32>) -> bool {
matches!(
code,
Some(error_code::SERVICE_ALREADY_RUNNING) | Some(error_code::SERVICE_NOT_ACTIVE)
)
}
pub fn console_entry_point_message(code: Option<i32>, detail: &str) -> String {
if code == Some(error_code::FAILED_SERVICE_CONTROLLER_CONNECT) {
"`all-smi service run` is the Service Control Manager entry point, not a way to start the \
exporter by hand. Windows starts it for you once the service is registered. Run \
`all-smi service install --now` from an elevated terminal to register and start the \
service, or run `all-smi api` to serve metrics in the foreground."
.to_string()
} else {
format!("failed to connect to the Windows service control dispatcher: {detail}")
}
}
pub fn executable_from_command_line(command_line: &str) -> Option<&str> {
let trimmed = command_line.trim_start();
if let Some(rest) = trimmed.strip_prefix('"') {
let end = rest.find('"')?;
let exe = &rest[..end];
if exe.is_empty() { None } else { Some(exe) }
} else {
let end = trimmed.find([' ', '\t']).unwrap_or(trimmed.len());
let exe = &trimmed[..end];
if exe.is_empty() { None } else { Some(exe) }
}
}
pub fn normalize_windows_path(path: &str) -> String {
let replaced = path.replace('/', "\\");
let trimmed = replaced.trim_end_matches('\\');
if trimmed.is_empty() {
replaced.to_ascii_lowercase()
} else {
trimmed.to_ascii_lowercase()
}
}
pub fn strip_verbatim_prefix(path: &str) -> &str {
let Some(rest) = path.strip_prefix(r"\\?\") else {
return path;
};
let bytes = rest.as_bytes();
let is_drive_path = bytes.len() >= 3
&& bytes[0].is_ascii_alphabetic()
&& bytes[1] == b':'
&& (bytes[2] == b'\\' || bytes[2] == b'/');
if is_drive_path { rest } else { path }
}
pub fn command_line_targets(command_line: &str, exe: &Path) -> bool {
let Some(existing) = executable_from_command_line(command_line) else {
return false;
};
let Some(exe) = exe.to_str() else {
return false;
};
normalize_windows_path(existing) == normalize_windows_path(exe)
}
pub fn binary_path_conflict(existing_command_line: &str, current_exe: &Path) -> ServiceError {
let existing = executable_from_command_line(existing_command_line)
.map(str::to_string)
.unwrap_or_else(|| existing_command_line.to_string());
ServiceError::Conflict(format!(
"a Windows service named `all-smi` already exists and runs {existing}, not {}. Refusing \
to repoint it. Pass --force to reconfigure it anyway, or remove it first with \
`all-smi service uninstall --force`.",
current_exe.display()
))
}
pub fn stop_timeout_error(verb: &str) -> ServiceError {
ServiceError::CommandFailed {
cmd: format!("Service Control Manager: {verb}"),
stderr: format!(
"the service did not reach SERVICE_STOPPED within {STOP_TIMEOUT_SECS}s; check the log \
under %PROGRAMDATA%\\all-smi\\{LOG_DIR_NAME} and retry"
),
}
}
#[cfg(test)]
#[path = "scm_tests.rs"]
mod tests;