# Dashboard And Label Mapping

## Runtime Architecture Selection

Before selecting a dashboard profile, check whether TiCDC is deployed:

1. Inspect Clinic metadata and node inventory for any TiCDC component, pod, node, service, or job.
   - If metadata conclusively shows that TiCDC is not deployed, do not select a dashboard profile.
   - Mark `TiCDC deployment=not deployed`, `TiCDC runtime architecture=N/A`, `cluster status=normal`, and the inspection result `Healthy`.
   - Do not treat absent TiCDC metric families as missing data in this case.
   - If metadata is unavailable or inconclusive, continue with metric probes.

Then distinguish TiCDC runtime architecture:

1. Probe the new-arch-only maintainer event handling metric family:

```promql
count({__name__=~"ticdc_maintainer_handle_event_duration.*", <cluster_selector>})
```

If this metric family has series in the inspected window, the cluster is `new arch`.
For histogram exports, Clinic Data Proxy may expose concrete series such as `ticdc_maintainer_handle_event_duration_bucket`, `_sum`, or `_count`.

2. If the maintainer event handling metric family is absent, probe old-arch-compatible owner lag metrics:

```promql
max(ticdc_owner_resolved_ts_lag{<cf_selector>}) by (<keyspace_label>, changefeed)
```

```promql
max(ticdc_owner_checkpoint_ts_lag{<cf_selector>}) by (<keyspace_label>, changefeed)
```

If both owner lag metrics exist and `ticdc_maintainer_handle_event_duration.*` is absent, the cluster is `old arch`.

3. If neither probe is conclusive, architecture is unresolved and the health result is `Unknown`.

## New-Arch Dashboard Selection

Use this section only for `new arch`.

Use exactly one dashboard profile per inspection:

- Premium:
  - `metrics/nextgengrafana/ticdc_new_arch_next_gen.json`
  - Labels in queries:
    - cluster label: `sharedpool_id`
    - keyspace label: `keyspace_name`
- Dedicated:
  - `metrics/grafana/ticdc_new_arch.json`
  - Labels in queries:
    - cluster label: `tidb_cluster`
    - keyspace label: `namespace`

Do not mix labels across profiles.

## Code-Level Source Of Truth

From `pkg/metrics/init.go`:
- `getKeyspaceLabel()` returns:
  - `"keyspace_name"` when `kerneltype.IsNextGen() == true`
  - `"namespace"` otherwise

This is why premium and dedicated panels use different keyspace labels.

## Variable Mapping

When converting dashboard queries to API queries:

- Keep:
  - `k8s_cluster`
  - `instance`
  - `changefeed`
- Replace:
  - `<cluster_label>` with one of `sharedpool_id` or `tidb_cluster`
  - `<keyspace_label>` with one of `keyspace_name` or `namespace`

For Clinic Data Proxy, prefer the concrete label names discovered from `/api/v1/series`.
Some TiCDC metrics expose `tidb_cluster_id="<cluster_id>"` as the stable cluster selector even when dashboard variables use `tidb_cluster`.
If `tidb_cluster_id` exists in discovered series, use it for `<cluster_selector>` and `<cf_selector>` instead of guessing the expanded `tidb_cluster` label value.

## New-Arch Profile Probe (Fallback)

If architecture metadata is unavailable, probe metrics:

1. Try premium label:

```promql
count(ticdc_owner_checkpoint_ts_lag{sharedpool_id=~".+"})
```

2. Try dedicated label:

```promql
count(ticdc_owner_checkpoint_ts_lag{tidb_cluster=~".+"})
```

Interpretation:
- Premium probe has data, dedicated has none: choose premium.
- Dedicated probe has data, premium has none: choose dedicated.
- Both empty or both non-empty: mark architecture unresolved.

## Base Selector Templates

Use these placeholders in all rules:

- Cluster scope:

```text
<cluster_selector> = k8s_cluster="<k8s_cluster>", <cluster_label>="<cluster_id>"
```

- Changefeed scope:

```text
<cf_selector> = k8s_cluster="<k8s_cluster>", <cluster_label>="<cluster_id>", <keyspace_label>=~"<keyspace_regex>", changefeed=~"<changefeed_regex>"
```

When Clinic Data Proxy series expose `tidb_cluster_id`, the preferred selector templates become:

```text
<cluster_selector> = tidb_cluster_id="<cluster_id>"
```

```text
<cf_selector> = tidb_cluster_id="<cluster_id>", <keyspace_label>=~"<keyspace_regex>", changefeed=~"<changefeed_regex>"
```

For old-arch sink checks, additionally keep the TiCDC instance filter:

```text
instance=~"<ticdc_instance_regex>"
```

Default `<ticdc_instance_regex>` to `.*` unless the user asks for one TiCDC instance or the evidence must be scoped to one instance.

## URL Construction For Findings

### Cluster Clinic Portal URL

Build one report-level Clinic portal URL:

- If a user-provided Clinic portal link already exists, reuse its host and `/portal/#/orgs/<...>/clusters/<...>` path directly.
- Otherwise build:

```text
<clinic_base_url>/portal/#/orgs/<org_id>/clusters/<cluster_id>?from=<from_sec>&to=<to_sec>
```

Fallback when the inspected window is unavailable:

```text
<clinic_base_url>/portal/#/orgs/<org_id>/clusters/<cluster_id>
```

Inputs needed:

- `clinic_base_url` from the current Clinic environment or the user-provided Clinic link host.
- `org_id` from Clinic metadata. This is the value used in the Clinic portal `orgs/<...>` segment.
- `cluster_id`.
- `from_sec`, `to_sec` for the inspected time window in epoch seconds.

Rules:
- Use the same `org_id` value for both the Clinic portal path and Clinic REST path parameters.
- Do not use `tenant_id` in the portal URL. `tenant_id` may still be required by O11Y / NGM request headers, but it is not the Clinic portal org path value.
- Reuse the same Clinic host as the queried environment.
- Keep the exact inspected window when it is known so the link opens the same time range.
- Place this link in the report summary and in the top-level `Evidence links` section.

For every abnormal finding, provide a URL that opens the exact same query and time range.

Priority:
1. Use `panel_url` from source API when available.
2. Use `dashboard_url` from source API when available.
3. Always build `explore_url` as fallback (required).

### Inputs Needed

- `grafana_base_url` from clinic metadata or data source response.
- `datasource_uid` for Prometheus in that Grafana.
- `from_ms`, `to_ms` (same range used in the query evaluation).
- `expr` (the exact PromQL used for the finding).

### Explore URL Template

Build URL:

```text
<grafana_base_url>/explore?left=<url_encoded_left_json>
```

`left` JSON payload template:

```json
{
  "datasource": "<datasource_uid>",
  "queries": [
    {
      "refId": "A",
      "expr": "<promql_expr>",
      "instant": false,
      "range": true
    }
  ],
  "range": {
    "from": "<from_ms>",
    "to": "<to_ms>"
  }
}
```

Notes:
- Keep the exact `expr` string used in the verdict to avoid mismatch.
- Use the same `from_ms/to_ms` as the inspected window.
- URL-encode the whole `left` JSON.

### Output Placement

For each finding, place links directly under the finding block:

```text
Links:
  - Explore: <explore_url>
  - Panel: <panel_url or N/A>
  - Dashboard: <dashboard_url or N/A>
```
