fix: surface webprobe performance evidence mode
This commit is contained in:
@@ -46,6 +46,7 @@ const network=readJsonl('network.jsonl');
|
||||
const browserProcess=readJsonl('browser-process.jsonl');
|
||||
const performanceRows=readJsonl('performance-events.jsonl');
|
||||
const manifest=readJson('manifest.json')||{};
|
||||
const heartbeat=readJson('heartbeat.json')||{};
|
||||
const report=readJson('analysis/report.json')||{};
|
||||
function unique(values){return Array.from(new Set(values.filter(Boolean)));}
|
||||
function numOrNull(value){const n=Number(value); return Number.isFinite(n)?n:null}
|
||||
@@ -746,13 +747,68 @@ function compactPerfCapture(item){
|
||||
function compactPerfFinding(item){
|
||||
return {severity:item?.severity??item?.level??null,id:short(item?.id||item?.kind||item?.code||'',56),count:item?.count??item?.sampleCount??null,rootCause:item?.rootCause??item?.rootCauseStatus??null,summary:short(item?.summary||item?.message||'',110),valuesRedacted:true};
|
||||
}
|
||||
function compactPerfCommand(row){
|
||||
return {bucket:row?.bucket||null,id:short(commandFileId(row)||'',40),type:short(commandFileType(row)||'',32),ts:short(commandFileTs(row)||'',32),ageSeconds:row?.data?.ageSeconds??null,relative:short(row?.relative||'',72),valuesRedacted:true};
|
||||
}
|
||||
function runnerStatusForPerformance(){
|
||||
const status=String(report?.heartbeat?.status||heartbeat?.status||report?.manifest?.status||manifest?.status||'').trim();
|
||||
const terminal=/^(completed|failed|force-stopped|stopped|abandoned)$/u.test(status);
|
||||
const failed=/^(failed|force-stopped|abandoned|not-running)$/u.test(status);
|
||||
return {status:status||null,terminal,notRunningOrFailed:failed,updatedAt:report?.heartbeat?.updatedAt||heartbeat?.updatedAt||heartbeat?.lastSampleAt||null,valuesRedacted:true};
|
||||
}
|
||||
function performanceCaptureCommandStatus(commandFiles){
|
||||
const rows=commandFiles.filter((row)=>commandFileType(row)==='performanceCapture');
|
||||
const byBucket=(bucket)=>rows.filter((row)=>row.bucket===bucket);
|
||||
return {
|
||||
total:rows.length,
|
||||
pendingCount:byBucket('pending').length,
|
||||
processingCount:byBucket('processing').length,
|
||||
doneCount:byBucket('done').length,
|
||||
failedCount:byBucket('failed').length,
|
||||
abandonedCount:byBucket('abandoned').length,
|
||||
pending:byBucket('pending').slice(0,6).map(compactPerfCommand),
|
||||
processing:byBucket('processing').slice(0,6).map(compactPerfCommand),
|
||||
failed:byBucket('failed').slice(-4).map(compactPerfCommand),
|
||||
done:byBucket('done').slice(-4).map(compactPerfCommand),
|
||||
valuesRedacted:true
|
||||
};
|
||||
}
|
||||
function performanceToolFindings(){
|
||||
const ids=new Set(['tool-pending-commands-unconsumed','tool-runner-heartbeat-stale','tool-target-page-not-ready','tool-runner-force-stopped','frontend-browser-freeze-runner-blocker','frontend-playwright-responsiveness-red','frontend-cdp-metrics-timeout-red','frontend-performance-probe-drain-errors','frontend-performance-loaf-only-no-cpu-profile']);
|
||||
return (Array.isArray(report.findings)?report.findings:[]).filter((item)=>ids.has(String(item?.id||item?.kind||item?.code||''))).slice(0,10).map(compactPerfFinding);
|
||||
}
|
||||
function performanceEvidenceMode(perf, commandFiles){
|
||||
const s=perf.summary||{};
|
||||
const captureCount=Number(s.captureCount??0);
|
||||
const hasCpuProfile=captureCount>0;
|
||||
const hasPerformanceObserverEvidence=
|
||||
Number(s.longTaskCount??0)>0||
|
||||
Number(s.longAnimationFrameCount??0)>0||
|
||||
Number(s.eventLoopGapCount??0)>0||
|
||||
Number(s.scriptHotspotCount??0)>0||
|
||||
(Array.isArray(perf.longTasks)&&perf.longTasks.length>0)||
|
||||
(Array.isArray(perf.longAnimationFrames)&&perf.longAnimationFrames.length>0)||
|
||||
(Array.isArray(perf.eventLoopGaps)&&perf.eventLoopGaps.length>0)||
|
||||
(Array.isArray(perf.scriptHotspots)&&perf.scriptHotspots.length>0);
|
||||
const commands=performanceCaptureCommandStatus(commandFiles);
|
||||
const runner=runnerStatusForPerformance();
|
||||
const pendingPerformanceCapture=commands.pendingCount>0||commands.processingCount>0;
|
||||
const cpuProfileStatus=hasCpuProfile?String(s.cpuProfileStatus||'captured'):pendingPerformanceCapture?'pending-command-no-cpu-profile':'no-cpu-profile';
|
||||
const attributionMode=hasCpuProfile?'cpu-profile-and-performance-observer':hasPerformanceObserverEvidence?'loaf-only-no-cpu-profile':'no-frontend-performance-evidence';
|
||||
const statement=hasCpuProfile
|
||||
? 'CPU profile artifacts are present; hotspot rows may be used as CPU profile evidence.'
|
||||
: pendingPerformanceCapture
|
||||
? 'LoAF-only / no CPU profile: performanceCapture is pending or processing, so CPU profile hotspots are unavailable for this run.'
|
||||
: 'LoAF-only / no CPU profile: no completed performanceCapture artifact is present, so do not cite CPU profile hotspots.';
|
||||
return {attributionMode,cpuProfileStatus,hasCpuProfile,noCpuProfile:!hasCpuProfile,loafOnly:!hasCpuProfile&&hasPerformanceObserverEvidence,pendingPerformanceCapture,runner,performanceCaptureCommands:commands,statement,valuesRedacted:true};
|
||||
}
|
||||
function performanceSummaryFromReport(){
|
||||
const perf=report.frontendPerformance&&typeof report.frontendPerformance==='object'?report.frontendPerformance:{};
|
||||
const summary=perf.summary&&typeof perf.summary==='object'?perf.summary:{};
|
||||
const findings=Array.isArray(report.findings)?report.findings.filter((item)=>String(item?.id||item?.kind||'').match(/^frontend-(?:long|event-loop|cpu-profile|performance)/u)).slice(0,6).map(compactPerfFinding):[];
|
||||
const captureRows=Array.isArray(perf.captures)?perf.captures:[];
|
||||
return {
|
||||
summary:{...summary, rawPerformanceRowCount:performanceRows.length, valuesRedacted:true},
|
||||
const commandFiles=readCommandFiles();
|
||||
const model={summary:{...summary, rawPerformanceRowCount:performanceRows.length, valuesRedacted:true},
|
||||
longTasks:Array.isArray(perf.longTasks)?perf.longTasks.slice(0,3).map(compactPerfEvent):[],
|
||||
longAnimationFrames:Array.isArray(perf.longAnimationFrames)?perf.longAnimationFrames.slice(0,3).map(compactPerfEvent):[],
|
||||
eventLoopGaps:Array.isArray(perf.eventLoopGaps)?perf.eventLoopGaps.slice(0,3).map(compactPerfEvent):[],
|
||||
@@ -761,16 +817,31 @@ function performanceSummaryFromReport(){
|
||||
profileStacks:Array.isArray(perf.profileStacks)?perf.profileStacks.slice(0,3).map(compactProfileStack):[],
|
||||
captures:captureRows.slice(-3).map(compactPerfCapture),
|
||||
findings,
|
||||
toolFindings:performanceToolFindings(),
|
||||
valuesRedacted:true
|
||||
};
|
||||
model.evidenceMode=performanceEvidenceMode(model,commandFiles);
|
||||
return {
|
||||
...model
|
||||
};
|
||||
}
|
||||
function renderPerformanceSummary(perf){
|
||||
const s=perf.summary||{};
|
||||
const mode=perf.evidenceMode||{};
|
||||
const captureCommands=mode.performanceCaptureCommands||{};
|
||||
const runner=mode.runner||{};
|
||||
const lines=['WEB-PROBE frontend performance '+(manifest.jobId||'-'),'======================================================='];
|
||||
lines.push('events='+String(s.eventCount??0)+' rawRows='+String(s.rawPerformanceRowCount??0)+' longTask='+String(s.longTaskCount??0)+' loaf='+String(s.longAnimationFrameCount??0)+' eventLoopGap='+String(s.eventLoopGapCount??0)+' captures='+String(s.captureCount??0));
|
||||
lines.push('max longTask='+String(s.maxLongTaskMs??'-')+'ms budget='+String(s.longTaskRedMs??'-')+'ms; max LoAF='+String(s.maxLongAnimationFrameMs??'-')+'ms budget='+String(s.longAnimationFrameRedMs??'-')+'ms; max gap='+String(s.maxEventLoopGapMs??'-')+'ms budget='+String(s.eventLoopGapRedMs??'-')+'ms');
|
||||
lines.push('evidence attribution='+String(mode.attributionMode||s.attributionMode||'-')+' cpuProfile='+String(mode.cpuProfileStatus||s.cpuProfileStatus||'-')+' pendingPerformanceCapture='+String(captureCommands.pendingCount??0)+' processingPerformanceCapture='+String(captureCommands.processingCount??0)+' runner='+String(runner.status||'-')+' runnerNotRunningOrFailed='+String(runner.notRunningOrFailed===true));
|
||||
if(mode.statement) lines.push('status: '+mode.statement);
|
||||
if(mode.pendingPerformanceCapture===true){
|
||||
const pending=[...(captureCommands.pending||[]),...(captureCommands.processing||[])].slice(0,4).map((item)=>String(item.id||'-')+'('+String(item.bucket||'-')+')').join(', ');
|
||||
lines.push('pending performanceCapture: '+(pending||'-'));
|
||||
}
|
||||
if(runner.notRunningOrFailed===true) lines.push('runner state: not-running/failed for performance evidence; treat missing CPU profile as tool state, not as proof that no CPU hotspot exists.');
|
||||
lines.push('','CPU profile hotspots');
|
||||
if(perf.profileHotspots.length===0) lines.push('-');
|
||||
if(perf.profileHotspots.length===0) lines.push(mode.noCpuProfile===true?'(no CPU profile hotspots; no completed performanceCapture artifact in this run)':'-');
|
||||
for(const item of perf.profileHotspots.slice(0,10)) lines.push(String(item.selfTimeMs??0)+'ms self '+short(item.functionName||'(anonymous)',44)+' '+short(item.url||item.scriptId||'-',92)+' line='+String(item.lineNumber??'-')+' captures='+String(item.captureCount??'-'));
|
||||
lines.push('','CPU profile stacks');
|
||||
if(perf.profileStacks.length===0) lines.push('-');
|
||||
@@ -786,6 +857,9 @@ function renderPerformanceSummary(perf){
|
||||
lines.push('','Findings');
|
||||
if(perf.findings.length===0) lines.push('-');
|
||||
for(const item of perf.findings.slice(0,12)) lines.push(String(item.severity||'-')+': '+String(item.id||item.kind||'-')+' count='+String(item.count??'-')+' '+short(item.summary||item.message||'',150));
|
||||
lines.push('','Tool status');
|
||||
if(!Array.isArray(perf.toolFindings)||perf.toolFindings.length===0) lines.push('-');
|
||||
for(const item of (perf.toolFindings||[]).slice(0,10)) lines.push(String(item.severity||'-')+': '+String(item.id||'-')+' count='+String(item.count??'-')+' '+short(item.summary||'',150));
|
||||
lines.push('','NEXT',' capture: bun scripts/cli.ts web-probe observe command '+(manifest.jobId||'<observer>')+' --type performanceCapture --duration-ms 5000 --wait-ms 8000',' analyze: bun scripts/cli.ts web-probe observe analyze '+(manifest.jobId||'<observer>'),'DISCLOSURE source=existing artifacts valuesRedacted=true; this view does not start browser/probe or mutate runtime.');
|
||||
return lines.join('\\n');
|
||||
}
|
||||
@@ -816,7 +890,7 @@ function renderProjectSummary(project){
|
||||
const rows=turnSummaryRows();
|
||||
if(view==='performance-summary'){
|
||||
const perf=performanceSummaryFromReport();
|
||||
console.log(JSON.stringify({ok:true,command:'web-probe-observe collect',view,stateDir:dir,summary:perf.summary,artifactFileCount:files.length,skippedFileCount:skippedFiles.length,renderedText:renderPerformanceSummary(perf),sourceFiles:['performance-events.jsonl','artifacts.jsonl','analysis/report.json'],drillDown:'bun scripts/cli.ts web-probe observe collect '+String(manifest.jobId||'<observer>')+' --view files --file analysis/report.json',valuesRedacted:true}));
|
||||
console.log(JSON.stringify({ok:true,command:'web-probe-observe collect',view,stateDir:dir,summary:perf.summary,evidenceMode:perf.evidenceMode,artifactFileCount:files.length,skippedFileCount:skippedFiles.length,renderedText:renderPerformanceSummary(perf),sourceFiles:['performance-events.jsonl','artifacts.jsonl','analysis/report.json','commands/pending/*.json','commands/processing/*.json','heartbeat.json','manifest.json'],drillDown:'bun scripts/cli.ts web-probe observe collect '+String(manifest.jobId||'<observer>')+' --view files --file analysis/report.json',valuesRedacted:true}));
|
||||
process.exit(0);
|
||||
}
|
||||
if(view==='project-summary'||view==='project-mdtodo-summary'){
|
||||
|
||||
Reference in New Issue
Block a user