// SPEC: pikasTech/unidesk#1436 WEB-PROBE performance analyzer. // Responsibility: Source string for offline frontend long-task/LoAF/profile hotspot analysis. export function nodeWebObserveAnalyzerPerformanceSource(): string { return String.raw` var frontendPerformanceSourceMapper = null; async function buildFrontendPerformanceReport(rows, artifacts, samples, network) { const sourceRows = Array.isArray(rows) ? rows : []; const sourceSamples = Array.isArray(samples) ? samples : []; const sourceNetwork = Array.isArray(network) ? network : []; const sourceMapState = await buildFrontendPerformanceSourceMapState(artifacts); frontendPerformanceSourceMapper = createFrontendPerformanceSourceMapper(sourceMapState.maps); const events = []; const drainErrors = []; const captures = []; const scriptGroups = new Map(); const profileFunctionGroups = new Map(); const profileStackGroups = new Map(); for (const row of sourceRows) { if (!row || typeof row !== "object") continue; if (row.type === "performance-drain-error") { drainErrors.push(compactPerformanceDrainError(row)); continue; } if (row.type === "performance-capture-completed") { const capture = compactPerformanceCapture(row); captures.push(capture); for (const fn of Array.isArray(row.summary?.topFunctions) ? row.summary.topFunctions : []) mergeProfileFunction(profileFunctionGroups, fn, capture); for (const stack of Array.isArray(row.summary?.topStacks) ? row.summary.topStacks : []) mergeProfileStack(profileStackGroups, stack, capture); continue; } if (row.type !== "performance-event") continue; const perf = row.performance && typeof row.performance === "object" ? row.performance : {}; const event = compactPerformanceEventRow(row, perf); if (!event.kind) continue; events.push(event); for (const script of Array.isArray(perf.scripts) ? perf.scripts : []) mergeLoafScript(scriptGroups, script, event); } await attachCpuProfileTimelines(captures); const longTasks = events.filter((item) => item.kind === "longtask"); const loafs = events.filter((item) => item.kind === "long-animation-frame"); const gaps = events.filter((item) => item.kind === "event-loop-gap"); const scriptHotspots = Array.from(scriptGroups.values()) .map(finalizeScriptHotspot) .sort((left, right) => Number(right.totalDurationMs ?? 0) - Number(left.totalDurationMs ?? 0) || Number(right.count ?? 0) - Number(left.count ?? 0)) .slice(0, 40); const profileHotspots = Array.from(profileFunctionGroups.values()) .map(finalizeProfileFunctionHotspot) .sort((left, right) => Number(right.selfTimeMs ?? 0) - Number(left.selfTimeMs ?? 0) || Number(right.totalTimeMs ?? 0) - Number(left.totalTimeMs ?? 0)) .slice(0, 40); const profileStacks = Array.from(profileStackGroups.values()) .map(finalizeProfileStackHotspot) .sort((left, right) => Number(right.selfTimeMs ?? 0) - Number(left.selfTimeMs ?? 0)) .slice(0, 30); const performanceWindows = buildFrontendPerformanceWindows({ events, captures, profileHotspots, profileStacks, scriptHotspots, samples: sourceSamples, network: sourceNetwork }); const sourceAttribution = buildFrontendSourceAttribution({ scriptHotspots, profileHotspots, profileStacks, sourceMapState }); const attribution = frontendPerformanceAttributionStatus({ captures, profileHotspots, profileStacks, scriptHotspots, longTasks, loafs, gaps, performanceWindows }); return { summary: { rowCount: sourceRows.length, eventCount: events.length, longTaskCount: longTasks.length, longAnimationFrameCount: loafs.length, eventLoopGapCount: gaps.length, drainErrorCount: drainErrors.length, captureCount: captures.length, profileHotspotCount: profileHotspots.length, scriptHotspotCount: scriptHotspots.length, longTaskRedMs: alertThresholds.longTaskRedMs, longAnimationFrameRedMs: alertThresholds.longAnimationFrameRedMs, eventLoopGapRedMs: alertThresholds.eventLoopGapRedMs, maxLongTaskMs: frontendPerformanceMaxNumber(longTasks, (item) => item.durationMs), maxLongAnimationFrameMs: frontendPerformanceMaxNumber(loafs, (item) => item.durationMs), maxEventLoopGapMs: frontendPerformanceMaxNumber(gaps, (item) => item.durationMs), cpuProfileStatus: attribution.cpuProfileStatus, attributionMode: attribution.attributionMode, noCpuProfile: attribution.noCpuProfile, loafOnly: attribution.loafOnly, cpuProfileHotspotEvidence: attribution.cpuProfileHotspotEvidence, evidenceNote: attribution.evidenceNote, windowCorrelationCount: performanceWindows.length, cpuProfileWindowCoveredCount: performanceWindows.filter((item) => item.cpuProfileCoverageStatus === "covered").length, cpuProfileWindowOverlappedCount: performanceWindows.filter((item) => item.cpuProfileCoverageStatus === "overlapped").length, cpuProfileWindowMissedCount: performanceWindows.filter((item) => item.cpuProfileCoverageStatus === "missed").length, cpuProfileWindowMissingCount: performanceWindows.filter((item) => item.cpuProfileCoverageStatus === "no-cpu-profile").length, sourceMapStatus: sourceAttribution.sourceMapStatus, captureArtifacts: performanceCaptureArtifacts(artifacts), valuesRedacted: true, }, longTasks: longTasks.sort(descDuration).slice(0, 40), longAnimationFrames: loafs.sort(descDuration).slice(0, 40), eventLoopGaps: gaps.sort(descDuration).slice(0, 40), scriptHotspots, profileHotspots, profileStacks, performanceWindows, sourceAttribution, captures: captures.slice(-20), drainErrors: drainErrors.slice(-20), valuesRedacted: true, }; } function buildFrontendPerformanceFindings(report) { const findings = []; const summary = report?.summary || {}; const severeLongTasks = (report?.longTasks || []).filter((item) => Number(item.durationMs ?? 0) >= alertThresholds.longTaskRedMs); if (severeLongTasks.length > 0) findings.push({ id: "frontend-longtask-red", severity: "red", summary: "PerformanceObserver captured long tasks over YAML budget; use LoAF scripts or CPU profile hotspots for function attribution", count: severeLongTasks.length, budgetMs: alertThresholds.longTaskRedMs, maxDurationMs: summary.maxLongTaskMs, events: severeLongTasks.slice(0, 20), topScripts: (report?.scriptHotspots || []).slice(0, 8), topProfileFunctions: (report?.profileHotspots || []).slice(0, 8), valuesRedacted: true, }); const severeLoafs = (report?.longAnimationFrames || []).filter((item) => Number(item.durationMs ?? 0) >= alertThresholds.longAnimationFrameRedMs || Number(item.blockingDurationMs ?? 0) >= alertThresholds.longAnimationFrameRedMs); if (severeLoafs.length > 0) findings.push({ id: "frontend-long-animation-frame-red", severity: "red", summary: "Long Animation Frame entries exceeded YAML budget and include script attribution when Chromium exposes it", count: severeLoafs.length, budgetMs: alertThresholds.longAnimationFrameRedMs, maxDurationMs: summary.maxLongAnimationFrameMs, events: severeLoafs.slice(0, 20), topScripts: (report?.scriptHotspots || []).slice(0, 12), valuesRedacted: true, }); const severeGaps = (report?.eventLoopGaps || []).filter((item) => Number(item.durationMs ?? 0) >= alertThresholds.eventLoopGapRedMs); if (severeGaps.length > 0) findings.push({ id: "frontend-event-loop-gap-red", severity: "red", summary: "web-probe page-side event loop gap probe observed main-thread stalls over YAML budget", count: severeGaps.length, budgetMs: alertThresholds.eventLoopGapRedMs, maxDurationMs: summary.maxEventLoopGapMs, events: severeGaps.slice(0, 20), valuesRedacted: true, }); if ((report?.profileHotspots || []).length > 0) findings.push({ id: "frontend-cpu-profile-hotspots", severity: severeLongTasks.length > 0 || severeLoafs.length > 0 || severeGaps.length > 0 ? "red" : "info", summary: "CDP CPU profile capture produced function-level hotspots for offline attribution", count: report.profileHotspots.length, captures: (report?.captures || []).slice(-8), topFunctions: report.profileHotspots.slice(0, 12), topStacks: (report?.profileStacks || []).slice(0, 8), valuesRedacted: true, }); if (summary.noCpuProfile === true && (severeLongTasks.length > 0 || severeLoafs.length > 0 || severeGaps.length > 0)) findings.push({ id: "frontend-performance-loaf-only-no-cpu-profile", severity: "amber", summary: "frontend performance evidence is LoAF/LongTask/event-loop only because no completed CPU profile capture is present; do not cite CPU profile hotspots for this run", count: 1, attributionMode: summary.attributionMode || "loaf-only-no-cpu-profile", cpuProfileStatus: summary.cpuProfileStatus || "missing", captureCount: summary.captureCount ?? 0, windowCorrelations: (report?.performanceWindows || []).slice(0, 8), longTaskCount: summary.longTaskCount ?? 0, longAnimationFrameCount: summary.longAnimationFrameCount ?? 0, eventLoopGapCount: summary.eventLoopGapCount ?? 0, topScripts: (report?.scriptHotspots || []).slice(0, 12), nextAction: "Run an explicit performanceCapture command and re-run observe analyze before making CPU-profile hotspot claims; existing LoAF scripts remain valid browser-side attribution.", valuesRedacted: true, }); if ((report?.drainErrors || []).length > 0) findings.push({ id: "frontend-performance-probe-drain-errors", severity: "amber", summary: "page-side performance probe had drain errors; improve probe visibility before relying on absence of long-task events", count: report.drainErrors.length, errors: report.drainErrors.slice(0, 12), valuesRedacted: true, }); return findings; } function frontendPerformanceAttributionStatus(input) { const captureCount = Array.isArray(input?.captures) ? input.captures.length : 0; const profileHotspotCount = Array.isArray(input?.profileHotspots) ? input.profileHotspots.length : 0; const profileStackCount = Array.isArray(input?.profileStacks) ? input.profileStacks.length : 0; const scriptHotspotCount = Array.isArray(input?.scriptHotspots) ? input.scriptHotspots.length : 0; const eventCount = (Array.isArray(input?.longTasks) ? input.longTasks.length : 0) + (Array.isArray(input?.loafs) ? input.loafs.length : 0) + (Array.isArray(input?.gaps) ? input.gaps.length : 0); const hasCpuProfile = captureCount > 0; const hasCpuProfileHotspots = profileHotspotCount > 0 || profileStackCount > 0; const windowRows = Array.isArray(input?.performanceWindows) ? input.performanceWindows : []; const coveredWindowCount = windowRows.filter((item) => item?.cpuProfileCoverageStatus === "covered" || item?.cpuProfileCoverageStatus === "overlapped").length; const missedWindowCount = windowRows.filter((item) => item?.cpuProfileCoverageStatus === "missed").length; if (hasCpuProfile) { return { cpuProfileStatus: hasCpuProfileHotspots ? (coveredWindowCount > 0 ? "captured-with-window-hotspots" : "captured-hotspots-outside-performance-window") : "captured-no-hotspots", attributionMode: missedWindowCount > 0 && coveredWindowCount === 0 ? "cpu-profile-missed-performance-window" : "cpu-profile-and-performance-observer", noCpuProfile: false, loafOnly: false, cpuProfileHotspotEvidence: hasCpuProfileHotspots, evidenceNote: hasCpuProfileHotspots ? coveredWindowCount > 0 ? "completed performanceCapture artifacts overlap severe frontend performance windows; attached CPU hotspots are same-capture candidates" : "completed performanceCapture artifacts produced CPU profile hotspots, but none overlap the severe frontend performance windows" : "completed performanceCapture artifacts exist, but no CPU profile hotspots were extracted", valuesRedacted: true, }; } return { cpuProfileStatus: "missing", attributionMode: eventCount > 0 || scriptHotspotCount > 0 ? "loaf-only-no-cpu-profile" : "no-frontend-performance-evidence", noCpuProfile: true, loafOnly: eventCount > 0 || scriptHotspotCount > 0, cpuProfileHotspotEvidence: false, evidenceNote: eventCount > 0 || scriptHotspotCount > 0 ? "LongTask/LoAF/event-loop evidence is present, but no completed performanceCapture CPU profile exists" : "no frontend performance events or completed performanceCapture CPU profile exist", valuesRedacted: true, }; } function compactPerformanceEventRow(row, perf) { const window = performanceEventWindow(row, perf); return { ts: row.ts ?? null, seq: row.seq ?? null, sampleSeq: row.sampleSeq ?? null, sampleGroupSeq: row.sampleGroupSeq ?? null, commandId: row.commandId ?? null, pageRole: row.pageRole ?? null, pageId: row.pageId ?? null, pageEpoch: numberOrNull(row.pageEpoch), kind: perf.kind ?? null, name: limitText(perf.name ?? "", 120), durationMs: numberOrNull(perf.duration), blockingDurationMs: numberOrNull(perf.blockingDuration), startTime: numberOrNull(perf.startTime), thresholdMs: numberOrNull(perf.thresholdMs), timeOrigin: numberOrNull(perf.timeOrigin), observedAt: numberOrNull(perf.observedAt), startEpochMs: window.startEpochMs, endEpochMs: window.endEpochMs, startAt: window.startAt, endAt: window.endAt, windowSource: window.source, path: limitText(perf.path ?? "", 160), url: safeReportUrl(perf.url), scriptCount: Array.isArray(perf.scripts) ? perf.scripts.length : 0, attributionCount: Array.isArray(perf.attribution) ? perf.attribution.length : 0, scripts: (Array.isArray(perf.scripts) ? perf.scripts : []).slice(0, 8).map(compactLoafScript), valuesRedacted: true, }; } function compactPerformanceDrainError(row) { return { ts: row?.ts ?? null, sampleSeq: row?.sampleSeq ?? null, pageRole: row?.pageRole ?? null, pageId: row?.pageId ?? null, reason: row?.drain?.reason ?? row?.reason ?? null, message: limitText(row?.drain?.error?.message ?? row?.error?.message ?? "", 200), valuesRedacted: true, }; } function compactPerformanceCapture(row) { const artifact = row.artifact && typeof row.artifact === "object" ? row.artifact : {}; const summary = row.summary && typeof row.summary === "object" ? row.summary : {}; const durationMs = numberOrNull(artifact.durationMs ?? summary.durationMs); const endEpochMs = performanceTimestampMs(row.ts); const startEpochMs = Number.isFinite(endEpochMs) && Number.isFinite(durationMs) ? endEpochMs - Number(durationMs) : null; return { ts: row.ts ?? null, commandId: row.commandId ?? null, captureId: row.captureId ?? artifact.captureId ?? null, pageRole: row.pageRole ?? artifact.pageRole ?? null, pageId: row.pageId ?? artifact.pageId ?? null, durationMs, startEpochMs, endEpochMs, startAt: startEpochMs === null ? null : new Date(startEpochMs).toISOString(), endAt: endEpochMs === null ? null : new Date(endEpochMs).toISOString(), windowSource: startEpochMs === null ? "missing-capture-window" : "completed-event-ts-minus-artifact-duration", profileTotalTimeMs: numberOrNull(summary.totalTimeMs), profileSampleCount: numberOrNull(summary.sampleCount), profileTimelineStatus: "not-loaded", profileTimelineSampleCount: null, profileTimelineTotalTimeMs: null, profileTimelineStartAt: null, profileTimelineEndAt: null, profileTimeline: null, path: artifact.path ?? null, summaryPath: artifact.summaryPath ?? null, sha256: artifact.sha256 ?? null, summarySha256: artifact.summarySha256 ?? null, valuesRedacted: true, }; } function compactLoafScript(script) { const value = script && typeof script === "object" ? script : {}; const source = sourceAttributionFor(value.sourceURL, value.sourceFunctionName, value.lineNumber, value.columnNumber, value.sourceCharPosition); return { invoker: limitText(value.invoker ?? "", 160), invokerType: limitText(value.invokerType ?? "", 80), sourceURL: safeReportUrl(value.sourceURL), sourceFunctionName: limitText(value.sourceFunctionName ?? "", 160), sourceFile: source.sourceFile, sourceLine: source.sourceLine, sourceColumn: source.sourceColumn, sourceCharPosition: numberOrNull(value.sourceCharPosition), sourceMapStatus: source.sourceMapStatus, sourceAttributionMode: source.sourceAttributionMode, lineNumber: numberOrNull(value.lineNumber), columnNumber: numberOrNull(value.columnNumber), startTime: numberOrNull(value.startTime), executionStart: numberOrNull(value.executionStart), durationMs: numberOrNull(value.duration), forcedStyleAndLayoutDurationMs: numberOrNull(value.forcedStyleAndLayoutDuration), pauseDurationMs: numberOrNull(value.pauseDuration), valuesRedacted: true, }; } function mergeLoafScript(groups, script, event) { const compact = compactLoafScript(script); const key = loafScriptKey(compact); const group = groups.get(key) || { ...compact, key, count: 0, totalDurationMs: 0, maxDurationMs: 0, firstAt: event.ts, lastAt: event.ts, examples: [], valuesRedacted: true }; const duration = Number(compact.durationMs || 0); group.count += 1; group.totalDurationMs += duration; group.maxDurationMs = Math.max(group.maxDurationMs, duration); group.lastAt = event.ts; if (group.examples.length < 8) group.examples.push({ ts: event.ts, startAt: event.startAt, endAt: event.endAt, sampleSeq: event.sampleSeq, durationMs: event.durationMs, scriptDurationMs: compact.durationMs, pageRole: event.pageRole, sourceCharPosition: compact.sourceCharPosition, valuesRedacted: true }); groups.set(key, group); } function loafScriptKey(script) { if (!script || typeof script !== "object") return ""; return [script.sourceFunctionName || script.invoker || "(anonymous)", script.sourceURL || "", script.lineNumber ?? "", script.columnNumber ?? ""].join("@"); } function finalizeScriptHotspot(group) { return { ...group, totalDurationMs: roundMs(group.totalDurationMs), maxDurationMs: roundMs(group.maxDurationMs), valuesRedacted: true, }; } function mergeProfileFunction(groups, fn, capture) { const value = fn && typeof fn === "object" ? fn : {}; const source = sourceAttributionFor(value.url, value.functionName, value.lineNumber, value.columnNumber, null); const key = [value.functionName || "(anonymous)", value.url || value.scriptId || "", value.lineNumber ?? "", value.columnNumber ?? ""].join("@"); const group = groups.get(key) || { functionName: value.functionName || "(anonymous)", url: safeReportUrl(value.url), scriptId: value.scriptId ?? null, sourceFile: source.sourceFile, sourceLine: source.sourceLine, sourceColumn: source.sourceColumn, sourceMapStatus: source.sourceMapStatus, sourceAttributionMode: source.sourceAttributionMode, lineNumber: numberOrNull(value.lineNumber), columnNumber: numberOrNull(value.columnNumber), key, captureCount: 0, sampleCount: 0, selfTimeMs: 0, totalTimeMs: 0, maxSelfTimeMs: 0, captures: [], valuesRedacted: true }; const selfTime = Number(value.selfTimeMs || 0); const totalTime = Number(value.totalTimeMs || 0); group.captureCount += 1; group.sampleCount += Number(value.sampleCount || 0); group.selfTimeMs += selfTime; group.totalTimeMs += totalTime; group.maxSelfTimeMs = Math.max(group.maxSelfTimeMs, selfTime); if (group.captures.length < 8) group.captures.push({ captureId: capture.captureId, commandId: capture.commandId, selfTimeMs: roundMs(selfTime), totalTimeMs: roundMs(totalTime), valuesRedacted: true }); groups.set(key, group); } function finalizeProfileFunctionHotspot(group) { return { ...group, selfTimeMs: roundMs(group.selfTimeMs), totalTimeMs: roundMs(group.totalTimeMs), maxSelfTimeMs: roundMs(group.maxSelfTimeMs), valuesRedacted: true, }; } function mergeProfileStack(groups, stack, capture) { const value = stack && typeof stack === "object" ? stack : {}; const key = String(value.key || "").slice(0, 800); if (!key) return; const frames = Array.isArray(value.frames) ? value.frames.slice(-10).map(annotateProfileFrameSource) : []; const group = groups.get(key) || { key, sampleCount: 0, selfTimeMs: 0, maxSelfTimeMs: 0, leaf: annotateProfileFrameSource(value.leaf), frames, captures: [], valuesRedacted: true }; const selfTime = Number(value.selfTimeMs || 0); group.sampleCount += Number(value.sampleCount || 0); group.selfTimeMs += selfTime; group.maxSelfTimeMs = Math.max(group.maxSelfTimeMs, selfTime); if (group.captures.length < 6) group.captures.push({ captureId: capture.captureId, commandId: capture.commandId, selfTimeMs: roundMs(selfTime), valuesRedacted: true }); groups.set(key, group); } function finalizeProfileStackHotspot(group) { return { ...group, selfTimeMs: roundMs(group.selfTimeMs), maxSelfTimeMs: roundMs(group.maxSelfTimeMs), valuesRedacted: true, }; } function performanceCaptureArtifacts(artifacts) { return (Array.isArray(artifacts) ? artifacts : []) .filter((item) => item && item.kind === "performance-cpu-profile") .slice(-20) .map((item) => ({ ts: item.ts ?? null, captureId: item.captureId ?? null, commandId: item.commandId ?? null, path: item.path ?? null, summaryPath: item.summaryPath ?? null, sha256: item.sha256 ?? null, summarySha256: item.summarySha256 ?? null, valuesRedacted: true })); } async function attachCpuProfileTimelines(captures) { for (const capture of Array.isArray(captures) ? captures : []) { const profilePath = typeof capture?.path === "string" && capture.path ? capture.path : null; if (!profilePath) { capture.profileTimelineStatus = "missing-profile-path"; continue; } const profile = await readJson(profilePath); const timeline = buildCpuProfileSampleTimeline(profile, capture); capture.profileTimelineStatus = timeline.status; capture.profileTimelineSampleCount = timeline.sampleCount; capture.profileTimelineTotalTimeMs = timeline.totalTimeMs; capture.profileTimelineStartAt = timeline.startAt; capture.profileTimelineEndAt = timeline.endAt; capture.profileTimeline = timeline.samples; } } function buildCpuProfileSampleTimeline(profile, capture) { if (!profile || typeof profile !== "object") return emptyCpuProfileTimeline("profile-read-failed"); const nodes = Array.isArray(profile.nodes) ? profile.nodes : []; const sampleIds = Array.isArray(profile.samples) ? profile.samples : []; if (nodes.length === 0 || sampleIds.length === 0) return emptyCpuProfileTimeline("profile-has-no-samples"); const byId = new Map(); const parentById = new Map(); for (const node of nodes) { const id = Number(node?.id); if (!Number.isFinite(id)) continue; byId.set(id, node); for (const childId of Array.isArray(node?.children) ? node.children : []) { const child = Number(childId); if (Number.isFinite(child)) parentById.set(child, id); } } const profileStart = Number(profile.startTime); const profileEnd = Number(profile.endTime); const profileDurationMs = Number.isFinite(profileStart) && Number.isFinite(profileEnd) && profileEnd > profileStart ? (profileEnd - profileStart) / 1000 : null; const captureStart = Number(capture?.startEpochMs); const captureEnd = Number(capture?.endEpochMs); const baseEpochMs = Number.isFinite(captureStart) ? captureStart : Number.isFinite(captureEnd) && Number.isFinite(profileDurationMs) ? captureEnd - Number(profileDurationMs) : null; if (!Number.isFinite(baseEpochMs)) return emptyCpuProfileTimeline("missing-capture-timebase"); const deltas = Array.isArray(profile.timeDeltas) ? profile.timeDeltas : []; const fallbackDeltaMs = Number.isFinite(profileDurationMs) && sampleIds.length > 0 ? Number(profileDurationMs) / sampleIds.length : 0; const samples = []; let cursor = Number(baseEpochMs); for (let index = 0; index < sampleIds.length; index += 1) { const nodeId = Number(sampleIds[index]); const rawDelta = Number(deltas[index]); const durationMs = Number.isFinite(rawDelta) && rawDelta > 0 ? rawDelta / 1000 : fallbackDeltaMs; const startEpochMs = cursor; const endEpochMs = cursor + (Number.isFinite(durationMs) && durationMs > 0 ? durationMs : 0); cursor = endEpochMs; const frames = cpuProfileFramesForNode(nodeId, byId, parentById).map(annotateProfileFrameSource).filter(Boolean); samples.push({ index, nodeId: Number.isFinite(nodeId) ? nodeId : null, startEpochMs: roundMs(startEpochMs), endEpochMs: roundMs(endEpochMs), durationMs: roundMs(endEpochMs - startEpochMs), leaf: frames[frames.length - 1] || null, frames, valuesRedacted: true, }); } const totalTimeMs = samples.reduce((sum, sample) => sum + Number(sample.durationMs || 0), 0); const startAt = samples.length > 0 ? new Date(Number(samples[0].startEpochMs)).toISOString() : null; const endAt = samples.length > 0 ? new Date(Number(samples[samples.length - 1].endEpochMs)).toISOString() : null; return { status: "loaded", sampleCount: samples.length, totalTimeMs: roundMs(totalTimeMs), startAt, endAt, samples, valuesRedacted: true, }; } function emptyCpuProfileTimeline(status) { return { status, sampleCount: 0, totalTimeMs: null, startAt: null, endAt: null, samples: [], valuesRedacted: true }; } function cpuProfileFramesForNode(nodeId, byId, parentById) { const frames = []; const seen = new Set(); let current = nodeId; while (Number.isFinite(current) && byId.has(current) && !seen.has(current)) { seen.add(current); const node = byId.get(current); const callFrame = node?.callFrame && typeof node.callFrame === "object" ? node.callFrame : {}; frames.push(compactCpuProfileTimelineFrame(callFrame, node)); current = parentById.get(current); } return frames.reverse(); } function compactCpuProfileTimelineFrame(callFrame, node) { return { functionName: limitText(callFrame?.functionName || "(anonymous)", 160), url: safeReportUrl(callFrame?.url || ""), scriptId: callFrame?.scriptId ?? node?.callFrame?.scriptId ?? null, lineNumber: numberOrNull(callFrame?.lineNumber), columnNumber: numberOrNull(callFrame?.columnNumber), valuesRedacted: true, }; } function sameWindowProfileEvidence(event, capture, input) { const timeline = Array.isArray(capture?.profileTimeline) ? capture.profileTimeline : []; const eventStart = Number(event?.startEpochMs); const eventEnd = Number(event?.endEpochMs); if (!capture) return null; if (timeline.length === 0 || ![eventStart, eventEnd].every(Number.isFinite)) { return { captureId: capture.captureId ?? null, commandId: capture.commandId ?? null, evidenceScope: "same-capture-aggregate-context", timelineStatus: capture.profileTimelineStatus || "missing-profile-timeline", topFunctions: (Array.isArray(input?.profileHotspots) ? input.profileHotspots : []).slice(0, 5), topStacks: (Array.isArray(input?.profileStacks) ? input.profileStacks : []).slice(0, 3), note: "CPU profile sample timeline is unavailable; hotspot rows are capture-level context, not same-window evidence.", valuesRedacted: true, }; } const functionGroups = new Map(); const stackGroups = new Map(); let sampleCount = 0; let totalTimeMs = 0; for (const sample of timeline) { const start = Number(sample?.startEpochMs); const end = Number(sample?.endEpochMs); if (![start, end].every(Number.isFinite)) continue; const overlapMs = Math.max(0, Math.min(end, eventEnd) - Math.max(start, eventStart)); if (overlapMs <= 0) continue; sampleCount += 1; totalTimeMs += overlapMs; const frames = Array.isArray(sample?.frames) ? sample.frames : []; const leaf = sample?.leaf || frames[frames.length - 1] || null; mergeWindowProfileFunction(functionGroups, leaf, overlapMs, overlapMs); for (const frame of frames) mergeWindowProfileFunction(functionGroups, frame, 0, overlapMs); mergeWindowProfileStack(stackGroups, frames, leaf, overlapMs); } const topFunctions = Array.from(functionGroups.values()) .map(finalizeWindowProfileFunction) .sort((left, right) => Number(right.selfTimeMs ?? 0) - Number(left.selfTimeMs ?? 0) || Number(right.totalTimeMs ?? 0) - Number(left.totalTimeMs ?? 0)) .slice(0, 8); const topStacks = Array.from(stackGroups.values()) .map(finalizeWindowProfileStack) .sort((left, right) => Number(right.selfTimeMs ?? 0) - Number(left.selfTimeMs ?? 0)) .slice(0, 5); return { captureId: capture.captureId ?? null, commandId: capture.commandId ?? null, evidenceScope: "same-window-cpu-samples", timelineStatus: sampleCount > 0 ? "window-samples" : "no-window-samples", sampleCount, totalTimeMs: roundMs(totalTimeMs), windowStartAt: Number.isFinite(eventStart) ? new Date(eventStart).toISOString() : null, windowEndAt: Number.isFinite(eventEnd) ? new Date(eventEnd).toISOString() : null, topFunctions, topStacks, valuesRedacted: true, }; } function mergeWindowProfileFunction(groups, frame, selfTimeMs, totalTimeMs) { if (!frame || typeof frame !== "object") return; const source = frame.sourceMapStatus ? { sourceFile: frame.sourceFile ?? sourceFileFromUrl(frame.url), sourceLine: frame.sourceLine ?? numberOrNull(frame.lineNumber), sourceColumn: frame.sourceColumn ?? numberOrNull(frame.columnNumber), sourceMapStatus: frame.sourceMapStatus, sourceAttributionMode: frame.sourceAttributionMode ?? "browser-raw-url-line-column", } : sourceAttributionFor(frame.url, frame.functionName, frame.lineNumber, frame.columnNumber, null); const key = [frame.functionName || "(anonymous)", frame.url || frame.scriptId || "", frame.lineNumber ?? "", frame.columnNumber ?? ""].join("@"); const group = groups.get(key) || { functionName: limitText(frame.functionName || "(anonymous)", 160), url: safeReportUrl(frame.url || ""), scriptId: frame.scriptId ?? null, sourceFile: source.sourceFile, sourceLine: source.sourceLine, sourceColumn: source.sourceColumn, sourceMapStatus: source.sourceMapStatus, sourceAttributionMode: source.sourceAttributionMode, lineNumber: numberOrNull(frame.lineNumber), columnNumber: numberOrNull(frame.columnNumber), sampleCount: 0, selfTimeMs: 0, totalTimeMs: 0, valuesRedacted: true, }; group.sampleCount += selfTimeMs > 0 ? 1 : 0; group.selfTimeMs += Number(selfTimeMs || 0); group.totalTimeMs += Number(totalTimeMs || 0); groups.set(key, group); } function finalizeWindowProfileFunction(group) { return { ...group, selfTimeMs: roundMs(group.selfTimeMs), totalTimeMs: roundMs(group.totalTimeMs), valuesRedacted: true }; } function mergeWindowProfileStack(groups, frames, leaf, selfTimeMs) { const stackFrames = Array.isArray(frames) ? frames.filter(Boolean) : []; if (stackFrames.length === 0) return; const key = stackFrames.map((frame) => [frame.functionName || "(anonymous)", frame.url || frame.scriptId || "", frame.lineNumber ?? "", frame.columnNumber ?? ""].join("@")).join(" <- "); const group = groups.get(key) || { key, leaf: annotateProfileFrameSource(leaf), frames: stackFrames.slice(-10).map(annotateProfileFrameSource), sampleCount: 0, selfTimeMs: 0, valuesRedacted: true }; group.sampleCount += 1; group.selfTimeMs += Number(selfTimeMs || 0); groups.set(key, group); } function finalizeWindowProfileStack(group) { return { ...group, selfTimeMs: roundMs(group.selfTimeMs), valuesRedacted: true }; } function buildFrontendPerformanceWindows(input) { const aggregatedLoafHotspotKeys = new Set( (Array.isArray(input?.scriptHotspots) ? input.scriptHotspots : []) .filter((item) => Number(item?.totalDurationMs ?? 0) >= alertThresholds.longAnimationFrameRedMs) .map(loafScriptKey) .filter(Boolean) ); const events = (Array.isArray(input?.events) ? input.events : []) .filter((item) => item && (item.kind === "longtask" || item.kind === "long-animation-frame" || item.kind === "event-loop-gap")) .filter((item) => Number(item.durationMs ?? 0) >= frontendPerformanceWindowBudget(item) || hasAggregatedLoafHotspot(item, aggregatedLoafHotspotKeys)) .sort(descDuration) .slice(0, 20); return events.map((event) => correlateFrontendPerformanceWindow(event, input)).filter(Boolean); } function hasAggregatedLoafHotspot(event, keys) { if (event?.kind !== "long-animation-frame" || !(keys instanceof Set) || keys.size === 0) return false; for (const script of Array.isArray(event?.scripts) ? event.scripts : []) { if (keys.has(loafScriptKey(script))) return true; } return false; } function correlateFrontendPerformanceWindow(event, input) { const captures = Array.isArray(input?.captures) ? input.captures : []; const matchingCaptures = captures.filter((capture) => samePageContext(event, capture)); const overlapping = matchingCaptures.filter((capture) => windowsOverlap(event, capture)); const covering = overlapping.filter((capture) => windowCovers(capture, event)); const selectedCapture = covering[0] || overlapping[0] || nearestCapture(event, matchingCaptures) || nearestCapture(event, captures); const coverageStatus = selectedCapture ? covering.length > 0 ? "covered" : overlapping.length > 0 ? "overlapped" : "missed" : "no-cpu-profile"; const profileEvidence = coverageStatus === "covered" || coverageStatus === "overlapped" ? sameWindowProfileEvidence(event, selectedCapture, input) : null; const sourceHint = event.kind === "long-animation-frame" ? topLoafScriptHint(event) : null; return { kind: event.kind, ts: event.ts ?? null, startAt: event.startAt ?? null, endAt: event.endAt ?? null, startEpochMs: event.startEpochMs ?? null, endEpochMs: event.endEpochMs ?? null, durationMs: event.durationMs ?? null, blockingDurationMs: event.blockingDurationMs ?? null, sampleSeq: event.sampleSeq ?? null, pageRole: event.pageRole ?? null, pageId: event.pageId ?? null, pageEpoch: event.pageEpoch ?? null, path: event.path ?? null, url: event.url ?? null, scriptCount: event.scriptCount ?? null, topLoafScript: sourceHint, cpuProfileCoverageStatus: coverageStatus, coverageReason: cpuProfileCoverageReason(event, selectedCapture, coverageStatus), capture: selectedCapture ? compactWindowCapture(selectedCapture, event) : null, relatedNetwork: relatedNetworkRows(event, input?.network), relatedSamples: relatedSampleRows(event, input?.samples), profileEvidence, valuesRedacted: true, }; } function frontendPerformanceWindowBudget(event) { if (event?.kind === "longtask") return alertThresholds.longTaskRedMs; if (event?.kind === "long-animation-frame") return alertThresholds.longAnimationFrameRedMs; if (event?.kind === "event-loop-gap") return alertThresholds.eventLoopGapRedMs; return 0; } function performanceEventWindow(row, perf) { const timeOrigin = Number(perf?.timeOrigin); const startTime = Number(perf?.startTime); const duration = Number(perf?.duration); if (Number.isFinite(timeOrigin) && timeOrigin > 0 && Number.isFinite(startTime)) { const startEpochMs = timeOrigin + startTime; const endEpochMs = startEpochMs + (Number.isFinite(duration) ? duration : 0); return { startEpochMs: roundMs(startEpochMs), endEpochMs: roundMs(endEpochMs), startAt: new Date(startEpochMs).toISOString(), endAt: new Date(endEpochMs).toISOString(), source: "performance-timeOrigin-startTime" }; } const observedAt = Number(perf?.observedAt); if (Number.isFinite(observedAt) && observedAt > 0) { const endEpochMs = observedAt; const startEpochMs = endEpochMs - (Number.isFinite(duration) ? duration : 0); return { startEpochMs: roundMs(startEpochMs), endEpochMs: roundMs(endEpochMs), startAt: new Date(startEpochMs).toISOString(), endAt: new Date(endEpochMs).toISOString(), source: "observedAt-minus-duration" }; } const rowMs = performanceTimestampMs(row?.ts); if (Number.isFinite(rowMs)) { const endEpochMs = rowMs; const startEpochMs = endEpochMs - (Number.isFinite(duration) ? duration : 0); return { startEpochMs: roundMs(startEpochMs), endEpochMs: roundMs(endEpochMs), startAt: new Date(startEpochMs).toISOString(), endAt: new Date(endEpochMs).toISOString(), source: "row-ts-minus-duration" }; } return { startEpochMs: null, endEpochMs: null, startAt: null, endAt: null, source: "missing-window" }; } function performanceTimestampMs(value) { const ms = Date.parse(String(value || "")); return Number.isFinite(ms) ? ms : null; } function samePageContext(event, capture) { if (!capture) return false; if (event.pageRole && capture.pageRole && event.pageRole !== capture.pageRole) return false; if (event.pageId && capture.pageId && event.pageId !== capture.pageId) return false; return true; } function windowsOverlap(left, right) { const leftStart = Number(left?.startEpochMs); const leftEnd = Number(left?.endEpochMs); const rightStart = Number(right?.startEpochMs); const rightEnd = Number(right?.endEpochMs); if (![leftStart, leftEnd, rightStart, rightEnd].every(Number.isFinite)) return false; return leftStart <= rightEnd && rightStart <= leftEnd; } function windowCovers(outer, inner) { const outerStart = Number(outer?.startEpochMs); const outerEnd = Number(outer?.endEpochMs); const innerStart = Number(inner?.startEpochMs); const innerEnd = Number(inner?.endEpochMs); if (![outerStart, outerEnd, innerStart, innerEnd].every(Number.isFinite)) return false; return outerStart <= innerStart && outerEnd >= innerEnd; } function nearestCapture(event, captures) { const rows = Array.isArray(captures) ? captures : []; const eventStart = Number(event?.startEpochMs); const eventEnd = Number(event?.endEpochMs); if (![eventStart, eventEnd].every(Number.isFinite) || rows.length === 0) return null; return rows .map((capture) => ({ capture, distanceMs: captureWindowDistanceMs(eventStart, eventEnd, capture) })) .filter((item) => Number.isFinite(item.distanceMs)) .sort((left, right) => left.distanceMs - right.distanceMs)[0]?.capture || null; } function captureWindowDistanceMs(eventStart, eventEnd, capture) { const start = Number(capture?.startEpochMs); const end = Number(capture?.endEpochMs); if (![start, end].every(Number.isFinite)) return Number.POSITIVE_INFINITY; if (eventEnd < start) return start - eventEnd; if (end < eventStart) return eventStart - end; return 0; } function cpuProfileCoverageReason(event, capture, status) { if (status === "no-cpu-profile") return "no completed performanceCapture artifact is present"; if (!capture) return "no comparable capture window is present"; if (status === "covered") return "performanceCapture window fully covers this frontend performance event"; if (status === "overlapped") return "performanceCapture window overlaps this frontend performance event, but does not fully cover it"; const eventStart = Number(event?.startEpochMs); const eventEnd = Number(event?.endEpochMs); const captureStart = Number(capture?.startEpochMs); const captureEnd = Number(capture?.endEpochMs); if (Number.isFinite(captureStart) && Number.isFinite(eventEnd) && captureStart > eventEnd) return "nearest performanceCapture started after this frontend performance window ended"; if (Number.isFinite(captureEnd) && Number.isFinite(eventStart) && captureEnd < eventStart) return "nearest performanceCapture ended before this frontend performance window started"; return "completed performanceCapture exists but its window could not be matched to this frontend event"; } function compactWindowCapture(capture, event) { const distanceMs = captureWindowDistanceMs(Number(event?.startEpochMs), Number(event?.endEpochMs), capture); return { captureId: capture.captureId ?? null, commandId: capture.commandId ?? null, pageRole: capture.pageRole ?? null, pageId: capture.pageId ?? null, startAt: capture.startAt ?? null, endAt: capture.endAt ?? null, durationMs: capture.durationMs ?? null, windowDistanceMs: Number.isFinite(distanceMs) ? roundMs(distanceMs) : null, profileSampleCount: capture.profileSampleCount ?? null, profileTotalTimeMs: capture.profileTotalTimeMs ?? null, profileTimelineStatus: capture.profileTimelineStatus ?? null, profileTimelineSampleCount: capture.profileTimelineSampleCount ?? null, profileTimelineTotalTimeMs: capture.profileTimelineTotalTimeMs ?? null, profileTimelineStartAt: capture.profileTimelineStartAt ?? null, profileTimelineEndAt: capture.profileTimelineEndAt ?? null, valuesRedacted: true, }; } function relatedNetworkRows(event, network) { const rows = Array.isArray(network) ? network : []; const start = Number(event?.startEpochMs); const end = Number(event?.endEpochMs); const toleranceMs = Math.max(1000, Math.min(10000, Number(event?.durationMs || 0) + 1000)); if (![start, end].every(Number.isFinite)) return []; return rows .filter((item) => { const ms = performanceTimestampMs(item?.ts); if (!Number.isFinite(ms)) return false; return ms >= start - toleranceMs && ms <= end + toleranceMs; }) .sort((left, right) => Math.abs(Number(performanceTimestampMs(left?.ts)) - start) - Math.abs(Number(performanceTimestampMs(right?.ts)) - start)) .slice(0, 8) .map(compactRelatedNetworkRow); } function compactRelatedNetworkRow(item) { const url = item?.url ?? item?.request?.url ?? item?.response?.url ?? item?.detail?.url ?? ""; return { ts: item?.ts ?? null, sampleSeq: item?.sampleSeq ?? null, phase: item?.phase ?? item?.type ?? null, method: item?.method ?? item?.request?.method ?? null, status: item?.status ?? item?.response?.status ?? null, urlPath: urlPathOnly(url), bodyByteCount: numberOrNull(item?.bodyByteCount ?? item?.response?.bodyByteCount ?? item?.bodySummary?.byteCount ?? item?.bodySummary?.bodyByteCount), durationMs: numberOrNull(item?.durationMs ?? item?.response?.durationMs), routeSessionId: item?.routeSessionId ?? item?.sessionId ?? null, traceId: item?.traceId ?? item?.bodySummary?.traceId ?? null, valuesRedacted: true, }; } function relatedSampleRows(event, samples) { const rows = Array.isArray(samples) ? samples : []; const start = Number(event?.startEpochMs); const end = Number(event?.endEpochMs); const toleranceMs = Math.max(1000, Math.min(10000, Number(event?.durationMs || 0) + 1000)); if (![start, end].every(Number.isFinite)) return []; return rows .filter((item) => { const ms = performanceTimestampMs(item?.ts); if (!Number.isFinite(ms)) return false; return ms >= start - toleranceMs && ms <= end + toleranceMs; }) .slice(-6) .map((sample) => ({ ts: sample?.ts ?? null, sampleSeq: sample?.seq ?? sample?.sampleSeq ?? null, pageRole: sample?.pageRole ?? null, path: limitText(sample?.path ?? "", 160), routeSessionId: sample?.routeSessionId ?? null, activeSessionId: sample?.activeSessionId ?? null, traceIds: sampleTraceIdsForPerformance(sample).slice(0, 6), valuesRedacted: true, })); } function sampleTraceIdsForPerformance(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)); } if (sample?.traceId) ids.add(String(sample.traceId)); return Array.from(ids); } function topLoafScriptHint(event) { const scripts = Array.isArray(event?.scripts) ? event.scripts : []; return scripts .slice() .sort((left, right) => Number(right.durationMs ?? 0) - Number(left.durationMs ?? 0))[0] || null; } async function buildFrontendPerformanceSourceMapState(artifacts) { const candidates = sourceMapArtifactCandidates(artifacts); const maps = []; let failedCount = 0; for (const candidate of candidates) { const parsed = await readJson(candidate.path); const map = parseFrontendSourceMap(parsed, candidate); if (map) maps.push(map); else failedCount += 1; } return { artifactCount: candidates.length, loadedCount: maps.length, failedCount, maps, valuesRedacted: true }; } function sourceMapArtifactCandidates(artifacts) { const rows = []; const seen = new Set(); for (const item of Array.isArray(artifacts) ? artifacts : []) { if (!item || typeof item !== "object") continue; const paths = [item.path, item.sourceMapPath, item.mapPath, item.summaryPath].filter((value) => typeof value === "string" && value); for (const candidatePath of paths) { const kind = String(item.kind || item.type || "").toLowerCase(); const url = String(item.url || item.sourceURL || item.sourceMapUrl || ""); const pathText = String(candidatePath); const looksLikeMap = kind.includes("source-map") || kind.includes("sourcemap") || pathText.endsWith(".map") || url.endsWith(".map"); if (!looksLikeMap || seen.has(pathText)) continue; seen.add(pathText); rows.push({ path: pathText, url: url || null, kind: item.kind ?? null, ts: item.ts ?? null, valuesRedacted: true }); } } return rows.slice(-40); } function parseFrontendSourceMap(raw, candidate) { if (!raw || typeof raw !== "object" || typeof raw.mappings !== "string" || !Array.isArray(raw.sources)) return null; const parsedMappings = parseSourceMapMappings(raw.mappings, raw.sources, Array.isArray(raw.names) ? raw.names : []); return { path: candidate.path, url: candidate.url ?? null, file: typeof raw.file === "string" ? raw.file : null, sources: raw.sources, names: Array.isArray(raw.names) ? raw.names : [], lines: parsedMappings.lines, mappingCount: parsedMappings.mappingCount, valuesRedacted: true, }; } function parseSourceMapMappings(mappings, sources, names) { const lines = []; let sourceIndex = 0; let originalLine = 0; let originalColumn = 0; let nameIndex = 0; const rawLines = String(mappings || "").split(";"); for (let generatedLine = 0; generatedLine < rawLines.length; generatedLine += 1) { let generatedColumn = 0; const segments = []; for (const segmentText of rawLines[generatedLine].split(",")) { if (!segmentText) continue; const values = decodeSourceMapVlqSegment(segmentText); if (values.length === 0) continue; generatedColumn += Number(values[0] || 0); if (values.length >= 4) { sourceIndex += Number(values[1] || 0); originalLine += Number(values[2] || 0); originalColumn += Number(values[3] || 0); if (values.length >= 5) nameIndex += Number(values[4] || 0); if (sources[sourceIndex]) { segments.push({ generatedColumn, source: String(sources[sourceIndex]), originalLine, originalColumn, name: names[nameIndex] ? String(names[nameIndex]) : null, valuesRedacted: true, }); } } } lines[generatedLine] = segments; } return { lines, mappingCount: lines.reduce((sum, line) => sum + (Array.isArray(line) ? line.length : 0), 0), valuesRedacted: true }; } function decodeSourceMapVlqSegment(segmentText) { const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const values = []; let value = 0; let shift = 0; for (const char of String(segmentText || "")) { const digit = chars.indexOf(char); if (digit < 0) return []; const continuation = (digit & 32) !== 0; value += (digit & 31) << shift; if (continuation) { shift += 5; continue; } const negative = (value & 1) === 1; const decoded = value >> 1; values.push(negative ? -decoded : decoded); value = 0; shift = 0; } return values; } function createFrontendPerformanceSourceMapper(maps) { const loadedMaps = Array.isArray(maps) ? maps.filter((item) => item && Array.isArray(item.lines)) : []; return { loadedCount: loadedMaps.length, resolve(url, lineNumber, columnNumber, sourceCharPosition) { if (loadedMaps.length === 0) return null; const matching = loadedMaps.filter((map) => sourceMapMatchesUrl(map, url)); if (matching.length === 0) return { status: "missing", mapped: null }; const generatedLine = sourceMapGeneratedLine(lineNumber); const generatedColumn = sourceMapGeneratedColumn(columnNumber, sourceCharPosition); if (![generatedLine, generatedColumn].every(Number.isFinite)) return { status: "unmapped", mapped: null }; for (const map of matching) { const mapped = lookupSourceMapPosition(map, generatedLine, generatedColumn); if (mapped) return { status: "mapped", mapped, map }; } return { status: "unmapped", mapped: null }; }, }; } function sourceMapMatchesUrl(map, url) { const raw = String(url || ""); if (!raw) return false; const urlBase = sourceFileFromUrl(raw); const mapFile = sourceFileFromUrl(map?.file || ""); const mapPathBase = sourceFileFromUrl(map?.path || ""); const mapUrlBase = sourceFileFromUrl(map?.url || ""); const expectedMapBase = urlBase ? urlBase + ".map" : ""; return Boolean( (mapFile && urlBase && mapFile === urlBase) || (mapPathBase && expectedMapBase && mapPathBase === expectedMapBase) || (mapUrlBase && expectedMapBase && mapUrlBase === expectedMapBase) || (mapPathBase && urlBase && mapPathBase.replace(/\.map$/u, "") === urlBase) || (mapUrlBase && urlBase && mapUrlBase.replace(/\.map$/u, "") === urlBase) ); } function sourceMapGeneratedLine(lineNumber) { const line = Number(lineNumber); return Number.isFinite(line) && line >= 0 ? Math.floor(line) : 0; } function sourceMapGeneratedColumn(columnNumber, sourceCharPosition) { if (sourceCharPosition !== null && sourceCharPosition !== undefined) { const charPosition = Number(sourceCharPosition); if (Number.isFinite(charPosition) && charPosition >= 0) return Math.floor(charPosition); } const column = Number(columnNumber); return Number.isFinite(column) && column >= 0 ? Math.floor(column) : null; } function lookupSourceMapPosition(map, generatedLine, generatedColumn) { const line = Array.isArray(map?.lines?.[generatedLine]) ? map.lines[generatedLine] : []; let selected = null; for (const segment of line) { if (Number(segment?.generatedColumn) <= generatedColumn) selected = segment; else break; } if (!selected) return null; return { sourceFile: limitText(selected.source || "", 180), sourceLine: Number.isFinite(Number(selected.originalLine)) ? Number(selected.originalLine) + 1 : null, sourceColumn: numberOrNull(selected.originalColumn), sourceName: selected.name ? limitText(selected.name, 160) : null, generatedLine, generatedColumn, valuesRedacted: true, }; } function buildFrontendSourceAttribution(input) { const sourceFiles = new Map(); const add = (row) => { const file = row?.sourceFile || sourceFileFromUrl(row?.sourceURL || row?.url); if (!file) return; const existing = sourceFiles.get(file) || { sourceFile: file, count: 0, sourceMapStatus: row?.sourceMapStatus || "missing", valuesRedacted: true }; existing.count += 1; if (existing.sourceMapStatus !== "mapped") existing.sourceMapStatus = row?.sourceMapStatus || existing.sourceMapStatus; sourceFiles.set(file, existing); }; for (const row of Array.isArray(input?.scriptHotspots) ? input.scriptHotspots : []) add(row); for (const row of Array.isArray(input?.profileHotspots) ? input.profileHotspots : []) add(row); for (const stack of Array.isArray(input?.profileStacks) ? input.profileStacks : []) { for (const frame of Array.isArray(stack?.frames) ? stack.frames : []) add(frame); } const rows = Array.from(sourceFiles.values()); const mappedCount = rows.filter((item) => item.sourceMapStatus === "mapped").length; const unmappedCount = rows.filter((item) => item.sourceMapStatus === "unmapped").length; const state = input?.sourceMapState && typeof input.sourceMapState === "object" ? input.sourceMapState : {}; const loadedCount = Number(state.loadedCount ?? 0); const status = mappedCount > 0 ? "mapped" : loadedCount > 0 || unmappedCount > 0 ? "unmapped" : "missing"; const note = status === "mapped" ? "Source-map artifact was loaded; mapped rows include original source file and line while raw browser locations remain present." : loadedCount > 0 ? "Source-map artifact was loaded, but observed browser locations did not resolve to original source positions." : "No source-map artifact is loaded by this analyzer; function/file/line fields are raw browser URLs or CPU profile callFrame locations."; return { sourceMapStatus: status, note, sourceMapArtifactCount: numberOrNull(state.artifactCount), sourceMapLoadedCount: numberOrNull(state.loadedCount), sourceMapFailedCount: numberOrNull(state.failedCount), sourceFiles: rows.sort((left, right) => Number(right.count ?? 0) - Number(left.count ?? 0)).slice(0, 20), valuesRedacted: true, }; } function annotateProfileFrameSource(frame) { if (!frame || typeof frame !== "object") return frame ?? null; const source = sourceAttributionFor(frame.url, frame.functionName, frame.lineNumber, frame.columnNumber, null); return { ...frame, sourceFile: source.sourceFile, sourceLine: source.sourceLine, sourceColumn: source.sourceColumn, sourceMapStatus: source.sourceMapStatus, sourceAttributionMode: source.sourceAttributionMode, valuesRedacted: true }; } function sourceAttributionFor(url, functionName, lineNumber, columnNumber, sourceCharPosition) { const resolved = frontendPerformanceSourceMapper?.resolve ? frontendPerformanceSourceMapper.resolve(url, lineNumber, columnNumber, sourceCharPosition) : null; if (resolved?.status === "mapped" && resolved.mapped) { return { functionName: limitText(resolved.mapped.sourceName || functionName || "", 160), sourceFile: resolved.mapped.sourceFile, sourceLine: resolved.mapped.sourceLine, sourceColumn: resolved.mapped.sourceColumn, sourceCharPosition: numberOrNull(sourceCharPosition), sourceMapStatus: "mapped", sourceAttributionMode: "source-map-original-position", rawSourceFile: sourceFileFromUrl(url), rawLineNumber: numberOrNull(lineNumber), rawColumnNumber: numberOrNull(columnNumber), valuesRedacted: true, }; } return { functionName: limitText(functionName ?? "", 160), sourceFile: sourceFileFromUrl(url), sourceLine: numberOrNull(lineNumber), sourceColumn: numberOrNull(columnNumber), sourceCharPosition: numberOrNull(sourceCharPosition), sourceMapStatus: resolved?.status === "unmapped" ? "unmapped" : "missing", sourceAttributionMode: resolved?.status === "unmapped" ? "source-map-loaded-no-match" : "browser-raw-url-line-column", valuesRedacted: true, }; } function sourceFileFromUrl(value) { const text = String(value || ""); if (!text) return null; try { const parsed = new URL(text, "http://local.invalid"); const path = parsed.pathname || ""; const parts = path.split("/").filter(Boolean); return limitText(parts[parts.length - 1] || path || text, 160); } catch { const clean = text.split(/[?#]/u)[0]; const parts = clean.split("/").filter(Boolean); return limitText(parts[parts.length - 1] || clean, 160); } } function urlPathOnly(value) { const text = String(value || ""); if (!text) return null; try { const parsed = new URL(text, "http://local.invalid"); return limitText(parsed.pathname || "/", 180); } catch { return limitText(text.split("?")[0] || text, 180); } } function frontendPerformanceMaxNumber(items, getter) { const selected = maxByNumber(items, getter); return selected ? numberOrNull(getter(selected)) : null; } function descDuration(left, right) { return Number(right.durationMs ?? 0) - Number(left.durationMs ?? 0) || String(right.ts || "").localeCompare(String(left.ts || "")); } function roundMs(value) { return Number((Number(value || 0)).toFixed(2)); } `; }