Frontend Labs
β‹―

10. Report Generation & Visualization

10.1 vegeta β€” text, histogram, and interactive HTML plots (built-in)

vegeta attack -targets=targets.txt -rate=500 -duration=60s | tee results.bin | vegeta report

vegeta report -type=json results.bin > report.json
vegeta plot results.bin > plot.html    # self-contained interactive latency-over-time HTML

10.2 k6 β€” JSON/InfluxDB output + Grafana dashboards, or a static HTML summary

For a single static report:

k6 run --out json=results.json flash_sale.js
// add to flash_sale.js for an automatic HTML summary at the end of the run
import { htmlReport } from "https://raw.githubusercontent.com/benc-uk/k6-reporter/main/dist/bundle.js";

export function handleSummary(data) {
  return { "summary.html": htmlReport(data) };
}

For live dashboards during long-running tests, stream metrics to Prometheus/ InfluxDB and visualize in Grafana:

k6 run --out experimental-prometheus-rw flash_sale.js
# then point a Grafana dashboard (k6's official "k6 Prometheus" dashboard ID: 19665)
# at the same Prometheus instance for real-time percentile graphs during the run

10.3 Locust β€” built-in HTML + CSV, or programmatic access

locust -f locustfile.py --headless --users 5000 --spawn-rate 200 --run-time 5m \
  --host http://localhost:8080 --html report.html \
  --csv results   # produces results_stats.csv, results_stats_history.csv, results_failures.csv

results_stats_history.csv has a time-series row per reporting interval β€” feed this into the Python report generator below for custom charts Locust's built-in HTML doesn't provide (e.g., overlaying multiple test runs).

10.4 JMeter β€” HTML Dashboard Report

jmeter -n -t flash_sale.jmx -l results.jtl -e -o report_dir/
# report_dir/index.html: response-time percentiles over time, throughput,
# error rate, and APDEX score out of the box

10.5 Custom Python report generator β€” merge multiple tools' raw output into one chart

Useful when comparing runs across tools (e.g., a baseline wrk run vs. a k6 run with tc netem network shaping applied), or when you need a report format your tooling doesn't natively produce (e.g., attaching a chart to a PR comment).

# generate_report.py
import json
import csv
import matplotlib.pyplot as plt

def load_vegeta_json(path):
    with open(path) as f:
        data = json.load(f)
    return {
        "p50": data["latencies"]["50th"] / 1e6,   # ns -> ms
        "p95": data["latencies"]["95th"] / 1e6,
        "p99": data["latencies"]["99th"] / 1e6,
        "throughput": data["throughput"],
        "success_ratio": data["success"],
    }

def load_locust_history_csv(path):
    timestamps, p95s, rps = [], [], []
    with open(path) as f:
        for row in csv.DictReader(f):
            timestamps.append(float(row["Timestamp"]))
            p95s.append(float(row["95%"]))
            rps.append(float(row["Requests/s"]))
    return timestamps, p95s, rps

def render_comparison_chart(runs: dict, out_path="report.png"):
    """runs: {"label": {"p50":.., "p95":.., "p99":.., "throughput":..}, ...}"""
    labels = list(runs.keys())
    p50s = [runs[l]["p50"] for l in labels]
    p95s = [runs[l]["p95"] for l in labels]
    p99s = [runs[l]["p99"] for l in labels]

    x = range(len(labels))
    width = 0.25
    fig, ax1 = plt.subplots(figsize=(10, 5))

    ax1.bar([i - width for i in x], p50s, width, label="p50")
    ax1.bar(x, p95s, width, label="p95")
    ax1.bar([i + width for i in x], p99s, width, label="p99")
    ax1.set_xticks(list(x))
    ax1.set_xticklabels(labels, rotation=20, ha="right")
    ax1.set_ylabel("Latency (ms)")
    ax1.set_title("Latency percentiles by test run")
    ax1.legend()

    plt.tight_layout()
    plt.savefig(out_path, dpi=150)
    print(f"wrote {out_path}")

if __name__ == "__main__":
    runs = {
        "baseline (no shaping)": load_vegeta_json("baseline.json"),
        "with tc netem 4G profile": load_vegeta_json("throttled.json"),
    }
    render_comparison_chart(runs)
pip install matplotlib
python generate_report.py

10.6 What a good report includes, regardless of tool

  1. Test parameters: tool, version, target rate/concurrency, duration, ramp shape, environment (staging vs prod-like), network conditions applied.
  2. Headline SLI numbers: p50/p95/p99/p99.9, error rate, throughput achieved vs. target β€” and whether thresholds/SLAs passed or failed (pass/fail, not just numbers).
  3. Time-series graphs, not just aggregate numbers β€” a flat p99 line across the whole run reads very differently from one that spikes only during the ramp phase.
  4. Resource utilization overlay: CPU/memory/DB connections/GC pauses on the server during the same window (Grafana dashboard alongside the load tool's output) β€” without this, you can see that latency degraded but not why.
  5. Comparison to the previous baseline run β€” a single run's numbers are much less useful than a trend; store historical JSON/CSV output in version control or a metrics store to diff against.

Generated from real-life-scale-guide.md Β· 8 Oct 2026 16:36