Files
pikasTech-unidesk/scripts/src/hwlab-node-web-observe-analyzer-api-dom-lag-source.ts
T

426 lines
17 KiB
TypeScript

// SPEC: PJ2026-01040111 long-running Workbench observation.
// Responsibility: Analyzer API-to-DOM lag and trace-events page-read source fragment.
export function nodeWebObserveAnalyzerApiDomLagSource(): string {
return String.raw`function buildApiDomLagReport(samples, network) {
const windowMs = 30_000;
const budgetMs = Number.isFinite(Number(alertThresholds.sameOriginApiSlowMs)) ? Number(alertThresholds.sameOriginApiSlowMs) : 10_000;
const sampleRows = (Array.isArray(samples) ? samples : [])
.map((sample) => {
const tsMs = timestampMs(sample?.ts);
return {
sample,
tsMs,
pageKey: samplePageKey(sample),
digest: digestSample(sample),
sessionIds: new Set([sample?.routeSessionId, sample?.activeSessionId].filter(Boolean).map(String)),
traceIds: sampleTraceIds(sample)
};
})
.filter((item) => Number.isFinite(item.tsMs))
.sort((a, b) => a.tsMs - b.tsMs);
const samplesByPage = new Map();
for (const row of sampleRows) {
const rows = samplesByPage.get(row.pageKey) || [];
rows.push(row);
samplesByPage.set(row.pageKey, rows);
}
const naturalApiResponses = (Array.isArray(network) ? network : [])
.filter((item) => item?.observerInitiated !== true && item?.type === "response" && isApiLikePath(urlPath(item?.url)));
const telemetryExcluded = [];
const nonStateRelevant = [];
const stateRelevantResponses = [];
for (const item of naturalApiResponses) {
const event = compactApiDomLagResponseEvent(item);
if (!Number.isFinite(event.tsMs)) {
nonStateRelevant.push(event);
continue;
}
if (isApiDomLagTelemetryPath(event.path)) telemetryExcluded.push(event);
else if (!isApiDomLagStateRelevantPath(event.path)) nonStateRelevant.push(event);
else stateRelevantResponses.push(event);
}
const candidates = [];
for (const event of stateRelevantResponses) {
const pageSamples = samplesByPage.get(event.pageKey) || [];
const before = lastSampleAtOrBefore(pageSamples, event.tsMs, event);
const firstAfter = firstSampleAfter(pageSamples, event.tsMs, event.tsMs + windowMs, event);
const baselineDigest = before?.digest ?? null;
const change = firstSampleAfter(pageSamples, event.tsMs, event.tsMs + windowMs, event, (row) => !baselineDigest || row.digest !== baselineDigest);
candidates.push({
...event,
windowMs,
budgetMs,
firstSampleDeltaMs: firstAfter ? Math.max(0, Math.round(firstAfter.tsMs - event.tsMs)) : null,
domChangeDeltaMs: change ? Math.max(0, Math.round(change.tsMs - event.tsMs)) : null,
overBudget: change ? (change.tsMs - event.tsMs) > budgetMs : false,
domChanged: Boolean(change),
noDomChangeWithinWindow: !change,
beforeSample: compactApiDomLagSample(before),
firstAfterSample: compactApiDomLagSample(firstAfter),
changeSample: compactApiDomLagSample(change),
confidence: apiDomLagConfidence(event.path),
valuesRedacted: true
});
}
const changedDeltas = candidates.map((item) => nullableNumber(item.domChangeDeltaMs)).filter(Number.isFinite).sort((a, b) => a - b);
const groups = groupApiDomLagCandidates(candidates);
const overBudget = candidates.filter((item) => item.overBudget === true);
return {
summary: {
windowMs,
budgetMs,
naturalApiResponseCount: naturalApiResponses.length,
telemetryExcludedCount: telemetryExcluded.length,
nonStateRelevantResponseCount: nonStateRelevant.length,
stateRelevantResponseCount: stateRelevantResponses.length,
candidateCount: candidates.length,
domChangedCount: changedDeltas.length,
noDomChangeWithinWindowCount: candidates.filter((item) => item.noDomChangeWithinWindow === true).length,
lowConfidenceStreamOpenCount: candidates.filter((item) => item.confidence === "low-stream-open-only").length,
overBudgetCount: overBudget.length,
p50DomChangeDeltaMs: percentile(changedDeltas, 50),
p95DomChangeDeltaMs: percentile(changedDeltas, 95),
maxDomChangeDeltaMs: changedDeltas.length > 0 ? Math.max(...changedDeltas) : null,
groupCount: groups.length,
valuesRedacted: true
},
groups,
worstCandidates: candidates
.filter((item) => Number.isFinite(nullableNumber(item.domChangeDeltaMs)))
.sort((a, b) => nullableNumber(b.domChangeDeltaMs) - nullableNumber(a.domChangeDeltaMs))
.slice(0, 20),
recentCandidates: candidates.slice(-40),
telemetryExcluded: telemetryExcluded.slice(0, 20),
nonStateRelevant: nonStateRelevant.slice(0, 20),
valuesRedacted: true
};
}
function compactApiDomLagResponseEvent(item) {
const parsed = parseApiDomLagUrl(item?.url);
const tsMs = timestampMs(item?.ts);
return {
ts: item?.ts ?? null,
tsMs,
pageRole: item?.pageRole ?? null,
pageId: item?.pageId ?? null,
pageKey: String(item?.pageRole || "control") + ":" + String(item?.pageId || "default"),
commandId: item?.commandId ?? null,
method: String(item?.method || "GET").toUpperCase(),
status: Number.isFinite(Number(item?.status)) ? Number(item.status) : null,
path: parsed.path,
rawPath: parsed.rawPath,
queryKeys: parsed.queryKeys,
sessionId: parsed.sessionId,
traceId: parsed.traceId,
urlHash: item?.url ? sha256(item.url) : null,
routeKind: apiDomLagRouteKind(parsed.path),
valuesRedacted: true
};
}
function parseApiDomLagUrl(value) {
try {
const parsed = new URL(String(value || "http://invalid.local/"));
const rawPath = parsed.pathname || "-";
const queryKeys = Array.from(parsed.searchParams.keys()).sort().slice(0, 12);
const sessionId = parsed.searchParams.get("sessionId") || parsed.searchParams.get("includeSessionId") || firstIdInText(parsed.pathname + " " + parsed.search, /\bses_[A-Za-z0-9_-]+\b/u);
const traceId = parsed.searchParams.get("traceId") || firstIdInText(parsed.pathname + " " + parsed.search, /\btrc_[A-Za-z0-9_-]+\b/u);
return {
rawPath,
path: normalizeApiPath(rawPath),
queryKeys,
sessionId,
traceId
};
} catch {
const rawPath = urlPath(value);
return {
rawPath,
path: normalizeApiPath(rawPath),
queryKeys: [],
sessionId: firstIdInText(String(value || ""), /\bses_[A-Za-z0-9_-]+\b/u),
traceId: firstIdInText(String(value || ""), /\btrc_[A-Za-z0-9_-]+\b/u)
};
}
}
function firstIdInText(text, pattern) {
const match = String(text || "").match(pattern);
return match ? match[0] : null;
}
function nullableNumber(value) {
if (value === null || value === undefined || value === "") return NaN;
const numeric = Number(value);
return Number.isFinite(numeric) ? numeric : NaN;
}
function isApiDomLagTelemetryPath(path) {
const value = String(path || "");
return value === "/v1/web-performance" || value === "/v1/health" || value === "/health";
}
function isApiDomLagStateRelevantPath(path) {
const value = String(path || "");
return value.startsWith("/auth/") || value.startsWith("/v1/workbench/") || value === "/v1/agent/chat" || value === "/v1/agent/chat/steer";
}
function apiDomLagRouteKind(path) {
const value = String(path || "");
if (value === "/v1/workbench/events") return "workbench-events-stream";
if (value.startsWith("/v1/workbench/sessions")) return "workbench-sessions";
if (value.startsWith("/v1/workbench/traces")) return "workbench-traces";
if (value.startsWith("/v1/workbench/turns")) return "workbench-turns";
if (value === "/v1/agent/chat" || value === "/v1/agent/chat/steer") return "agent-chat-submit";
if (value.startsWith("/auth/")) return "auth";
return "state-api";
}
function apiDomLagConfidence(path) {
return String(path || "") === "/v1/workbench/events" ? "low-stream-open-only" : "medium-response-to-dom";
}
function sampleTraceIds(sample) {
const ids = new Set();
for (const group of [sample?.messages, sample?.traceRows, sample?.turns, sample?.diagnostics]) {
if (!Array.isArray(group)) continue;
for (const item of group) if (item?.traceId) ids.add(String(item.traceId));
}
return ids;
}
function lastSampleAtOrBefore(rows, tsMs, event) {
let result = null;
for (const row of rows) {
if (row.tsMs > tsMs) break;
if (apiDomLagSampleMatchesEvent(row, event)) result = row;
}
return result;
}
function firstSampleAfter(rows, startMs, endMs, event, predicate = null) {
for (const row of rows) {
if (row.tsMs < startMs) continue;
if (row.tsMs > endMs) break;
if (!apiDomLagSampleMatchesEvent(row, event)) continue;
if (typeof predicate === "function" && !predicate(row)) continue;
return row;
}
return null;
}
function apiDomLagSampleMatchesEvent(row, event) {
if (!row || !event) return false;
if (event.sessionId && !row.sessionIds.has(String(event.sessionId))) return false;
if (event.traceId && row.traceIds.size > 0 && !row.traceIds.has(String(event.traceId))) return false;
return true;
}
function compactApiDomLagSample(row) {
if (!row) return null;
const sample = row.sample || {};
return {
seq: sample.seq ?? null,
ts: sample.ts ?? null,
pageRole: sample.pageRole ?? null,
pageId: sample.pageId ?? null,
routeSessionId: sample.routeSessionId ?? null,
activeSessionId: sample.activeSessionId ?? null,
messageCount: Array.isArray(sample.messages) ? sample.messages.length : null,
traceRowCount: Array.isArray(sample.traceRows) ? sample.traceRows.length : null,
diagnosticCount: Array.isArray(sample.diagnostics) ? sample.diagnostics.length : null,
valuesRedacted: true
};
}
function groupApiDomLagCandidates(candidates) {
const groups = new Map();
for (const item of candidates || []) {
const key = [item.method || "-", item.path || "-", item.status ?? "-", item.confidence || "-"].join(" ");
const group = groups.get(key) || {
method: item.method ?? null,
path: item.path ?? "-",
routeKind: item.routeKind ?? null,
status: item.status ?? null,
confidence: item.confidence ?? null,
count: 0,
domChangedCount: 0,
noDomChangeWithinWindowCount: 0,
overBudgetCount: 0,
firstAt: item.ts ?? null,
lastAt: item.ts ?? null,
deltas: [],
examples: []
};
group.count += 1;
group.firstAt = minIso(group.firstAt, item.ts ?? null);
group.lastAt = maxIso(group.lastAt, item.ts ?? null);
if (item.domChanged === true && Number.isFinite(Number(item.domChangeDeltaMs))) {
group.domChangedCount += 1;
group.deltas.push(Number(item.domChangeDeltaMs));
}
if (item.noDomChangeWithinWindow === true) group.noDomChangeWithinWindowCount += 1;
if (item.overBudget === true) group.overBudgetCount += 1;
if (group.examples.length < 6) {
group.examples.push({
ts: item.ts ?? null,
sessionId: item.sessionId ?? null,
traceId: item.traceId ?? null,
domChangeDeltaMs: item.domChangeDeltaMs ?? null,
firstSampleDeltaMs: item.firstSampleDeltaMs ?? null,
changeSeq: item.changeSample?.seq ?? null,
beforeSeq: item.beforeSample?.seq ?? null,
valuesRedacted: true
});
}
groups.set(key, group);
}
return Array.from(groups.values())
.map((item) => {
const deltas = item.deltas.slice().sort((a, b) => a - b);
return {
method: item.method,
path: item.path,
routeKind: item.routeKind,
status: item.status,
confidence: item.confidence,
count: item.count,
domChangedCount: item.domChangedCount,
noDomChangeWithinWindowCount: item.noDomChangeWithinWindowCount,
overBudgetCount: item.overBudgetCount,
p50DomChangeDeltaMs: percentile(deltas, 50),
p95DomChangeDeltaMs: percentile(deltas, 95),
maxDomChangeDeltaMs: deltas.length > 0 ? Math.max(...deltas) : null,
firstAt: item.firstAt,
lastAt: item.lastAt,
examples: item.examples,
valuesRedacted: true
};
})
.sort((a, b) => Number(b.maxDomChangeDeltaMs ?? -1) - Number(a.maxDomChangeDeltaMs ?? -1) || b.count - a.count || String(a.path).localeCompare(String(b.path)));
}
function detectTraceEventsPageReadIssues(network) {
const events = (Array.isArray(network) ? network : [])
.filter((item) => item?.observerInitiated !== true && (item?.type === "response" || item?.type === "requestfailed"))
.map(compactTraceEventsPageReadEvent)
.filter((item) => item !== null);
const http404 = events.filter((item) => item.type === "response" && Number(item.status) === 404);
const httpErrors = events.filter((item) => item.type === "response" && Number(item.status) >= 400 && Number(item.status) !== 404);
const requestFailed = events.filter((item) => item.type === "requestfailed");
return {
events,
http404,
httpErrors,
requestFailed,
summary: traceEventsPageReadIssueSummary(events),
valuesRedacted: true
};
}
function compactTraceEventsPageReadEvent(item) {
const parsed = parseTraceEventsPageReadUrl(item?.url);
if (!parsed.match) return null;
const failureText = item?.failureKind ?? item?.failure ?? item?.errorText ?? null;
return {
ts: item?.ts ?? null,
pageRole: item?.pageRole ?? null,
pageId: item?.pageId ?? null,
commandId: item?.commandId ?? null,
method: String(item?.method || "GET").toUpperCase(),
type: item?.type ?? null,
status: Number.isFinite(Number(item?.status)) ? Number(item.status) : null,
path: "/v1/workbench/traces/:traceId/events",
rawPath: parsed.rawPath,
traceId: parsed.traceId,
afterProjectedSeq: parsed.afterProjectedSeq,
sinceSeq: parsed.sinceSeq,
limit: parsed.limit,
tail: parsed.tail,
queryKeys: parsed.queryKeys,
failureKind: failureText ? limitText(String(failureText), 120) : null,
urlHash: item?.url ? sha256(item.url) : null,
valuesRedacted: true
};
}
function parseTraceEventsPageReadUrl(value) {
const fallback = {
match: false,
rawPath: urlPath(value),
traceId: firstIdInText(String(value || ""), /\btrc_[A-Za-z0-9_-]+\b/u),
afterProjectedSeq: null,
sinceSeq: null,
limit: null,
tail: null,
queryKeys: [],
};
try {
const parsed = new URL(String(value || ""), "http://invalid.local/");
const rawPath = parsed.pathname || "-";
const match = rawPath.match(/^\/v1\/workbench\/traces\/([^/]+)\/events$/u);
return {
match: Boolean(match),
rawPath,
traceId: match ? decodeURIComponent(match[1]) : fallback.traceId,
afterProjectedSeq: numericSearchParam(parsed.searchParams, "afterProjectedSeq"),
sinceSeq: numericSearchParam(parsed.searchParams, "sinceSeq") ?? numericSearchParam(parsed.searchParams, "afterSeq"),
limit: numericSearchParam(parsed.searchParams, "limit"),
tail: numericSearchParam(parsed.searchParams, "tail"),
queryKeys: Array.from(parsed.searchParams.keys()).sort().slice(0, 12),
};
} catch {
return fallback;
}
}
function numericSearchParam(searchParams, key) {
const raw = searchParams?.get?.(key);
if (raw === null || raw === undefined || raw === "") return null;
const parsed = Number(raw);
return Number.isFinite(parsed) ? parsed : null;
}
function traceEventsPageReadIssueSummary(events) {
const items = Array.isArray(events) ? events : [];
const statuses = uniqueSorted(items.map((item) => item.status).filter((item) => item !== null && item !== undefined).map(String));
const traceIds = uniqueSorted(items.map((item) => item.traceId).filter(Boolean).map(String)).slice(0, 8);
const afterProjectedSeqs = uniqueSorted(items.map((item) => item.afterProjectedSeq).filter((item) => item !== null && item !== undefined).map(String)).slice(0, 8);
const sinceSeqs = uniqueSorted(items.map((item) => item.sinceSeq).filter((item) => item !== null && item !== undefined).map(String)).slice(0, 8);
const failureKinds = uniqueSorted(items.map((item) => item.failureKind).filter(Boolean).map(String)).slice(0, 6);
return {
eventCount: items.length,
responseErrorCount: items.filter((item) => item.type === "response" && Number(item.status) >= 400).length,
http404Count: items.filter((item) => item.type === "response" && Number(item.status) === 404).length,
requestFailedCount: items.filter((item) => item.type === "requestfailed").length,
statuses,
traceIds,
afterProjectedSeqs,
sinceSeqs,
failureKinds,
firstAt: items.reduce((value, item) => minIso(value, item.ts ?? null), null),
lastAt: items.reduce((value, item) => maxIso(value, item.ts ?? null), null),
rootCauseVisibility: "browser network rows identify trace-events page-read path; OTel trace_events_read should confirm backend paging fields",
valuesRedacted: true
};
}
function uniqueSorted(values) {
return Array.from(new Set((values || []).filter((item) => item !== null && item !== undefined).map(String))).sort();
}
function compactApiDomLagForOutput(report) {
if (!report || typeof report !== "object") return null;
return {
summary: report.summary ?? null,
groups: Array.isArray(report.groups) ? report.groups.slice(0, 8) : [],
worstCandidates: Array.isArray(report.worstCandidates) ? report.worstCandidates.slice(0, 8) : [],
recentCandidates: Array.isArray(report.recentCandidates) ? report.recentCandidates.slice(-8) : [],
valuesRedacted: true
};
}
`;
}