Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export function App() {
return (
<div className="dashboard-app">
<AppHeader section={section} />
<main className="dashboard-main" data-layout={section === "differential-testing" || section === "streaming-diff" || selected ? "full" : "index"} data-section={section}>
{section === "differential-testing" ? <DifferentialTestingPage /> : section === "streaming-diff" ? <StreamingDiff projects={projects} projectsLoading={loading} /> : section === "new-oven" ? <NewOvenPage /> : section === "run-burn" ? <RunBurnPage /> : selected ? (
<main className="dashboard-main" data-layout={section === "differential-testing" || section === "performance-tracing" || section === "streaming-diff" || selected ? "full" : "index"} data-section={section}>
{section === "differential-testing" ? <DifferentialTestingPage /> : section === "performance-tracing" ? <DifferentialTestingPage ovenId="performance-tracing" /> : section === "streaming-diff" ? <StreamingDiff projects={projects} projectsLoading={loading} /> : section === "new-oven" ? <NewOvenPage /> : section === "run-burn" ? <RunBurnPage /> : selected ? (
error ? <DashboardError message={error} /> : loading && !progress ? <EmptyState title="Loading progress" detail="Reading the selected Burnlist." /> : progress ? (
<ChecklistDashboard backHref={listHref(filter)} data={progress} />
) : <EmptyState title="Choose a Burnlist" detail="Select an item from the list to inspect its progress." icon={ListChecks} />
Expand Down
3 changes: 2 additions & 1 deletion dashboard/src/components/AppHeader/AppHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ export function AppHeader({ section }: { section: string }) {
<span className="dashboard-brand-name">Burnlist</span>
</a>
{section === "differential-testing" && <div className="dashboard-oven-title">Differential Testing</div>}
{section === "performance-tracing" && <div className="dashboard-oven-title">Performance Tracing</div>}
{section === "streaming-diff" && <div className="dashboard-oven-title">Streaming Diff</div>}
<nav aria-label="Primary navigation" className="dashboard-primary-nav">
{section !== "differential-testing" && section !== "streaming-diff" && HEADER_LINKS.map((link, index) => (
{section !== "differential-testing" && section !== "performance-tracing" && section !== "streaming-diff" && HEADER_LINKS.map((link, index) => (
<span className="dashboard-primary-nav-item" key={link.href}>
{index > 0 && <span aria-hidden="true" className="dashboard-primary-nav-separator">·</span>}
<a aria-label={link.label} aria-current={section === link.section ? "page" : undefined} className="dashboard-primary-nav-link" href={link.href} title={link.label}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
import { useEffect, useRef } from "react";
import { ovenRepoKey } from "@lib";
import { adaptPerformanceTracingReport, ovenRepoKey } from "@lib";
// @ts-expect-error The canonical renderer is plain ESM so React and the direct Oven route share one implementation.
import { startDifferentialTestingLiveUpdates } from "../../../../ovens/differential-testing/renderer/differential-testing-renderer.js";

export function DifferentialTestingPage() {
export function DifferentialTestingPage({ ovenId = "differential-testing" }: { ovenId?: "differential-testing" | "performance-tracing" }) {
const root = useRef<HTMLDivElement>(null);

useEffect(() => {
document.body.classList.add("driving-parity-view");
return () => document.body.classList.remove("driving-parity-view");
}, []);
if (ovenId === "performance-tracing") document.body.classList.add("performance-tracing-oven");
return () => {
document.body.classList.remove("driving-parity-view");
document.body.classList.remove("performance-tracing-oven");
};
}, [ovenId]);

useEffect(() => {
if (!root.current) return;
const controller = startDifferentialTestingLiveUpdates(root.current, { repoKey: ovenRepoKey() });
const performanceTracing = ovenId === "performance-tracing";
const controller = startDifferentialTestingLiveUpdates(root.current, {
repoKey: ovenRepoKey(),
dataOvenId: ovenId,
...(performanceTracing ? {
adaptPayload: adaptPerformanceTracingReport,
mountOptions: { initialChart: "current", initialProgressChart: "delta" },
onError: (error: unknown) => {
if (!root.current) return;
const message = document.createElement("div");
message.className = "empty";
message.textContent = error instanceof Error ? error.message : String(error);
root.current.replaceChildren(message);
},
} : {}),
});
return () => controller.stop();
}, []);
}, [ovenId]);

return <div ref={root} className="shell driving-parity-view" />;
return <div ref={root} className={`shell driving-parity-view${ovenId === "performance-tracing" ? " performance-tracing-oven" : ""}`} />;
}
3 changes: 2 additions & 1 deletion dashboard/src/lib/hrefs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import type { Burnlist, Filter, SelectedBurnlist } from "./types";
export function currentSection() {
if (window.location.pathname === "/ovens/new") return "new-oven";
if (window.location.pathname === "/ovens/differential-testing/view") return "differential-testing";
if (window.location.pathname === "/ovens/performance-tracing/view") return "performance-tracing";
if (window.location.pathname === "/ovens/streaming-diff/view") return "streaming-diff";
if (window.location.pathname === "/runs/new") return "run-burn";
return "burnlists";
}

export function ovenRepoKey() {
return ["differential-testing", "streaming-diff"].includes(currentSection())
return ["differential-testing", "performance-tracing", "streaming-diff"].includes(currentSection())
? new URLSearchParams(window.location.search).get("repoKey")
: null;
}
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { formatTime } from "./format";
export { burnlistHref, currentSection, filterFromUrl, listHref, ovenRepoKey, selectedBurnlist, streamingDiffSelection } from "./hrefs";
export { adaptPerformanceTracingReport } from "./performance-tracing.mjs";
export { applyStreamingDiffUpdate, fileKindChip, groupStreamingDiffCard, isTextFileKind, mapStreamingDiffFeeds, mapStreamingDiffLandingFeeds, parseStreamingDiffCard, streamingDiffAutoOpenHref, streamingDiffFeedHref, streamingDiffFeedKey, streamingDiffRepositories } from "./streaming-diff.mjs";
export type { Burnlist, ChecklistProgressData, ChecklistItem, CompletedItem, Filter, HistoryPoint, ProgressData, Project, SelectedBurnlist, StreamingDiffCard, StreamingDiffFeed, StreamingDiffFile, StreamingDiffFileKind, StreamingDiffIdentity, Warning } from "./types";
export { cn, joinClasses } from "./utils";
Expand Down
127 changes: 127 additions & 0 deletions dashboard/src/lib/performance-tracing.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
const metricDefinitions = [
{ id: "startup.ready", label: "startup.ready", metric: "startupReadyMs", budget: "startupReadyMs" },
{ id: "frame.p95", label: "frame.p95", metric: "p95FrameMs", budget: "p95FrameMs" },
{ id: "frame.p99", label: "frame.p99", metric: "p99FrameMs", budget: "p99FrameMs" },
{ id: "frame.max", label: "frame.max", metric: "maxFrameMs", budget: "maxFrameMs" },
{ id: "frame.over33", label: "frame.over33", metric: "over33msRatio", budget: "over33msRatio" },
{ id: "step.p95", label: "step.p95", metric: "p95StepCallMs", budget: "p95StepCallMs" },
];

function finite(value) {
const number = Number(value);
return Number.isFinite(number) ? number : null;
}

function reportHistory(report) {
const fallback = [{
runId: report.runId,
generatedAt: report.generatedAt,
status: report.status,
comparisonKey: "current-report",
metrics: report.metrics ?? {},
budgets: report.budgets ?? {},
}];
if (!Array.isArray(report.history) || !report.history.length) return fallback;
const current = [...report.history].reverse().find((point) => point.runId === report.runId
&& point.generatedAt === report.generatedAt) ?? report.history.at(-1);
if (!current?.comparisonKey) return fallback;
const comparable = report.history.filter((point) => point.comparisonKey === current.comparisonKey);
return comparable.length ? comparable : fallback;
}

function passCount(point) {
return metricDefinitions.reduce((total, definition) => {
const actual = finite(point.metrics?.[definition.metric]);
const limit = finite(point.budgets?.[definition.budget]);
return total + (actual !== null && limit !== null && actual <= limit ? 1 : 0);
}, 0);
}

export function adaptPerformanceTracingReport(report) {
const history = reportHistory(report);
const fields = metricDefinitions.map((definition) => {
const samples = history.map((point, index) => {
const actual = finite(point.metrics?.[definition.metric]);
const limit = finite(point.budgets?.[definition.budget]);
if (actual === null || limit === null) return [index, limit, actual, 3];
return [index, limit, actual, actual <= limit ? 0 : 1];
});
const failedSampleCount = samples.filter((sample) => sample[3] === 1).length;
const missingSampleCount = samples.filter((sample) => sample[3] !== 0 && sample[3] !== 1).length;
const deltas = samples
.map((sample) => sample[1] === null || sample[2] === null ? null : Math.abs(Number(sample[2]) - Number(sample[1])))
.filter((value) => value !== null);
return {
id: definition.id,
label: definition.label,
samples,
sampleLabels: history.map((point) => new Date(point.generatedAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })),
failedSampleCount,
missingSampleCount,
trustStatus: missingSampleCount ? "blocked" : "pass",
maxDelta: deltas.length ? Math.max(...deltas) : null,
sourceOwner: `performance trace / ${definition.metric}`,
semantics: { kind: "number", meaning: `${definition.metric} measured against ${definition.budget}` },
};
});

const progress = history.map((point, index) => {
const frame = passCount(point);
const previous = index > 0 ? passCount(history[index - 1]) : null;
return {
timestamp: point.generatedAt,
frame,
frames: metricDefinitions.length,
frameDelta: previous === null ? null : frame - previous,
};
});
const log = history.map((point, index) => {
const frame = passCount(point);
const previous = index > 0 ? passCount(history[index - 1]) : null;
const frameDelta = previous === null ? null : frame - previous;
return {
timestamp: point.generatedAt,
frame,
frames: metricDefinitions.length,
frameDelta,
firstFailingTick: null,
result: frameDelta === null ? point.status === "pass" ? "pass" : "blocked" : frameDelta > 0 ? "improved" : frameDelta < 0 ? "worsened" : "unchanged",
};
}).reverse();
const failedFields = fields.filter((field) => field.failedSampleCount > 0).length;
const blockedFields = fields.filter((field) => field.missingSampleCount > 0).length;
const frameTimingSeries = Array.isArray(report.runs?.[0]?.frameTiming?.series) ? report.runs[0].frameTiming.series : [];
const frameLimit = finite(report.budgets?.p95FrameMs);
const primaryChartSamples = frameTimingSeries.map((sample, index) => {
const actual = finite(sample.frameMs);
return [Number(sample.frame ?? index), frameLimit, actual, frameLimit === null || actual === null || actual <= frameLimit ? 0 : 1];
});

return {
subtitle: report.runId ?? "retained performance trace",
publishedAt: report.generatedAt,
scenarioCatalog: {
selectedScenarioId: report.scenario?.id ?? "performance-tracing",
scenarios: [{ id: report.scenario?.id ?? "performance-tracing" }],
},
refresh: null,
primaryChartTitle: "Frame timing",
historyTitle: "History log",
primaryChartField: {
id: "frame.p95.history",
label: "frame.timing",
samples: primaryChartSamples,
},
progress,
log,
fields,
summary: {
fields: { total: fields.length, failed: failedFields, blocked: blockedFields },
frames: {
total: history.length,
failed: history.filter((point) => point.status !== "pass").length,
blocked: 0,
},
},
};
}
Loading
Loading