Skip to content

Instantly share code, notes, and snippets.

@tunetheweb
Created September 26, 2024 19:03
Show Gist options
  • Save tunetheweb/afa7f7aabde02aa3632ca4b12f380bb4 to your computer and use it in GitHub Desktop.
Save tunetheweb/afa7f7aabde02aa3632ca4b12f380bb4 to your computer and use it in GitHub Desktop.
<script>
(function () {
function reportWebVitals(metric, debugData) {
var name = metric.name,
delta = metric.delta,
id = metric.id,
navigationType = metric.navigationType,
value = metric.value,
entry = metric.entries[0];
if (name === 'CLS') {
value *= 1000;
delta *= 1000;
}
var deviceMemory;
if ('deviceMemory' in navigator) {
deviceMemory = navigator.deviceMemory.toString();
}
var log = {
event: name,
eventCategory: 'Web Vitals',
eventLabel: id,
eventModel: formatData(Object.assign(debugData, {
metric_value: value,
metric_delta: delta,
metric_id: id,
metric_start_time: entry && entry.startTime,
navigation_type: navigationType,
device_memory: deviceMemory
}))
};
window.dataLayer = window.dataLayer || [];
console.log('Web Vitals', name, log.eventModel.metric_value, log);
window.dataLayer.push(log);
}
function reportLCP(metric) {
var attribution = metric.attribution;
var targetElement = attribution.lcpEntry && attribution.lcpEntry.element;
var notRestoredReasons = null;
var navigationEntry = attribution.navigationEntry;
if (navigationEntry) {
var activationStart = navigationEntry.activationStart || 0;
var redirectDuration = navigationEntry.redirectEnd - navigationEntry.redirectStart;
var waitingDuration = navigationEntry.workerStart || navigationEntry.fetchStart;
var cacheDuration = navigationEntry.domainLookupStart - waitingDuration;
var dnsDuration = (navigationEntry.connectStart - navigationEntry.dnsStart) || 0;
var connectionDuration = navigationEntry.connectEnd - navigationEntry.connectStart;
var serverResponseDuration = attribution.timeToFirstByte - navigationEntry.connectEnd;
if (navigationEntry.notRestoredReasons && navigationEntry.notRestoredReasons.reasons) {
notRestoredReasons = notRestoredReasons.reasons.map(function mapReasons(details) {
return details.reason;
}).join(',');
}
}
var debugData = {
debug_time_to_first_byte: attribution.timeToFirstByte,
debug_resource_load_delay: attribution.resourceLoadDelay,
debug_resource_load_duration: attribution.resourceLoadDuration,
debug_element_render_delay: attribution.elementRenderDelay,
debug_url: attribution.url,
debug_target: attribution.element || '(not set)',
debug_target_node_name: targetElement && targetElement.nodeName,
debug_target_loading_attr: targetElement && targetElement.getAttribute('loading'),
debug_nav_activation_start: activationStart,
debug_nav_redirect_duration: redirectDuration,
debug_nav_waiting_duration: waitingDuration,
debug_nav_cache_duration: cacheDuration,
debug_nav_dns_duration: dnsDuration,
debug_nav_connect_duration: connectionDuration,
debug_nav_server_response_duration: serverResponseDuration,
debug_not_restored_reasons: notRestoredReasons
};
reportWebVitals(metric, debugData);
}
function reportCLS(metric) {
var attribution = metric.attribution;
var debugData = {
debug_time: attribution.largestShiftTime,
debug_load_state: attribution.loadState,
debug_target: attribution.largestShiftTarget || '(not set)',
};
reportWebVitals(metric, debugData);
}
function reportINP(metric) {
var attribution = metric.attribution;
var loafData = getLoafAttribution(attribution);
var debugData = Object.assign({
debug_event: attribution.interactionType,
debug_time: attribution.interactionTime,
debug_load_state: attribution.loadState,
debug_target: attribution.interactionTarget || '(not set)',
debug_input_delay: attribution.inputDelay,
debug_processing_duration: attribution.processingDuration,
debug_presentation_delay: attribution.presentationDelay,
debug_processed_event_entries_length: attribution.processedEventEntries.length
}, loafData);
reportWebVitals(metric, debugData);
}
function getLoafAttribution(attribution) {
var loafEntries = attribution && attribution.longAnimationFrameEntries;
var loafEntriesLength = loafEntries && loafEntries.length || 0;
if (loafEntriesLength === 0) {
return {
debug_loaf_meta_length: 0
};
}
// The last LoAF entry is usually the most relevant.
var loaf = loafEntries[loafEntriesLength - 1];
var loafEndTime = loaf.startTime + loaf.duration;
var loafAttribution = {
// Stats for the LoAF entry itself.
debug_loaf_entry_start_time: loaf.startTime,
debug_loaf_entry_end_time: loafEndTime,
debug_loaf_entry_work_duration: loaf.renderStart ? loaf.renderStart - loaf.startTime : loaf.duration,
debug_loaf_entry_render_duration: loaf.renderStart ? loafEndTime - loaf.renderStart : 0,
debug_loaf_entry_pre_layout_duration: loaf.styleAndLayoutStart ? loaf.styleAndLayoutStart - loaf.renderStart : 0,
debug_loaf_entry_style_and_layout_duration: loaf.styleAndLayoutStart ? loafEndTime - loaf.styleAndLayoutStart : 0,
// LoAF metadata.
debug_loaf_meta_length: loafEntriesLength,
debug_loaf_meta_script_length: loaf.scripts.length,
};
// Stats across all LoAF entries and scripts.
var interactionTime = attribution.interactionTime;
var inputDelay = attribution.inputDelay;
var processingDuration = attribution.processingDuration;
var totalStyleAndLayout = null;
var totalForcedStyleAndLayout = null;
var phases = {};
loafEntries.forEach(function processLoafEntries(loafEntry) {
totalStyleAndLayout += loafEndTime - loafEntry.styleAndLayoutStart;
loafEntry.scripts.forEach(function processScriptEntries(script) {
var scriptEndTime = script.startTime + script.duration;
if (scriptEndTime < interactionTime) {
return;
}
totalForcedStyleAndLayout += script.forcedStyleAndLayoutDuration;
var blockingDuration = scriptEndTime - Math.max(interactionTime, script.startTime);
var invokerType = script.invokerType.replace('-', '_');
var phase = 'phase2';
if (script.startTime < (interactionTime + inputDelay)) {
phase = 'phase1';
} else if (script.startTime > (interactionTime + inputDelay + processingDuration)) {
phase = 'phase3';
}
if (!(phase in phases)) {
phases[phase] = {};
}
if (!(invokerType in phases[phase])) {
phases[phase][invokerType] = 0;
}
phases[phase][invokerType] += blockingDuration;
});
});
Object.entries(phases).forEach(function processPhases(phaseEntry) {
var phase = phaseEntry[0];
var invokers = phaseEntry[1];
Object.entries(invokers).forEach(function processInvokers(invokerEntry) {
var invoker = invokerEntry[0];
var blockingDuration = invokerEntry[1];
loafAttribution['debug_loaf_' + phase + '_' + invoker] = blockingDuration;
});
});
loafAttribution.debug_loaf_total_style_layout_duration = totalStyleAndLayout;
loafAttribution.debug_loaf_entry_total_forced_style_and_layout_duration = totalForcedStyleAndLayout;
// Stats for the slowest script in the LoAF entry.
var scriptAttribution = {};
var slowestScriptDuration = 0;
loaf.scripts.forEach(function forEachScript(script) {
var scriptEndTime = script.startTime + script.duration;
var blockingDuration = scriptEndTime - Math.max(interactionTime, script.startTime);
if (interactionTime < scriptEndTime && blockingDuration <= slowestScriptDuration) {
return;
}
slowestScriptDuration = blockingDuration;
var phase = 'processing';
if (script.startTime < (interactionTime + inputDelay)) {
phase = 'input delay';
} else if (script.startTime > (interactionTime + inputDelay + processingDuration)) {
phase = 'presentation';
}
scriptAttribution = {
debug_loaf_script_phase: phase,
debug_loaf_script_blocking_duration: blockingDuration,
debug_loaf_script_total_duration: script.duration,
debug_loaf_script_compile_duration: script.executionStart - script.startTime,
debug_loaf_script_exec_duration: script.startTime + script.duration - script.executionStart,
debug_loaf_script_forced_style_and_layout_duration: script.forcedStyleAndLayoutDuration,
debug_loaf_script_pause_duration: script.pauseDuration,
debug_loaf_script_type: script.invokerType,
debug_loaf_script_invoker: script.invoker,
debug_loaf_script_source_url: script.sourceURL,
debug_loaf_script_source_function_name: script.sourceFunctionName,
debug_loaf_script_source_char_position: script.sourceCharPosition,
}
});
return Object.assign(loafAttribution, scriptAttribution);
}
function formatData(debugData) {
return Object.fromEntries(Object.entries(debugData).map(function formatDataEntry(kv) {
var k = kv[0];
var v = kv[1];
// Convert all floats to ints.
return [k, typeof v == 'number' ? Math.round(v) : v];
}));
}
var script = document.createElement('script');
script.src =
'https://unpkg.com/web-vitals/dist/web-vitals.attribution.iife.js';
script.onload = function () {
webVitals.onLCP(reportLCP);
webVitals.onCLS(reportCLS);
webVitals.onINP(reportINP);
};
document.head.appendChild(script);
})();
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment