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:
Developer
2026-04-06 23:36:03 +08:00
parent 4d71566c0d
commit 349d783fd1
697 changed files with 24114 additions and 163282 deletions

View File

@@ -0,0 +1,247 @@
// Sub2API Admin API Performance Test
// 管理后台 API 性能测试
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Rate, Trend, Counter } from 'k6/metrics';
import { config, getBaseUrl, getAdminCredentials } from '../common/utils.js';
import { httpGet, httpPost, randomSleep } from '../common/utils.js';
// ============= 自定义指标 =============
const adminUserListDuration = new Trend('admin_user_list_duration');
const adminGroupStatsDuration = new Trend('admin_group_stats_duration');
const adminDashboardDuration = new Trend('admin_dashboard_duration');
const adminErrorRate = new Rate('admin_errors');
// ============= 测试配置 =============
export const options = {
scenarios: {
admin_load: {
executor: 'ramping-vus',
startVUs: 2,
stages: [
{ duration: '1m', target: 5 },
{ duration: '3m', target: 20 },
{ duration: '2m', target: 0 },
],
},
},
thresholds: {
'admin_user_list_duration': ['p(95)<1000', 'p(99)<2000'],
'admin_group_stats_duration': ['p(95)<1500', 'p(99)<3000'],
'admin_errors': ['rate<0.02'],
},
};
// ============= 辅助函数 =============
function getAdminHeaders() {
return {
'Authorization': `Bearer ${getAdminToken()}`,
'Content-Type': 'application/json',
};
}
let adminToken = null;
let tokenExpiry = 0;
function getAdminToken() {
const now = Date.now();
if (adminToken && now < tokenExpiry) {
return adminToken;
}
const credentials = getAdminCredentials();
const res = http.post(
`${getBaseUrl()}/api/v1/auth/login`,
JSON.stringify({
email: credentials.email,
password: credentials.password,
}),
{
headers: { 'Content-Type': 'application/json' },
tags: { name: 'admin_login' },
}
);
if (res.status === 200) {
adminToken = res.json('token');
tokenExpiry = now + (55 * 60 * 1000);
return adminToken;
}
throw new Error('Admin login failed');
}
// ============= 测试场景 =============
/**
* 测试用户列表
*/
function testUserList() {
const res = http.get(`${getBaseUrl()}/api/v1/admin/users?page=1&limit=20`, {
headers: getAdminHeaders(),
tags: { name: 'admin_user_list' },
});
adminUserListDuration.add(res.timings.duration);
adminErrorRate.add(res.status !== 200);
check(res, {
'User List: status is 200': (r) => r.status === 200,
'User List: has data': (r) => r.json('data') !== undefined,
});
return res;
}
/**
* 测试分组统计
*/
function testGroupStats(groupId = 1) {
const res = http.get(`${getBaseUrl()}/api/v1/admin/groups/${groupId}/stats`, {
headers: getAdminHeaders(),
tags: { name: 'admin_group_stats' },
});
adminGroupStatsDuration.add(res.timings.duration);
adminErrorRate.add(res.status !== 200);
check(res, {
'Group Stats: status is 200': (r) => r.status === 200,
'Group Stats: has stats': (r) => r.json() !== undefined,
});
return res;
}
/**
* 测试仪表板
*/
function testDashboard() {
const res = http.get(`${getBaseUrl()}/api/v1/admin/dashboard`, {
headers: getAdminHeaders(),
tags: { name: 'admin_dashboard' },
});
adminDashboardDuration.add(res.timings.duration);
adminErrorRate.add(res.status !== 200);
check(res, {
'Dashboard: status is 200': (r) => r.status === 200,
});
return res;
}
/**
* 测试余额调整
*/
function testBalanceAdjustment() {
// 首先获取一个用户 ID
const userListRes = testUserList();
if (userListRes.status !== 200 || !userListRes.json('data')?.length) {
return;
}
const userId = userListRes.json('data')[0].id;
const res = http.post(
`${getBaseUrl()}/api/v1/admin/users/${userId}/balance`,
JSON.stringify({
balance: 10.00,
operation: 'add',
notes: 'Performance test adjustment',
}),
{
headers: getAdminHeaders(),
tags: { name: 'admin_balance_adjust' },
}
);
check(res, {
'Balance Adjust: status is 200': (r) => r.status === 200,
});
adminErrorRate.add(res.status !== 200);
}
// ============= 主测试函数 =============
export default function () {
group('Admin API', () => {
const mode = __VU % 5;
switch (mode) {
case 0:
testDashboard();
break;
case 1:
testUserList();
randomSleep(0.1, 0.5);
testUserList();
break;
case 2:
testGroupStats(1);
testGroupStats(2);
break;
case 3:
testBalanceAdjustment();
break;
case 4:
// 综合测试
testDashboard();
testUserList();
testGroupStats(1);
break;
}
});
randomSleep(1, 3);
}
export function handleSummary(data) {
return {
'admin-performance-report.json': JSON.stringify(data, null, 2),
'admin-performance-summary.txt': `
================================================================================
Sub2API Admin API 性能测试报告
================================================================================
测试时间: ${new Date().toISOString()}
测试持续: ${(data.state.testRunDurationMs / 1000 / 60).toFixed(2)} 分钟
--------------------------------------------------------------------------------
核心指标
--------------------------------------------------------------------------------
总请求数: ${data.metrics?.requests_total?.values?.count || 0}
错误率: ${((data.metrics?.admin_errors?.values?.rate || 0) * 100).toFixed(2)}%
--------------------------------------------------------------------------------
操作类型性能
--------------------------------------------------------------------------------
仪表板:
平均响应: ${data.metrics?.admin_dashboard_duration?.values?.avg?.toFixed(2) || 0} ms
P95 响应: ${data.metrics?.admin_dashboard_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
用户列表:
平均响应: ${data.metrics?.admin_user_list_duration?.values?.avg?.toFixed(2) || 0} ms
P95 响应: ${data.metrics?.admin_user_list_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
P99 响应: ${data.metrics?.admin_user_list_duration?.values?.['p(99)']?.toFixed(2) || 0} ms
分组统计:
平均响应: ${data.metrics?.admin_group_stats_duration?.values?.avg?.toFixed(2) || 0} ms
P95 响应: ${data.metrics?.admin_group_stats_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
P99 响应: ${data.metrics?.admin_group_stats_duration?.values?.['p(99)']?.toFixed(2) || 0} ms
================================================================================
测试完成
================================================================================
`,
};
}

View File

@@ -0,0 +1,318 @@
// Sub2API API Key Management Performance Test
// API Key 管理接口性能测试
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, generateTestAPIKeyName } from '../common/utils.js';
// ============= 自定义指标 =============
const apiKeyListDuration = new Trend('apikey_list_duration');
const apiKeyCreateDuration = new Trend('apikey_create_duration');
const apiKeyUpdateDuration = new Trend('apikey_update_duration');
const apiKeyDeleteDuration = new Trend('apikey_delete_duration');
const apiKeyErrorRate = new Rate('apikey_errors');
// ============= 测试配置 =============
export const options = {
scenarios: {
apikey_load: {
executor: 'ramping-vus',
startVUs: 5,
stages: [
{ duration: '1m', target: 20 }, // 预热
{ duration: '3m', target: 50 }, // 正常负载
{ duration: '2m', target: 100 }, // 峰值负载
{ duration: '1m', target: 0 }, // 冷却
],
},
},
thresholds: {
'apikey_list_duration': ['p(95)<500', 'p(99)<1000'],
'apikey_create_duration': ['p(95)<1000', 'p(99)<2000'],
'apikey_errors': ['rate<0.01'],
},
};
// ============= 辅助函数 =============
/**
* 获取认证头
*/
function getHeaders() {
return {
'Authorization': `Bearer ${getAuthToken()}`,
'Content-Type': 'application/json',
};
}
/**
* 创建测试用 API Key
*/
function createTestAPIKey(name) {
const res = http.post(
`${getBaseUrl()}/api/v1/keys`,
JSON.stringify({ name: name || generateTestAPIKeyName() }),
{ headers: getHeaders(), tags: { name: 'apikey_create' } }
);
apiKeyCreateDuration.add(res.timings.duration);
return res;
}
/**
* 列出 API Keys
*/
function listAPIKeys(params = {}) {
const url = `${getBaseUrl()}/api/v1/keys${params.page ? `?page=${params.page}` : ''}`;
const res = http.get(url, { headers: getHeaders(), tags: { name: 'apikey_list' } });
apiKeyListDuration.add(res.timings.duration);
return res;
}
/**
* 获取单个 API Key 详情
*/
function getAPIKey(id) {
const res = http.get(`${getBaseUrl()}/api/v1/keys/${id}`, {
headers: getHeaders(),
tags: { name: 'apikey_get' },
});
return res;
}
/**
* 更新 API Key
*/
function updateAPIKey(id, updates) {
const res = http.patch(`${getBaseUrl()}/api/v1/keys/${id}`, JSON.stringify(updates), {
headers: getHeaders(),
tags: { name: 'apikey_update' },
});
apiKeyUpdateDuration.add(res.timings.duration);
return res;
}
/**
* 删除 API Key
*/
function deleteAPIKey(id) {
const res = http.delete(`${getBaseUrl()}/api/v1/keys/${id}`, {
headers: getHeaders(),
tags: { name: 'apikey_delete' },
});
apiKeyDeleteDuration.add(res.timings.duration);
return res;
}
// ============= 测试场景 =============
/**
* 测试 CRUD 完整流程
*/
function testCRUDFlow() {
// 1. 创建
const createRes = createTestAPIKey(`perf-crud-${Date.now()}`);
check(createRes, {
'Create: status is 201': (r) => r.status === 201,
'Create: has key data': (r) => r.json('id') !== undefined,
});
apiKeyErrorRate.add(createRes.status !== 201);
if (createRes.status !== 201) return null;
const keyId = createRes.json('id');
// 2. 读取
const getRes = getAPIKey(keyId);
check(getRes, {
'Get: status is 200': (r) => r.status === 200,
'Get: id matches': (r) => r.json('id') === keyId,
});
// 3. 更新
const updateRes = updateAPIKey(keyId, { name: `updated-${Date.now()}` });
check(updateRes, {
'Update: status is 200': (r) => r.status === 200,
'Update: name changed': (r) => r.json('name').includes('updated'),
});
apiKeyErrorRate.add(updateRes.status !== 200);
// 4. 删除
const deleteRes = deleteAPIKey(keyId);
check(deleteRes, {
'Delete: status is 204': (r) => r.status === 204,
});
apiKeyErrorRate.add(deleteRes.status !== 204);
return keyId;
}
/**
* 测试列表查询
*/
function testListOperations() {
// 测试分页
const page1 = listAPIKeys({ page: 1 });
check(page1, {
'List Page 1: status is 200': (r) => r.status === 200,
'List Page 1: has data': (r) => Array.isArray(r.json('data')),
});
apiKeyErrorRate.add(page1.status !== 200);
// 测试搜索
const searchRes = http.get(`${getBaseUrl()}/api/v1/keys?search=perf`, {
headers: getHeaders(),
tags: { name: 'apikey_search' },
});
check(searchRes, {
'Search: status is 200': (r) => r.status === 200,
});
// 随机睡眠模拟用户浏览
randomSleep(0.1, 0.5);
}
/**
* 测试并发创建
*/
function testConcurrentCreate() {
// 批量创建请求
const batch = [];
const count = 5;
for (let i = 0; i < count; i++) {
batch.push([
'POST',
`${getBaseUrl()}/api/v1/keys`,
JSON.stringify({ name: `batch-${Date.now()}-${i}` }),
{
headers: getHeaders(),
tags: { name: 'apikey_batch_create' },
},
]);
}
const responses = http.batch(batch);
let successCount = 0;
for (const res of responses) {
if (res.status === 201) successCount++;
apiKeyErrorRate.add(res.status !== 201);
}
check(responses[0], {
'Batch Create: at least 80% success': () => successCount / count >= 0.8,
});
return successCount;
}
// ============= 主测试函数 =============
export default function () {
const vuId = __VU;
const iteration = __ITER__;
group('API Key Management', () => {
// 每个 VU 的操作模式
const mode = vuId % 4;
switch (mode) {
case 0:
// 读为主:列表 + 单个查询
listAPIKeys();
randomSleep(0.2, 0.5);
// 获取一个 key 进行查询
const listRes = listAPIKeys();
if (listRes.status === 200 && listRes.json('data').length > 0) {
const keyId = listRes.json('data')[0].id;
getAPIKey(keyId);
}
break;
case 1:
// 写为主:创建 + 更新
testCRUDFlow();
break;
case 2:
// 混合操作
listAPIKeys();
randomSleep(0.1, 0.3);
testListOperations();
break;
case 3:
// 批量操作
if (iteration % 10 === 0) {
testConcurrentCreate();
} else {
listAPIKeys();
}
break;
}
});
// 全局思考时间
randomSleep(0.5, 2);
}
// ============= 测试报告 =============
export function handleSummary(data) {
return {
'apikey-performance-report.json': JSON.stringify(data, null, 2),
'apikey-performance-summary.txt': generateSummary(data),
};
}
function generateSummary(data) {
const metrics = data.metrics;
return `
================================================================================
Sub2API API Key Management 性能测试报告
================================================================================
测试时间: ${new Date().toISOString()}
测试持续: ${(data.state.testRunDurationMs / 1000 / 60).toFixed(2)} 分钟
--------------------------------------------------------------------------------
核心指标
--------------------------------------------------------------------------------
总请求数: ${metrics?.requests_total?.values?.count || 0}
错误率: ${((metrics?.apikey_errors?.values?.rate || 0) * 100).toFixed(2)}%
--------------------------------------------------------------------------------
操作类型性能
--------------------------------------------------------------------------------
列表操作:
平均响应: ${metrics?.apikey_list_duration?.values?.avg?.toFixed(2) || 0} ms
P95 响应: ${metrics?.apikey_list_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
P99 响应: ${metrics?.apikey_list_duration?.values?.['p(99)']?.toFixed(2) || 0} ms
创建操作:
平均响应: ${metrics?.apikey_create_duration?.values?.avg?.toFixed(2) || 0} ms
P95 响应: ${metrics?.apikey_create_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
P99 响应: ${metrics?.apikey_create_duration?.values?.['p(99)']?.toFixed(2) || 0} ms
更新操作:
平均响应: ${metrics?.apikey_update_duration?.values?.avg?.toFixed(2) || 0} ms
P95 响应: ${metrics?.apikey_update_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
删除操作:
平均响应: ${metrics?.apikey_delete_duration?.values?.avg?.toFixed(2) || 0} ms
P95 响应: ${metrics?.apikey_delete_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
================================================================================
测试完成
================================================================================
`;
}

View File

@@ -0,0 +1,440 @@
// Sub2API Gateway Performance Test
// Gateway API 性能测试
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Rate, Trend, Counter, Gauge } from 'k6/metrics';
import { config, getBaseUrl, getAuthToken } from '../common/utils.js';
import { httpGet, httpPost, randomSleep, generateTestAPIKeyName } from '../common/utils.js';
// ============= 自定义指标 =============
// Gateway 请求指标
const gatewayRequestDuration = new Trend('gateway_request_duration');
const gatewayTTFT = new Trend('gateway_ttft'); // Time To First Token
const gatewayTokenThroughput = new Trend('gateway_token_throughput');
const gatewayErrorRate = new Rate('gateway_errors');
// 平台分类指标
const platformMetrics = {
openai: {
duration: new Trend('gateway_openai_duration'),
errors: new Rate('gateway_openai_errors'),
},
claude: {
duration: new Trend('gateway_claude_duration'),
errors: new Rate('gateway_claude_errors'),
},
gemini: {
duration: new Trend('gateway_gemini_duration'),
errors: new Rate('gateway_gemini_errors'),
},
};
// ============= 测试配置 =============
export const options = {
scenarios: {
gateway_load: {
executor: 'ramping-vus',
startVUs: 5,
stages: [
{ duration: '2m', target: 10 }, // 预热
{ duration: '5m', target: 50 }, // 正常负载
{ duration: '2m', target: 100 }, // 峰值负载
{ duration: '2m', target: 0 }, // 冷却
],
},
},
thresholds: {
// Gateway 整体阈值
'gateway_request_duration': ['p(95)<2000', 'p(99)<5000'],
'gateway_errors': ['rate<0.05'], // 5% 以下错误率
// TTFT 阈值
'gateway_ttft': ['p(95)<3000', 'p(99)<5000'],
// 按平台分类
'gateway_openai_duration': ['p(95)<1500'],
'gateway_claude_duration': ['p(95)<2000'],
'gateway_gemini_duration': ['p(95)<1500'],
},
};
// ============= 测试数据准备 =============
// 获取测试用 API Key
function getTestAPIKey() {
const token = getAuthToken();
// 列出 API Keys 获取第一个
const res = http.get(`${getBaseUrl()}/api/v1/keys`, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
tags: { name: 'list_api_keys' },
});
if (res.status === 200) {
const keys = res.json('data');
if (keys && keys.length > 0) {
return keys[0].key;
}
}
// 如果没有 API Key创建一个
const createRes = http.post(
`${getBaseUrl()}/api/v1/keys`,
JSON.stringify({
name: `perf-test-${Date.now()}`,
}),
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
tags: { name: 'create_api_key' },
}
);
if (createRes.status === 201) {
return createRes.json('key');
}
throw new Error('Failed to get or create API key for testing');
}
// ============= 测试场景 =============
/**
* 测试 OpenAI Chat Completions
*/
function testOpenAIChat(apiKey) {
const start = Date.now();
const res = http.post(
`${getBaseUrl()}/v1/chat/completions`,
JSON.stringify({
model: 'gpt-3.5-turbo',
messages: [
{ role: 'user', content: 'Say hello in one sentence.' }
],
max_tokens: 50,
temperature: 0.7,
}),
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
tags: { name: 'gateway_openai_chat' },
}
);
const duration = Date.now() - start;
// 记录指标
platformMetrics.openai.duration.add(duration);
platformMetrics.openai.errors.add(res.status !== 200);
check(res, {
'OpenAI Chat: status is 200': (r) => r.status === 200,
'OpenAI Chat: has content': (r) => r.json('choices[0].message.content') !== undefined,
});
return res;
}
/**
* 测试 OpenAI Streaming
*/
function testOpenAIStream(apiKey) {
const start = Date.now();
let firstTokenTime = 0;
let tokenCount = 0;
const res = http.post(
`${getBaseUrl()}/v1/chat/completions`,
JSON.stringify({
model: 'gpt-3.5-turbo',
messages: [
{ role: 'user', content: 'Count from 1 to 5.' }
],
max_tokens: 100,
stream: true,
}),
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
tags: { name: 'gateway_openai_stream' },
}
);
const duration = Date.now() - start;
// 解析 SSE 流获取 TTFT
if (res.headers['Content-Type']?.includes('text/event-stream')) {
const lines = res.body.split('\n');
for (const line of lines) {
if (line.startsWith('data: ') && !line.includes('[DONE]')) {
if (firstTokenTime === 0) {
firstTokenTime = Date.now();
}
tokenCount++;
}
}
}
const ttft = firstTokenTime > 0 ? firstTokenTime - start : duration;
// 记录指标
platformMetrics.openai.duration.add(duration);
platformMetrics.openai.errors.add(res.status !== 200);
gatewayTTFT.add(ttft);
if (tokenCount > 0) {
gatewayTokenThroughput.add(tokenCount / (duration / 1000));
}
check(res, {
'OpenAI Stream: status is 200': (r) => r.status === 200,
'OpenAI Stream: is streaming': (r) => r.headers['Content-Type']?.includes('text/event-stream'),
});
return res;
}
/**
* 测试 Claude Messages API
*/
function testClaudeMessages(apiKey) {
const start = Date.now();
const res = http.post(
`${getBaseUrl()}/v1/messages`,
JSON.stringify({
model: 'claude-3-5-haiku-20241022',
max_tokens: 50,
messages: [
{ role: 'user', content: 'Say hello in one sentence.' }
],
}),
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
},
tags: { name: 'gateway_claude_messages' },
}
);
const duration = Date.now() - start;
// 记录指标
platformMetrics.claude.duration.add(duration);
platformMetrics.claude.errors.add(res.status !== 200);
check(res, {
'Claude Messages: status is 200': (r) => r.status === 200,
'Claude Messages: has content': (r) => r.json('content[0].text') !== undefined,
});
return res;
}
/**
* 测试 Gemini Generate Content
*/
function testGeminiGenerate(apiKey) {
const start = Date.now();
const res = http.post(
`${getBaseUrl()}/v1beta/models/gemini-pro:generateContent`,
JSON.stringify({
contents: [
{
parts: [{ text: 'Say hello in one sentence.' }]
}
],
generationConfig: {
maxOutputTokens: 50,
},
}),
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
tags: { name: 'gateway_gemini_generate' },
}
);
const duration = Date.now() - start;
// 记录指标
platformMetrics.gemini.duration.add(duration);
platformMetrics.gemini.errors.add(res.status !== 200);
check(res, {
'Gemini Generate: status is 200': (r) => r.status === 200,
'Gemini Generate: has content': (r) => r.json('candidates[0].content.parts[0].text') !== undefined,
});
return res;
}
/**
* 综合 Gateway 测试
*/
function testGatewayMixed(apiKey) {
// 模拟真实用户行为70% 非流式 + 30% 流式
const rand = Math.random();
if (rand < 0.3) {
return testOpenAIStream(apiKey);
} else if (rand < 0.5) {
return testOpenAIChat(apiKey);
} else if (rand < 0.7) {
return testClaudeMessages(apiKey);
} else {
return testGeminiGenerate(apiKey);
}
}
// ============= 主测试函数 =============
export default function () {
// 提前获取 API Key每个 VU 一次)
const apiKey = __ITER__ === 0 ? getTestAPIKey() : null;
// 或者使用全局缓存
if (!globalThis.__testApiKey__) {
globalThis.__testApiKey__ = getTestAPIKey();
}
group('Gateway API Performance', () => {
// 随机选择测试场景
const testType = __VU % 4;
switch (testType) {
case 0:
testOpenAIChat(globalThis.__testApiKey__);
break;
case 1:
testOpenAIStream(globalThis.__testApiKey__);
break;
case 2:
testClaudeMessages(globalThis.__testApiKey__);
break;
case 3:
testGeminiGenerate(globalThis.__testApiKey__);
break;
default:
testGatewayMixed(globalThis.__testApiKey__);
}
});
// 模拟用户思考时间
randomSleep(0.5, 2);
}
// ============= 测试结束清理 =============
export function handleSummary(data) {
return {
'gateway-performance-report.json': JSON.stringify(data, null, 2),
'gateway-performance-summary.txt': generateSummary(data),
};
}
function generateSummary(data) {
const metrics = data.metrics;
return `
================================================================================
Sub2API Gateway 性能测试报告
================================================================================
测试时间: ${new Date().toISOString()}
测试持续: ${(data.state.testRunDurationMs / 1000 / 60).toFixed(2)} 分钟
峰值 VU: ${data.state.metrics?.vus?.peak || 0}
--------------------------------------------------------------------------------
核心指标
--------------------------------------------------------------------------------
总请求数: ${metrics?.requests_total?.values?.count || 0}
成功请求: ${metrics?.requests_total?.values?.passes || 0}
失败请求: ${metrics?.requests_total?.values?.fails || 0}
错误率: ${((metrics?.gateway_errors?.values?.rate || 0) * 100).toFixed(2)}%
平均响应时间: ${metrics?.gateway_request_duration?.values?.avg?.toFixed(2) || 0} ms
P50 响应时间: ${metrics?.gateway_request_duration?.values?.['p(50)']?.toFixed(2) || 0} ms
P95 响应时间: ${metrics?.gateway_request_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
P99 响应时间: ${metrics?.gateway_request_duration?.values?.['p(99)']?.toFixed(2) || 0} ms
最大响应时间: ${metrics?.gateway_request_duration?.values?.max?.toFixed(2) || 0} ms
--------------------------------------------------------------------------------
TTFT (Time To First Token)
--------------------------------------------------------------------------------
平均 TTFT: ${metrics?.gateway_ttft?.values?.avg?.toFixed(2) || 0} ms
P95 TTFT: ${metrics?.gateway_ttft?.values?.['p(95)']?.toFixed(2) || 0} ms
P99 TTFT: ${metrics?.gateway_ttft?.values?.['p(99)']?.toFixed(2) || 0} ms
--------------------------------------------------------------------------------
按平台分类
--------------------------------------------------------------------------------
OpenAI:
平均响应时间: ${metrics?.gateway_openai_duration?.values?.avg?.toFixed(2) || 0} ms
P95 响应时间: ${metrics?.gateway_openai_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
错误率: ${((metrics?.gateway_openai_errors?.values?.rate || 0) * 100).toFixed(2)}%
Claude:
平均响应时间: ${metrics?.gateway_claude_duration?.values?.avg?.toFixed(2) || 0} ms
P95 响应时间: ${metrics?.gateway_claude_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
错误率: ${((metrics?.gateway_claude_errors?.values?.rate || 0) * 100).toFixed(2)}%
Gemini:
平均响应时间: ${metrics?.gateway_gemini_duration?.values?.avg?.toFixed(2) || 0} ms
P95 响应时间: ${metrics?.gateway_gemini_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
错误率: ${((metrics?.gateway_gemini_errors?.values?.rate || 0) * 100).toFixed(2)}%
--------------------------------------------------------------------------------
阈值检查
--------------------------------------------------------------------------------
${checkThresholds(data)}
================================================================================
测试完成
================================================================================
`;
}
function checkThresholds(data) {
const checks = [];
const thresholds = options.thresholds;
for (const [metric, threshold] of Object.entries(thresholds)) {
const actual = data.metrics?.[metric]?.values;
if (!actual) continue;
const p95 = actual['p(95)'];
const thresholdValue = parseFloat(threshold[0]);
if (p95 !== undefined) {
const passed = p95 <= thresholdValue;
checks.push(` ${passed ? 'OK' : 'FAIL'} ${metric}: P95=${p95.toFixed(2)}ms (threshold: ${thresholdValue}ms)`);
}
}
return checks.join('\n');
}

View File

@@ -0,0 +1,101 @@
// Sub2API Health Check Performance Test
// 健康检查接口性能测试
import http from 'k6/http';
import { check, sleep, group } from 'k6';
import { Rate, Trend, Counter } from 'k6/metrics';
import { getBaseUrl } from '../common/utils.js';
// ============= 自定义指标 =============
const healthCheckDuration = new Trend('health_check_duration');
const healthErrorRate = new Rate('health_errors');
// ============= 测试配置 =============
export const options = {
scenarios: {
health_load: {
executor: 'ramping-vus',
startVUs: 5,
stages: [
{ duration: '30s', target: 20 },
{ duration: '2m', target: 50 },
{ duration: '30s', target: 0 },
],
},
},
thresholds: {
'health_check_duration': ['p(95)<100', 'p(99)<200'],
'health_errors': ['rate<0.001'],
},
};
// ============= 测试场景 =============
export default function () {
group('Health Check', () => {
const start = Date.now();
// 健康检查端点
const res = http.get(`${getBaseUrl()}/health`, {
tags: { name: 'health_check' },
});
const duration = Date.now() - start;
healthCheckDuration.add(duration);
check(res, {
'Health: status is 200': (r) => r.status === 200,
'Health: has status field': (r) => r.json('status') !== undefined,
'Health: response time < 100ms': () => duration < 100,
});
healthErrorRate.add(res.status !== 200);
// 可选:详细健康检查
if (__VU % 10 === 0) {
const detailedRes = http.get(`${getBaseUrl()}/health/detailed`, {
tags: { name: 'health_detailed' },
});
check(detailedRes, {
'Detailed Health: status is 200': (r) => r.status === 200,
});
}
});
// 健康检查通常不需要太长的思考时间
sleep(0.01);
}
export function handleSummary(data) {
return {
'health-performance-report.json': JSON.stringify(data, null, 2),
'health-performance-summary.txt': `
================================================================================
Sub2API Health Check 性能测试报告
================================================================================
测试时间: ${new Date().toISOString()}
测试持续: ${(data.state.testRunDurationMs / 1000 / 60).toFixed(2)} 分钟
--------------------------------------------------------------------------------
核心指标
--------------------------------------------------------------------------------
总请求数: ${data.metrics?.requests_total?.values?.count || 0}
错误率: ${((data.metrics?.health_errors?.values?.rate || 0) * 100).toFixed(3)}%
平均响应时间: ${data.metrics?.health_check_duration?.values?.avg?.toFixed(2) || 0} ms
P50 响应时间: ${data.metrics?.health_check_duration?.values?.['p(50)']?.toFixed(2) || 0} ms
P95 响应时间: ${data.metrics?.health_check_duration?.values?.['p(95)']?.toFixed(2) || 0} ms
P99 响应时间: ${data.metrics?.health_check_duration?.values?.['p(99)']?.toFixed(2) || 0} ms
最大响应时间: ${data.metrics?.health_check_duration?.values?.max?.toFixed(2) || 0} ms
================================================================================
测试完成
================================================================================
`,
};
}

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