---
name: slopus/metrics-graphana
source: https://app.decimal.ai/s/slopus-metrics-graphana@1/SKILL.md
source_sha256: b5eb6475d75b
---

# Metrics & Grafana

You are the observability operator for the Happy infrastructure. You can query live Prometheus metrics, manage Grafana dashboards as code, and investigate production behavior.

## Environment Variables

Credentials are stored in the repo root `.env` file (gitignored). Load them before running commands:

```
GRAFANA_URL=...
GRAFANA_USER=...
GRAFANA_PASSWORD=...
GRAFANA_PROMETHEUS_UID=...
```

To load in shell:
```bash
set -a; source .env; set +a
```

All commands below use `$GRAFANA_URL`, `$GRAFANA_USER`, `$GRAFANA_PASSWORD`, and `$GRAFANA_PROMETHEUS_UID` from the environment.

---

## Prerequisites

### Install grafanactl

```bash
go install github.com/grafana/grafanactl/cmd/grafanactl@latest
```

Ensure `$HOME/go/bin` is on your PATH.

### Configure grafanactl

```bash
# Load env vars first
set -a; source .env; set +a

# Create a context for the Happy Grafana instance
grafanactl config set contexts.happy.grafana.server "$GRAFANA_URL"
grafanactl config set contexts.happy.grafana.user "$GRAFANA_USER"
grafanactl config set contexts.happy.grafana.password "$GRAFANA_PASSWORD"
grafanactl config set contexts.happy.grafana.org-id 1

# Switch to the context
grafanactl config use-context happy

# Verify
grafanactl config check
```

Config file lives at `~/Library/Application Support/grafanactl/config.yaml` (macOS) or `~/.config/grafanactl/config.yaml` (Linux).

---

## grafanactl CLI Reference

### List resources

```bash
grafanactl resources list                    # List all resource types
grafanactl resources get dashboards          # List all dashboards
grafanactl resources get folders             # List all folders
```

### Pull dashboards (export to disk)

```bash
grafanactl resources pull dashboards -p ./resources -o json
grafanactl resources pull dashboards/DASHBOARD_ID -p ./resources -o json
```

### Push dashboards (deploy from disk)

```bash
# Push all dashboards from ./resources
grafanactl resources push dashboards -p ./resources

# Push a specific dashboard
grafanactl resources push dashboards/DASHBOARD_ID -p ./resources

# IMPORTANT: Use --omit-manager-fields to keep dashboards editable from the Grafana UI
grafanactl resources push dashboards -p ./resources --omit-manager-fields

# Dry run (no changes)
grafanactl resources push dashboards -p ./resources --dry-run
```

### Workflow: Edit a dashboard

```bash
# 1. Pull current state
mkdir -p /tmp/grafana-work
grafanactl resources pull dashboards -p /tmp/grafana-work -o json

# 2. Edit the JSON files (add panels, modify queries, etc.)

# 3. Push back — always use --omit-manager-fields to avoid locking the UI
grafanactl resources push dashboards -p /tmp/grafana-work --omit-manager-fields
```

> **Warning:** Pushing without `--omit-manager-fields` marks the dashboard as "provisioned"
> and locks it from UI edits. Always include this flag unless you explicitly want CLI-only management.

---

## Querying Prometheus Directly

You can query Prometheus through Grafana's datasource proxy API. This is useful for live investigation without touching the Grafana UI.

### Instant query (current value)

```bash
curl -s -u "$GRAFANA_USER:$GRAFANA_PASSWORD" \
  --data-urlencode 'query=YOUR_PROMQL_HERE' \
  "$GRAFANA_URL/api/datasources/proxy/uid/$GRAFANA_PROMETHEUS_UID/api/v1/query" \
  | python3 -m json.tool
```

### Range query (time series)

```bash
curl -s -u "$GRAFANA_USER:$GRAFANA_PASSWORD" \
  --data-urlencode 'query=YOUR_PROMQL_HERE' \
  --data-urlencode 'start=UNIX_TIMESTAMP' \
  --data-urlencode 'end=UNIX_TIMESTAMP' \
  --data-urlencode 'step=60' \
  "$GRAFANA_URL/api/datasources/proxy/uid/$GRAFANA_PROMETHEUS_UID/api/v1/query_range" \
  | python3 -m json.tool
```

### List all metric names

```bash
curl -s -u "$GRAFANA_USER:$GRAFANA_PASSWORD" \
  "$GRAFANA_URL/api/datasources/proxy/uid/$GRAFANA_PROMETHEUS_UID/api/v1/label/__name__/values" \
  | python3 -c "import json,sys; [print(n) for n in json.load(sys.stdin)['data']]"
```

### Filter metric names

```bash
# Find all RPC-related metrics
curl -s -u "$GRAFANA_USER:$GRAFANA_PASSWORD" \
  "$GRAFANA_URL/api/datasources/proxy/uid/$GRAFANA_PROMETHEUS_UID/api/v1/label/__name__/values" \
  | python3 -c "import json,sys; [print(n) for n in json.load(sys.stdin)['data'] if 'rpc' in n.lower()]"
```

---

## Key Metrics

### Application metrics (handy-server)

| Metric | Type | Description |
|--------|------|-------------|
| `rpc_calls_total` | counter | RPC calls by method and result (success, not_available, target_disconnected, timeout) |
| `rpc_call_duration_seconds_bucket` | histogram | RPC call duration by method |
| `rpc_lookup_retries_bucket` | histogram | Number of retries per socket lookup by method |
| `rpc_fetchsockets_timeouts_total` | counter | fetchSockets timeout count by context (lookup, presence) |
| `websocket_connections_total` | gauge | Active WebSocket connections by type |
| `websocket_events_total` | counter | WebSocket events by type |
| `http_requests_total` | counter | HTTP requests by method, route, status |
| `http_request_duration_seconds_bucket` | histogram | HTTP request duration by route |
| `session_cache_operations_total` | counter | Session cache hits/misses by operation |
| `session_alive_events_total` | counter | Session keepalive events |
| `machine_alive_events_total` | counter | Machine keepalive events |
| `database_records_total` | gauge | Record counts by table |
| `database_updates_skipped_total` | counter | Skipped DB updates by type |

### Useful PromQL queries

```promql
# RPC success rate by method
sum by(method) (rate(rpc_calls_total{result="success"}[5m]))
/ (sum by(method) (rate(rpc_calls_total[5m])))

# RPC failures by method and reason
sum by (method, result) (rate(rpc_calls_total{result!="success"}[5m]))

# RPC failures by type only
sum by (result) (rate(rpc_calls_total{result!="success"}[5m]))

# RPC P95 latency by method
histogram_quantile(0.95, sum by (method, le) (rate(rpc_call_duration_seconds_bucket[5m])))

# Socket lookup retry distribution (P95)
histogram_quantile(0.95, sum by (method, le) (rate(rpc_lookup_retries_bucket[5m])))

# fetchSockets timeout rate by context
sum by (context) (rate(rpc_fetchsockets_timeouts_total[5m]))

# HTTP error rate
sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))

# Top routes by request rate
topk(10, sum by(method, route) (rate(http_requests_total[5m])))
```

---

## Dashboards

### Happy Server Application Metrics
- **ID:** `470da978-91f7-4721-be2c-cc451bf074a2`
- **Tags:** happy-server, application, websocket, http, database
- **Panels:** WebSocket connections, session cache, alive events, HTTP metrics, database stats, RPC metrics

### Adding a panel

When adding panels to a dashboard JSON, follow this pattern:
1. Use the Prometheus datasource: `{"type": "prometheus", "uid": "$GRAFANA_PROMETHEUS_UID"}`
2. Pick the next available `id` (check existing panels for max id)
3. Position with `gridPos`: `h` = height (8 standard), `w` = width (12 half, 24 full), `x` = column (0 or 12), `y` = row
4. Common panel types: `stat`, `timeseries`, `piechart`, `bargauge`, `table`
5. Set appropriate `unit`: `percentunit`, `ops`, `s`, `short`, `reqps`

---

## Tips

- Always pull before editing to get the latest state
- Use `--dry-run` on push to preview changes
- The `--omit-manager-fields` flag is essential for hybrid CLI+UI workflows
- Range queries need Unix timestamps — use `date +%s` to get current time
- When investigating metrics, start with instant queries for current state, then use range queries for trends