perf(web): share service health snapshots

This commit is contained in:
vince 2026-06-20 14:13:16 +02:00
parent 9aa30b07db
commit 5e66367687
2 changed files with 180 additions and 10 deletions

View file

@ -3,7 +3,7 @@ import {
DASHBOARD_SCHEMA_VERSION,
type DashboardDocument,
} from "@dimensionlab/dashboard-model";
import { resolveDashboardDatasources } from ".";
import { resolveDashboardDatasources, resolveDashboardTile } from ".";
describe("dashboard datasource resolution", () => {
test("hydrates telemetry, service health, weather, and summary data from live adapters", async () => {
@ -124,6 +124,100 @@ describe("dashboard datasource resolution", () => {
expect(resolved).not.toBe(testDocument);
expect(testDocument.telemetry[0].value.value).toBe(1);
});
test("shares service health snapshots across aggregate tile hydration", async () => {
let resolveFetch: ((response: Response) => void) | undefined;
const fetch = vi.fn((input: RequestInfo | URL) => {
const url = String(input);
if (url !== "https://service.example/health") {
throw new Error(`Unhandled test request: ${url}`);
}
return new Promise<Response>((resolve) => {
resolveFetch = resolve;
});
});
const document = testDashboard();
const moduleTile = resolveDashboardTile(
document,
{ kind: "module", id: "runtime-health-summary" },
{ fetch },
);
const statusTile = resolveDashboardTile(
document,
{ kind: "status", stripId: "footer", id: "system-status" },
{ fetch },
);
await Promise.resolve();
expect(fetch).toHaveBeenCalledTimes(1);
resolveFetch?.(jsonResponse({ status: "UP", ping: 42 }));
expect(await moduleTile).toMatchObject({
state: "ready",
item: {
id: "runtime-health-summary",
severity: "ok",
value: "all systems operational",
},
});
expect(await statusTile).toMatchObject({
state: "ready",
item: {
id: "system-status",
severity: "ok",
value: "All systems operational",
},
});
});
test("isolates service health snapshots by datasource fetch context", async () => {
const firstFetch = vi.fn(async () =>
jsonResponse({
status: "UP",
ping: 42,
})
);
const secondFetch = vi.fn(async () =>
jsonResponse({
status: "DOWN",
ping: 0,
})
);
const document = testDashboard();
document.serviceGroups[0].services[0] = {
...document.serviceGroups[0].services[0],
id: "api-isolated",
datasource: {
type: "external",
adapter: "http-status",
reference: "GET https://service.example/isolated",
},
};
await resolveDashboardTile(
document,
{ kind: "status", stripId: "footer", id: "system-status" },
{ fetch: firstFetch },
);
const second = await resolveDashboardTile(
document,
{ kind: "status", stripId: "footer", id: "system-status" },
{ fetch: secondFetch },
);
expect(firstFetch).toHaveBeenCalledTimes(1);
expect(secondFetch).toHaveBeenCalledTimes(1);
expect(second).toMatchObject({
state: "ready",
item: {
id: "system-status",
severity: "danger",
value: "1 service down",
},
});
});
});
const testDocument = testDashboard();

View file

@ -41,6 +41,7 @@ export type DashboardTileResolution =
export interface DatasourceResolutionOptions {
fetch?: DatasourceFetch;
now?: () => number;
prometheusBaseUrl?: string;
prometheusRangeSeconds?: number;
prometheusStepSeconds?: number;
@ -113,9 +114,7 @@ export async function resolveDashboardTile(
const item = module.id === "runtime-health-summary"
? runtimeHealthSummary(
module,
await Promise.all(
document.serviceGroups.map((group) => resolveServiceGroup(group, context)),
),
await serviceGroupsSnapshot(document, context),
)
: await resolveModule(module, context);
@ -132,7 +131,7 @@ export async function resolveDashboardTile(
item: await resolveStatusTile(
statusItem,
document.metadata.refreshIntervalSeconds,
document.serviceGroups,
document,
context,
),
};
@ -140,14 +139,27 @@ export async function resolveDashboardTile(
interface DatasourceContext {
fetch: DatasourceFetch;
fetchIdentity: number;
now: () => number;
prometheusBaseUrl: string;
prometheusRangeSeconds: number;
prometheusStepSeconds: number;
requestTimeoutMs: number;
serviceGroupsSnapshot?: Promise<ServiceGroup[]>;
}
type DatasourceFetch = (input: string, init?: RequestInit) => Promise<Response>;
interface ServiceGroupsSnapshotEntry {
expiresAt: number;
snapshot: Promise<ServiceGroup[]>;
}
const serviceGroupsSnapshotTtlMs = 30_000;
const serviceGroupsSnapshotCache = new Map<string, ServiceGroupsSnapshotEntry>();
const datasourceFetchIdentities = new WeakMap<DatasourceFetch, number>();
let nextDatasourceFetchIdentity = 1;
interface PrometheusVectorResult {
metric?: Record<string, string>;
value?: [number, string];
@ -159,8 +171,12 @@ interface PrometheusMatrixResult {
}
function datasourceContext(options: DatasourceResolutionOptions): DatasourceContext {
const fetch = options.fetch || globalThis.fetch;
return {
fetch: options.fetch || globalThis.fetch,
fetch,
fetchIdentity: datasourceFetchIdentity(fetch),
now: options.now || Date.now,
prometheusBaseUrl:
options.prometheusBaseUrl ||
process.env.PROMETHEUS_BASE_URL ||
@ -171,6 +187,68 @@ function datasourceContext(options: DatasourceResolutionOptions): DatasourceCont
};
}
function datasourceFetchIdentity(fetch: DatasourceFetch): number {
const existing = datasourceFetchIdentities.get(fetch);
if (existing) return existing;
const next = nextDatasourceFetchIdentity;
nextDatasourceFetchIdentity += 1;
datasourceFetchIdentities.set(fetch, next);
return next;
}
function serviceGroupsSnapshot(
document: DashboardDocument,
context: DatasourceContext,
): Promise<ServiceGroup[]> {
if (context.serviceGroupsSnapshot) return context.serviceGroupsSnapshot;
const key = serviceGroupsSnapshotKey(document, context);
const now = context.now();
const cached = serviceGroupsSnapshotCache.get(key);
if (cached && cached.expiresAt > now) {
context.serviceGroupsSnapshot = cached.snapshot;
return cached.snapshot;
}
const snapshot = Promise.all(
document.serviceGroups.map((group) => resolveServiceGroup(group, context)),
);
context.serviceGroupsSnapshot = snapshot;
serviceGroupsSnapshotCache.set(key, {
expiresAt: now + serviceGroupsSnapshotTtlMs,
snapshot,
});
snapshot.catch(() => {
if (serviceGroupsSnapshotCache.get(key)?.snapshot === snapshot) {
serviceGroupsSnapshotCache.delete(key);
}
});
return snapshot;
}
function serviceGroupsSnapshotKey(
document: DashboardDocument,
context: DatasourceContext,
): string {
return JSON.stringify(
{
fetchIdentity: context.fetchIdentity,
prometheusBaseUrl: context.prometheusBaseUrl,
prometheusRangeSeconds: context.prometheusRangeSeconds,
prometheusStepSeconds: context.prometheusStepSeconds,
requestTimeoutMs: context.requestTimeoutMs,
serviceGroups: document.serviceGroups.map((group) => ({
id: group.id,
services: group.services.map((service) => ({
datasource: service.datasource,
id: service.id,
})),
})),
},
);
}
async function resolveTelemetryCard(
card: TelemetryCard,
context: DatasourceContext,
@ -485,13 +563,11 @@ function resolveStatusItem(
async function resolveStatusTile(
item: StatusItem,
refreshIntervalSeconds: number | undefined,
serviceGroups: ServiceGroup[],
document: DashboardDocument,
context: DatasourceContext,
): Promise<StatusItem> {
if (item.id === "system-status") {
const resolvedGroups = await Promise.all(
serviceGroups.map((group) => resolveServiceGroup(group, context)),
);
const resolvedGroups = await serviceGroupsSnapshot(document, context);
const health = serviceHealthSummary(resolvedGroups);
return {
...structuredClone(item),