Vegeta is a versatile HTTP load testing tool and library written in Go. It supports constant-rate attacks via a simple CLI (echo "GET https://example.com" | vegeta attack -rate=100 -duration=30s | vegeta report) and structured JSON targets for complex scenarios. Because Vegeta is a command-line tool rather than a daemon, standard uptime monitors can't probe it directly — Vigilmon closes this gap with heartbeat monitors for scheduled attack jobs, a lightweight results server that exposes Vegeta metrics over HTTP, and alerting when latency or error rates exceed your baselines.
What You'll Build
- A Vigilmon heartbeat monitor that signals when a scheduled Vegeta attack completes
- A results HTTP endpoint that exposes the latest Vegeta metrics (p99, error rate)
- A Vigilmon HTTP monitor that checks the results endpoint for performance degradation
- Alert thresholds for stale or failing load test runs
Prerequisites
- Vegeta installed (
go install github.com/tsenart/vegeta@latestor a binary release) - A free account at vigilmon.online
Step 1: Verify Your Vegeta Installation
Confirm the CLI is available and can reach your target:
# Check version
vegeta version
# Quick single-request probe
echo "GET https://api.example.com/health" | vegeta attack -rate=1 -duration=1s | vegeta report
A healthy target produces output like:
Requests [total, rate, throughput] 1, 1.00, 1.00
Duration [total, attack, wait] 1.001s, 1s, 1.001ms
Latencies [min, mean, 50, 90, 95, 99, max] 1.001ms, 1.001ms, 1.001ms, 1.001ms, 1.001ms, 1.001ms, 1.001ms
Bytes In [total, mean] 28, 28.00
Bytes Out [total, mean] 0, 0.00
Success [ratio] 100.00%
Status Codes [code:count] 200:1
For structured multi-target attacks, use a JSON targets file:
[
{"method": "GET", "url": "https://api.example.com/health"},
{"method": "GET", "url": "https://api.example.com/api/users"},
{"method": "POST", "url": "https://api.example.com/api/orders",
"header": {"Content-Type": ["application/json"]},
"body": "eyJxdHkiOiAxfQ=="}
]
Attack with it:
vegeta attack -targets=targets.json -rate=50 -duration=60s | tee results.bin | vegeta report
Step 2: Set Up a Heartbeat Monitor for Scheduled Attacks
Vegeta attacks are typically scheduled (nightly baseline, pre-release soak, CI gate). A Vigilmon heartbeat monitor alerts you when the expected ping stops arriving — meaning the attack job failed silently or the machine is down.
Create the Heartbeat Monitor
- Log in to Vigilmon → Add Monitor → Heartbeat.
- Name:
vegeta-nightly-attack. - Expected interval: Match your schedule (e.g.,
86400seconds for daily). - Grace period: 30 minutes.
- Copy the unique heartbeat URL:
https://vigilmon.online/heartbeat/your-unique-id.
Wire the Heartbeat Into Your Attack Script
#!/bin/bash
# run-vegeta.sh
set -euo pipefail
RATE="${VEGETA_RATE:-100}"
DURATION="${VEGETA_DURATION:-60s}"
TARGETS="${VEGETA_TARGETS:-targets.json}"
RESULTS_FILE="/tmp/vegeta-results.json"
VIGILMON_HEARTBEAT="${VIGILMON_HEARTBEAT_URL}"
RESULTS_ENDPOINT="${VEGETA_RESULTS_ENDPOINT:-http://localhost:3011/vegeta/results}"
echo "Starting Vegeta attack at ${RATE} req/s for ${DURATION}..."
vegeta attack \
-targets="${TARGETS}" \
-rate="${RATE}" \
-duration="${DURATION}" \
| vegeta report -type=json > "${RESULTS_FILE}"
echo "Attack complete. Posting results..."
curl -s -X POST \
-H "Content-Type: application/json" \
-d "@${RESULTS_FILE}" \
"${RESULTS_ENDPOINT}"
echo "Pinging Vigilmon heartbeat..."
curl -fsS "${VIGILMON_HEARTBEAT}"
echo "Done."
Add to crontab:
0 2 * * * /opt/load-tests/run-vegeta.sh >> /var/log/vegeta.log 2>&1
Step 3: Build a Results Health Endpoint
Expose the latest Vegeta metrics over HTTP so Vigilmon can check performance thresholds on every poll.
Node.js Results Server
// vegeta-results-server.js
const express = require('express');
const app = express();
app.use(express.json());
const MAX_AGE_MS = 25 * 60 * 60 * 1000; // 25 hours
const P99_THRESHOLD_MS = 500; // alert if p99 > 500ms
const ERROR_RATE_THRESHOLD = 0.01; // alert if error rate > 1%
let latestResult = null;
app.post('/vegeta/results', (req, res) => {
latestResult = {
receivedAt: new Date().toISOString(),
metrics: req.body,
};
console.log('Received Vegeta results:', JSON.stringify(latestResult.metrics, null, 2));
res.json({ received: true, timestamp: latestResult.receivedAt });
});
app.get('/health/vegeta', (req, res) => {
if (!latestResult) {
return res.status(503).json({
status: 'no_results',
message: 'No Vegeta results have been posted yet',
});
}
const ageMs = Date.now() - new Date(latestResult.receivedAt).getTime();
if (ageMs > MAX_AGE_MS) {
return res.status(503).json({
status: 'stale',
message: `Last results are ${Math.round(ageMs / 3600000)}h old`,
lastRun: latestResult.receivedAt,
});
}
const m = latestResult.metrics;
// Vegeta JSON report: latencies in nanoseconds, success ratio 0.0–1.0
const p99Ms = (m?.latencies?.['99th'] || 0) / 1e6;
const successRatio = m?.success || 1;
const errorRate = 1 - successRatio;
if (p99Ms > P99_THRESHOLD_MS || errorRate > ERROR_RATE_THRESHOLD) {
return res.status(503).json({
status: 'degraded',
p99LatencyMs: Math.round(p99Ms),
errorRate: errorRate.toFixed(4),
successRatio: successRatio.toFixed(4),
lastRun: latestResult.receivedAt,
});
}
res.json({
status: 'ok',
p99LatencyMs: Math.round(p99Ms),
errorRate: errorRate.toFixed(4),
successRatio: successRatio.toFixed(4),
lastRun: latestResult.receivedAt,
});
});
app.listen(3011, () => console.log('Vegeta results server on :3011'));
Python Alternative
# vegeta_results_server.py
import os
import time
from datetime import datetime, timezone
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pydantic import BaseModel
app = FastAPI()
P99_THRESHOLD_MS = float(os.environ.get("P99_THRESHOLD_MS", "500"))
ERROR_RATE_THRESHOLD = float(os.environ.get("ERROR_RATE_THRESHOLD", "0.01"))
MAX_AGE_HOURS = float(os.environ.get("MAX_AGE_HOURS", "25"))
latest_result = None
class VegetaMetrics(BaseModel):
latencies: dict
success: float
requests: int = 0
@app.post("/vegeta/results")
def receive_results(metrics: VegetaMetrics):
global latest_result
latest_result = {"received_at": datetime.now(timezone.utc).isoformat(), "metrics": metrics}
return {"received": True}
@app.get("/health/vegeta")
def vegeta_health():
if latest_result is None:
return JSONResponse(status_code=503, content={"status": "no_results"})
age_hours = (datetime.now(timezone.utc) - datetime.fromisoformat(latest_result["received_at"])).total_seconds() / 3600
if age_hours > MAX_AGE_HOURS:
return JSONResponse(status_code=503, content={"status": "stale", "age_hours": round(age_hours, 1)})
m = latest_result["metrics"]
p99_ms = (m.latencies.get("99th", 0)) / 1e6 # nanoseconds to ms
error_rate = 1 - m.success
if p99_ms > P99_THRESHOLD_MS or error_rate > ERROR_RATE_THRESHOLD:
return JSONResponse(status_code=503, content={
"status": "degraded",
"p99_ms": round(p99_ms, 1),
"error_rate": round(error_rate, 4),
})
return {"status": "ok", "p99_ms": round(p99_ms, 1), "error_rate": round(error_rate, 4)}
Deploy and verify:
curl http://localhost:3011/health/vegeta
# {"status":"no_results","message":"No Vegeta results have been posted yet"}
# After running the attack script:
curl http://localhost:3011/health/vegeta
# {"status":"ok","p99LatencyMs":42,"errorRate":"0.0000","successRatio":"1.0000","lastRun":"2026-07-03T02:15:33.012Z"}
Step 4: Create Vigilmon Monitors
Heartbeat Monitor
Already created in Step 2. Confirm it appears in your Vigilmon dashboard.
HTTP Monitor for the Results Endpoint
- Log in to Vigilmon → Add Monitor → HTTP.
- URL:
http://your-server.example.com:3011/health/vegeta. - Check interval: 60 minutes.
- Response timeout: 10 seconds.
- Expected status:
200. - Keyword:
"status":"ok". - Click Save.
Step 5: Integrate With CI/CD
GitHub Actions
# .github/workflows/vegeta-load-test.yml
name: Vegeta Load Test
on:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
jobs:
vegeta:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Vegeta
run: |
VEGETA_VERSION=12.11.1
curl -LO "https://github.com/tsenart/vegeta/releases/download/v${VEGETA_VERSION}/vegeta_${VEGETA_VERSION}_linux_amd64.tar.gz"
tar xzf vegeta_*.tar.gz
sudo mv vegeta /usr/local/bin/
- name: Run attack
run: |
vegeta attack \
-targets=targets.json \
-rate=100 \
-duration=60s \
| vegeta report -type=json > results.json
- name: Post results
run: |
curl -s -X POST \
-H "Content-Type: application/json" \
-d "@results.json" \
"${{ secrets.VEGETA_RESULTS_ENDPOINT }}"
- name: Ping Vigilmon heartbeat
if: success()
run: curl -fsS "${{ secrets.VIGILMON_HEARTBEAT_URL }}"
- name: Upload results artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: vegeta-results
path: results.json
Shell Cron with Threshold Check
#!/bin/bash
# run-vegeta-with-check.sh
# Exits non-zero if p99 > 500ms or error rate > 1%, so cron email captures failures
set -euo pipefail
vegeta attack -targets=targets.json -rate=100 -duration=60s \
| vegeta report -type=json > /tmp/vegeta-results.json
# Extract p99 latency (nanoseconds) and success ratio using jq
P99_NS=$(jq '.latencies["99th"]' /tmp/vegeta-results.json)
SUCCESS=$(jq '.success' /tmp/vegeta-results.json)
P99_MS=$(echo "scale=2; $P99_NS / 1000000" | bc)
ERROR_RATE=$(echo "scale=4; 1 - $SUCCESS" | bc)
echo "p99 latency: ${P99_MS}ms | error rate: ${ERROR_RATE}"
if (( $(echo "$P99_MS > 500" | bc -l) )); then
echo "FAIL: p99 latency ${P99_MS}ms exceeds 500ms threshold"
exit 1
fi
if (( $(echo "$ERROR_RATE > 0.01" | bc -l) )); then
echo "FAIL: error rate ${ERROR_RATE} exceeds 1% threshold"
exit 1
fi
# Post results and signal success
curl -s -X POST -H "Content-Type: application/json" \
-d "@/tmp/vegeta-results.json" \
"${VEGETA_RESULTS_ENDPOINT}"
curl -fsS "${VIGILMON_HEARTBEAT_URL}"
Step 6: Configure Alerting
In Vigilmon under Settings → Notifications, configure alert routing:
| Monitor | Trigger | Recommended Action | |---|---|---| | Heartbeat | Ping not received within grace period | Check cron logs; confirm server is running; inspect CI pipeline | | HTTP (results endpoint) | 503 or keyword absent | Review Vegeta output; check if p99 or error rate exceeded threshold |
Recommended thresholds:
- Heartbeat grace period: 30 minutes beyond your expected attack duration
- Confirmation period: 1 failure (a missing load test run is unambiguous signal)
- Recovery notification: Enable so your team knows when tests are passing again
Common Vegeta Monitoring Failure Modes
| Scenario | What Vigilmon Catches | |---|---| | Cron job not running (machine rebooted) | Heartbeat goes silent | | Target service is down during attack | Attack exits with high error rate; results endpoint returns 503 | | p99 latency regression after deploy | Results endpoint returns 503 with p99 above threshold | | Results server not running | HTTP monitor returns connection refused | | Vegeta binary not found in PATH | Script fails before heartbeat ping; heartbeat fires | | CI runner quota exhausted | Heartbeat goes silent |
Vegeta produces precise, reproducible HTTP load metrics but provides no built-in alerting for when tests silently stop running or when results cross your performance baselines. Vigilmon's heartbeat and HTTP monitors close this gap — you'll know within your configured interval whether your scheduled attacks are running and staying within acceptable latency bounds.
Get started free at vigilmon.online — your Vegeta pipeline monitor is running in under 5 minutes.