/** * 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 };