Skip to content

Instantly share code, notes, and snippets.

@jwcastillo
Created June 13, 2026 04:05
Show Gist options
  • Select an option

  • Save jwcastillo/66d5569a2d56c5193973595f595a96b4 to your computer and use it in GitHub Desktop.

Select an option

Save jwcastillo/66d5569a2d56c5193973595f595a96b4 to your computer and use it in GitHub Desktop.
k6 scripting recipes: regex URL generation, parallel request bursts, and DNS/host override

k6 scripting recipes

Three small, self-contained k6 scripts for common load-testing needs that are solved by scripting rather than configuration. Each runs against https://test.k6.io by default; override the target with TARGET_URL.

Recipes

regex-urls.js — generate URLs from a pattern

Generates a request path from a regex-like pattern on each iteration, so a single VU exercises many distinct URLs.

URL_PATTERN='/api/v(1|2)/items/[a-f0-9]{8}' k6 run regex-urls.js

Supports character classes ([a-z0-9]), \d/\w, quantifiers ({n}, {n,m}, ?, +, *), and simple alternation ((a|b)). It is a small expander, not a full regex engine — nested groups/classes and unescaped . (treated as a literal) are out of scope. For full fidelity, bundle randexp.js with the k6 bundler.

burst.js — bursts of parallel requests

Fires a fixed-size burst of parallel requests, measures the batch duration, then pauses before the next burst.

BURST_SIZE=50 BURST_DELAY=2 BURSTS=30 k6 run burst.js

Each iteration sends BURST_SIZE requests via http.batch() and records a custom burst_batch_duration trend and burst_requests counter.

dns-override.js — pin a hostname to an IP

Maps a hostname to a specific IP via k6's native hosts option — useful for hitting one backend behind a CDN/load balancer or testing a node before it is in rotation.

OVERRIDE_HOST=test.k6.io OVERRIDE_IP=203.0.113.10 k6 run dns-override.js

The IP is supplied via env var so the script never ships a stale hardcoded address. With no OVERRIDE_IP, hosts is empty and k6 resolves normally.

Two more, inline

URLs / bodies from a file (round-robin):

import http from 'k6/http';
import { SharedArray } from 'k6/data';
import exec from 'k6/execution';

const urls = new SharedArray('urls', () =>
  open('./urls.txt').split('\n').filter(Boolean)
);

export default function () {
  http.get(urls[exec.scenario.iterationInTest % urls.length]);
}

Prometheus remote write (a flag, not a script):

K6_PROMETHEUS_RW_SERVER_URL=http://localhost:9090/api/v1/write \
  k6 run --out experimental-prometheus-rw script.js
import http from 'k6/http';
import { sleep } from 'k6';
import { Counter, Trend } from 'k6/metrics';
// Recipe: send fixed-size bursts of parallel requests with a pause between them.
//
// BURST_SIZE=50 BURST_DELAY=2 BURSTS=30 k6 run burst.js
//
// Each iteration fires BURST_SIZE requests in parallel via http.batch(), records
// how long the batch took, then sleeps BURST_DELAY seconds before the next burst.
const BURST_SIZE = parseInt(__ENV.BURST_SIZE || '20', 10);
const BURST_DELAY = parseFloat(__ENV.BURST_DELAY || '1');
const BURSTS = parseInt(__ENV.BURSTS || '20', 10);
const BASE = __ENV.TARGET_URL || 'https://test.k6.io';
const burstReqs = new Counter('burst_requests');
const burstLatency = new Trend('burst_batch_duration', true);
export const options = {
scenarios: {
burst: {
executor: 'per-vu-iterations',
vus: 1,
iterations: BURSTS, // each iteration = one burst
maxDuration: '30m',
},
},
summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(90)', 'p(95)', 'p(99)', 'p(99.9)'],
};
export default function () {
const requests = [];
for (let k = 0; k < BURST_SIZE; k++) {
requests.push(['GET', `${BASE}/`]);
}
const t0 = Date.now();
const responses = http.batch(requests); // fire all N in parallel
burstLatency.add(Date.now() - t0);
burstReqs.add(responses.length);
sleep(BURST_DELAY); // pause between bursts
}
import http from 'k6/http';
import { check } from 'k6';
// Recipe: pin a hostname to a specific IP (DNS / load-balancer override).
//
// Useful for hitting one backend behind a CDN or load balancer, testing a new
// node before it is in rotation, or bypassing DNS entirely.
//
// Set the IP via env var so the script never ships a stale, hardcoded address:
// OVERRIDE_HOST=test.k6.io OVERRIDE_IP=203.0.113.10 k6 run dns-override.js
//
// With no OVERRIDE_IP set, `hosts` is empty and k6 resolves normally — so the
// script still runs instead of hanging against a dead address.
const HOST = __ENV.OVERRIDE_HOST || 'test.k6.io';
const IP = __ENV.OVERRIDE_IP; // e.g. 203.0.113.10 (optionally host:ip:port)
const BASE = __ENV.TARGET_URL || `https://${HOST}`;
export const options = {
vus: 10,
duration: '30s',
summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(90)', 'p(95)', 'p(99)', 'p(99.9)'],
// hosts maps a name (optionally name:port) to an IP (optionally ip:port).
hosts: IP ? { [HOST]: IP } : {},
};
export default function () {
const res = http.get(`${BASE}/`);
check(res, {
'status is 200': (r) => r.status === 200,
'responded': (r) => r.status > 0,
});
}
// Recipe: generate request paths from a regex-like pattern, one per iteration.
//
// k6 run regex-urls.js
// URL_PATTERN='/users/[0-9]{1,6}' k6 run regex-urls.js
//
// The generator supports the subset most used in load testing: character
// classes ([a-z0-9]), escapes \d \w, quantifiers {n} {n,m} ? + *, and simple
// alternation (a|b). For full regex fidelity you can bundle randexp.js with the
// k6 bundler, but this covers the common cases.
//
// NOTE: in regex `?` `+` `*` are quantifiers. For a LITERAL `?` in the URL (the
// start of a query string) escape it: `\?`. An unescaped `.` is treated as a
// literal dot, and nested groups/classes are out of scope.
import http from 'k6/http';
import { check } from 'k6';
export const options = {
vus: 20,
duration: '30s',
summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(90)', 'p(95)', 'p(99)', 'p(99.9)'],
};
const BASE = __ENV.TARGET_URL || 'https://test.k6.io';
const PATTERN = __ENV.URL_PATTERN || '/news/[a-z]{4,8}\\.php';
function randInt(a, b) { return Math.floor(Math.random() * (b - a + 1)) + a; }
function pick(arr) { return arr[randInt(0, arr.length - 1)]; }
function genFromPattern(pattern) {
let i = 0;
function expandClass(body) {
const chars = [];
let j = 0;
while (j < body.length) {
if (body[j] === '\\') {
const e = body[j + 1];
if (e === 'd') { for (let c = 48; c <= 57; c++) chars.push(String.fromCharCode(c)); }
else if (e === 'w') {
for (let c = 48; c <= 57; c++) chars.push(String.fromCharCode(c));
for (let c = 97; c <= 122; c++) chars.push(String.fromCharCode(c));
for (let c = 65; c <= 90; c++) chars.push(String.fromCharCode(c));
chars.push('_');
} else chars.push(e);
j += 2;
} else if (body[j + 1] === '-' && j + 2 < body.length) {
for (let c = body.charCodeAt(j); c <= body.charCodeAt(j + 2); c++) chars.push(String.fromCharCode(c));
j += 3;
} else { chars.push(body[j]); j++; }
}
return chars;
}
function parseQuant() {
if (pattern[i] === '{') {
const close = pattern.indexOf('}', i);
const spec = pattern.slice(i + 1, close);
i = close + 1;
const parts = spec.split(',');
const min = parseInt(parts[0], 10);
const max = parts.length > 1 ? parseInt(parts[1], 10) : min;
return randInt(min, max);
}
if (pattern[i] === '?') { i++; return randInt(0, 1); }
if (pattern[i] === '+') { i++; return randInt(1, 5); }
if (pattern[i] === '*') { i++; return randInt(0, 5); }
return 1;
}
let out = '';
while (i < pattern.length) {
if (pattern[i] === '[') {
const close = pattern.indexOf(']', i);
const chars = expandClass(pattern.slice(i + 1, close));
i = close + 1;
const n = parseQuant();
for (let k = 0; k < n; k++) out += pick(chars);
} else if (pattern[i] === '\\') {
const e = pattern[i + 1];
i += 2;
let chars;
if (e === 'd') chars = '0123456789'.split('');
else if (e === 'w') chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'.split('');
else chars = [e];
const n = parseQuant();
for (let k = 0; k < n; k++) out += pick(chars);
} else if (pattern[i] === '(') {
const close = pattern.indexOf(')', i);
const alts = pattern.slice(i + 1, close).split('|');
i = close + 1;
const chosen = pick(alts);
const n = parseQuant();
for (let k = 0; k < n; k++) out += chosen;
} else {
const ch = pattern[i];
i++;
const n = parseQuant();
for (let k = 0; k < n; k++) out += ch;
}
}
return out;
}
export default function () {
const path = genFromPattern(PATTERN);
const res = http.get(`${BASE}${path}`);
check(res, { 'responded': (r) => r.status > 0 });
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment