From ae6c3b43974fc16d02d2d5fc86997af1edca57e7 Mon Sep 17 00:00:00 2001 From: jaturapornchai <39358840+jaturapornchai@users.noreply.github.com> Date: Fri, 10 Apr 2026 04:43:53 +0700 Subject: [PATCH] U8: k6 load tests update (aggressive thresholds + diverse categories) Add p95<5s / p99<15s (10s for stress) thresholds and <2% error rate across smoke/chat/stress. Each script now rotates through 4 diverse prompt categories (thai/code/general, small/medium) via a shared _categories.js helper, tagged per request for per-category metrics. Smoke exercises the chat endpoint in addition to homepage/status; chat ramps to 20 VUs; stress caps at 100 VUs (previously 500) to surface aggressive-mode latency without swamping the host. Co-Authored-By: Claude Opus 4.6 (1M context) --- loadtest/_categories.js | 12 ++++++++++++ loadtest/chat.js | 23 +++++++++++------------ loadtest/smoke.js | 24 +++++++++++++++++++++--- loadtest/stress.js | 27 ++++++++++++++------------- 4 files changed, 58 insertions(+), 28 deletions(-) create mode 100644 loadtest/_categories.js diff --git a/loadtest/_categories.js b/loadtest/_categories.js new file mode 100644 index 0000000..8194caa --- /dev/null +++ b/loadtest/_categories.js @@ -0,0 +1,12 @@ +// Shared chat prompt categories for load tests. +// Diverse mix of Thai/English and small/medium prompt sizes. +export const categories = [ + { name: 'thai_small', body: { model: 'bcproxy/auto', messages: [{ role: 'user', content: 'สวัสดี ตอบสั้นๆ' }] } }, + { name: 'code_small', body: { model: 'bcproxy/auto', messages: [{ role: 'user', content: 'Python fn reverse string' }] } }, + { name: 'general_medium', body: { model: 'bcproxy/auto', messages: [{ role: 'user', content: 'Explain photosynthesis in 3 sentences for a 10-year-old.' }] } }, + { name: 'thai_medium', body: { model: 'bcproxy/auto', messages: [{ role: 'user', content: 'อธิบายการทำงานของ AI 3 ย่อหน้า สำหรับเด็ก 10 ขวบ' }] } }, +]; + +export function pickCategory() { + return categories[Math.floor(Math.random() * categories.length)]; +} diff --git a/loadtest/chat.js b/loadtest/chat.js index e2951f6..01db217 100644 --- a/loadtest/chat.js +++ b/loadtest/chat.js @@ -1,34 +1,33 @@ import http from 'k6/http'; import { check, sleep } from 'k6'; +import { pickCategory } from './_categories.js'; export const options = { stages: [ - { duration: '30s', target: 10 }, - { duration: '1m', target: 10 }, - { duration: '30s', target: 30 }, - { duration: '1m', target: 30 }, + { duration: '30s', target: 20 }, + { duration: '1m', target: 20 }, { duration: '30s', target: 0 }, ], thresholds: { - http_req_duration: ['p(95)<10000'], - http_req_failed: ['rate<0.15'], + 'http_req_duration{expected_response:true}': ['p(95)<5000', 'p(99)<15000'], + 'http_req_failed': ['rate<0.02'], }, }; const BASE_URL = __ENV.BASE_URL || 'http://localhost:3333'; -const payload = JSON.stringify({ - model: 'auto', - messages: [{ role: 'user', content: 'say hi in 1 word' }], -}); - const params = { headers: { 'Content-Type': 'application/json' }, timeout: '20s', }; export default function () { - const res = http.post(`${BASE_URL}/v1/chat/completions`, payload, params); + const cat = pickCategory(); + const res = http.post( + `${BASE_URL}/v1/chat/completions`, + JSON.stringify(cat.body), + { ...params, tags: { category: cat.name } }, + ); if (res.status === 429) { const retryAfter = parseInt(res.headers['Retry-After'] || '5'); diff --git a/loadtest/smoke.js b/loadtest/smoke.js index e930a40..a6a36ea 100644 --- a/loadtest/smoke.js +++ b/loadtest/smoke.js @@ -1,17 +1,24 @@ import http from 'k6/http'; import { check, sleep } from 'k6'; import { reportTo } from './_report.js'; +import { pickCategory } from './_categories.js'; export const options = { - vus: 1, - duration: '10s', + vus: 5, + duration: '30s', thresholds: { - http_req_failed: ['rate<0.01'], + 'http_req_duration{expected_response:true}': ['p(95)<5000', 'p(99)<15000'], + 'http_req_failed': ['rate<0.02'], }, }; const BASE_URL = __ENV.BASE_URL || 'http://localhost:3333'; +const params = { + headers: { 'Content-Type': 'application/json' }, + timeout: '30s', +}; + export default function () { const homepage = http.get(`${BASE_URL}/`); check(homepage, { @@ -23,6 +30,17 @@ export default function () { 'status endpoint 200': (r) => r.status === 200, }); + const cat = pickCategory(); + const res = http.post( + `${BASE_URL}/v1/chat/completions`, + JSON.stringify(cat.body), + { ...params, tags: { category: cat.name } }, + ); + check(res, { + 'chat status 200': (r) => r.status === 200, + 'chat body has content': (r) => r.body && r.body.includes('content'), + }); + sleep(1); } diff --git a/loadtest/stress.js b/loadtest/stress.js index 55c3b90..b017384 100644 --- a/loadtest/stress.js +++ b/loadtest/stress.js @@ -1,36 +1,37 @@ import http from 'k6/http'; import { check } from 'k6'; import { Trend } from 'k6/metrics'; +import { pickCategory } from './_categories.js'; export const options = { stages: [ - { duration: '1m', target: 100 }, - { duration: '2m', target: 100 }, - { duration: '1m', target: 300 }, - { duration: '2m', target: 300 }, - { duration: '1m', target: 500 }, - { duration: '1m', target: 500 }, + { duration: '1m', target: 50 }, + { duration: '2m', target: 50 }, + { duration: '30s', target: 100 }, { duration: '30s', target: 0 }, ], - // No strict thresholds — goal is to observe where errors start + thresholds: { + 'http_req_duration{expected_response:true}': ['p(95)<5000', 'p(99)<10000'], + 'http_req_failed': ['rate<0.02'], + }, }; const BASE_URL = __ENV.BASE_URL || 'http://localhost:3333'; const chatDuration = new Trend('chat_req_duration', true); -const payload = JSON.stringify({ - model: 'auto', - messages: [{ role: 'user', content: 'say hi in 1 word' }], -}); - const params = { headers: { 'Content-Type': 'application/json' }, timeout: '30s', }; export default function () { - const res = http.post(`${BASE_URL}/v1/chat/completions`, payload, params); + const cat = pickCategory(); + const res = http.post( + `${BASE_URL}/v1/chat/completions`, + JSON.stringify(cat.body), + { ...params, tags: { category: cat.name } }, + ); chatDuration.add(res.timings.duration);