diff --git a/PR_DESCRIPTION_SOROBAN_BILLING_DASHBOARD.md b/PR_DESCRIPTION_SOROBAN_BILLING_DASHBOARD.md new file mode 100644 index 0000000..0fcee6f --- /dev/null +++ b/PR_DESCRIPTION_SOROBAN_BILLING_DASHBOARD.md @@ -0,0 +1,47 @@ +# PR: Grafana Dashboard for Soroban Billing Observability + +## Summary + +Adds a committed Grafana dashboard JSON (`docs/dashboards/soroban-billing.json`) so on-call engineers can see Soroban billing deduction latency (P50/P95), error category breakdown, and call rate out of the box — no manual panel creation required. + +## Changes + +### New files +- `docs/dashboards/soroban-billing.json` — Grafana 11.5.2 dashboard with three rows: Deduction Latency, Error Category Breakdown, Call Rate & Throughput +- `docs/dashboards/README.md` — Documents metric names, provenance, error category → HTTP status mapping, bucket boundaries, SLO thresholds, and import instructions + +### Modified files +- `README.md` — Observability section now links to both dashboards under `docs/dashboards/` + +## Dashboard panels + +| Row | Panels | +|-----|--------| +| Deduction Latency | P50/P95 line chart, P50 stat, P95 stat, bucket distribution bars | +| Error Category Breakdown | Rate by HTTP status (proxy for `SorobanRpcErrorCategory`), total error bar chart | +| Call Rate & Throughput | Total call rate, success rate gauge | + +## Metric provenance + +| Metric | Source file | +|--------|-------------| +| `billing_deduct_duration_seconds` | `src/metrics/registry.ts` — recorded by `billingDeductHistogramMiddleware` | +| `http_requests_total` | `src/metrics.ts` — recorded by `metricsMiddleware` | + +Labels used: `route="/api/billing/deduct"`, `status_code` (maps to `SorobanRpcErrorCategory`). + +## Validation + +The JSON can be validated with: +```bash +# Parse check +node -e "JSON.parse(require('fs').readFileSync('docs/dashboards/soroban-billing.json','utf8')); console.log('valid')" +``` + +## Security + +- No private data baked in (no hardcoded IPs, tokens, or secrets) +- Datasource UID is a `$datasource` template variable — resolves at import time +- Grafana version pinned to `11.5.2` in `__requires` + +closes #415 diff --git a/README.md b/README.md index cb4de9a..3e34b51 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,12 @@ When refreshing it: Run `npm run lint`, `npm run typecheck`, and `npm test` after editing the fixture. -### Observability (Prometheus Metrics) +### Observability (Prometheus Metrics & Dashboards) + +Grafana dashboards are committed under [`docs/dashboards/`](./docs/dashboards/README.md): + +- **[Soroban Billing](./docs/dashboards/soroban-billing.json)** — P50/P95 deduction latency, error category breakdown by `SorobanRpcErrorCategory`, and call rate panels. Import via Grafana → Dashboards → Import. +- **[Billing Deduct HTTP Latency](./docs/grafana-dashboard-billing-deduct.json)** — HTTP-level latency percentiles for `POST /api/billing/deduct`. The application exposes a standard Prometheus text-format metrics endpoint at `GET /api/metrics`. It automatically tracks: diff --git a/docs/dashboards/README.md b/docs/dashboards/README.md new file mode 100644 index 0000000..df65f44 --- /dev/null +++ b/docs/dashboards/README.md @@ -0,0 +1,90 @@ +# Grafana Dashboards + +This directory contains committed Grafana dashboard JSON files for the Callora backend. +Import them via **Dashboards → Import → Upload JSON file** in your Grafana instance. + +--- + +## `soroban-billing.json` — Soroban Billing Observability + +**UID:** `callora-soroban-billing` +**Grafana version:** 11.5.2 +**Datasource:** Prometheus (variable `$datasource`, type `prometheus`) + +### Rows and panels + +| Row | Panel | Description | +|-----|-------|-------------| +| Deduction Latency | P50 / P95 line chart | `billing_deduct_duration_seconds` histogram quantiles over time | +| Deduction Latency | P50 stat (current) | Instant P50 deduct latency | +| Deduction Latency | P95 stat (current) | Instant P95 deduct latency | +| Deduction Latency | Bucket distribution | Per-bucket rate bars for full latency shape | +| Error Category Breakdown | Rate by status code | Maps HTTP status → `SorobanRpcErrorCategory` | +| Error Category Breakdown | Total errors bar chart | Aggregate error count by category over selected range | +| Call Rate & Throughput | Deduct call rate | Total `POST /api/billing/deduct` requests/s | +| Call Rate & Throughput | Success rate | 200 / total; drops signal billing failures | + +### Metric names and provenance + +| Metric | Type | Registered in | Labels | +|--------|------|---------------|--------| +| `billing_deduct_duration_seconds` | Histogram | `src/metrics/registry.ts` | `route`, `status_code` | +| `billing_deduct_duration_seconds_bucket` | (auto) | `src/metrics/registry.ts` | `route`, `status_code`, `le` | +| `http_requests_total` | Counter | `src/metrics.ts` | `method`, `route`, `status_code`, `route_group` | +| `http_request_duration_seconds` | Histogram | `src/metrics.ts` | `method`, `route`, `status_code`, `route_group` | + +All metrics are exposed at `GET /api/metrics` (Prometheus text format). +In production the endpoint requires `Authorization: Bearer $METRICS_API_KEY`. + +### Error category → HTTP status mapping + +The `SorobanRpcErrorCategory` enum (defined in `src/services/sorobanBilling.ts`) maps to +HTTP status codes in `src/routes/billing.ts`: + +| `SorobanRpcErrorCategory` | HTTP status | Panel colour | +|---------------------------|-------------|--------------| +| *(success)* | 200 | green | +| `INSUFFICIENT_BALANCE` | 402 | yellow | +| `CONTRACT_ERROR` | 502 | red | +| `NETWORK_ERROR` | 502 | red | +| `TIMEOUT` | 504 | orange | +| `SIMULATION_FAILED` (diagnostics) | 502 | red | + +Because the histogram middleware and counter both record `status_code` as a label, +the dashboard slices errors by category without requiring a dedicated per-category counter. + +### Bucket boundaries + +`billing_deduct_duration_seconds` uses these buckets (seconds): + +``` +0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10 +``` + +The SLO thresholds on the latency panels are: +- **green** → < 500 ms +- **yellow** → 500 ms – 2 s +- **red** → > 2 s + +### Datasource variable + +The dashboard uses a `$datasource` template variable of type `datasource` (Prometheus). +On import, Grafana will prompt you to select your Prometheus datasource. No UID is hardcoded — +the variable resolves at runtime so the dashboard works across environments. + +### Import instructions + +1. Open Grafana → **Dashboards → Import** +2. Click **Upload JSON file** and select `docs/dashboards/soroban-billing.json` +3. Select your Prometheus datasource when prompted +4. Click **Import** + +To provision automatically, copy the JSON to your Grafana provisioning +`dashboards/` directory and add a provider config pointing at that folder. + +--- + +## `../grafana-dashboard-billing-deduct.json` — Billing Deduct HTTP Latency + +Legacy dashboard focused on HTTP-level deduct latency percentiles. +See `docs/grafana-dashboard-billing-deduct.json` for details. diff --git a/docs/dashboards/soroban-billing.json b/docs/dashboards/soroban-billing.json new file mode 100644 index 0000000..ae8df7c --- /dev/null +++ b/docs/dashboards/soroban-billing.json @@ -0,0 +1,573 @@ +{ + "__inputs": [], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "11.5.2" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "2.x" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "barchart", + "name": "Bar chart", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { "type": "grafana", "uid": "-- Grafana --" }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Soroban billing deduction latency, error category breakdown, and call rate. Metric source: billing_deduct_duration_seconds (histogram) from src/metrics/registry.ts and http_request_duration_seconds / http_requests_total from src/metrics.ts.", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, + "id": 10, + "title": "Deduction Latency", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "$datasource" }, + "description": "P50 and P95 latency of POST /api/billing/deduct calls, measured by the billingDeductHistogramMiddleware. Metric: billing_deduct_duration_seconds.", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisLabel": "Latency (s)", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { "graph": false, "legend": false, "tooltip": false }, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 3, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": true, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "line+area" } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "yellow", "value": 0.5 }, + { "color": "red", "value": 2 } + ] + }, + "unit": "s" + }, + "overrides": [ + { + "matcher": { "id": "byName", "options": "P95" }, + "properties": [ + { "id": "color", "value": { "fixedColor": "orange", "mode": "fixed" } } + ] + }, + { + "matcher": { "id": "byName", "options": "P50" }, + "properties": [ + { "id": "color", "value": { "fixedColor": "green", "mode": "fixed" } } + ] + } + ] + }, + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 1 }, + "id": 1, + "options": { + "legend": { + "calcs": ["mean", "lastNotNull", "max"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } + }, + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "$datasource" }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum by (le) (rate(billing_deduct_duration_seconds_bucket{route=\"/api/billing/deduct\"}[$__rate_interval])))", + "legendFormat": "P50", + "range": true, + "refId": "A" + }, + { + "datasource": { "type": "prometheus", "uid": "$datasource" }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum by (le) (rate(billing_deduct_duration_seconds_bucket{route=\"/api/billing/deduct\"}[$__rate_interval])))", + "legendFormat": "P95", + "range": true, + "refId": "B" + } + ], + "title": "Deduct Latency — P50 / P95", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "$datasource" }, + "description": "Current P50 and P95 deduction latency as instant stat values. Source: billing_deduct_duration_seconds.", + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "yellow", "value": 0.5 }, + { "color": "red", "value": 2 } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { "h": 4, "w": 6, "x": 12, "y": 1 }, + "id": 2, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "horizontal", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "$datasource" }, + "editorMode": "code", + "expr": "histogram_quantile(0.50, sum by (le) (rate(billing_deduct_duration_seconds_bucket{route=\"/api/billing/deduct\"}[$__rate_interval])))", + "instant": true, + "legendFormat": "P50", + "refId": "A" + } + ], + "title": "P50 Deduct Latency (current)", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "$datasource" }, + "description": "Current P95 deduction latency. Source: billing_deduct_duration_seconds.", + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "green", "value": null }, + { "color": "yellow", "value": 0.5 }, + { "color": "red", "value": 2 } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { "h": 4, "w": 6, "x": 18, "y": 1 }, + "id": 3, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "horizontal", + "reduceOptions": { "calcs": ["lastNotNull"], "fields": "", "values": false }, + "textMode": "auto" + }, + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "$datasource" }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum by (le) (rate(billing_deduct_duration_seconds_bucket{route=\"/api/billing/deduct\"}[$__rate_interval])))", + "instant": true, + "legendFormat": "P95", + "refId": "A" + } + ], + "title": "P95 Deduct Latency (current)", + "type": "stat" + }, + { + "datasource": { "type": "prometheus", "uid": "$datasource" }, + "description": "Full latency distribution heatmap across all buckets. Source: billing_deduct_duration_seconds_bucket.", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "bars", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { "graph": false, "legend": false, "tooltip": false }, + "lineWidth": 1, + "scaleDistribution": { "type": "linear" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [{ "color": "green", "value": null }] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { "h": 4, "w": 12, "x": 12, "y": 5 }, + "id": 4, + "options": { + "legend": { "calcs": [], "displayMode": "list", "placement": "bottom", "showLegend": true }, + "tooltip": { "hideZeros": false, "mode": "multi", "sort": "none" } + }, + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "$datasource" }, + "editorMode": "code", + "expr": "rate(billing_deduct_duration_seconds_bucket{route=\"/api/billing/deduct\"}[$__rate_interval])", + "legendFormat": "le={{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Deduct Duration — Bucket Distribution", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 9 }, + "id": 11, + "title": "Error Category Breakdown", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "$datasource" }, + "description": "Rate of deduct calls by HTTP status code, which maps directly to SorobanRpcErrorCategory: 200=success, 402=INSUFFICIENT_BALANCE, 502=CONTRACT_ERROR/NETWORK_ERROR/SIMULATION_FAILED, 504=TIMEOUT. Source: http_requests_total{route=\"/api/billing/deduct\"}.", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisBorderShow": false, + "axisLabel": "req/s", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "none", + "hideFrom": { "graph": false, "legend": false, "tooltip": false }, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 3, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": true, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [{ "color": "green", "value": null }] + }, + "unit": "reqps" + }, + "overrides": [ + { + "matcher": { "id": "byRegexp", "options": ".*2[0-9]{2}.*" }, + "properties": [{ "id": "color", "value": { "fixedColor": "green", "mode": "fixed" } }] + }, + { + "matcher": { "id": "byRegexp", "options": ".*402.*" }, + "properties": [{ "id": "color", "value": { "fixedColor": "yellow", "mode": "fixed" } }] + }, + { + "matcher": { "id": "byRegexp", "options": ".*50[2-9].*" }, + "properties": [{ "id": "color", "value": { "fixedColor": "red", "mode": "fixed" } }] + }, + { + "matcher": { "id": "byRegexp", "options": ".*504.*" }, + "properties": [{ "id": "color", "value": { "fixedColor": "orange", "mode": "fixed" } }] + } + ] + }, + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 10 }, + "id": 5, + "options": { + "legend": { + "calcs": ["sum", "lastNotNull"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } + }, + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "$datasource" }, + "editorMode": "code", + "expr": "sum by (status_code) (rate(http_requests_total{route=\"/api/billing/deduct\"}[$__rate_interval]))", + "legendFormat": "HTTP {{status_code}}", + "range": true, + "refId": "A" + } + ], + "title": "Deduct Request Rate by Status Code (Error Category Proxy)", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "$datasource" }, + "description": "Total error count by HTTP status over selected time range. 402=INSUFFICIENT_BALANCE, 502=CONTRACT_ERROR or NETWORK_ERROR, 504=TIMEOUT. Source: http_requests_total{route=\"/api/billing/deduct\"}.", + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "fillOpacity": 80, + "gradientMode": "none", + "hideFrom": { "graph": false, "legend": false, "tooltip": false }, + "lineWidth": 0 + }, + "mappings": [ + { "options": { "200": { "color": "green", "index": 0, "text": "Success (200)" } }, "type": "value" }, + { "options": { "402": { "color": "yellow", "index": 1, "text": "INSUFFICIENT_BALANCE (402)" } }, "type": "value" }, + { "options": { "502": { "color": "red", "index": 2, "text": "CONTRACT/NETWORK ERROR (502)" } }, "type": "value" }, + { "options": { "504": { "color": "orange", "index": 3, "text": "TIMEOUT (504)" } }, "type": "value" } + ], + "thresholds": { + "mode": "absolute", + "steps": [{ "color": "green", "value": null }] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 10 }, + "id": 6, + "options": { + "barRadius": 0.05, + "barWidth": 0.7, + "fullHighlight": false, + "groupWidth": 0.7, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "orientation": "auto", + "showValue": "auto", + "stacking": "none", + "tooltip": { "hideZeros": false, "mode": "single", "sort": "none" }, + "xTickLabelRotation": 0, + "xTickLabelSpacing": 200 + }, + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "$datasource" }, + "editorMode": "code", + "expr": "sum by (status_code) (increase(http_requests_total{route=\"/api/billing/deduct\"}[$__range]))", + "instant": true, + "legendFormat": "{{status_code}}", + "refId": "A" + } + ], + "title": "Total Deduct Errors by Category (selected range)", + "type": "barchart" + }, + { + "collapsed": false, + "gridPos": { "h": 1, "w": 24, "x": 0, "y": 18 }, + "id": 12, + "title": "Call Rate & Throughput", + "type": "row" + }, + { + "datasource": { "type": "prometheus", "uid": "$datasource" }, + "description": "Overall deduct call rate (all status codes). Source: http_requests_total{route=\"/api/billing/deduct\"}.", + "fieldConfig": { + "defaults": { + "color": { "mode": "palette-classic" }, + "custom": { + "axisBorderShow": false, + "axisLabel": "req/s", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "none", + "hideFrom": { "graph": false, "legend": false, "tooltip": false }, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 3, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": true, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "off" } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [{ "color": "green", "value": null }] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 12, "x": 0, "y": 19 }, + "id": 7, + "options": { + "legend": { + "calcs": ["mean", "max", "lastNotNull"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { "hideZeros": false, "mode": "multi", "sort": "desc" } + }, + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "$datasource" }, + "editorMode": "code", + "expr": "sum(rate(http_requests_total{route=\"/api/billing/deduct\"}[$__rate_interval]))", + "legendFormat": "Deduct call rate", + "range": true, + "refId": "A" + } + ], + "title": "Deduct Call Rate (all outcomes)", + "type": "timeseries" + }, + { + "datasource": { "type": "prometheus", "uid": "$datasource" }, + "description": "Success rate of deduct calls. Values approaching 1.0 = healthy. Drops indicate INSUFFICIENT_BALANCE, contract errors, or RPC timeouts. Source: http_requests_total{route=\"/api/billing/deduct\"}.", + "fieldConfig": { + "defaults": { + "color": { "mode": "thresholds" }, + "custom": { + "axisBorderShow": false, + "axisLabel": "", + "axisPlacement": "auto", + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "none", + "hideFrom": { "graph": false, "legend": false, "tooltip": false }, + "lineInterpolation": "smooth", + "lineWidth": 2, + "pointSize": 3, + "scaleDistribution": { "type": "linear" }, + "showPoints": "never", + "spanNulls": true, + "stacking": { "group": "A", "mode": "none" }, + "thresholdsStyle": { "mode": "line+area" } + }, + "max": 1, + "min": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { "color": "red", "value": null }, + { "color": "yellow", "value": 0.9 }, + { "color": "green", "value": 0.99 } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { "h": 8, "w": 12, "x": 12, "y": 19 }, + "id": 8, + "options": { + "legend": { + "calcs": ["mean", "lastNotNull"], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { "hideZeros": false, "mode": "single", "sort": "none" } + }, + "targets": [ + { + "datasource": { "type": "prometheus", "uid": "$datasource" }, + "editorMode": "code", + "expr": "sum(rate(http_requests_total{route=\"/api/billing/deduct\",status_code=\"200\"}[$__rate_interval])) / sum(rate(http_requests_total{route=\"/api/billing/deduct\"}[$__rate_interval]))", + "legendFormat": "Success rate", + "range": true, + "refId": "A" + } + ], + "title": "Deduct Success Rate", + "type": "timeseries" + } + ], + "preload": false, + "refresh": "30s", + "schemaVersion": 41, + "tags": ["callora", "soroban", "billing", "deduct", "latency", "errors"], + "templating": { + "list": [ + { + "current": { "selected": false, "text": "default", "value": "default" }, + "hide": 0, + "includeAll": false, + "label": "Datasource", + "multi": false, + "name": "datasource", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + } + ] + }, + "time": { "from": "now-6h", "to": "now" }, + "timepicker": {}, + "timezone": "browser", + "title": "Callora / Soroban Billing", + "uid": "callora-soroban-billing", + "version": 1, + "weekStart": "" +}