1
0
Files
DiAL/openspec/specs/target-metrics-api/spec.md
lanyuanxiaoyao 1c5cfafda6 feat: 前端指标体系增强 — Dashboard/Metrics API、2×4 统计区、趋势图面积+异常标记、连续状态列
- 新增 GET /api/dashboard 合并原 summary+targets 首屏接口
- 新增 GET /api/targets/:id/metrics 合并原 stats+trend 概览接口
- 后端指标纯函数:可用率、百分位、故障段分析、连续状态、UTC 小时分桶
- ProbeStore 窗口取数方法替代全量历史查询
- SummaryCards 扩展为 4 卡片(新增异常事件数)+ 数据新鲜度展示
- 表格新增「连续」列(Tag 渲染 capped 状态)
- OverviewTab 重构为 2×4 Statistic 多维度布局
- TrendChart 改为延迟范围面积图 + 红色异常标记点
- 删除旧路由(summary/targets/trend)和 computeTrendStats
- 同步 delta specs 到主 specs 并归档变更
2026-05-14 12:32:41 +08:00

165 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Purpose
定义单目标指标 API 的端点、响应类型、延迟百分位计算、MTTR 计算、最长故障时长计算、故障事件计数、当前连续状态、趋势数据应用层分桶和无数据口径。
## Requirements
### Requirement: 单目标指标 API
系统 SHALL 提供 `GET /api/targets/:id/metrics` 端点,返回单个目标在指定时间窗口内的概览统计和趋势数据。
#### Scenario: 获取目标指标
- **WHEN** 客户端请求 `GET /api/targets/1/metrics?from=ISO&to=ISO&bucket=1h`
- **THEN** 系统 SHALL 返回 JSON 对象包含 targetId、window、stats、trend 字段
#### Scenario: from 或 to 参数缺失
- **WHEN** 客户端请求 `GET /api/targets/1/metrics` 未提供 from 或 to 参数
- **THEN** 系统 SHALL 返回 400 状态码和错误信息
#### Scenario: 目标不存在
- **WHEN** 客户端请求 `GET /api/targets/999/metrics?from=ISO&to=ISO`
- **THEN** 系统 SHALL 返回 404 状态码和错误信息
#### Scenario: 无效的目标 ID
- **WHEN** 客户端请求 `GET /api/targets/abc/metrics?from=ISO&to=ISO`
- **THEN** 系统 SHALL 返回 400 状态码和错误信息
#### Scenario: bucket 参数缺失
- **WHEN** 客户端请求 `GET /api/targets/1/metrics?from=ISO&to=ISO` 未提供 bucket 参数
- **THEN** 系统 SHALL 默认使用 bucket=`1h`
#### Scenario: 不支持的 bucket 参数
- **WHEN** 客户端请求 `GET /api/targets/1/metrics?from=ISO&to=ISO&bucket=5m`
- **THEN** 系统 SHALL 返回 400 状态码和错误信息
### Requirement: TargetMetricsResponse 共享类型
系统 SHALL 在 `src/shared/api.ts` 中定义 `TargetMetricsResponse` 类型。
#### Scenario: 类型定义
- **WHEN** 前后端引用 `TargetMetricsResponse` 类型
- **THEN** 该类型 SHALL 包含 targetIdnumber、windowfrom/to/bucket、stats 和 trend 字段
#### Scenario: stats 字段
- **WHEN** metrics 响应包含 stats
- **THEN** stats SHALL 包含 totalChecks、upChecks、downChecks、availability、avgDurationMs、p95DurationMs、p99DurationMs、mttr、longestOutage、incidentCount、currentStreak 字段
#### Scenario: trend 字段
- **WHEN** metrics 响应包含 trend
- **THEN** trend SHALL 为数组,每个元素包含 bucketStart、avgDurationMs、minDurationMs、maxDurationMs、availability、totalChecks、upChecks、downChecks 字段
### Requirement: P95/P99 延迟计算
系统 SHALL 在后端应用层计算 P95 和 P99 延迟百分位数。
#### Scenario: 正常计算 P95
- **WHEN** 时间窗口内存在成功检查记录matched=1 且 duration_ms 不为 null
- **THEN** 系统 SHALL 取出所有成功检查的 duration_ms在后端应用层排序后取第 95 百分位值返回为 p95DurationMs
#### Scenario: 正常计算 P99
- **WHEN** 时间窗口内存在成功检查记录
- **THEN** 系统 SHALL 取第 99 百分位值返回为 p99DurationMs
#### Scenario: 无成功检查记录
- **WHEN** 时间窗口内无 matched=1 且 duration_ms 不为 null 的记录
- **THEN** p95DurationMs 和 p99DurationMs SHALL 返回 null
#### Scenario: 百分位计算方法
- **WHEN** 计算第 N 百分位
- **THEN** 系统 SHALL 将 duration_ms 升序排列,取 index = ceil(count * N / 100) - 1 位置的值
### Requirement: MTTR 计算
系统 SHALL 在后端应用层计算平均恢复时间Mean Time To Recovery
#### Scenario: 存在已恢复的故障段
- **WHEN** 时间窗口内存在至少一个已恢复的故障段(连续 matched=0 后跟 matched=1
- **THEN** 系统 SHALL 计算所有已恢复故障段的平均持续时间(从首个 matched=0 的 timestamp 到恢复后首个 matched=1 的 timestamp 之差),返回为 mttr毫秒
#### Scenario: 无已恢复的故障段
- **WHEN** 时间窗口内无已恢复的故障段(全部正常,或当前仍在故障中且无历史恢复)
- **THEN** mttr SHALL 返回 null
#### Scenario: 当前正在故障中
- **WHEN** 时间窗口内最后一段故障尚未恢复
- **THEN** 该未恢复的故障段 SHALL 不计入 MTTR 平均值
#### Scenario: 窗口起始即为故障且后续恢复
- **WHEN** 时间窗口内第一条记录即为 matched=0故障跨越了 from 边界),且该故障段在窗口内恢复
- **THEN** 该故障段 SHALL 不计入 MTTR 平均值(因无法确定真实故障开始时间),但 SHALL 计入 incidentCount
### Requirement: 最长故障时长
系统 SHALL 在后端应用层计算时间窗口内最长的单次故障持续时间。
#### Scenario: 存在故障段
- **WHEN** 时间窗口内存在故障段
- **THEN** 系统 SHALL 返回最长故障段的持续时间为 longestOutage毫秒
#### Scenario: 无故障
- **WHEN** 时间窗口内无 matched=0 的记录
- **THEN** longestOutage SHALL 返回 null
#### Scenario: 窗口起始即为故障
- **WHEN** 时间窗口内第一条记录即为 matched=0
- **THEN** 该故障段的持续时间 SHALL 从 from 参数开始计算
#### Scenario: 当前正在故障中
- **WHEN** 最后一段故障尚未恢复
- **THEN** 该故障段的持续时间 SHALL 计算为从故障开始到时间窗口 to 参数的时间差
### Requirement: 故障事件计数
系统 SHALL 在后端应用层计算时间窗口内的故障事件次数。
#### Scenario: 计算故障事件数
- **WHEN** 时间窗口内存在状态翻转matched 从 1 变为 0
- **THEN** 系统 SHALL 返回翻转次数为 incidentCount
#### Scenario: 无故障事件
- **WHEN** 时间窗口内所有检查均为 matched=1
- **THEN** incidentCount SHALL 返回 0
#### Scenario: 窗口起始即为故障
- **WHEN** 时间窗口内第一条记录即为 matched=0 且无前序记录可判断翻转
- **THEN** 该故障 SHALL 计为 1 次事件
#### Scenario: 连续异常只计一次
- **WHEN** 某目标连续 10 次 matched=0
- **THEN** 该连续异常段 SHALL 仅计为 1 次事件
### Requirement: 当前连续状态
系统 SHALL 返回目标当前的连续状态信息。
#### Scenario: 当前连续正常
- **WHEN** 目标最近的检查记录连续为 matched=1
- **THEN** currentStreak SHALL 返回 `{ up: true, count: N }`N 为连续正常的检查次数
#### Scenario: 当前连续异常
- **WHEN** 目标最近的检查记录连续为 matched=0
- **THEN** currentStreak SHALL 返回 `{ up: false, count: N }`N 为连续异常的检查次数
#### Scenario: 连续状态达到取数上限
- **WHEN** 连续状态次数达到后端取数或计算上限
- **THEN** currentStreak SHALL 返回 `{ up: boolean, count: N, capped: true }`,前端据此展示上限标记
#### Scenario: 无检查记录
- **WHEN** 目标没有任何检查记录
- **THEN** currentStreak SHALL 返回 null
### Requirement: 趋势数据应用层分桶
系统 SHALL 在后端应用层按 UTC 小时分桶生成趋势数据。
#### Scenario: 按小时生成趋势
- **WHEN** metrics 请求 bucket=`1h`
- **THEN** 系统 SHALL 按 UTC 小时生成 trend 数组,每个点包含该小时内的 totalChecks、upChecks、downChecks、availability、avgDurationMs、minDurationMs、maxDurationMs
#### Scenario: 小时内无成功检查
- **WHEN** 某小时内存在检查记录但无成功检查记录
- **THEN** avgDurationMs、minDurationMs、maxDurationMs SHALL 返回 nullavailability SHALL 基于 upChecks/totalChecks 返回 0
#### Scenario: 小时内无检查记录
- **WHEN** 某小时内没有任何检查记录
- **THEN** 系统 MAY 不返回该小时对应的 trend 点
### Requirement: 无数据口径
系统 SHALL 对无数据窗口返回稳定的空指标口径。
#### Scenario: 窗口内无检查记录
- **WHEN** 指定时间窗口内没有任何检查记录
- **THEN** stats SHALL 返回 totalChecks=0、upChecks=0、downChecks=0、availability=0、avgDurationMs=null、p95DurationMs=null、p99DurationMs=null、mttr=null、longestOutage=null、incidentCount=0、currentStreak=nulltrend SHALL 返回空数组