// @ts-nocheck import http from 'k6/http'; import { check, sleep } from 'k6'; import { Rate, Trend } from 'k6/metrics'; const errorRate = new Rate('errors'); const responseTime = new Trend('response_time'); export const options = { stages: [ { duration: '1m', target: 50 }, // 1分钟内增加到50用户 { duration: '2m', target: 100 }, // 2分钟内增加到100用户 { duration: '3m', target: 200 }, // 3分钟内增加到200用户 { duration: '5m', target: 300 }, // 5分钟内增加到300用户(压力峰值) { duration: '2m', target: 100 }, // 2分钟内减少到100用户 { duration: '1m', target: 0 }, // 1分钟内减少到0 ], thresholds: { // 压力测试允许比负载测试更高的延迟;峰值 300 并发下 p95<2s、p99<3s 视为可接受 http_req_duration: ['p(95)<2000', 'p(99)<3000'], http_req_failed: ['rate<0.05'], // 错误率<5% errors: ['rate<0.05'], }, }; const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000'; const pages = [ '/', '/about', '/services', '/products', '/news', '/contact', ]; export default function () { const page = pages[Math.floor(Math.random() * pages.length)]; const res = http.get(`${BASE_URL}${page}`, { tags: { name: page }, }); // 记录非200响应的详细信息以便诊断混合渲染模式下的异常 if (res.status !== 200) { const bodyLength = res.body ? res.body.length : 0; console.warn(`[WARN] ${page} returned status ${res.status} (body length: ${bodyLength})`); } const success = check(res, { 'status is 200': (r) => r.status === 200, 'response time < 2000ms': (r) => r.timings.duration < 2000, 'body is not empty': (r) => r.body && r.body.length > 0, }); errorRate.add(!success); responseTime.add(res.timings.duration); sleep(Math.random() * 2 + 0.5); // 0.5-2.5秒随机等待 } export function handleSummary(data) { const metrics = data.metrics; const httpReqs = metrics?.http_reqs?.values || {}; const httpDuration = metrics?.http_req_duration?.values || {}; const httpFailed = metrics?.http_req_failed?.values || {}; // 构建按页面分组的统计(利用 k6 按 name 标签自动聚合的子指标) const perPage = {}; for (const page of pages) { const pageDur = metrics?.[`http_req_duration{name:${page}}`]?.values; const pageFail = metrics?.[`http_req_failed{name:${page}}`]?.values; perPage[page] = { avg_response_time: pageDur?.avg ?? null, p95_response_time: pageDur?.['p(95)'] ?? null, p99_response_time: pageDur?.['p(99)'] ?? null, max_response_time: pageDur?.max ?? null, error_rate: pageFail?.rate ?? null, total_requests: pageFail ? (pageFail.fails + pageFail.passes) : null, error_count: pageFail?.fails ?? null, }; } const summary = { type: 'stress-test', stages: [ { duration: '1m', target: 50, desc: 'Ramp up to 50 users' }, { duration: '2m', target: 100, desc: 'Ramp up to 100 users' }, { duration: '3m', target: 200, desc: 'Ramp up to 200 users' }, { duration: '5m', target: 300, desc: 'Peak load at 300 users' }, { duration: '2m', target: 100, desc: 'Scale down to 100 users' }, { duration: '1m', target: 0, desc: 'Scale down to 0' }, ], thresholds: { http_req_duration: ['p(95)<2000ms', 'p(99)<3000ms'], http_req_failed: ['rate<0.05'], errors: ['rate<0.05'], }, results: { overall: { total_requests: httpReqs.count || 0, error_rate: httpFailed.rate || 0, avg_response_time: httpDuration.avg || 0, p95_response_time: httpDuration['p(95)'] || 0, p99_response_time: httpDuration['p(99)'] || 0, max_response_time: httpDuration.max || 0, }, per_page: perPage, pages_tested: pages, }, checks_passed: data.root_group?.checks?.map((c) => ({ name: c.name, passes: c.passes, fails: c.fails, })), }; return { 'performance/stress-test-summary.json': JSON.stringify(summary, null, 2), }; }