Page-view context
Gives the ID of the current page view to the worker, for its hang reports, and to the crash-report context of Chromium, before a hang starts.
The page-view context gives the ID of the current page view to two parts: the worker and the crash reports of the browser. These parts report while the main thread cannot operate. The context sets the ID at the start and again at each new page view. Thus a hang report or a crash report has the page view of the hang.
What it measures
The page-view context measures nothing. It updates one attribute, lag.page_view.id, in two places:
- The worker. The worker monitor sends a
contextmessage to the worker. The worker adds the context to each hang report and to each record of the hang journal. - The other pages of the origin. The peer hang watch adds the context to each heartbeat. Another page reports a hang of this page with this context.
- The crash-report context of Chromium.
window.crashReportkeeps keys and values that the browser adds to its crash reports, for example when it stops an unresponsive page.
It answers this question: which page view was the page in when it hung or crashed? The main thread cannot give this information during a hang, thus the worker and the browser must have it before the hang starts. Page views explains the unit.
How it works
- At its start, the context reads the current page view of the page-view vitals (
vitals.getView()). - It gives
{ "lag.page_view.id": id }to each receiver withsetContext(). The worker monitor is a receiver: it sends the context to the worker, and it sends the context again after each restart. The peer hang watch is also a receiver. - With a crash-report context, it uses
initialize(512)one time. Then it usesset("lag.page_view.id", id). - It subscribes to the page-view vitals. At each new page view (a restore from the back/forward cache, or a soft navigation), it does steps 2 and 3 again with the new ID.
- At
stop(), it removes the subscription and deletes the key from the crash-report context.
The crash-report context accepts only one initialize(). If another script of the page initialized it first, initialize() fails, and the context uses set() all the same. A failure of set() goes to the logger at the level debug, and the context continues.
setupAllMonitors() also adds lag.page_view.id to each event of the other monitors, through withEventContext(). That wrapper is part of the setup, not of this factory. An attribute of the event itself has priority. Thus an abandoned hang keeps the page-view ID of the page that hung.
Browser support
The worker part works in all browsers that have the worker monitor. The crash-report context is only in Chromium (browser support):
| Feature | Chromium | Firefox | Safari |
|---|---|---|---|
window.crashReport (initialize, set, delete) | 145 (origin trial 140 to 145) | not available | not available |
Crash reports with the reason oom or unresponsive | yes | not available | not available |
The crash-reporting endpoint | 139 | not available | not available |
is_top_level and visibility_state in the report | 138 | not available | not available |
JavaScript call stacks for unresponsive pages (Document-Policy: include-js-call-stacks-in-crash-reports) | 137 | not available | not available |
The browser sends a crash report only to the reporting endpoint of the page. That is the crash-reporting endpoint of the Reporting-Endpoints header, or else the default endpoint. A script cannot read crash reports with a ReportingObserver. The report body has the key-value data in crash_report_api.
Metrics
The page-view context records no metrics and sends no events. The hang reports of the worker and the crash reports of the browser carry its attribute.
Configuration
function createInstrumentedPageViewContext(
deps : Pick<CoreDeps, "logger"> & Partial<CrashReportDeps>,
vitals : PageViewVitals,
receivers : readonly PageContextReceiver[],
) : MonitorHandle<PageViewContext>;
The factory uses the logger, the page-view vitals and the list of receivers. A receiver is an object with setContext(attributes), for example the WorkerLagMonitor. CrashReportDeps (crashReport) is optional. getAttributes() gives the current context.
setupAllMonitors() adds the context only when the page-view vitals operate, and only with a worker monitor, a peer hang watch or a crash-report context. createBrowserDeps() gives window.crashReport only if it has a set() function, and only while the option crashReportContext is not false (the default is true).
import { metrics } from "@opentelemetry/api";
import {
createBrowserDeps,
createInstrumentedLifecycle,
createInstrumentedPageViewContext,
createInstrumentedPageViewVitals,
} from "@mark1russell7/lag";
const deps = createBrowserDeps(window, { logger : console, meter : metrics.getMeter("lag") });
const lifecycle = createInstrumentedLifecycle(deps).monitor;
const vitals = lifecycle && deps.PerformanceObserver
? createInstrumentedPageViewVitals({ ...deps, PerformanceObserver : deps.PerformanceObserver }, lifecycle).monitor
: undefined;
if (vitals) {
// A receiver gets the context at the start and at each new page view
const receiver = { setContext : (attributes : Record<string, string>) => console.log(attributes) };
// deps.crashReport is window.crashReport, where the browser has it
const context = createInstrumentedPageViewContext(deps, vitals, [receiver]);
console.log(context.monitor?.getAttributes());
}
Cost
- Messages: one
contextmessage to the worker for each page view. - Crash-report context: one
initialize(), and oneset()for each page view. The context asks for 512 bytes for the keys and values of the monitors. - Timers and observers: none.
Limits
- The crash report goes to your server. The browser sends it to the reporting endpoint, not to the OpenTelemetry collector. Connect it with the events of the page view through
lag.page_view.idon the server. - Only Chromium has the crash-report context. In Firefox and Safari, only the worker gets the context.
- The context of the start of the hang. The worker keeps the last context that it got. A hang report has the page view that was current when the hang started.
- A fixed list of receivers. The factory gets the receivers at its start. A receiver that you make later does not get the context.
Tests
Unit tests:
instrumented/page-view-context.test.ts: each receiver gets the current page-view ID at the start and at each new page view. The crash-report context getsinitialize()one time,set()for each page view, anddelete()atstop(). The context still sets the ID when another script initialized the crash-report context first. A failure of the crash-report context goes to the logger at the leveldebug, and the receivers still get the context.setup-all-monitors.test.ts: the worker monitor gets the ID of the current page view. The events of the other monitors get it too.browser/browser-deps.test.ts: the adapter useswindow.crashReportwhere the browser has it, and not withcrashReportContext: false.
Browser test (Vitest browser mode with Playwright, in Chromium, Firefox, WebKit and Chrome):
hang-journal.test.ts: the journal record of a hang has the attributes{ "lag.page_view.id": <the ID of the page view> }.
Source
instrumented/page-view-context.ts: the factory.events.ts:withEventContext().setup-all-monitors.ts: the event context and the receivers.dep-groups.ts:CrashReportDepsandCrashReportContextLike.