Files
tokens-reef/deploy/performance-testing/test-suites/mixed-workload.test.js
Developer 349d783fd1 refactor: clean up project structure
- Remove old review reports (keep latest only)
- Move docs/ to deploy/docs-backup/
- Move performance-testing/ to deploy/
- Clean up test output files
- Organize root directory
2026-04-06 23:36:03 +08:00

415 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Sub2API Mixed Workload Performance Test
// 综合负载性能测试 - 模拟真实用户行为
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Rate, Trend, Counter } from 'k6/metrics';
import { config, getBaseUrl, getAuthToken } from '../common/utils.js';
import { httpGet, httpPost, randomSleep, randomChoice } from '../common/utils.js';
// ============= 自定义指标 =============
const totalRequestDuration = new Trend('total_request_duration');
const errorRate = new Rate('errors');
const throughputCounter = new Counter('throughput');
// 分模块指标
const moduleMetrics = {
health: new Trend('health_duration'),
auth: new Trend('auth_duration'),
apiKeys: new Trend('apikeys_duration'),
gateway: new Trend('gateway_duration'),
admin: new Trend('admin_duration'),
};
// ============= 测试配置 =============
export const options = {
scenarios: {
baseline: {
executor: 'ramping-vus',
startVUs: 10,
stages: [
{ duration: '2m', target: 10 }, // 预热
{ duration: '3m', target: 50 }, // 正常负载
{ duration: '1m', target: 0 }, // 冷却
],
},
load: {
executor: 'ramping-vus',
startVUs: 20,
stages: [
{ duration: '2m', target: 20 }, // 预热
{ duration: '2m', target: 100 }, // 正常负载
{ duration: '2m', target: 200 }, // 峰值负载
{ duration: '2m', target: 200 }, // 持续峰值
{ duration: '2m', target: 0 }, // 冷却
],
},
stress: {
executor: 'ramping-vus',
startVUs: 50,
stages: [
{ duration: '1m', target: 50 }, // 预热
{ duration: '2m', target: 200 }, // 正常负载
{ duration: '2m', target: 500 }, // 高负载
{ duration: '3m', target: 1000 }, // 极限负载
{ duration: '2m', target: 0 }, // 冷却
],
},
soak: {
executor: 'constant-vus',
vus: 100,
duration: '8h',
},
spike: {
executor: 'ramping-vus',
startVUs: 50,
stages: [
{ duration: '30s', target: 50 }, // 基线
{ duration: '1m', target: 1000 }, // 尖峰
{ duration: '1m', target: 1000 }, // 保持尖峰
{ duration: '30s', target: 50 }, // 恢复
{ duration: '2m', target: 0 }, // 冷却
],
},
},
thresholds: {
// 全局阈值
'errors': ['rate<0.02'], // 错误率 < 2%
'total_request_duration': ['p(95)<2000', 'p(99)<5000'],
// 各模块阈值
'health_duration': ['p(95)<200'],
'auth_duration': ['p(95)<500'],
'apikeys_duration': ['p(95)<1000'],
'gateway_duration': ['p(95)<3000'],
'admin_duration': ['p(95)<1500'],
},
};
// ============= 辅助函数 =============
function getHeaders() {
return {
'Authorization': `Bearer ${getAuthToken()}`,
'Content-Type': 'application/json',
};
}
// ============= 测试场景 =============
/**
* 健康检查测试
*/
function testHealthCheck() {
const start = Date.now();
const res = http.get(`${getBaseUrl()}/health`, {
tags: { name: 'health' },
});
moduleMetrics.health.add(Date.now() - start);
errorRate.add(res.status !== 200);
check(res, {
'Health check OK': (r) => r.status === 200,
});
}
/**
* 认证测试
*/
function testAuth() {
const start = Date.now();
// 登录
const loginRes = http.post(
`${getBaseUrl()}/api/v1/auth/login`,
JSON.stringify({
email: 'user@example.com',
password: 'password123',
}),
{
headers: { 'Content-Type': 'application/json' },
tags: { name: 'auth_login' },
}
);
moduleMetrics.auth.add(Date.now() - start);
errorRate.add(loginRes.status !== 200);
check(loginRes, {
'Login OK': (r) => r.status === 200,
'Has token': (r) => r.json('token') !== undefined,
});
}
/**
* API Key 管理测试
*/
function testAPIKeys() {
const start = Date.now();
// 列出 keys
const listRes = http.get(`${getBaseUrl()}/api/v1/keys`, {
headers: getHeaders(),
tags: { name: 'apikeys_list' },
});
check(listRes, {
'List OK': (r) => r.status === 200,
});
// 随机选择一个操作
const op = randomChoice(['create', 'update', 'delete']);
if (listRes.status === 200) {
const keys = listRes.json('data');
if (op === 'create' && keys.length < 10) {
// 创建新 key
const createRes = http.post(
`${getBaseUrl()}/api/v1/keys`,
JSON.stringify({ name: `perf-test-${Date.now()}` }),
{ headers: getHeaders(), tags: { name: 'apikeys_create' } }
);
check(createRes, {
'Create OK': (r) => r.status === 201,
});
} else if (keys.length > 0) {
// 更新或删除
const keyId = keys[0].id;
if (op === 'update') {
const updateRes = http.patch(
`${getBaseUrl()}/api/v1/keys/${keyId}`,
JSON.stringify({ name: `updated-${Date.now()}` }),
{ headers: getHeaders(), tags: { name: 'apikeys_update' } }
);
check(updateRes, {
'Update OK': (r) => r.status === 200,
});
}
// delete 操作可选执行,避免清理测试数据
}
}
moduleMetrics.apikeys.add(Date.now() - start);
}
/**
* Gateway API 测试
*/
function testGateway() {
const start = Date.now();
// 准备 API Key
const listRes = http.get(`${getBaseUrl()}/api/v1/keys`, {
headers: getHeaders(),
});
if (listRes.status !== 200) {
return;
}
const keys = listRes.json('data');
if (!keys || keys.length === 0) {
return;
}
const apiKey = keys[0].key;
// 随机选择一个平台
const platform = randomChoice(['openai', 'claude', 'gemini']);
let res;
let model;
switch (platform) {
case 'openai':
res = http.post(
`${getBaseUrl()}/v1/chat/completions`,
JSON.stringify({
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: 'Hi' }],
max_tokens: 10,
}),
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
tags: { name: 'gateway_openai' },
}
);
break;
case 'claude':
res = http.post(
`${getBaseUrl()}/v1/messages`,
JSON.stringify({
model: 'claude-3-5-haiku-20241022',
messages: [{ role: 'user', content: 'Hi' }],
max_tokens: 10,
}),
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'anthropic-version': '2023-06-01',
},
tags: { name: 'gateway_claude' },
}
);
break;
case 'gemini':
res = http.post(
`${getBaseUrl()}/v1beta/models/gemini-pro:generateContent`,
JSON.stringify({
contents: [{ parts: [{ text: 'Hi' }] }],
}),
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
tags: { name: 'gateway_gemini' },
}
);
break;
}
moduleMetrics.gateway.add(Date.now() - start);
// Gateway 错误不计入全局错误率(上游可能不可用)
check(res, {
'Gateway OK or expected error': (r) => r.status < 500,
});
}
/**
* Admin API 测试(仅部分 VU
*/
function testAdmin() {
if (__VU % 10 !== 0) {
return; // 只有 10% 的 VU 执行 admin 测试
}
const start = Date.now();
const res = http.get(`${getBaseUrl()}/api/v1/admin/dashboard`, {
headers: getHeaders(),
tags: { name: 'admin_dashboard' },
});
moduleMetrics.admin.add(Date.now() - start);
check(res, {
'Admin OK': (r) => r.status === 200,
});
}
// ============= 用户行为模拟 =============
/**
* 模拟真实用户行为
*/
function simulateUserBehavior() {
// 根据权重选择操作
const rand = Math.random();
if (rand < 0.05) {
// 5% 健康检查
testHealthCheck();
} else if (rand < 0.10) {
// 5% 登录
testAuth();
} else if (rand < 0.25) {
// 15% API Key 管理
testAPIKeys();
} else if (rand < 0.95) {
// 70% Gateway 请求
testGateway();
} else {
// 5% Admin 请求
testAdmin();
}
}
// ============= 主测试函数 =============
export default function () {
const start = Date.now();
group('Mixed Workload', () => {
simulateUserBehavior();
});
totalRequestDuration.add(Date.now() - start);
throughputCounter.add(1);
// 随机思考时间
randomSleep(0.5, 3);
}
// ============= 测试报告 =============
export function handleSummary(data) {
const metrics = data.metrics;
return {
'mixed-workload-report.json': JSON.stringify(data, null, 2),
'mixed-workload-summary.txt': `
================================================================================
Sub2API 综合负载性能测试报告
================================================================================
测试时间: ${new Date().toISOString()}
测试持续: ${(data.state.testRunDurationMs / 1000 / 60).toFixed(2)} 分钟
峰值 VU: ${metrics?.vus?.peak || 0}
最终 VU: ${metrics?.vus?.value || 0}
--------------------------------------------------------------------------------
核心指标
--------------------------------------------------------------------------------
总请求数: ${metrics?.throughput?.values?.count || 0}
错误率: ${((metrics?.errors?.values?.rate || 0) * 100).toFixed(2)}%
平均响应时间: ${metrics?.total_request_duration?.values?.avg?.toFixed(2) || 0} ms
P50 响应时间: ${metrics?.total_request_duration?.values?.['p(50)']?.toFixed(2) || 0} ms
P95 响应时间: ${metrics?.total_request_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
P99 响应时间: ${metrics?.total_request_duration?.values?.['p(99)']?.toFixed(2) || 0} ms
最大响应时间: ${metrics?.total_request_duration?.values?.max?.toFixed(2) || 0} ms
--------------------------------------------------------------------------------
分模块性能
--------------------------------------------------------------------------------
健康检查:
平均: ${metrics?.health_duration?.values?.avg?.toFixed(2) || 0} ms
P95: ${metrics?.health_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
认证:
平均: ${metrics?.auth_duration?.values?.avg?.toFixed(2) || 0} ms
P95: ${metrics?.auth_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
API Key 管理:
平均: ${metrics?.apikeys_duration?.values?.avg?.toFixed(2) || 0} ms
P95: ${metrics?.apikeys_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
Gateway:
平均: ${metrics?.gateway_duration?.values?.avg?.toFixed(2) || 0} ms
P95: ${metrics?.gateway_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
P99: ${metrics?.gateway_duration?.values?.['p(99)']?.toFixed(2) || 0} ms
管理后台:
平均: ${metrics?.admin_duration?.values?.avg?.toFixed(2) || 0} ms
P95: ${metrics?.admin_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
================================================================================
测试完成
================================================================================
`,
};
}