Monitoring all memory used by a webpage: performance.measureMemory ()

The author of the article, the translation of which we publish today, talks about how to monitor the memory allocated to web pages. Careful attention to the memory of pages working in production helps maintain the productivity of web projects at a high level.



Browsers automatically control the memory allocated to web pages. When a page creates an object, the browser, using its internal mechanisms, allocates memory to store this object. Since memory is not an infinite resource, the browser periodically performs garbage collection, during which unnecessary objects are detected and the memory they occupy is cleared. The process of detecting such objects, however, is not ideal. It has been proventhat absolutely accurate and complete identification of such objects is an insoluble task. As a result, browsers are replacing the idea of ​​finding “unnecessary objects” with the idea of ​​finding “unreachable objects”. If a web page cannot access the object through the variables it has and the fields of other objects accessible to it, this means that the browser can safely clear the memory occupied by such an object. The difference between “unnecessary” and “unreachable” leads to memory leaks, as illustrated in the following example:

const object = { a: new Array(1000), b: new Array(2000) };
setInterval(() => console.log(object.a), 1000);

There is a large unnecessary array here b, but the browser does not free the memory it occupies due to the fact that it is reachable through the object property object.bin the callback. As a result, the memory occupied by this array is leaking.

Memory leaks are a common occurrence in web development. They very easily appear in programs when, for example, developers forget to unsubscribe from the event listener, when they accidentally capture objects in an element iframe, when they forget to close the worker, when they collect objects in arrays. If a web page has memory leaks, this leads to the fact that over time the memory consumption of the page increases. Such a page seems slow and slow for users.

The first step in solving this problem is to take measurements. The new performance.measureMemory () API allows developers to measure memory usage by web pages in production and, as a result, detect memory leaks that slip through local tests.

How is the new performance.measureMemory () API different from the old performance.memory?


If you are familiar with the existing non-standard API performance.memory, then you may be interested in the question of how the new API differs from the old one. The main difference is that the old API returns the size of the JavaScript heap, and the new one evaluates the memory usage of the entire web page. This distinction is important when Chrome organizes heap sharing between multiple web pages (or multiple instances of the same page). In such cases, the results returned by the old API may be distorted. Since the old API is defined in terms specific to the implementation, such as a heap, standardizing it is a hopeless business.

Another difference is that in Chrome, the new API takes memory measurements when collecting garbage. This reduces the “noise” in the measurement results, but may take some time to get the results. Please note that creators of other browsers may decide to implement the new API without binding to garbage collection.

Recommended Ways to Use the New API


The use of memory by web pages depends on the occurrence of events, on user actions, on garbage collection. That is why the API is performance.measureMemory()designed to study the level of memory usage in production. The results of calling this API in a test environment are less useful. Here are examples of options for using it:

  • - .
  • A/B- , .
  • .
  • , . .


Currently, the API in question is supported only in Chrome 83, according to the Origin Trial scheme. The results returned by the API are highly implementation dependent, as different browsers use different ways of representing objects in memory and different ways of evaluating memory usage. Browsers can exclude some areas of memory from accounting if the complete accounting of all used memory is an unreasonably difficult or impossible task. As a result, we can say that the results produced by this API in different browsers are not comparable. It makes sense to compare only the results obtained in the same browser.

Current work progress


Stepcondition
1. Creating API Explanations
Completed
2. Creating a draft specification
Performed
3. Gathering feedback and finalizing the project
Performed
4. Origin Trial Tests
Performed
5. Launch
Not started

Using performance.measureMemory ()


â–Ť Enable Origin Trial Support


The API performance.measureMemory()is available in Chrome 83 according to the Origin Trial scheme. This phase is expected to end with the release of Chrome 84.

Origin Trial allows developers to take advantage of Chrome’s new features and share feedback on the convenience, usability, and effectiveness of these features with the web community. Details about this program can be found here . You can subscribe to the program on the registration page .

â–ŤRegistration in the Origin Trial program


  1. Request a token for the opportunity you are interested in.
  2. Add the token to the pages of the pilot project. There are 2 ways to do this:

    • Add a <meta>tag origin-trialto the title of each page. For example, it might look like this: <meta http-equiv="origin-trial" content="TOKEN_GOES_HERE">.
    • If you have access to the server settings, it can add a token using an HTTP header Origin-Trial. As a result, the response header should appear something like the following: Origin-Trial: TOKEN_GOES_HERE.

â–ŤEnabling a new feature through Chrome flags


In order to experiment with performance.measureMemory(), while dispensing with the Origin Trial token, you need to enable the flag #experimental-web-platform-featuresin chrome://flags.

â–ŤChecking API Use


A function call performance.measureMemory()may fail, with a SecurityError thrown . This can happen if the environment does not meet the security requirements for information leaks. During the Origin Trial test in Chrome, this API requires the inclusion of Site Isolation . When the API is ready for normal use, it will rely on the crossOriginIsolated property . A web page can operate in this mode by setting the headers COOP and COEP .

Here is a sample code:

if (performance.measureMemory) {
  let result;
  try {
    result = await performance.measureMemory();
  } catch (error) {
    if (error instanceof DOMException &&
        error.name === "SecurityError") {
      console.log("The context is not secure.");
    } else {
      throw error;
    }
  }
  console.log(result);
}

â–ŤLocal testing


Chrome takes a memory measurement when collecting garbage. This means that accessing the API does not instantly resolve the promise. To get the result, you need to wait for the next garbage collection session. The API forcibly starts garbage collection after a certain timeout, which is currently set to 20 seconds. If you run Chrome with a command line flag --enable-blink-features='ForceEagerMeasureMemory', the timeout will be reduced to zero, which is useful for local debugging and local testing.

Example


It is recommended that you use the new API by defining a global memory monitor that measures the level of memory usage of the entire page and sends the results to the server, where they can be aggregated and analyzed. The easiest way to work with this API is to take periodic measurements. For example, they can run every Mminute. This, however, introduces distortions into the data, since peaks in memory use can occur between the measurements. The following example demonstrates how, using the Poisson process , to make measurements free of systematic errors. This approach ensures that measurement sessions can, with equal probability, occur at any point in time ( here is a demonstration of this approach,here is the source code).

First, declare a function that plans the next start of a session to measure the amount of consumed memory using a function setTimeout()with a randomly set interval. This function should be called after loading the page in the browser window.

function scheduleMeasurement() {
  if (!performance.measureMemory) {
    console.log("performance.measureMemory() is not available.");
    return;
  }
  const interval = measurementInterval();
  console.log("Scheduling memory measurement in " +
      Math.round(interval / 1000) + " seconds.");
  setTimeout(performMeasurement, interval);
}

//       .
window.onload = function () {
  scheduleMeasurement();
}

The function measurementInterval()finds a random interval, expressed in milliseconds, set in such a way that one measurement would be carried out approximately every five minutes. If you are interested in the mathematical concepts on which this function is based, read about the exponential distribution .

function measurementInterval() {
  const MEAN_INTERVAL_IN_MS = 5 * 60 * 1000;
  return -Math.log(Math.random()) * MEAN_INTERVAL_IN_MS;
}

As a result, the asynchronous function performMeasurement()calls our API, records the result, and plans the next measurement.

async function performMeasurement() {
  // 1.  performance.measureMemory().
  let result;
  try {
    result = await performance.measureMemory();
  } catch (error) {
    if (error instanceof DOMException &&
        error.name === "SecurityError") {
      console.log("The context is not secure.");
      return;
    }
    //    .
    throw error;
  }
  // 2.  .
  console.log("Memory usage:", result);
  // 3.   .
  scheduleMeasurement();
}

The measurement results may look like this:

// ,    :
{
  bytes: 60_000_000,
  breakdown: [
    {
      bytes: 40_000_000,
      attribution: ["https://foo.com"],
      userAgentSpecificTypes: ["Window", "JS"]
    },
    {
      bytes: 20_000_000,
      attribution: ["https://foo.com/iframe"],
      userAgentSpecificTypes: ["Window", "JS"]
    }
  ]
}

An estimate of the overall level of memory usage is displayed in the field bytes. When deriving this estimate, the separators of the digits of numbers are used. These values ​​are highly implementation dependent. If they are received for different browsers, then you can not compare them. The way they are obtained can vary even in different versions of the same browser. While the Origin Trial program lasts, the return values ​​include indicators of JavaScript memory usage by the main window, indicators of memory usage of elements iframefrom the same site, and indicators of related windows. When the API is ready, this value will be information about the memory consumed by JavaScript, the DOM, all elements iframeassociated with windows and web workers.

Listbreakdowngives more detailed information about the used memory. Each entry describes a piece of memory and associates this fragment with a set of windows, elements iframeor workers identified by a URL. The field userAgentSpecificTypeslists the types of memory determined by the implementation features.

It is important to consider these lists in a general way and not try, relying on the features of a certain browser, to analyze everything based on them. For example, some browsers may return empty list breakdownor empty fields attribution. Other browsers may return attributionmultiple URLs in an element , indicating that they cannot pinpoint which of these URLs the memory belongs to.

Feedback


The Web Performance Community Group and the Chrome development team will be happy to find out what you think about performance.measureMemory()and learn about your experience using this API.

Share your ideas about the API device with us


Is there something in this API that doesn't work as expected? Maybe it lacks something that you need to implement your idea? Open a new task in the project tracker or comment on an existing task.

Report an implementation issue


Found a bug in your Chrome implementation? Or maybe it turned out that the implementation is different from the specification? Record the error here: new.crbug.com . Try to include as much detail as possible in your message, include simple instructions on how to reproduce the error, and indicate that the problem is related to Blink>PerformanceAPIs. Glitch is a very good way to demonstrate errors .

Support us


performance.measureMemory()Are you planning to use ? If so, tell us about it. These stories help the Chrome development team prioritize. These stories show the creators of other browsers the importance of supporting new features. If you want, send a tweet to @ChromiumDev and tell us about where and how you use the new API.

Dear readers! Have you tried performance.measureMemory()?


All Articles