Skip to content

Instantly share code, notes, and snippets.

@noamr
Last active October 10, 2023 04:32
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save noamr/2d4e2bdd48661fa39e32283efecaf418 to your computer and use it in GitHub Desktop.
Save noamr/2d4e2bdd48661fa39e32283efecaf418 to your computer and use it in GitHub Desktop.
Diagnose INP issues by correlating event timing & long animation frames
(function init() {
function processAndFilterLoAFs(entries) {
function floorObject(o) {
return Object.fromEntries(Array.from(Object.entries(o)).map(([key, value]) => [key, typeof value === "number" ? Math.floor(value) :
value
]))
}
function processEntry(entry) {
const startTime = entry.startTime;
const endTime = entry.startTime + entry.duration;
const delay = entry.desiredRenderStart ? Math.max(0, entry.startTime - entry.desiredRenderStart) : 0;
const deferredDuration = Math.max(0, entry.desiredRenderStart - entry.startTime);
const renderDuration = entry.styleAndLayoutStart - entry.renderStart;
const workDuration = entry.renderStart ? entry.renderStart - entry.startTime : entry.duration;
const totalForcedStyleAndLayoutDuration = entry.scripts.reduce((sum, script) => sum + script.forcedStyleAndLayoutDuration, 0);
const styleAndLayoutDuration = entry.styleAndLayoutStart ? endTime - entry.styleAndLayoutStart : 0;
const scripts = entry.scripts.map(script => {
const delay = script.startTime - script.desiredExecutionStart;
const scriptEnd = script.startTime + script.duration;
const compileDuration = script.executionStart - script.startTime;
const execDuration = scriptEnd - script.executionStart;
return floorObject({
delay,
compileDuration,
execDuration,
...script.toJSON()
});
})
return floorObject({
startTime,
delay,
deferredDuration,
renderDuration,
workDuration,
styleAndLayoutDuration,
totalForcedStyleAndLayoutDuration,
...entry.toJSON(),
scripts
});
}
return entries.map(processEntry);
}
function analyze() {
return loafs.map(loaf => ({
blockingDuration: loaf.blockingDuration,
loaf,
scripts: loaf.scripts,
events: events.filter(e => overlap(e, loaf))
})).filter(l => (l.blockingDuration && l.events.length));
}
let loafs = [];
let events = [];
function processLoAFs(entries) {
loafs = [...loafs, ...processAndFilterLoAFs(entries.getEntries())];
console.log(analyze());
}
function processEvents(entries) {
events = [...events, ...entries.getEntries()];
console.log(analyze());
}
new PerformanceObserver(processLoAFs).observe({
type: "long-animation-frame",
buffered: true
});
new PerformanceObserver(processEvents).observe({
type: "event",
buffered: true
});
function overlap(e1, e2) {
return e1.startTime < (e2.startTime + e2.duration) &&
e2.startTime < (e1.startTime + e1.duration)
}
window.whynp = analyze;
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment