Introduction
Have you ever experienced this scenario: one morning you discover your website is running slow, but you have no idea why. CPU is normal, memory is sufficient, disk space is plenty — but it’s just slow. After hours of troubleshooting, you finally find that some kernel parameters were quietly changed by an update, or a cron job is consuming excessive I/O.
Performance degradation is often subtle and gradual. By the time users complain, the problem has been accumulating for days or weeks.
The traditional approach is to manually run a few benchmarks and get a “roughly good” number. But without continuous tracking, you don’t know if performance has improved or declined.
Automated performance benchmarking + AI baseline management solves exactly this problem. It alerts you when performance starts declining, instead of waiting for user complaints.
Why Establish Performance Baselines
A Performance Baseline is your server’s performance data collection under “healthy” conditions. With baselines, you can:
- Quantify performance changes: Is today’s CPU throughput higher or lower than last week?
- Detect hidden degradation: Users aren’t complaining, but benchmark data shows consistent decline
- Validate optimization effects: After kernel upgrade or parameter tuning, how much did performance actually improve?
- Capacity planning: Predict when to scale based on trends
Baselines are not one-time — they are continuously updated reference points.
System Architecture
┌─────────────────────────────────────────────────────┐
│ Benchmark Scheduler (cron) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ CPU Benchmark│ │ Disk Benchmark│ │ Network Benchmark│
│ │ (sysbench) │ │ (fio/dd) │ │ (iperf3) │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ └─────────────────┼─────────────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Benchmark Store │ │
│ │ (SQLite / Timescale)│ │
│ └──────────┬──────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ AI Baseline Engine │ │
│ │ · Sliding window stats│ │
│ │ · Anomaly detection (3σ)│ │
│ │ · Trend analysis │ │
│ └──────────┬──────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Alert & Report │ │
│ │ · Alert notifications│ │
│ │ · Visual charts │ │
│ └─────────────────────┘ │
└─────────────────────────────────────────────────────┘
Step 1: Install Benchmarking Tools
We need several classic tools:
# Ubuntu/Debian
sudo apt update
sudo apt install -y sysbench fio iperf3 lmbench sysstat
# Verify installation
sysbench --version
fio --version
iperf3 --version
sysbench: CPU, memory, file I/O benchmarking fio: Deep disk I/O benchmarking iperf3: Network bandwidth and latency testing lmbench: System call latency testing
Step 2: Write Benchmark Scripts
Create a unified benchmark entry script:
#!/bin/bash
# /opt/benchmarks/run-all.sh
LOG_DIR="/var/log/benchmarks"
mkdir -p "$LOG_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
HOSTNAME=$(hostname)
echo "=== Benchmark Run: $TIMESTAMP on $HOSTNAME ==="
# 1. CPU benchmark (sysbench)
echo "--- CPU Benchmark ---"
sysbench cpu --threads=4 --time=30 run \
| tee "$LOG_DIR/cpu_${TIMESTAMP}.log"
# 2. Memory benchmark
echo "--- Memory Benchmark ---"
sysbench memory --threads=4 --memory-block-size=1M \
--memory-total-size=1G run \
| tee "$LOG_DIR/memory_${TIMESTAMP}.log"
# 3. Disk sequential read/write
echo "--- Disk Sequential I/O ---"
fio --name=seq_read --filename=/tmp/bench_seq \
--size=512M --bs=1M --rw=read \
--direct=1 --numjobs=1 --time_based \
--runtime=30 --group_reporting \
--output-format=json \
| tee "$LOG_DIR/disk_seq_${TIMESTAMP}.log"
# 4. Disk random read/write
echo "--- Disk Random I/O ---"
fio --name=rand_rw --filename=/tmp/bench_rand \
--size=256M --bs=4K --rw=randrw \
--direct=1 --numjobs=4 --time_based \
--runtime=30 --group_reporting \
--output-format=json \
| tee "$LOG_DIR/disk_rand_${TIMESTAMP}.log"
# 5. Network bandwidth test
echo "--- Network Benchmark ---"
iperf3 -c benchmark.server.com -t 30 \
--json > "$LOG_DIR/network_${TIMESTAMP}.log" 2>&1
echo "=== Benchmark Complete: $TIMESTAMP ==="
Step 3: Parse & Store Results
Benchmark results need structured storage for trend analysis. Here’s a Python script to parse various log formats:
#!/usr/bin/env python3
"""Parse benchmark results and store in SQLite."""
import sqlite3
import json
import re
import subprocess
from datetime import datetime
from pathlib import Path
DB_PATH = "/var/lib/benchmarks/benchmarks.db"
def init_db():
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
c.execute('''
CREATE TABLE IF NOT EXISTS benchmarks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hostname TEXT,
timestamp DATETIME,
test_type TEXT,
metric_name TEXT,
metric_value REAL,
unit TEXT,
raw_log TEXT
)
''')
c.execute('CREATE INDEX IF NOT EXISTS idx_host_time ON benchmarks(hostname, timestamp)')
conn.commit()
return conn
def parse_sysbench_cpu(log_path):
"""Parse sysbench CPU results."""
results = []
with open(log_path) as f:
content = f.read()
match = re.search(r'events per second:\s+(\d+)', content)
if match:
results.append(("cpu_events_per_sec", float(match.group(1)), "ops/s"))
match = re.search(r'Total number of events:\s+(\d+)', content)
if match:
results.append(("cpu_total_events", int(match.group(1)), "count"))
for p in [50, 95, 99]:
match = re.search(r'pt\((\d+)\).\s+[\d.]+\s+([\d.]+)', content)
if match and int(match.group(1)) == p:
results.append((f"cpu_lat_p{p}", float(match.group(2)), "ms"))
return results
def parse_fio_json(log_path):
"""Parse fio JSON output."""
results = []
with open(log_path) as f:
data = json.load(f)
job = data.get("jobs", [{}])[0]
read = job.get("read", {})
write = job.get("write", {})
results.append(("disk_read_iops", read.get("iops", {}).get("mean", 0), "IOPS"))
results.append(("disk_read_bw_mbps", read.get("bw_mean", 0) / 1024, "MB/s"))
results.append(("disk_write_iops", write.get("iops", {}).get("mean", 0), "IOPS"))
results.append(("disk_write_bw_mbps", write.get("bw_mean", 0) / 1024, "MB/s"))
lat = read.get("lat_ns", {})
results.append(("disk_read_lat_p99", lat.get("99th", 0) / 1e6, "us"))
return results
def parse_iperf_json(log_path):
"""Parse iperf3 JSON output."""
results = []
with open(log_path) as f:
data = json.load(f)
for stream in data.get("end", {}).get("sum_sent", {}).get("streams", []):
results.append(("net_bw_mbps", stream.get("sum_sent", {}).get("bits_per_second", 0) / 1e6, "Mbps"))
results.append(("net_retrans", stream.get("sum_sent", {}).get("retransmits", 0), "count"))
return results
def store_results(conn, test_type, parsed_results, raw_log_path):
hostname = subprocess.check_output(["hostname"]).decode().strip()
timestamp = datetime.utcnow().isoformat()
c = conn.cursor()
for metric_name, metric_value, unit in parsed_results:
with open(raw_log_path) as f:
raw_log = f.read()[:50000]
c.execute('''
INSERT INTO benchmarks
(hostname, timestamp, test_type, metric_name, metric_value, unit, raw_log)
VALUES (?, ?, ?, ?, ?, ?, ?)
''', (hostname, timestamp, test_type, metric_name, metric_value, unit, raw_log))
conn.commit()
def main():
conn = init_db()
log_dir = Path("/var/log/benchmarks")
for log_file in sorted(log_dir.glob("*.log")):
name = log_file.stem
if name.endswith(".log"):
continue
test_type = "cpu" if "cpu" in name else \
"memory" if "memory" in name else \
"disk_seq" if "disk_seq" in name else \
"disk_rand" if "disk_rand" in name else \
"network" if "network" in name else "unknown"
if test_type in ("cpu", "memory"):
results = parse_sysbench_cpu(log_file)
elif test_type.startswith("disk"):
results = parse_fio_json(log_file)
elif test_type == "network":
results = parse_iperf_json(log_file)
else:
continue
if results:
store_results(conn, test_type, results, log_file)
print(f"Stored {len(results)} metrics from {name}")
if __name__ == "__main__":
main()
Step 4: AI Baseline Engine
This is the core of the entire system. We need an engine that learns “normal” performance patterns and detects anomalies:
#!/usr/bin/env python3
"""AI-driven baseline engine for benchmark anomaly detection."""
import sqlite3
import numpy as np
from datetime import datetime, timedelta
from collections import defaultdict
class BenchmarkBaseline:
def __init__(self, db_path):
self.db_path = db_path
self.lookback_days = 14
self.sigma_threshold = 2.5
def get_historical_data(self, metric_name, days=14):
"""Get historical data for a metric."""
conn = sqlite3.connect(self.db_path)
c = conn.cursor()
since = (datetime.utcnow() - timedelta(days=days)).isoformat()
c.execute('''
SELECT timestamp, metric_value
FROM benchmarks
WHERE metric_name = ? AND timestamp >= ?
ORDER BY timestamp
''', (metric_name, since))
conn.close()
return np.array([row[1] for row in c.fetchall()])
def compute_baseline(self, data):
"""Compute baseline: mean ± std dev."""
if len(data) < 3:
return None, None, None
mean = np.mean(data)
std = np.std(data)
# Re-filter outliers and recalculate
if std > 0:
clean_data = data[np.abs(data - mean) < 3 * std]
if len(clean_data) > 3:
mean = np.mean(clean_data)
std = np.std(clean_data)
return mean, std, len(data)
def detect_anomaly(self, metric_name, current_value):
"""Detect if current value is anomalous."""
historical = self.get_historical_data(metric_name)
if len(historical) < 3:
return {
"status": "insufficient_data",
"message": f"Only {len(historical)} historical data points"
}
mean, std, count = self.compute_baseline(historical)
if std == 0:
return {
"status": "stable",
"baseline_mean": float(mean),
"baseline_std": 0,
"current_value": float(current_value),
"z_score": 0,
"deviation_pct": 0
}
z_score = (current_value - mean) / std
# Higher is better for throughput metrics
if metric_name in ("disk_read_bw_mbps", "disk_write_bw_mbps",
"net_bw_mbps", "cpu_events_per_sec"):
if z_score < -self.sigma_threshold:
status = "degraded"
elif z_score > self.sigma_threshold:
status = "improved"
else:
status = "normal"
else:
# Lower is better for latency metrics
if z_score > self.sigma_threshold:
status = "degraded"
elif z_score < -self.sigma_threshold:
status = "improved"
else:
status = "normal"
deviation_pct = abs(z_score) / self.sigma_threshold * 100
return {
"status": status,
"baseline_mean": float(mean),
"baseline_std": float(std),
"current_value": float(current_value),
"z_score": float(z_score),
"deviation_pct": float(deviation_pct),
"sample_count": int(count)
}
def detect_trend(self, metric_name, days=7):
"""Detect performance trend: improving, degrading, or stable."""
historical = self.get_historical_data(metric_name, days)
if len(historical) < 5:
return "insufficient_data"
x = np.arange(len(historical))
slope = np.polyfit(x, historical, 1)[0]
mean = np.mean(historical)
if mean == 0:
return "stable"
normalized_slope = slope / abs(mean)
# Threshold: daily change > 2% indicates trend
if normalized_slope > 0.02:
return "improving"
elif normalized_slope < -0.02:
return "degrading"
else:
return "stable"
def generate_report(self):
"""Generate complete performance report."""
conn = sqlite3.connect(self.db_path)
c = conn.cursor()
c.execute('SELECT DISTINCT metric_name FROM benchmarks')
metrics = [row[0] for row in c.fetchall()]
conn.close()
latest_values = {}
conn = sqlite3.connect(self.db_path)
c = conn.cursor()
for metric in metrics:
c.execute('''
SELECT metric_value FROM benchmarks
WHERE metric_name = ?
ORDER BY timestamp DESC LIMIT 1
''', (metric,))
row = c.fetchone()
if row:
latest_values[metric] = row[0]
conn.close()
report = []
anomalies_found = 0
for metric, current_value in latest_values.items():
result = self.detect_anomaly(metric, current_value)
trend = self.detect_trend(metric)
report.append({
"metric": metric,
**result,
"trend": trend
})
if result.get("status") == "degraded":
anomalies_found += 1
return {
"generated_at": datetime.utcnow().isoformat(),
"metrics_analyzed": len(report),
"anomalies_found": anomalies_found,
"details": report
}
Step 5: Scheduling & Alerts
# Add to crontab
# Run full benchmark daily at 2 AM
0 2 * * * /opt/benchmarks/run-all.sh
# Parse results after benchmark
35 2 * * * /usr/bin/python3 /opt/benchmarks/parse_results.py
# Generate baseline report daily at 3 AM
0 3 * * * /usr/bin/python3 /opt/benchmarks/baseline_engine.py --report
# Weekly report every Sunday at 4 AM
0 4 * * 0 /usr/bin/python3 /opt/benchmarks/weekly_report.py
Alert notification integration:
# Add alert logic to baseline_engine.py
import requests
def send_alert(metric, result):
"""Send alert notification."""
if result["status"] != "degraded":
return
severity = "high" if result["deviation_pct"] > 150 else "medium"
message = f"VPS Performance Anomaly Alert\n\n"
message += f"Metric: {metric}\n"
message += f"Current: {result['current_value']:.2f}\n"
message += f"Baseline Mean: {result['baseline_mean']:.2f}\n"
message += f"Deviation: {result['deviation_pct']:.1f}%\n"
message += f"Z-Score: {result['z_score']:.2f}\n"
message += f"Server: {HOSTNAME}\n"
message += f"Time: {datetime.utcnow().isoformat()}"
# Slack
# requests.post(SLACK_WEBHOOK, json={"text": message})
# Telegram
# requests.post(TELEGRAM_API, json={"chat_id": CHAT_ID, "text": message})
print(f"[ALERT] {severity}: {metric} - {result['current_value']:.2f}")
Step 6: Visualization Dashboard
#!/usr/bin/env python3
"""Generate HTML performance dashboard."""
import sqlite3
import json
from datetime import datetime, timedelta
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
def generate_dashboard():
conn = sqlite3.connect(DB_PATH)
since = (datetime.utcnow() - timedelta(days=30)).isoformat()
metrics = ["cpu_events_per_sec", "disk_read_bw_mbps",
"disk_write_bw_mbps", "net_bw_mbps"]
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
axes = axes.flatten()
for idx, metric in enumerate(metrics):
ax = axes[idx]
c = conn.cursor()
c.execute('''
SELECT timestamp, metric_value FROM benchmarks
WHERE metric_name = ? AND timestamp >= ?
ORDER BY timestamp
''', (metric, since))
data = c.fetchall()
if not data:
continue
timestamps = [row[0] for row in data]
values = [row[1] for row in data]
ax.scatter(timestamps, values, s=20, alpha=0.6, c='#6366f1')
if len(values) > 3:
mean = np.mean(values)
std = np.std(values)
ax.axhline(y=mean, color='#ef4444', linestyle='--',
label=f'Mean: {mean:.1f}')
ax.axhline(y=mean + 2.5*std, color='#f59e0b',
linestyle=':', alpha=0.7, label='Upper Limit')
ax.axhline(y=mean - 2.5*std, color='#f59e0b',
linestyle=':', alpha=0.7)
ax.set_title(metric, fontsize=12, color='white')
ax.set_ylabel('Value', color='white')
ax.tick_params(colors='white')
ax.grid(True, alpha=0.3)
ax.set_facecolor('#0f172a')
plt.tight_layout()
plt.savefig('/var/www/benchmark-dashboard/chart.png',
dpi=150, facecolor='#0f172a')
conn.close()
print("Dashboard chart generated.")
if __name__ == "__main__":
generate_dashboard()
Real-World Case Study
Last year, we deployed this system on a 2C2G VPS. Three months later, the AI baseline engine detected an anomaly:
| Time | Metric | Current | Baseline Mean | Deviation |
|---|---|---|---|---|
| Day 1 | disk_read_bw_mbps | 450 | 452 | -0.4% |
| Day 15 | disk_read_bw_mbps | 438 | 452 | -3.1% |
| Day 30 | disk_read_bw_mbps | 380 | 452 | -15.9% ⚠️ |
| Day 45 | disk_read_bw_mbps | 290 | 452 | -35.8% 🔴 |
By Day 30, the system had already sent a medium-priority alert. Investigation revealed that SSD TRIM was not properly configured, causing severe write amplification. After fixing, performance recovered to 445 MB/s.
Key takeaway: Without automated baseline tracking, this problem might have gone unnoticed until users complained.
Advanced: Multi-Server Benchmark Comparison
If you have multiple VPS instances, you can do cross-server comparison:
def compare_servers(metric_name):
"""Compare same metric across multiple VPS servers."""
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
c.execute('''
SELECT hostname, metric_value, timestamp
FROM benchmarks
WHERE metric_name = ?
ORDER BY timestamp DESC
''', (metric_name,))
servers = defaultdict(list)
for hostname, value, ts in c.fetchall():
servers[hostname].append((value, ts))
conn.close()
results = []
for hostname, data_points in servers.items():
values = [v for v, t in sorted(data_points, key=lambda x: x[1])]
if len(values) > 3:
mean = np.mean(values)
std = np.std(values)
latest = values[-1]
z_score = (latest - mean) / std if std > 0 else 0
results.append({
"hostname": hostname,
"baseline_mean": mean,
"current": latest,
"z_score": z_score,
"status": "ok" if abs(z_score) < 2.5 else "anomaly"
})
return sorted(results, key=lambda x: abs(x["z_score"]), reverse=True)
Summary
The core value of automated benchmarking + AI baseline management:
- Proactive, not reactive: Catch degradation before users notice
- Data-driven, not gut-feeling: Let data speak
- Continuously updated: Baselines adapt to hardware aging and software changes
- Zero cost: All open-source tools, running locally, no cloud APIs
Next steps:
- Install sysbench, fio, iperf3
- Deploy benchmark scripts, run first baseline test
- Set up Cron jobs
- After one week, review data and adjust sigma threshold
- Integrate alert notifications
Make performance degradation impossible to hide. Start by establishing your first baseline today.
