122 lines
3.4 KiB
JavaScript
122 lines
3.4 KiB
JavaScript
import http from "k6/http";
|
|
import { check, sleep } from "k6";
|
|
import { Counter, Rate, Trend } from "k6/metrics";
|
|
|
|
const BASE_URL = (__ENV.BASE_URL || "http://host.docker.internal").replace(
|
|
/\/$/,
|
|
"",
|
|
);
|
|
const API_URL = `${BASE_URL}/api/v1`;
|
|
const FLAG_KEY = __ENV.FLAG_KEY || "";
|
|
const SUBJECT_PREFIX = __ENV.SUBJECT_PREFIX || "k6_subject";
|
|
const SUBJECT_COUNTRY = __ENV.SUBJECT_COUNTRY || "US";
|
|
const SUBJECT_POOL = Number(__ENV.SUBJECT_POOL || "20000");
|
|
const THINK_TIME_SECONDS = Number(__ENV.THINK_TIME_SECONDS || "0");
|
|
|
|
const START_RATE = Number(__ENV.START_RPS || "20");
|
|
const RAMP_UP_RATE = Number(__ENV.RAMP_UP_RPS || "200");
|
|
const HOLD_RATE = Number(__ENV.HOLD_RPS || "200");
|
|
const PRE_ALLOCATED_VUS = Number(__ENV.PRE_ALLOCATED_VUS || "100");
|
|
const MAX_VUS = Number(__ENV.MAX_VUS || "600");
|
|
|
|
const RAMP_UP_DURATION = __ENV.RAMP_UP_DURATION || "30s";
|
|
const HOLD_DURATION = __ENV.HOLD_DURATION || "2m";
|
|
const RAMP_DOWN_DURATION = __ENV.RAMP_DOWN_DURATION || "20s";
|
|
|
|
const THRESHOLD_ERROR_RATE = __ENV.THRESHOLD_ERROR_RATE || "0.01";
|
|
const THRESHOLD_P95_MS = __ENV.THRESHOLD_P95_MS || "250";
|
|
const THRESHOLD_P99_MS = __ENV.THRESHOLD_P99_MS || "500";
|
|
|
|
if (!FLAG_KEY) {
|
|
throw new Error("FLAG_KEY is required");
|
|
}
|
|
|
|
export const options = {
|
|
scenarios: {
|
|
decide_hot_path: {
|
|
executor: "ramping-arrival-rate",
|
|
startRate: START_RATE,
|
|
timeUnit: "1s",
|
|
preAllocatedVUs: PRE_ALLOCATED_VUS,
|
|
maxVUs: MAX_VUS,
|
|
stages: [
|
|
{ target: RAMP_UP_RATE, duration: RAMP_UP_DURATION },
|
|
{ target: HOLD_RATE, duration: HOLD_DURATION },
|
|
{ target: 0, duration: RAMP_DOWN_DURATION },
|
|
],
|
|
},
|
|
},
|
|
thresholds: {
|
|
http_req_failed: [`rate<${THRESHOLD_ERROR_RATE}`],
|
|
http_req_duration: [
|
|
`p(95)<${THRESHOLD_P95_MS}`,
|
|
`p(99)<${THRESHOLD_P99_MS}`,
|
|
],
|
|
decide_status_200_rate: ["rate>0.99"],
|
|
},
|
|
summaryTrendStats: [
|
|
"avg",
|
|
"min",
|
|
"med",
|
|
"p(90)",
|
|
"p(95)",
|
|
"p(99)",
|
|
"max",
|
|
],
|
|
};
|
|
|
|
const decideStatus200Rate = new Rate("decide_status_200_rate");
|
|
const decideAssignedRate = new Rate("decide_experiment_assigned_rate");
|
|
const decideRequests = new Counter("decide_requests_total");
|
|
const decideDuration = new Trend("decide_request_duration_ms", true);
|
|
|
|
function buildSubjectId() {
|
|
const idx = ((__ITER * 104729 + __VU * 8191) % SUBJECT_POOL) + 1;
|
|
return `${SUBJECT_PREFIX}_${idx}`;
|
|
}
|
|
|
|
function buildPayload() {
|
|
return JSON.stringify({
|
|
subject_id: buildSubjectId(),
|
|
subject_attributes: { country: SUBJECT_COUNTRY },
|
|
flags: [FLAG_KEY],
|
|
});
|
|
}
|
|
|
|
export default function () {
|
|
const response = http.post(`${API_URL}/decide`, buildPayload(), {
|
|
headers: { "Content-Type": "application/json" },
|
|
tags: { endpoint: "decide" },
|
|
});
|
|
|
|
decideRequests.add(1);
|
|
decideDuration.add(response.timings.duration);
|
|
decideStatus200Rate.add(response.status === 200);
|
|
|
|
let reason = "";
|
|
if (response.status === 200) {
|
|
const body = response.json();
|
|
if (body && body.decisions && body.decisions.length > 0) {
|
|
reason = String(body.decisions[0].reason || "");
|
|
}
|
|
}
|
|
decideAssignedRate.add(reason === "experiment_assigned");
|
|
|
|
check(response, {
|
|
"status is 200": (r) => r.status === 200,
|
|
"has one decision": (r) => {
|
|
const body = r.json();
|
|
return (
|
|
body !== null &&
|
|
typeof body === "object" &&
|
|
Array.isArray(body.decisions) &&
|
|
body.decisions.length === 1
|
|
);
|
|
},
|
|
});
|
|
|
|
if (THINK_TIME_SECONDS > 0) {
|
|
sleep(THINK_TIME_SECONDS);
|
|
}
|
|
}
|