Files

415 lines
11 KiB
JavaScript
Raw Permalink Normal View History

// 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
================================================================================
测试完成
================================================================================
`,
};
}