Monitora l'utilizzo totale della memoria della tua pagina web con MeasurementUserAgentSpecificMemory()

Scopri come misurare la memoria utilizzata della tua pagina web in produzione per rilevare le regressioni.

Brendan Kenny
Brendan Kenny
Ulan Degenbaev
Ulan Degenbaev

I browser gestiscono automaticamente la memoria delle pagine web. Ogni volta che una pagina web crea un oggetto, il browser alloca una porzione di memoria "dietro le quinte" per archiviare l'oggetto. Poiché la memoria è una risorsa limitata, il browser esegue la garbage collection per rilevare quando un oggetto non è più necessario e liberare il blocco di memoria sottostante.

Tuttavia, il rilevamento non è perfetto e il problema della terminazione di Alan Turing ha dimostrato che il rilevamento perfetto è un'attività impossibile. Pertanto, i browser approssimano la nozione di "un oggetto è necessario" con la nozione di "un oggetto è raggiungibile". Se la pagina web non riesce a raggiungere un oggetto tramite le sue variabili e i campi di altri oggetti raggiungibili, il browser può recuperare l'oggetto in sicurezza. La differenza tra queste due nozioni porta a perdite di memoria, come illustrato nell'esempio seguente.

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

In questo caso, l'array più grande b non è più necessario, ma il browser non lo recupera perché è ancora raggiungibile tramite object.b nel callback. In questo modo, la memoria dell'array più grande viene persa.

Le perdite di memoria sono molto diffuse sul web, come si può vedere da questo studio. È facile introdurne uno dimenticando di annullare la registrazione di un listener di eventi, acquisendo accidentalmente oggetti da un iframe, non chiudendo un worker, accumulando oggetti in array e così via. Se una pagina web presenta perdite di memoria, la memoria utilizzata aumenta nel tempo e la pagina web appare lenta e sovraccarica agli utenti.

Il primo passo per risolvere questo problema è misurarlo. La nuova API performance.measureUserAgentSpecificMemory() consente agli sviluppatori di misurare la memoria utilizzata dalle loro pagine web in produzione e, di conseguenza, di rilevare le perdite di memoria che sfuggono ai test locali.

In che modo performance.measureUserAgentSpecificMemory() si differenzia dall'API performance.memory precedente?

Se hai familiarità con l'API performance.memory non standard esistente, potresti chiederti in che modo la nuova API si differenzia da questa. La differenza principale è che l'API precedente restituisce le dimensioni dello heap JavaScript, mentre la nuova API stima la memoria utilizzata dalla pagina web. Questa differenza diventa importante quando Chrome condivide lo stesso heap con più pagine web (o più istanze della stessa pagina web). In questi casi, il risultato della vecchia API potrebbe essere arbitrariamente errato. Poiché la vecchia API è definita in termini specifici dell'implementazione, come "heap", la sua standardizzazione è impossibile.

Un'altra differenza è che la nuova API esegue la misurazione della memoria durante la garbage collection. In questo modo si riduce il rumore nei risultati, ma potrebbe essere necessario un po' di tempo prima che vengano generati. Tieni presente che altri browser potrebbero decidere di implementare la nuova API senza fare affidamento sulla garbage collection.

Casi d'uso suggeriti

La memoria utilizzata di una pagina web dipende dalla tempistica degli eventi, dalle azioni degli utenti e dalle garbage collection. Per questo motivo, l'API di misurazione della memoria è destinata all'aggregazione dei dati sulla memoria utilizzata in produzione. I risultati delle singole chiamate sono meno utili. Esempi di casi d'uso:

  • Rilevamento delle regressioni durante l'implementazione di una nuova versione della pagina web per individuare nuove perdite di memoria.
  • Eseguire test A/B su una nuova funzionalità per valutarne l'impatto sulla memoria e rilevare perdite di memoria.
  • Correlare la memoria utilizzata con la durata della sessione per verificare la presenza o l'assenza di perdite di memoria.
  • Correlare la memoria utilizzata con le metriche utente per comprendere l'impatto complessivo della memoria utilizzata.

Compatibilità del browser

Browser Support

  • Chrome: 89.
  • Edge: 89.
  • Firefox: not supported.
  • Safari: not supported.

Source

Al momento l'API è supportata solo nei browser basati su Chromium, a partire da Chrome 89. Il risultato dell'API dipende in larga misura dall'implementazione, perché i browser hanno modi diversi di rappresentare gli oggetti in memoria e di stimare la memoria utilizzata. I browser potrebbero escludere alcune regioni di memoria dal conteggio se un conteggio corretto è troppo oneroso o non fattibile. Pertanto, i risultati non possono essere confrontati tra i browser. È utile solo per confrontare i risultati per lo stesso browser.

In uso: performance.measureUserAgentSpecificMemory()

Rilevamento delle caratteristiche

La funzione performance.measureUserAgentSpecificMemory non sarà disponibile o potrebbe non riuscire con un SecurityError se l'ambiente di esecuzione non soddisfa i requisiti di sicurezza per impedire fughe di informazioni multiorigine. Si basa sull'isolamento multiorigine, che una pagina web può attivare impostando le intestazioni COOP+COEP.

Il supporto può essere rilevato in fase di runtime:

if (!window.crossOriginIsolated) {
  console.log('performance.measureUserAgentSpecificMemory() is only available in cross-origin-isolated pages');
} else if (!performance.measureUserAgentSpecificMemory) {
  console.log('performance.measureUserAgentSpecificMemory() is not available in this browser');
} else {
  let result;
  try {
    result = await performance.measureUserAgentSpecificMemory();
  } catch (error) {
    if (error instanceof DOMException && error.name === 'SecurityError') {
      console.log('The context is not secure.');
    } else {
      throw error;
    }
  }
  console.log(result);
}

Test locale

Chrome esegue la misurazione della memoria durante la garbage collection, il che significa che l'API non risolve immediatamente la promessa del risultato, ma attende la garbage collection successiva.

La chiamata all'API forza una garbage collection dopo un determinato timeout, che attualmente è impostato su 20 secondi, anche se potrebbe avvenire prima. L'avvio di Chrome con il flag della riga di comando --enable-blink-features='ForceEagerMeasureMemory' riduce il timeout a zero ed è utile per il debug e i test locali.

Esempio

L'utilizzo consigliato dell'API consiste nel definire un monitor della memoria globale che campiona la memoria utilizzata dell'intera pagina web e invia i risultati a un server per l'aggregazione e l'analisi. Il modo più semplice è campionare periodicamente, ad esempio ogni M minuti. Tuttavia, questo introduce un bias nei dati perché i picchi di memoria possono verificarsi tra i campioni.

L'esempio seguente mostra come eseguire misurazioni della memoria imparziali utilizzando un processo di Poisson, che garantisce che i campioni abbiano la stessa probabilità di verificarsi in qualsiasi momento.

Innanzitutto, definisci una funzione che pianifica la misurazione della memoria successiva utilizzando setTimeout() con un intervallo randomizzato.

function scheduleMeasurement() {
  // Check measurement API is available.
  if (!window.crossOriginIsolated) {
    console.log('performance.measureUserAgentSpecificMemory() is only available in cross-origin-isolated pages');
    console.log('See https://web.dev/coop-coep/ to learn more')
    return;
  }
  if (!performance.measureUserAgentSpecificMemory) {
    console.log('performance.measureUserAgentSpecificMemory() is not available in this browser');
    return;
  }
  const interval = measurementInterval();
  console.log(`Running next memory measurement in ${Math.round(interval / 1000)} seconds`);
  setTimeout(performMeasurement, interval);
}

La funzione measurementInterval() calcola un intervallo casuale in millisecondi in modo che, in media, venga eseguita una misurazione ogni cinque minuti. Se ti interessa la matematica alla base della funzione, consulta Distribuzione esponenziale.

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

Infine, la funzione asincrona performMeasurement() richiama l'API, registra il risultato e pianifica la misurazione successiva.

async function performMeasurement() {
  // 1. Invoke performance.measureUserAgentSpecificMemory().
  let result;
  try {
    result = await performance.measureUserAgentSpecificMemory();
  } catch (error) {
    if (error instanceof DOMException && error.name === 'SecurityError') {
      console.log('The context is not secure.');
      return;
    }
    // Rethrow other errors.
    throw error;
  }
  // 2. Record the result.
  console.log('Memory usage:', result);
  // 3. Schedule the next measurement.
  scheduleMeasurement();
}

Infine, inizia a misurare.

// Start measurements.
scheduleMeasurement();

Il risultato potrebbe essere il seguente:

// Console output:
{
  bytes: 60_100_000,
  breakdown: [
    {
      bytes: 40_000_000,
      attribution: [{
        url: 'https://example.com/',
        scope: 'Window',
      }],
      types: ['JavaScript']
    },

    {
      bytes: 20_000_000,
      attribution: [{
          url: 'https://example.com/iframe',
          container: {
            id: 'iframe-id-attribute',
            src: '/iframe',
          },
          scope: 'Window',
      }],
      types: ['JavaScript']
    },

    {
      bytes: 100_000,
      attribution: [],
      types: ['DOM']
    },
  ],
}

La stima della memoria utilizzata totale viene restituita nel campo bytes. Questo valore dipende molto dall'implementazione e non può essere confrontato tra i browser. Può anche cambiare tra versioni diverse dello stesso browser. Il valore include la memoria JavaScript e DOM di tutti gli iframe, delle finestre correlate e dei web worker nel processo corrente.

L'elenco breakdown fornisce ulteriori informazioni sulla memoria utilizzata. Ogni voce descrive una parte della memoria e la attribuisce a un insieme di finestre, iframe e worker identificati dall'URL. Il campo types elenca i tipi di memoria specifici dell'implementazione associati alla memoria.

È importante trattare tutti gli elenchi in modo generico e non impostare come hardcoded le ipotesi basate su un determinato browser. Ad esempio, alcuni browser potrebbero restituire un valore breakdown vuoto o un valore attribution vuoto. Altri browser potrebbero restituire più voci in attribution, indicando che non sono stati in grado di distinguere quale di queste voci possiede la memoria.

Feedback

Il Web Performance Community Group e il team di Chrome vorrebbero conoscere le tue opinioni e le tue esperienze con performance.measureUserAgentSpecificMemory().

Parlaci della progettazione dell'API

C'è qualcosa nell'API che non funziona come previsto? Oppure mancano delle proprietà necessarie per implementare la tua idea? Invia una segnalazione relativa alle specifiche nel repository GitHub performance.measureUserAgentSpecificMemory() o aggiungi le tue considerazioni a un problema esistente.

Segnala un problema relativo all'implementazione

Hai trovato un bug nell'implementazione di Chrome? O l'implementazione è diversa dalla specifica? Segnala un bug all'indirizzo new.crbug.com. Assicurati di includere quanti più dettagli possibili, fornisci istruzioni semplici per riprodurre il bug e imposta Componenti su Blink>PerformanceAPIs.

Mostra il tuo sostegno

Hai intenzione di utilizzare performance.measureUserAgentSpecificMemory()? Il tuo supporto pubblico aiuta il team di Chrome a dare la priorità alle funzionalità e mostra agli altri fornitori di browser quanto sia fondamentale supportarle. Invia un tweet a @ChromiumDev e facci sapere dove e come lo stai utilizzando.

Link utili

Ringraziamenti

Un grande ringraziamento a Domenic Denicola, Yoav Weiss, Mathias Bynens per le revisioni del design delle API e a Dominik Inführ, Hannes Payer, Kentaro Hara, Michael Lippautz per le revisioni del codice in Chrome. Ringrazio anche Per Parker, Philipp Weis, Olga Belomestnykh, Matthew Bolohan e Neil Mckay per aver fornito un prezioso feedback degli utenti che ha migliorato notevolmente l'API.