# PRD - 目标机资源监控（SSH方式 · Target System Resource Monitoring）

> **文档版本**: v1.0
> **创建日期**: 2026-08-21
> **模块类型**: 性能测试 - 目标机资源监控
> **优先级**: P1
> **关联 PRD**: `_PRD_性能测试模块需求文档.md`、`_PRD_执行机资源监控.md`

---

## 一、背景与目标

### 1.1 背景

`_PRD_执行机资源监控.md` 实现了**执行机/压测机**（运行压测引擎的机器）的 CPU、内存、网络监控，用于判断压测结果是否受执行机瓶颈影响。但性能测试还缺少另一半关键数据——**被测系统（目标机）自身的资源占用**。

被测系统（统一管理平台，192.168.5.44）在压测期间：

- CPU 是否被打满（是否达到处理瓶颈）
- 内存是否持续增长（是否存在内存泄漏 / GC 压力）
- 资源曲线与 TPS / 响应时间曲线的对应关系（定位瓶颈是 I/O 密集还是 CPU 密集）

这些指标是**性能测试结论的必要参数**，目前完全缺失。JMeter + PerfMon 通过在被测机上部署 ServerAgent 实现，本平台需要一种**零 Agent 部署**的方式，即通过 SSH 远程采集。

### 1.2 方案选择

| 方案 | 描述 | 结论 |
|------|------|------|
| **方案 A（选用）** | 压测引擎通过 **SSH（paramiko）** 连接目标机，周期执行 `/proc/stat`、`/proc/meminfo` 命令计算 CPU%/内存% | ✅ 零 Agent、零改造目标机、paramiko 已是项目依赖 |
| 方案 B | 目标机部署 Agent（如 PerfMon ServerAgent）上报 | ❌ 需改造目标机，违背零部署原则 |
| 方案 C | 通过被测系统 API 暴露资源指标 | ❌ 需被测系统配合改造，不可行 |

### 1.3 目标

1. **目标机资源采集**：压测执行期间通过 SSH 定时采集目标机 CPU、内存指标
2. **实时推送**：与执行机资源并列，通过 WebSocket 随快照推送（`targetResource` 字段）
3. **趋势分析**：监控页 + 报告页展示目标机资源曲线（CPU + 内存）
4. **瓶颈定位**：资源曲线与 TPS/响应时间曲线对照，辅助判断被测系统瓶颈
5. **零 Agent 部署**：仅依赖 paramiko SSH 连接，不改造目标机
6. **容错降级**：SSH 连接失败时不影响压测执行，仅记录错误并跳过目标机监控

---
## 二、功能范围

### 2.1 核心功能

| 功能 | 描述 | 优先级 |
|------|------|--------|
| 目标机 CPU 使用率采集 | 通过 `/proc/stat` 两次采样差值计算整体 CPU% | P0 |
| 目标机内存使用率采集 | 通过 `/proc/meminfo` 读取 MemTotal / MemAvailable 计算 | P0 |
| 目标机信息展示 | 主机名、操作系统、CPU 核数、总内存 | P1 |
| 采样周期 | 默认 2 秒（SSH 往返有开销，比本机采样放宽） | P0 |
| WebSocket 实时推送 | `targetResource` 字段随快照推送 | P0 |
| 趋势图表 | 监控页 + 报告页目标机资源趋势图 | P0 |
| 资源报警提示 | 目标机 CPU/内存超过阈值时监控页提示 | P1 |
| SSH 异常降级 | 连接失败自动重连，重连失败则跳过监控，不影响压测 | P0 |

### 2.2 采集指标清单

| 指标 | 单位 | 说明 | 计算来源 |
|------|------|------|---------|
| cpu_percent | % | 目标机 CPU 总使用率 | `/proc/stat` 两次读取差值：`100 * (1 - idle_delta / total_delta)` |
| mem_percent | % | 目标机物理内存使用率 | `/proc/meminfo`：`100 * (1 - MemAvailable / MemTotal)` |
| mem_used_mb | MB | 已用内存（含 cache） | `MemTotal - MemAvailable` |
| mem_total_mb | MB | 总内存 | `/proc/meminfo` MemTotal |
| hostname | str | 目标机主机名 | `hostname` 命令（一次性） |
| os_name | str | 操作系统名称 | `/etc/os-release` 或 `uname -a`（一次性） |
| cpu_cores | int | CPU 核数 | `nproc`（一次性） |
| load_avg | str | 系统负载（可选） | `/proc/loadavg` |
| uptime | str | 系统运行时长（可选） | `/proc/uptime` 或 `uptime` |
| connected | bool | SSH 连接是否正常 | 采样时状态 |

### 2.3 与执行机资源监控的区别

| 维度 | 执行机资源监控（已有） | 目标机资源监控（本次） |
|------|----------------------|----------------------|
| 监控对象 | 压测引擎所在机器（执行机） | 被测系统（目标机，如 192.168.5.44） |
| 采集方式 | 本机进程内直接采集 | 远程 SSH 命令采集 |
| 采样间隔 | 1 秒 | 2 秒（SSH 往返开销） |
| 指标范围 | CPU/内存/网络 | CPU/内存（+ 可选负载） |
| 字段命名 | `resource` | `targetResource` |
| 依赖 | psutil / 标准库 | paramiko（已有依赖） |
| 降级策略 | 标准库兜底 | SSH 失败跳过，不影响压测 |

---
## 三、详细需求

### 3.1 数据模型

#### 3.1.1 TargetResourceSnapshot（目标机资源快照）

随 WebSocket 快照推送，每采样周期一条：

```json
{
  "cpuPercent": 45.2,
  "memPercent": 62.1,
  "memUsedMb": 9812.5,
  "memTotalMb": 15481.2,
  "hostname": "loacalhost",
  "osName": "openEuler 24.03",
  "cpuCores": 8,
  "loadAvg": "1.09, 2.98, 2.61",
  "connected": true
}
```

#### 3.1.2 存储方案

与执行机资源监控一致，采用两层存储：

| 层次 | 存储 | 说明 |
|------|------|------|
| 实时推送 | WebSocket，不落库 | 监控页实时显示 |
| 历史报告 | `performance_task` 增加 `target_resource_summary` JSON 字段 | 独立字段存储目标机采样序列 + 统计，与 `resource_summary`（执行机）并列 |

`target_resource_summary` 结构：

```json
{
  "targetHost": "192.168.5.44",
  "osName": "openEuler 24.03",
  "cpuCores": 8,
  "stats": {
    "sampleCount": 150,
    "avgCpuPercent": 35.2,
    "maxCpuPercent": 87.4,
    "avgMemPercent": 61.8,
    "maxMemPercent": 74.2
  },
  "series": [
    {"elapsed": 2.0, "cpuPercent": 30.1, "memPercent": 61.5, "loadAvg": "1.09, 2.98, 2.61"},
    {"elapsed": 4.0, "cpuPercent": 42.7, "memPercent": 61.9, "loadAvg": "1.10, 2.95, 2.60"}
  ]
}
```

### 3.2 采集模块设计

#### 3.2.1 TargetResourceMonitor 类

新建 `backend/app/executors/target_resource_monitor.py`：

```python
class TargetResourceMonitor:
    """目标机资源监控器：通过 SSH 远程采集目标系统 CPU/内存指标"""

    def __init__(self, host, port=22, username=None, password=None,
                 private_key=None, sampling_interval=2.0, timeout=10):
        self._host = host
        self._interval = sampling_interval
        self._ssh = None                 # paramiko SSHClient（复用连接）
        self._last_cpu = None            # 上次 /proc/stat 原始值（差值计算）
        self._meta = {                   # 一次性元信息
            "hostname": None, "osName": None, "cpuCores": None
        }

    def connect(self) -> bool:
        """建立 SSH 连接并获取目标机元信息，失败返回 False"""

    def sample(self) -> dict | None:
        """单次采样：单条 shell 命令合并读取 /proc/stat + /proc/meminfo + /proc/loadavg，
         返回含 cpuPercent/memPercent 的字典；连接失败尝试重连，仍失败返回 None"""

    def dispose(self):
        """关闭 SSH 连接"""
```

**关键实现要点**：

1. **单条命令合并采集**：`cat /proc/stat | head -1; cat /proc/meminfo | grep -E 'MemTotal|MemAvailable'; cat /proc/loadavg` —— 一次 SSH 往返取全部字段，减少连接开销
2. **持久连接**：TSH 连接建立后复用，避免每采样周期新建连接
3. **CPU 差值计算**：第一次采样只记录原始值（无差值）；从第二次起 `usage = 100 * (1 - (idle2-idle1)/(total2-total1))`
4. **断线重连**：`exec_command` 抛异常 → 重建连接（最多重试 1 次）→ 仍失败返回 None 并计数，连续失败 N 次（默认 5）后停止监控该任务
5. **采样间隔 2 秒**：SSH 往返约 10-50ms，2 秒间隔足够且不增加目标机负担
6. **超时控制**：连接 timeout 10s，命令 timeout 10s，防止目标机不可达时阻塞压测

#### 3.2.2 集成方式

在 `PerformanceExecutor` 中与 `_snapshot_loop`、`_resource_loop`（执行机）并行启动 `_target_resource_loop`：

```
PerformanceExecutor.execute()
├── _snapshot_loop（现有）：每秒采集请求指标 → 回调推送
├── _resource_loop（现有）：每秒采集执行机资源 → 合并快照 resource 字段
└── _target_resource_loop（新增）：每 2 秒 SSH 采集目标机资源 → 合并快照 targetResource 字段
```

推送合并策略：与执行机资源一致，合并进现有 `performance_snapshot` 消息的 data（同频率、同频道）：

```json
{
  "type": "performance_snapshot",
  "task_id": "perf_xxx",
  "data": {
    "elapsed": 5.2,
    "tps": 123.45,
    "...": "...",
    "resource": { "cpuPercent": 45.2, "memPercent": 62.1 },
    "targetResource": { "cpuPercent": 35.2, "memPercent": 61.8 }
  }
}
```

#### 3.2.3 开关与配置

目标机监控默认**关闭**（需要配置目标机地址凭据），任务级或全局配置启用：

```yaml
# config.py 新增（env 可覆盖）
TARGET_RESOURCE_MONITOR_ENABLED: bool = False  # 默认关闭，配置目标机后开启
TARGET_MONITOR_HOST: str = ""                  # 目标机 IP（如 192.168.5.44）
TARGET_MONITOR_PORT: int = 22
TARGET_MONITOR_USERNAME: str = ""              # 如 root
TARGET_MONITOR_PASSWORD: str = ""              # 如 Ubains@123
TARGET_MONITOR_INTERVAL: float = 2.0           # 采样间隔（秒）
TARGET_ALERT_CPU: float = 85.0                 # CPU 告警阈值（%）
TARGET_ALERT_MEM: float = 85.0                 # 内存告警阈值（%）
```

> 说明：目标机凭据属于敏感信息，通过环境变量注入；不写入任务表。同一平台多台目标机（后续扩展）可演进为任务级配置，本次先全局配置。

### 3.3 实时监控页

在 `MonitorPanel.vue` 增加「目标机资源」区块（与「执行机资源」区块并列）：

#### 3.3.1 资源卡片

| 卡片 | 显示内容 | 告警阈值 |
|------|---------|---------|
| 目标机 CPU | 当前 CPU % + 迷你趋势 | > 85% 红色 |
| 目标机内存 | 当前内存 % + 已用/总量 | > 85% 红色 |
| 目标机信息 | 主机名 / 系统 / 核数 | 连接断开显示红色「未连接」 |

#### 3.3.2 资源趋势图

- 「目标机资源趋势」组合图：CPU % + 内存 % 双折线，与 TPS 图共享时间轴
- 无 `targetResource` 数据时隐藏该区块（保持现有布局）

### 3.4 报告页

在 `ReportPanel.vue` 增加「目标机资源」区块：

- 目标机信息卡片：目标机地址、系统、CPU 核数
- 资源汇总卡片：平均 CPU / 最大 CPU / 平均内存 / 最大内存
- 目标机资源趋势图（CPU + 内存双折线）
- 数据来源：`target_resource_summary` JSON 字段（无数据时隐藏）

### 3.5 批量监控页

`BatchMonitor.vue` 增加目标机资源趋势图（选中任务维度，逻辑与执行机资源一致）。

### 3.6 后端配置说明

| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| TARGET_RESOURCE_MONITOR_ENABLED | false | 总开关，默认关闭 |
| TARGET_MONITOR_HOST | 空 | 目标机地址，启用时必填 |
| TARGET_MONITOR_PORT | 22 | SSH 端口 |
| TARGET_MONITOR_USERNAME | 空 | SSH 用户名 |
| TARGET_MONITOR_PASSWORD | 空 | SSH 密码（建议环境变量注入） |
| TARGET_MONITOR_INTERVAL | 2.0 | 采样间隔秒数 |
| TARGET_ALERT_CPU / TARGET_ALERT_MEM | 85.0 | 告警阈值 |

---
## 四、非功能需求

| 项 | 要求 |
|----|------|
| 采集开销 | 单次采样 1 次 SSH 往返（< 100ms），间隔 2 秒，不影响压测 |
| 目标机影响 | 仅执行 `cat` 类只读命令，无侵入、无改动 |
| 容错 | SSH 不可达/认证失败 → 记录错误 + 跳过监控，压测正常继续 |
| 安全性 | 凭据仅存环境变量，不落库；密码模式支持，预留私钥模式 |
| 数据量控制 | target_resource_summary 序列上限 3000 点，超出降采样（每 2 点取 1） |
| 兼容性 | 目标机需支持 `/proc/stat` + `/proc/meminfo`（Linux 通用），非 Linux 目标机自动降级跳过 |

---
## 五、现有资产复用与对比

### 5.1 资产复用

| 现有资产 | 用途 |
|---------|------|
| `deploy_executor.py` | paramiko SSH 连接模式（密码/私钥认证、AutoAddPolicy） |
| `_resource_loop` 框架 | 目标机资源循环并行（复用快照合并模式） |
| WebSocket Manager | 快照推送通道（增加 targetResource 字段） |
| `resource_summary` 存储模式 | `target_resource_summary` JSON 字段同构 |
| `MonitorPanel.vue` / `ReportPanel.vue` 资源区块 | 目标机资源 UI 复用（新增独立区块） |

### 5.2 与 JMeter PerfMon 对比

| 维度 | JMeter + PerfMon | 本方案 |
|------|------------------|--------|
| Agent 部署 | 每台被测机需部署 ServerAgent | 零部署，SSH 远程采集 |
| 指标范围 | CPU/内存/磁盘/网络 | CPU/内存（核心指标） |
| 采样方式 | UDP 采样 | SSH 命令采样（只读命令） |
| 目标机改造 | 需开放 PerfMon 端口 4444 | 仅需 SSH 访问，改动最小 |
| 集成度 | 插件单独配置 | 与快照推送天然合并 |

---
## 六、风险与缓解

| 风险 | 影响 | 缓解措施 |
|------|------|---------|
| 目标机 SSH 凭据未知/变更 | 无法采集 | 配置化 + 环境变量注入；连接失败明确报错提示 |
| SSH 往返延迟影响采样 | 采样频率受限 | 间隔放宽至 2s，单命令批量采集 |
| 目标机不可达（压测中宕机） | 采样中断 | 断线重连 + 连续失败停止，不影响压测 |
| CPU 差值首采样为 0 | 首点为 0 | 跳过第一次采样差值，从第二次计算 |
| 目标机非 Linux | 命令不适用 | 自动检测失败后跳过，仅提示不支持 |
| 凭据明文存储风险 | 安全 | 仅环境变量注入，不写库不写前端 |

---
## 七、附录

### 7.1 数据流

```
TargetResourceMonitor 每 2 秒 SSH 采样
    ├── /proc/stat 差值 → cpuPercent
    ├── /proc/meminfo → memPercent / memUsedMb / memTotalMb
    └── /proc/loadavg → loadAvg
        ↓
合并进 performance_snapshot 消息 (data.targetResource)
        ↓
WebSocket 推送给前端
    ├── MonitorPanel 目标机资源卡片 + 趋势图
    └── BatchMonitor 目标机资源曲线
        ↓
执行结束 → target_resource_summary 写入任务 JSON 字段
        ↓
ReportPanel 报告页目标机资源区块
```

### 7.2 后端 API 变更

| 接口 | 变更 | 说明 |
|------|------|------|
| GET /api/performance/report/{taskId} | 扩展 | 响应增加 targetResourceSummary 字段 |

### 7.3 前端 API/类型变更

| 文件 | 变更 |
|------|------|
| `frontend/src/types/performance.ts` | PerformanceSnapshot 增加 targetResource 字段；Report 增加 targetResourceSummary |
| `frontend/src/views/performance/MonitorPanel.vue` | 目标机资源卡片 + 趋势图 |
| `frontend/src/views/performance/ReportPanel.vue` | 目标机资源汇总 + 趋势图 |
| `frontend/src/views/performance/BatchMonitor.vue` | 目标机资源趋势图 |

### 7.4 目标机 SSH 验证记录

| 项 | 值 |
|----|----|
| 目标机地址 | 192.168.5.44 |
| 认证方式 | root / Ubains@123（与 5.60 服务器同凭据，已验证通过） |
| 操作系统 | openEuler 24.03 (LTS-SP3) |
| CPU 核数 | 8（QEMU Virtual CPU 2.5+） |
| 总内存 | 15853548 kB ≈ 15.8 GB |
| 关键命令 | `/proc/stat`、`/proc/meminfo`、`/proc/loadavg` 均可用 |

---
*本文档为目标机资源监控功能（SSH 方案）的需求定义，后续将输出计划执行文档用于指导开发。*