#[cfg(target_os = "linux")]
mod collector;
mod tui;
use std::io::{self, Write};
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
use anyhow::{Context, Result, bail};
use clap::Parser;
use rigtop_core::agent::run_agent;
use rigtop_core::hosts::host_specs;
use rigtop_core::{Config, DataSource, LocalSource, MIN_POLL_INTERVAL};
#[derive(Debug, Parser)]
#[command(
version,
about = "btop-inspired system monitor with storage health and multi-host support"
)]
struct Cli {
#[arg(long)]
dump_json: bool,
#[arg(long, default_value_t = 1000, value_name = "MS")]
interval_ms: u64,
#[arg(long)]
pretty: bool,
#[arg(long, value_name = "PATH")]
config: Option<PathBuf>,
#[arg(long = "host", value_name = "DESTINATION", conflicts_with_all = ["agent", "dump_json"])]
hosts: Vec<String>,
#[arg(long, value_name = "COMMAND")]
remote_command: Option<String>,
#[arg(long, conflicts_with = "dump_json")]
agent: bool,
#[arg(long, conflicts_with_all = ["agent", "dump_json", "hosts"])]
demo: bool,
#[arg(long, conflicts_with_all = ["agent", "dump_json", "hosts", "uninstall_collector"])]
install_collector: bool,
#[arg(long, conflicts_with_all = ["agent", "dump_json", "hosts"])]
uninstall_collector: bool,
}
fn main() -> Result<()> {
let cli = Cli::parse();
let interval = Duration::from_millis(cli.interval_ms);
if interval < MIN_POLL_INTERVAL {
bail!(
"--interval-ms must be at least {} ms",
MIN_POLL_INTERVAL.as_millis()
);
}
if cli.install_collector || cli.uninstall_collector {
#[cfg(target_os = "linux")]
return if cli.install_collector {
collector::install()
} else {
collector::uninstall()
};
#[cfg(not(target_os = "linux"))]
bail!("the collector is for Linux (SMART data comes from smartctl run by systemd)");
}
let config = match &cli.config {
Some(path) => Config::load(path),
None => Config::load_default(),
}
.context("failed to load the configuration")?;
if cli.dump_json {
return dump_json(&config, interval, cli.pretty);
}
if cli.agent {
let source = LocalSource::with_config(&config);
let stdin = io::BufReader::new(io::stdin());
return run_agent(source, interval, stdin, io::stdout().lock()).context("agent failed");
}
let specs = if cli.demo {
rigtop_core::demo::host_specs(&config)
} else {
host_specs(&config, &cli.hosts, cli.remote_command.as_deref())
};
tui::run(config, interval, specs)
}
fn dump_json(config: &Config, interval: Duration, pretty: bool) -> Result<()> {
let mut source = LocalSource::with_config(config);
if let Some(reason) = source.gpu_unavailable_reason() {
eprintln!("note: NVIDIA GPUs unavailable: {reason}");
}
thread::sleep(interval);
let snapshot = source.poll().context("failed to collect a snapshot")?;
let mut out = io::stdout().lock();
if pretty {
serde_json::to_writer_pretty(&mut out, &snapshot)?;
} else {
serde_json::to_writer(&mut out, &snapshot)?;
}
writeln!(out)?;
Ok(())
}