Posted on
Artificial Intelligence

Artificial Intelligence Rust Server Monitoring

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Artificial Intelligence + Rust Server Monitoring (with Bash Glue)

If you’ve ever been paged at 3 AM only to find “CPU high” and a wall of undifferentiated logs, you know classic threshold-based monitoring can be noisy and blind to novel failures. What if your monitoring could learn a node’s normal behavior and flag only the weird stuff—without chewing CPU or pulling in heavy runtimes?

In this post, we’ll build a tiny, fast Rust agent that:

  • Samples CPU and memory,

  • Learns a rolling baseline,

  • Flags anomalies in real time,

  • Streams JSON logs so Bash can route alerts anywhere (mail, Slack, webhooks).

You get AI-powered anomaly detection with the reliability of a single static binary, orchestrated by the Linux tools you already trust: Bash + systemd + journalctl.

Why this approach works

  • Rust keeps the agent lean and predictable. No garbage collection pauses or hefty interpreters, which is perfect for busy servers or small VMs.

  • Unsupervised anomaly detection (robust z-scores in this example) adapts to your node’s unique baseline—less brittle than static thresholds, and no labeled data required.

  • Bash-first integration means observability stays simple: pipe JSON to jq, curl a webhook, or grep for audits.

  • You own the stack. No SaaS lock-in. Single binary. Easy to audit.

What we’ll build (high level)

  • airmon: A Rust daemon that emits JSON lines like: {"ts": "...", "cpu_pct": 7.1, "mem_pct": 54.3, "score": 0.8, "anomaly": false}

  • A rolling baseline model using robust statistics (median/MAD). It’s simple, fast, and resilient to spikes.

  • A systemd unit to run it as a service.

  • Bash snippets to alert on anomalies.


1) Install prerequisites

We’ll install compilers, curl, pkg-config, OpenSSL headers (common for Rust crates), and jq for JSON handling. Then we’ll install Rust via rustup.

Debian/Ubuntu (apt):

sudo apt update
sudo apt install -y curl build-essential pkg-config libssl-dev jq

Fedora/RHEL/CentOS Stream (dnf):

sudo dnf groupinstall -y "Development Tools"
sudo dnf install -y curl pkgconfig openssl-devel jq

openSUSE Leap/Tumbleweed (zypper):

sudo zypper refresh
sudo zypper install -y curl gcc make pkg-config libopenssl-devel jq

Install Rust toolchain (works on all distros):

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
rustc --version
cargo --version

Optional for testing load later (stress-ng):

  • apt:
sudo apt install -y stress-ng
  • dnf:
sudo dnf install -y stress-ng
  • zypper:
sudo zypper install -y stress-ng

2) Build the Rust AI monitor

Create a new Rust project:

cargo new airmon
cd airmon

Edit Cargo.toml to add dependencies:

cat > Cargo.toml <<'EOF'
[package]
name = "airmon"
version = "0.1.0"
edition = "2021"

[dependencies]
sysinfo = "0.30"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
EOF

Replace src/main.rs with:

cat > src/main.rs <<'EOF'
use serde::Serialize;
use std::collections::VecDeque;
use std::env;
use std::thread;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use sysinfo::{CpuExt, System, SystemExt};

#[derive(Serialize, Debug)]
struct Record {
    ts: u64,          // epoch seconds
    cpu_pct: f64,     // 0..100
    mem_pct: f64,     // 0..100
    z_cpu: f64,       // robust z-score
    z_mem: f64,       // robust z-score
    score: f64,       // combined anomaly score
    anomaly: bool,    // true if score exceeds threshold
}

fn env_u64(key: &str, default: u64) -> u64 {
    env::var(key).ok().and_then(|s| s.parse().ok()).unwrap_or(default)
}

fn env_f64(key: &str, default: f64) -> f64 {
    env::var(key).ok().and_then(|s| s.parse().ok()).unwrap_or(default)
}

fn median(vals: &mut [f64]) -> f64 {
    if vals.is_empty() { return 0.0; }
    vals.sort_by(|a, b| a.partial_cmp(b).unwrap());
    let mid = vals.len() / 2;
    if vals.len() % 2 == 0 {
        (vals[mid - 1] + vals[mid]) / 2.0
    } else {
        vals[mid]
    }
}

fn robust_z(x: f64, window: &VecDeque<f64>) -> f64 {
    if window.len() < 5 {
        return 0.0;
    }
    let mut w: Vec<f64> = window.iter().cloned().collect();
    let med = median(&mut w);
    let mut devs: Vec<f64> = w.into_iter().map(|v| (v - med).abs()).collect();
    let mad = median(&mut devs);
    // 1.4826 scales MAD to stddev for normal distributions
    let denom = (1.4826 * mad).max(1e-6);
    (x - med).abs() / denom
}

fn main() {
    // Tunables via environment variables:
    // AIRM_INTERVAL: seconds between samples (default 5)
    // AIRM_WINDOW:   rolling window size (default 120 ~ 10min at 5s)
    // AIRM_THRESHOLD: anomaly score threshold (default 5.0)
    let interval = env_u64("AIRM_INTERVAL", 5);
    let window_size = env_u64("AIRM_WINDOW", 120) as usize;
    let threshold = env_f64("AIRM_THRESHOLD", 5.0);

    let sleep = Duration::from_secs(interval);
    let mut sys = System::new_all();

    let mut cpu_hist: VecDeque<f64> = VecDeque::with_capacity(window_size);
    let mut mem_hist: VecDeque<f64> = VecDeque::with_capacity(window_size);

    loop {
        sys.refresh_cpu();
        sys.refresh_memory();

        let cpu = sys.global_cpu_info().cpu_usage() as f64; // 0..100
        let mem_total = sys.total_memory() as f64; // KiB
        let mem_used = sys.used_memory() as f64;   // KiB
        let mem_pct = if mem_total > 0.0 {
            (mem_used / mem_total) * 100.0
        } else { 0.0 };

        // update windows
        if cpu_hist.len() == window_size { cpu_hist.pop_front(); }
        if mem_hist.len() == window_size { mem_hist.pop_front(); }
        cpu_hist.push_back(cpu);
        mem_hist.push_back(mem_pct);

        // compute robust z-scores and combined anomaly score
        let zc = robust_z(cpu, &cpu_hist);
        let zm = robust_z(mem_pct, &mem_hist);
        // Combine features; you can weight them differently if needed
        let score = 0.5 * zc + 0.5 * zm;
        let anomaly = score.is_finite() && score >= threshold;

        let ts = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
        let rec = Record {
            ts, cpu_pct: cpu, mem_pct, z_cpu: zc, z_mem: zm, score, anomaly
        };
        println!("{}", serde_json::to_string(&rec).unwrap());

        thread::sleep(sleep);
    }
}
EOF

Build a release binary:

cargo build --release

Install it system-wide:

sudo install -Dm755 target/release/airmon /usr/local/bin/airmon

Try it:

AIRM_INTERVAL=2 AIRM_WINDOW=60 AIRM_THRESHOLD=5 /usr/local/bin/airmon | jq .

You should see a stream of JSON with anomaly=false under normal load. We’ll stress it soon.


3) Run it as a service (systemd) and alert with Bash

Create an env file to tweak thresholds without rebuilding:

sudo mkdir -p /etc/airmon
sudo tee /etc/airmon/airmon.env >/dev/null <<'EOF'
AIRM_INTERVAL=5
AIRM_WINDOW=120
AIRM_THRESHOLD=5.0
EOF

Create the systemd unit:

sudo tee /etc/systemd/system/airmon.service >/dev/null <<'EOF'
[Unit]
Description=AI Rust Monitor (CPU/Memory anomaly detection)
After=network.target

[Service]
EnvironmentFile=/etc/airmon/airmon.env
ExecStart=/usr/local/bin/airmon
Restart=always
RestartSec=2
# Optional: run as dedicated user (create first)
# User=airmon
# Group=airmon
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable --now airmon.service
systemctl status airmon.service --no-pager

Watch JSON logs:

journalctl -u airmon.service -f -o cat

Send alerts from Bash (example: print only anomalies):

journalctl -u airmon.service -f -o cat | jq -r 'select(.anomaly==true) | "ALERT ts=\(.ts) cpu=\(.cpu_pct|round) mem=\(.mem_pct|round) score=\(.score|round)"'

Slack webhook example:

# export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/XXX/YYY/ZZZ"
journalctl -u airmon.service -f -o cat | \
jq -rc 'select(.anomaly==true) | {text: ("[airmon] Anomaly: cpu=" + (.cpu_pct|tostring) + " mem=" + (.mem_pct|tostring) + " score=" + (.score|tostring))}' | \
while read -r line; do
  curl -sS -X POST -H 'Content-type: application/json' --data "$line" "$SLACK_WEBHOOK_URL" >/dev/null
done

Email via mailx, webhook to your incident tool, or even write to a file—the output is just JSON, so Bash makes routing trivial.


4) Validate with a real-world test

Generate a short CPU and memory surge to see anomalies fire.

  • apt:
sudo apt install -y stress-ng
  • dnf:
sudo dnf install -y stress-ng
  • zypper:
sudo zypper install -y stress-ng

Then:

stress-ng --cpu 4 --vm 2 --vm-bytes 70% -t 30s

In another terminal:

journalctl -u airmon.service -f -o cat | jq 'select(.anomaly==true)'

You should see anomaly=true while the surge runs, then return to normal.


5) Production tips and extensions

  • Tune thresholds per host role:

    • Busy build boxes: raise AIRM_THRESHOLD (e.g., 7–8).
    • Latency-sensitive services: lower it (e.g., 4–5).
  • Add more features:

    • Extend the Rust code to include load average, disk IO, or network RX/TX; combine features as a weighted score.
  • Quiet known-noise windows:

    • Wrap alerts in a Bash guard during deploys/cron windows, or drop a flag file that suppresses alerting temporarily.
  • Ship to your monitoring stack:

    • Pipe JSON into a lightweight collector or pushgateway. Or expose Prometheus text format from the Rust app on a localhost port if you prefer scraping.
  • Hardening:

    • Run as non-root user.
    • Resource limits with systemd (CPUQuota, MemoryMax).
    • Log rotation is handled by journald; adjust as needed.

Why “AI” here?

We used a robust, unsupervised anomaly detector (median/MAD z-scores) that adapts online. It’s fast, explainable, and avoids overfitting. If you want heavier ML:

  • Swap in a more advanced model (e.g., Isolation Forest) via Rust’s ML ecosystem (linfa) and score the latest sample against a rolling fit.

  • Keep the interface exactly the same: JSON to stdout.

The key is the pattern: learn the baseline locally, decide locally, and let Bash route the outcome.


Conclusion and next steps

You now have a tiny, auditable, AI-enabled Rust agent that flags unusual resource behavior and plays perfectly with Linux tooling. From here:

  • Deploy airmon to a staging node.

  • Tune AIRM_THRESHOLD and AIRM_WINDOW for your workload.

  • Plug the JSON stream into your alert transport of choice.

  • Extend features (load, IO, network) as needed.

If you want a step-by-step repo, reply and I’ll package this into a GitHub project with CI and container images. Happy monitoring!