一、先聊聊 Locust 的默认界面
用过 Locust 的朋友应该都有同感:它自带的 Web 界面简单实用,能看每秒请求数、响应时间、失败率这些核心指标。但真要在实际项目里把它当成一个正式的压力测试平台来用,就有点不够用了。比如想看某个接口的 P99 延迟趋势、想按时间范围筛选数据、想对比多台压测机的指标,默认界面就给不了了。这时候就得自己动手,给它扩展一个监控面板。
很多人一听“自定义开发”就觉得难,其实要分情况。如果你只是想改改样式,那确实不复杂;但如果你想在界面上加一个完全自定义的监控面板,那就得从前端层面动手了。下面我会用一套比较直观的思路,带着大家一步步把这事做明白。
二、从哪下手:Locust 的前端是拆开的
Locust 的 Web 界面其实分两部分:一部分是后端提供的静态文件,另一部分是运行时从接口拿数据。默认的页面是用 Vue 2 写的,打包后放在 locust/web/dist 目录里。所以我们要做的不是去改源码,而是自己写一个独立的前端页面,然后让它去读 Locust 暴露的 HTTP API。这样既不破坏原有功能,又能自由扩展。
2.1 Locust 提供的 API 够不够用
Locust 默认有几个关键的接口,比如 /stats/requests 可以拿到每个请求类型的统计信息,/stats/distribution 可以拿到响应时间分布,/exceptions 能拿异常信息。这些接口返回的都是 JSON,随便用什么前端框架都能消费。我们只需要在自己的监控面板里定时请求这些接口,然后把数据画成图表就行。
你可以先在浏览器里访问一下 http://localhost:8089/stats/requests,看看返回的数据结构。大致是这样:
{
"stats": [
{
"name": "/api/login",
"method": "POST",
"num_requests": 4521,
"num_failures": 12,
"median_response_time": 65,
"average_response_time": 72.4,
"min_response_time": 10,
"max_response_time": 890,
"avg_content_length": 220,
"current_rps": 45.2,
"current_fail_per_sec": 0.1
}
],
"total_rps": 156.8,
"total_fail_per_sec": 0.3
}
可以看到每一项都是我们想要的关键数据。所以“难”不在数据获取,而在于怎么把数据变成直观的图表,以及怎么让页面好用。
三、自己搭一个监控面板:技术选型和整体设计
我这边选用的技术栈是 Vue 3 + JavaScript + Vite + ECharts。为什么这么选?因为 Vue 3 是目前社区非常活跃的前端框架,写起来舒服;ECharts 是图表库里的老大哥,功能全、文档多。下面所有示例都是基于这个技术栈,我会把关键代码完整地贴出来。
3.1 项目的整体结构
我们先规划一下面板的布局。左边是一个侧边栏,显示压测任务的基本信息;中间是几个大图表,分别显示 RPS(每秒请求数)、响应时间、失败率;底部是一个表格,展示每个接口的详细指标。为了简单起见,我用单页组件的方式来实现。
3.2 第一步:搭一个基本的前端脚手架
首先创建一个 Vite + Vue 项目(这里我用命令行创建,注意所有命令都写在代码块里):
# 创建一个名为 locust-monitor 的 Vue 3 项目
npm create vite@latest locust-monitor -- --template vue
# 进入项目目录
cd locust-monitor
# 安装依赖
npm install
# 安装 ECharts 和 axios(用于请求数据)
npm install echarts axios
装完之后,项目里会有 src 目录,我们主要在这里写代码。
3.3 第二步:写一个负责获取数据的模块
为了让代码清晰一点,我们单独写一个 API 模块,用来请求 Locust 的接口。这里要注意,前端的开发服务器和 Locust 的 Web 地址不是同一个,所以需要配置代理,或者在后端允许跨域。我建议配置 Vite 的代理,这样更安全,也省去跨域带来的麻烦。
先看 vite.config.js 的配置:
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
plugins: [vue()],
server: {
proxy: {
// 把 /locust-api 开头的请求转发到 Locust 的地址
'/locust-api': {
target: 'http://localhost:8089',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/locust-api/, '')
}
}
}
});
这样我们在前端代码里就可以直接请求 /locust-api/stats/requests 了,不会遇到跨域问题。
接下来是 src/api/locust.js 这个文件:
import axios from 'axios';
// 创建一个 axios 实例,统一设置超时时间
const http = axios.create({
baseURL: '/locust-api',
timeout: 5000
});
// 获取统计数据,如果提供了 name 参数,就只获取那个请求的统计
export function fetchRequests(name = '') {
return http.get('/stats/requests', {
params: { name }
});
}
// 获取异常信息
export function fetchExceptions() {
return http.get('/exceptions');
}
// 获取当前任务信息,比如并发数、运行状态
export function fetchTasks() {
return http.get('/tasks');
}
// 获取所有测试数据的总览,包括用户数、状态等
export function fetchOverview() {
return http.get('/stats/requests/overview');
}
3.4 第三步:做一个可以自动刷新的数据面板
监控面板最关键的就是“动起来”。我们要每隔几秒去拉一次数据,然后更新图表。这里我用 Vue 3 的 onMounted 和 setInterval 来实现定时刷新。同时,为了避免组件卸载后定时器还在跑,也要在 onUnmounted 里清理掉。
下面是一个完整的组件示例,注意看注释。
<template>
<div class="dashboard">
<!-- 顶部显示总体情况 -->
<div class="overview">
<p>总 RPS:{{ totalRps }}</p>
<p>总失败率:{{ totalFailPerSec }}</p>
<p>当前用户数:{{ userCount }}</p>
</div>
<!-- RPS 走势图 -->
<div ref="rpsChart" class="chart" style="height: 300px;"></div>
<!-- 接口延迟表格 -->
<table>
<thead>
<tr>
<th>接口名</th>
<th>请求次数</th>
<th>中位数延迟(ms)</th>
<th>平均延迟(ms)</th>
<th>失败次数</th>
</tr>
</thead>
<tbody>
<tr v-for="item in statsList" :key="item.name">
<td>{{ item.method }} {{ item.name }}</td>
<td>{{ item.num_requests }}</td>
<td>{{ item.median_response_time }}</td>
<td>{{ item.average_response_time }}</td>
<td>{{ item.num_failures }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import * as echarts from 'echarts';
import { fetchRequests, fetchOverview } from '../api/locust';
// 定义响应式变量
const totalRps = ref(0);
const totalFailPerSec = ref(0);
const userCount = ref(0);
const statsList = ref([]);
// 图表实例和 DOM 引用
const rpsChart = ref(null);
let rpsChartInstance = null;
let timer = null;
// 定义时间序列数据,用于 x 轴
const timePoints = [];
const rpsData = [];
// 封装刷新数据的方法
async function refreshData() {
try {
// 并行请求两个接口,提升效率
const [requestsRes, overviewRes] = await Promise.all([
fetchRequests(),
fetchOverview()
]);
// 保存统计结果到表格数据
statsList.value = requestsRes.data.stats;
// 保存总 RPS 和失败率
totalRps.value = requestsRes.data.total_rps.toFixed(2);
totalFailPerSec.value = requestsRes.data.total_fail_per_sec.toFixed(2);
// 保存用户数
userCount.value = overviewRes.data.user_count;
// 构造时间点,显示到秒即可
const now = new Date();
const timeStr = now.toTimeString().split(' ')[0];
timePoints.push(timeStr);
// 保留最近 30 个点,防止图表无限增长
if (timePoints.length > 30) {
timePoints.shift();
}
// 把最新的 RPS 存进数组,并保证和 x 轴长度一致
rpsData.push(requestsRes.data.total_rps);
if (rpsData.length > 30) {
rpsData.shift();
}
// 更新图表
updateRpsChart();
} catch (error) {
console.error('刷新数据失败', error);
}
}
// 绘制 RPS 走势图
function updateRpsChart() {
if (!rpsChartInstance) {
// 初始化图表实例,只需要做一次
rpsChartInstance = echarts.init(rpsChart.value);
}
// 设置图表的配置项和数据
rpsChartInstance.setOption({
title: { text: '每秒请求数(RPS)' },
tooltip: { trigger: 'axis' },
xAxis: {
type: 'category',
data: timePoints
},
yAxis: {
type: 'value',
name: 'RPS'
},
series: [
{
name: 'RPS',
type: 'line',
smooth: true,
data: rpsData
}
]
});
}
// 组件挂载完成后开始定时刷新
onMounted(() => {
refreshData(); // 立即刷新一次
timer = setInterval(refreshData, 3000); // 然后每 3 秒刷新
});
// 组件卸载时清理定时器
onUnmounted(() => {
if (timer) {
clearInterval(timer);
}
// 如果图表实例存在,销毁它避免内存泄漏
if (rpsChartInstance) {
rpsChartInstance.dispose();
rpsChartInstance = null;
}
});
</script>
<style scoped>
.dashboard {
padding: 20px;
}
.overview {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.chart {
width: 100%;
border: 1px solid #eee;
border-radius: 4px;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 6px 10px;
text-align: left;
}
</style>
这样一个能实时刷新、带折线图和表格的基础监控面板就有了。你可以把 App.vue 里的默认内容替换成这个组件,然后运行 npm run dev 启动前端,再启动 Locust 压测,就能看到数据在跳动了。
3.5 第四步:把面板变得更有用一点
上面的示例只是开了个头,实际使用中你可能会想加上以下这些功能:
3.5.1 按接口筛选
当接口特别多的时候,所有线画在一起会非常乱。我们可以加一个下拉框,选择只看某个接口的 RPS 和延迟。这里不写完整代码,只给一个核心思路:在 refreshData 里多传一个 name 参数给接口,然后数据只保留那一个接口的。
// 在 data 里加一个 selectedName
const selectedName = ref('');
// 改造 refreshData 函数
async function refreshData() {
// 注意这里把 selectedName 传进去了
const requestsRes = await fetchRequests(selectedName.value);
// 其他逻辑不变
}
然后在模板里加一个下拉框绑定 selectedName,@change 事件里调用一次 refreshData 就行。
3.5.2 显示响应时间分布
Locust 的 /stats/distribution 接口可以拿到类似“1ms 内 100 次、5ms 内 300 次”这样的分布数据。我们用 ECharts 的饼图或柱状图画出来,能看到大多数请求的耗时集中在哪里。这对于调优接口很有帮助。
import { fetchDistribution } from '../api/locust';
// 在 locust.js 里补上
export function fetchDistribution() {
return http.get('/stats/distribution');
}
// 在组件中调用
async function loadDistribution() {
const res = await fetchDistribution();
// 处理 res.data 的数据结构,转换成 ECharts 需要的格式
// 然后 setOption 绘制饼图
}
3.5.3 自定义时间范围
默认接口只能拿当前统计,不过 Locust 有一个内存中的历史数据,可以通过 start_time 和 end_time 参数去拿,如果你不想依赖后端,也可以自己在前端存历史数据。我就简单介绍一下思路:用一个数组把每次查到的 total_rps 和 time 存下来,存 100 条,然后提供一个“清空”按钮,或者画一个历史区间条。这个方案实现起来很简单,对压测这种场景已经够了。
四、什么时候用这个方案更合适
自定义监控面板并不是所有场景都适合,我们得清楚它的好处和代价。
4.1 应用场景
比如你要做一个持续半小时的压测,期间你们团队领导想实时看一个“带公司 Logo 的、有品牌色调的、看着很专业”的大屏,那默认的 Locust 界面就撑不住场面了。这时候用自定义面板就非常合适。还有,如果你需要在压测的过程中同时观察服务器 CPU、内存、JVM 指标,想把它们和 Locust 的请求数据放在同一个页面上对比,那默认界面做不到,但自己的面板可以同时请求多个数据源,你想怎么整合都行。
再比如,你想在压测结束后自动生成一份带图表的报告,虽然 Locust 自带 CSV 导出,但报告页面得自己写,如果你之前已经做好了监控面板,直接把数据截图上去就行,省了不少事。
4.2 技术优缺点
先说优点:
- 灵活性高:你想加什么图表就加什么图表,不受默认界面的限制。
- 技术栈自由:不一定非要 Vue,React、Svelte 都行,反正只是请求 HTTP 接口。
- 可复用性强:写好的面板可以抽成模板,下次压测直接复用。
再说缺点:
- 开发成本:毕竟要写前端,如果团队里没有人会,那学习成本还是有点的。
- 维护成本:如果 Locust 接口的字段变了,你的面板也得跟着改。
- 数据实时性受限:Locust 的统计数据本身有一定的聚合延迟,你即时刷新也做不到毫秒级准确,但对压测场景来说足够了。
4.3 注意事项
有几个坑是很容易踩的,这里重点提一下。
第一,注意 Locust 的 Web 地址不能随便暴露到公网。你自己开发的面板如果部署在服务器上,一定要在前面加一层登录认证,否则很容易被人乱点操作压测任务。可以简单用 Nginx 加 Basic Auth,或者在后端加个 token 验证。
第二,定时器别开太猛。有的同学图省事,把刷新间隔设为 500 毫秒,结果压测还没开始,浏览器先给接口跑挂了。一般 2-3 秒比较合理,因为 Locust 的统计周期默认是秒级的,刷太快没意义。
第三,处理接口异常。压测过程中如果 Locust 服务重启了,或者端口被占用了,你的监控面板就会一直报错。这时候至少要给用户一个“数据暂时不可用”的提示,而不是让页面白屏。
第四,ECharts 实例的销毁问题。Vue 组件切换路由或者关闭页面时,如果不销毁图表实例,浏览器内存会一直涨。前面示例代码里已经写了 dispose,这一点很重要。
五、进阶:把监控面板变成一个小平台
如果你对前端比较熟,可以在这个基础上做更多事。比如把多个压测机的数据汇聚到一个面板上。原理很简单:Locust 支持 --master 和 --worker 模式,每个 worker 也有自己的 Web 接口,你在面板里配置好几个 worker 的地址,然后定时轮询它们的数据,最后用不同的颜色在同一个图表里展示。
再比如,你可以把压测历史记录存到数据库里,每次压测完自动生成报告链接。这里我简单展示一下前端如何把当前数据导出成 JSON,方便后端接收。
// 在 Vue 组件中引入一个方法
function exportCurrentData() {
// 假设 statsList 里是当前接口的统计数据
const payload = {
exportTime: new Date().toISOString(),
userCount: userCount.value,
totalRps: totalRps.value,
stats: statsList.value
};
// 用 Blob 生成一个可下载的 JSON 文件
const blob = new Blob([JSON.stringify(payload, null, 2)], {
type: 'application/json'
});
// 创建一个下载链接并自动点击
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = `locust-data-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(a.href);
}
这样你就有了一个“导出数据”按钮,后续想怎么处理都行。
六、总结一下
回到最初的问题,Locust 的 Web 界面自定义开发到底难不难?我的答案是:只要掌握了“前端框架 + HTTP API”这种组合,就一点都不难。真正难的可能是你自己不知道从哪一步开始下手。这篇文章给了一条明确的路径:先用 Vite 搭出前端项目,再通过代理解决跨域,然后写一个定时拉取数据的函数,最后用 ECharts 把数据可视化。示例代码虽然不长,但已经覆盖了最核心的知识点。
如果你之前没怎么写过前端,建议先照着示例代码跑起来,再慢慢改样式、加图表。如果已经会前端,那就可以直接按照这个思路去定制你自己的压测监控平台了。说白了,Locust 只是提供了数据源,怎么展示、怎么分析,完全看你的创造力。
这个东西一旦跑通,你会发现以后压测的日子里,再也不用盯着那个朴素的默认界面等了。一套顺手的监控面板,不仅能让汇报变得体面,还能帮你更迅速地发现性能问题,这才是它的真正价值。
评论
围绕“Locust Web界面自定义开发难?基于前端框架扩展监控面板的实现思路”参与讨论