139 lines
4.9 KiB
JavaScript

/**
* SCENARIO: location-update.js
* Real-time location stress test — simulasi banyak User berjalan serentak.
*
* WalkGuide mengirim lokasi setiap 5 detik saat WalkGuide aktif.
* Ini endpoint paling high-frequency di seluruh sistem.
*
* Target: p95 < 300ms, error rate < 1%
*/
import { sleep, check } from "k6";
import {
DEFAULT_THRESHOLDS,
handleSummary,
metricsHelper,
} from "../modules/metrics-helper.js";
import * as api from "../modules/api-client.js";
import { testData } from "../modules/test-data.js";
const BASE_URL = __ENV.BASE_URL || "http://202.46.28.160:8080";
export const options = {
scenarios: {
location_stress: {
executor: "ramping-vus",
startVUs: 0,
stages: [
{ duration: "30s", target: 20 }, // ramp-up
{ duration: "90s", target: 50 }, // sustain at 50 walking users
{ duration: "60s", target: 100 }, // peak load
{ duration: "30s", target: 0 }, // ramp-down
],
gracefulRampDown: "15s",
},
},
thresholds: {
...DEFAULT_THRESHOLDS,
walkguide_location_latency_ms: ["p(95)<300", "p(99)<600"],
http_req_duration: ["p(95)<400"],
},
};
// ── VU shared state (tiap VU punya token sendiri) ─────────────────────────────
let vuTokens = null;
let vuBaseLat = null;
let vuBaseLng = null;
// ── Main VU function ──────────────────────────────────────────────────────────
export default function locationStress() {
// ── Inisialisasi: register + login sekali per VU ───────────────────────────
if (!vuTokens) {
const suffix = `loc_${__VU}_${Date.now()}`;
const payload = testData.userRegisterPayload(suffix);
const res = api.register(payload);
if (res.status === 200) {
const body = api.parseBody(res);
if (body && body.data) vuTokens = body.data;
}
if (!vuTokens) {
console.error(`[location] VU ${__VU} failed to get tokens`);
sleep(2);
return;
}
// Posisi awal acak di Surabaya
const coord = testData.randomSurabayaCoord(0.05);
vuBaseLat = coord.lat;
vuBaseLng = coord.lng;
// Start walkguide session
const startRes = api.startWalkguide(vuTokens.accessToken);
check(startRes, {
"walkguide-start: 2xx": (r) => r.status >= 200 && r.status < 300,
});
sleep(0.5);
}
// ── Poll actuator untuk JVM heap (hanya VU 1, tidak mengganggu tes lain) ────
if (__VU === 1 && __ITER % 10 === 0) {
metricsHelper.pollJvmHeap(BASE_URL);
metricsHelper.pollDbConnections(BASE_URL);
}
// ── Simulasi walking: kirim 3 location update berturut-turut (15 detik real) ─
for (let i = 0; i < 3; i++) {
const locPayload = testData.locationUpdatePayload(vuBaseLat, vuBaseLng);
const res = api.updateLocation(
vuTokens.accessToken,
locPayload.lat,
locPayload.lng,
locPayload.accuracy,
locPayload.speed,
locPayload.heading,
);
check(res, {
"location-update: status 2xx": (r) => r.status >= 200 && r.status < 300,
"location-update: latency ok": (r) => r.timings.duration < 500,
});
// Update base coordinate (simulasi pergerakan)
vuBaseLat += testData.randomFloat(-0.0003, 0.0003);
vuBaseLng += testData.randomFloat(-0.0003, 0.0003);
// 5 detik interval seperti production
sleep(5);
}
// ── Kadang Guardian juga poll lokasi (setiap 10 iteration) ──────────────────
// Ini simulasi Guardian membuka map screen
if (__ITER % 5 === 0 && vuTokens) {
// Re-login as guardian to test guardian endpoint
// Dalam real load test, Guardian VU terpisah — di sini kita skip
// karena token kita adalah ROLE_USER
}
}
// ── Setup: siapkan backend ────────────────────────────────────────────────────
export function setup() {
const res = api.ping();
if (res.status !== 200)
throw new Error(`Backend not reachable: ${res.status}`);
console.log(`✅ Location stress test starting. Backend: ${BASE_URL}`);
return { startTime: Date.now() };
}
// ── Teardown: stop semua walkguide session ────────────────────────────────────
export function teardown(data) {
console.log(
`Location stress test finished. Duration: ${(Date.now() - data.startTime) / 1000}s`,
);
// Note: individual VU stop walkguide tidak bisa dilakukan di teardown
// karena token sudah tidak tersedia. Dalam production, session timeout handles ini.
}
export { handleSummary };