feat: add workbench performance dashboard
This commit is contained in:
@@ -257,6 +257,7 @@ function webPerformanceSummaryPayload(): JsonRecord {
|
||||
namespace: "hwlab-v03",
|
||||
gitopsTarget: "D601/v03",
|
||||
summary: { status: "ready", sampleCount: 93, sampleSeries: 12, problemCount: 2, lowSampleThreshold: 5, workbenchJourneySeries: 1, workbenchEventPhaseSeries: 1, workbenchBackendEventVisibleSeries: 1 },
|
||||
dashboard: webPerformanceDashboardPayload(rows),
|
||||
rows,
|
||||
problems: rows.filter((row) => row.tone === "warn"),
|
||||
webVitals: rows.filter((row) => row.kind === "web-vital"),
|
||||
@@ -272,6 +273,71 @@ function performanceRow(row: JsonRecord): JsonRecord {
|
||||
return { method: "GET", statusClass: "2xx", outcome: "ok", unit: "seconds", sampleState: "ok", ...row };
|
||||
}
|
||||
|
||||
function webPerformanceDashboardPayload(rows: JsonRecord[]): JsonRecord {
|
||||
const workbench = rows.filter((row) => String(row.kind).startsWith("workbench"));
|
||||
const api = rows.filter((row) => row.kind === "api");
|
||||
const vital = rows.filter((row) => row.kind === "web-vital");
|
||||
const longTask = rows.filter((row) => row.kind === "long-task");
|
||||
return {
|
||||
schemaVersion: "hwlab-web-performance-dashboard-v1",
|
||||
generatedAt: "2026-06-18T04:30:00.000Z",
|
||||
sampleWindow: { label: "当前采集窗口", bucketKind: "snapshot", buckets: [{ id: "current", label: "当前窗口", observedAt: "2026-06-18T04:30:00.000Z", sampleCount: 93 }] },
|
||||
freshness: { observedAt: "2026-06-18T04:30:00.000Z", source: "fake-server", namespace: "hwlab-v03", gitopsTarget: "D601/v03", status: "watch", statusLabel: "预警", lowSampleThreshold: 5 },
|
||||
cards: [
|
||||
{ id: "health", label: "采集状态", value: "预警", unit: "状态", tone: "warn", detail: "fake-server" },
|
||||
{ id: "samples", label: "样本数", value: 93, unit: "样本", tone: "ok", detail: "12 个序列" },
|
||||
{ id: "workbench", label: "工作台序列", value: 3, unit: "序列", tone: "ok", detail: "hwlab-v03 / D601/v03" },
|
||||
{ id: "problems", label: "问题行", value: 2, unit: "条", tone: "warn", detail: "低样本阈值 5 个样本" }
|
||||
],
|
||||
trends: [
|
||||
{ id: "workbench-experience", title: "工作台体感趋势", subtitle: "首屏、会话切换和提交首个可见结果的 P95", unit: "seconds", points: workbench.map(dashboardPoint) },
|
||||
{ id: "api-timing", title: "同源 API 耗时", subtitle: "Cloud Web 同源 API 请求 P95", unit: "seconds", points: api.map(dashboardPoint) },
|
||||
{ id: "web-vitals", title: "浏览器核心指标", subtitle: "浏览器体感当前窗口", unit: "mixed", points: vital.map(dashboardPoint) }
|
||||
],
|
||||
distributions: [
|
||||
{ id: "status", title: "状态分布", subtitle: "按健康状态聚合当前窗口行数", unit: "count", buckets: [{ label: "正常", count: 4, tone: "ok" }, { label: "预警", count: 2, tone: "warn" }] },
|
||||
{ id: "samples", title: "样本状态", subtitle: "正常、低样本和暂无数据分布", unit: "count", buckets: [{ label: "正常样本", count: 6, tone: "ok" }] }
|
||||
],
|
||||
topN: [
|
||||
{ id: "top-api", title: "慢 API TopN", subtitle: "同源 API P95 最慢路由", unit: "seconds", rows: api.map(dashboardTopRow) },
|
||||
{ id: "top-workbench", title: "工作台慢路径 TopN", subtitle: "工作台 journey 和事件可见延迟", unit: "seconds", rows: workbench.map(dashboardTopRow) },
|
||||
{ id: "top-long-task", title: "长任务 TopN", subtitle: "主线程长任务当前窗口", unit: "seconds", rows: longTask.map(dashboardTopRow) }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
function dashboardPoint(row: JsonRecord): JsonRecord {
|
||||
return { label: dashboardMetricLabel(row), detail: dashboardRouteLabel(row.route), value: row.p95, p50: row.p50, p75: row.p75, p95: row.p95, count: row.count, tone: row.tone, sampleState: row.sampleState };
|
||||
}
|
||||
|
||||
function dashboardTopRow(row: JsonRecord): JsonRecord {
|
||||
return { label: dashboardMetricLabel(row), detail: dashboardRouteLabel(row.route), value: row.p95, unit: row.unit ?? "seconds", count: row.count, tone: row.tone };
|
||||
}
|
||||
|
||||
function dashboardRouteLabel(route: unknown): string {
|
||||
const value = String(route ?? "").trim();
|
||||
if (!value || value === "-") return "当前窗口";
|
||||
return value
|
||||
.replace(/:sessionId/gu, ":会话")
|
||||
.replace(/:traceId/gu, ":轨迹")
|
||||
.replace(/:runId/gu, ":运行")
|
||||
.replace(/:threadId/gu, ":线程")
|
||||
.replace(/:turnId/gu, ":轮次");
|
||||
}
|
||||
|
||||
function dashboardMetricLabel(row: JsonRecord): string {
|
||||
const metric = String(row.metric ?? "unknown");
|
||||
const labels: Record<string, string> = {
|
||||
"session-switch-full-load": "切换会话完整加载",
|
||||
"trace-event-projected": "轨迹事件投影",
|
||||
"backend-event-visible": "后端事件到可见",
|
||||
LCP: "最大内容绘制",
|
||||
fetchJson: "同源 API 请求",
|
||||
longtask: "主线程长任务"
|
||||
};
|
||||
return labels[metric] ?? metric;
|
||||
}
|
||||
|
||||
function createScenarioState(scenarioId: string): ScenarioState {
|
||||
const base = structuredClone(capture.scenario);
|
||||
const id = scenarioId || "baseline";
|
||||
|
||||
Reference in New Issue
Block a user