Skip to content

Instantly share code, notes, and snippets.

@noamr
Last active February 7, 2024 08:55
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save noamr/316bd48157ab35e4f632a8c2583281b7 to your computer and use it in GitHub Desktop.
Save noamr/316bd48157ab35e4f632a8c2583281b7 to your computer and use it in GitHub Desktop.
WhyNP: Analyze LoAFs & event timing entries to find cause of bad INP
(function() {
let pending_loaf_entries = [];
let pending_event_entries = [];
let timeout_handle = null;
const combined_map = new Map();
function print() {
const entries = [...combined_map.entries()].sort((a, b) => b.duration - a.duration);
console.log(entries.map(([loaf, event]) => {
let longest_script = null;
let total_thrashing_duration = 0;
for (const script of loaf.scripts) {
total_thrashing_duration += script.forcedStyleAndLayoutDuration;
if (script.duration > (longest_script?.duration ?? 0))
longest_script = script;
}
return {
loaf_blocking_duration: loaf.blockingDuration,
event_duration: event.duration,
longest_script,
total_thrashing_duration,
render_time: (loaf.startTime + loaf.duration - loaf.renderStart),
loaf, event
};
}));
}
function process() {
for (const event_entry of pending_event_entries) {
for (const loaf_entry of pending_loaf_entries) {
if (!loaf_entry.blockingDuration)
continue;
/* This LoAF ends before this event starts.*/
if ((loaf_entry.startTime + loaf_entry.duration) < event_entry.startTime)
continue;
/* This LoAF starts after this event ends. */
if (loaf_entry.startTime > (event_entry.startTime + event_entry.duration))
break;
if (event_entry.duration > (combined_map.get(loaf_entry)?.duration ?? 0))
combined_map.set(loaf_entry, event_entry);
}
}
pending_loaf_entries = [];
pending_event_entries = [];
print();
}
function schedule_process() {
// Debounce, to make sure all the relevant entries are in.
if (timeout_handle)
clearTimeout(timeout_handle);
timeout_handle = setTimeout(() => {
timeout_handle = null;
process();
}, 1000);
}
new PerformanceObserver(entries => {
pending_loaf_entries = [...pending_loaf_entries, ...entries.getEntries()];
}).observe({type: "long-animation-frame", buffered: true});
new PerformanceObserver(entries => {
const unique_entries = Object.values(Object.fromEntries(entries.getEntries().map(entry => [JSON.stringify(entry), entry])));
pending_event_entries = [...pending_event_entries, ...unique_entries];
schedule_process();
}).observe({type: "event", buffered: true, durationThreshold: 150});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment