aboutsummaryrefslogtreecommitdiff
path: root/src/check.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/check.rs')
-rw-r--r--src/check.rs91
1 files changed, 91 insertions, 0 deletions
diff --git a/src/check.rs b/src/check.rs
new file mode 100644
index 0000000..bfde1b9
--- /dev/null
+++ b/src/check.rs
@@ -0,0 +1,91 @@
+use crate::{Check, Config, Success, 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<Config>, i: usize) {
+ loop {
+ check_service(&config, i).await;
+ sleep(Duration::from_secs(config.interval)).await;
+ }
+}
+
+async fn check_service(config: &Arc<Config>, 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")),
+ };
+ info!("check {i}:{j} => {r:?}");
+ {
+ let mut g = STATUS.write().await;
+ g.insert((i, j), r);
+ }
+ },
+ ));
+ while let Some(_) = futs.next().await {}
+}
+
+impl Check {
+ pub async fn check(&self) -> Result<Success> {
+ 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()
+ })
+ }
+ }
+ }
+}