feat(telemetry): local move-preview adoption metrics (Phase 4)
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
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
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
import { Dawg } from './dawg';
|
||||
import { dictKey, idbGetDawg, idbPutDawg, idbDelDawg, requestPersist } from './store';
|
||||
import { gateway } from '../gateway';
|
||||
import { noteDictFetched, noteDictCacheHit, noteDictMiss as noteDictMissMetric } from '../localeval-metrics';
|
||||
import type { Variant } from '../model';
|
||||
|
||||
// Loaded readers by key. A dictionary is immutable, so an instance is reused for
|
||||
@@ -31,6 +32,7 @@ let dictDisabled = false;
|
||||
*/
|
||||
export function noteDictMiss(): void {
|
||||
if (!dictDisabled && ++misses >= MISS_LIMIT) dictDisabled = true;
|
||||
noteDictMissMetric();
|
||||
}
|
||||
|
||||
/** dictLoadingDisabled reports whether the bad-connection breaker has tripped for this
|
||||
@@ -73,6 +75,7 @@ async function load(variant: Variant, version: string, key: string, signal?: Abo
|
||||
if (cached) {
|
||||
const reader = new Dawg(new Uint8Array(cached));
|
||||
instances.set(key, reader);
|
||||
noteDictCacheHit();
|
||||
return reader;
|
||||
}
|
||||
} catch {
|
||||
@@ -101,6 +104,7 @@ async function load(variant: Variant, version: string, key: string, signal?: Abo
|
||||
return null; // not a dawg — do not cache it
|
||||
}
|
||||
instances.set(key, reader);
|
||||
noteDictFetched();
|
||||
void idbPutDawg(key, buf);
|
||||
void requestPersist();
|
||||
return reader;
|
||||
|
||||
Reference in New Issue
Block a user