/** * Load test: HLS streaming - master playlist and segments * Usage: k6 run loadtests/stream/hls.js * Requires: Stream server running, AUTH_TOKEN (JWT), TRACK_ID (UUID of track with HLS) * * Env: STREAM_ORIGIN, AUTH_TOKEN, TRACK_ID * Example: AUTH_TOKEN=xxx TRACK_ID=550e8400-e29b-41d4-a716-446655440000 k6 run loadtests/stream/hls.js */ import http from 'k6/http'; import { check, sleep } from 'k6'; import { Rate, Trend } from 'k6/metrics'; const errorRate = new Rate('hls_errors'); const manifestDuration = new Trend('hls_manifest_duration'); const segmentDuration = new Trend('hls_segment_duration'); const STREAM_ORIGIN = __ENV.STREAM_ORIGIN || 'http://localhost:8082'; const AUTH_TOKEN = __ENV.AUTH_TOKEN || ''; const TRACK_ID = __ENV.TRACK_ID || ''; export const options = { scenarios: { hls_streaming: { executor: 'constant-arrival-rate', rate: 5, timeUnit: '1s', duration: '2m', preAllocatedVUs: 10, maxVUs: 50, }, }, thresholds: { http_req_duration: ['p(95)<2000', 'p(99)<5000'], hls_errors: ['rate<0.01'], hls_manifest_duration: ['p(95)<500', 'p(99)<1000'], hls_segment_duration: ['p(95)<2000', 'p(99)<5000'], }, }; function getHeaders() { const h = { Accept: 'application/vnd.apple.mpegurl,*/*' }; if (AUTH_TOKEN) { h['Authorization'] = `Bearer ${AUTH_TOKEN}`; } return h; } export function setup() { if (!TRACK_ID) { console.warn('TRACK_ID not set - HLS tests will 404. Set TRACK_ID and AUTH_TOKEN for real streaming load.'); } if (!AUTH_TOKEN) { console.warn('AUTH_TOKEN not set - HLS routes require JWT. Requests may return 401.'); } return { trackId: TRACK_ID || '00000000-0000-0000-0000-000000000000' }; } export default function (data) { const { trackId } = data; if (!trackId || trackId === '00000000-0000-0000-0000-000000000000') { // Still hit the endpoint to exercise the auth path const url = `${STREAM_ORIGIN}/hls/${trackId}/master.m3u8`; const res = http.get(url, { headers: getHeaders() }); errorRate.add(res.status !== 200 && res.status !== 404); sleep(1); return; } // 1. Fetch master playlist const manifestUrl = `${STREAM_ORIGIN}/hls/${trackId}/master.m3u8`; const manifestStart = Date.now(); const manifestRes = http.get(manifestUrl, { headers: getHeaders() }); manifestDuration.add(Date.now() - manifestStart); const manifestOk = check(manifestRes, { 'master.m3u8 returns 200': (r) => r.status === 200, }); errorRate.add(!manifestOk); if (!manifestOk) { sleep(1); return; } // 2. Parse playlist for segment URLs (simplified: extract quality playlists or segments) const body = manifestRes.body; const qualityMatch = body.match(/\/hls\/[^/]+\/([^/\s]+)\/playlist\.m3u8/); const quality = qualityMatch ? qualityMatch[1] : 'high'; sleep(0.2); // 3. Fetch quality playlist const qualityUrl = `${STREAM_ORIGIN}/hls/${trackId}/${quality}/playlist.m3u8`; const qualityRes = http.get(qualityUrl, { headers: getHeaders() }); errorRate.add(qualityRes.status !== 200); if (qualityRes.status !== 200) { sleep(1); return; } // 4. Fetch first segment (if present) const segmentMatch = qualityRes.body.match(/(segment_\d+\.ts)/); if (segmentMatch) { const segmentName = segmentMatch[1]; const segmentUrl = `${STREAM_ORIGIN}/hls/${trackId}/${quality}/${segmentName}`; const segmentStart = Date.now(); const segmentRes = http.get(segmentUrl, { headers: getHeaders() }); segmentDuration.add(Date.now() - segmentStart); errorRate.add(segmentRes.status !== 200); } sleep(1); }