Skip to content

Commit

Permalink
MWPW-154969 Log LCP and key user attributes
Browse files Browse the repository at this point in the history
This adds the ability to log performance data back to Lana.

To enable the feature, metadata `pageperf = ‘on’ ` must be set for the page.  This feature only works in Chrome browsers.

Additionally `pageperf-rate` can be set to determine what percentage of pages will send data.  E.g `pageperf-rate=15` would have 15% of the pages send perf data.
The default rate is `50`%.

The sending of the data to Lana is delayed so as to not affect any parts of the page load.

Data sent:
* Chrome Version
* CLS Score
* Country (only if georouting is enabled via geo2)
* Downlink: Mbps - Chrome caps value at `10`
* LCP Score
* LCP Element Type
* LCP URL
  * If there is no identifying url, the element html will be used.
* Adobe IMS Logged In Status
* OS
* URL of page
* Window Width & Height
* MEP Manifest Data
  * Manifest(s) Used (only the ones that have been applied to the page)
  * Selected Target in the manifest
  • Loading branch information
chrischrischris committed Aug 2, 2024
1 parent 071a858 commit 1518047
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 0 deletions.
97 changes: 97 additions & 0 deletions libs/utils/logWebVitals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
const LANA_CLIENT_ID = 'pageperf';

let lanaSent = false;

function sendToLana(lanaData) {
if (lanaSent) return;
const ua = window.navigator.userAgent;

Object.assign(lanaData, {
chromeVer: (ua.match(/Chrome\/(\d+\.\d+\.\d+\.\d+)/) || [])[1] || '',
country: sessionStorage.getItem('akamai') || '',
// eslint-disable-next-line compat/compat
downlink: window.navigator?.connection?.downlink || '',
loggedIn: window.adobeIMS?.isSignedInUser() || false,
os: (ua.match(/Windows/) && 'win')
|| (ua.match(/Mac/) && 'mac')
|| (ua.match(/Android/) && 'android')
|| (ua.match(/Linux/) && 'linux')
|| '',
windowHeight: window.innerHeight,
windowWidth: window.innerWidth,
url: `${window.location.host}${window.location.pathname}`,
});

lanaData.cls ||= 0;
const lanaDataStr = Object.entries(lanaData)
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, value]) => `${key}=${value}`)
.join(',');

window.lana.log(lanaDataStr, {
clientId: LANA_CLIENT_ID,
sampleRate: 100,
});

lanaSent = true;
}

function observeCLS(lanaData) {
let cls = 0;
/* c8 ignore start */
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
if (!entry.hadRecentInput) {
cls += entry.value;
}
}
lanaData.cls = cls.toPrecision(4);
}).observe({ type: 'layout-shift', buffered: true });
}

function getElementInfo(el) {
const elSrc = el.src || el.currentSrc || el.href || el.poster;
if (elSrc) {
try {
const srcUrl = new URL(elSrc);
return srcUrl.origin === window.location.origin ? srcUrl.pathname : srcUrl.href;
} catch {
// fall through
}
}
const elHtml = el.outerHTML.replaceAll(',', '');
if (elHtml.length <= 100) return elHtml;
return `${el.outerHTML.substring(0, 100)}...`;
}

function observeLCP(lanaData, delay) {
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1]; // Use the latest LCP candidate
lanaData.lcp = parseInt(lastEntry.startTime, 10);
const lcpEl = lastEntry.element;
lanaData.lcpElType = lcpEl.nodeName.toLowerCase();
lanaData.lcpEl = getElementInfo(lcpEl);

setTimeout(() => {
sendToLana(lanaData);
}, parseInt(delay, 10));
}).observe({ type: 'largest-contentful-paint', buffered: true });
/* c8 ignore stop */
}

function logMepExperiments(lanaData, mep) {
mep?.experiments?.forEach((exp, idx) => {
// only log manifests that affect the page
if (exp.selectedVariantName === 'default') return;
lanaData[`manifest${idx + 1}path`] = exp.manifestPath;
lanaData[`manifest${idx + 1}selected`] = exp.selectedVariantName;
});
}

export default function webVitals(mep, { delay = 1000 } = {}) {
const lanaData = {};
logMepExperiments(lanaData, mep);
observeCLS(lanaData);
observeLCP(lanaData, delay);
}
17 changes: 17 additions & 0 deletions libs/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,21 @@ export function scrollToHashedElement(hash) {
});
}

function logPagePerf() {
const isChrome = () => {
const nav = window.navigator;
return nav.userAgent.includes('Chrome') && nav.vendor.includes('Google');
};

if (getMetadata('pageperf') === 'on' && isChrome()) {
const sampleRate = parseInt(getMetadata('pageperf-rate'), 10) || 50;
if (Math.random() * 100 <= sampleRate) {
import('./logWebVitals.js')
.then((mod) => mod.default(getConfig().mep, getMetadata('pageperf-delay') || 1000));
}
}
}

export async function loadDeferred(area, blocks, config) {
const event = new Event(MILO_EVENTS.DEFERRED);
area.dispatchEvent(event);
Expand Down Expand Up @@ -1025,6 +1040,8 @@ export async function loadDeferred(area, blocks, config) {
sampleRUM.observe(blocks);
sampleRUM.observe(area.querySelectorAll('picture > img'));
});

logPagePerf();
}

function initSidekick() {
Expand Down
53 changes: 53 additions & 0 deletions test/utils/logWebVitals.test.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 55 additions & 0 deletions test/utils/logWebVitalsUtils.test.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 1518047

Please sign in to comment.