---
name: op-clinic-grafana-access
description: |
  Access On-Premises PingCAP Clinic and online Grafana data by reusing a local Chrome login session,
  discovering dashboard and panel PromQL from Grafana dashboard JSON, and preparing normalized query
  inputs and evidence payloads for TiDB diagnosis workflows. Use when a user provides an On-Premises
  Clinic cluster link, an online Grafana dashboard link, or a Clinic bundle workflow rooted at a portal link and needs
  monitoring data access, dashboard discovery, cluster-summary normalization, or bundle/log evidence extraction.
  Local Clinic auth here
  depends on a browser cookie from a real Chrome login session instead of API-key auth.
---

# op-clinic-grafana-access

Use this skill when the task is:
- route one target through a unified dispatcher across Clinic, Grafana, and bundle paths
- parse one Clinic link into org / cluster / time window context
- reuse local Chrome Clinic login state to access Clinic pages and data endpoints
- discover dashboard metadata and `panels[].targets[].expr` from Grafana dashboard JSON
- produce normalized JSON evidence that later steps such as `oncall-toolkit analyze` can consume
- download one Clinic diagnostic bundle to local disk, extract it, and summarize `meta.yaml` plus node log paths
- read cluster configuration from the extracted Clinic bundle, especially `meta.yaml -> topology.server_configs` and per-instance `config`
- analyze one online Grafana dashboard link by reading `/api/dashboards/uid/{uid}` and querying `/api/datasources/proxy/{id}/api/v1/query(_range)`

Do not use this skill for:
- general TiDB diagnosis without a Clinic link, Grafana link, or bundle target
- screenshot / DOM-based scraping
- cookie capture from packet dumps
- headless login automation


## P81 / Perimeter81 Grafana Access

P81-protected Grafana links (`.pzero.perimeter81.com`) use short-lived `p0sid`
session cookies. The script handles P81 hosts automatically.

### Usage

Run the script normally. It reads cookies from Chrome DB and sends browser-grade
headers required by the P81 gateway (`x-grafana-org-id`, `Origin`, `Referer`,
`accept` with wildcards, `sec-ch-ua` with real browser format, `User-Agent` with
`major.0.0.0` version format).

```bash
python3 scripts/dispatch_access.py \
  "https://xxxxx.pzero.perimeter81.com/d/abc/..."

# Or directly
python3 scripts/analyze_online_grafana.py \
  "https://xxxxx.pzero.perimeter81.com/d/abc/..." \
  --datasource-id 77
```

### If the P81 session has expired

If the `p0sid` cookie has expired server-side, the script prints a message to
stderr telling you to re-authenticate P81 in Chrome and re-run.

### How it works

The `grafana_session()` function detects P81 hosts via `is_perimeter81_host()`.
For P81 hosts it automatically adds the headers that the P81 gateway requires:

- `x-grafana-org-id: 1` — Grafana org scope
- `Origin` / `Referer` — same-origin checks
- `sec-ch-ua` with real browser format (`"Not)A;Brand";v="24"`)
- `accept: application/json, text/plain, */*` — P81 requires wildcards
- `user-agent: Chrome/{major}.0.0.0` — version format check

These headers are P81-specific and are **not** set for non-P81 Grafana URLs.

Non-P81 Grafana access remains unchanged (plain `accept: application/json`
and the default User-Agent).

## Evidence Policy

This skill is the preferred access path whenever a Clinic link exists.

Mandatory rules:

- if a Clinic link is available, use Clinic first
- if the target is `https://clinic.pingcap.com/portal/#/orgs/...`, first use
  `clinic-api` exact cluster lookup to fetch metadata
- if that `clinic-api` metadata says `clusterType=cloud`, treat the link as a
  TiDB Cloud Clinic case and hand off to the TiDB Cloud Clinic analysis flow
  outside this skill
- if that `clinic-api` metadata is not `clusterType=cloud`, continue inside
  this skill and stop using `clinic-api` for the rest of the workflow
- after the above preflight, all in-skill Clinic access must use the local
  Google Chrome Clinic login session and Chrome profile reuse path
- if the target is `clinic.pingcap.com.cn/portal`, skip `clinic-api` preflight
  and directly use the local Chrome Clinic login session path
- if Clinic `data_status == -1`, analysis must wait and poll for up to 10 minutes before giving up
- if Clinic `data_status` is still not `100` after the 10-minute wait window, return a blocking error that says critical evidence is missing and analysis cannot continue
- if Clinic `data_status` is not ready yet for other statuses, wait until `status == 100` and rerun
- do not replace pending Clinic data with screenshot-based analysis just because the window is still preparing
- only fall back to screenshots or image attachments when no Clinic link or no raw Clinic evidence is available at all
- comments from tickets or issues are not input evidence for this access layer; comments belong to the caller as optional reference only
- do not interpret empty metric series until the readiness gate has passed
- for metrics that use a `store` label, such as `tidb_tikvclient_rpc_net_latency_seconds_bucket`, do not use the TiKV `instance` address directly; always resolve `instance -> store id` first
- when cluster configuration is needed, do not infer it from screenshots or comments; download and extract the Clinic bundle first, then read `meta.yaml`

## Mandatory Reminder

Before using this skill, always remind the user:

- log in to Local Clinic once in Google Chrome on the same machine account
- for `clinic.pingcap.com/portal`, be ready for a two-step flow:
  `clinic-api` metadata preflight first, then local Chrome Clinic login reuse
  only if the case stays in this skill
- for `clinic.pingcap.com.cn/portal`, assume the default access path is the
  local Chrome Clinic login state plus the matching Chrome profile
- if they use a non-default Chrome profile, tell the skill which profile to read
- rerun the skill after login if the cookie is missing or expired

Local Clinic in this workflow does **not** use API-key auth. The skill must reuse the browser cookie `clinic.session_id`.

## Quick Start

1. If the target is `clinic.pingcap.com/portal`, run the `clinic-api` metadata preflight first
2. If that metadata says `clusterType=cloud`, stop and hand off to the TiDB Cloud Clinic analysis flow outside this skill
3. Otherwise ask the user to log in to Local Clinic once in Google Chrome when the route will access Clinic or a Clinic bundle endpoint
4. Run `scripts/check_login.py` to verify the browser session can be reused for Clinic or bundle access
5. Run `scripts/dispatch_access.py` as the default entrypoint
   - Clinic link -> `scripts/analyze_cluster.py`
   - online Grafana link -> `scripts/analyze_online_grafana.py`
   - Clinic portal link plus `--route bundle` -> `scripts/download_bundle.py`
6. `scripts/analyze_cluster.py` now stops at pure access and evidence normalization
   - for `clinic.pingcap.com/portal`, it performs the `clinic-api` metadata preflight before browser-login access
   - if `clusterType=cloud`, it returns a handoff decision instead of continuing locally
   - if `data_status == -1`, it waits up to 10 minutes before failing with a blocking evidence-missing error
   - when a caller still needs optional follow-up evidence such as `metrics_only_perf_diagnosis.py` or `deep_tikv_write_path_rca.py`, prefer enabling that through dispatcher flags instead of calling those helper scripts directly from another skill
7. Run `scripts/download_bundle.py` when you need the raw diagnostic package, `meta.yaml`, and extracted logs
8. Only when you need lower-level debugging or explicit downstream diagnosis, fall back to the task-specific scripts:
   - `scripts/discover_queries.py`
   - `scripts/fetch_metric_values.py`
   - `scripts/basic_healthy_check.py`
   - `scripts/metrics_only_perf_diagnosis.py`
   - `scripts/deep_tikv_write_path_rca.py`
   - `scripts/resolve_store_id.py`
   - `scripts/discover_online_grafana.py`
   - `scripts/fetch_online_grafana_values.py`

Example:

```bash
python3 scripts/check_login.py --base-url "https://clinic.pingcap.com.cn"
python3 scripts/dispatch_access.py "https://clinic.pingcap.com.cn/portal/#/orgs/1020/clusters/7506061236641706840?from=1775007600&to=1775011200"
```

For `clinic.pingcap.com/portal`, `analyze_cluster.py` first performs a
`clinic-api` metadata preflight. Only cases without `clusterType=cloud` stay in
this skill and continue through the browser-session path.

For `clinic.pingcap.com.cn/portal`, the browser-session path is the default.
Only add `--profile` when the reusable login is in a non-default Chrome
profile.

When you need TiDB-to-TiKV RPC-layer metrics, first resolve the TiKV `instance` to a PD `store id`:

```bash
python3 scripts/resolve_store_id.py \
  "https://clinic.pingcap.com.cn/portal/#/orgs/940/clusters/7485637750735359961?from=1774918800&to=1774920000" \
  --instance "10.70.76.168:20182"
```

Then query metrics such as `tidb_tikvclient_rpc_net_latency_seconds_bucket` with `store="<store-id>"`, not with the TiKV instance address.

When you need the cluster configuration, use the extracted bundle instead of guessing from ticket text:

```bash
python3 scripts/download_bundle.py \
  "https://clinic.pingcap.com.cn/portal/#/orgs/940/clusters/7485637750735359961?from=1772603400&to=1772604600" \
  --output-subdir "TICKET-8412"
```

Then inspect:

- `meta.yaml -> topology.server_configs.tikv` for cluster-wide TiKV defaults
- `meta.yaml -> topology.tikv_servers[].config` for per-instance overrides
- `meta.yaml -> topology.pd_servers[].config` and `topology.tidb_servers[].config` when PD or TiDB configuration is also relevant

Download one Clinic diagnostic bundle from a Clinic portal link to `~/Downloads/YYYY-MM-DD/`, then `zstd` decompress and `tar` extract it:

```bash
python3 scripts/download_bundle.py \
  "https://clinic.pingcap.com.cn/portal/#/orgs/940/clusters/7485637750735359961?from=1772603400&to=1772604600"
```

If another workflow such as `oncall-toolkit analyze` wants a stable case directory, pass `--output-subdir`:

```bash
python3 scripts/download_bundle.py \
  "https://clinic.pingcap.com.cn/portal/#/orgs/940/clusters/7485637750735359961?from=1772603400&to=1772604600" \
  --output-subdir "TICKET-8412"
```

By default the downloader auto-discovers the ready bundle `item_id` from Clinic `data_status` after the portal window reaches `status == 100`. Only pass `--item-id` when you need a manual override.

If the user logged in with a different Chrome profile:

```bash
python3 scripts/check_login.py --base-url "https://clinic.pingcap.com.cn" --profile "Profile 3"
```

If you want deterministic dashboard discovery instead of the default preference order, pass:

```bash
python3 scripts/dispatch_access.py "<clinic-link>" --dashboard-name "Performance-Overview"
```

For an online Grafana dashboard link, use the dispatcher directly:

```bash
python3 scripts/dispatch_access.py \
  "http://10.2.8.4:3100/d/<uid>/<slug>?from=1775266800&to=1775268600"
```

For lower-level Grafana-only discovery, discover dashboard metadata and panel PromQL directly:

```bash
python3 scripts/discover_online_grafana.py \
  "http://10.2.8.4:3100/d/<uid>/<slug>?from=1775266800&to=1775268600"
```

Then fetch one panel expression through Grafana datasource proxy `1`:

```bash
python3 scripts/fetch_online_grafana_values.py \
  "http://10.2.8.4:3100/d/<uid>/<slug>?from=1775266800&to=1775268600" \
  --datasource-id 1 \
  --panel-title "QPS"
```

If the user only gives the Grafana base URL, also pass `--dashboard-uid` because the root URL alone is not enough to identify a dashboard:

```bash
python3 scripts/discover_online_grafana.py \
  "http://10.2.8.4:3100/" \
  --dashboard-uid "<uid>"
```

## Workflow

1. Decide the route:
   - Clinic link
   - online Grafana link or base URL
   - Clinic bundle target
2. For Clinic or bundle routes, remind the user to log in to Local Clinic once in Google Chrome
3. For Clinic or bundle routes, verify login reuse with `scripts/check_login.py`
4. Run `scripts/dispatch_access.py` as the default entrypoint
5. Let the dispatcher select the concrete access script:
   - `scripts/analyze_cluster.py` for Clinic access and evidence normalization
   - `scripts/analyze_online_grafana.py` for Grafana access
   - `scripts/download_bundle.py` for bundle download and extraction
6. When the route is Clinic, `scripts/analyze_cluster.py` parses the Clinic link, waits for `data_status == 100`, discovers dashboard metadata, and returns normalized access evidence only
7. Run `scripts/download_bundle.py` when you need the raw package contents under `~/Downloads/YYYY-MM-DD/`, or under a case-specific directory such as `~/Downloads/TICKET-8412/` when `--output-subdir` is provided
   - if the diagnosis needs cluster configuration, read the extracted `meta.yaml` first
   - prefer `topology.server_configs` for cluster-wide defaults
   - prefer per-instance `config` blocks only as overrides on top of those defaults
8. Save the normalized access/evidence JSON
9. Only after access discovery is stable, extend to diagnosis scripts
10. Use `scripts/basic_healthy_check.py` when you need the fixed first-pass Clinic healthy checklist output
11. Use `scripts/metrics_only_perf_diagnosis.py` when you need the reusable metrics-only diagnosis modules by themselves:
   - `cluster_meta`
   - `basic_healthy_check`
   - `performance_overview_summary`
   - `tikv_risk_summary`
   - `pd_risk_summary`
   - `overall_judgement`
   - `scope_note`
12. Use `scripts/deep_tikv_write_path_rca.py` only when the caller explicitly wants deeper TiKV write-path RCA on top of the Clinic access layer
13. When another skill needs Clinic, Grafana, or bundle access, keep the integration boundary at `scripts/dispatch_access.py` rather than importing multiple internal helper scripts one by one

## Authentication Notes

- Browser auth source: Google Chrome cookie DB
- Required cookie: `clinic.session_id`
- Required derived header: `x-csrf-token` from the JWT payload
- Before querying metrics or downloading a Clinic bundle, first call `data_status` for the same `from/to` window and wait until `status == 100`
- If `data_status == -1`, poll for up to 10 minutes before giving up
- If `data_status` still does not reach `100` after the 10-minute wait window, return a blocking error that says critical evidence is missing and analysis cannot continue
- If `data_status` is pending or not ready with other statuses, return a pending status to the caller and tell it to wait, instead of switching to screenshot analysis
- Before querying TiDB-to-TiKV RPC-layer metrics, resolve the TiKV instance address to the PD `store` label first
- Before reading cluster configuration, make sure the bundle has been fully downloaded and extracted, then read `meta.yaml` from the extracted directory
- Default profile behavior: `--profile auto` scans `Default` and `Profile *`
- The user only needs to log in to Local Clinic once per browser session on the same machine account
- For online Grafana, the scripts first try anonymous access and then reuse Chrome cookies for the target host when available
- If online Grafana returns `401 Unauthorized`, ask the user to log in to that Grafana in Chrome once on the same machine account and rerun with `--profile` if needed
- Script outputs intentionally do not print the raw session cookie, CSRF token, account email, or local cookie DB path
- Override knobs:
  - `CLINIC_BASE_URL`
  - `CLINIC_CHROME_PROFILE`
  - `CLINIC_CHROME_DATA_DIR`

Read `references/local-login.md` when you need the concrete login and troubleshooting flow.

## Discovery Source Priority

- Primary discovery path:
  1. `GET /clinic/api/v1/orgs/{orgId}/clusters/{clusterId}/dashboard`
  2. extract Grafana dashboard `uid` from returned URLs
  3. `GET /grafana/api/dashboards/uid/{uid}`
- Default dashboard selection prefers:
  1. `Performance-Overview-Cluster-Clinic`
  2. `TiDB-Cluster-Clinic`
  3. `Overview-Cluster-Clinic`
  4. `TiKV-Details-Cluster-Clinic`
- For reproducible results, pass `--dashboard-name` or `--dashboard-uid`
- Do not use page DOM or screenshots as discovery input
- Do not interpret empty metric series before confirming the window has passed the `data_status == 100` readiness gate
- Do not treat ticket comments as monitoring evidence in this skill
- Do not assume the `store` label equals the TiKV instance address

## Expected Inputs

- one Clinic link like:
  - `https://clinic.pingcap.com.cn/portal/#/orgs/1020/clusters/7506061236641706840?from=1775007600&to=1775011200`
- optional dashboard selection:
  - dashboard `uid`
  - dashboard name substring
- optional bundle override:
  - Clinic `item_id` if you need to force a specific downloadable item instead of the auto-discovered one

## Expected Output Shape

The dispatcher is the default top-level entrypoint and adds route metadata:

```json
{
  "dispatcher": {
    "route": "clinic",
    "target": "https://clinic.pingcap.com.cn/portal/#/orgs/1020/clusters/7506061236641706840?from=1775007600&to=1775011200"
  }
}
```

The Clinic access route should normalize to:

```json
{
  "source": {
    "kind": "clinic_link",
    "cluster_id": "7506061236641706840",
    "ready": true
  },
  "dashboard_discovery": {
    "dashboard": {
      "uid": "951f996857f117bdclusterv8_5_x",
      "title": "Performance-Overview-Cluster-Clinic"
    }
  },
  "cluster_meta": {
    "id": "7506061236641706840",
    "version": "v8.5.0"
  },
  "cluster_summary": {
    "cluster_name": "cmp-cluster"
  }
}
```

The Grafana access route should normalize to:

```json
{
  "source": {
    "kind": "grafana_link",
    "dashboard_uid": "<uid>",
    "ready": true
  },
  "grafana_context": {
    "base_url": "http://10.2.8.4:3100"
  },
  "cluster_summary": {},
  "dashboard_discovery": {}
}
```

The bundle route should normalize to:

```json
{
  "download": {
    "download_url": "https://clinic.pingcap.com.cn/clinic/api/v1/orgs/940/clusters/7485637750735359961/download/807852937340760c-1a8c5e737cba5540-136da2059dbdae7e",
    "archive_path": "/Users/you/Downloads/2026-04-03/diag-cmp2-prod-gTqh1h7tYxp.diag.tar.zst"
  },
  "extraction": {
    "tar_path": "/Users/you/Downloads/2026-04-03/diag-cmp2-prod-gTqh1h7tYxp.diag.tar",
    "extract_dir": "/Users/you/Downloads/2026-04-03/diag-cmp2-prod-gTqh1h7tYxp.diag"
  },
  "meta": {
    "meta_yaml_path": "/Users/you/Downloads/2026-04-03/diag-cmp2-prod-gTqh1h7tYxp.diag/meta.yaml",
    "tidb_version": "v6.5.6"
  },
  "logs": {
    "components": {
      "tikv": [],
      "tidb": [],
      "pd": [],
      "slow": []
    }
  }
}
```

Downstream diagnosis outputs such as metrics-only evidence or deep TiKV RCA are intentionally not embedded in the dispatcher or `analyze_cluster.py` output. Call those scripts explicitly, or use a higher-level workflow such as `oncall-toolkit`, after access evidence is ready.

## References

- Read `references/page-flow.md` when you need the concrete request chain
- Read `references/internal-schema.md` when extending normalized output fields
- Read `references/local-login.md` when you need the browser-login-only usage flow
