184 lines
5.5 KiB
JavaScript
184 lines
5.5 KiB
JavaScript
// k6 load test simulating realistic e-commerce traffic for 5 shoppers.
|
|
//
|
|
// Each VU plays one shopper with a stable userId + anonymousId. Per iteration
|
|
// the shopper fires a random Segment-spec event from a small catalog
|
|
// (Product Viewed / Product Added / Order Completed), then "thinks" for a
|
|
// short while before the next action.
|
|
//
|
|
// Usage:
|
|
// brew install k6
|
|
// k6 run tests/k6/track.js
|
|
//
|
|
// Overrides:
|
|
// k6 run -e WRITE_KEY=xxx -e BASE=http://localhost:3049 \
|
|
// -e VUS=5 -e DURATION=2m tests/k6/track.js
|
|
|
|
import http from 'k6/http';
|
|
import { check, sleep } from 'k6';
|
|
import { randomItem, randomIntBetween } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js';
|
|
import encoding from 'k6/encoding';
|
|
|
|
const BASE = __ENV.BASE ?? 'http://localhost:3049';
|
|
const WRITE_KEY = __ENV.WRITE_KEY ?? 'cdp_dev_writekey_1234567890';
|
|
const VUS = parseInt(__ENV.VUS ?? '5', 10);
|
|
const DURATION = __ENV.DURATION ?? '1m';
|
|
|
|
const AUTH = 'Basic ' + encoding.b64encode(`${WRITE_KEY}:`);
|
|
|
|
export const options = {
|
|
scenarios: {
|
|
shoppers: {
|
|
executor: 'constant-vus',
|
|
vus: VUS,
|
|
duration: DURATION,
|
|
},
|
|
},
|
|
thresholds: {
|
|
http_req_failed: ['rate<0.01'],
|
|
http_req_duration: ['p(95)<500', 'p(99)<1500'],
|
|
checks: ['rate>0.99'],
|
|
},
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Fixtures
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const SHOPPERS = [
|
|
{ user_id: 'u_001', email: 'alice@example.com', plan: 'pro', country: 'US' },
|
|
{ user_id: 'u_002', email: 'bob@example.com', plan: 'free', country: 'VN' },
|
|
{ user_id: 'u_003', email: 'charlie@example.com', plan: 'pro', country: 'GB' },
|
|
{ user_id: 'u_004', email: 'dana@example.com', plan: 'free', country: 'SG' },
|
|
{ user_id: 'u_005', email: 'eric@example.com', plan: 'team', country: 'JP' },
|
|
];
|
|
|
|
const PRODUCTS = [
|
|
{ id: 'sku_alpha', name: 'Alpha Hoodie', category: 'apparel', brand: 'CDP', price: 49.0 },
|
|
{ id: 'sku_beta', name: 'Beta Mug', category: 'drinkware', brand: 'CDP', price: 12.5 },
|
|
{ id: 'sku_gamma', name: 'Gamma Backpack', category: 'bags', brand: 'CDP', price: 89.0 },
|
|
{ id: 'sku_delta', name: 'Delta Sneakers', category: 'footwear', brand: 'Athleta', price: 129.0 },
|
|
{ id: 'sku_eps', name: 'Epsilon Headset', category: 'electronics',brand: 'Sonix', price: 199.0 },
|
|
];
|
|
|
|
const EVENT_KINDS = ['Product Viewed', 'Product Added', 'Order Completed'];
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Per-iteration
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export default function () {
|
|
// Stable per-VU identity. __VU starts at 1.
|
|
const shopper = SHOPPERS[(__VU - 1) % SHOPPERS.length];
|
|
const anonymousId = `anon_${shopper.user_id}`;
|
|
|
|
const eventName = randomItem(EVENT_KINDS);
|
|
let properties;
|
|
switch (eventName) {
|
|
case 'Product Viewed':
|
|
properties = productProps(randomItem(PRODUCTS));
|
|
break;
|
|
case 'Product Added':
|
|
properties = {
|
|
...productProps(randomItem(PRODUCTS)),
|
|
quantity: randomIntBetween(1, 3),
|
|
};
|
|
break;
|
|
case 'Order Completed':
|
|
properties = orderProps();
|
|
break;
|
|
}
|
|
|
|
const now = new Date().toISOString();
|
|
const messageId = `k6_${shopper.user_id}_${__ITER}_${Date.now()}`;
|
|
|
|
const payload = JSON.stringify({
|
|
type: 'track',
|
|
messageId,
|
|
userId: shopper.user_id,
|
|
anonymousId,
|
|
event: eventName,
|
|
properties,
|
|
traits: {
|
|
email: shopper.email,
|
|
plan: shopper.plan,
|
|
country: shopper.country,
|
|
},
|
|
timestamp: now,
|
|
sentAt: now,
|
|
context: {
|
|
library_name: 'k6-ecommerce-sim',
|
|
library_version: '0.2.0',
|
|
ip: '127.0.0.1',
|
|
userAgent: 'k6/loadtest',
|
|
locale: 'en-US',
|
|
page: {
|
|
path: '/checkout',
|
|
host: 'shop.example.com',
|
|
title: 'Shop',
|
|
url: 'https://shop.example.com/checkout',
|
|
},
|
|
},
|
|
});
|
|
|
|
const res = http.post(`${BASE}/v1/track`, payload, {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Authorization: AUTH,
|
|
},
|
|
tags: { event: eventName },
|
|
});
|
|
|
|
check(res, {
|
|
'status 200': (r) => r.status === 200,
|
|
'body ok': (r) => r.json('ok') === true,
|
|
'fast (<500ms)': (r) => r.timings.duration < 500,
|
|
});
|
|
|
|
// Think time -- a real shopper does not click 100x/sec.
|
|
sleep(randomIntBetween(1, 4));
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function productProps(p) {
|
|
return {
|
|
product_id: p.id,
|
|
sku: p.id,
|
|
name: p.name,
|
|
category: p.category,
|
|
brand: p.brand,
|
|
price: p.price,
|
|
currency: 'USD',
|
|
};
|
|
}
|
|
|
|
function orderProps() {
|
|
// 1 - 3 line items
|
|
const lines = [];
|
|
const n = randomIntBetween(1, 3);
|
|
let total = 0;
|
|
for (let i = 0; i < n; i++) {
|
|
const p = randomItem(PRODUCTS);
|
|
const qty = randomIntBetween(1, 2);
|
|
total += p.price * qty;
|
|
lines.push({
|
|
product_id: p.id,
|
|
sku: p.id,
|
|
name: p.name,
|
|
price: p.price,
|
|
quantity: qty,
|
|
});
|
|
}
|
|
return {
|
|
order_id: `ord_${Date.now()}_${randomIntBetween(1000, 9999)}`,
|
|
revenue: Number(total.toFixed(2)),
|
|
currency: 'USD',
|
|
tax: Number((total * 0.08).toFixed(2)),
|
|
shipping: 5,
|
|
total: Number((total + total * 0.08 + 5).toFixed(2)),
|
|
products: lines,
|
|
};
|
|
}
|