import http from 'k6/http'; import { check, sleep } from 'k6'; import { BASE, FEEDS, ITEMS, as, me, pick, feedId, p95 } from './lib.js'; // An evening's browsing: twenty-five people at once opening their feed list, All Subscriptions, // a feed, a search, the Directory and a feed's page in it. Each answer's budget is well above what // it takes now, so it fails on a query that has started asking once per feed -- the Directory // asked the database three questions a feed until 0.10.0 -- not on a slow minute. Fifty measured // the queue for SQLite's one connection (#136) more than any query. export const options = { scenarios: { evening: { executor: 'ramping-vus', stages: [{ duration: '15s', target: 25 }, { duration: '45s', target: 25 }, { duration: '10s', target: 0 }], }, }, thresholds: { http_req_failed: ['rate==0'], checks: ['rate==1'], // About twice each one's p95 on 2026-10-05 on Tower. SQLite: 236, 118, 133, 119, 259, 312ms; // Postgres: 54, 34, 24, 44, 71, 88ms. 'http_req_duration{name:feeds}': p95(500, 150), 'http_req_duration{name:all-entries}': p95(300, 100), 'http_req_duration{name:feed-entries}': p95(300, 100), 'http_req_duration{name:search}': p95(300, 100), 'http_req_duration{name:directory}': p95(600, 200), 'http_req_duration{name:listed}': p95(700, 200), }, }; export default function () { const u = me(); const feeds = http.get(`${BASE}/api/feeds`, as(u, 'feeds')); check(feeds, { 'your thirty feeds': r => r.status === 200 && r.json().length === 30 }); const all = http.get(`${BASE}/api/entries?limit=50&filter=all&sort=published&dir=desc`, as(u, 'all-entries')); check(all, { 'All Subscriptions, a page of it': r => r.status === 200 && r.json().entries.length === 50 }); const f = pick(feeds.json()); const one = http.get(`${BASE}/api/feeds/${f.id}/entries?limit=50&sort=title&dir=asc`, as(u, 'feed-entries')); check(one, { 'a feed of yours, every item': r => r.status === 200 && r.json().entries.length === ITEMS }); // Every generated item mentions the weather, in its text, not its title. const found = http.get(`${BASE}/api/entries?q=weather&limit=50`, as(u, 'search')); check(found, { 'a search of everything you read': r => r.status === 200 && r.json().total === 30 * ITEMS }); const dir = http.get(`${BASE}/api/directory`, as(u, 'directory')); check(dir, { 'the whole Directory': r => r.status === 200 && r.json().length === FEEDS }); const listed = http.get(`${BASE}/api/directory/${feedId(Math.floor(Math.random() * FEEDS))}`, as(u, 'listed')); check(listed, { "a feed's page in the Directory": r => r.status === 200 && r.json().items.length === ITEMS }); sleep(1 + Math.random() * 2); }