soak-with-memory-check.js (4132B)
1 /** 2 * Soak test with memory leak detection. 3 * 4 * Runs the same 15 rps steady-state load as soak.js. At setup() time, queries 5 * Prometheus for a baseline of the connectivity-tester-stage container memory. 6 * After the soak, teardown() re-queries and fails the `checks` threshold if 7 * memory grew more than MEMORY_GROWTH_THRESHOLD (default 30%). 8 * 9 * Env vars: 10 * SOAK_RPS — requests/sec (default: 15) 11 * SOAK_DURATION — total test duration (default: 30m) 12 * PROMETHEUS_URL — Prometheus HTTP API base URL 13 * MEMORY_GROWTH_THRESHOLD — fractional threshold, e.g. 0.30 = 30% (default: 0.30) 14 */ 15 16 import http from 'k6/http'; 17 import { check } from 'k6'; 18 import { BASE_URL, SERVER_NAMES, randomItem } from './config.js'; 19 20 const SOAK_RPS = parseInt(__ENV.SOAK_RPS || '15', 10); 21 const SOAK_DURATION = __ENV.SOAK_DURATION || '30m'; 22 const PROMETHEUS_URL = __ENV.PROMETHEUS_URL || 'http://prometheus-operated.monitoring.svc.cluster.local:9090'; 23 const MEMORY_GROWTH_THRESHOLD = parseFloat(__ENV.MEMORY_GROWTH_THRESHOLD || '0.30'); 24 25 // cAdvisor metric — excludes page cache, reflects true resident memory 26 const MEMORY_QUERY = 'avg(container_memory_working_set_bytes{namespace="matrix",pod=~"connectivity-tester-stage-.*",container="federation-tester-api"})'; 27 28 export const options = { 29 scenarios: { 30 soak: { 31 executor: 'constant-arrival-rate', 32 exec: 'federationOk', 33 rate: SOAK_RPS, 34 timeUnit: '1s', 35 duration: SOAK_DURATION, 36 // At 15 rps × ~1.5s avg = ~23 VUs needed; allocate headroom 37 preAllocatedVUs: 40, 38 maxVUs: 80, 39 }, 40 }, 41 thresholds: { 42 http_req_failed: ['rate<0.05'], 43 // If p95 climbs above 5s during a soak that was fine at 1.2s, something is leaking 44 http_req_duration: ['p(95)<5000'], 45 // Memory growth check — reported via teardown() check() 46 checks: ['rate==1.0'], 47 }, 48 }; 49 50 function queryMemoryBytes() { 51 const url = `${PROMETHEUS_URL}/api/v1/query?query=${encodeURIComponent(MEMORY_QUERY)}`; 52 const res = http.get(url, { timeout: '10s', tags: { name: 'prometheus_memory_query' } }); 53 if (res.status !== 200) { 54 console.warn(`Prometheus query failed: HTTP ${res.status}`); 55 return null; 56 } 57 try { 58 const body = JSON.parse(res.body); 59 if (body.status === 'success' && body.data.result.length > 0) { 60 return parseFloat(body.data.result[0].value[1]); 61 } 62 console.warn('Prometheus returned no results for memory query'); 63 } catch (e) { 64 console.warn(`Failed to parse Prometheus response: ${e}`); 65 } 66 return null; 67 } 68 69 export function setup() { 70 const baseline = queryMemoryBytes(); 71 if (baseline !== null) { 72 console.log(`Baseline memory: ${(baseline / 1024 / 1024).toFixed(1)} MiB`); 73 } else { 74 console.warn('Could not establish baseline memory; memory growth check will be skipped in teardown'); 75 } 76 return { baselineMemory: baseline }; 77 } 78 79 export function federationOk() { 80 const server = randomItem(SERVER_NAMES); 81 const res = http.get( 82 `${BASE_URL}/api/federation/federation-ok?server_name=${encodeURIComponent(server)}`, 83 { timeout: '15s' }, 84 ); 85 check(res, { 86 'federation-ok: status 200': (r) => r.status === 200, 87 'federation-ok: GOOD or BAD': (r) => 88 r.body.trim() === 'GOOD' || r.body.trim() === 'BAD', 89 }); 90 } 91 92 export function teardown(data) { 93 if (data.baselineMemory === null) { 94 console.warn('Baseline memory was unavailable; skipping memory growth check'); 95 return; 96 } 97 const current = queryMemoryBytes(); 98 if (current === null) { 99 console.warn('Post-soak memory unavailable; skipping memory growth check'); 100 return; 101 } 102 103 const growth = (current - data.baselineMemory) / data.baselineMemory; 104 const baseMiB = (data.baselineMemory / 1024 / 1024).toFixed(1); 105 const currentMiB = (current / 1024 / 1024).toFixed(1); 106 console.log(`Memory: baseline=${baseMiB} MiB → current=${currentMiB} MiB (${(growth * 100).toFixed(1)}% growth)`); 107 108 check(growth, { 109 [`memory growth <= ${(MEMORY_GROWTH_THRESHOLD * 100).toFixed(0)}%`]: (g) => g <= MEMORY_GROWTH_THRESHOLD, 110 }); 111 }