Open trainings opened 1 year ago
Hi @trainings,
Thanks for opening such a detailed issue, much appreciated!
Since this is something that would require new development, I would like to know if this workaround can help you in the meanwhile:
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/@elastic/apm-rum@5.12.0/dist/bundles/elastic-apm-rum.umd.min.js"></script>
</head>
<body>
Sample app
<input type="button" value="click me" id="button-clickable" elementtiming="text">
<input type="button" elementtiming="text" value="click me 2" id="button-redirect">
<script>
elasticApm.init({
serviceName: "your-service",
serverUrl: "http://localhost:8200/"
})
// Just after the agent initialization start observing element and kept reference if needed
let timingEntry;
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
// your logic, just an example
timingEntry = entry
});
});
observer.observe({ entryTypes: ["element"]});
</script>
<script>
// observe the page load transaction end
// then, you can replace the calculate LCP
// with the value extracted from element timing if needed
elasticApm.observe("transaction:end", tr => {
if (tr.type === 'page-load') {
if (tr.marks.agent.largestContentfulPaint) {
// your logic here
// you can overwrite the LCP value here if needed
tr.marks.agent.largestContentfulPaint = "your-calculated-value-here"
}
}
})
</script>
<input type="button" elementtiming="text" value="last button" id="button-redirect-last">
</body>
</html>
P.S. I added a few comments in the snippet.
Thanks, Alberto
Hi @devcorpio,
I have another question. If the PerfomanceObserver is triggered after sending the page-load transaction. Maybe we can somehow postpone the sending of this transaction? Or maybe there are better solutions? Ultimately, my LCP is larger than the entire page-load.
I am using the angular version of elastic.
Hi @Shestak2039,
At this moment, we are doing something similar to what you mentioned. Once the browser fires the load event, the RUM agent waits 1 extra second before sending the transaction. The goal of that was to capture "late" LCPs entries. We did that on this PR.
Thanks, Alberto
@devcorpio
I and the author of the issue are from the same team. And we have a problem that one second is not enough. We have a list of elements that takes a long time to return, and we've written elementtiming inside the first element of the table to treat it as an LCP. But now the page-load transaction is sent before we get the rendering time of the first element of the table. Maybe you can suggest some solutions? Or maybe you can make this time configurable when initializing the elastic in the application?
Thanks, Vlad
Hi @Shestak2039,
This is a workaround using the current agent APIs that might help you.
The strategy to follow would be:
The code would be something like this:
window.addEventListener('load', () => {
// This will retrieve the current transaction (page load)
const tr = elasticApm.getCurrentTransaction();
// You can attach a span to it:
const span = tr.startSpan("my-span", "my-span-type", { blocking: true })
// you will need to keep a reference of your span variable.
// Once you have the data you need to report, you can call:
span.end()
// on this particular case, span.end() will call end the transaction automatically
})
You will see that I created a span that extends the transaction's life. (via blocking config option)
Then, since you will decide when the span ends (based on your business logic) you could then overwrite the LCP if needed as suggested in a previous comment. You would also be able to create labels dynamically if needed
Please, let me know if this helps you.
Thanks, Alberto
Hi @devcorpio, I have another question) Is it possible to somehow pass LCP element selector to apm? I'm trying to use your old solution where we replaced the LCP, but the string I am sending cannot be seen.
Hi @Shestak2039,
You should be able to do that via labels. You can add labels to an existing transaction. I'm assuming that you want to attach it to the page-load transaction, is that right?
Example:
elasticApm.observe("transaction:end", tr => {
if (tr.type === 'page-load') {
tr.addLabels({ lcpElementSelector: "your-selector" })
}
})
You should be able to see that in the transaction details:
Also in discover:
Let me know if this helps you. In case it is not, would you mind sharing the snippet you are using?
Cheers, Alberto
Yes, I would like to add to it, thanks. I will check and answer.
@devcorpio Thanks, it works. One question is when we use addLabel. Can't we add a number to the value?
By default (now) this rum agent extract this metric via browser api which works like this: https://web.dev/i18n/en/lcp/#what-elements-are-considered
But unfortunately it works not very well for Boring/Enterprise Applications which contain a lot of tables/forms etc, and very few images, etc. In fact, we very often encounter a situation for SPA applications where LCP calculated by user icon in right corner and this photo loaded and rendered before the table with business data (fetched via API). Roughly speaking, the default algorithm is false positive in such cases.
We would like to enable developers to mark critical (most important) elements on the pages and measure LCP by this elements instead of default algorithm, like described here: https://web.dev/i18n/en/custom-metrics/#element-timing-api
Such custom approach will allow us to measure and optimize aspects of our site's experience that have unique case by case for each applications.
I hope that this fix could be done ~here https://github.com/elastic/apm-agent-rum-js/blob/main/packages/rum-core/src/performance-monitoring/metrics.js#L245
Th target solution could work: