perf(web): cache dashboard tile responses
This commit is contained in:
parent
cda40401ca
commit
bc1f64d566
2 changed files with 386 additions and 4 deletions
|
|
@ -1,6 +1,12 @@
|
|||
import { describe, expect, test, vi } from "vitest";
|
||||
import { dimensionLabDashboardFixture } from "$lib/dashboard-seed/dimensionlab";
|
||||
import { loadDashboardResponse, loadDashboardTileResponse } from "./dashboard";
|
||||
import {
|
||||
createDashboardTileCache,
|
||||
dashboardTileCacheKey,
|
||||
handleDashboardTileRoute,
|
||||
loadDashboardResponse,
|
||||
loadDashboardTileResponse,
|
||||
} from "./dashboard";
|
||||
|
||||
describe("dashboard API route", () => {
|
||||
test("returns the ready dashboard shell without hydrating live datasources", async () => {
|
||||
|
|
@ -145,6 +151,243 @@ describe("dashboard API route", () => {
|
|||
});
|
||||
});
|
||||
|
||||
test("caches ready tile responses until the tile ttl expires", async () => {
|
||||
const cache = createDashboardTileCache();
|
||||
let now = 1_000;
|
||||
const fetch = vi.fn(async () =>
|
||||
jsonResponse({
|
||||
status: "UP",
|
||||
ping: 42,
|
||||
}),
|
||||
);
|
||||
const tile = { kind: "service", groupId: "essentials", id: "vaultwarden" } as const;
|
||||
|
||||
const first = await loadDashboardTileResponse(tile, {
|
||||
fetch,
|
||||
now: () => now,
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: cache,
|
||||
});
|
||||
const second = await loadDashboardTileResponse(tile, {
|
||||
fetch,
|
||||
now: () => now + 29_999,
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: cache,
|
||||
});
|
||||
now += 30_001;
|
||||
const third = await loadDashboardTileResponse(tile, {
|
||||
fetch,
|
||||
now: () => now,
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: cache,
|
||||
});
|
||||
|
||||
expect(first).toEqual(second);
|
||||
expect(third.state).toBe("ready");
|
||||
expect(fetch).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test("keeps telemetry tiles cached for fifteen seconds", async () => {
|
||||
const cache = createDashboardTileCache();
|
||||
let now = 1_000;
|
||||
const fetch = telemetryFetch();
|
||||
const tile = { kind: "telemetry", id: "infra-ram" } as const;
|
||||
|
||||
await loadDashboardTileResponse(tile, {
|
||||
fetch,
|
||||
now: () => now,
|
||||
prometheusBaseUrl: "https://prometheus.example",
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: cache,
|
||||
});
|
||||
await loadDashboardTileResponse(tile, {
|
||||
fetch,
|
||||
now: () => now + 14_999,
|
||||
prometheusBaseUrl: "https://prometheus.example",
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: cache,
|
||||
});
|
||||
now += 15_001;
|
||||
await loadDashboardTileResponse(tile, {
|
||||
fetch,
|
||||
now: () => now,
|
||||
prometheusBaseUrl: "https://prometheus.example",
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: cache,
|
||||
});
|
||||
|
||||
expect(fetch).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
test("keeps weather module tiles cached for ten minutes", async () => {
|
||||
const cache = createDashboardTileCache();
|
||||
let now = 1_000;
|
||||
const fetch = vi.fn(async () =>
|
||||
jsonResponse({
|
||||
current: {
|
||||
apparent_temperature: 19,
|
||||
temperature_2m: 20,
|
||||
weather_code: 0,
|
||||
wind_speed_10m: 11,
|
||||
},
|
||||
}),
|
||||
);
|
||||
const tile = { kind: "module", id: "weather-amsterdam" } as const;
|
||||
|
||||
await loadDashboardTileResponse(tile, {
|
||||
fetch,
|
||||
now: () => now,
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: cache,
|
||||
});
|
||||
await loadDashboardTileResponse(tile, {
|
||||
fetch,
|
||||
now: () => now + 599_999,
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: cache,
|
||||
});
|
||||
now += 600_001;
|
||||
await loadDashboardTileResponse(tile, {
|
||||
fetch,
|
||||
now: () => now,
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: cache,
|
||||
});
|
||||
|
||||
expect(fetch).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test("coalesces concurrent tile requests for the same cache key", async () => {
|
||||
const cache = createDashboardTileCache();
|
||||
let resolveFetch: ((response: Response) => void) | undefined;
|
||||
const fetch = vi.fn(() =>
|
||||
new Promise<Response>((resolve) => {
|
||||
resolveFetch = resolve;
|
||||
})
|
||||
);
|
||||
const tile = { kind: "service", groupId: "essentials", id: "vaultwarden" } as const;
|
||||
|
||||
const first = loadDashboardTileResponse(tile, {
|
||||
fetch,
|
||||
now: () => 1_000,
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: cache,
|
||||
});
|
||||
const second = loadDashboardTileResponse(tile, {
|
||||
fetch,
|
||||
now: () => 1_000,
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: cache,
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
expect(fetch).toHaveBeenCalledTimes(1);
|
||||
|
||||
resolveFetch?.(jsonResponse({ status: "UP", ping: 42 }));
|
||||
expect(await first).toEqual(await second);
|
||||
});
|
||||
|
||||
test("uses structured tile cache keys when identifiers contain delimiters", () => {
|
||||
expect(
|
||||
dashboardTileCacheKey({ kind: "service", groupId: "a:b", id: "c" }),
|
||||
).not.toBe(
|
||||
dashboardTileCacheKey({ kind: "service", groupId: "a", id: "b:c" }),
|
||||
);
|
||||
expect(
|
||||
dashboardTileCacheKey({ kind: "status", stripId: "a:b", id: "c" }),
|
||||
).not.toBe(
|
||||
dashboardTileCacheKey({ kind: "status", stripId: "a", id: "b:c" }),
|
||||
);
|
||||
});
|
||||
|
||||
test("uses thirty-second status aggregate and five-minute static status ttl buckets", async () => {
|
||||
const cache = createDashboardTileCache();
|
||||
const fetch = vi.fn(async () => jsonResponse({ status: "UP", ping: 42 }));
|
||||
const serviceCheckCount = dimensionLabDashboardFixture.serviceGroups
|
||||
.flatMap((group) => group.services).length;
|
||||
|
||||
await loadDashboardTileResponse(
|
||||
{ kind: "status", stripId: "footer-status", id: "system-status" },
|
||||
{
|
||||
fetch,
|
||||
now: () => 1_000,
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: cache,
|
||||
},
|
||||
);
|
||||
await loadDashboardTileResponse(
|
||||
{ kind: "status", stripId: "footer-status", id: "system-status" },
|
||||
{
|
||||
fetch,
|
||||
now: () => 31_001,
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: cache,
|
||||
},
|
||||
);
|
||||
|
||||
await loadDashboardTileResponse(
|
||||
{ kind: "status", stripId: "footer-status", id: "auto-refresh" },
|
||||
{
|
||||
fetch,
|
||||
now: () => 1_000,
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: cache,
|
||||
},
|
||||
);
|
||||
await loadDashboardTileResponse(
|
||||
{ kind: "status", stripId: "footer-status", id: "auto-refresh" },
|
||||
{
|
||||
fetch,
|
||||
now: () => 300_999,
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: cache,
|
||||
},
|
||||
);
|
||||
await loadDashboardTileResponse(
|
||||
{ kind: "status", stripId: "footer-status", id: "auto-refresh" },
|
||||
{
|
||||
fetch,
|
||||
now: () => 301_001,
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: cache,
|
||||
},
|
||||
);
|
||||
|
||||
expect(fetch).toHaveBeenCalledTimes(serviceCheckCount * 2);
|
||||
});
|
||||
|
||||
test("serves tile route responses with short private cache headers", async () => {
|
||||
const response = await handleDashboardTileRoute(
|
||||
"/api/dashboard/tile/service/essentials/vaultwarden",
|
||||
{
|
||||
fetch: vi.fn(async () => jsonResponse({ status: "UP", ping: 42 })),
|
||||
refreshSeedDocument: true,
|
||||
seedIfEmpty: true,
|
||||
tileCache: createDashboardTileCache(),
|
||||
},
|
||||
);
|
||||
|
||||
expect(response.headers.get("cache-control")).toBe(
|
||||
"private, max-age=5, stale-while-revalidate=30",
|
||||
);
|
||||
});
|
||||
|
||||
test("does not hydrate tile routes when live datasources are disabled", async () => {
|
||||
const previous = process.env.DISABLE_LIVE_DATASOURCES;
|
||||
process.env.DISABLE_LIVE_DATASOURCES = "1";
|
||||
|
|
@ -179,3 +422,43 @@ function jsonResponse(payload: unknown): Response {
|
|||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
function telemetryFetch() {
|
||||
return vi.fn(async (input: RequestInfo | URL) => {
|
||||
const url = String(input);
|
||||
|
||||
if (url.startsWith("https://prometheus.example/api/v1/query_range")) {
|
||||
return jsonResponse({
|
||||
status: "success",
|
||||
data: {
|
||||
result: [
|
||||
{
|
||||
metric: { host: "linux-infra" },
|
||||
values: [
|
||||
[1771430000, "10"],
|
||||
[1771430060, "20"],
|
||||
[1771430120, "42"],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (url.startsWith("https://prometheus.example/api/v1/query")) {
|
||||
return jsonResponse({
|
||||
status: "success",
|
||||
data: {
|
||||
result: [
|
||||
{
|
||||
metric: { host: "linux-infra" },
|
||||
value: [1771430400, "42"],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error(`Unhandled test request: ${url}`);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue