2e8fa83814
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 57s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m17s
Measure uptake of the client-side local move-preview accelerator (§5) so adoption can be watched before defaulting it on: app cold starts, dictionary loads by result (fetched / cache_hit / miss) and move previews by path (local / network — the backend load shed). A small best-effort client beacon (POST /metrics/local-eval, session-gated) batches counter deltas and posts them on a 60s timer and when the app is backgrounded — never on the gameplay path: the in-app counters are plain in-memory increments, only the periodic flush touches the network and it is fire-and-forget. The gateway folds each batch into three OTel counters (local_eval_cold_start_total, local_eval_dict_load_total, local_eval_preview_total), clamped against a spoofed inflation. - gateway: counters + recordLocalEval + session-gated /metrics/local-eval handler - ui: localeval-metrics accumulator/beacon; hooks in the dict loader, in Game.recompute and in bootstrap (skipped under the mock harness) - caddy: route /metrics/* to the gateway - docs: ARCHITECTURE §11; Grafana "Scrabble — Users" dashboard panels
62 lines
3.2 KiB
JSON
62 lines
3.2 KiB
JSON
{
|
|
"uid": "scrabble-users",
|
|
"title": "Scrabble — Users",
|
|
"tags": ["scrabble"],
|
|
"timezone": "",
|
|
"schemaVersion": 39,
|
|
"version": 2,
|
|
"refresh": "30s",
|
|
"time": { "from": "now-7d", "to": "now" },
|
|
"panels": [
|
|
{
|
|
"type": "timeseries",
|
|
"title": "Active users (in-memory, single gateway)",
|
|
"description": "Distinct accounts with an authenticated action within the window. Resets on gateway restart; correct for a single instance (MVP).",
|
|
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
|
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
"targets": [{ "refId": "A", "expr": "max(active_users) by (window)", "legendFormat": "{{window}}" }]
|
|
},
|
|
{
|
|
"type": "timeseries",
|
|
"title": "New accounts (rate by kind)",
|
|
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 },
|
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
"targets": [{ "refId": "A", "expr": "sum(rate(accounts_created_total[1h])) by (kind)", "legendFormat": "{{kind}}" }]
|
|
},
|
|
{
|
|
"type": "timeseries",
|
|
"title": "New accounts (cumulative by kind)",
|
|
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 8 },
|
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
"targets": [{ "refId": "A", "expr": "sum(accounts_created_total) by (kind)", "legendFormat": "{{kind}}" }]
|
|
},
|
|
{
|
|
"type": "timeseries",
|
|
"title": "App opens vs dictionary fills (cumulative)",
|
|
"description": "Local move-preview adoption. App opens are client cold starts; dictionary fills are IndexedDB downloads of a dawg. The gap between them is how many launches reuse an already-cached dictionary. Client-reported, best-effort (a batched beacon), so slightly lossy.",
|
|
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 16 },
|
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
"targets": [
|
|
{ "refId": "A", "expr": "sum(local_eval_cold_start_total)", "legendFormat": "app opens (cold start)" },
|
|
{ "refId": "B", "expr": "sum(local_eval_dict_load_total{result=\"fetched\"})", "legendFormat": "dictionary fills (IndexedDB)" }
|
|
]
|
|
},
|
|
{
|
|
"type": "timeseries",
|
|
"title": "Dictionary loads by result (rate)",
|
|
"description": "Client dictionary loads for the local preview, by tier: fetched (network download), cache_hit (IndexedDB), miss (a warm-up that failed or timed out — trips the bad-connection breaker).",
|
|
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 16 },
|
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
"targets": [{ "refId": "A", "expr": "sum(rate(local_eval_dict_load_total[1h])) by (result)", "legendFormat": "{{result}}" }]
|
|
},
|
|
{
|
|
"type": "timeseries",
|
|
"title": "Move previews by path (rate)",
|
|
"description": "Where the move preview is computed: local (on-device, the accelerator) vs network (the fallback to game.evaluate). The local share is the backend load shed.",
|
|
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 24 },
|
|
"datasource": { "type": "prometheus", "uid": "prometheus" },
|
|
"targets": [{ "refId": "A", "expr": "sum(rate(local_eval_preview_total[1h])) by (path)", "legendFormat": "{{path}}" }]
|
|
}
|
|
]
|
|
}
|