     Metrics 巡检 API 实施方案                                                                                                                                             
   
     Context                                                                                                                                                               
                                                                                                                                                                         
     将 RunMetricsHealthCheck() 中的核心逻辑抽取为 HTTP API，实现单集群 Metrics 巡检。不修改现有 RunMetricsHealthCheck() 函数。

     实施步骤

     Step 1: 写计划文档到 docs/metrics_healthcheck.md

     完整的分析文档和实施方案（包含 RunMetricsHealthCheck 调用链分析、新 API 设计等）。

     Step 2: 新建 pkg/service/metrics_healthcheck.go — Service 层

     定义请求/响应结构体和核心巡检逻辑。

     请求：复用 HealthCheckCommonParameters（pkg/service/slowquery_analyzer.go:38-49）

     响应：
     type MetricsHealthCheckResponse struct {
         Name          string                         `json:"name"`
         Description   string                         `json:"description"`
         CheckType     string                         `json:"checkType"`
         Status        string                         `json:"status"`
         ResultJsonUrl string                         `json:"resultJsonUrl"`
         Summary       MetricsHealthCheckSummary      `json:"summary"`
         Results       []MetricsHealthCheckResultItem `json:"results"`
     }

     核心方法 Analyze()：
     1. 加载 metrics 查询配置（confMetrics.AppConfig.GetCPUResource() 等）
     2. 根据 DeployType 创建 Prometheus client:
       - 如果传了 ClusterUrls.ClusterMetricsURL，用 CreatePrometheusClientWithUrls()
       - 否则用 CreatePrometheusClient() 自动解析
     3. Premium 适配：替换 PromQL 中的 $tidb_cluster:
       - dedicated 集群: $tidb_cluster → req.ClusterId
       - premium 集群: $tidb_cluster → req.PhysicalClusterId（从请求入参获取）
     4. 调用 MetricsQueryBatch 执行查询（$tidb_cluster 替换已在 MetricsQueryBatch 中统一处理）
     5. 复用 getResourceDiffResult()/getPerformanceDiffResult() 计算差异
     6. 复用 getResourceQueryResult() 格式化结果 + 状态判定

     Premium 适配：直接修改 MetricsQueryBatch，在替换 $rate 的同时替换 $tidb_cluster，详见下方"Premium 集群适配详细方案"。

     Step 3: 修改 pkg/handlers/slowquery_handler.go — 新增 Handler

     新增 MetricsHealthCheckHandler struct 和 MetricsHealthCheckAnalyze 方法：

     1. 解析请求、校验必填字段（clusterId, timeRange）
     2. 自动填充逻辑（参考 HealthCheckAnalyze）：当 ClusterUrls 或 Region 为空时，通过 clinicClient.GetClusterDataURLs() 反查填充:
       - ClusterMetricsURL
       - DataPlaneMetricsURL
       - Region
       - Provider
     3. 调用 service.Analyze()
     4. 生成 S3 报告地址，异步上传（复用 saveHealthCheckResultToS3 模式）
     5. 返回 JSON 响应

     Step 4: 修改 pkg/server/router.go — 注册路由

     在 DataProxy routes 块中注册：
     metricsHCSvc := service.NewMetricsHealthCheckService()
     metricsHCHandler := handlers.NewMetricsHealthCheckHandler(metricsHCSvc, dataProxyCfg)
     metricsHCHandler.RegisterRoutes(r)

     路由: POST /clinic-internal/healthcheck/metrics/analyze

     Step 5: 配置加载

     在 Service 初始化时确保 confMetrics.LoadConfigForMetrics() 被调用以加载 config-metrics.yaml。

     需要修改/新建的文件

     ┌──────┬────────────────────────────────────┬─────────────────────────────────────────────────────────┐
     │ 操作 │              文件路径              │                          说明                           │
     ├──────┼────────────────────────────────────┼─────────────────────────────────────────────────────────┤
     │ 新建 │ docs/metrics_healthcheck.md        │ 分析文档 + 实施方案                                     │
     ├──────┼────────────────────────────────────┼─────────────────────────────────────────────────────────┤
     │ 新建 │ pkg/service/metrics_healthcheck.go │ Service 层：请求/响应结构 + 巡检逻辑                    │
     ├──────┼────────────────────────────────────┼─────────────────────────────────────────────────────────┤
     │ 修改 │ pkg/handlers/slowquery_handler.go  │ 新增 MetricsHealthCheckHandler + Handler 方法           │
     ├──────┼────────────────────────────────────┼─────────────────────────────────────────────────────────┤
     │ 修改 │ pkg/server/router.go               │ 注册新路由                                              │
     ├──────┼────────────────────────────────────┼─────────────────────────────────────────────────────────┤
     │ 修改 │ pkg/prometheus/metrics_query.go    │ MetricsQueryBatch 中增加 $tidb_cluster 替换             │
     ├──────┼────────────────────────────────────┼─────────────────────────────────────────────────────────┤
     │ 修改 │ pkg/metrics/metrics_query.go       │ 新增导出函数 GetSingleClusterMetrics() 包装现有私有函数 │
     └──────┴────────────────────────────────────┴─────────────────────────────────────────────────────────┘

     不修改的文件（仅复用）:
     - pkg/metrics/metrics_calculate.go — 直接调用导出函数
     - pkg/metrics/metrics_table.go — 直接调用导出函数
     - metrics_check_test.go — RunMetricsHealthCheck() 不动

     复用的现有函数

     ┌─────────────────────────────────────────────┬──────────────────────────────────────────┬────────────────────────┐
     │                    函数                     │                   文件                   │          用途          │
     ├─────────────────────────────────────────────┼──────────────────────────────────────────┼────────────────────────┤
     │ confMetrics.AppConfig.GetCPUResource() 等   │ pkg/config/metrics/config.go             │ 获取查询配置           │
     ├─────────────────────────────────────────────┼──────────────────────────────────────────┼────────────────────────┤
     │ prometheus.CreatePrometheusClient()         │ pkg/prometheus/prometheus_api_sdk.go:135 │ 创建 Prometheus 客户端 │
     ├─────────────────────────────────────────────┼──────────────────────────────────────────┼────────────────────────┤
     │ prometheus.CreatePrometheusClientWithUrls() │ pkg/prometheus/prometheus_api_sdk.go:265 │ 通过 URL 创建客户端    │
     ├─────────────────────────────────────────────┼──────────────────────────────────────────┼────────────────────────┤
     │ metrics.getResourceDiffResult()             │ pkg/metrics/metrics_calculate.go:16      │ 资源指标差异计算       │
     ├─────────────────────────────────────────────┼──────────────────────────────────────────┼────────────────────────┤
     │ metrics.getPerformanceDiffResult()          │ pkg/metrics/metrics_calculate.go:158     │ 性能指标差异计算       │
     ├─────────────────────────────────────────────┼──────────────────────────────────────────┼────────────────────────┤
     │ metrics.getResourceQueryResult()            │ pkg/metrics/metrics_table.go:11          │ 格式化 + 状态判定      │
     ├─────────────────────────────────────────────┼──────────────────────────────────────────┼────────────────────────┤
     │ clinicClient.GetClusterDataURLs()           │ pkg/clinic-internal-api/client.go:71     │ 反查集群 URLs          │
     ├─────────────────────────────────────────────┼──────────────────────────────────────────┼────────────────────────┤
     │ common.GetCluster()                         │ pkg/common/common_query.go:15            │ 获取集群信息（备用）   │
     ├─────────────────────────────────────────────┼──────────────────────────────────────────┼────────────────────────┤
     │ aws.UploadFileToS3()                        │ pkg/thirdapi/aws/s3.go                   │ S3 上传                │
     ├─────────────────────────────────────────────┼──────────────────────────────────────────┼────────────────────────┤
     │ HealthCheckCommonParameters                 │ pkg/service/slowquery_analyzer.go:38     │ 请求基类               │
     └─────────────────────────────────────────────┴──────────────────────────────────────────┴────────────────────────┘

     注意：metrics_calculate.go 中的 getResourceDiffResult/getPerformanceDiffResult 和 metrics_table.go 中的 getResourceQueryResult 是小写未导出函数。新 Service 需要：
     - 方案A: 在 pkg/metrics/ 下新增一个导出函数包装它们（如 GetSingleClusterMetrics()），不修改原有函数
     - 方案B: 把这些函数改为大写导出（影响最小，只改首字母）

     推荐方案A：新增导出函数，完全不影响现有代码。

     Premium 集群适配详细方案

     当前 PromQL 查询中的变量

     所有巡检查询（cpuResource、memResource、latencyQueries、diskQueries）都包含：
     - $rate — 当前由 MetricsQueryBatch 替换为 4m（primary）或 30m（baseline）
     - $tidb_cluster — 当前 没有被替换（是用户后加的变量）

     适配方案：修改 MetricsQueryBatch

     直接在 pkg/prometheus/metrics_query.go 的 MetricsQueryBatch 中增加 $tidb_cluster 替换逻辑。

     由于 MetricsQueryBatch 已有 cluster myModel.Cluster 参数，可以从中获取信息：
     - dedicated 集群: $tidb_cluster → cluster.ClusterID
     - premium 集群: $tidb_cluster → cluster.PhysicalClusterID（如果非空）

     修改位置：pkg/prometheus/metrics_query.go:72-73

     // 现有代码
     primaryQuery := strings.ReplaceAll(q.PrimaryQuery, "$rate", "4m")
     baseQuery := strings.ReplaceAll(q.PrimaryQuery, "$rate", "30m")

     // 新增：替换 $tidb_cluster（适配 premium 集群）
     tidbClusterID := cluster.ClusterID
     if cluster.DeployType == "premium" && cluster.PhysicalClusterID != "" {
         tidbClusterID = cluster.PhysicalClusterID
     }
     primaryQuery = strings.ReplaceAll(primaryQuery, "$tidb_cluster", tidbClusterID)
     baseQuery = strings.ReplaceAll(baseQuery, "$tidb_cluster", tidbClusterID)

     对现有 RunMetricsHealthCheck() 的影响：
     - 现有流程通过 common.GetCluster() 获取 model.Cluster，其中已有 ClusterID、DeployType、PhysicalClusterID 字段
     - dedicated 集群: PhysicalClusterID 为空，使用 ClusterID 替换 → 行为与之前一致（之前未替换，现在替换为实际值，Prometheus 端仍然是集群级别 endpoint，不影响结果）
     - premium 集群: 使用 PhysicalClusterID 替换 → 正确过滤到目标集群
     - 不会破坏现有逻辑

     新 API 中的适配

     新 Service 层构建 model.Cluster 时，从请求入参填充：
     cluster := myModel.Cluster{
         ClusterID:         req.ClusterId,
         PhysicalClusterID: req.PhysicalClusterId,  // 从 HealthCheckCommonParameters 获取
         DeployType:        req.DeployType,
         Region:            req.Region,
         Provider:          req.Provider,
         OrgID:             req.TenantId,
         ProjectID:         req.ProjectId,
     }

     这样 MetricsQueryBatch 会自动完成 $tidb_cluster 的正确替换。

     API 接口定义

     请求

     POST /clinic-internal/healthcheck/metrics/analyze
     Content-Type: application/json

     {
         "name": "metrics_healthcheck",
         "clusterId": "1379661944646416054",       // 必填
         "physicalClusterId": "",                   // premium 集群必填
         "reportId": "report-xxx",
         "provider": "aws",
         "region": "us-west-2",
         "deployType": "dedicated",
         "projectId": "xxx",
         "tenantId": "xxx",
         "timeRange": {                             // 必填
             "start": "2026-03-14T00:00:00Z",
             "end": "2026-03-14T23:59:59Z"
         },
         "clusterUrls": {                           // 可选，为空时自动获取
             "clusterMetricsUrl": "",
             "dataPlaneMetricsUrl": ""
         }
     }

     响应

     {
         "name": "metrics_healthcheck",
         "description": "Cluster metrics health check: compares current metrics against 14-day baseline",
         "checkType": "metrics",
         "status": "success",
         "resultJsonUrl": "s3://delicated-healthcheck/healthcheck-report/xxx.json",
         "summary": {
             "totalMetrics": 20,
             "needAttentionCount": 3
         },
         "results": [
             {
                 "queryName": "TiDB_CPU",
                 "timeAggregation": "P90",
                 "threshold": "25.3%",
                 "status": "Need Attention",
                 "max": "0.85",
                 "avg": "0.72",
                 "link": "https://clinic.pingcap.com/grafana/d/..."
             }
         ]
     }

     验证方式

     1. go build ./... — 编译通过
     2. 启动 API server: go run cmd/api/main.go
     3. 发送 POST 请求到 /clinic-internal/healthcheck/metrics/analyze
     4. 验证返回 JSON 结构正确，指标结果包含资源类和性能类
     5. 验证本地 ./output/healthcheck-report/ 下生成了结果文件
     6. 验证现有 RunMetricsHealthCheck() 测试不受影响: go test -v -run TestMetricsHealthCheck