Problem
The JavaScript agent currently sends data 1,000ms after the Page Load event, in order to capture resources and metrics which change immediately after the event.
There are cases, however, where this delay is not sufficient. For example where a client-side A/B testing framework is used which manipulates the DOM after Page Load, resulting in resources and events (such as Largest Contentful Paint) that occur >1,000ms after the transaction is closed and shipped to APM. Other cases may include slow loading lazy images and third-party content, or complex operations such as flight availability checks or insurance quotes. Further, JavaScript errors which occur after the transaction ends are not collected.
Taking LCP as an example, Google measures LCP as the last candidate before a user interaction. This results in a situation where Elastic APM can report an earlier candidate than other tools (browser developer tools, web-vitals, CrUX) resulting in a discrepancy in aggregate LCP values.
Proposed Solutions
- Statically increase
PAGE_LOAD_DELAY (5,000ms?) - this will make it less likely that spans, events and metrics which occur shortly after Page Load are missed in the JavaScript agent.
- Dynamically increase the delay if there are active spans when the
PAGE_LOAD_DELAY duration expires, up to a maximum delay.
- Increase data collected in the
page_exit beacon (currently used for INP), to update metrics such as LCP and add spans which occurred after the initial transaction ends
- Add incremental beacons during the page lifecycle to add spans, update metrics etc.
Workaround
It is possible to modify the agent's behavior with a blocking span, holding the transaction open until a fixed / dynamic threshold is met.
This adds a span to transactions which must ultimately be ignored, and requires implementation effort to determine the correct logic for transaction ending.
window.addEventListener('load', () => {
const FALLBACK_PAGE_LOAD_DELAY = 5000;
// retrieve the current Elastic APM transaction (page load)
const tr = elasticApm.getCurrentTransaction();
// attach a blocking span to the transaction, preventing the transaction closing until the span ends
const span = tr.startSpan("delay-span-ignore", "delay", { blocking: true });
// race a fallback promise with a custom promise which resolves when all dynamic content is complete
// note `sleep(time)` should be implemented as a promise that resolves with a `setTimeout(time)`
Promise.race([
sleep(FALLBACK_PAGE_LOAD_DELAY),
clientScriptsComplete()
]).then(()=>{
span.end();
});
// OR manually end the transaction afer a set time
// setTimeout(()=>{span.end()},FALLBACK_PAGE_LOAD_DELAY);
});
Problem
The JavaScript agent currently sends data 1,000ms after the Page Load event, in order to capture resources and metrics which change immediately after the event.
There are cases, however, where this delay is not sufficient. For example where a client-side A/B testing framework is used which manipulates the DOM after Page Load, resulting in resources and events (such as Largest Contentful Paint) that occur >1,000ms after the transaction is closed and shipped to APM. Other cases may include slow loading lazy images and third-party content, or complex operations such as flight availability checks or insurance quotes. Further, JavaScript errors which occur after the transaction ends are not collected.
Taking LCP as an example, Google measures LCP as the last candidate before a user interaction. This results in a situation where Elastic APM can report an earlier candidate than other tools (browser developer tools, web-vitals, CrUX) resulting in a discrepancy in aggregate LCP values.
Proposed Solutions
PAGE_LOAD_DELAY(5,000ms?) - this will make it less likely that spans, events and metrics which occur shortly after Page Load are missed in the JavaScript agent.PAGE_LOAD_DELAYduration expires, up to a maximum delay.page_exitbeacon (currently used for INP), to update metrics such as LCP and add spans which occurred after the initial transaction endsWorkaround
It is possible to modify the agent's behavior with a blocking span, holding the transaction open until a fixed / dynamic threshold is met.
This adds a span to transactions which must ultimately be ignored, and requires implementation effort to determine the correct logic for transaction ending.