apm-agent-rum-js icon indicating copy to clipboard operation
apm-agent-rum-js copied to clipboard

Feature: Add LCP Element Attribution

Open simonhearne opened this issue 1 year ago • 3 comments

The LargestContentfulPaint API exposes the element property for attribution of the element that triggered the LCP value.

This has value in front-end monitoring as it enables developers to determine which elements are responsible for high LCP values, e.g. large hero images, especially as LCP elements can vary by viewport size.

I have created a temporary solution below which captures element.tagName and element.src for the case of images and adds these as labels to the APM beacon. Ideally these would be first class properties on RUM transactions with LCP values.

// capture LCP attribution
var lcp = {};
const observer = new PerformanceObserver((list) => {
  const entries = list.getEntries();
  const lastEntry = entries[entries.length - 1]; // Use the latest LCP candidate
  console.log(lastEntry.element);
  lcp.elementType = lastEntry.element.tagName;
  lcp.elementURL = lastEntry.element.hasAttribute('src') ? lastEntry.element.src : '';
});
observer.observe({ type: "largest-contentful-paint", buffered: true });

//...

elasticApm.observe('transaction:end', tr =>  {
  if (typeof(lcp) !== 'undefined') {
    elasticApm.addLabels({'lcpElementType':lcp.elementType});
    elasticApm.addLabels({'lcpElementURL':lcp.elementURL});
  }
  //...
});

simonhearne avatar Jun 19 '23 13:06 simonhearne

Something like this would also be handy for INP. I'm doing something similar where I'm adding web-vitals with attribution to get the element and class name for the top scoring INP item.

Right now we have an easy way to see if we have bad scores with INP and LCP, but not determine what element is possibly causing it via RUM.

1davidmichael avatar Mar 20 '24 15:03 1davidmichael

I've been working on this for awhile and have come up with a solution where I'm using web-vitals with attribution to add labels on a custom transaction to associate the INP score with what caused it. I'm not sure if its useful but could be handy to implement natively here.

function logToAPM(...args) {
  const inpInfo = args; // Spread syntax to convert arguments to array
  const transaction = elasticApm.startTransaction(pageName(), "inp");
  console.debug(args);

  inpInfo.forEach((inp) => {
    inp.entries.forEach((entry) => {
      const tagName = entry.target?.tagName?.toLowerCase() ?? "N/A";
      const className = entry.target?.className ?? "N/A";

      const labels = {
        inpDuration: inp?.value,
        inpTag: tagName,
        inpClass: className,
        inpRating: inp?.rating,
        inpName: entry?.name,
        templateType: pageName(),
        inpInteractionTarget: inp?.attribution?.interactionTarget,
      };

      const span = transaction.startSpan(entry.name, "inp", {
        startTime: entry.startTime,
      });
      span.addLabels(labels);
      span.end({ endTime: entry.startTime + entry.duration });
    });
  });

  transaction.end();
  console.debug(transaction);
}

webVitals.onINP(logToAPM)

1davidmichael avatar Jun 05 '24 13:06 1davidmichael