mod capabilities;
mod claim;
mod cli;
mod client;
mod commands;
mod config;
mod daemon;
mod enforce;
mod help;
mod history;
mod job;
mod keys;
mod lifecycle;
mod logsel;
mod paths;
mod peers;
mod pipeline;
mod proto;
mod sched;
mod schema;
mod spec;
mod style;
mod supervisor;
mod sys;
#[cfg(test)]
mod testutil;
mod top;
mod units;
mod usage;
mod watchers;
use anyhow::{Context, Result};
use clap::{CommandFactory, Parser};
use cli::{Cli, Command};
const EXIT_USAGE: i32 = 2;
fn main() {
unsafe {
libc::signal(libc::SIGPIPE, libc::SIG_DFL);
}
let code = match run() {
Ok(code) => code,
Err(e) => {
eprintln!("qex: {e:#}");
1
}
};
std::process::exit(code);
}
fn run() -> Result<i32> {
let cli = Cli::parse();
let Some(command) = cli.command else {
print_root_help();
return Ok(0);
};
match command {
Command::Help(args) => cmd_help(args.topic.as_deref()),
Command::Schema(args) => cmd_schema(args.which.as_deref()),
Command::Config(args) => cmd_config(args),
Command::Submit(args) => commands::submit(args),
Command::Run(args) => commands::run(args),
Command::Pipeline(args) => commands::pipeline(args),
Command::List(args) => commands::list(args),
Command::Status(args) => commands::status(args),
Command::Wait(args) => commands::wait(args),
Command::Logs(args) => commands::logs(args),
Command::Kill(args) => commands::kill(args),
Command::Cancel(args) => commands::cancel(args),
Command::Rerun(args) => commands::rerun(args),
Command::Clean(args) => commands::clean(args),
Command::Gc(args) => commands::gc(args),
Command::Du(args) => commands::du(args),
Command::Info(args) => commands::info(args),
Command::Version(args) => commands::version(args),
Command::Watchers(args) => watchers::report(args.json),
Command::Top(args) => top::run(args),
Command::Daemon(_) => {
daemon::run()?;
Ok(0)
}
Command::Supervise(args) => {
let id = args
.id
.parse::<uuid::Uuid>()
.context("the supervise command needs a job id")?;
supervisor::main(id)
}
}
}
fn print_root_help() {
print!("{}", help::banner());
println!();
let mut cmd = Cli::command();
cmd.print_help().ok();
println!();
println!("Topics for `qex help <topic>`: {}", help::TOPICS.join(", "));
}
fn cmd_help(topic: Option<&str>) -> Result<i32> {
let Some(name) = topic else {
print_root_help();
return Ok(0);
};
match help::topic(name) {
Some(text) => {
print!("{text}");
Ok(0)
}
None => {
eprintln!(
"qex: there is no help topic `{name}`.\n\nThe topics are: {}\n\nAgents: run `qex help agents`.",
help::TOPICS.join(", ")
);
Ok(EXIT_USAGE)
}
}
}
fn cmd_schema(which: Option<&str>) -> Result<i32> {
let Some(name) = which else {
eprintln!(
"qex: name a schema. The schemas are: {}\n\nExample: qex schema job",
schema::NAMES.join(", ")
);
return Ok(EXIT_USAGE);
};
match schema::schema(name) {
Some(text) => {
print!("{text}");
Ok(0)
}
None => {
eprintln!(
"qex: there is no schema `{name}`. The schemas are: {}",
schema::NAMES.join(", ")
);
Ok(EXIT_USAGE)
}
}
}
fn cmd_config(args: cli::ConfigArgs) -> Result<i32> {
use cli::ConfigAction;
let json_flag = args.json;
match args
.action
.unwrap_or(ConfigAction::Show { json: json_flag })
{
ConfigAction::Path => {
let path = paths::config_file()?;
let exists = path.exists();
println!("{}", path.display());
if !exists {
eprintln!("qex: this file does not exist. qex uses the default values.");
}
Ok(0)
}
ConfigAction::Show { json } => {
let cfg = config::Config::load()?;
cfg.validate()?;
if json || json_flag {
println!("{}", serde_json::to_string_pretty(&cfg)?);
} else {
print_config_summary(&cfg)?;
}
Ok(0)
}
}
}
fn print_config_summary(cfg: &config::Config) -> Result<()> {
let path = paths::config_file()?;
println!(
"config file: {} ({})",
path.display(),
if path.exists() {
"read"
} else {
"absent; qex uses the default values"
}
);
println!();
println!(
"machine: {} cores, {}",
sys::cpu_count(),
units::format_size(sys::total_memory())
);
println!(
"budget: {} cores, {}",
cfg.budget_cpu()?,
units::format_size(cfg.budget_mem()?)
);
println!(
"default job: {} core(s), {}, {}",
cfg.default_cpu(),
units::format_size(cfg.default_mem()?),
match cfg.default_timeout()? {
Some(d) => format!("timeout {}", units::format_duration(d)),
None => "no timeout".to_string(),
}
);
println!(
"keep free: {} of memory",
units::format_size(cfg.reserve_mem()?)
);
match enforce::startup_warning(cfg) {
Some(warning) => {
println!("enforcement: {:?} — NOT ACTIVE", cfg.enforce.mode);
println!(" {warning}");
}
None if cfg.enforce.mode.is_on() => {
println!("enforcement: {:?}, active", cfg.enforce.mode)
}
None => println!("enforcement: off; the claims control the queue only"),
}
println!("peers: {}", peers::describe(cfg));
println!("oversized: {:?}", cfg.queue.oversized);
println!("environment: capture {:?}", cfg.submit.env_capture);
Ok(())
}