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
This commit is contained in:
414
deploy/performance-testing/test-suites/mixed-workload.test.js
Normal file
414
deploy/performance-testing/test-suites/mixed-workload.test.js
Normal file
@@ -0,0 +1,414 @@
|
||||
// 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
|
||||
|
||||
================================================================================
|
||||
测试完成
|
||||
================================================================================
|
||||
`,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user