mod app;
mod availability;
mod benchmarks;
mod collectors;
mod config;
mod ipmi;
mod metrics;
mod recommendations;
mod smart;
mod temperature;
mod thresholds;
mod ui;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use clap::Parser;
use app::App;
use config::Config;
fn main() -> std::io::Result<()> {
#[cfg(not(target_os = "linux"))]
{
eprintln!("╔══════════════════════════════════════════════════════════════╗");
eprintln!("║ WARNING: cargo-slow is designed for Linux systems only! ║");
eprintln!("║ ║");
eprintln!("║ Most metrics (CPU, memory, disk, temperatures, PSI, etc.) ║");
eprintln!("║ are read from /proc and /sys which don't exist on macOS. ║");
eprintln!("║ ║");
eprintln!("║ Only basic benchmarks will work. For full functionality, ║");
eprintln!("║ please run on a Linux system. ║");
eprintln!("╚══════════════════════════════════════════════════════════════╝");
eprintln!();
}
let config = parse_config();
let app = App::new(config.clone())?;
app.ensure_test_file()?;
let running = Arc::new(AtomicBool::new(true));
setup_signal_handler(running.clone());
let interval = Duration::from_secs(config.interval);
let use_headless = config.headless || !is_terminal();
if !config.headless && !is_terminal() {
eprintln!("Warning: stdout is not a TTY, running in headless mode");
}
if use_headless {
ui::run_headless(app, running, interval)?;
} else {
ui::run(app, running, interval)?;
}
Ok(())
}
fn parse_config() -> Config {
Config::parse_from(strip_cargo_subcommand(std::env::args_os()))
}
fn strip_cargo_subcommand<I>(args: I) -> Vec<std::ffi::OsString>
where
I: IntoIterator<Item = std::ffi::OsString>,
{
let mut args: Vec<std::ffi::OsString> = args.into_iter().collect();
if args.get(1).map(|arg| arg == "slow").unwrap_or(false) {
args.remove(1);
}
args
}
static SIGNAL_RECEIVED: AtomicBool = AtomicBool::new(false);
fn setup_signal_handler(running: Arc<AtomicBool>) {
let running_clone = running.clone();
std::thread::spawn(move || {
while running_clone.load(Ordering::Relaxed) {
if SIGNAL_RECEIVED.load(Ordering::Relaxed) {
running_clone.store(false, Ordering::Relaxed);
break;
}
std::thread::sleep(std::time::Duration::from_millis(50));
}
});
unsafe {
libc::signal(
libc::SIGINT,
signal_handler as *const () as libc::sighandler_t,
);
libc::signal(
libc::SIGTERM,
signal_handler as *const () as libc::sighandler_t,
);
}
}
extern "C" fn signal_handler(_: i32) {
SIGNAL_RECEIVED.store(true, Ordering::Relaxed);
}
fn is_terminal() -> bool {
unsafe { libc::isatty(libc::STDOUT_FILENO) != 0 }
}
#[cfg(test)]
mod tests {
use std::ffi::OsString;
use super::strip_cargo_subcommand;
fn osvec(args: &[&str]) -> Vec<OsString> {
args.iter().map(OsString::from).collect()
}
#[test]
fn strips_injected_slow_subcommand_token() {
assert_eq!(
strip_cargo_subcommand(osvec(&["cargo-slow", "slow", "--headless"])),
osvec(&["cargo-slow", "--headless"])
);
}
#[test]
fn leaves_direct_binary_invocation_untouched() {
assert_eq!(
strip_cargo_subcommand(osvec(&["cargo-slow", "--headless"])),
osvec(&["cargo-slow", "--headless"])
);
}
#[test]
fn only_strips_the_first_slow_token() {
assert_eq!(
strip_cargo_subcommand(osvec(&["cargo-slow", "slow", "-c", "slow"])),
osvec(&["cargo-slow", "-c", "slow"])
);
}
}