From 7e322e326e6281889fee3bbc94aafedbffe4800e Mon Sep 17 00:00:00 2001 From: Lyon <88232613+pikasTech@users.noreply.github.com> Date: Tue, 23 Jun 2026 11:36:03 +0800 Subject: [PATCH] fix: improve web-probe trace drilldown visibility (#734) Co-authored-by: Codex --- scripts/src/hwlab-node-impl.ts | 81 ++++++++++++++----- .../hwlab-node-web-observe-runner-source.ts | 6 ++ 2 files changed, 67 insertions(+), 20 deletions(-) diff --git a/scripts/src/hwlab-node-impl.ts b/scripts/src/hwlab-node-impl.ts index 85d15dd8..3bbc6f58 100644 --- a/scripts/src/hwlab-node-impl.ts +++ b/scripts/src/hwlab-node-impl.ts @@ -8326,7 +8326,7 @@ function renderWebObserveCollectTable(value: Record): string { ]]), "", "Finding samples:", - webObserveTable(["SEQ", "TS", "KIND", "CONTROL", "OBSERVER", "SESSION", "PATH", "DETAIL"], jsonFindingSamples.length > 0 ? jsonFindingSamples.map((item) => [ + webObserveTable(["SEQ", "TS", "KIND", "CONTROL", "OBSERVER", "SESSION", "PATH", "TRACE", "DETAIL"], jsonFindingSamples.length > 0 ? jsonFindingSamples.map((item) => [ webObserveText(item.seq ?? item.sampleSeq ?? item.fromSeq), webObserveShort(webObserveText(item.ts ?? item.sampleTs ?? item.fromTs), 24), webObserveShort(webObserveText(item.diffKind ?? item.kind ?? item.type ?? item.metric ?? item.anomaly), 28), @@ -8334,15 +8334,16 @@ function renderWebObserveCollectTable(value: Record): string { webObserveShort(webObserveText(item.observer ?? item.observerValue ?? item.afterValue), 28), webObserveShort(webObserveText(item.sessionId ?? item.routeSessionId ?? item.activeSessionId), 36), webObserveShort(webObserveText(item.path ?? item.urlPath ?? item.url), 48), + webObserveShort(webObserveText(item.trace ?? item.traceId ?? item.controlTraceId ?? item.observerTraceId), 48), webObserveShort(webObserveText(item.detail ?? item.summary ?? item.message ?? item.preview), 96), - ]) : [["-", "-", "-", "-", "-", "-", "-", "-"]]), + ]) : [["-", "-", "-", "-", "-", "-", "-", "-", "-"]]), "", ); } if (jsonlTail.length > 0) { lines.push( "JSONL tail:", - webObserveTable(["TS", "ROLE", "TYPE", "COMMAND", "MSG", "TRACE", "LOAD", "ATTEMPTS", "READY", "DOM", "PATH", "MESSAGE"], jsonlTail.map((item) => { + webObserveTable(["TS", "ROLE", "TYPE", "COMMAND", "MSG", "TRACE", "TRACE_IDS", "MSG_STATUS", "LOAD", "ATTEMPTS", "READY", "DOM", "PATH", "MESSAGE"], jsonlTail.map((item) => { const error = record(item.error); const attempts = Array.isArray(error.attempts) ? error.attempts : []; const lastAttempt = attempts.length > 0 ? record(attempts[attempts.length - 1]) : {}; @@ -8362,6 +8363,8 @@ function renderWebObserveCollectTable(value: Record): string { webObserveShort(webObserveText(item.commandId), 28), webObserveText(item.messageCount), webObserveText(item.traceRowCount ?? item.traceEventCount), + webObserveShort(webObserveText(item.traceIds), 48), + webObserveShort(webObserveText(item.messageStatuses), 48), webObserveText(item.loadingCount), webObserveText(attempts.length), webObserveShort(webObserveText(readiness.reason), 24), @@ -8470,6 +8473,9 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec "const slimLoadingSegment = (item) => { const v = objectOrNull(item) || {}; return { firstAt: v.firstAt ?? null, lastAt: v.lastAt ?? null, endedAt: v.endedAt ?? null, firstSeq: v.firstSeq ?? null, lastSeq: v.lastSeq ?? null, durationSeconds: v.durationSeconds ?? null, upperBoundSeconds: v.upperBoundSeconds ?? null, endedGapSeconds: v.endedGapSeconds ?? null, sampleCount: v.sampleCount ?? null, maxCount: v.maxCount ?? null, ownerCount: v.ownerCount ?? null, ongoing: v.ongoing === true, owners: Array.isArray(v.owners) ? v.owners.slice(0, 6).map((owner) => ({ ownerKind: clip(owner?.ownerKind, 32), ownerLabel: clip(owner?.ownerLabel, 120), count: owner?.count ?? null })) : [] }; };", "const slimLoadingMetrics = (value) => { const v = objectOrNull(value); if (!v) return null; return { summary: objectOrNull(v.summary), longestSegments: takeHead(v.longestSegments ?? v.segments, 8).map(slimLoadingSegment), owners: takeHead(v.owners, 8).map(slimLoadingOwner), timeline: takeTail(v.timeline, 12).map((item) => { const row = objectOrNull(item) || {}; return { seq: row.seq ?? null, ts: row.ts ?? null, promptIndex: row.promptIndex ?? null, loadingCount: row.loadingCount ?? null, ownerCount: row.ownerCount ?? null, owners: Array.isArray(row.owners) ? row.owners.slice(0, 6).map((owner) => ({ ownerKind: clip(owner?.ownerKind, 32), ownerLabel: clip(owner?.ownerLabel, 120), count: owner?.count ?? null })) : [] }; }), valuesRedacted: true }; };", "const srcMetrics = objectOrNull(source?.sampleMetrics);", + "const fullRecentWindow = objectOrNull(fullSource?.windows?.recent);", + "const fullRecentMetrics = objectOrNull(fullRecentWindow?.sampleMetrics);", + "const fullArchiveMetrics = objectOrNull(fullSource?.sampleMetrics);", "const metrics = srcMetrics ? {", " sampleCount: srcMetrics.sampleCount ?? null,", " loadingSampleCount: srcMetrics.loadingSampleCount ?? null,", @@ -8481,18 +8487,19 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec " loadingCurrentContinuousSeconds: srcMetrics.loadingCurrentContinuousSeconds ?? null,", " loadingOverFiveSecondSegmentCount: srcMetrics.loadingOverFiveSecondSegmentCount ?? null,", " loading: slimLoadingMetrics(srcMetrics.loading),", - " roundItems: takeTail(srcMetrics.roundItems, 8).map(slimRound),", - " rounds: Array.isArray(srcMetrics.rounds) ? takeTail(srcMetrics.rounds, 8).map(slimRound) : srcMetrics.rounds,", - " turnColumns: takeTail(srcMetrics.turnColumns, 8).map(slimTurnColumn),", - " turnTimingRows: Array.isArray(srcMetrics.turnTimingRows) ? srcMetrics.turnTimingRows.length : (srcMetrics.turnTimingRows ?? null),", - " turnTimingNonMonotonicCount: srcMetrics.turnTimingNonMonotonicCount ?? null,", - " turnTimingTotalElapsedDecreaseCount: srcMetrics.turnTimingTotalElapsedDecreaseCount ?? null,", - " turnTimingTotalElapsedForwardJumpCount: srcMetrics.turnTimingTotalElapsedForwardJumpCount ?? null,", - " turnTimingTotalElapsedForwardJumpMaxSeconds: srcMetrics.turnTimingTotalElapsedForwardJumpMaxSeconds ?? null,", - " turnTimingTerminalElapsedGrowthCount: srcMetrics.turnTimingTerminalElapsedGrowthCount ?? null,", - " turnTimingRecentUpdateJumpCount: srcMetrics.turnTimingRecentUpdateJumpCount ?? null,", - " turnTimingRecentUpdateSawtoothJumpCount: srcMetrics.turnTimingRecentUpdateSawtoothJumpCount ?? null,", - " turnTimingRecentUpdateMaxIncreaseSeconds: srcMetrics.turnTimingRecentUpdateMaxIncreaseSeconds ?? null,", + " roundItems: takeTail(firstNonEmptyArray(srcMetrics.roundItems, srcMetrics.rounds, fullRecentMetrics?.rounds, fullArchiveMetrics?.rounds), 8).map(slimRound),", + " rounds: takeTail(firstNonEmptyArray(srcMetrics.roundItems, srcMetrics.rounds, fullRecentMetrics?.rounds, fullArchiveMetrics?.rounds), 8).map(slimRound),", + " turnColumns: takeTail(firstNonEmptyArray(srcMetrics.turnColumns, fullRecentMetrics?.turnColumns, fullArchiveMetrics?.turnColumns), 8).map(slimTurnColumn),", + " turnTimingRows: srcMetrics.turnTimingRows ?? fullRecentMetrics?.summary?.turnTimingRows ?? fullRecentMetrics?.turnTimingRows?.length ?? fullArchiveMetrics?.summary?.turnTimingRows ?? fullArchiveMetrics?.turnTimingRows?.length ?? null,", + " turnTimingNonMonotonicCount: srcMetrics.turnTimingNonMonotonicCount ?? fullRecentMetrics?.summary?.turnTimingNonMonotonicCount ?? fullRecentMetrics?.turnTimingNonMonotonic?.length ?? fullArchiveMetrics?.summary?.turnTimingNonMonotonicCount ?? fullArchiveMetrics?.turnTimingNonMonotonic?.length ?? null,", + " turnTimingTotalElapsedDecreaseCount: srcMetrics.turnTimingTotalElapsedDecreaseCount ?? fullRecentMetrics?.summary?.turnTimingTotalElapsedDecreaseCount ?? fullArchiveMetrics?.summary?.turnTimingTotalElapsedDecreaseCount ?? null,", + " turnTimingTotalElapsedZeroResetCount: srcMetrics.turnTimingTotalElapsedZeroResetCount ?? fullRecentMetrics?.summary?.turnTimingTotalElapsedZeroResetCount ?? fullRecentMetrics?.turnTimingElapsedZeroResets?.length ?? fullArchiveMetrics?.summary?.turnTimingTotalElapsedZeroResetCount ?? fullArchiveMetrics?.turnTimingElapsedZeroResets?.length ?? null,", + " turnTimingTotalElapsedForwardJumpCount: srcMetrics.turnTimingTotalElapsedForwardJumpCount ?? fullRecentMetrics?.summary?.turnTimingTotalElapsedForwardJumpCount ?? fullRecentMetrics?.turnTimingTotalElapsedForwardJumps?.length ?? fullArchiveMetrics?.summary?.turnTimingTotalElapsedForwardJumpCount ?? fullArchiveMetrics?.turnTimingTotalElapsedForwardJumps?.length ?? null,", + " turnTimingTotalElapsedForwardJumpMaxSeconds: srcMetrics.turnTimingTotalElapsedForwardJumpMaxSeconds ?? fullRecentMetrics?.summary?.turnTimingTotalElapsedForwardJumpMaxSeconds ?? fullArchiveMetrics?.summary?.turnTimingTotalElapsedForwardJumpMaxSeconds ?? null,", + " turnTimingTerminalElapsedGrowthCount: srcMetrics.turnTimingTerminalElapsedGrowthCount ?? fullRecentMetrics?.summary?.turnTimingTerminalElapsedGrowthCount ?? fullRecentMetrics?.turnTimingTerminalElapsedGrowth?.length ?? fullArchiveMetrics?.summary?.turnTimingTerminalElapsedGrowthCount ?? fullArchiveMetrics?.turnTimingTerminalElapsedGrowth?.length ?? null,", + " turnTimingRecentUpdateJumpCount: srcMetrics.turnTimingRecentUpdateJumpCount ?? fullRecentMetrics?.summary?.turnTimingRecentUpdateJumpCount ?? fullRecentMetrics?.summary?.turnTimingRecentUpdateSawtoothJumpCount ?? fullArchiveMetrics?.summary?.turnTimingRecentUpdateJumpCount ?? fullArchiveMetrics?.summary?.turnTimingRecentUpdateSawtoothJumpCount ?? null,", + " turnTimingRecentUpdateSawtoothJumpCount: srcMetrics.turnTimingRecentUpdateSawtoothJumpCount ?? fullRecentMetrics?.summary?.turnTimingRecentUpdateSawtoothJumpCount ?? fullArchiveMetrics?.summary?.turnTimingRecentUpdateSawtoothJumpCount ?? null,", + " turnTimingRecentUpdateMaxIncreaseSeconds: srcMetrics.turnTimingRecentUpdateMaxIncreaseSeconds ?? fullRecentMetrics?.summary?.turnTimingRecentUpdateMaxIncreaseSeconds ?? fullArchiveMetrics?.summary?.turnTimingRecentUpdateMaxIncreaseSeconds ?? null,", " promptSegments: srcMetrics.promptSegments ?? null", "} : null;", "const srcRuntimeAlerts = objectOrNull(source?.runtimeAlerts);", @@ -8500,7 +8507,6 @@ function runNodeWebProbeObserveAnalyze(options: NodeWebProbeObserveOptions, spec "const srcPagePerformance = objectOrNull(source?.pagePerformance);", "const srcPagePerformanceSummary = objectOrNull(srcPagePerformance?.summary);", "const pagePerformance = srcPagePerformance ? { sameOriginApiPaths: srcPagePerformance.sameOriginApiPaths ?? srcPagePerformanceSummary?.sameOriginApiPathCount ?? null, longLivedStreamPathCount: srcPagePerformance.longLivedStreamPathCount ?? srcPagePerformanceSummary?.longLivedStreamPathCount ?? null, longLivedStreamOpenOverFiveSecondSampleCount: srcPagePerformance.longLivedStreamOpenOverFiveSecondSampleCount ?? srcPagePerformanceSummary?.longLivedStreamOpenOverFiveSecondSampleCount ?? null, slowPathCount: srcPagePerformance.slowPathCount ?? srcPagePerformanceSummary?.slowPathCount ?? null, slowSampleCount: srcPagePerformance.slowSampleCount ?? srcPagePerformanceSummary?.slowSampleCount ?? null, worstP95Ms: srcPagePerformance.worstP95Ms ?? srcPagePerformanceSummary?.worstP95Ms ?? null } : null;", - "const fullRecentWindow = objectOrNull(fullSource?.windows?.recent);", "const fullPagePerformance = objectOrNull(fullRecentWindow?.pagePerformance) || objectOrNull(fullSource?.pagePerformance);", "const fullArchivePagePerformance = objectOrNull(fullSource?.pagePerformance);", "const isLongLivedApi = (item) => item?.isLongLivedStream === true || item?.routeKind === 'same-origin-api-stream' || item?.budgetMetric === 'streamOpenMs';", @@ -9977,18 +9983,51 @@ const slimFinding=(item)=>{ const firstArray=(...values)=>values.find((value)=>Array.isArray(value))||[]; const compactMetric=(value)=>value===undefined||value===null?undefined:(typeof value==='object'?short(JSON.stringify(slimObject(value,8)),160):short(value,160)); const compactProjectionPair=(messageCount,traceCount)=>messageCount===undefined&&traceCount===undefined?undefined:'msg='+String(messageCount??'-')+' trace='+String(traceCount??'-'); +const firstNestedRef=(value)=>value&&typeof value==='object'?(value.control&&typeof value.control==='object'?value.control:(value.observer&&typeof value.observer==='object'?value.observer:null)):null; +const compactTraceList=(value)=>{ + if(Array.isArray(value)) return value.length>0?value.slice(0,4).map((item)=>String(item)).join(','):undefined; + if(value===undefined||value===null||value==='') return undefined; + return String(value); +}; +const traceIdsFromRows=(rows)=>{ + if(!Array.isArray(rows)) return undefined; + const ids=[]; + for(const row of rows){ + if(!row||typeof row!=='object') continue; + const value=row.traceId??row.businessTraceId??row.id; + if(value&&!ids.includes(String(value))) ids.push(String(value)); + if(ids.length>=4) break; + } + return ids.length>0?ids:undefined; +}; +const messageStatusesFromRows=(rows)=>{ + if(!Array.isArray(rows)) return undefined; + const values=[]; + for(const row of rows){ + if(!row||typeof row!=='object') continue; + const status=row.status??row.dataStatus??row.messageStatus; + const trace=row.traceId??row.businessTraceId; + const text=[status,trace].filter(Boolean).join(':'); + if(text&&!values.includes(text)) values.push(String(text)); + if(values.length>=4) break; + } + return values.length>0?values:undefined; +}; const slimFindingSample=(value)=>{ if(!value||typeof value!=='object') return null; + const refValue=firstNestedRef(value)||{}; const controlCount=compactProjectionPair(value.controlMessageCount,value.controlTraceRowCount)??value.controlValue??value.beforeMessageCount??value.beforeValue; const observerCount=compactProjectionPair(value.observerMessageCount,value.observerTraceRowCount)??value.observerValue??value.afterMessageCount??value.afterValue; + const trace=compactTraceList(value.traceIds)??compactTraceList(value.missingTraceIdsInObserver)??compactTraceList(value.controlTraceIds)??compactTraceList(value.observerTraceIds)??value.traceId??refValue.traceId; return { - seq:value.seq??value.sampleSeq??value.fromSeq??value.controlSeq??null, - ts:short(value.ts??value.sampleTs??value.fromTs??value.controlTs,32), + seq:value.seq??value.sampleSeq??value.fromSeq??value.controlSeq??refValue.seq??null, + ts:short(value.ts??value.sampleTs??value.fromTs??value.controlTs??refValue.ts,32), diffKind:short(value.diffKind??value.kind??value.type??value.metric??value.anomaly,48), control:compactMetric(controlCount), observer:compactMetric(observerCount), - sessionId:short(value.sessionId??value.routeSessionId??value.activeSessionId??value.controlSessionId??value.observerSessionId,64), - path:short(value.path??value.urlPath??value.url??value.controlPath??value.observerPath,120), + sessionId:short(value.sessionId??value.routeSessionId??value.activeSessionId??value.controlSessionId??value.observerSessionId??refValue.routeSessionId??refValue.activeSessionId,64), + path:short(value.path??value.urlPath??value.url??value.controlPath??value.observerPath??refValue.url,120), + trace:short(trace,120), detail:short(value.detail??(value.messageTextDigestDiff?'messageDigest '+String(value.controlMessageDigest??'-')+' != '+String(value.observerMessageDigest??'-'):undefined)??value.summary??value.message??value.preview??value.expectedPattern,180), valuesRedacted:true }; @@ -10019,6 +10058,8 @@ const slimJsonl=(value)=>{ routeSessionId:short(value.routeSessionId||snapshot?.routeSessionId,48), activeSessionId:short(value.activeSessionId||snapshot?.activeSessionId,48), messageCount:value.messageCount??(Array.isArray(value.messages)?value.messages.length:undefined)??snapshot?.messageCount, + messageStatuses:short(compactTraceList(messageStatusesFromRows(value.messages)),160), + traceIds:short(compactTraceList(value.traceIds??traceIdsFromRows(value.traceRows)),160), traceEventCount:value.traceEventCount??snapshot?.traceEventCount, traceRowCount:value.traceRowCount??(Array.isArray(value.traceRows)?value.traceRows.length:undefined)??snapshot?.traceRowCount, loadingCount:value.loadingCount??(Array.isArray(value.loadings)?value.loadings.length:undefined)??snapshot?.loadingCount, diff --git a/scripts/src/hwlab-node-web-observe-runner-source.ts b/scripts/src/hwlab-node-web-observe-runner-source.ts index 99289192..322c2627 100644 --- a/scripts/src/hwlab-node-web-observe-runner-source.ts +++ b/scripts/src/hwlab-node-web-observe-runner-source.ts @@ -4777,6 +4777,8 @@ function detectCrossPageProjectionDiffs(samples) { diffKind: traceVisibilityDivergent ? "trace-visibility" : "projection", control: ref(control), observer: ref(observer), + controlTraceIds: [...controlTraceIds].slice(0, 8), + observerTraceIds: [...observerTraceIds].slice(0, 8), missingTraceIdsInObserver: missingInObserver.slice(0, 8), controlMessageCount: controlMessages, observerMessageCount: observerMessages, @@ -4837,6 +4839,8 @@ function detectAdjacentCrossPageProjectionDiffs(samples) { const controlZero = detectTerminalZeroElapsed([control]).length > 0; const observerZero = detectTerminalZeroElapsed([observer]).length > 0; const missingInObserver = [...visibleTraceIds(control)].filter((item) => !visibleTraceIds(observer).has(item)); + const controlTraceIds = visibleTraceIds(control); + const observerTraceIds = visibleTraceIds(observer); const controlMessageDigest = digestMessageTexts(control); const observerMessageDigest = digestMessageTexts(observer); const messageTextDigestDiff = controlMessageDigest !== observerMessageDigest; @@ -4849,6 +4853,8 @@ function detectAdjacentCrossPageProjectionDiffs(samples) { diffKind: traceVisibilityDivergent ? "trace-visibility" : "projection", control: ref(control), observer: ref(observer), + controlTraceIds: [...controlTraceIds].slice(0, 8), + observerTraceIds: [...observerTraceIds].slice(0, 8), missingTraceIdsInObserver: missingInObserver.slice(0, 8), controlMessageCount: controlMessages, observerMessageCount: observerMessages,