提交 9ea984a8 authored 作者: 陈泽健's avatar 陈泽健

feat(performance): 混合场景超时误读消除与脱敏前缀保留及业务断言指引

- 请求明细页 (RequestDetailPanel.vue):在 errors_only 采集模式下顶部展示黄色提示条,防止「全是超时」误读
- 报告页 (ReportPanel.vue):执行汇总补充展示明细采集模式(on/errors_only/off)
- 后端脱敏 (performance_executor.py):Authorization 头保留前 20 字符 + ***,便于核对 token 已携带,其余敏感字段仍整体脱敏
- 后端服务 (performance_service.py/schemas):报告摘要序列化输出 request_detail_enabled 字段
- 需求与问题处理文档:新增排查报告、执行计划、业务断言指引,更新 HANDOFF 交接记录
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 ae4b9c68
# HANDOFF — 性能测试模块会话交接文档
> **生成时间**: 2026-09-02
> **生成时间**: 2026-09-07
> **当前分支**: `platform-auto-test`
> **最近提交**: `03ee164c` docs(performance): HANDOFF 补充 5.60 部署 md5 复核与双 commit 记录(已同步 origin/platform-auto-test)
> **会话窗口**: 性能测试 — 混合场景(mix)执行失败修复:CSV 强制校验(无 CSV 不允许执行)+ 登录计入 api_summary 监控,已部署 5.60 并端到端验证通过
> **状态**: ✅ 修复已实现、部署 5.60、E2E 验收通过(A/B 拒绝 + C 实跑含「登录」行);**改动未提交**(见待办)
> **会话窗口(上一窗口)**: 修复 Java 进程 CPU 监控失真(ps 生命周期平均 → JSTAT 瞬时采样)`ad4163bb`
> **最近提交**: `0aa655a5` fix(performance): 混合场景CSV强制校验与登录监控记录+目标机监控开启与监控图表X轴修复(已同步 origin/platform-auto-test)
> **会话窗口**: 性能测试 — 目标机监控开启(TARGET_MONITOR_ENABLED=true + 运行前校验)+ 监控图表 X 轴修复(dataZoom/零尺寸 resize/空态自动展开),提交推送 `0aa655a5` 并部署 5.60(容器重建)
> **状态**: ✅ 已提交推送 + 部署 5.60 验证通过(env 生效 / 代码标记命中);**待真实混合场景任务端到端确认目标机曲线**
> **会话窗口(上一窗口)**: 2026-09-02 混合场景 CSV 强制校验 + 登录计入监控(该窗口改动随本窗口一并提交进 `0aa655a5`)
---
## ⚡ 最新会话更新(2026-09-02)— 混合场景执行失败修复:CSV 强制校验 + 登录计入监控 ✅
## ⚡ 最新会话更新(2026-09-07)— 目标机监控开启 + 监控图表 X 轴修复 ✅
### A. 会话背景
用户在 5.60 观察运行中的混合场景任务,反馈两个问题:
1. **监控页目标机资源无数据**(CPU/内存/磁盘曲线空白,空态提示「不可用」);
2. **执行机资源图显示异常**,X 轴被压成极窄一条。
另:混合场景运行后仍观察到 CSV 报错文案不精确(列映射配置错误时报的是账号数错误)。
### B. 根因
| # | 问题 | 根因 |
|---|------|------|
| 1 | 目标机无监控数据 | `deploy/docker-compose.yml` 未设置 `TARGET_MONITOR_ENABLED``config.py` 默认 `false``545ee9b4` 改的默认值)→ 容器内监控开关关闭,快照 `targetResource` 恒为空 |
| 2 | 代码无提醒 | 服务层运行前不检查监控开关,用户跑完全程才发现无数据 |
| 3 | X 轴窄条 | 折叠面板内 `echarts.init` 得到**零尺寸画布**,展开后未 `resize()`;且无 `dataZoom`,长时压测点位全挤在一屏 |
| 4 | 空态默默无数据 | 目标机面板持续无数据时不展开,用户看不到空态原因 |
| 5 | CSV 报错顺序 | `_run_mix()` 中列映射校验在 `per_vu_login` 分支内且账号数校验先于列校验,配置错误被数量错误掩盖 |
### C. 修复内容
| 文件 | 改动 |
|------|------|
| `deploy/docker-compose.yml` | 环境变量新增 `TARGET_MONITOR_ENABLED=true`(含根因注释) |
| `backend/app/services/performance_service.py` | `_validate_run_preconditions()` 增加目标机监控开关检查:开关关闭时任务启动前直接报错提示(避免白跑) |
| `backend/app/executors/performance_executor.py` | `_run_mix()` 列映射/缺列校验**先于**账号数校验(缺列是配置错误,优先报告);映射缺失 `logger.warning` 保留 |
| `frontend/src/views/performance/MonitorPanel.vue` | ① 资源/网络/目标机三组图增加 `dataZoom`(inside + slider),刷新时用 `getCurrentZoom()` 保留用户缩放窗口;② 零尺寸画布(width/height=0)时兜底 `resize()`;③ 图表更新改 `nextTick` 包装(等折叠面板 v-show 生效);④ 运行中连续 10 个快照(约 10s)无目标机数据 → 自动展开空态面板展示原因(每次连接重置);⑤ `handleWindowResize()` 监听窗口尺寸变化全部图表自适应;⑥ 空态文案补充「未开启 TARGET_MONITOR_ENABLED」 |
| `frontend/src/views/performance/TaskList.vue` | `handleRun()` mix 无 CSV 前置拦截(ElMessage.warning,不发请求);CSV 模板改为前端 XLSX 动态生成(`downloadCsvTemplate()`,不再依赖部署静态文件);schema + `types/performance.ts` 新增 `csvParameterizationEnabled` / `csvContent` / `perVuLogin` 三字段 |
| `backend/app/schemas/performance.py` | `PerformanceTaskListItem` 新增上述 3 字段(支撑前端拦截) |
### D. 提交推送 ✅(/GitCommit 三重门控)
- Commit `0aa655a5` fix(performance): 混合场景CSV强制校验与登录监控记录+目标机监控开启与监控图表X轴修复
- **10 个文件**(8 修改 + 2 新增文档),+655/-44,已推送 `origin/platform-auto-test`
- 本提交同时包含上一窗口(2026-09-02)未提交的 CSV 强制校验 + 登录监控改动
- 审查中排除:项目管理模块 14 个未跟踪文件(其他窗口半成品)、`backend/tests/test_screenshot_cleanup.py`**坏测试**,引用不存在的 `cleanup_screenshots_loop`,提交会弄挂 pytest 收集)、`backend/tmp/` + `tmp/` 临时产物
### E. 部署 5.60 ✅(脚本 `tmp/deploy_monitor_fix_560.py`)
1. 后端 3 文件 + docker-compose.yml + 前端 dist 94 文件上传;
2. **容器重建** `docker compose up -d app`(⚠️ env 变更必须重建,`restart` 不会重读 compose env —— 上一窗口用 `restart` 对代码生效但 env 类改动不适用);
3. 验证全过:`/health` 200;容器内 `TARGET_MONITOR_ENABLED='true'``_validate_run_preconditions`×3、`metrics_api_name`×6 代码标记命中;启动日志无 error/monitor 异常。
### F. 待办(交接给下一窗口)
| # | 任务 | 优先级 | 说明 |
|---|------|--------|------|
| 1 | **真实混合场景任务端到端确认** | P1 | 跑一次 mix 任务,监控页确认:目标机 CPU/内存/磁盘曲线出现、执行机资源图 X 轴正常且可缩放(会在 5.44 产生真实会议数据,需用户同意) |
| 2 | **修复/删除 `backend/tests/test_screenshot_cleanup.py`** | P1 | 坏测试:import 不存在的 `cleanup_screenshots_loop` / `start_screenshot_cleanup_loop`,任何 `pytest tests/` 收集即中断;5.60 启动日志同样报 `SCREENSHOT_RETENTION_DAYS` 属性缺失(同一未完成功能:Settings 缺配置 + cleanup_service 缺循环函数) |
| 3 | 清理 5.60 验证克隆任务 | P2 | 上一窗口遗留 A/B/C 三个 `*_A_no_csv` 等克隆任务 |
| 4 | 项目管理模块等 14 个未跟踪文件 | — | 属其他窗口工作,勿在本窗口提交 |
---
## ⚡ 会话更新(2026-09-02)— 混合场景执行失败修复:CSV 强制校验 + 登录计入监控 ✅
### A. 会话背景
......
# 执行计划:混合场景「全量超时」误读消除 —— 明细采集模式标注 + 脱敏前缀保留 + 业务断言
> 生成时间:2026-09-07
> 关联文档:`_问题处理_混合场景全量超时_请求明细token脱敏误读.md`
> 状态:📋 方案已定,待实施
> 性质:**体验优化,非缺陷修复**(本次排查结论:平台无缺陷,误读源于采集模式与脱敏展示)
---
## 问题概述
混合场景 100 并发执行完成后,报告页「察看结果树」看起来「全部超时」,
请求头 token 显示 `***`,用户误判为平台没传 token / 执行全部失败。
排查确认(详见问题处理文档):
1. 真实成功率 **90.7%**(2xx 11448 / 总 12615,4xx/5xx 均为 0);
2. 明细表只有 1166 条失败记录 —— 任务配置 `request_detail_enabled=errors_only`
**成功请求根本不入库**,明细列表天然「全是超时」;
3. `***``_desensitize()` 入库前脱敏,真实请求带有效 Bearer token
(11448 个 2xx + 0 个 401 为证)。
需要消除两类误读,并提升业务成功率真实度。
---
## 执行步骤
### Step 1: 请求明细页标注采集模式(errors_only / off / on)
**文件**`frontend/src/views/performance/RequestDetailPanel.vue`
**现状**:组件已读取采集模式(约 417 行
`enabled.value = res.request_detail_enabled || 'off'`),
仅在 `off` 时显示空态「请求详情采集未开启…」(11-14 行);
`errors_only` 模式**无任何提示**,用户看到列表全是失败即误读为「全部超时」。
**改动**:页面顶部(`enabled !== 'off'` 且有数据时)增加一条 `el-alert`
```vue
<!-- 采集模式提示:errors_only 下列表只含失败请求,防止「全是超时」误读(2026-09-07) -->
<el-alert
v-if="enabled === 'errors_only' && total > 0"
type="warning"
:closable="true"
show-icon
title="当前采集模式为「仅失败请求」(errors_only):此处仅展示超时/断言失败等失败样本,成功请求未入库。执行真实成功率请看报告页执行汇总(本次成功率 = 成功数 / 总请求数)。"
/>
```
同时把 `off` 空态文案补充采集模式说明(可选:
「可在任务配置中切换 全量采集(on) / 仅失败(errors_only) / 关闭(off)」)。
**验收标准**
- errors_only 任务明细页顶部出现黄色提示条;
- on / off 模式页面行为不变。
### Step 2: 报告页执行汇总补充采集模式标识
**文件**`frontend/src/views/performance/ReportPanel.vue`
**现状**:报告数据中已含 `request_detail_enabled`
`backend/app/models/performance.py:392` 序列化已输出),前端未展示。
**改动**:执行汇总信息卡片(状态/总请求/成功率附近)追加一个只读字段:
「请求明细采集:仅失败(errors_only)/ 全量(on)/ 关闭(off)」,
取值映射与 Step 1 一致。
**验收标准**:报告页可见本次执行的采集模式;三种取值均正确显示。
### Step 3: Authorization 脱敏保留前缀(可核对 token 已携带)
**文件**`backend/app/executors/performance_executor.py`
**现状**`_desensitize()`(约 2781-2810 行)对所有命中
`_SENSITIVE_KEYS`(含 authorization/token/password/secret/sign)的键整体替换 `"***"`
**改动**:dict 分支对 `authorization` 键特判 —— 保留前 20 字符 + `"***"`
其余敏感键维持整体脱敏:
```python
_SENSITIVE_PREFIX_KEEP = {"authorization"} # 保留前缀便于核对 token 已携带
@staticmethod
def _desensitize(data: Any) -> Any:
...
if isinstance(data, dict):
result = {}
for k, v in data.items():
kl = k.lower()
if any(s in kl for s in PerformanceExecutor._SENSITIVE_KEYS):
# Authorization 保留前 20 字符(如 "Bearer eyJhbGciOiJIUzI1NiIs"),
# 既防泄露又可核对请求确实携带 token(2026-09-07)
if any(s in kl for s in PerformanceExecutor._SENSITIVE_PREFIX_KEEP) \
and isinstance(v, str) and len(v) > 20:
result[k] = v[:20] + "***"
else:
result[k] = "***"
else:
result[k] = PerformanceExecutor._desensitize(v)
return result
```
> 注意:历史已入库的 `***` 明细不回填(无原始值);
> password / token / sign 等仍整体脱敏。
**验收标准**
- 新执行的明细中 `Authorization` 显示形如
`Bearer eyJhbGciOiJIUzI1NiIs***``password` 等仍为 `***`
- 5.60 部署后跑一次带明细采集的小任务验证。
### Step 4: 混合场景写接口业务断言指引(成功率真实度)
**现状**`ResultValidator` 已支持 `response_body` 断言
(JSON Path / 文本包含,`performance_executor.py:428-520`),
但当前混合场景任务未配置 —— 预约接口返回 `A0023 该时段已有会议安排`
(HTTP 200 业务失败)被计入成功,成功率虚高。
**改动**(配置指引 + 文档,不改代码):
1. 在混合场景任务编辑页为三个接口补断言:
- 预约 PUT / 模板创建 POST:
`{"type": "response_body", "path": "$.success", "operator": "equals", "value": true}`
- 模板查询 GET:
`{"type": "response_body", "path": "$.code", "operator": "equals", "value": "200"}`
2.`Docs/PRD/性能测试/` 使用指南补一节
「业务断言:HTTP 200 ≠ 业务成功」,给出上述示例与 A0023 案例。
**验收标准**
- 配置断言后执行,`A0023` 响应计入断言失败(fail_count / assertion_fail),
api_summary 成功率反映业务真实结果。
### Step 5: 压测方法论落地建议(输出给被测系统团队,非本仓库代码)
整理为结论反馈(已含在问题处理文档 §3.2 / §4.4):
1. `getTemplatePage` 大结果集/深分页性能排查(单发 58KB × 权重 40%);
2. 压测数据隔离:压测前清理模板测试数据,或压测租户使用独立 companyNumber;
3. 连接池水位核查(目标机监控 max_used_connections 93 / max 151);
4. 混合场景建议开启思考时间(think_time),贴近真实用户节奏。
**验收标准**:问题处理文档 + 本计划归档,结论可直接转发被测系统团队。
---
## 部署与验证
| 项 | 方式 |
|----|------|
| 前端 Step 1/2 | 本地 `npm run build` → scp dist/ → 5.60 volume 热更新 |
| 后端 Step 3 | scp `performance_executor.py``docker restart plat-auto-test-app` |
| 端到端验证 | 5.60 跑一次小并发 mix 任务(2 VU / 60s / 开明细采集),核对 Step 1-3 效果 |
## 回归清单
- [ ] errors_only 明细页提示条显示、关闭按钮可用
- [ ] on / off 模式明细页行为不变(无提示条 / 空态文案)
- [ ] 报告页汇总显示采集模式
- [ ] 新明细 Authorization 前缀保留、password 仍整体脱敏
- [ ] 配置业务断言后 A0023 计为失败
- [ ] 非 mix 场景(单接口/并发/长稳)回归无影响
---
*本文档由 Claude Code 生成*
# 问题处理:混合场景执行结果「全部超时」排查 —— token 脱敏误读 + 被测系统持续满负荷劣化
> 处理时间:2026-09-07
> 现象报告人:用户(5.60 上混合场景执行完成,报告页察看结果树显示全是超时)
> 关联文档:`_执行计划_混合场景全量超时_请求明细token脱敏优化与业务断言.md`
> 状态:✅ 根因已定位(平台无缺陷,被测系统满负荷劣化 + 明细采集模式误读);修复方案已定(待部署)
---
## 1. 问题现象
5.60 上混合场景任务 `perf_1c72c087ba4f4854b5f9a8c9a348f565`(100 并发 / 1800s / mix)
执行完成并已输出报告,但报告页「察看结果树」中请求明细**看起来全部是超时**
`status=0 / response_time=30047ms / error_type=timeout`)。
用户反馈两点疑虑:
1. 「为什么执行的结果全是超时?」
2. 「请求传参的 token 是 `***`」—— 怀疑请求头里没有带有效 token,导致被测系统全部超时。
用户在**被测系统(192.168.5.44)上手动操作是正常**的,因此要求确认
「是平台的问题,还是被测系统的问题」,且如果是被测系统问题需要提供有力证据。
---
## 2. 排查过程
### 2.1 数据库实证(5.60 MySQL)
本次执行 `exec_c043b3e5c9a74e46a1debe21053e805b` 的汇总记录:
| 指标 | 值 |
|------|-----|
| status | completed |
| total_requests | 12615 |
| success_count | **11448** |
| fail_count | 1167 |
| error_type_timeout | 1167 |
| error_type_connect_error | 0 |
| error_type_client_error | 0 |
| status_2xx | **11448** |
| status_3xx / 4xx / 5xx | 0 / 0 / 0 |
| avg_response_time | 14345 ms |
关键发现:**真实成功率 90.7%(11448/12615),不是 100% 超时**
### 2.2 请求明细表只有失败记录(errors_only 采集模式)
`performance_request_details` 表中该执行**只有 1166 条记录,全部 `success=0`**
成功请求一条都没入库 —— 采集模式是 `request_detail_enabled="errors_only"`
所以报告页「察看结果树」看起来「全是超时」,其实是**明细列表只展示了失败样本**
成功请求根本没有被采集。
### 2.3 「token 是 ***」是脱敏显示,不是没传 token
`backend/app/executors/performance_executor.py:2735` `_desensitize()`
请求明细入库前,`Authorization / password / token / secret / sign` 等敏感字段
统一替换为 `***`(防止真实 token 泄露进数据库与报告页面)。
真实请求头是 `Authorization: Bearer eyJhbGci...`(per-VU / 共享 token 均正常附加)。
**铁证**:本次压测被测系统返回 **11448 个 HTTP 200、0 个 401/403**
若 token 真没传(或传的是字面 `***`),鉴权层会立刻打回 401,
不可能有 11448 个 2xx 响应。
### 2.4 超时的时间分布:集中在前 6 分钟 + 结尾,中段几乎无超时
```
02:24 100 02:29 69 02:40 2 02:48 75
02:25 200 02:30 1 02:41 3 02:49 29
02:26 200 02:36 8 02:44 6 02:50 21
02:27 200 02:37 1 02:45 3 02:51 7
02:28 200 02:38-39 0 02:46 1 02:52 17
02:53 12
02:54 9
```
特征:02:24–02:29(前 6 分钟)累计 **970 个超时**,02:30–02:47 中段几乎为 0,
02:48–02:54(结尾 7 分钟)**185 个超时** —— 典型的「洪峰积压 → 缓过来 → 再度劣化」曲线,
说明被测系统在持续满负荷下先出现积压,短暂恢复后再次劣化。
### 2.5 接口维度统计(api_summary)
| 接口 | 权重 | 请求数 | 2xx | 失败 | avg | p95 | max |
|------|------|--------|-----|------|-----|-----|-----|
| 登录(预登录) | - | 1 | 1 | 0 | 740ms | - | - |
| 模板查询 GET | 40 | 5625 | 4987 | 638 | **19084ms** | 30985ms | 31050ms |
| 模板创建 POST | 20 | 2828 | 2593 | 235 | 14039ms | 30984ms | 31051ms |
| 预约 PUT | 30 | 4161 | 3867 | 294 | 8150ms | 30972ms | 31054ms |
三个业务接口的 **p95 全部 ≈ 31s(≈30s 超时阈值)**,即大量请求打到 30 秒超时被切断。
### 2.6 对照实验(5.60 容器内,与压测同链路、同签名逻辑、同被测系统)
**实验 A:单发真实接口(用任务里的真实模板 body + 真实签名)**
| 接口 | 结果 |
|------|------|
| 登录 | 200,640ms |
| 模板查询 GET | 200,**254ms**(压测时 avg 19s) |
| 模板创建 POST | 200,1231ms,OPERATION_SUCCESSFUL |
| 预约 PUT | 200,320ms(`A0023 该时段已有会议安排` —— 业务校验正常工作) |
**实验 B:阶梯并发打模板查询接口**
| 并发 | 200 OK | 超时 | avg | max |
|------|--------|------|-----|-----|
| 1 | 1 | 0 | 28ms | 28ms |
| 10 | 10 | 0 | 62ms | 68ms |
| 30 | 30 | 0 | 272ms | 345ms |
| 60 | 60 | 0 | 364ms | 499ms |
| 100 | 100 | 0 | 712ms | 967ms |
→ 瞬时 100 并发也扛得住,0 超时。平台链路(token / 签名 / 网络 / 并发引擎)全通。
**实验 C:同任务历史对比(同一平台、同一配置)**
| 执行 | 总请求 | 失败 | 平均响应 |
|------|--------|------|---------|
| 09-02 `exec_8d4bc2f5` | 31355 | **0** | 5.7s |
| 09-07 `exec_c043b3e5` | 12615 | 1167 | **14.3s** |
同一任务(`perf_1c72c087`:100 并发 / 1800s / mix / think_time=0),
两次结果差近一倍 —— 平台代码未变,变量只在被测系统当时的状态。
---
## 3. 根因结论
### 3.1 直接原因(本次现象)—— 平台无缺陷
1. **「全是超时」是明细采集模式导致的误读**
任务配置 `request_detail_enabled=errors_only`(只记录失败请求),
明细表里只有 1166 条失败样本,报告页明细列表因此看起来 100% 超时;
真实成功率 **90.7%**(2xx 11448 / 总 12615)。
2. **「token 是 ***」是脱敏显示**:入库前敏感字段统一脱敏,
真实请求带有效 Bearer token(11448 个 2xx + 0 个 401 为证)。
### 3.2 根本原因(性能结果本身)—— 被测系统在持续满负荷下劣化
1. **无思考时间持续压 30 分钟**`think_time_enabled=0`
每个 VU 响应一回来立刻发下一个,100 并发 = 持续满负荷。
用户手动使用是单用户低频操作,两者量级完全不同。
2. **测试数据膨胀**:模板创建接口(权重 20%)每次压测都往模板查询接口
查询的同一张表插数据(companyNumber 均为 `CN-WQF-UBAINS`)。
该查询单发已返回 **58KB**;表越压越大 → 查询越来越慢
(三接口中最慢:avg 19s、p95 31s)→ 30s 超时被切断。
09-02 之后表里已多一批数据,09-07 基数更大 → 比上次差 ——
两次结果的差异本身就是数据膨胀的证据。
3. **预约接口大量业务冲突**:压测中预约返回 `A0023 该时段已有会议安排`
(HTTP 200 但业务失败)。100 个 VU 抢同一 `cnum` 的时段,
数据库锁等待拖慢整体响应。
4. **被测系统连接池水位偏高**:目标机资源监控显示
`max_used_connections=93 / max_connections=151`,持续满负荷下连接池水位接近上限。
### 3.3 责任界定
- **平台测试管理平台:无缺陷**。登录/签名/token/并发引擎全链路正确,
明细脱敏与 errors_only 采集为既有设计(详见修复方案 3.1 / 3.2)。
- **被测系统(192.168.5.44):存在性能劣化**。30 分钟持续 100 并发下
出现 31s 级响应(p95≈超时阈值),且数据膨胀会逐次放大劣化。
---
## 4. 修复方案(平台侧体验优化,非缺陷修复)
### 4.1 明细采集模式显式标注(消除「全是超时」误读)
- 报告页「察看结果树」顶部 / 明细列表空态显示当前采集模式
`errors_only`:仅记录失败请求 / `off`:未采集 / `on`:全量采集),
并提示「成功请求未展示不代表执行失败」。
- 执行汇总卡片补充 `request_detail_enabled` 展示。
### 4.2 请求明细脱敏规则微调(保留 token 前缀便于核对)
`_desensitize()``Authorization` 头由整体 `***` 改为保留前 20 字符 +
`***`(如 `Bearer eyJhbGciOiJIUzI1NiIs...***`),
既防泄露又可核对「token 确实带上了」;密码/token 字段仍整体脱敏。
### 4.3 业务断言支持 `response_body` code 校验(可选,提升成功率真实度)
预约接口配置断言 `code == "200"`,将 `A0023` 等业务失败从「成功」中剔除,
使成功率反映业务真实结果(需在报告页断言失败展示处给出响应体片段便于定位)。
### 4.4 压测方法论建议(被测系统侧,输出给被测系统团队)
1. 排查 `getTemplatePage` 大结果集 / 深分页性能(单发 58KB × 高频);
2. 压测前清理模板测试数据,或压测用独立 companyNumber 隔离,消除数据膨胀干扰;
3. 核查应用线程池 / 连接池配置(max_used_connections 已到 93/151);
4. 混合场景建议开启思考时间(think_time),更贴近真实用户节奏。
---
## 5. 验证结果
| 验证项 | 结果 |
|--------|------|
| 本次执行真实成功率 90.7%(2xx 11448 / 12615) | ✅ 数据库确认 |
| 明细表仅存失败样本(errors_only 采集) | ✅ 数据库确认(1166 条全 success=0) |
| 请求头脱敏为 `***` 但真实带 token(0 个 401 + 11448 个 2xx) | ✅ 对照实验 A/B |
| 单发接口正常(登录 640ms / 查询 254ms / 创建 1231ms / 预约 320ms) | ✅ 对照实验 A |
| 瞬时 100 并发 0 超时(avg 712ms / max 967ms) | ✅ 对照实验 B |
| 同任务 09-02 全成功(31355 请求 0 失败)vs 09-07 1167 失败 | ✅ 对照实验 C |
| 报告页采集模式标注 / 脱敏前缀保留 / 业务断言 | ⏳ 待部署 |
---
## 6. 经验总结
1. **「全是超时」先看明细采集模式再下结论**:errors_only 模式下
明细列表天然只有失败样本,成功率要看执行汇总,不是看察看结果树。
2. **脱敏 ≠ 没传**:敏感字段入库前统一脱敏是安全设计,
判断「有没有带 token」要看 401/403 占比,不是看明细里的 `***`
3. **压测结论要用对照实验支撑**:单发正常 + 瞬时并发正常 + 历史同配置对比,
才能把「被测系统持续满负荷劣化」与「平台缺陷」区分开。
4. **压测数据要隔离**:写接口往查询接口的数据表灌数据,
会让性能结果逐次劣化 —— 压测数据膨胀是慢查询类问题的隐藏放大器。
5. **业务失败 ≠ HTTP 失败**:HTTP 200 但业务返回错误码(A0023),
需要断言层把业务失败剔出来,成功率才真实。
---
*本文档由 Claude Code 生成*
......@@ -264,6 +264,29 @@ mix → <el-tag type="primary" size="small">混合场景 Mix</el-tag>
2. 长时间运行时如果前端 WS 断连,监控页自动切回放模式(已有机制)
3. 单次 8h 执行的内存/磁盘需在生产 5.60 提前确认(数据目录容量、快照膨胀)
### 5.4 业务断言指引:HTTP 200 ≠ 业务成功(2026-09-07 补充)
混合场景写接口在并发冲突时常见「HTTP 200 + 业务错误码」响应,
如预约接口返回 `{"success":false,"code":"A0023","message":"该时段已有会议安排,请重新选择"}`
无断言时这类响应计入成功,**成功率虚高**
**建议为每个接口配置业务断言**(任务编辑 → 接口 → 断言):
| 接口 | 断言配置 | 作用 |
|------|---------|------|
| 预约 PUT / 模板创建 POST | `response_body``$.success` equals `true` | A0023 等业务失败计入断言失败 |
| 模板查询 GET | `response_body``$.code` equals `"200"` | 业务码校验 |
| 通用兜底 | `status_code` equals `200` | HTTP 层校验 |
断言失败计入 `fail_count` / `error_type_assertion`
报告「接口级统计明细」成功率即反映业务真实结果。
> 排查案例:2026-09-07 混合场景 100 并发执行被误读为「全部超时」,
> 真相是 errors_only 采集模式只入库失败样本(真实成功率 90.7%)+ token 脱敏显示 `***`。
> 详见 `_问题处理_混合场景全量超时_请求明细token脱敏误读.md`。
> 同批压测还确认:持续满负荷下模板查询接口随测试数据膨胀逐步劣化(单发 254ms → 压测 avg 19s),
> **压测数据隔离(独立 companyNumber / 压测前清库)是保证结果可比的前提**。
---
## 六、验收标准
......
......@@ -2732,8 +2732,9 @@ class PerformanceExecutor:
"""
构建请求明细记录(JMeter 察看结果树,2026-08-25 新增)
脱敏规则:请求头/请求体中的 Authorization / password / token / secret / sign
等敏感字段在入库前替换为 `***`。
脱敏规则:请求头/请求体中的 password / token / secret / sign 等敏感字段
在入库前替换为 `***`;Authorization 头保留前 20 字符 + `***`
(便于核对请求确实携带 token,2026-09-07)。
响应体截断:默认保留前 8KB(防大响应撑爆数据库)。
错误消息截断:保留前 500 字符。
......@@ -2785,6 +2786,7 @@ class PerformanceExecutor:
将 dict/list/str 递归中的敏感字段值替换为 `***`。
敏感键名匹配(不区分大小写):authorization / password / token / secret / sign
及其子串组合(如 X-SIGN、access_token、client_secret)。
例外:authorization 值为长字符串时保留前 20 字符(核对 token 已携带)。
Args:
data: 请求头 / 请求体 / 响应头 / 响应体(任意可 JSON 序列化结构)
......@@ -2793,13 +2795,23 @@ class PerformanceExecutor:
Any: 脱敏后的数据
"""
_SENSITIVE_KEYS = ("authorization", "password", "token", "secret", "sign")
# Authorization 头保留前 20 字符(如 "Bearer eyJhbGciOiJIUzI1NiIs"),
# 既防泄露又可核对请求确实携带 token(2026-09-07 混合场景超时误读排查)
_PREFIX_KEEP_KEYS = ("authorization",)
if isinstance(data, dict):
return {
k: ("***" if any(s in k.lower() for s in _SENSITIVE_KEYS)
else PerformanceExecutor._desensitize(v))
for k, v in data.items()
}
result = {}
for k, v in data.items():
kl = k.lower()
if any(s in kl for s in _SENSITIVE_KEYS):
if (any(s in kl for s in _PREFIX_KEEP_KEYS)
and isinstance(v, str) and len(v) > 20):
result[k] = v[:20] + "***"
else:
result[k] = "***"
else:
result[k] = PerformanceExecutor._desensitize(v)
return result
if isinstance(data, list):
return [PerformanceExecutor._desensitize(v) for v in data]
if isinstance(data, str):
......
......@@ -517,6 +517,8 @@ class PerformanceReportSummary(BaseModel):
start_time: Optional[str] = None
end_time: Optional[str] = None
account_key: str = ""
# 请求明细采集模式(off/on/errors_only),报告页展示防止「全是超时」误读(2026-09-07)
request_detail_enabled: str = "off"
model_config = ConfigDict(populate_by_name=True, alias_generator=to_camel)
......
......@@ -688,6 +688,7 @@ class PerformanceService:
"start_time": task.start_time.isoformat() if task.start_time else None,
"end_time": task.end_time.isoformat() if task.end_time else None,
"account_key": task.account_key,
"request_detail_enabled": task.request_detail_enabled or "off",
},
"task_config": task_config,
"metrics": {
......@@ -818,6 +819,8 @@ class PerformanceService:
"start_time": execution.start_time.isoformat() if execution.start_time else None,
"end_time": execution.end_time.isoformat() if execution.end_time else None,
"account_key": task.account_key if task else "",
# 请求明细采集模式(off/on/errors_only),报告页展示防止「全是超时」误读(2026-09-07)
"request_detail_enabled": execution.request_detail_enabled or "off",
},
"task_config": task_config,
"metrics": {
......
......@@ -693,6 +693,8 @@ export interface PerformanceReportSummary {
startTime: string | null
endTime: string | null
accountKey: string | null
/** 请求明细采集模式(off / on / errors_only) */
requestDetailEnabled?: string
/** 场景类型 */
scenarioType?: PerfScenarioType
}
......
......@@ -188,6 +188,11 @@
<el-descriptions-item label="开始时间 Start">{{ formatTime(report.summary.startTime) }}</el-descriptions-item>
<el-descriptions-item label="结束时间 End">{{ formatTime(report.summary.endTime) }}</el-descriptions-item>
<el-descriptions-item label="实际时长 Duration">{{ report.summary.durationActual != null ? report.summary.durationActual.toFixed(1) + 's' : '-' }}</el-descriptions-item>
<el-descriptions-item label="明细采集 Detail Mode">
<el-tag v-if="report.summary.requestDetailEnabled === 'on'" type="success" size="small">全量采集 on</el-tag>
<el-tag v-else-if="report.summary.requestDetailEnabled === 'errors_only'" type="warning" size="small">仅失败 errors_only</el-tag>
<el-tag v-else type="info" size="small">关闭 off</el-tag>
</el-descriptions-item>
</el-descriptions>
<!-- 事务标记(仅事务任务) -->
......
......@@ -11,12 +11,22 @@
<!-- 未开启采集 且 确实无数据(避免后端 flag 误置时掩盖已采集数据) -->
<el-empty
v-if="enabled === 'off' && total === 0 && !loading"
description="请求详情采集未开启,请在任务配置中开启「请求详情采集」"
description="请求详情采集未开启,可在任务配置中开启「请求详情采集」(支持全量 on / 仅失败 errors_only)"
:image-size="80"
/>
<!-- 有数据时强制显示(后端 flag 误置时仍显示数据) -->
<template v-else-if="items.length > 0 || total > 0">
<!-- 采集模式提示:errors_only 下列表只含失败请求,防止「全是超时/失败」误读(2026-09-07) -->
<el-alert
v-if="enabled === 'errors_only'"
type="warning"
:closable="false"
show-icon
class="mode-alert"
title="当前采集模式为「仅记录失败请求」(errors_only):此列表仅展示超时、断言失败、异常等失败样本,成功请求未入库。执行真实成功率请以报告页顶部的「执行汇总」为准。"
/>
<!-- 筛选栏 -->
<div class="filter-bar">
<el-select
......@@ -460,6 +470,10 @@ onMounted(() => {
padding: 4px 0;
}
.mode-alert {
margin-bottom: 12px;
}
.filter-bar {
display: flex;
align-items: center;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论