Memory
Samples the memory use of the page each 30 s with measureUserAgentSpecificMemory() or with the legacy performance.memory of Chromium.
MemoryMonitor samples the memory use of the page. In a cross-origin-isolated page, it uses performance.measureUserAgentSpecificMemory(). Otherwise it uses the legacy performance.memory of Chromium. The attribute source tells which API gave the value. Only Chromium has these APIs.
What it measures
The monitor gives two signals:
- The used memory (
lag_memory_used_bytes_histogram): the bytes of each sample, with the attributesource(modernorlegacy). - The usage ratio (
lag_memory_usage_ratio_histogram): the used JavaScript heap divided by the heap limit. Only the legacy API gives the limit.
The monitor answers this question: does the memory of the page increase over its life? A slow increase of the used memory can show a leak. Garbage collection also uses the main thread: in one study of Chrome, a major collection took more than 100 ms, in incremental steps (clocks and timers).
How it works
- At its start, the monitor takes one sample immediately. Then it takes one sample each 30 000 ms (
defaultMemoryIntervalMs). - If the browser has
measureUserAgentSpecificMemory(), the monitor uses it, and recordsbyteswithsource="modern". - If the modern API fails one time, the monitor uses the legacy API from then on, and logs the change at the level
debug. - The legacy API gives
usedJSHeapSize,totalJSHeapSizeandjsHeapSizeLimit. The monitor recordsusedJSHeapSizewithsource="legacy", and the ratio of used to limit.
The modern API can take many seconds, thus the monitor does not start a sample while one waits. It does not record a sample that comes back after stop().
createBrowserDeps() gives the memory source only when the browser has one of the two APIs. Without them, setupAllMonitors() does not start the monitor.
Browser support
| API | Chromium | Firefox | Safari |
|---|---|---|---|
performance.measureUserAgentSpecificMemory() | 89, only in a cross-origin-isolated page | not available | not available |
performance.memory | yes (not standard, deprecated) | not available | not available |
These facts come from browser support:
- Without cross-origin isolation,
measureUserAgentSpecificMemoryis undefined. The result comes at the next garbage collection, with a default timeout of approximately 20 s. - The modern API also measures the dedicated workers of the page.
- MDN says that the values of
performance.memoryare not reliable. They count too much with shared heaps, and too little with workers or cross-site iframes. Use them only as a coarse hint.
Measurement validity
The monitor does not pause while the page is hidden, and it uses no SampleValidator. A memory sample does not depend on timer delays, thus throttled timers change only the time of the sample.
Metrics
| Metric | Kind | Unit | Attributes | Description |
|---|---|---|---|---|
lag_memory_used_bytes_histogram | Histogram | By | source | The used heap memory of each sample. |
lag_memory_usage_ratio_histogram | Histogram | 1 | None | The used heap divided by the heap limit. Only the legacy source supplies the limit. |
Do not mix the two sources in one series: they measure different things. The monitor sends no events.
Configuration
function createInstrumentedMemory(
deps : CoreDeps & MemoryDeps & Pick<TimerDeps, "setIntervalFn" | "clearIntervalFn">,
) : MonitorHandle<MemoryMonitor>;
The factory uses CoreDeps (logger, clock, meter), MemoryDeps and the interval functions of TimerDeps.
| Option | Default | What it does |
|---|---|---|
memorySource | necessary | The functions readLegacy and measureModern. createBrowserDeps() makes them from performance. |
memoryIntervalMs | 30 000 | The time between two samples. createBrowserDeps() passes the option of the same name. |
import { metrics } from "@opentelemetry/api";
import { createBrowserDeps, createInstrumentedMemory } from "@mark1russell7/lag";
const deps = createBrowserDeps(window, {
logger : console,
meter : metrics.getMeter("lag"),
memoryIntervalMs : 60_000,
});
if (deps.memorySource) {
const memory = createInstrumentedMemory({ ...deps, memorySource : deps.memorySource });
memory.stop();
}
Cost
- Timers: one interval callback each 30 s.
- Records: one or two histogram values for each sample.
- The API: the modern API waits for a garbage collection. The research does not give its cost.
Limits
- Only Chromium. Firefox and Safari give no values.
- Two different measurements. The modern API measures the memory of the page and of its dedicated workers. The legacy API gives the JavaScript heap. Compare values only of one source.
- A fixed interval. The research recommends a random (Poisson) schedule for the modern API. The monitor uses a fixed interval.
- One failure stops the modern API. After one failure, the monitor uses the legacy API for the rest of the page life.
Tests
Unit tests:
MemoryMonitor.test.ts: the first sample comes at the start. The monitor uses the modern API when it is available, and the legacy API when the modern API fails. After one failure, it does not use the modern API again. It takes samples at the interval and stops atstop(). It does not start a sample while one waits, and it drops a modern sample that comes afterstop().browser/browser-deps.test.ts: the adapter finds the legacy and the standard memory APIs.
Browser tests (Vitest browser mode with Playwright, in Chromium, Firefox, WebKit and Chrome, unless an item names other browsers):
lag-monitors.test.ts(only where the browser hasperformance.memory):lag_memory_used_bytes_histogramhas a value of more than 0, with thesourcelegacy.browser-apis.test.ts(only withperformance.memory): the first sample comes at once, and it is more than 1 MB.coi/isolation.test.ts(a cross-origin-isolated page, only withmeasureUserAgentSpecificMemory()): the first sample has thesourcemodernand more than 1 MB. The project starts Chromium in the new headless mode withForceEagerMeasureMemory, so that the promise resolves at once and not at the next garbage collection.
Source
MemoryMonitor.ts: the monitor.instrumented/memory.ts: the factory.browser/browser-deps.ts: the memory source of the browser.