Skip to content

Instantly share code, notes, and snippets.

@noamr
Last active May 28, 2024 17:20
Show Gist options
  • 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});
})();
@jinja12
Copy link

jinja12 commented May 28, 2024

Could you please explain the code if possible ? I had injected the above function into my console where I was running my Nextjs application built over Typescript as described here in this issue: GoogleChrome/web-vitals#489

Following is the output received form the above script:
My input was clicking on the Add Item (4s) button and immediately then clicking on the Show button to display the animation frame. The code and the deployed website is linked in the above issue for reference.

UI:
image

Console:
image

Please note that this was simulated on the Iphone 14 Pro Max dimensions alonside 4x CPU Throttling.
Could you please tell what new insights the above script provides apart from the regular web-vitals-attribution output from the onINP() function ? The source function name and URL seems to differ from the expected output as I have described in the issue.

Thanks a lot in advance!

@jinja12
Copy link

jinja12 commented May 28, 2024

@noamr Sorry for the delayed ping :/

@noamr
Copy link
Author

noamr commented May 28, 2024

This gist is discontinued, it's integrated into https://github.com/GoogleChrome/web-vitals

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment