1. Introduction
performance.measureMemory()
API that estimates the memory usage of the web application including all its iframes and workers.
The new API is intended for aggregating memory usage data from production. The main use cases are:
-
Regression detection during rollout of a new version of the web application to catch new memory leaks.
-
A/B testing a new feature to evaluate its memory impact.
-
Correlating memory usage with user metrics to understand the overall impact of memory usage.
1.1. Examples
A performance.measureMemory()
call returns a Promise
and starts
an asynchronous measurement of the memory allocated by the page.
asyncfunction run() { const result= await performance. measureMemory(); console. log( result); } run();
For a simple page without iframes and workers the result might look as follows:
Here all memory is attributed to the main page.{ bytes: 1000000 , breakdown: [ { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], userAgentSpecificTypes: [ "JS" , "DOM" ], }, ], }
Other possible valid results:
Here the implementation provides only the total memory usage.{ bytes: 1000000 , breakdown: [], }
Here the implementation does not break memory down by memory types.{ bytes: 1000000 , breakdown: [ { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], userAgentSpecificTypes: [], }, ], }
For a page that embeds a same-origin iframe the result might attribute some memory to that iframe and provide diagnostic information for identifying the iframe:
< html > < body > < iframe id = "example-id" src = "redirect.html?target=iframe.html" ></ iframe > </ body > </ html >
Note how the{ bytes: 1500000 , breakdown: [ { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], userAgentSpecificTypes: [ "JS" , "DOM" ], }, { bytes: 500000 , attribution: [ { url: 'https://example.com/iframe.html' container: { id: "example-id" , src: 'redirect.html?target=iframe.html' , }, scope: 'Window' , } ], userAgentSpecificTypes: [ 'JS' , 'DOM' ], }, ], }
url
and container.src
fields differ for the iframe.
The former reflects the current location.href
of the iframe whereas the
latter is the value of the src
attribute of the iframe element.
It is not always possible to separate iframe memory from page memory in a meaningful way. An implementation is allowed to lump together some or all of iframe and page memory:
{ bytes: 1500000 , breakdown: [ { bytes: 1500000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, { url: "https://example.com/iframe.html" , container: { id: "example-id" , src: "redirect.html?target=iframe.html" , }, scope: "Window" , }, ], userAgentSpecificTypes: [ "JS" , "DOM" ], }, ], };
An implementation might lump together worker and page memory. If a worker is spawned by an iframe, then worker’s attribution entry has a{ bytes: 1800000 , breakdown: [ { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], userAgentSpecificTypes: [ "JS" , "DOM" ], }, { bytes: 800000 , attribution: [ { url: "https://example.com/worker.js" , scope: "DedicatedWorkerGlobalScope" , }, ], userAgentSpecificTypes: [ "JS" ], }, ], };
container
field
corresponding to the iframe element.
Memory of shared and service workers is not included in the result.
Consider a page with the following structure:
exampleA cross-origin iframe embeds to other iframes and spawns a worker. All memory of these resources is attributed to the first iframe.. com( 1000000 bytes) | *-- foo. com/ iframe1( 500000 bytes) | *-- foo. com/ iframe2( 200000 bytes) | *-- bar. com/ iframe2( 300000 bytes) | *-- foo. com/ worker. js( 400000 bytes)
< html > < body > < iframe id = "example-id" src = "https://foo.com/iframe1" ></ iframe > </ body > </ html >
Note that the{ bytes: 2400000 , breakdown: [ { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], userAgentSpecificTypes: [ "JS" , "DOM" ], }, { bytes: 1400000 , attribution: [ { url: "cross-origin-url" , container: { id: "example-id" , src: "https://foo.com/iframe1" , }, scope: "cross-origin-aggregated" , }, ], userAgentSpecificTypes: [ "JS" , "DOM" ], }, ], }
url
and scope
fields of the cross-origin iframe entry have
special values indicating that information is not available.
location.href
of the same-origin iframe.
example. com( 1000000 bytes) | *-- foo. com/ iframe1( 500000 bytes) | *-- example. com/ iframe2( 200000 bytes)
< html > < body > < iframe id = "example-id" src = "https://foo.com/iframe1" ></ iframe > </ body > </ html >
{ bytes: 1700000 , breakdown: [ { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], userAgentSpecificTypes: [ "JS" , "DOM" ], }, { bytes: 500000 , attribution: [ { url: "cross-origin-url" , container: { id: "example-id" , src: "https://foo.com/iframe1" , }, scope: "cross-origin-aggregated" , }, ], userAgentSpecificTypes: [ "JS" , "DOM" ], }, { bytes: 200000 , attribution: [ { url: "https://example.com/iframe2" , container: { id: "example-id" , src: "https://foo.com/iframe1" , }, scope: "Window" , }, ], userAgentSpecificTypes: [ "JS" , "DOM" ], }, ], }
2. Data model
2.1. Memory measurement result
The performance.measureMemory()
function returns a Promise
that resolves to an instance of MemoryMeasurement
dictionary:
dictionary {
MemoryMeasurement unsigned long long ;
bytes sequence <MemoryBreakdownEntry >; };
breakdown
measurement .
bytes
-
A number that represents the total memory usage.
measurement .
breakdown
-
An array that partitions the total
bytes
and provides attribution and type information.
dictionary {
MemoryBreakdownEntry unsigned long long ;
bytes sequence <MemoryAttribution >;
attribution sequence <DOMString >; };
userAgentSpecificTypes
breakdown .
bytes
-
The size of the memory that this entry describes.
breakdown .
attribution
-
An array of JavaScript realms specified by their URLs and/or container elements that use the memory.
breakdown .
userAgentSpecificTypes
-
An array of implementation defined memory types associated with the memory.
dictionary {
MemoryAttribution USVString ;
url MemoryAttributionContainer ;
container DOMString ; };
scope
attribution .
url
-
If this attribution corresponds to a same-origin JavaScript realm, then this field contains realm’s URL. Otherwise, the attribution is for one or more cross-origin JavaScript realms and this field contains a sentinel value:
"cross-origin-url"
. attribution .
container
-
Describes the DOM element that (maybe indirectly) contains the JavaScript realms. It may be empty if the attribution is for the same-origin top-level realm. Note that cross-origin realms cannot be top-level due to cross-origin isolation.
attribution .
scope
-
Describes the type of the same-origin JavaScript realm:
"Window", "DedicatedWorkerGlobalScope", "SharedWorkerGlobalScope", "ServiceWorkerGlobalScope"
or contains"cross-origin-aggregated"
for the cross-origin case.
dictionary {
MemoryAttributionContainer DOMString ;
id USVString ; };
src
2.2. Intermediate memory measurement
This specification assumes the existences of an implementation-defined algorithm that can measure the memory of objects allocated by a set of JavaScript agent clusters. The result of such algorithm is an intermediate memory measurement, which is a set of intermediate memory breakdown entries.
An intermediate memory breakdown entry is a struct containing the following items:
- bytes
-
The size of the memory that this intermediate memory breakdown entry describes.
- realms
-
A set of JavaScript realms to which the memory is attributed to.
- user agent specific types
-
A set of strings specifying implementation defined memory types associated with the memory.
Algorithms defined in this specification show how to convert an intermediate memory measurement to an instance of MemoryMeasurement
.
2.3. Memory attribution token
The link between an embedded JavaScript realm and its container element is ephemeral and is not guaranteed to always exist. For example, navigation to another document in the container element or removal of the container element from the DOM tree severs the link.
A memory attribution token provides a way to get from a JavaScript realm to its container element. It is a struct containing the following items:
- container
-
An instance of
MemoryAttributionContainer
. - cross-origin aggregated flag
-
A boolean flag indicating whether the token was created for aggregating the memory usage of cross-origin JavaSript realms.
It is stored in a new internal field of WindowOrWorkerGlobalScope
at construction time and is always available for memory reporting.
3. Processing model
3.1. Extensions to the Performance
interface
partial interface Performance {Promise <MemoryMeasurement >measureMemory (); };
self .
measureMemory()
-
A method that performs an asynchronous memory measurement. Details about the result of the method are in § 2.1 Memory measurement result.
3.2. Top-level algorithms
measureMemory()
method steps are:
-
Assert: current Realm's agent's agent cluster's cross-origin isolated is true.
-
If memory measurement allowed predicate given current Realm is false, then:
-
Return a promise rejected with a "
SecurityError
"DOMException
.
-
-
Let agent clusters be the result of getting all agent clusters given current Realm.
-
Let promise be a new
Promise
. -
Start asynchronous implementation-defined memory measurement given agent clusters and promise.
-
Return promise.
-
Let global object be realm’s global object.
-
If global object is a
SharedWorkerGlobalScope
, then return true. -
If global object is a
ServiceWorkerGlobalScope
, then return true. -
If global object is a
Window
then-
Let settings object be realm’s settings object.
-
If settings object’s origin is the same as settings object’s top-level origin, then return true.
-
-
Return false.
-
If realm’s global object is a
Window
, then:-
Let group be the browsing context group that contains realm’s global object's browsing context.
-
Return the result of getting the values of group’s agent cluster map.
-
-
Return « realm’s agent's agent cluster ».
Promise
promise run these steps in parallel:
-
Let intermediate memory measurement be an implementation-defined intermediate memory measurement of agent clusters.
-
Queue a global task on the TODO task source given promise’s relevant global object to resolve promise with the result of creating a new memory measurement given intermediate memory measurement.
3.3. Converting an intermediate memory measurement to the result
-
Let bytes be 0.
-
For each intermediate memory breakdown entry intermediate entry in intermediate measurement:
-
Set bytes to bytes plus intermediate entry’s bytes.
-
-
Let breakdown be a new list.
-
For each intermediate memory breakdown entry intermediate entry in intermediate measurement:
-
Let breakdown entry be the result of creating a new memory breakdown entry given intermediate entry.
-
Append breakdown entry to breakdown.
-
-
Return a new
MemoryMeasurement
whose:
-
Let attribution a new list.
-
For each JavaScript realm realm in intermediate entry’s realms:
-
Let attribution entry be the result of creating a new memory attribution given realm.
-
Append attribution entry to attribution.
-
-
Return a new
MemoryBreakdownEntry
whose:-
attribution
is attribution, -
userAgentSpecificTypes
is intermediate entry’s user agent specific types.
-
Let token be realm’s global object's memory attribution token.
-
If token’s cross-origin aggregated flag is true, then
-
Let scope name be identifier of realm’s global object's interface.
-
Return a new
MemoryAttribution
whose:-
url
is realm’s settings object's creation URL, -
scope
is scope name.
-
3.4. Creating or obtaining a memory attribution token
HTMLElement
container element, an memory attribution token parent token:
-
If container element is null, then:
-
Assert: parent origin is null.
-
Assert: parent token is null.
-
Assert: origin is equal to parent origin
-
Return a new memory attribution token whose:
-
container is null,
-
cross-origin aggregated flag is false.
-
-
-
Let container be the result of extracting container element attributes given container element.
-
If origin is equal to top-level origin, then:
-
Return a new memory attribution token whose:
-
container is container,
-
cross-origin aggregated flag is false.
-
-
-
If parent origin is equal to top-level origin, then:
-
Return a new memory attribution token whose:
-
container is container,
-
cross-origin aggregated flag is true.
-
-
-
Return parent token.
WorkerGlobalScope
worker global scope, an environment settings object outside settings:
-
If worker global scope is a
DedicatedWorkerGlobalScope
, then return outside settings’s global object's memory attribution token. -
Assert: worker global scope is a
SharedWorkerGlobalScope
or aServiceWorkerGlobalScope
. -
Return a new memory attribution token whose:
-
container is null,
-
cross-origin aggregated flag is false.
-
HTMLElement
container element:
-
Switch on container element’s local name:
- "iframe"
-
Return a new
MemoryAttributionContainer
whose: - "frame"
-
Return a new
MemoryAttributionContainer
whose: - "object"
-
Return a new
MemoryAttributionContainer
whose:
4. Integration with the existing specification
4.1. Extension to WindowOrWorkerGlobalScope
A new internal field is added to WindowOrWorkerGlobalScope
:
- A memory attribution token
-
An memory attribution token that is used for reporting the memory usage of this environment.
4.2. Extensions to the existing algorithms
The run a worker algorithm sets the memory attribution token field of the newly created global object in step 6:
Let realm execution context be the result of creating a new JavaScript realm given agent and the following customizations:
...
Set global object’s memory attribution token to the result of obtaining a worker memory attribution token given the global object and outside settings.
The create and initialize a Document object algorithm sets the memory attribution token field of the newly created global object:
Otherwise:
Let token be an empty memory attribution token.
If browsingContext is not a top-level browsing context, then:
Let parentToken be parentEnvironment’s global object's memory attribution token.
Set token to the result of obtaining a window memory attribution token with origin, parentEnvironment’s origin, topLevelOrigin, browsingContext’s container, parentToken.
Else, set token to the result of obtaining a window memory attribution token with origin, null topLevelOrigin, null, null.
Let window global scope be the global object of realm execution context’s Realm component.
Set window global scope’s memory attribution token to token.
The create a new browsing context algorithm sets the memory attribution token field of the newly created global object:
Let token be an empty token.
If embedder is null, then set token to the result of obtaining a window memory attribution token with origin, null, topLevelOrigin, null, null.
Else, set token to the result of obtaining a window memory attribution token with origin, embedder’s relevant settings object's origin, topLevelOrigin, embedder, embedder’s relevant global object's memory attribution token.
Let window global scope be the global object of realm execution context’s Realm component.
Set window global scope’s memory attribution token to token.
5. Acknowledgements
Thanks to Domenic Denicola and Shu-yu Guo for contributing to the API design and for reviewing this specification.Also thanks to Anne van Kesteren, Boris Zbarsky, Dominik Inführ, Chris Hamilton, Hannes Payer, Joe Mason, Kentaro Hara, L. David Baron, Mathias Bynens, Matthew Bolohan, Michael Lippautz, Neil Mckay Olga Belomestnykh, Per Parker, Philipp Weis, and Yoav Weiss, for their feedback and contributions.