-
Notifications
You must be signed in to change notification settings - Fork 33
U8: k6 load tests update #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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)]; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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'], | ||
| }, | ||
|
Comment on lines
6
to
14
|
||
| }; | ||
|
|
||
| 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'); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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'], | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+7
to
+11
|
||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| 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'), | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
| 'chat body has content': (r) => r.body && r.body.includes('content'), | |
| 'chat body has content': (r) => { | |
| if (!r.body) { | |
| return false; | |
| } | |
| try { | |
| const body = JSON.parse(r.body); | |
| return Array.isArray(body.choices) | |
| && body.choices.length > 0 | |
| && body.choices[0] | |
| && body.choices[0].message | |
| && typeof body.choices[0].message.content === 'string' | |
| && body.choices[0].message.content.length > 0; | |
| } catch (_) { | |
| return false; | |
| } | |
| }, |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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'], | ||
| }, | ||
|
Comment on lines
+13
to
+16
|
||
| }; | ||
|
|
||
| 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); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR description says categories are “rotated” per VU iteration, but
pickCategory()currently selects randomly, which can skew per-category breakdowns and make runs less reproducible. If rotation is required, consider a deterministic selection (e.g., based on__ITER/__VU) to cycle through categories evenly.