elastic / apm-agent-rum-js

https://www.elastic.co/guide/en/apm/agent/rum-js/current/index.html
MIT License
279 stars 136 forks source link

Feature: Add LCP Element Attribution #1378

Open simonhearne opened 1 year ago

simonhearne commented 1 year ago

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});
  }
  //...
});
1davidmichael commented 8 months ago

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 commented 5 months ago

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)
poshaughnessy commented 1 month ago

We would also really like this feature, please. We've done something similar to @1davidmichael, adding code to append labels to the INP and LCP transactions (a bit more complicated in our case though - we added the labels to the existing INP/LCP transactions). It's been very useful for our work to improve our Core Web Vitals. But it's not so nice to maintain the extra code and use it across our applications. Rather than create another library for ourselves, it would be much easier for us if it was a built-in feature in apm-agent-rum-js, please!