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

Scopri come misurare l'utilizzo della memoria della tua pagina web in produzione per rilevare 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 un blocco di memoria "under the hood" 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 per liberare il blocco di memoria sottostante.

Tuttavia, il rilevamento non è perfetto e è stato dimostrato che un rilevamento perfetto è un compito impossibile. Di conseguenza, i browser approssimano il concetto di "un oggetto è necessario" con quello di "un oggetto è raggiungibile". Se la pagina web non può raggiungere un oggetto tramite le sue variabili e i campi di altri oggetti raggiungibili, il browser può rivendicare 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. Di conseguenza, la memoria dell'array più grande viene divulgata.

Le fughe di memoria sono prevalenti sul web. È 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, l'utilizzo della memoria aumenta nel tempo e la pagina web appare lenta e gonfia agli utenti.

Il primo passo per risolvere questo problema è misurarlo. La nuova API performance.measureUserAgentSpecificMemory() consente agli sviluppatori di misurare l'utilizzo della memoria delle loro pagine web in produzione e quindi di rilevare perdite di memoria che non superano i test locali.

Qual è la differenza tra performance.measureUserAgentSpecificMemory() e l'API performance.memory precedente?

Se hai familiarità con l'API performance.memory non standard esistente, ti starai chiedendo quali sono le differenze della nuova API. La differenza principale è che la vecchia API restituisce la dimensione dell'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 disattivato. Poiché la vecchia API è definita in termini specifici dell'implementazione come "heap", la standardizzazione è senza problemi.

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 tempo prima che vengano restituiti. Tieni presente che altri browser potrebbero decidere di implementare la nuova API senza fare affidamento sulla garbage collection.

Casi d'uso suggeriti

L'utilizzo della memoria di una pagina web dipende dalla tempistica degli eventi, delle azioni degli utenti e delle garbage collection. Ecco perché l'API di misurazione della memoria è pensata per aggregare i dati di utilizzo della memoria dalla produzione. I risultati delle singole chiamate sono meno utili. Esempi di casi d'uso:

  • Rilevamento della regressione durante l'implementazione di una nuova versione della pagina web per rilevare nuove perdite di memoria.
  • Test A/B di una nuova funzionalità per valutare l'impatto sulla memoria e rilevare le perdite di memoria.
  • Correlazione dell'utilizzo della memoria con la durata della sessione per verificare la presenza o l'assenza di perdite di memoria.
  • Correlazione dell'utilizzo della memoria con le metriche utente per comprendere l'impatto complessivo della memoria utilizzata.

Compatibilità del browser

Supporto dei browser

  • 89
  • 89
  • x
  • x

Origine

Attualmente l'API è supportata solo nei browser basati su Chromium, a partire da Chrome 89. Il risultato dell'API dipende molto dall'implementazione perché i browser hanno modi diversi di rappresentare gli oggetti in memoria e diversi modi per stimare la memoria utilizzata. I browser possono escludere alcune regioni di memoria dalla contabilità se una contabilità corretta è troppo costosa o non fattibile. Di conseguenza, i risultati non possono essere confrontati tra browser. È significativo solo 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 errore SecurityError se l'ambiente di esecuzione non soddisfa i requisiti di sicurezza per prevenire le fughe di informazioni multiorigine. Si basa sull'isolamento multiorigine, che una pagina web può attivare impostando intestazioni COOP+COEP.

Il supporto può essere rilevato in fase di esecuzione:

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 di risultati, ma attende la successiva garbage collection.

La chiamata all'API forza una garbage collection dopo un timeout, che al momento è impostato su 20 secondi, ma potrebbe verificarsi 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 monitoraggio della memoria globale che analizzi l'utilizzo della memoria dell'intera pagina web e che invii i risultati a un server per l'aggregazione e l'analisi. Il modo più semplice è eseguire il campionamento periodicamente, ad esempio ogni M minuti. Tuttavia, ciò introduce un bias nei dati, perché potrebbero verificarsi picchi di memoria tra i campioni.

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

Innanzitutto, definisci una funzione che pianifichi la successiva misurazione della memoria 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 che in media viene effettuata una misurazione ogni cinque minuti. Consulta Distribuzione esponenziale se ti interessa conoscere i calcoli matematici alla base della funzione.

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

Infine, la funzione performMeasurement() asincrona 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 la misurazione.

// Start measurements.
scheduleMeasurement();

Il risultato potrebbe avere il seguente aspetto:

// 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 dell'utilizzo totale della memoria viene restituita nel campo bytes. Questo valore dipende fortemente dall'implementazione e non può essere confrontato tra browser. e può anche variare da una versione all'altra dello stesso browser. Il valore include la memoria JavaScript e DOM di tutti gli iframe, delle finestre correlate e dei web worker nel processo attuale.

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 eseguire l'hardcode delle ipotesi in base a 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 in grado di distinguere quale di queste voci possiede la memoria.

Feedback

Il Web Performance Community Group e il team di Chrome vorrebbero conoscere la tua opinione ed esperienza con performance.measureUserAgentSpecificMemory().

Parlaci della progettazione dell'API

C'è qualcosa che non funziona come previsto nell'API? Oppure mancano delle proprietà che ti servono per implementare la tua idea? Invia un problema relativo alle specifiche sul repository GitHub performance.measureUserAgentSpecificMemory() o aggiungi le tue opinioni a un problema esistente.

Segnalare un problema con l'implementazione

Hai trovato un bug nell'implementazione di Chrome? Oppure l'implementazione è diversa dalle specifiche? Segnala un bug all'indirizzo new.crbug.com. Assicurati di includere il maggior numero di dettagli possibile, fornisci istruzioni semplici per riprodurre il bug e imposta l'opzione Componenti su Blink>PerformanceAPIs. Glitch è la soluzione perfetta per condividere riproduzioni in modo facile e veloce.

Mostra il tuo sostegno

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

Link utili

Ringraziamenti

Ringraziamo Domenic Denicola, Yoav Weiss, Mathias Bynens per le recensioni di progettazione delle API e Dominik Inführ, Hannes Payer, Kentaro Hara e Michael Lippautz per le revisioni del codice in Chrome. Ringrazio inoltre Per Parker, Philipp Weis, Olga Belomestnykh, Matthew Bolohan e Neil Mckay per il prezioso feedback fornito dagli utenti, che ha molto migliorato l'API.

Immagine hero di Harrison Broadbent su Unsplash