PerfLoad No script HTTP load testing

PerfLoad / CI/CD load testing

CI/CD Load Testing: Replay a Baseline Test on Every Deploy

A load test you only run by hand tends to get run before big launches and not much otherwise. PerfLoad's runner exposes a REST API, so a pipeline can replay a test you've already tuned in the UI using only curl and jq: no extra tooling, no test code in the repository.

Automate Your Load Tests →

You need a PerfLoad runner your pipeline can reach. The self-hosted guide covers where to run it.

How the baseline-run model works

Instead of defining the test in pipeline YAML, you define it once in PerfLoad and let the pipeline reference it by Run ID. That saved run is the baseline: its configuration is what every later run starts from.

  1. Configure a test in PerfLoad — request, headers, variables, users, and duration.
  2. Save the run. Running it once stores it in the run history.
  3. Store the Run ID in CI/CD as a variable, for example BASELINE_RUN_ID.
  4. Retrieve the configuration from the runner API with GET /runs/:id; the test settings are under .config.
  5. Override parameters when needed — a smaller run for every pull request, a larger one before a release.
  6. Execute a new run by posting that configuration to POST /runs.
  7. Poll for completion. A run moves through created, running, and ends as finished, failed, or stopped.
  8. Retrieve the metrics from GET /runs/:id/summary: p95, p99, requests per second, and error rate.

The script

Here is the whole flow in shell. It assumes BASELINE_RUN_ID is set in your CI environment. The additions to the runner's documented example are set -eo pipefail, so an unreachable runner fails the job instead of polling forever, and a guard that fails it when the run doesn't finish:

load-test.sh
set -eo pipefail

RUNNER=http://my-runner:3000

# 1. Fetch config from the baseline run, override VUs
CONFIG=$(curl -sf "$RUNNER/runs/$BASELINE_RUN_ID" | jq '.config | .users = 50')

# 2. Start a new run
NEW_ID=$(curl -sf -X POST "$RUNNER/runs" \
  -H "Content-Type: application/json" \
  -d "$CONFIG" | jq -r '.id')

echo "Run ID: $NEW_ID"

# 3. Poll until the run reaches a terminal status
while true; do
  STATUS=$(curl -sf "$RUNNER/runs/$NEW_ID/status" | jq -r '.status')
  echo "Status: $STATUS"
  [ "$STATUS" = "finished" ] || [ "$STATUS" = "failed" ] || [ "$STATUS" = "stopped" ] && break
  sleep 2
done

# Don't read metrics from a run that didn't finish
[ "$STATUS" = "finished" ] || { echo "Run ended as $STATUS"; exit 1; }

# 4. Print results
curl -sf "$RUNNER/runs/$NEW_ID/summary" | jq '{
  totalRequests: .metrics.http_reqs.values.count,
  rps:           .metrics.http_reqs.values.rate,
  latencyAvg:    .metrics.http_req_duration.values.avg,
  latencyP95:    .metrics.http_req_duration.values["p(95)"],
  latencyP99:    .metrics.http_req_duration.values["p(99)"],
  errorRate:     .metrics.http_req_failed.values.rate
}'

Override parameters per pipeline stage

Because the config is just JSON, jq can change any field before you post it. A quick smoke test on each change and a heavier run on release use the same baseline:

# Smoke test
CONFIG=$(curl -s $RUNNER/runs/$BASELINE_RUN_ID | jq '.config | .users = 5 | .duration = "30s"')

# Load test
CONFIG=$(curl -s $RUNNER/runs/$BASELINE_RUN_ID | jq '.config | .users = 100 | .duration = "5m"')

Why persisted run history matters here

A baseline Run ID is only useful if it still exists next month. When the runner is started with a named volume (-v perfload-runs:/app/runs), run history lives outside the container, so replacing the container with a newer image doesn't lose your baseline Run IDs. On Kubernetes, the equivalent is a PersistentVolumeClaim mounted at /app/runs; without one, a pod restart clears the history and your stored Run ID stops resolving. The installation guide has both setups.

Practical notes for pipelines

FAQ

Where do I get the Run ID?

Run the test once in the Workbench or Dashboard and copy the ID from the run's entry in the Dashboard's history.

Does this fail the build when latency regresses?

Not by itself. The script reports metrics and fails if the run doesn't finish. To gate on latency, compare a value from the summary, such as p95, to a limit in a later step. The GitHub Actions example shows one way.

Can I use this outside GitHub Actions?

Yes. It is plain shell plus HTTP calls, so it works in any CI system that can run curl and jq.

Do I need to install anything in the pipeline?

Only curl and jq, which most CI images already include.

Related guides

Make load testing part of every deploy

Tune a test once in PerfLoad, then let the pipeline replay it.

Automate Your Load Tests →