use crate::{log::update_service, Check, Config, Success, GLOBAL_ERROR, STATUS}; use anyhow::{anyhow, bail, Context, Result}; use futures::{stream::FuturesUnordered, StreamExt}; use log::info; use std::{ sync::Arc, time::{Duration, Instant}, }; use tokio::{ process::Command, time::{sleep, timeout}, }; pub async fn check_loop(config: Arc, i: usize) { loop { check_service(&config, i).await; sleep(Duration::from_secs(config.interval)).await; } } async fn check_service(config: &Arc, i: usize) { let service = &config.services[i]; let mut futs = FuturesUnordered::from_iter(service.checks.iter().enumerate().map( |(j, check)| async move { let r = match timeout(Duration::from_secs(30), check.check()).await { Ok(Ok(succ)) => Ok(succ), Ok(Err(e)) => Err(e), Err(_) => Err(anyhow!("timed out")), }; let r2 = r .as_ref() .err() .map(|e| (format!("{e}"), format!("{e:?}"))) .to_owned(); info!("check {i}:{j} => {r:?}"); { let mut g = STATUS.write().await; g.insert((i, j), r); } let config = config.clone(); tokio::task::spawn(async move { if let Err(e) = update_service(config.clone(), i, j, r2).await { *GLOBAL_ERROR.write().await = Some(e); } }) }, )); while let Some(_) = futs.next().await {} } impl Check { pub async fn check(&self) -> Result { match self { Check::Systemd(sname) => { let output = Command::new("systemctl") .arg("show") .arg("--no-pager") .arg(sname) .output() .await .context("systemctl")?; let output = String::from_utf8(output.stdout).context("systemctl output")?; for line in output.split("\n") { if let Some((key, value)) = line.split_once("=") { match key { "ActiveState" if value != "active" => { bail!("{value}") } _ => (), } } } Ok(Success::default()) } Check::Shell { command, .. } => { let args = shlex::split(&command).ok_or(anyhow!("command syntax invalid"))?; let status = Command::new(args.get(0).ok_or(anyhow!("argv0 missing"))?) .args(&args[1..]) .status() .await; match status { Ok(status) if status.success() => Ok(Success::default()), Ok(status) => bail!("failed with code {}", status.code().unwrap_or(1)), Err(e) => bail!("command failed to execute: {e}"), } } Check::Http { url, .. } => { let k = Instant::now(); let r = reqwest::get(url).await?; if !r.status().is_success() { bail!("http status: {}", r.status().as_str()) } Ok(Success { latency: Some(k.elapsed()), ..Default::default() }) } } } }