# MetricsHealthCheckAnalyze 实现逻辑与 timeAggregation 语义分析

## 分析范围

- 只基于代码与已生成结果文件分析，不改业务代码。
- 入口函数：`pkg/handlers/healtheck_handler.go` 的 `MetricsHealthCheckAnalyze`。
- 结果文件：`output/healthcheck-report/10989049060142230334_saas01-wlt_metrics_healthcheck.json`。
- 重点字段：`timeAggregation = "P90"`、`timeAggregation = "AVG"`。

## 一、调用链与数据流

### 1) Handler 层：参数校验与请求补全

`MetricsHealthCheckAnalyze` 的关键流程：

1. 解析请求 JSON 为 `MetricsHealthCheckRequest`。
2. 校验 `clusterId` 与 `timeRange.start/end` 必填。
3. 当 `clusterUrls.clusterMetricsURL` 或 `region` 缺失时，调用 `clinicClient.GetClusterDataURLs` 补全 `ClusterMetricsURL / DataPlaneMetricsURL / Region / Provider / DeployType`。
4. 调用 `metricsHCSvc.Analyze(ctx, &req)` 执行巡检。
5. 将响应补充 `resultJsonUrl`，并异步落盘到 `./output/healthcheck-report/...json`。

### 2) Service 层：组装 cluster 并触发核心巡检

`MetricsHealthCheckService.Analyze`：

1. 将请求映射到 `model.Cluster`（包括 clusterId、deployType、region、provider、metrics URL）。
2. 调用 `metrics.GetSingleClusterMetricsResultWithRange(ctx, cluster, startTime, endTime)`。
3. 将底层 `TableRowAggregation` 转换成接口返回格式 `MetricsHealthCheckResultItem`。

### 3) Metrics 查询层：Primary 与 Baseline

`GetSingleClusterMetricsResultWithRange` 会对资源类和性能类分别执行：

- Primary 时间窗：`[startTime, endTime]`
- Baseline 时间窗：`[startTime-14天, startTime]`

具体由 `prometheus.MetricsQueryBatchWithRange` 实现。

### 4) 差异计算层：生成 timeAggregation 结果

资源类指标在 `getResourceDiffResult` 中计算两条 DiffResult：

- `TimeAggregation = "P90"`：对 Primary 的时间序列做 `Percentile(..., 90)`，再与 Baseline 的 P90 做比值差异。
- `TimeAggregation = "AVG"`：对 Primary 的时间序列做 `Mean(...)`，再与 Baseline 的 AVG 做比值差异。

差异公式：

- `diffP90 = (primaryP90 / baselineP90) - 1`
- `diffAvg = (primaryAvg / baselineAvg) - 1`

同时 `Max/Avg` 字段来自 Primary 原始时间点值（非 trimmed）。

### 5) 表格映射层：输出 threshold/status/link

`getResourceQueryResult` 将 `DiffResult` 转成最终 JSON 字段：

- `threshold = fmt.Sprintf("%.1f%%", diff*100)`
- `timeAggregation` 直接取 `DiffResult.TimeAggregation`
- `queryName` 取查询名（如 `TiDB_CPU_P80`）
- 资源类会拼接 `queryName + "_" + timeAggregation` 做状态与链接路由（例如 `TiDB_CPU_P80_P90`、`TiDB_CPU_P80_AVG`）
- 状态判定由各 `getXXXState` 函数完成；TiDB CPU 相关阈值是 `threshold > 0.2` 时标记 `Need Attention`，否则 `No Risk`。

## 二、timeAggregation 的代码语义

## 1) 先区分两个“百分位”

- `queryName` 中的 `P80/P90`：来自 PromQL（实例维度聚合）
  - 例如 `TiDB_CPU_P80` 的查询是 `quantile(0.8, ... by (instance))`。
- `timeAggregation` 中的 `P90/AVG`：来自代码统计（时间维度聚合）
  - 对同一个 queryName 在时间窗内拿到的一串值，再做 P90 或平均。

所以 `queryName = TiDB_CPU_P80` 且 `timeAggregation = P90` 的真实含义是：

“先在每个采样时刻对实例 CPU 取 P80，再对整个时间窗内这些 P80 点位取时间维度 P90，并与基线窗口对应统计值比较。”

## 2) `timeAggregation = "P90"`

- 数据源：`trimmedPrimary` 与 `trimmedBaseline`（去掉头尾 5% 异常值后的序列）。
- 计算：`stats.Percentile(..., 90)`。
- 产物：DiffResult 中 `TimeAggregation: "P90"`，阈值字段来自 `(primaryP90/baselineP90)-1`。

## 3) `timeAggregation = "AVG"`

- 数据源：同样用 `trimmedPrimary` 与 `trimmedBaseline`。
- 计算：`stats.Mean(...)`。
- 产物：DiffResult 中 `TimeAggregation: "AVG"`，阈值字段来自 `(primaryAvg/baselineAvg)-1`。

## 4) 为什么同一个 queryName 会有两条记录

因为资源类每个 queryName 固定产出两种时间聚合视角：

- 一条是 `P90`（偏“高位风险”）
- 一条是 `AVG`（偏“整体水平”）

两条的 `max/avg` 往往相同，因为它们都来自同一批 Primary 原始点位统计；不同的是 `threshold` 的计算口径（P90 比 P90，AVG 比 AVG）。

## 三、对你给出的样例的逐条解释

样例：

```json
{
  "queryName": "TiDB_CPU_P80",
  "timeAggregation": "P90",
  "threshold": "-0.6%",
  "status": "No Risk",
  "max": "28.36",
  "avg": "10.89"
}
```

语义：

1. `queryName = TiDB_CPU_P80`
   - 每个采样时刻先取 TiDB 实例 CPU 的 P80 值。
2. `timeAggregation = P90`
   - 对整个 primary 时间窗里的这组 “P80 序列” 再取时间维度 P90。
3. `threshold = -0.6%`
   - `(primaryP90 / baselineP90 - 1) * 100 = -0.6%`，说明 primary 相比 baseline 略降。
4. `status = No Risk`
   - 该类规则阈值是 `diff > 0.2` 才 `Need Attention`；`-0.006` 显然未超阈值。
5. `max = 28.36`、`avg = 10.89`
   - 来自 primary 原始时间点序列（不是 P90 聚合后的单点）。

同一 queryName 下另一条：

```json
{
  "queryName": "TiDB_CPU_P80",
  "timeAggregation": "AVG",
  "threshold": "6.9%",
  "status": "No Risk",
  "max": "28.36",
  "avg": "10.89"
}
```

差异只在阈值计算口径：

- 这里是 `(primaryAvg / baselineAvg - 1)`，得到 `+6.9%`。
- 但仍未超过 20% 告警线，所以仍是 `No Risk`。

## 四、结论

- `timeAggregation` 不是 PromQL 里的聚合标签，而是代码在“时间维度”上做的二次统计口径。
- `P90` 和 `AVG` 同时存在是设计使然：一个看高位波动，一个看整体均值。
- `queryName` 的 `P80/P90` 与 `timeAggregation` 的 `P90/AVG` 属于不同维度，不冲突。
- 你给出的 `TiDB_CPU_P80` 两条记录（P90 与 AVG）数值关系完全符合当前实现逻辑。
