apiVersion: v1 kind: ConfigMap metadata: name: k6-step-script namespace: loadtest data: step.js: | // Step-load (staircase) test. Hits a collection of endpoints, stepping // the request rate up over time, tagging every request with its step and // endpoint so latency can be sliced either way in Grafana. // // Env: // BASE_URL - target base URL // ENDPOINTS_JSON - [{"name":"list","method":"GET","path":"/x","weight":2}, ...] // START_RPS - rate of step 1 // STEP_RPS - rate added each step // STEP_DURATION - hold time per step, e.g. "2m" // STEPS - number of steps import http from 'k6/http'; import { check } from 'k6'; import exec from 'k6/execution'; const BASE = __ENV.BASE_URL; let endpoints; try { endpoints = JSON.parse(__ENV.ENDPOINTS_JSON); } catch (e) { throw new Error('ENDPOINTS_JSON is not valid JSON: ' + e.message); } // expand weights const pool = []; for (const ep of endpoints) { for (let i = 0; i < (ep.weight || 1); i++) pool.push(ep); } const startRps = parseInt(__ENV.START_RPS || '50'); const stepRps = parseInt(__ENV.STEP_RPS || '50'); const stepDurMs = ms(__ENV.STEP_DURATION || '2m'); const steps = parseInt(__ENV.STEPS || '5'); const rampMs = 5000; // Build stage list and per-step [startMs, endMs, label] windows. // Each step: 5s quick ramp to the rate, then a hold. const stages = []; const windows = []; let clock = 0; for (let i = 0; i < steps; i++) { const rate = startRps + i * stepRps; stages.push({ duration: ms2str(rampMs), target: rate }); clock += rampMs; const wStart = clock; stages.push({ duration: ms2str(stepDurMs), target: rate }); clock += stepDurMs; windows.push([wStart, clock, `step${i + 1} @ ${rate} rps`]); } const totalMs = clock; const maxRate = startRps + (steps - 1) * stepRps; const vus = Math.max(10, Math.ceil(maxRate * 2)); function ms(s) { const m = /^(\d+)(ms|s|m)$/.exec(String(s)); if (!m) throw new Error('bad duration: ' + s); return +m[1] * ({ ms: 1, s: 1000, m: 60000 })[m[2]]; } function ms2str(n) { return n + 'ms'; } export const options = { discardResponseBodies: true, scenarios: { staircase: { executor: 'ramping-arrival-rate', startRate: 0, preAllocatedVUs: vus, maxVUs: vus, stages: stages, }, }, thresholds: { http_req_failed: ['rate<0.05'] }, }; export default function () { const elapsed = exec.scenario.progress * totalMs; let step = 'ramp-up'; for (const [s, e, label] of windows) { if (elapsed >= s && elapsed < e) { step = label; break; } } const ep = pool[exec.scenario.iterationInTest % pool.length]; const url = BASE + ep.path; const params = { tags: { name: ep.name, step: step }, timeout: '15s' }; const res = (ep.method || 'GET') === 'POST' ? http.post(url, ep.body || '', params) : http.get(url, params); check(res, { ok: (r) => r.status < 300 }); }