import http from 'k6/http'; import { check } from 'k6'; import { BASE, as, pick } from './lib.js'; // Listeners seeking: fifty at once asking for ranges of the same few episodes, as a player does // on every seek and every few seconds of playback. Every range is checked against what the feed // served, byte by byte at a sample of offsets, so a wrong offset fails, not only a wrong status. export const options = { scenarios: { seeking: { executor: 'constant-vus', vus: 50, duration: '45s' } }, thresholds: { http_req_failed: ['rate==0'], checks: ['rate==1'], // About four times its p95 on 2026-10-05, 48ms on SQLite and 45 on Postgres: a range is a // file read, little to vary. 'http_req_duration{name:range}': ['p(95)<200'], }, }; // The generated file: ID3's ten bytes, then a byte its offset gives (run.js, mediaByte). const SIZE = 2 * 1024 * 1024; const byte = k => (k * 31 + 7) & 255; export function setup() { const r = http.get(`${BASE}/api/entries?filter=downloaded&limit=50`, as('listener', 'setup')); const files = r.json().entries.flatMap(e => e.enclosures).filter(x => x.path).map(x => x.id); if (!files.length) throw new Error('the listener has no downloaded files to seek through'); return { files }; } export default function ({ files }) { const start = 10 + Math.floor(Math.random() * (SIZE - 11)); const end = Math.min(SIZE - 1, start + Math.floor(Math.random() * 65536)); const r = http.get(`${BASE}/media/${pick(files)}`, { headers: { 'X-Load-User': 'listener', Range: `bytes=${start}-${end}` }, responseType: 'binary', tags: { name: 'range' }, }); const body = r.status === 206 ? new Uint8Array(r.body) : new Uint8Array(0); check(r, { 'part of the file': r => r.status === 206, 'the range asked for': r => r.headers['Content-Range'] === `bytes ${start}-${end}/${SIZE}`, 'its bytes': () => { if (body.length !== end - start + 1) return false; for (const at of [0, body.length - 1, ...Array.from({ length: 30 }, () => Math.floor(Math.random() * body.length))]) if (body[at] !== byte(start + at)) return false; return true; }, }); }