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
- Test parameters: tool, version, target rate/concurrency, duration, ramp shape, environment (staging vs prod-like), network conditions applied.
- 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).
- 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.
- 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.
- 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.