tutorial

Monitoring Vegeta HTTP Load Testing Pipelines with Vigilmon: Heartbeat, Results, and Alerting

How to monitor Vegeta HTTP load testing pipelines with Vigilmon — heartbeat monitoring for scheduled attacks, HTTP monitors for a results endpoint, and alerting when load tests exceed latency thresholds.

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@latest or 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

  1. Log in to VigilmonAdd Monitor → Heartbeat.
  2. Name: vegeta-nightly-attack.
  3. Expected interval: Match your schedule (e.g., 86400 seconds for daily).
  4. Grace period: 30 minutes.
  5. 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

  1. Log in to VigilmonAdd Monitor → HTTP.
  2. URL: http://your-server.example.com:3011/health/vegeta.
  3. Check interval: 60 minutes.
  4. Response timeout: 10 seconds.
  5. Expected status: 200.
  6. Keyword: "status":"ok".
  7. 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.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →