import http from 'k6/http'; import { check, sleep } from 'k6'; import { Trend } from 'k6/metrics'; import { BASE, as, me } from './lib.js'; // A flood of sign-in attempts with wrong passwords while everyone else goes on using the site. // Two things only many requests at once show: whether checking passwords, tens of milliseconds // of CPU each, starves the rest of the server, and whether a wrong password for a name that is // an account takes longer to refuse than one for a name that is not -- the answers read the // same, and the time would tell anyone which names are accounts here. const gap = new Trend('signin_name_gap', true); export const options = { scenarios: { attempts: { executor: 'constant-vus', vus: 40, duration: '40s', exec: 'attempt' }, everyone: { executor: 'constant-vus', vus: 5, duration: '40s', exec: 'browse' }, }, thresholds: { http_req_failed: ['rate==0'], checks: ['rate==1'], // 57ms on 2026-10-05 on SQLite, 68 on Postgres; 4.3s while password checks ran on the async // workers (#137). 'http_req_duration{name:meanwhile}': ['p(95)<300'], // Known name against unknown, one straight after the other: no gap but noise. 0.2ms on // 2026-10-05; 31ms while an unknown name was refused without a check (#138). signin_name_gap: ['med<10'], }, }; const refused = { headers: { 'Content-Type': 'application/json' }, responseCallback: http.expectedStatuses(401) }; export function attempt() { const known = http.post(`${BASE}/api/login`, JSON.stringify({ name: 'piper', password: 'not-the-password' }), { ...refused, tags: { name: 'signin-known' } }); const unknown = http.post(`${BASE}/api/login`, JSON.stringify({ name: `nobody-${__VU}-${__ITER}`, password: 'not-the-password' }), { ...refused, tags: { name: 'signin-unknown' } }); check(known, { 'a wrong password refused': r => r.status === 401 }); check(unknown, { 'an unknown name refused': r => r.status === 401 }); gap.add(known.timings.duration - unknown.timings.duration); } export function browse() { const r = http.get(`${BASE}/api/feeds`, as(me(), 'meanwhile')); check(r, { 'the site still answers': r => r.status === 200 }); sleep(0.5); }