提交 6ab2c268 authored 作者: 陈泽健's avatar 陈泽健

feat(performance): 性能测试场景增强 + 目标机监控 + 事务处理 + 多项修复

- 新增两种压测场景:burst(瞬时并发/集合点同步)和 endurance(长稳/多接口/CSV 参数化)
- 新增目标机资源监控(SSH 采集 CPU/内存/负载)+ Java 进程监控
- 新增事务处理时间(Transaction)多步骤串行 + 瀑布图/步骤明细表
- 新增批量执行监控页 BatchMonitor + 合并报告页 ProjectReport
- 新增项目管理(项目卡片/批量执行/一键执行)
- 报告页增强:场景标签(burst橙色/endurance红色)、apiSummary 多接口明细表
- 指标体系增强:P95/标准差/Apdex/延迟/连接时间/字节数/峰值TPS/错误分类
- 修复 3 个 P0:批量详情 API 500、残留 completed 状态、status_2xx 缺失
- 修复 body 字符串存储导致 4XX
- 新增 curl 导入、自定义登录凭据、读取系统配置 URL
- 压缩 HANDOFF 文档至 154 行
- 新增配套 PRD 需求文档与问题处理文档
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 f4892d6c
# 执行计划 - 修复执行机资源图表显示太窄
> **关联问题文档**: `_问题处理_执行机资源图表显示太窄.md`
> **创建日期**: 2026-08-21
> **预计总工时**: 0.5 天
> **修复类型**: 前端 UI 布局修复(无后端改动)
---
## 一、执行概述
本计划将「执行机资源图表显示太窄」问题分 2 个 Phase 修复:
1. **Phase 1**:三个页面的资源图表布局改为全宽堆叠(0.5 天)
2. **Phase 2**:构建验证 + 回归 + 文档更新(与 Phase 1 同日完成)
---
## 二、任务分解
### Phase 1:布局修复
**目标**:将资源趋势图从「左右并排(span 12)窄图」改为「上下堆叠(span 24)全宽图」。
| # | 任务 | 关键文件 | 验收标准 |
|---|------|---------|---------|
| 1.1 | MonitorPanel.vue 资源区块布局改造 | `frontend/src/views/performance/MonitorPanel.vue` | CPU&内存 + 网络各占全宽(span 24)上下堆叠,高度 280px |
| 1.2 | BatchMonitor.vue 资源区块布局改造 | `frontend/src/views/performance/BatchMonitor.vue` | 同上 |
| 1.3 | ReportPanel.vue 资源区块布局改造 | `frontend/src/views/performance/ReportPanel.vue` | 同上 |
**验收方式**:浏览器打开监控页/批量监控页/报告页,展开资源区块,确认两图全宽堆叠。
---
### Phase 2:构建验证 + 回归 + 文档
**目标**:验证修改无编译错误、功能不回归,并同步文档。
| # | 任务 | 关键文件 | 验收标准 |
|---|------|---------|---------|
| 2.1 | 前端构建验证 | `frontend/` | `npm run build` 无错误 |
| 2.2 | 功能回归 | MonitorPanel / BatchMonitor / ReportPanel | 折叠展开正常、无数据隐藏正常、图表渲染正常 |
| 2.3 | 更新 HANDOFF 文档 | `HANDOFF_性能测试.md` | 记录本次修复 |
**验收方式**:构建通过,手动回归通过。
---
## 三、验收标准总览
| 验收项 | 对应 Phase |
|--------|-----------|
| 监控页资源图全宽堆叠 | Phase 1 |
| 批量监控页资源图全宽堆叠 | Phase 1 |
| 报告页资源图全宽堆叠 | Phase 1 |
| npm run build 通过 | Phase 2 |
| 功能无回归 | Phase 2 |
---
## 四、关键实现细节
### 4.1 MonitorPanel.vue 修改点
**现状**(第 171-184 行):
```html
<!-- 资源趋势图 -->
<el-row :gutter="12">
<el-col :span="12">
<el-card shadow="never" class="chart-card">
<template #header><span>CPU &amp; 内存趋势 CPU / Memory</span></template>
<div ref="resourceChartRef" class="chart-sm" />
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="never" class="chart-card">
<template #header><span>网络趋势 Network(发送 / 接收 KB/s)</span></template>
<div ref="networkChartRef" class="chart-sm" />
</el-card>
</el-col>
</el-row>
```
**改为**
```html
<!-- 资源趋势图(全宽堆叠) -->
<el-card shadow="never" class="chart-card">
<template #header><span>CPU &amp; 内存趋势 CPU / Memory</span></template>
<div ref="resourceChartRef" class="chart" />
</el-card>
<el-card shadow="never" class="chart-card">
<template #header><span>网络趋势 Network(发送 / 接收 KB/s)</span></template>
<div ref="networkChartRef" class="chart" />
</el-card>
```
> 说明:移除 `el-row/el-col` 包裹即可实现全宽;`chart-sm`(220px)改为 `chart`(280px),与主趋势图高度一致。
### 4.2 BatchMonitor.vue 修改点
同 MonitorPanel.vue(第 249-262 行),结构完全相同,同样改为全宽堆叠 + `chart` 类。
### 4.3 ReportPanel.vue 修改点
**现状**(第 322-335 行):
```html
<el-row :gutter="12">
<el-col :span="12">
<el-card shadow="never" class="chart-card">
<template #header><span>CPU &amp; 内存趋势 CPU / Memory</span></template>
<div ref="resourceChartRef" class="chart" />
</el-card>
</el-col>
<el-col :span="12">
<el-card shadow="never" class="chart-card">
<template #header><span>网络趋势 Network(发送 / 接收 KB/s)</span></template>
<div ref="networkChartRef" class="chart" />
</el-card>
</el-col>
</el-row>
```
**改为**:移除 `el-row/el-col` 包裹,两个 `el-card` 直接堆叠(`.chart-card` 自带 margin-bottom 12px)。
---
## 五、测试计划
### 手动测试
1. 监控页:展开「执行机资源」折叠面板 → 两图全宽堆叠,高度 280px
2. 批量监控页:同上
3. 报告页:加载含 resourceSummary 的报告 → 资源区块两图全宽堆叠
4. 无 resource 数据的任务 → 资源区块隐藏,不报错
5. 折叠面板默认折叠 → 点击展开正常
### 前端构建
```bash
cd frontend && npm run build
```
---
## 六、风险评估
| 风险 | 影响 | 缓解措施 |
|------|------|---------|
| 修改影响图表初始化逻辑 | 图表无法渲染 | 仅改模板布局与样式类,不动 JS 初始化逻辑 |
| 各页面结构差异 | 遗漏某页 | 三个文件逐一对比确认,全部覆盖 |
| 高度增加页面变长 | 滚动增加 | 可接受,资源区块默认折叠不影响主视图 |
---
## 七、后续工作
- [ ] 实施完成后更新 CLAUDE.md 当前进度
- [ ] 更新 HANDOFF_性能测试.md 交接文档
- [ ] Git 提交(Conventional Commits)
---
## 八、附录
### 修改文件清单
| 文件 | 操作 | 修改点 |
|------|------|--------|
| `frontend/src/views/performance/MonitorPanel.vue` | 修改 | 资源趋势图 span12 并排 → 全宽堆叠;chart-sm → chart |
| `frontend/src/views/performance/BatchMonitor.vue` | 修改 | 同上 |
| `frontend/src/views/performance/ReportPanel.vue` | 修改 | 资源趋势图 span12 并排 → 全宽堆叠 |
| `Docs/PRD/性能测试/问题处理/_问题处理_执行机资源图表显示太窄.md` | 新建 | 问题处理文档(本文档前置) |
---
*本文档为执行机资源图表过窄问题的修复执行计划。*
\ No newline at end of file
# 执行计划:修复会议模板任务 body 字符串存储导致 4XX 响应
> 生成时间:2026-08-21
> 关联文档:`_问题处理_会议模板任务body字符串存储导致4XX响应.md`
> 状态:📋 待执行
---
## 问题概述
通过「会议模板」接口预设(ApiPreset)创建的性能测试任务,执行时**全部请求返回 4XX**
**根因**:前端 `TaskList.vue``handleSave()` 第 769 行 `body: form.body || null` **未做 `JSON.parse()`**(对比 `ApiPresetDialog.vue` 第 295 行正确解析),导致 body 作为**字符串**(且含 Windows CMD `^` 转义残留)存入 MySQL JSON 列。执行时 `_resolve_body()` 返回字符串,`json=str` 被 aiohttp **双重序列化**,API 收到字符串字面量无法反序列化 → 4XX。
---
## 执行步骤
### Step 1: 前端修复 — TaskList.vue 补 JSON.parse
**文件**`frontend/src/views/performance/TaskList.vue``handleSave()`(约第 769 行)
**改动**
```typescript
// 修复前
body: form.body || null,
// 修复后:与 bodyTemplate 保持一致,JSON.stringify 存储的对象需反解析
body: form.body ? JSON.parse(form.body) : null,
```
**验收标准**
- 在 TaskList.vue 新建/编辑任务,body 填写合法 JSON 数组/对象
- 点击保存,`POST /api/performance/tasks` 请求体中 `body`**对象/数组**(而非字符串)
- 执行任务返回 2XX,不再 4XX
### Step 2: 后端防御修复 — 执行器字符串 body 反序列化
**文件**`backend/app/executors/performance_executor.py``_send_request()`(约第 1018 行)
**改动**:在 `session.request(json=...)` 之前,对字符串 body 做防御性 `json.loads`
```python
# 防御:字符串 body 反序列化回 JSON 对象(防止前端直传字符串的历史脏数据)
if isinstance(json_data, str):
try:
json_data = json.loads(json_data)
except (json.JSONDecodeError, TypeError):
logger.warning(f"任务 {task.id} body 不是合法 JSON 字符串,原样发送: {str(json_data)[:80]}")
pass
```
**验收标准**
- 对已存在的脏数据任务(body 为字符串)执行时,先 `json.loads` 再发送,请求体为 JSON 对象
- 非 JSON 字符串(如纯文本)保持原样发送,不抛异常
### Step 3: 数据库脏数据修复
**执行命令**(5.60 MySQL 容器内):
```bash
docker exec -it plat-auto-test-mysql mysql -uroot -p 平台数据库名 -e "
UPDATE performance_tasks
SET body = JSON_REMOVE(body, '$')
WHERE JSON_TYPE(body) = 'STRING'
AND JSON_UNQUOTE(body) LIKE '^[%'
AND id = 'perf_ecbd92aae3b845ab9bd651ca49ca111e';
"
```
> 说明:`JSON_REMOVE(body, '$')` 会去掉字符串外层引号,将 JSON 字符串转回 JSON 对象。执行前先备份或先 SELECT 验证。
**验收标准**
- `SELECT JSON_TYPE(body), body FROM performance_tasks WHERE id='perf_ecbd92...'` 显示 `JSON_TYPE``ARRAY`/`OBJECT`(不再 STRING)
- `^` 字符残留需在 Step 4 的代码层清洗或手动更新
### Step 4: 前端构建
```bash
cd frontend && npm run build
```
**验收标准**:构建成功,无 TypeScript 类型错误。
### Step 5: 部署到 5.60
```bash
scp backend/app/executors/performance_executor.py ubains@192.168.5.60:/data/third_party/plat-auto-test/backend/app/executors/
scp -r frontend/dist ubains@192.168.5.60:/data/third_party/plat-auto-test/frontend/
ssh ubains@192.168.5.60 "cd /data/third_party/plat-auto-test/deploy && docker compose restart app"
```
**验收标准**
- 容器重启成功(`Container plat-auto-test-app Started`
- `grep -n 'json.loads' backend/app/executors/performance_executor.py` 确认防御代码已部署
- `grep -n 'JSON.parse(form.body)' frontend/dist/assets/index-*.js` 确认前端已部署
### Step 6: 端到端验证
1. 浏览器 **Ctrl+F5** 强刷前端
2. 项目管理 → 新建任务 → body 填写 JSON 数组(模拟会议模板 insertByFkeyId)
3. 保存并执行
4. 监控页确认:**status_2xx > 0,status_4xx = 0**
5. 对问题任务 `perf_ecbd92...` 重新执行,确认 4XX 消失
---
## 改动清单
| 文件 | 类型 | 说明 |
|------|------|------|
| `frontend/src/views/performance/TaskList.vue` | **MODIFY** | `handleSave()` body 加 `JSON.parse()`(1 行) |
| `backend/app/executors/performance_executor.py` | **MODIFY** | `_send_request()` 字符串 body 防御性 `json.loads`(+6 行) |
| `performance_tasks` 表 | **DATA** | 清洗问题任务 body 脏数据(`JSON_REMOVE`) |
| `Docs/.../_问题处理_会议模板任务body字符串存储导致4XX响应.md` | **NEW** | 问题处理文档 |
| `Docs/.../_执行计划_修复会议模板任务body字符串存储导致4XX响应.md` | **NEW** | 本次执行计划 |
**无需改动**
- 后端 API 路由 / schema(`body: Optional[Any]` 本身无问题)
- `ApiPresetDialog.vue`(已有正确 JSON.parse)
- 数据库表结构
---
## 部署后回归
| 验证项 | 预期 |
|--------|------|
| TaskList.vue 新建任务(有 body) | 保存成功,body 为 JSON 对象,执行 2XX |
| TaskList.vue 新建任务(bodyTemplate) | 不受影响,bodyTemplate 正常 |
| ApiPresetDialog 创建预设 | 不受影响,仍正常 |
| 问题任务 perf_ecbd92...(脏数据) | 后端防御解析后正常发送 |
| 旧任务回归(会议 book PUT 等) | 正常执行 |
---
*本文档由 Claude Code 生成*
\ No newline at end of file
# 执行计划:修复批量监控页任务状态进入即显示「已完成」
> 生成时间:2026-08-21
> 关联文档:`_问题处理_批量监控页任务状态残留completed.md`
> 状态:✅ 已完成并部署
---
## 问题概述
点击「执行全部」跳转到 `BatchMonitor.vue`,任务列表**一进来就显示「已完成」**(带 TPS/响应时间数据),但顶部进度条显示「执行中」。
**根因**`create_batch_and_start()` 创建 batch 记录后、后台任务启动前,**没有将本次任务的状态重置为 pending**。前端在后台任务开始执行前的窗口期查询 `get_batch_details()`,读到的是上一次执行残留的 `completed` 状态。
**附带发现**`_run_batch_background()` 漏传 `batch_id`,导致后台创建第二个 batch 记录。
---
## 执行步骤
### Step 1: 后端 — 前置重置任务状态
**文件**`backend/app/services/performance_service.py``create_batch_and_start()`
**改动**:在 batch 记录提交后、后台任务启动前,重置任务状态为 pending:
```python
self.db.add(batch_record)
# 将批次内所有任务状态重置为 pending(避免读到上一次执行的残留 completed/failed 状态)
for task_id in task_ids:
task = await self.db.get(PerformanceTask, task_id)
if task and task.status in ("completed", "failed", "cancelled", "pending"):
task.status = "pending"
await self.db.commit()
```
**注意**:跳过 `running` 状态的任务,避免干扰其他正在执行的批量。
**验收标准**
- 点击「执行全部」后立即查询 batch-details,所有任务状态为 `pending`
- 正在 `running` 的任务不会被重置
### Step 2: 后端 — 修复漏传 batch_id
**文件**`backend/app/services/performance_service.py``_run_batch_background()`
**改动**
```python
await service.batch_run_tasks(
task_ids=task_ids,
project_id=project_id,
batch_id=batch_id, # 修复前:漏传,导致创建第二个 batch 记录
)
```
**验收标准**:每次批量执行只创建 1 条 batch 记录
### Step 3: 构建前端
```bash
cd frontend && npm run build
```
**验收标准**:构建成功,无 TypeScript 错误(本次前端无代码改动,仅重新构建部署)
### Step 4: 部署到 5.60
```bash
scp backend/app/services/performance_service.py ubains@192.168.5.60:.../backend/app/services/
scp -r frontend/dist ubains@192.168.5.60:.../frontend/
ssh ubains@192.168.5.60 "cd /data/third_party/plat-auto-test/deploy && docker compose restart app"
```
**验收标准**:容器重启成功,`grep -n 'task.status = .pending'` 确认修复代码已部署
### Step 5: 补充文档
- `Docs/PRD/性能测试/问题处理/_问题处理_批量监控页任务状态残留completed.md`(NEW)
- `Docs/PRD/性能测试/问题处理/_执行计划_修复批量监控页任务状态残留completed.md`(NEW)
---
## 改动清单
| 文件 | 类型 | 说明 |
|------|------|------|
| `backend/app/services/performance_service.py` | **MODIFY** | `create_batch_and_start()` 前置重置任务状态为 pending(+4 行) |
| `backend/app/services/performance_service.py` | **MODIFY** | `_run_batch_background()` 补传 `batch_id`(1 行) |
| `Docs/.../_问题处理_批量监控页任务状态残留completed.md` | **NEW** | 问题处理文档 |
| `Docs/.../_执行计划_修复批量监控页任务状态残留completed.md` | **NEW** | 本次执行计划 |
**无需改动**
- 前端代码(`BatchMonitor.vue` 逻辑本身正确,只是读到的数据有问题)
- 数据库结构 / schema
---
## 验证计划
1. 浏览器 **Ctrl+F5** 强刷
2. 进入项目管理 → 点击「执行全部」
3. 确认页面跳转到批量监控页后:
- 任务列表显示「等待中」(pending)而非「已完成」
- 进度条显示「执行中」,进度 0/N
4. 观察任务逐个流转:等待中 → 运行中 → 已完成
5. 确认全部完成后进度条 100%、显示「查看合并报告」
---
*本文档由 Claude Code 生成*
\ No newline at end of file
# 执行计划:修复批量详情 API 500 错误(actual_concurrency 字段缺失)
> 生成时间:2026-08-21
> 关联文档:`_问题处理_批量详情API500_actual_concurrency字段缺失.md`
> 状态:✅ 已完成并验证
---
## 问题概述
批量执行启动后,前端 `BatchMonitor.vue` 调用 `GET /api/performance/batch-details/{batch_id}` 返回 **500**,页面无法展示任务列表和实时监控。
**根因**`get_batch_details()` 访问 ORM 对象不存在的属性 `task.actual_concurrency`,抛出 `AttributeError``PerformanceTask` 模型只有 `concurrency`(配置并发数),没有 `actual_concurrency` 字段。
---
## 执行步骤
### Step 1: 本地定位并修复
**文件**`backend/app/services/performance_service.py`
**改动**`get_batch_details()` 中(约第 1648 行)
```python
# 修复前(报错)
"actual_concurrency": task.actual_concurrency,
# 修复后
"actual_concurrency": task.concurrency,
```
**验收标准**
- 本地运行 `python -c "from app.services.performance_service import PerformanceService; ..."` 无 AttributeError
- schema 与前端类型无需改动(`BatchTaskItem.actual_concurrency` 仅是响应字段名,本身正确)
### Step 2: 上传修复文件到 5.60 服务器
```bash
scp app/services/performance_service.py ubains@192.168.5.60:/data/third_party/plat-auto-test/backend/app/services/performance_service.py
```
**验收标准**:上传成功,无报错
### Step 3: 重启后端容器
```bash
ssh ubains@192.168.5.60 "cd /data/third_party/plat-auto-test/deploy && docker compose restart app"
```
**验收标准**`Container plat-auto-test-app Started`,无启动异常
### Step 4: 验证 API
```bash
# 用数据库中的真实 batch_id 验证
curl http://localhost/api/performance/batch-details/batch_xxx
# 不存在的 batch_id 验证 404 语义
curl http://localhost/api/performance/batch-details/nonexistent-id-123
```
**验收标准**
- 真实批次返回 200,`tasks[]` 内包含 `actualConcurrency` 字段
- 不存在批次返回 `{"detail":"批量执行记录不存在"}`
### Step 5: 补充文档
**文件**
- `Docs/PRD/性能测试/问题处理/_问题处理_批量详情API500_actual_concurrency字段缺失.md`(NEW)
- `Docs/PRD/性能测试/问题处理/_执行计划_修复批量详情API500_actual_concurrency字段缺失.md`(NEW)
**验收标准**:问题处理 + 执行计划文档配对齐全
---
## 改动清单
| 文件 | 类型 | 说明 |
|------|------|------|
| `backend/app/services/performance_service.py` | **MODIFY** | `get_batch_details()``task.actual_concurrency``task.concurrency`(1 行) |
| `Docs/.../_问题处理_批量详情API500_actual_concurrency字段缺失.md` | **NEW** | 问题处理文档(根因分析 + 修复 + 验证) |
| `Docs/.../_执行计划_修复批量详情API500_actual_concurrency字段缺失.md` | **NEW** | 本次执行计划 |
**无需改动**
- `schemas/performance.py``BatchTaskItem.actual_concurrency` 是正确响应字段名)
- `types/performance.ts``actualConcurrency` 前端类型正确)
- 前端代码(无前端问题)
---
## 验证结果
| 验证项 | 结果 | 说明 |
|--------|------|------|
| 容器重启 | ✅ | `docker compose restart app` 成功 |
| 真实批次详情 | ✅ 200 | 返回 projectName/status/tasks,任务含 actualConcurrency=10 |
| 不存在批次 | ✅ 404 语义 | `{"detail":"批量执行记录不存在"}` |
| 前端页面 | 待用户复验 | 强刷后进入批量监控页确认任务列表正常展示 |
---
## 教训
1. service 层访问 ORM 属性前,先核对 `models/*.py` 字段定义,不要凭 schema 或前端类型猜测字段来源。
2. 500 定位最快路径:容器日志搜 `AttributeError`,直接给出行号和属性名。
3. 修复闭环:改代码 → 上传 → 重启 → 真实数据调接口确认 200,缺一不可。
---
*本文档由 Claude Code 生成*
\ No newline at end of file
# 问题处理:会议模板新增任务 body 字符串存储导致 4XX 响应
> 处理时间:2026-08-21
> 现象报告人:用户(执行会议模板新增任务时)
> 关联文档:`_执行计划_修复会议模板任务body字符串存储导致4XX响应.md`(修复计划)
> 状态:🔍 根因已确定,待修复
---
## 1. 问题现象
用户在项目管理页面,通过「会议模板」接口预设(ApiPreset)创建性能测试任务后,执行该任务时**全部请求返回 4XX 状态码**
**问题任务**
| 字段 | 值 |
|------|-----|
| 任务 ID | `perf_ecbd92aae3b845ab9bd651ca49ca111e` |
| 接口预设 | `preset_meeting_insertByFkeyId`(POST /meetingV3/api/template/insertByFkeyId) |
| 请求数 | 16,215 次 |
| 状态码分布 | 全部 4XX(status_4xx=16215) |
| 断言 | 无(assertions=[]),所以 fail_count=0 |
| 并发/时长 | 10 并发,持续 60s |
---
## 2. 初步定位
### 2.1 容器日志确认
查询 5.60 服务器容器日志:
```bash
docker logs plat-auto-test-app 2>&1 | grep -E '4[0-9][0-9]|status_4|4XX|status.*4' | tail -20
```
日志显示:
- 登录成功(Token 获取正常)
- 任务执行正常完成,无异常堆栈
- 只有 count 数据展示 4XX
### 2.2 数据库查询
```sql
-- 查询问题任务 body 原始存储
SELECT id, name, body, body_template, method, target_url
FROM performance_tasks
WHERE id = 'perf_ecbd92aae3b845ab9bd651ca49ca111e';
```
**结果**`body` 列存储的内容为 **JSON 字符串**(含 `^` 转义字符),而非 JSON 对象/数组。
---
## 3. 根因分析
### 3.1 直接原因
**`_resolve_body()` 返回字符串 → `json=str` 双重序列化 → API 收到字符串字面量 → 反序列化失败 → 400/422**
执行引擎 `_send_request()` 的核心调用链:
```
_resolve_body(task, index) → 返回字符串 "[...]"
session.request(method, url, json=字符串) → aiohttp 内部 json.dumps(字符串)
→ 实际发送 HTTP body: "\"[...]\"" (带引号的字符串字面量)
→ API 期望 JSON 对象/数组,收到字符串 → 反序列化失败 → 4XX
```
### 3.2 前端根因:TaskList.vue 缺少 JSON.parse()
**问题代码**`frontend/src/views/performance/TaskList.vue` 第 769 行):
```typescript
const data: any = {
// ...
body: form.body || null, // ❌ 未 JSON.parse,直接发送原始字符串
bodyTemplate: form.bodyTemplate
? (typeof form.bodyTemplate === 'string' ? JSON.parse(form.bodyTemplate) : form.bodyTemplate)
: null, // ✅ bodyTemplate 正确解析了
// ...
}
```
**对比正确代码**`frontend/src/views/performance/ApiPresetDialog.vue` 第 295 行):
```typescript
data.body = JSON.parse(form.body) // ✅ 正确解析 JSON 字符串为对象
```
`bodyTemplate``JSON.parse()` 处理,但 `body` 没有——这是**不一致的编码遗漏**
### 3.3 数据流追踪
```
用户输入 JSON 字符串 body → 前端 form.body 为字符串
→ handleSave(): body: form.body || null → 未 JSON.parse
→ POST /api/performance/tasks → body 字段为字符串
→ Pydantic 接收字符串 → SQLAlchemy JSON 列存储字符串
→ MySQL 存储为 JSON 字符串字面量(带外层引号)
→ 执行时 _resolve_body() 返回字符串
→ json=字符串 → aiohttp 双重序列化
→ API 收到字符串字面量 → 4XX
```
### 3.4 `^` 字符的来源
问题任务 body 中包含 `^` 字符(如 `^[{^\"messageName^\":...`),这是**Windows CMD 环境的转义字符残留**
- `^` 在 CMD 中用于转义 `&` `|` `<` `>` 等特殊字符
- 用户可能从 CMD 窗口复制粘贴 curl 命令或 body 内容时引入了 `^`
另一个任务 `perf_82d72c5c17f94f9ebba1f899b41bc018` 的 URL 中也存在 `^&`(如 `pageNum=1^&pageSize=10`),验证了这一点。
### 3.5 为什么旧任务(8月13日前)没出问题
旧任务(如会议 book PUT)虽然 body 也是字符串,但那时**成功是因为 body 中的 `{__NOW__}` 占位符被正常替换**,且旧版本的执行引擎可能对字符串 body 做了额外处理。当前版本代码中,`_resolve_body()` 对字符串 body 不做二次解析,直接返回字符串,导致 `json=str` 双重序列化。
### 3.6 为什么没有断言失败
任务 `assertions=[]`(无断言),所有响应(包括 4XX)均被视为「成功响应」,所以 `fail_count=0`。如果任务配置了断言(如 `status == 200`),4XX 会被标记为失败,问题会更早暴露。
---
## 4. 影响范围
### 4.1 受影响的任务
通过 **TaskList.vue 页面创建**(非 ApiPresetDialog)且**使用 body** 的任务均受影响。具体判断标准:
| 条件 | 受影响 |
|------|--------|
| TaskList.vue 创建 + 有 body | ✅ 是 |
| ApiPresetDialog 创建(预设管理) | ❌ 否(有 JSON.parse) |
| GET 请求(无 body) | ❌ 否 |
| body 为 null/空 | ❌ 否 |
| 只使用 bodyTemplate | ❌ 否(bodyTemplate 有 JSON.parse) |
### 4.2 已确认受影响的任务
| 任务 ID | 名称 | URL | 问题 |
|---------|------|-----|------|
| `perf_ecbd92aae3b845ab9bd651ca49ca111e` | 会议模板新增 | POST insertByFkeyId | body 为字符串,含 `^` 字符,全部 4XX |
| `perf_82d72c5c17f94f9ebba1f899b41bc018` | 会议模板列表查询 | GET list(含 `^&`) | URL 含 `^&`,但 GET 请求无 body |
### 4.3 不涉及的范围
- **ApiPresetDialog 创建的预设**:不涉及
- **bodyTemplate 字段**:不涉及(前端已正确 JSON.parse)
- **GET 请求**:不涉及(`_resolve_body` 对 GET 返回 None)
- **后端执行引擎**:逻辑本身无 bug,只是对字符串 body 缺少防御性处理
---
## 5. 修复方案
### 5.1 前端修复(根因修复)
**`frontend/src/views/performance/TaskList.vue`**`handleSave()` 第 769 行:
```typescript
// 修复前
body: form.body || null,
// 修复后
body: form.body ? JSON.parse(form.body) : null,
```
### 5.2 后端防御性修复
**`backend/app/executors/performance_executor.py`**`_send_request()` 第 1018-1020 行:
```python
# 修复前:直接 json=json_data
async with session.request(method, url, headers=headers, json=json_data, ...)
# 修复后:如果 json_data 是字符串,尝试反序列化
if isinstance(json_data, str):
try:
json_data = json.loads(json_data)
except (json.JSONDecodeError, TypeError):
pass # 非 JSON 字符串,保持原样
async with session.request(method, url, headers=headers, json=json_data, ...)
```
### 5.3 数据库脏数据修复
```sql
-- 清理 body 中被错误存储为字符串的 JSON(去除 ^ 转义字符并重新解析)
UPDATE performance_tasks
SET body = JSON_REMOVE(body, '$')
WHERE JSON_TYPE(body) = 'STRING'
AND JSON_UNQUOTE(body) LIKE '^[%'
AND id = 'perf_ecbd92aae3b845ab9bd651ca49ca111e';
```
---
## 6. 经验总结
1. **三端 JSON 字段一致性**`body` 字段贯穿前端(表单输入)→ 后端(Pydantic schema)→ 数据库(JSON 列),前端提交时必须确保 `JSON.parse()`,否则整个链路断裂。
2. **编辑/创建页面代码对齐**`TaskList.vue``ApiPresetDialog.vue` 都涉及 body 字段的编辑提交,但一处有 JSON.parse 一处没有——**同类型字段的提交逻辑应统一处理,避免复制遗漏**
3. **Windows CMD `^` 转义**:涉及 Windows CMD 环境的数据输入,需提示用户不要从 CMD 窗口直接复制粘贴包含 `^` 的内容。
4. **断言配置的重要性**:无断言的任务即使全部 4XX 也不会标记为失败,建议在创建任务时默认添加 `status == 200` 断言。
5. **执行引擎防御性编程**`json=str` 应该做 `json.loads(str)` 回退,防止前端错误传播到 API 请求。
---
*本文档由 Claude Code 生成*
\ No newline at end of file
# 问题处理 - 执行机资源图表显示太窄
> **文档类型**: 问题处理
> **创建日期**: 2026-08-21
> **模块**: 性能测试 - 执行机资源监控 UI
> **优先级**: P1
> **关联文档**: `_PRD_执行机资源监控.md`、`_PRD_执行机资源监控_计划执行.md`
---
## 一、问题描述
### 1.1 现象
压测任务执行中,在**实时监控页(MonitorPanel)****批量监控页(BatchMonitor)****报告页(ReportPanel)** 打开「执行机资源 Resource Monitor」区块时,**资源趋势图显示过窄**
- CPU & 内存趋势图与网络趋势图**并排显示**(各占 50% 宽度 `el-col :span="12"`),每个图仅约页面一半宽
- 资源曲线在窄图中难以辨认趋势细节(CPU 波动、内存爬升被横向压缩)
- 与上方 TPS / 响应时间图(独占全宽)对比明显失衡
### 1.2 影响
| 影响项 | 说明 |
|--------|------|
| 可读性 | 曲线被横向压缩,波动细节难以观察,影响瓶颈定位分析 |
| 一致性 | 其他趋势图均为全宽,资源图半宽显示风格不统一 |
| 操作体验 | 用户需反复缩放/悬停才能读取数据,交互成本高 |
### 1.3 截图对比(示意)
```
现状(半宽并排) 期望(全宽堆叠)
┌────────────────────┐ ┌──────────────────────────┐
│ CPU&内存 │ 网络 │ │ CPU & 内存趋势 │
│ (窄) │ (窄) │ │ (全宽) │
└────────────────────┘ ├──────────────────────────┤
│ 网络趋势(全宽) │
└──────────────────────────┘
```
---
## 二、问题定位
### 2.1 根因分析
资源趋势图在两个页面中均使用 `el-col :span="12"` 左右并排布局,导致每个图表只占半屏宽度:
| 文件 | 位置 | 问题代码 |
|------|------|---------|
| `frontend/src/views/performance/MonitorPanel.vue` | 第 171-184 行 | `<el-col :span="12">` × 2,resourceChartRef / networkChartRef 并排 |
| `frontend/src/views/performance/BatchMonitor.vue` | 第 249-262 行 | `<el-col :span="12">` × 2,与 MonitorPanel 相同的并排结构 |
| `frontend/src/views/performance/ReportPanel.vue` | 第 322-335 行 | `<el-col :span="12">` × 2,resourceChartRef / networkChartRef 并排 |
`el-col :span="12"` 表示占据 24 栅格中的 12 格 = 50% 宽度。两图并排时彼此各占一半,图表区域被横向压缩。
### 2.2 同类对比
监控页 / 批量监控页 / 报告页的**主趋势图**(TPS、响应时间、状态码、延迟、并发)均为:
- 全宽(`:span="24"` 或整行独占)
- 高度 280px(`.chart`
而资源区块的图表:
- 半宽(`:span="12"`
- 高度 220px(`.chart-sm`
---
## 三、修复方案
### 3.1 方案:资源图表改为全宽堆叠
| 项 | 现状 | 修复后 |
|----|------|--------|
| 布局 | CPU&内存 + 网络 左右并排(span 12 + 12) | 上下堆叠(span 24 + 24) |
| 图表高度 | 220px(.chart-sm) | 280px(.chart,与其他趋势图一致) |
| 样式类 | chart-sm | chart |
### 3.2 修改点
| 文件 | 修改内容 |
|------|---------|
| `MonitorPanel.vue` | 资源区块 `el-row` 改为两个独立 `el-row`,每个 `el-col :span="24"`;class 由 `chart-sm` 改为 `chart`(280px) |
| `BatchMonitor.vue` | 同上 |
| `ReportPanel.vue` | 资源区块 `el-row :span="12"` 改为 `span="24"` 堆叠;class 由 `chart` 保持 280px |
### 3.3 不改动项
- 后端数据(resourceSummary 结构)不变
- ECharts 配置项不变
- 折叠面板(默认折叠)行为不变
- 资源卡片(CPU/内存/网络 4 卡片)布局不变
---
## 四、验收标准
| # | 验收项 | 通过标准 |
|---|--------|---------|
| 1 | MonitorPanel 资源区块 | CPU&内存趋势图、网络趋势图各占全宽,上下堆叠,高度 280px |
| 2 | BatchMonitor 资源区块 | 同上 |
| 3 | ReportPanel 资源区块 | 同上 |
| 4 | 前端构建 | `npm run build` 无错误 |
| 5 | 回归 | 折叠展开正常、无数据时区块隐藏、图表渲染正常 |
---
## 五、实施进度
- [ ] 编写问题处理文档(本文档)
- [ ] 输出计划执行文档
- [ ] 修改 MonitorPanel.vue
- [ ] 修改 BatchMonitor.vue
- [ ] 修改 ReportPanel.vue
- [ ] 前端构建验证
- [ ] 更新 HANDOFF 文档
---
*本文档为执行机资源图表过窄问题的分析与处理记录。*
\ No newline at end of file
# 问题处理:批量监控页任务状态进入即显示「已完成」
> 处理时间:2026-08-21
> 现象报告人:用户(点击「执行全部」跳转批量监控页后)
> 关联文档:`_PRD_批量执行跳转监控页.md`
> 状态:✅ 已修复并部署
---
## 1. 问题现象
用户点击「执行全部」后跳转到 `BatchMonitor.vue` 页面,发现:
- **任务列表**中所有任务状态**立即显示「已完成」**(绿色标签),TPS / 响应时间有数据
- **顶部进度条**却显示「执行中」,`doneCount` / `totalCount` 的比例也可能不对
预期行为:任务进入时应为「等待中」→ 逐个变为「运行中」→「已完成」。
---
## 2. 初步定位
### 2.1 数据流向
```
用户点击「执行全部」
→ Router: POST /api/performance/projects/{id}/run-all
→ service.create_batch_and_start(task_ids, project_id)
→ 1. 创建 batch 记录 (status=running)
→ 2. asyncio.create_task(_run_batch_background(...))
→ 3. 立即返回 batch_id
→ 前端跳转: /performance/batch-monitor?batchId=xxx&projectId=xxx
→ BatchMonitor.onMounted() → loadBatchDetails()
→ GET /api/performance/batch-details/{batch_id}
→ service.get_batch_details() → 读取各 PerformanceTask 的当前 status
```
### 2.2 关键时间差
`create_batch_and_start()`**当前 event loop 的同步代码**中创建 batch 记录并启动 `asyncio.create_task()`,然后立即返回。
后台任务 `_run_batch_background()` 创建新的数据库会话、初始化 service、逐个调用 `run_task()` — 这些步骤**需要若干事件循环周期**
在后台任务尚未执行到第一个 `run_task()` 时,前端已经调用了 `GET /batch-details/{batch_id}`,此时各任务的 `status` 仍然是**上一次执行留下的残留值**
### 2.3 残留状态来源
`run_project_all()` 的查询条件:
```python
PerformanceTask.status.in_(["pending", "completed", "failed", "cancelled"])
```
即:只有处于这 4 种状态的任务才被纳入本次执行。但**纳入后并不会立即修改任务状态** — 状态修改发生在后续 `run_task()` 中(设为 `running``completed/failed`)。
所以,如果上一次批量执行已完成(任务全是 `completed`),本次再点「执行全部」时:
| 时间点 | batch.status | task.status | 说明 |
|--------|-------------|-------------|------|
| API 返回前 | — | completed | 上次残留 |
| API 返回后、后台未启动 | running | completed | batch 已创建,但 task 还没被重置 |
| 后台开始执行 task #1 | running | running(#1), completed(#2~N) | #1 开始执行,其余还没动 |
| task #1 完成 | running | completed(#1, #2~N) | #2 还没开始 |
**用户进入页面时,恰好命中第 2 行:batch.status=running,但 task.status 全是上一次的 completed。**
---
## 3. 根因分析
**直接原因**`create_batch_and_start()` 在创建 batch 记录后、启动后台执行前,**没有将相关任务的状态重置为 `pending`**
**深层原因**
1. `batch_run_tasks()` 方法本身在 `if not batch_id` 分支中会创建 batch 记录,但不负责重置 task status — 它假设 `run_task()` 会在执行时自动设为 `running`
2. `run_task()` 在执行开始前会检查 `if task.status == "running": raise ValueError("不能重复启动")` — 但不检查也不重置 `completed` / `failed` 状态
3. 新增的 `create_batch_and_start()``asyncio.create_task()` 之前创建了 batch 记录但**未做任何 task 状态前置处理**
因此,前端在后台任务启动前的窗口期查询到的全是残留状态。
---
## 4. 修复方案
`create_batch_and_start()` 中,**提交 batch 记录后、启动后台任务前**,将所有涉及的任务状态重置为 `pending`
```python
# backend/app/services/performance_service.py — create_batch_and_start()
self.db.add(batch_record)
# 将批次内所有任务状态重置为 pending(避免读到上一次执行的残留 completed/failed 状态)
for task_id in task_ids:
task = await self.db.get(PerformanceTask, task_id)
if task and task.status in ("completed", "failed", "cancelled", "pending"):
task.status = "pending"
await self.db.commit()
# 在后台异步执行
task = asyncio.create_task(...)
```
**设计考量**
- 只重置 `completed` / `failed` / `cancelled` / `pending` 状态的任务 — **跳过正在 `running` 的任务**,避免干扰其他正在执行的批量
- 重置操作在 batch 记录提交后、后台任务启动前执行,确保前端查询时看到的状态是干净的
- 不修改 `run_task()` 的逻辑(它在执行开始时本来就会设为 `running`),仅做前置清理
### 同时修复的附带问题
`_run_batch_background()` 之前调用 `batch_run_tasks()`**未传递 `batch_id`**,导致后台使用 `if not batch_id:` 分支会创建**第二个** batch 记录。已修复为传递 `batch_id`
```python
await service.batch_run_tasks(
task_ids=task_ids,
project_id=project_id,
batch_id=batch_id, # 修复:复用已创建的 batch 记录
)
```
---
## 5. 部署与验证
### 5.1 部署步骤
1. 上传修复后的后端文件:
```bash
scp app/services/performance_service.py ubains@192.168.5.60:.../backend/app/services/
```
2. 构建前端并上传:
```bash
cd frontend && npm run build
scp -r dist ubains@192.168.5.60:.../frontend/
```
3. 重启后端容器:
```bash
ssh ubains@192.168.5.60 "cd /data/third_party/plat-auto-test/deploy && docker compose restart app"
```
### 5.2 验证结果
| 验证项 | 结果 |
|--------|------|
| 容器重启 | ✅ `Container plat-auto-test-app Started` |
| 部署确认(grep task.status = pending) | ✅ 第 1580 行存在 `task.status = "pending"` |
| 前端构建 | ✅ 无 TypeScript 错误 |
### 5.3 修复后预期行为
| 时间点 | batch.status | task.status | 说明 |
|--------|-------------|-------------|------|
| API 返回后(用户跳转到监控页) | running | pending(全部) | ✅ 前置重置完成 |
| 后台开始执行 task #1 | running | running(#1), pending(#2~N) | #1 开始 |
| task #1 完成 | running | completed(#1), pending(#2) | 正常流转 |
---
## 6. 经验总结(避免再犯)
1. **异步创建 + 同步返回**的模式下,**必须在返回前完成所有影响前端查询状态的前置操作**。后台任务的执行时机不可靠(取决于 event loop 调度),不能假设「后台会立即开始执行」。
2. **批量执行的状态管理是两层**:batch 记录层(`PerformanceBatchExecution.status`)和任务层(`PerformanceTask.status`)。两层状态必须在同一次 commit 中保持一致,否则前端会看到矛盾。
3. **重置状态时要排除 `running`**:不能无脑全部设为 `pending`,否则会干扰其他正在进行的批量执行。
4. **附带 bug 修复**`_run_batch_background()` 漏传 `batch_id`,导致创建重复 batch 记录 — 在排查主问题时顺带发现并修复。
---
*本文档由 Claude Code 生成*
\ No newline at end of file
# 问题处理:批量详情 API 500 错误 — actual_concurrency 字段缺失
> 处理时间:2026-08-21
> 现象报告人:用户(批量执行操作时)
> 关联文档:`_执行计划_批量执行跳转监控页.md`(问题处理)、`_PRD_批量执行跳转监控页.md`(需求)
> 状态:✅ 已修复并验证
---
## 1. 问题现象
用户执行「执行全部」等批量执行操作后,前端调用 **批量详情 API** 报错:
```
加载批次详情失败: Request failed with status code 500
```
前端页面无法展示任务列表与实时监控内容。
---
## 2. 初步定位
1. 查看前端 `BatchMonitor.vue` 的报错点为调用 `GET /api/performance/batch-details/{batch_id}`
2. 在后端容器日志中确认异常堆栈:
```
'PerformanceTask' object has no attribute 'actual_concurrency'. Did you mean: 'step_concurrency'?
```
3. 定位到 `backend/app/services/performance_service.py``get_batch_details()` 方法(约第 1648 行)访问了 `task.actual_concurrency`
---
## 3. 根因分析
### 3.1 字段不一致
| 位置 | 字段 | 说明 |
|------|------|------|
| `PerformanceTask` ORM 模型(`models/performance.py`) | **无 `actual_concurrency`** | 只有 `concurrency`(配置的并发数)和 `step_concurrency` |
| `BatchTaskItem` schema(`schemas/performance.py`) | `actual_concurrency: Optional[int]` | API 响应字段名正确 |
| 前端 TS 类型(`types/performance.ts`) | `actualConcurrency: number \| null` | 前端类型正确 |
| **service 层 `get_batch_details()`** | **`task.actual_concurrency` ❌** | **问题所在:ORM 对象上不存在该属性** |
### 3.2 为什么会 500
`get_batch_details()` 在组装任务字典时直接读取 `task.actual_concurrency`,而 `PerformanceTask` ORM 模型**从未定义过该字段**。Python 访问不存在的属性时抛出 `AttributeError`,FastAPI 捕获后返回 500。
> `suggest` 提示的 `step_concurrency` 是阶梯模式(step mode)的并发字段,与本场景无关;正确的取值是任务的**配置并发数 `concurrency`**。
### 3.3 为什么 schema 和前端没报错
- schema `BatchTaskItem.actual_concurrency` 只是响应模型(response_model),负责序列化输出,不涉及 ORM 属性访问,因此没问题。
- 前端 `BatchTaskItem.actualConcurrency` 只是 TS 类型声明,同样没问题。
**问题只出在 service 层读取 ORM 对象属性这一环。**
---
## 4. 修复方案
`get_batch_details()` 中访问不存在的 `task.actual_concurrency` 改为使用模型上真实存在的 `task.concurrency`(任务的配置并发数)。
```python
# backend/app/services/performance_service.py — get_batch_details()
tasks.append({
"task_id": task.id,
"name": task.name,
"status": task.status,
"actual_tps": task.actual_tps,
"avg_response_time": task.avg_response_time,
"error_rate": task.error_rate,
"actual_concurrency": task.concurrency, # 修复前:task.actual_concurrency(不存在)
"duration_actual": task.duration_actual,
})
```
---
## 5. 部署与验证
### 5.1 部署步骤
1. 上传修复后的文件到 5.60 服务器:
```bash
scp app/services/performance_service.py ubains@192.168.5.60:/data/third_party/plat-auto-test/backend/app/services/performance_service.py
```
2. 重启后端容器:
```bash
ssh ubains@192.168.5.60 "cd /data/third_party/plat-auto-test/deploy && docker compose restart app"
```
### 5.2 验证结果
| 验证项 | 结果 |
|--------|------|
| 容器重启 | ✅ 成功(`Container plat-auto-test-app Started`) |
| `GET /api/performance/batch-details/{batch_id}`(真实批次) | ✅ 返回 200,任务列表含 `actualConcurrency` 字段 |
| 不存在的 batch_id | ✅ 返回 404 语义(`{"detail":"批量执行记录不存在"}`) |
真实批次返回示例(节选):
```json
{
"batchId": "batch_0951f3cfb77842ea87f4d43b8296fc0a",
"projectId": "proj_5a7213144b5740cabd13fae3882c1901",
"status": "running",
"totalCount": 2,
"tasks": [
{
"taskId": "perf_82d72c5c17f94f9ebba1f899b41bc018",
"name": "会议模板列表查询",
"status": "completed",
"actualTps": 244.6,
"avgResponseTime": 39.87,
"errorRate": 0.0,
"actualConcurrency": 10,
"durationActual": 62.7588
}
]
}
```
---
## 6. 经验总结(避免再犯)
1. **ORM 属性访问前先确认字段存在**:service 层直接访问 ORM 对象属性时,务必先核对 `models/*.py` 中的字段定义,不要凭 schema/Types 猜测。
2. **三端字段命名契约**:ORM 模型 ↔ Pydantic schema(`from_attributes`)↔ 前端 TS 类型,三者的字段来源要一致;schema 字段名可以和 ORM 不同(靠手动组装或 alias),但 **service 组装时引用的必须是 ORM 真实属性**
3. **500 快速定位**`AttributeError` 类 500 错误,直接在容器日志里搜 `AttributeError` 一行即可定位到具体行号与属性名。
4. **部署验证闭环**:接口报 500 后,修复完必须重启容器 + 用真实数据调一次接口确认 200,才算闭环。
---
*本文档由 Claude Code 生成*
\ No newline at end of file
# 执行计划 - 事务处理时间(Transaction Time)
> **关联 PRD**:`_PRD_事务处理时间TransactionTime.md`
> **创建日期**:2026-08-21
> **预计总工时**:4 天
---
## 一、执行概述
本计划将 PRD 中的「事务处理时间」功能分为 4 个 Phase 实施:
1. **Phase 1**:数据模型扩展 + 后端事务执行引擎(1.5 天)
2. **Phase 2**:报告数据结构 + API 扩展(0.5 天)
3. **Phase 3**:前端任务配置 UI(步骤编辑器)(1 天)
4. **Phase 4**:报告展示(瀑布图 + 步骤明细)+ 联调测试(1 天)
每个 Phase 完成后可独立验收,Phase 1 是后续所有 Phase 的基础。
---
## 二、任务分解
### Phase 1:数据模型扩展 + 后端事务执行引擎
**目标**:扩展 PerformanceTask 增加 steps 字段,实现多步骤事务执行引擎。
| # | 任务 | 关键文件 | 验收标准 |
|---|------|---------|---------|
| 1.1 | PerformanceTask 模型增加 `steps` 字段(JSON, nullable) | `backend/app/models/performance.py` | steps 列存在,to_dict() 输出含 steps |
| 1.2 | 数据库迁移(SQLite ALTER TABLE 增加 steps 列) | `backend/app/database.py` 启动时自动迁移 或 `backend/scripts/migrate_add_steps.py` | 旧库升级后 steps 列存在,默认 NULL |
| 1.3 | TaskCreate/TaskUpdate schema 增加 `steps` 字段 | `backend/app/schemas/performance.py` | 可接收 steps 列表,校验每步骤含 url/method |
| 1.4 | TemplateResolver 扩展支持 `{{captured.key}}` 变量 | `backend/app/executors/template_resolver.py` | 解析 `{{captured.token}}` → shared_context["token"] |
| 1.5 | 新增 `_send_transaction` 方法:按步骤顺序执行 | `backend/app/executors/performance_executor.py` | 顺序执行 steps,每步骧独立记录指标 |
| 1.6 | MetricsCollector 扩展:每步骤指标 + 事务总耗时统计 | `backend/app/executors/performance_executor.py` | summary() 返回 step_metrics 列表 + transaction_time 统计 |
| 1.7 | _worker 逻辑分叉:steps 有值走事务流程 | `backend/app/executors/performance_executor.py` | steps=None 走原 _send_request(完全兼容) |
| 1.8 | 事务级断言与成功判定 | 同上 | 所有步骤成功 → 事务成功;任一步骤失败 → 事务失败 |
**验收方式**:通过 Swagger UI 创建含 2 个步骤的任务并执行,检查报告数据中 step_metrics 存在且正确。
---
### Phase 2:报告数据结构 + API 扩展
**目标**:事务执行结果落库,报告 API 返回事务时间数据。
| # | 任务 | 关键文件 | 验收标准 |
|------|------|---------|---------|
| 2.1 | PerformanceTask 模型增加事务结果字段:`transaction_summary`(JSON) | `backend/app/models/performance.py` | 存储每步骤统计汇总 |
| 2.2 | 服务层执行完成后写入 transaction_summary | `backend/app/services/performance_service.py` | 执行完成后 step 统计数据落库 |
| 2.3 | 报告 API 返回 transaction_summary | `backend/app/routers/performance.py` | GET /api/performance/report/{taskId} 响应含 transactionSummary |
| 2.4 | 报告 schema 增加 transactionSummary 字段 | `backend/app/schemas/performance.py` | PerformanceReportResponse 含 step_metrics 列表 |
**验收方式**:执行多步骤事务后,GET /api/performance/report/{taskId} 返回完整事务数据。
---
### Phase 3:前端任务配置 UI(步骤编辑器)
**目标**:任务创建/编辑弹窗支持多步骤事务配置。
| # | 任务 | 关键文件 | 验收标准 |
|------|------|---------|---------|
| 3.1 | TransactionStep TypeScript 类型定义 | `frontend/src/types/performance.ts` | TransactionStep 接口与后端 schema 对应 |
| 3.2 | 任务表单增加「任务类型」切换(单接口/多步骤) | `frontend/src/views/performance/TaskList.vue` | 切换后显示不同配置区域 |
| 3.3 | 步骤列表编辑器组件 | `frontend/src/views/performance/TaskList.vue`(内嵌)或独立组件 | 可添加/删除/上下移动步骤 |
| 3.4 | 每步骤配置表单(URL/Method/Headers/Body/断言/捕获) | 同上 | 每步骤可独立配置完整请求参数 |
| 3.5 | 步骤间变量引用提示 | 同上 | 显示可用 captured 变量提示(下拉选择已配置的 capture key) |
| 3.6 | 提交时组装 steps 数据 | 同上 | 创建/更新请求 body 含 steps 数组 |
**验收方式**:在前端创建一个含 3 个步骤(登录→查询→下单模拟)的事务任务,保存后数据库中 steps 字段正确。
---
### Phase 2(Phase 4):报告展示 + 联调测试
**目标**:报告页展示事务时间瀑布图和步骤明细,端到端联调。
| # | 任务 | 关键文件 | 验收标准 |
|------|------|---------|---------|
| 4.1 | ReportPanel 增加事务模式标识 | `frontend/src/views/performance/ReportPanel.vue` | 事务任务显示「多步骤事务」标签 |
| 4.2 | 事务时间瀑布图(ECharts 自定义系列) | 同上 | 各步骤耗时条形堆叠图,含成功/失败颜色标识 |
| 4.3 | 步骤耗时明细表 | 同上 | 表格展示步骤名/方法/URL/耗时/状态码/成功/错误 |
| 4.4 | MonitorPanel 事务时间趋势图(可选) | `frontend/src/views/performance/MonitorPanel.vue` | 实时展示事务 P50/P90/P95 趋势 |
| 4.5 | 端到端联调测试 | 全部相关文件 | 创建 → 执行 → 监控 → 报告全流程正常 |
| 4.6 | 向后兼容验证 | 现有单接口任务 | 旧任务(无 steps)执行、报告完全正常 |
| 4.7 | 前端构建验证 | `frontend/` | npm run build 无错误 |
**验收方式**:端到端流程——创建多步骤事务任务 → 执行 → 实时监控 → 查看报告瀑布图 → 旧任务回归验证。
---
## 三、验收标准总览
| 验收项 | 对应 Phase |
|--------|-----------|
| PerformanceTask.steps 字段存在且向后兼容 | Phase 1 |
| 多步骤任务按顺序执行,每步骤独立计时 | Phase 1 |
| steps=None 的旧任务执行行为完全不变 | Phase 1 |
| `{{captured.key}}` 变量在后续步骤正确解析 | Phase 1 |
| 事务总耗时 = 各步骤耗时之和 | Phase 1 |
| 报告 API 返回 step_metrics 事务数据 | Phase 2 |
| 前端可配置多步骤事务任务 | Phase 3 |
| 报告页展示事务时间瀑布图 | Phase 4 |
| 端到端流程全部正常 | Phase 4 |
| 旧任务回归无影响 | Phase 4 |
---
## 四、测试计划
### 单元测试
- TemplateResolver `{{captured.key}}` 解析测试
- MetricsCollector 事务级统计测试
### 集成测试
- 多步骤任务创建 → 执行 → 报告查询全流程
- 步骤失败策略(失败即终止)验证
- 旧单接口任务回归测试
### 手动测试
- 前端步骤编辑器交互
- 报告瀑布图渲染
- 与真实被测系统(5.44)联调
---
## 五、风险评估
| 风险 | 影响 | 缓解措施 |
|------|------|---------|
| 步骤间数据传递失败导致后续步骤全错 | 事务成功率骤降 | 变量未定义时记录警告并按原样发送,步骤级错误统计可见 |
| 事务模式下 TPS 语义变化 | 用户误解指标 | 报告中明确标注:事务模式下 TPS = 每秒事务数(非请求数) |
| 长事务占用协程 | 并发下降 | 事务超时 60s 默认配置,超时中断 |
| SQLite 存储步骤明细数据量大 | 数据库膨胀 | transaction_summary 只存汇总统计,不存每事务明细 |
| 前端步骤编辑器复杂度高 | 开发周期延长 | 复用现有断言/捕获配置组件,第一版不做拖拽排序(上下移动按钮代替) |
---
## 六、后续工作
- [ ] Phase 1-4 实施完成后更新 CLAUDE.md 当前进度
- [ ] 更新 HANDOFF_性能测试.md 交接文档
- [ ] Git 提交(Conventional Commits)
- [ ] 部署到 5.60 服务器(如需要)
---
## 七、附录
### 关键数据流
```
前端创建多步骤任务(steps 数组)
POST /api/performance/tasks → 落库 steps
执行:POST /api/performance/tasks/{id}/run
PerformanceExecutor.execute()
├── steps 为空 → _send_request(原逻辑)
└── steps 有值 → _send_transaction
├── Step1 发送 → 捕获 token → ctx
├── Step2 发送({{captured.token}} 替换)→ 捕获 id → ctx
└── Step3 发送({{captured.token}} + {{captured.id}} 替换)
MetricsCollector.summary() 含 step_metrics
服务层写库 transaction_summary
GET /api/performance/report/{taskId} → 前端渲染瀑布图
```
### 参考文件
| 文件 | 用途 |
|------|------|
| `backend/app/executors/performance_executor.py` | 现有执行引擎(_send_request / _worker / MetricsCollector) |
| `backend/app/executors/template_resolver.py` | 现有模板解析(扩展 captured 变量) |
| `backend/app/models/performance.py` | PerformanceTask 模型(扩展 steps 字段) |
| `frontend/src/views/performance/TaskList.vue` | 任务编辑弹窗(扩展步骤编辑器) |
| `frontend/src/views/performance/ReportPanel.vue` | 报告页(增加瀑布图) |
---
*本文档由 prd-plan skill 生成,关联 PRD:`_PRD_事务处理时间TransactionTime.md`*
# 执行计划 - 执行机资源监控
> **关联 PRD**:`_PRD_执行机资源监控.md`
> **创建日期**:2026-08-21
> **预计总工时**:2.5 天
---
## 一、执行概述
本计划将 PRD 中的「执行机资源监控」功能分为 3 个 Phase 实施:
1. **Phase 1**:ResourceMonitor 后端采集模块 + 集成到执行引擎(1 天)
2. **Phase 2**:实时监控页 + 批量监控页资源 UI(1 天)
3. **Phase 3**:报告页资源区块 + 联调测试(0.5 天)
---
## 二、任务分解
### Phase 1:ResourceMonitor 后端采集模块 + 集成到执行引擎
**目标**:实现 ResourceMonitor 采集类,集成到 PerformanceExecutor,资源数据随 WebSocket 快照推送,执行结束后写入 resource_summary。
| # | 任务 | 关键文件 | 验收标准 |
|---|------|---------|---------|
| 1.1 | 新建 `ResourceMonitor` 类(psutil 优先 + 标准库兜底) | `backend/app/executors/resource_monitor.py` | sample() 返回 cpu/mem/net 字典 |
| 1.2 | Windows 兜底(标准库实现) | 同上 | Windows 下即使无 psutil 也可采集 cpu/mem |
| 1.3 | Linux 兜底(/proc 读取) | 同上 | Linux 下文件读取方式可用 |
| 1.4 | 集成到 `PerformanceExecutor``_resource_loop` 协程 | `backend/app/executors/performance_executor.py` | 与 _snapshot_loop 并行运行,每秒采集 |
| 1.5 | 快照消息合并:resource 字段加入推送 | `backend/app/executors/performance_executor.py` | WebSocket 推送含 data.resource |
| 1.6 | 执行结束后写入 resource_summary | `backend/app/services/performance_service.py` | 任务 JSON 字段存储采样序列和统计 |
| 1.7 | PerformanceTask 模型增加 `resource_summary` 字段 | `backend/app/models/performance.py` | JSON 字段,nullable,to_dict() 输出 |
| 1.8 | 报告 API 返回 resource_summary | `backend/app/routers/performance.py` + `backend/app/schemas/performance.py` | GET 报告接口响应含 resourceSummary |
| 1.9 | 配置项:RESOURCE_MONITOR_ENABLED / INTERVAL | `backend/app/config.py` | 可通过环境变量控制开关和采样间隔 |
**验收方式**:执行一个压测任务,观察 WebSocket 推送消息含 resource 字段;执行完成后报告 API 返回 resource_summary。
---
### Phase 2:实时监控页 + 批量监控页资源 UI
**目标**`MonitorPanel.vue``BatchMonitor.vue` 增加资源利用率卡片和趋势图。
| # | 任务 | 关键文件 | 验收标准 |
|---|------|---------|---------|
| 2.1 | PerformanceSnapshot 类型增加 resource 字段 | `frontend/src/types/performance.ts` | 编译通过 |
| 2.2 | MonitorPanel 资源卡片(CPU/内存/网络发送/接收 x4) | `frontend/src/views/performance/MonitorPanel.vue` | 收到快照后实时更新数值 |
| 2.3 | MonitorPanel 资源趋势图(CPU + 内存双折线,网络发送/接收双折线) | 同上 | 随时间动态更新 |
| 2.4 | 资源 CPU/内存 > 85% 红色告警提示 | 同上 | 卡片数值超过阈值变红 |
| 2.5 | 资源趋势图默认折叠(点击展开) | 同上 | 默认隐藏,节省监控页空间 |
| 2.6 | BatchMonitor 资源趋势图(选中任务维度) | `frontend/src/views/performance/BatchMonitor.vue` | 批量监控页也可查看资源 |
**验收方式**:压测执行中,监控页显示资源卡片实时更新;趋势图正常渲染。
---
### Phase 3:报告页资源区块 + 联调测试
**目标**`ReportPanel.vue` 展示资源汇总和趋势图,端到端联调。
| # | 任务 | 关键文件 | 验收标准 |
|---|------|---------|---------|
| 3.1 | Report 类型增加 resourceSummary 字段 | `frontend/src/types/performance.ts` | 编译通过 |
| 3.2 | ReportPanel 资源汇总卡片(平均/最大 CPU、平均/最大内存、平均发送/接收) | `frontend/src/views/performance/ReportPanel.vue` | 汇总数据正确显示 |
| 3.3 | ReportPanel 资源趋势图(CPU+内存、网络) | 同上 | 图表渲染正确,无资源数据时隐藏 |
| 3.4 | 端到端联调测试 | 全部相关文件 | 创建 → 执行 → 监控 → 报告全流程资源数据正常 |
| 3.5 | 无 psutil 环境降级验证 | `resource_monitor.py` | 标准库兜底方式可用,不报错 |
| 3.6 | 前端构建验证 | `frontend/` | npm run build 无错误 |
**验收方式**:端到端流程——执行压测 → 实时监控资源 → 查看报告资源趋势 → 无 psutil 环境仍可运行。
---
## 三、验收标准总览
| 验收项 | 对应 Phase |
|--------|-----------|
| ResourceMonitor 跨平台采集 CPU/内存/网络 | Phase 1 |
| 资源指标随 WebSocket 快照推送 | Phase 1 |
| 执行结束后 resource_summary 落库 | Phase 1 |
| 报告 API 返回 resource_summary | Phase 1 |
| 监控页实时资源卡片 + 趋势图 | Phase 2 |
| 批量监控页资源趋势图 | Phase 2 |
| 报告页资源汇总 + 趋势图 | Phase 3 |
| 无 psutil 环境兜底正常工作 | Phase 3 |
| 端到端全流程正常 | Phase 3 |
---
## 四、测试计划
### 单元测试
- ResourceMonitor 在 Windows/Linux 下 sample() 正确性
- 网络速率差值计算
### 集成测试
- 压测执行 + 资源采集全流程
- 资源数据合并到 WebSocket 快照
### 手动测试
- 监控页资源卡片实时刷新
- 资源趋势图动态渲染
- 无 psutil 环境(可用 `pip uninstall psutil -y` 模拟)
---
## 五、风险评估
| 风险 | 影响 | 缓解措施 |
|------|------|---------|
| /proc 读取性能开销 | 采集耗时 | 关键文件只读前 2 行,设置缓存有效期 |
| 网络计数器差值首次采样为 0 | 首秒速率为 0 | 跳过第 1 次差值,第 2 次开始计算 |
| 监控页图表过多加载慢 | 页面卡顿 | 资源趋势图默认折叠,按需展开 |
| 服务器无 psutil 无法安装 | 采集失败 | 标准库兜底实现 |
---
## 六、后续工作
- [ ] Phase 1-3 实施完成后更新 CLAUDE.md 当前进度
- [ ] 更新 HANDOFF_性能测试.md 交接文档
- [ ] Git 提交(Conventional Commits)
- [ ] 部署到 5.60 服务器(如需要)
---
## 七、附录
### 关键文件
| 文件 | 用途 |
|------|------|
| `backend/app/executors/resource_monitor.py` | 新建:ResourceMonitor 采集类 |
| `backend/app/executors/performance_executor.py` | 修改:集成 _resource_loop |
| `backend/app/services/performance_service.py` | 修改:写入 resource_summary |
| `backend/app/models/performance.py` | 修改:增加 resource_summary 字段 |
| `backend/app/schemas/performance.py` | 修改:报告 schema 增加 resourceSummary |
| `backend/app/config.py` | 修改:增加资源监控配置项 |
| `frontend/src/types/performance.ts` | 修改:增加 resource 类型 |
| `frontend/src/views/performance/MonitorPanel.vue` | 修改:资源卡片 + 趋势图 |
| `frontend/src/views/performance/BatchMonitor.vue` | 修改:资源趋势图 |
| `frontend/src/views/performance/ReportPanel.vue` | 修改:资源汇总 + 趋势图 |
---
*本文档由 prd-plan skill 生成,关联 PRD:`_PRD_执行机资源监控.md`*
\ No newline at end of file
此差异已折叠。
...@@ -101,6 +101,21 @@ class Settings: ...@@ -101,6 +101,21 @@ class Settings:
DOCUMENT_OUTPUT_DIR: str = os.getenv("DOCUMENT_OUTPUT_DIR", "./data/documents/output") DOCUMENT_OUTPUT_DIR: str = os.getenv("DOCUMENT_OUTPUT_DIR", "./data/documents/output")
DOCUMENT_MAX_SIZE_MB: int = int(os.getenv("DOCUMENT_MAX_SIZE_MB", "50")) DOCUMENT_MAX_SIZE_MB: int = int(os.getenv("DOCUMENT_MAX_SIZE_MB", "50"))
# 执行机资源监控配置
RESOURCE_MONITOR_ENABLED: bool = os.getenv("RESOURCE_MONITOR_ENABLED", "true").lower() == "true"
RESOURCE_MONITOR_INTERVAL: float = float(os.getenv("RESOURCE_MONITOR_INTERVAL", "1.0"))
RESOURCE_ALERT_CPU: float = float(os.getenv("RESOURCE_ALERT_CPU", "85.0"))
RESOURCE_ALERT_MEM: float = float(os.getenv("RESOURCE_ALERT_MEM", "85.0"))
# 目标机资源监控配置(SSH 方式采集被测系统资源)
TARGET_MONITOR_ENABLED: bool = os.getenv("TARGET_MONITOR_ENABLED", "true").lower() == "true"
TARGET_MONITOR_HOST: str = os.getenv("TARGET_MONITOR_HOST", "192.168.5.44")
TARGET_MONITOR_PORT: int = int(os.getenv("TARGET_MONITOR_PORT", "22"))
TARGET_MONITOR_USERNAME: str = os.getenv("TARGET_MONITOR_USERNAME", "root")
TARGET_MONITOR_PASSWORD: str = os.getenv("TARGET_MONITOR_PASSWORD", "Ubains@123")
TARGET_MONITOR_INTERVAL: float = float(os.getenv("TARGET_MONITOR_INTERVAL", "1.0"))
TARGET_MONITOR_MAX_RETRIES: int = int(os.getenv("TARGET_MONITOR_MAX_RETRIES", "5"))
class Config: class Config:
"""Pydantic 配置""" """Pydantic 配置"""
env_file = ".env" env_file = ".env"
......
...@@ -193,6 +193,37 @@ async def _ensure_columns(conn) -> None: ...@@ -193,6 +193,37 @@ async def _ensure_columns(conn) -> None:
("api_presets", "login_password", "VARCHAR(200) DEFAULT NULL"), ("api_presets", "login_password", "VARCHAR(200) DEFAULT NULL"),
# 性能测试:任务归属项目ID(旧库升级) # 性能测试:任务归属项目ID(旧库升级)
("performance_tasks", "project_id", "VARCHAR(64) DEFAULT NULL"), ("performance_tasks", "project_id", "VARCHAR(64) DEFAULT NULL"),
# 性能测试:HTTP 状态码统计(旧库升级,合并报告聚合用)
("performance_tasks", "status_2xx", "INTEGER DEFAULT 0"),
("performance_tasks", "status_3xx", "INTEGER DEFAULT 0"),
("performance_tasks", "status_4xx", "INTEGER DEFAULT 0"),
("performance_tasks", "status_5xx", "INTEGER DEFAULT 0"),
# 性能测试:事务处理时间(多步骤事务模式,旧库升级)
("performance_tasks", "steps", "JSON"),
("performance_tasks", "transaction_fail_policy", "VARCHAR(20) DEFAULT 'fail_fast'"),
("performance_tasks", "transaction_timeout", "INTEGER DEFAULT 60"),
("performance_tasks", "transaction_summary", "JSON"),
# 性能测试:执行机资源监控汇总(旧库升级)
("performance_tasks", "resource_summary", "JSON"),
# 性能测试:目标机资源监控汇总(SSH 方式采集被测系统资源,旧库升级)
("performance_tasks", "target_resource_summary", "JSON"),
# 性能测试:场景增强字段(瞬时并发/长稳压测,2026-08-22 新增)
("performance_tasks", "scenario_type", "VARCHAR(20) DEFAULT NULL"),
("performance_tasks", "scenario_apis", "JSON"),
("performance_tasks", "loop_type", "VARCHAR(10) DEFAULT 'finite'"),
("performance_tasks", "loop_count", "INTEGER DEFAULT 1"),
("performance_tasks", "synchronizing_timer_enabled", "BOOLEAN DEFAULT 0"),
("performance_tasks", "synchronizing_timer_count", "INTEGER DEFAULT 0"),
("performance_tasks", "synchronizing_timer_timeout", "INTEGER DEFAULT 30000"),
("performance_tasks", "error_action", "VARCHAR(10) DEFAULT 'continue'"),
("performance_tasks", "think_time_enabled", "BOOLEAN DEFAULT 0"),
("performance_tasks", "think_time_min", "INTEGER DEFAULT 1000"),
("performance_tasks", "think_time_max", "INTEGER DEFAULT 3000"),
("performance_tasks", "think_time_distribution", "VARCHAR(10) DEFAULT 'constant'"),
("performance_tasks", "csv_parameterization_enabled", "BOOLEAN DEFAULT 0"),
("performance_tasks", "csv_content", "TEXT DEFAULT ''"),
("performance_tasks", "csv_variable_mapping", "JSON"),
("performance_tasks", "api_summary", "JSON"),
# 性能测试:快照增强指标(快照级 p95 等已于上条添加,本行仅作记录) # 性能测试:快照增强指标(快照级 p95 等已于上条添加,本行仅作记录)
] ]
......
此差异已折叠。
此差异已折叠。
...@@ -160,6 +160,63 @@ class PerformanceTask(Base): ...@@ -160,6 +160,63 @@ class PerformanceTask(Base):
step_concurrency: Mapped[list] = mapped_column(JSON, default=list, comment="阶梯并发配置") step_concurrency: Mapped[list] = mapped_column(JSON, default=list, comment="阶梯并发配置")
step_duration: Mapped[int] = mapped_column(Integer, default=60, comment="每阶梯时长(秒)") step_duration: Mapped[int] = mapped_column(Integer, default=60, comment="每阶梯时长(秒)")
# ===== 场景增强字段(瞬时并发/长稳压测,2026-08-22 新增)=====
# 场景类型: null=存量标准模式(concurrent/qps/step),burst=瞬时并发,endurance=长稳压测
scenario_type: Mapped[Optional[str]] = mapped_column(
String(20), nullable=True, default=None, comment="场景类型: burst/endurance(null=标准模式)"
)
# 长稳接口列表(仅 endurance 场景使用),每接口独立线程组
# 结构: [{"name":"","method":"","url":"","headers":{},"body":null,"thread_count":10,"assertions":[],"unique_fields":[]},...]
scenario_apis: Mapped[Optional[list]] = mapped_column(
JSON, nullable=True, default=None, comment="长稳接口列表,每接口独立线程数"
)
# 循环控制
loop_type: Mapped[str] = mapped_column(
String(10), default="finite", comment="循环类型: finite(有限次)/infinite(无限)"
)
loop_count: Mapped[int] = mapped_column(Integer, default=1, comment="有限循环次数")
# 集合点(Synchronizing Timer,burst 场景使用)
synchronizing_timer_enabled: Mapped[bool] = mapped_column(
Boolean, default=False, comment="是否启用集合点"
)
synchronizing_timer_count: Mapped[int] = mapped_column(
Integer, default=0, comment="集合点阈值,到达该数量线程后齐发"
)
synchronizing_timer_timeout: Mapped[int] = mapped_column(
Integer, default=30000, comment="集合点等待超时(ms)"
)
# 错误动作
error_action: Mapped[str] = mapped_column(
String(10), default="continue", comment="错误动作: continue/stop"
)
# 思考时间(Think Time,endurance 场景使用)
think_time_enabled: Mapped[bool] = mapped_column(
Boolean, default=False, comment="是否启用思考时间"
)
think_time_min: Mapped[int] = mapped_column(
Integer, default=1000, comment="均值/固定思考时间(ms)"
)
think_time_max: Mapped[int] = mapped_column(
Integer, default=3000, comment="高斯分布偏差上限(ms)"
)
think_time_distribution: Mapped[str] = mapped_column(
String(10), default="constant", comment="分布类型: constant/gaussian"
)
# CSV 参数化(endurance 场景使用)
csv_parameterization_enabled: Mapped[bool] = mapped_column(
Boolean, default=False, comment="是否启用CSV参数化"
)
csv_content: Mapped[str] = mapped_column(Text, default="", comment="CSV文件内容(首行表头)")
csv_variable_mapping: Mapped[dict] = mapped_column(
JSON, default=dict, comment="CSV列名→变量名映射"
)
# ===== 场景增强字段 END =====
# 认证配置 # 认证配置
auth_required: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否需要登录Token") auth_required: Mapped[bool] = mapped_column(Boolean, default=True, comment="是否需要登录Token")
account_key: Mapped[str] = mapped_column(String(50), default="superadmin", comment="使用的账号") account_key: Mapped[str] = mapped_column(String(50), default="superadmin", comment="使用的账号")
...@@ -187,6 +244,24 @@ class PerformanceTask(Base): ...@@ -187,6 +244,24 @@ class PerformanceTask(Base):
# 唯一性字段配置(每个请求自动生成唯一值,避免并发压测重复冲突) # 唯一性字段配置(每个请求自动生成唯一值,避免并发压测重复冲突)
unique_fields: Mapped[list] = mapped_column(JSON, default=list, comment="唯一性字段配置列表") unique_fields: Mapped[list] = mapped_column(JSON, default=list, comment="唯一性字段配置列表")
# 事务步骤列表(多步骤事务模式,None/空 = 单接口模式,向后兼容)
steps: Mapped[Optional[list]] = mapped_column(JSON, nullable=True, comment="事务步骤列表")
# 事务失败策略: fail_fast(失败即终止)/continue(继续执行后续步骤)
transaction_fail_policy: Mapped[str] = mapped_column(
String(20), default="fail_fast", comment="事务失败策略: fail_fast/continue"
)
# 单个事务超时(秒)
transaction_timeout: Mapped[int] = mapped_column(Integer, default=60, comment="事务超时(秒)")
# 执行机资源监控汇总(采样序列 + 统计,资源监控功能)
resource_summary: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True, comment="执行机资源监控汇总")
# 目标机资源监控汇总(SSH 方式采集被测系统资源)
target_resource_summary: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True, comment="目标机资源监控汇总")
# 事务处理时间汇总(每步骤统计,事务功能)
transaction_summary: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True, comment="事务处理时间汇总")
# 接口维度指标汇总(长稳压测多接口场景,按接口统计请求数/RT/TPS/错误率)
api_summary: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True, comment="接口维度指标汇总")
# 结果统计 # 结果统计
total_requests: Mapped[int] = mapped_column(Integer, default=0, comment="总请求数") total_requests: Mapped[int] = mapped_column(Integer, default=0, comment="总请求数")
success_count: Mapped[int] = mapped_column(Integer, default=0, comment="成功数") success_count: Mapped[int] = mapped_column(Integer, default=0, comment="成功数")
...@@ -218,6 +293,10 @@ class PerformanceTask(Base): ...@@ -218,6 +293,10 @@ class PerformanceTask(Base):
error_type_http_4xx: Mapped[int] = mapped_column(Integer, default=0, comment="HTTP 4xx错误数") error_type_http_4xx: Mapped[int] = mapped_column(Integer, default=0, comment="HTTP 4xx错误数")
error_type_http_5xx: Mapped[int] = mapped_column(Integer, default=0, comment="HTTP 5xx错误数") error_type_http_5xx: Mapped[int] = mapped_column(Integer, default=0, comment="HTTP 5xx错误数")
error_type_assertion: Mapped[int] = mapped_column(Integer, default=0, comment="断言失败数") error_type_assertion: Mapped[int] = mapped_column(Integer, default=0, comment="断言失败数")
status_2xx: Mapped[int] = mapped_column(Integer, default=0, comment="HTTP 2xx响应数")
status_3xx: Mapped[int] = mapped_column(Integer, default=0, comment="HTTP 3xx响应数")
status_4xx: Mapped[int] = mapped_column(Integer, default=0, comment="HTTP 4xx响应数")
status_5xx: Mapped[int] = mapped_column(Integer, default=0, comment="HTTP 5xx响应数")
# 时间 # 时间
start_time: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, comment="开始时间") start_time: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True, comment="开始时间")
...@@ -267,6 +346,21 @@ class PerformanceTask(Base): ...@@ -267,6 +346,21 @@ class PerformanceTask(Base):
"ramp_up": self.ramp_up, "ramp_up": self.ramp_up,
"step_concurrency": self.step_concurrency or [], "step_concurrency": self.step_concurrency or [],
"step_duration": self.step_duration, "step_duration": self.step_duration,
"scenario_type": self.scenario_type,
"scenario_apis": self.scenario_apis,
"loop_type": self.loop_type,
"loop_count": self.loop_count,
"synchronizing_timer_enabled": self.synchronizing_timer_enabled,
"synchronizing_timer_count": self.synchronizing_timer_count,
"synchronizing_timer_timeout": self.synchronizing_timer_timeout,
"error_action": self.error_action,
"think_time_enabled": self.think_time_enabled,
"think_time_min": self.think_time_min,
"think_time_max": self.think_time_max,
"think_time_distribution": self.think_time_distribution,
"csv_parameterization_enabled": self.csv_parameterization_enabled,
"csv_content": self.csv_content,
"csv_variable_mapping": self.csv_variable_mapping,
"auth_required": self.auth_required, "auth_required": self.auth_required,
"account_key": self.account_key, "account_key": self.account_key,
"login_username": self.login_username, "login_username": self.login_username,
...@@ -276,6 +370,13 @@ class PerformanceTask(Base): ...@@ -276,6 +370,13 @@ class PerformanceTask(Base):
"assertions": self.assertions or [], "assertions": self.assertions or [],
"capture_rules": self.capture_rules or [], "capture_rules": self.capture_rules or [],
"unique_fields": self.unique_fields or [], "unique_fields": self.unique_fields or [],
"steps": self.steps,
"transaction_fail_policy": self.transaction_fail_policy,
"transaction_timeout": self.transaction_timeout,
"resource_summary": self.resource_summary,
"target_resource_summary": self.target_resource_summary,
"transaction_summary": self.transaction_summary,
"api_summary": self.api_summary,
"total_requests": self.total_requests, "total_requests": self.total_requests,
"success_count": self.success_count, "success_count": self.success_count,
"fail_count": self.fail_count, "fail_count": self.fail_count,
...@@ -302,6 +403,10 @@ class PerformanceTask(Base): ...@@ -302,6 +403,10 @@ class PerformanceTask(Base):
"error_type_http_4xx": self.error_type_http_4xx, "error_type_http_4xx": self.error_type_http_4xx,
"error_type_http_5xx": self.error_type_http_5xx, "error_type_http_5xx": self.error_type_http_5xx,
"error_type_assertion": self.error_type_assertion, "error_type_assertion": self.error_type_assertion,
"status_2xx": self.status_2xx,
"status_3xx": self.status_3xx,
"status_4xx": self.status_4xx,
"status_5xx": self.status_5xx,
"actual_tps": self.actual_tps, "actual_tps": self.actual_tps,
"error_rate": self.error_rate, "error_rate": self.error_rate,
"start_time": self.start_time.isoformat() if self.start_time else None, "start_time": self.start_time.isoformat() if self.start_time else None,
......
此差异已折叠。
此差异已折叠。
...@@ -23,6 +23,7 @@ import type { ...@@ -23,6 +23,7 @@ import type {
PerformanceProjectStats, PerformanceProjectStats,
BatchRunRequest, BatchRunRequest,
BatchRunResponse, BatchRunResponse,
BatchDetailResponse,
ProjectReportResponse, ProjectReportResponse,
ProjectReportListResponse, ProjectReportListResponse,
} from '@/types/performance' } from '@/types/performance'
...@@ -231,6 +232,11 @@ export function runProjectAll(projectId: string): Promise<BatchRunResponse> { ...@@ -231,6 +232,11 @@ export function runProjectAll(projectId: string): Promise<BatchRunResponse> {
return request.post(`${BASE}/projects/${projectId}/run-all`) return request.post(`${BASE}/projects/${projectId}/run-all`)
} }
/** 获取批量执行详情 */
export function getBatchDetails(batchId: string): Promise<BatchDetailResponse> {
return request.get(`${BASE}/batch-details/${batchId}`)
}
// ==================== 合并报告 ==================== // ==================== 合并报告 ====================
/** 获取项目最新合并报告 */ /** 获取项目最新合并报告 */
......
...@@ -199,6 +199,12 @@ const routes: RouteRecordRaw[] = [ ...@@ -199,6 +199,12 @@ const routes: RouteRecordRaw[] = [
component: () => import('@/views/performance/ProjectReport.vue'), component: () => import('@/views/performance/ProjectReport.vue'),
meta: { title: '合并报告' } meta: { title: '合并报告' }
}, },
{
path: 'batch-monitor',
name: 'PerfBatchMonitor',
component: () => import('@/views/performance/BatchMonitor.vue'),
meta: { title: '批量执行监控' }
},
{ {
path: 'presets', path: 'presets',
name: 'PerfPresets', name: 'PerfPresets',
......
此差异已折叠。
此差异已折叠。
...@@ -256,10 +256,13 @@ const runProject = async (project: PerformanceProject) => { ...@@ -256,10 +256,13 @@ const runProject = async (project: PerformanceProject) => {
runningId.value = project.id runningId.value = project.id
try { try {
const res = await runProjectAll(project.id) const res = await runProjectAll(project.id)
ElMessage.success(res.message || '项目已开始执行') // 立即跳转批量执行监控页
router.push({
path: '/performance/batch-monitor',
query: { batchId: res.batchId, projectId: project.id },
})
} catch (error: any) { } catch (error: any) {
ElMessage.error('执行失败: ' + (error.response?.data?.detail || error.message || '')) ElMessage.error('执行失败: ' + (error.response?.data?.detail || error.message || ''))
} finally {
runningId.value = '' runningId.value = ''
} }
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论