-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathload-testing-sample.ts
More file actions
167 lines (147 loc) · 6.23 KB
/
Copy pathload-testing-sample.ts
File metadata and controls
167 lines (147 loc) · 6.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// Sample load testing script (k6 v2.0+)
//
// Usage:
// 1. Adjust the constants in the "Configuration" section below to match the real traffic.
// 2. Run with `k6 run --env SCENARIO=<smoke|average|stress|spike> load-testing-sample.ts`.
//
// References:
// - Scenarios: https://grafana.com/docs/k6/latest/using-k6/scenarios/
// - Executors: https://grafana.com/docs/k6/latest/using-k6/scenarios/executors/
// - Arrival rate VU alloc: https://grafana.com/docs/k6/latest/using-k6/scenarios/concepts/arrival-rate-vu-allocation/
// - Thresholds: https://grafana.com/docs/k6/latest/using-k6/thresholds/
// - Checks: https://grafana.com/docs/k6/latest/using-k6/checks/
// - TypeScript support: https://grafana.com/docs/k6/latest/using-k6/javascript-typescript-compatibility-mode/
import http from "k6/http";
import { check } from "k6";
import type { Options } from "k6/options";
// ============================================================
// 1) Configuration: edit these values to match real traffic
// ============================================================
// Base URL of the target system
const BASE_URL = "https://example.com";
// Endpoints called in a single iteration (tags isolate per-endpoint metrics)
interface Endpoint {
readonly name: string;
readonly path: string;
}
const ENDPOINTS: readonly Endpoint[] = [
{ name: "top", path: "/" },
{ name: "list", path: "/items" },
{ name: "detail", path: "/items/1" },
];
// Scenario names (single source of truth for both runtime validation and TS type)
const SCENARIO_NAMES = ["smoke", "average", "stress", "spike"] as const;
type ScenarioName = (typeof SCENARIO_NAMES)[number];
// Target req/s (total throughput) — fill in from your traffic measurement
const TARGET_RPS: Record<ScenarioName, number> = {
smoke: 3, // smoke test
average: 50, // normal traffic
stress: 150, // daily peak x 1.5
spike: 300, // observed spike value
};
// Values used to estimate VUs.
// The formula `rps * avgResp * safety` is the official k6 estimation,
// which is mathematically equivalent to Little's Law (L = λ * W).
// https://grafana.com/docs/k6/latest/using-k6/scenarios/concepts/arrival-rate-vu-allocation/
const AVG_RESPONSE_TIME_SEC = 0.2; // average response time you measured
const SAFETY_FACTOR = 2.0; // 1.5 - 2.0 (buffer for latency variance)
// ============================================================
// 2) Scenario selection
// `k6 run --env SCENARIO=stress script.ts` (defaults to smoke)
// ============================================================
const SCENARIO: ScenarioName = ((): ScenarioName => {
const v = __ENV.SCENARIO || "smoke";
if ((SCENARIO_NAMES as readonly string[]).includes(v)) {
return v as ScenarioName;
}
throw new Error(
`Invalid SCENARIO: "${v}". Expected one of: ${SCENARIO_NAMES.join(", ")}`,
);
})();
// Each iteration sends ENDPOINTS.length requests, so the executor's `rate`
// (= iterations/s) must be "target req/s / number of endpoints".
const iterRate = (rps: number): number =>
Math.max(1, Math.ceil(rps / ENDPOINTS.length));
// k6 official recommends avoiding `maxVUs` in most cases because dynamic VU
// allocation during a test can overload the load generator and skew results.
// Pre-allocate enough VUs up front instead.
// https://grafana.com/docs/k6/latest/using-k6/scenarios/concepts/arrival-rate-vu-allocation/
const allocVUs = (rps: number): number =>
Math.max(2, Math.ceil(rps * AVG_RESPONSE_TIME_SEC * SAFETY_FACTOR));
const scenarios: Required<Options>["scenarios"] = {
// constant-arrival-rate:
// https://grafana.com/docs/k6/latest/using-k6/scenarios/executors/constant-arrival-rate/
smoke: {
executor: "constant-arrival-rate",
rate: iterRate(TARGET_RPS.smoke),
timeUnit: "1s",
duration: "1m",
preAllocatedVUs: allocVUs(TARGET_RPS.smoke),
},
average: {
executor: "constant-arrival-rate",
rate: iterRate(TARGET_RPS.average),
timeUnit: "1s",
duration: "15m",
preAllocatedVUs: allocVUs(TARGET_RPS.average),
},
// ramping-arrival-rate:
// https://grafana.com/docs/k6/latest/using-k6/scenarios/executors/ramping-arrival-rate/
stress: {
executor: "ramping-arrival-rate",
startRate: iterRate(TARGET_RPS.average),
timeUnit: "1s",
preAllocatedVUs: allocVUs(TARGET_RPS.stress),
stages: [
{ duration: "5m", target: iterRate(TARGET_RPS.stress) }, // ramp up
{ duration: "10m", target: iterRate(TARGET_RPS.stress) }, // hold
{ duration: "2m", target: 0 }, // ramp down
],
},
spike: {
executor: "ramping-arrival-rate",
startRate: iterRate(TARGET_RPS.average),
timeUnit: "1s",
preAllocatedVUs: allocVUs(TARGET_RPS.spike),
stages: [
{ duration: "30s", target: iterRate(TARGET_RPS.spike) }, // sharp ramp up
{ duration: "1m", target: iterRate(TARGET_RPS.spike) }, // hold
{ duration: "30s", target: iterRate(TARGET_RPS.average) }, // sharp ramp down
],
},
};
// ============================================================
// 3) k6 options
// https://grafana.com/docs/k6/latest/using-k6/k6-options/
// ============================================================
export const options: Options = {
// Run only the selected scenario.
scenarios: { [SCENARIO]: scenarios[SCENARIO] },
// Thresholds for SLO — adjust to match your service.
// https://grafana.com/docs/k6/latest/using-k6/thresholds/
thresholds: {
http_req_failed: ["rate<0.01"], // error rate < 1%
http_req_duration: ["p(90)<1000", "p(99)<2000"], // overall response time
// Per-endpoint thresholds (isolated by tags)
"http_req_duration{endpoint:top}": ["p(90)<500"],
"http_req_duration{endpoint:list}": ["p(90)<800"],
"http_req_duration{endpoint:detail}": ["p(90)<800"],
},
// Extra tags used for result aggregation.
tags: {
scenario: SCENARIO,
},
};
// ============================================================
// 4) Iteration body executed by each VU
// ============================================================
export default function (): void {
for (const { name, path } of ENDPOINTS) {
const res = http.get(`${BASE_URL}${path}`, {
tags: { endpoint: name },
});
check(res, {
[`${name}: status is 2xx`]: (r) => r.status >= 200 && r.status < 300,
});
}
}