- Posted on
- • Artificial Intelligence
Artificial Intelligence Linux Capacity Planning
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
AI-Powered Linux Capacity Planning with Bash: From Metrics to Predictions
Ever been paged at 2 a.m. because a server hit 100% CPU, or found out you’ve been paying for idle capacity you don’t use? Traditional capacity planning is reactive and expensive. The good news: you can turn your Linux telemetry into forward-looking insights with a few Bash commands and a tiny bit of machine learning. This guide shows you how to collect the right metrics, build an AI-ready dataset, forecast your needs, and automate action—entirely from the command line.
What you’ll get:
A minimal, portable metrics pipeline in Bash (no heavy agents)
Actionable forecasts (e.g., “You’ll need 12 vCPUs next Monday at noon”)
Early anomaly detection to prevent outages
Why AI for Linux capacity planning?
Precision beats guesswork: AI can capture seasonality (work-hours vs nights/weekends), spikes, and growth trends better than static thresholds.
Cost and risk: Right-sizing reduces overprovisioning costs and helps you avoid downtime from saturation.
Linux-friendly: You don’t need a data lake. Careful Bash collection + a small Python model is enough to start.
Prerequisites: install the basics
These packages are used in the steps below. Install them with your distro’s package manager.
Python 3 and pip (for the tiny ML scripts)
cron (for scheduled metric collection)
Optional: sysstat (if you prefer sar/iostat over /proc-based sampling)
Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y python3 python3-pip cron
# Optional
sudo apt install -y sysstat
Fedora/RHEL/CentOS (dnf):
sudo dnf install -y python3 python3-pip cronie
# Optional
sudo dnf install -y sysstat
# Enable/Start cron service if needed:
sudo systemctl enable --now crond
openSUSE/SLE (zypper):
sudo zypper refresh
sudo zypper install -y python3 python3-pip cron
# Optional
sudo zypper install -y sysstat
# Enable/Start cron service if needed:
sudo systemctl enable --now cron
Python packages (user install):
pip3 install --user pandas scikit-learn numpy
Tip: Ensure ~/.local/bin is on your PATH if using --user.
1) Decide what you’re planning for (KPIs and targets)
You can’t model what you don’t define. Pick a small set of KPIs and a headroom policy:
CPU: target average under 70–75%; track spikes (95th percentile).
Memory: keep enough headroom to avoid swapping; track MemAvailable%.
Disk: watch utilization (% busy time) and latency; sustained > 80–90% busy is risky.
Network: track sustained RX/TX bandwidth and burst behavior.
Headroom rule of thumb:
- Capacity_needed = Peak_predicted / Target_utilization
- Example: If you expect 560% CPU across cores at peak and target 70% utilization, you need ceil(560/70) = 8 vCPUs.
Sanity checks you can run right now:
uptime # quick load snapshot
cat /proc/meminfo # MemAvailable
cat /proc/loadavg # 1/5/15-min load
2) Collect the right telemetry every minute (pure Bash)
The script below samples once per minute and writes a CSV with:
Timestamp
CPU used (%)
Load average (1 min)
Memory used (%)
Disk busy time (%)
Network RX/TX (kbps)
It uses /proc and /sys for portability and avoids locale-dependent parsing.
Create metrics.sh:
#!/usr/bin/env bash
set -euo pipefail
OUT_DIR="${OUT_DIR:-/var/log/capacity}"
OUT_CSV="${OUT_CSV:-$OUT_DIR/metrics.csv}"
# Auto-detect root block device if DISK is not set (e.g., sda, nvme0n1)
detect_disk() {
local src dev base
src=$(findmnt -n -o SOURCE / || true) # e.g., /dev/sda2 or /dev/nvme0n1p1
dev=${src#/dev/}
base=$(sed -E 's/p?[0-9]+$//' <<< "$dev") # strip partition suffix
echo "${base:-sda}"
}
# Auto-detect primary net dev if NETDEV is not set
detect_netdev() {
ip route 2>/dev/null | awk '/default/ {print $5; exit}' || echo "eth0"
}
DISK="${DISK:-$(detect_disk)}"
NETDEV="${NETDEV:-$(detect_netdev)}"
mkdir -p "$OUT_DIR"
if [ ! -s "$OUT_CSV" ]; then
echo "ts,cpu_used_pct,load1,mem_used_pct,disk_util_pct,net_rx_kbps,net_tx_kbps" > "$OUT_CSV"
fi
read_cpu() {
# outputs: total idle
# /proc/stat: cpu user nice system idle iowait irq softirq steal guest guest_nice
local arr total idle
read -r _ user nice system idle iowait irq softirq steal guest guest_nice < /proc/stat
total=$((user+nice+system+idle+iowait+irq+softirq+steal))
echo "$total $((idle+iowait))"
}
read_disk_io_ticks() {
# field 10 in /sys/block/$DISK/stat is "io_ticks" in ms
awk '{print $10}' "/sys/block/$DISK/stat"
}
read_net_bytes() {
local rx tx
read -r rx < "/sys/class/net/$NETDEV/statistics/rx_bytes"
read -r tx < "/sys/class/net/$NETDEV/statistics/tx_bytes"
echo "$rx $tx"
}
# Snapshots before interval
ts=$(date -u +"%Y-%m-%d %H:%M:%S")
read -r cpu_total1 cpu_idle1 < <(read_cpu)
disk_ticks1=$(read_disk_io_ticks)
read -r rx1 tx1 < <(read_net_bytes)
# Sleep for sampling interval
sleep 1
# Snapshots after interval
read -r cpu_total2 cpu_idle2 < <(read_cpu)
disk_ticks2=$(read_disk_io_ticks)
read -r rx2 tx2 < <(read_net_bytes)
# CPU used %
cpu_delta=$((cpu_total2 - cpu_total1))
idle_delta=$((cpu_idle2 - cpu_idle1))
cpu_used_pct=$(awk -v c="$cpu_delta" -v i="$idle_delta" 'BEGIN{ if(c>0){print (100*(c - i)/c)} else {print 0}}')
# Load 1-min
load1=$(awk '{print $1}' /proc/loadavg)
# Memory used %
mem_total_kb=$(awk '/MemTotal/ {print $2}' /proc/meminfo)
mem_avail_kb=$(awk '/MemAvailable/ {print $2}' /proc/meminfo)
mem_used_pct=$(awk -v t="$mem_total_kb" -v a="$mem_avail_kb" 'BEGIN{ if(t>0){print (100*(1 - a/t))} else {print 0}}')
# Disk util % over 1s (io_ticks ms / 10)
disk_io_ms=$((disk_ticks2 - disk_ticks1))
disk_util_pct=$(awk -v ms="$disk_io_ms" 'BEGIN{v=ms/10.0; if(v>100) v=100; if(v<0) v=0; print v}')
# Network kbps
rx_kbps=$(awk -v d="$((rx2 - rx1))" 'BEGIN{ if(d<0) d=0; print (8*d/1000.0) }')
tx_kbps=$(awk -v d="$((tx2 - tx1))" 'BEGIN{ if(d<0) d=0; print (8*d/1000.0) }')
echo "$ts,$cpu_used_pct,$load1,$mem_used_pct,$disk_util_pct,$rx_kbps,$tx_kbps" >> "$OUT_CSV"
Make it executable and schedule it:
sudo install -m 0755 metrics.sh /usr/local/bin/metrics.sh
sudo mkdir -p /var/log/capacity
# Run every minute
( crontab -l 2>/dev/null; echo '* * * * * /usr/local/bin/metrics.sh' ) | crontab -
Let this run for at least a few hours (ideally a week) to capture patterns.
Real-world example:
- A fintech team found that Monday 09:00–11:00 CPU peaked 35% higher than other weekdays due to customer logins. A 70% target utilization implied moving from 8 to 12 vCPUs on the front-end tier during that window. Auto-scaling policy was updated accordingly.
3) Forecast your CPU needs with a tiny model (lag-based regression)
We’ll train a lightweight model to predict the next 60 minutes of CPU. It uses only pandas and scikit-learn and runs in seconds.
Install Python deps (if you haven’t):
pip3 install --user pandas scikit-learn numpy
Create forecast_capacity.py:
#!/usr/bin/env python3
import argparse, math
import pandas as pd
import numpy as np
from sklearn.linear_model import LinearRegression
def make_lag_features(series, lags=5):
df = pd.DataFrame({"y": series})
for i in range(1, lags+1):
df[f"lag_{i}"] = df["y"].shift(i)
return df.dropna()
def main():
p = argparse.ArgumentParser(description="Forecast CPU usage and recommend vCPU capacity.")
p.add_argument("--csv", default="/var/log/capacity/metrics.csv")
p.add_argument("--horizon", type=int, default=60, help="Minutes to forecast")
p.add_argument("--vcpu", type=int, required=True, help="Current total vCPUs for this node/tier")
p.add_argument("--target-util", type=float, default=0.70, help="Target utilization (0-1)")
args = p.parse_args()
df = pd.read_csv(args.csv)
df["ts"] = pd.to_datetime(df["ts"])
df = df.sort_values("ts").set_index("ts")
# Resample to 1-minute, fill small gaps
df = df.resample("1min").mean().interpolate(limit=5)
y = df["cpu_used_pct"].clip(lower=0, upper=100)
feat = make_lag_features(y, lags=5)
y_aligned = feat["y"].copy()
X = feat.drop(columns=["y"])
# Train using all but last 'horizon' points
if len(X) <= args.horizon + 5:
raise SystemExit("Not enough data to forecast. Collect more minutes of metrics.")
X_train = X.iloc[:-args.horizon]
y_train = y_aligned.iloc[:-args.horizon]
model = LinearRegression()
model.fit(X_train, y_train)
# Recursive forecast for next horizon minutes
history = list(y.values)
preds = []
for _ in range(args.horizon):
feats = [history[-i] for i in range(1, 6)]
pred = float(model.predict([feats])[0])
pred = min(max(pred, 0.0), 100.0) # clamp
preds.append(pred)
history.append(pred)
last_ts = df.index[-1]
future_idx = pd.date_range(last_ts + pd.Timedelta(minutes=1), periods=args.horizon, freq="1min")
pred_series = pd.Series(preds, index=future_idx, name="pred_cpu_pct")
peak_pred = float(pred_series.max())
rec_vcpu = max(1, math.ceil(args.vcpu * (peak_pred/100.0) / args.target_util))
print("Forecast summary")
print("----------------")
print(f"Horizon: {args.horizon} min")
print(f"Peak predicted CPU: {peak_pred:.1f}%")
print(f"Current vCPU: {args.vcpu}")
print(f"Target utilization: {args.target_util*100:.0f}%")
print(f"Recommended vCPU: {rec_vcpu}")
print()
print("Top 5 predicted peaks:")
print(pred_series.sort_values(ascending=False).head(5).to_string())
if __name__ == "__main__":
main()
Run it:
python3 forecast_capacity.py --vcpu 8 --horizon 60
Output example:
Forecast summary
----------------
Horizon: 60 min
Peak predicted CPU: 83.4%
Current vCPU: 8
Target utilization: 70%
Recommended vCPU: 10
Top 5 predicted peaks:
2026-07-09 09:59:00 83.4
2026-07-09 10:00:00 82.7
2026-07-09 09:58:00 82.3
2026-07-09 09:57:00 80.9
2026-07-09 09:56:00 79.5
Action: Scale to 10 vCPUs before the 10:00 surge, or shift load.
4) Detect anomalies before they become outages (MAD-based)
Catch sudden disk or CPU spikes using robust statistics (Median Absolute Deviation). This flags “weird” points without being fooled by regular peaks.
Create detect_anomalies.py:
#!/usr/bin/env python3
import argparse
import pandas as pd
import numpy as np
def mad_based_flags(series, window=21, z=6.0):
x = series.copy()
roll_med = x.rolling(window, center=True, min_periods=5).median()
roll_mad = (x - roll_med).abs().rolling(window, center=True, min_periods=5).median() * 1.4826
score = (x - roll_med).abs() / (roll_mad.replace(0, np.nan))
return score > z
def main():
p = argparse.ArgumentParser(description="Detect resource anomalies from metrics CSV")
p.add_argument("--csv", default="/var/log/capacity/metrics.csv")
p.add_argument("--window", type=int, default=21)
p.add_argument("--z", type=float, default=6.0)
args = p.parse_args()
df = pd.read_csv(args.csv, parse_dates=["ts"]).set_index("ts").sort_index()
for col in ["cpu_used_pct", "disk_util_pct", "mem_used_pct"]:
if col not in df.columns:
continue
flags = mad_based_flags(df[col], window=args.window, z=args.z) | (df[col] > 95)
anomalies = df[flags].tail(10)
if not anomalies.empty:
print(f"Anomalies for {col}:")
print(anomalies[[col]].to_string())
print()
if __name__ == "__main__":
main()
Run it:
python3 detect_anomalies.py
Example remediation:
If disk_util_pct anomalies align with a backup job, reschedule it or move it to a disk-optimized node.
If cpu_used_pct anomalies correlate with a deploy, profile the new code path.
5) Automate and act
Wire it up so the system helps you before you’re in trouble.
- Cron the forecast for a daily planning snapshot:
# 07:30 every day; write summary to syslog
30 7 * * * /usr/bin/python3 /usr/local/bin/forecast_capacity.py --vcpu 8 --horizon 180 | /usr/bin/logger -t capacity-forecast
Gate deploys on available headroom:
- If predicted peak > 85% with current vCPU, require manual approval.
Auto-scale or schedule:
- For VMs: pre-scale before predicted peak, downscale after.
- For K8s: feed forecasts into HPA/VPA or a custom scaler.
Close the loop:
- Store forecasts and realized usage; review misses weekly; adjust lags, horizon, or add features (e.g., day-of-week, hour-of-day).
Real-world patterns to watch for
Busy Monday mornings: user login spikes, API traffic surges.
Nightly batch jobs: backups/ETL saturate disk and network; stagger them.
Month-end closes: finance/reporting loads. Pre-scale capacity and cache warm-ups.
Release days: higher CPU from new features or warm caches; throttle concurrency.
Conclusion and next steps
You don’t need a data science team to get practical, AI-assisted capacity planning: 1) Capture clean, minute-level metrics with a 30-line Bash script. 2) Forecast with a simple, transparent model. 3) Detect anomalies early and automate actions.
Your next step:
Deploy metrics.sh, let it run for a week, then run forecast_capacity.py and detect_anomalies.py.
Use the forecast to update your scaling policy or a change window.
Iterate: add features (hour-of-day, day-of-week), expand to memory and network planning, and plug into your CI/CD gates.
If this helped, consider standardizing it across your fleet and building a small internal “capacity report” that leadership can check every Monday morning. Your on-call rotation will thank you.