fix: bind performance evidence to production resources
This commit is contained in:
@@ -133,3 +133,85 @@ export const evaluateRuntimeBudgetEvidence = (evidence) => {
|
||||
});
|
||||
return { tiers, violations };
|
||||
};
|
||||
|
||||
export const evaluateRuntimeTraceBudgetEvidence = (evidence) => {
|
||||
if (evidence?.schema !== 'portfolio-runtime-trace-evidence/v1' || evidence.allocation === null || typeof evidence.allocation !== 'object') {
|
||||
throw new Error('Runtime trace evidence must use portfolio-runtime-trace-evidence/v1.');
|
||||
}
|
||||
const evaluated = evaluateRuntimeBudgetEvidence({
|
||||
schema: 'portfolio-runtime-budget-evidence/v1',
|
||||
peakRequests: evidence.peakRequests,
|
||||
tiers: [
|
||||
evidence.allocation,
|
||||
evidence.allocation.tier === 'low'
|
||||
? { tier: 'high', textures: [], geometryBytes: 0, renderTargets: [] }
|
||||
: { tier: 'low', textures: [], geometryBytes: 0, renderTargets: [] },
|
||||
],
|
||||
});
|
||||
const tier = evaluated.tiers.find((item) => item.tier === evidence.allocation.tier);
|
||||
if (tier === undefined) throw new Error('Runtime trace tier could not be evaluated.');
|
||||
return {
|
||||
tier,
|
||||
violations: evaluated.violations,
|
||||
};
|
||||
};
|
||||
|
||||
export const validateRuntimeBudgetTraceReport = (report) => {
|
||||
if (report?.schema !== 'runtime-budget-trace.v1'
|
||||
|| report.evidenceKind !== 'authenticated-test-build-production-path'
|
||||
|| report.gatingReferenceResult !== false) {
|
||||
throw new Error('Runtime budget verification requires an authenticated production-path capture.');
|
||||
}
|
||||
const expectedProfile = report.tier === 'high' ? 'desktop-high' : report.tier === 'low' ? 'mobile-low' : null;
|
||||
if (expectedProfile === null || report.profile !== expectedProfile) {
|
||||
throw new Error('Runtime budget capture profile and tier disagree.');
|
||||
}
|
||||
const viewport = report.observed?.viewport;
|
||||
const devicePixelRatio = report.observed?.devicePixelRatio;
|
||||
const rendererPixelRatio = report.observed?.rendererPixelRatio;
|
||||
const expectedRendererPixelRatio = report.tier === 'high' ? 1.5 : 1.25;
|
||||
if (!finitePositiveInteger(viewport?.width) || !finitePositiveInteger(viewport?.height)
|
||||
|| !(Number.isFinite(devicePixelRatio) && devicePixelRatio >= expectedRendererPixelRatio)
|
||||
|| rendererPixelRatio !== expectedRendererPixelRatio) {
|
||||
throw new Error('Runtime budget capture did not exercise the named tier DPR cap.');
|
||||
}
|
||||
const evidence = report.runtimeBudgetEvidence;
|
||||
if (evidence?.allocation?.tier !== report.tier || !Array.isArray(evidence.allocation.renderTargets)) {
|
||||
throw new Error('Runtime budget capture allocation and tier disagree.');
|
||||
}
|
||||
const framebuffers = evidence.allocation.renderTargets.filter((target) => target?.id === 'default-framebuffer');
|
||||
const expectedWidth = Math.round(viewport.width * rendererPixelRatio);
|
||||
const expectedHeight = Math.round(viewport.height * rendererPixelRatio);
|
||||
if (framebuffers.length !== 1 || framebuffers[0].width !== expectedWidth || framebuffers[0].height !== expectedHeight) {
|
||||
throw new Error('Runtime budget capture framebuffer dimensions do not match the observed viewport and DPR cap.');
|
||||
}
|
||||
return evaluateRuntimeTraceBudgetEvidence(evidence);
|
||||
};
|
||||
|
||||
const requestClass = (entry) => {
|
||||
if (entry.initiatorType === 'video' || /\.(?:mp4|webm)(?:[?#]|$)/iu.test(entry.name)) return 'video';
|
||||
if (entry.initiatorType === 'img' || /\/assets\/(?:environment-|high-tier-)/u.test(entry.name)) return 'imageScene';
|
||||
return null;
|
||||
};
|
||||
|
||||
export const calculatePeakRequestConcurrency = (entries) => {
|
||||
if (!Array.isArray(entries)) throw new Error('Resource timing entries are required.');
|
||||
const peaks = { imageScene: 0, video: 0 };
|
||||
for (const kind of ['imageScene', 'video']) {
|
||||
const events = [];
|
||||
for (const entry of entries) {
|
||||
if (requestClass(entry) !== kind) continue;
|
||||
if (!Number.isFinite(entry.startTime) || !Number.isFinite(entry.responseEnd) || entry.responseEnd < entry.startTime) {
|
||||
throw new Error('Tracked resource timing is incomplete.');
|
||||
}
|
||||
events.push({ at: entry.startTime, delta: 1 }, { at: entry.responseEnd, delta: -1 });
|
||||
}
|
||||
events.sort((left, right) => left.at - right.at || left.delta - right.delta);
|
||||
let active = 0;
|
||||
for (const event of events) {
|
||||
active += event.delta;
|
||||
peaks[kind] = Math.max(peaks[kind], active);
|
||||
}
|
||||
}
|
||||
return peaks;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user