add reservations to the model
This commit is contained in:
@@ -4,43 +4,39 @@ import { Counter, Trend } from "k6/metrics";
|
||||
|
||||
// ---------------- Configuration ----------------
|
||||
export const options = {
|
||||
// Adjust vus/duration for your environment
|
||||
scenarios: {
|
||||
steady_mutations: {
|
||||
executor: "constant-vus",
|
||||
vus: __ENV.VUS ? parseInt(__ENV.VUS, 10) : 20,
|
||||
duration: __ENV.DURATION || "5m",
|
||||
gracefulStop: "30s",
|
||||
},
|
||||
ramp_up: {
|
||||
executor: "ramping-vus",
|
||||
startVUs: 0,
|
||||
stages: [
|
||||
{
|
||||
duration: "1m",
|
||||
target: __ENV.RAMP_TARGET
|
||||
? parseInt(__ENV.RAMP_TARGET, 10)
|
||||
: 50,
|
||||
},
|
||||
{
|
||||
duration: "1m",
|
||||
target: __ENV.RAMP_TARGET
|
||||
? parseInt(__ENV.RAMP_TARGET, 10)
|
||||
: 50,
|
||||
},
|
||||
{ duration: "1m", target: 0 },
|
||||
],
|
||||
gracefulStop: "30s",
|
||||
startTime: "30s",
|
||||
},
|
||||
// Adjust vus/duration for your environment
|
||||
scenarios: {
|
||||
steady_mutations: {
|
||||
executor: "constant-vus",
|
||||
vus: __ENV.VUS ? parseInt(__ENV.VUS, 10) : 20,
|
||||
duration: __ENV.DURATION || "5m",
|
||||
gracefulStop: "30s",
|
||||
},
|
||||
thresholds: {
|
||||
http_req_failed: ["rate<0.02"], // < 2% failures
|
||||
http_req_duration: ["p(90)<800", "p(99)<1500"], // latency SLO
|
||||
"cart_add_item_duration{op:add}": ["p(90)<800"],
|
||||
"cart_fetch_duration{op:get}": ["p(90)<600"],
|
||||
ramp_up: {
|
||||
executor: "ramping-vus",
|
||||
startVUs: 0,
|
||||
stages: [
|
||||
{
|
||||
duration: "1m",
|
||||
target: __ENV.RAMP_TARGET ? parseInt(__ENV.RAMP_TARGET, 10) : 50,
|
||||
},
|
||||
{
|
||||
duration: "1m",
|
||||
target: __ENV.RAMP_TARGET ? parseInt(__ENV.RAMP_TARGET, 10) : 50,
|
||||
},
|
||||
{ duration: "1m", target: 0 },
|
||||
],
|
||||
gracefulStop: "30s",
|
||||
startTime: "30s",
|
||||
},
|
||||
summaryTrendStats: ["avg", "min", "med", "max", "p(90)", "p(95)", "p(99)"],
|
||||
},
|
||||
thresholds: {
|
||||
http_req_failed: ["rate<0.02"], // < 2% failures
|
||||
http_req_duration: ["p(90)<800", "p(99)<1500"], // latency SLO
|
||||
"cart_add_item_duration{op:add}": ["p(90)<800"],
|
||||
"cart_fetch_duration{op:get}": ["p(90)<600"],
|
||||
},
|
||||
summaryTrendStats: ["avg", "min", "med", "max", "p(90)", "p(95)", "p(99)"],
|
||||
};
|
||||
|
||||
// ---------------- Metrics ----------------
|
||||
@@ -52,197 +48,197 @@ const checkoutCounter = new Counter("cart_checkout_calls");
|
||||
|
||||
// ---------------- SKUs ----------------
|
||||
const SKUS = [
|
||||
"778290",
|
||||
"778345",
|
||||
"778317",
|
||||
"778277",
|
||||
"778267",
|
||||
"778376",
|
||||
"778244",
|
||||
"778384",
|
||||
"778365",
|
||||
"778377",
|
||||
"778255",
|
||||
"778286",
|
||||
"778246",
|
||||
"778270",
|
||||
"778266",
|
||||
"778285",
|
||||
"778329",
|
||||
"778425",
|
||||
"778407",
|
||||
"778418",
|
||||
"778430",
|
||||
"778469",
|
||||
"778358",
|
||||
"778351",
|
||||
"778319",
|
||||
"778307",
|
||||
"778278",
|
||||
"778251",
|
||||
"778253",
|
||||
"778261",
|
||||
"778263",
|
||||
"778273",
|
||||
"778281",
|
||||
"778294",
|
||||
"778297",
|
||||
"778302",
|
||||
"778290",
|
||||
"778345",
|
||||
"778317",
|
||||
"778277",
|
||||
"778267",
|
||||
"778376",
|
||||
"778244",
|
||||
"778384",
|
||||
"778365",
|
||||
"778377",
|
||||
"778255",
|
||||
"778286",
|
||||
"778246",
|
||||
"778270",
|
||||
"778266",
|
||||
"778285",
|
||||
"778329",
|
||||
"778425",
|
||||
"778407",
|
||||
"778418",
|
||||
"778430",
|
||||
"778469",
|
||||
"778358",
|
||||
"778351",
|
||||
"778319",
|
||||
"778307",
|
||||
"778278",
|
||||
"778251",
|
||||
"778253",
|
||||
"778261",
|
||||
"778263",
|
||||
"778273",
|
||||
"778281",
|
||||
"778294",
|
||||
"778297",
|
||||
"778302",
|
||||
];
|
||||
|
||||
// ---------------- Helpers ----------------
|
||||
function randomSku() {
|
||||
return SKUS[Math.floor(Math.random() * SKUS.length)];
|
||||
return SKUS[Math.floor(Math.random() * SKUS.length)];
|
||||
}
|
||||
function randomQty() {
|
||||
return 1 + Math.floor(Math.random() * 3); // 1..3
|
||||
return 1 + Math.floor(Math.random() * 3); // 1..3
|
||||
}
|
||||
function baseUrl() {
|
||||
const u = __ENV.BASE_URL || "http://localhost:8080/cart";
|
||||
// Allow user to pass either root host or full /cart path
|
||||
return u.endsWith("/cart") ? u : u.replace(/\/+$/, "") + "/cart";
|
||||
const u = __ENV.BASE_URL || "http://localhost:8080/cart";
|
||||
// Allow user to pass either root host or full /cart path
|
||||
return u.endsWith("/cart") ? u : u.replace(/\/+$/, "") + "/cart";
|
||||
}
|
||||
function extractCookie(res, name) {
|
||||
const cookies = res.cookies[name];
|
||||
if (!cookies || cookies.length === 0) return null;
|
||||
return cookies[0].value;
|
||||
const cookies = res.cookies[name];
|
||||
if (!cookies || cookies.length === 0) return null;
|
||||
return cookies[0].value;
|
||||
}
|
||||
function withCookies(headers, cookieJar) {
|
||||
if (!cookieJar || Object.keys(cookieJar).length === 0) return headers;
|
||||
const cookieStr = Object.entries(cookieJar)
|
||||
.map(([k, v]) => `${k}=${v}`)
|
||||
.join("; ");
|
||||
return { ...headers, Cookie: cookieStr };
|
||||
if (!cookieJar || Object.keys(cookieJar).length === 0) return headers;
|
||||
const cookieStr = Object.entries(cookieJar)
|
||||
.map(([k, v]) => `${k}=${v}`)
|
||||
.join("; ");
|
||||
return { ...headers, Cookie: cookieStr };
|
||||
}
|
||||
|
||||
// Maintain cart + owner cookies per VU
|
||||
let cartState = {
|
||||
cartid: null,
|
||||
cartowner: null,
|
||||
cartid: null,
|
||||
cartowner: null,
|
||||
};
|
||||
|
||||
// Refresh cookies from response
|
||||
function updateCookies(res) {
|
||||
const cid = extractCookie(res, "cartid");
|
||||
if (cid) cartState.cartid = cid;
|
||||
const owner = extractCookie(res, "cartowner");
|
||||
if (owner) cartState.cartowner = owner;
|
||||
const cid = extractCookie(res, "cartid");
|
||||
if (cid) cartState.cartid = cid;
|
||||
const owner = extractCookie(res, "cartowner");
|
||||
if (owner) cartState.cartowner = owner;
|
||||
}
|
||||
|
||||
// Build headers
|
||||
function headers() {
|
||||
const h = { "Content-Type": "application/json" };
|
||||
const jar = {};
|
||||
if (cartState.cartid) jar["cartid"] = cartState.cartid;
|
||||
if (cartState.cartowner) jar["cartowner"] = cartState.cartowner;
|
||||
return withCookies(h, jar);
|
||||
const h = { "Content-Type": "application/json" };
|
||||
const jar = {};
|
||||
if (cartState.cartid) jar["cartid"] = cartState.cartid;
|
||||
if (cartState.cartowner) jar["cartowner"] = cartState.cartowner;
|
||||
return withCookies(h, jar);
|
||||
}
|
||||
|
||||
// Ensure cart exists (GET /)
|
||||
function ensureCart() {
|
||||
if (cartState.cartid) return;
|
||||
const res = http.get(baseUrl() + "/", { headers: headers() });
|
||||
updateCookies(res);
|
||||
check(res, {
|
||||
"ensure cart status 200": (r) => r.status === 200,
|
||||
"ensure cart has id": () => !!cartState.cartid,
|
||||
});
|
||||
if (cartState.cartid) return;
|
||||
const res = http.get(baseUrl(), { headers: headers() });
|
||||
updateCookies(res);
|
||||
check(res, {
|
||||
"ensure cart status 200": (r) => r.status === 200,
|
||||
"ensure cart has id": () => !!cartState.cartid,
|
||||
});
|
||||
}
|
||||
|
||||
// Add random item
|
||||
function addRandomItem() {
|
||||
const payload = JSON.stringify({
|
||||
sku: randomSku(),
|
||||
quantity: randomQty(),
|
||||
country: "no",
|
||||
});
|
||||
const start = Date.now();
|
||||
const res = http.post(baseUrl(), payload, { headers: headers() });
|
||||
const dur = Date.now() - start;
|
||||
addItemTrend.add(dur, { op: "add" });
|
||||
if (res.status === 200) {
|
||||
addedItemsCounter.add(1);
|
||||
}
|
||||
updateCookies(res);
|
||||
check(res, {
|
||||
"add item status ok": (r) => r.status === 200,
|
||||
});
|
||||
const payload = JSON.stringify({
|
||||
sku: randomSku(),
|
||||
quantity: randomQty(),
|
||||
country: "no",
|
||||
});
|
||||
const start = Date.now();
|
||||
const res = http.post(baseUrl(), payload, { headers: headers() });
|
||||
const dur = Date.now() - start;
|
||||
addItemTrend.add(dur, { op: "add" });
|
||||
if (res.status === 200) {
|
||||
addedItemsCounter.add(1);
|
||||
}
|
||||
updateCookies(res);
|
||||
check(res, {
|
||||
"add item status ok": (r) => r.status === 200,
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch cart state
|
||||
function fetchCart() {
|
||||
const start = Date.now();
|
||||
const res = http.get(baseUrl() + "/", { headers: headers() });
|
||||
const dur = Date.now() - start;
|
||||
fetchTrend.add(dur, { op: "get" });
|
||||
updateCookies(res);
|
||||
check(res, { "fetch status ok": (r) => r.status === 200 });
|
||||
const start = Date.now();
|
||||
const res = http.get(baseUrl(), { headers: headers() });
|
||||
const dur = Date.now() - start;
|
||||
fetchTrend.add(dur, { op: "get" });
|
||||
updateCookies(res);
|
||||
check(res, { "fetch status ok": (r) => r.status === 200 });
|
||||
}
|
||||
|
||||
// Occasional checkout trigger
|
||||
function maybeCheckout() {
|
||||
if (!cartState.cartid) return;
|
||||
// // Small probability
|
||||
// if (Math.random() < 0.02) {
|
||||
// const start = Date.now();
|
||||
// const res = http.get(baseUrl() + "/checkout", { headers: headers() });
|
||||
// const dur = Date.now() - start;
|
||||
// checkoutTrend.add(dur, { op: "checkout" });
|
||||
// updateCookies(res);
|
||||
// if (res.status === 200) checkoutCounter.add(1);
|
||||
// check(res, { "checkout status ok": (r) => r.status === 200 });
|
||||
// }
|
||||
if (!cartState.cartid) return;
|
||||
// // Small probability
|
||||
// if (Math.random() < 0.02) {
|
||||
// const start = Date.now();
|
||||
// const res = http.get(baseUrl() + "/checkout", { headers: headers() });
|
||||
// const dur = Date.now() - start;
|
||||
// checkoutTrend.add(dur, { op: "checkout" });
|
||||
// updateCookies(res);
|
||||
// if (res.status === 200) checkoutCounter.add(1);
|
||||
// check(res, { "checkout status ok": (r) => r.status === 200 });
|
||||
// }
|
||||
}
|
||||
|
||||
// ---------------- k6 lifecycle ----------------
|
||||
export function setup() {
|
||||
// Provide SKU list length for summary
|
||||
return { skuCount: SKUS.length };
|
||||
// Provide SKU list length for summary
|
||||
return { skuCount: SKUS.length };
|
||||
}
|
||||
|
||||
export default function (data) {
|
||||
group("cart flow", () => {
|
||||
// Create or reuse cart
|
||||
ensureCart();
|
||||
group("cart flow", () => {
|
||||
// Create or reuse cart
|
||||
ensureCart();
|
||||
|
||||
// Random number of item mutations per iteration (1..5)
|
||||
const ops = 1 + Math.floor(Math.random() * 5);
|
||||
for (let i = 0; i < ops; i++) {
|
||||
addRandomItem();
|
||||
}
|
||||
// Random number of item mutations per iteration (1..5)
|
||||
const ops = 1 + Math.floor(Math.random() * 5);
|
||||
for (let i = 0; i < ops; i++) {
|
||||
addRandomItem();
|
||||
}
|
||||
|
||||
// Fetch state
|
||||
fetchCart();
|
||||
// Fetch state
|
||||
fetchCart();
|
||||
|
||||
// Optional checkout attempt
|
||||
maybeCheckout();
|
||||
});
|
||||
// Optional checkout attempt
|
||||
maybeCheckout();
|
||||
});
|
||||
|
||||
// Small think time
|
||||
sleep(Math.random() * 0.5);
|
||||
// Small think time
|
||||
sleep(Math.random() * 0.5);
|
||||
}
|
||||
|
||||
export function teardown(data) {
|
||||
// Optionally we could GET confirmation or clear cart cookie
|
||||
// Not implemented for load purpose.
|
||||
console.log(`Test complete. SKU count: ${data.skuCount}`);
|
||||
// Optionally we could GET confirmation or clear cart cookie
|
||||
// Not implemented for load purpose.
|
||||
console.log(`Test complete. SKU count: ${data.skuCount}`);
|
||||
}
|
||||
|
||||
// ---------------- Summary ----------------
|
||||
export function handleSummary(data) {
|
||||
return {
|
||||
stdout: JSON.stringify(
|
||||
{
|
||||
metrics: {
|
||||
mutations_avg: data.metrics.cart_add_item_duration?.avg,
|
||||
mutations_p95: data.metrics.cart_add_item_duration?.p(95),
|
||||
fetch_p95: data.metrics.cart_fetch_duration?.p(95),
|
||||
checkout_count: data.metrics.cart_checkout_calls?.count,
|
||||
},
|
||||
checks: data.root_checks,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
};
|
||||
return {
|
||||
stdout: JSON.stringify(
|
||||
{
|
||||
metrics: {
|
||||
mutations_avg: data.metrics.cart_add_item_duration?.avg,
|
||||
mutations_p95: data.metrics.cart_add_item_duration?.p(95),
|
||||
fetch_p95: data.metrics.cart_fetch_duration?.p(95),
|
||||
checkout_count: data.metrics.cart_checkout_calls?.count,
|
||||
},
|
||||
checks: data.root_checks,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user