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 le regressioni.

[Nome di persona]
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 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 ed è stato dimostrato che un rilevamento perfetto è un'operazione impossibile. Pertanto, i browser approssimano la nozione di "è necessario un oggetto" 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 in sicurezza l'oggetto. 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);

Qui 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 divulgata.

Le fughe di memoria sono prevalenti sul web. È facile introdurlo 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 delle perdite di memoria, l'utilizzo della memoria aumenta nel tempo e la pagina web risulta 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, rilevando così le perdite di memoria che non superano i test locali.

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

Se conosci l'API performance.memory non standard esistente, è possibile che ti stia chiedendo in che modo si differenzia dalla 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", standardizzarla è senza speranza.

Un'altra differenza è che la nuova API esegue la misurazione della memoria durante la garbage collection. Questo riduce il rumore nei risultati, ma potrebbe essere necessario attendere un po' di tempo prima che i risultati 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 utente e delle garbage collection. Ecco perché l'API di misurazione della memoria è destinata all'aggregazione dei dati di utilizzo della memoria di 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 valutarne l'impatto sulla memoria e rilevare eventuali 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 dell'utilizzo della memoria.

Compatibilità del browser

Supporto dei browser

  • 89
  • 89
  • x
  • x

Fonte

Attualmente l'API è supportata solo nei browser basati su Chromium, a partire da Chrome 89. Il risultato dell'API è fortemente dipendente dall'implementazione, perché i browser hanno modi diversi di rappresentare gli oggetti in memoria e modi diversi di stimare l'utilizzo della memoria. I browser potrebbero escludere alcune regioni di memoria dalla contabilità se la contabilità corretta è troppo costosa o impossibile. Pertanto, i risultati non possono essere confrontati tra i browser. È significativo solo confrontare i risultati per lo stesso browser.

In uso: performance.measureUserAgentSpecificMemory()

Rilevamento delle funzionalità

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 evitare fughe di informazioni tra origini. Si basa sull'isolamento multiorigine, che una pagina web può attivare impostando 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 dei risultati, ma attende la garbage collection successiva.

La chiamata all'API impone una garbage collection dopo un certo 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 è definire un monitoraggio della memoria globale che campioni l'utilizzo della memoria dell'intera pagina web e invii 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é potrebbero verificarsi picchi di memoria tra i campioni.

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

Innanzitutto, definisci una funzione che pianifica 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 in modo che in media ci sia una misurazione ogni cinque minuti. Consulta Distribuzione esponenziale se ti interessano 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 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 la misurazione.

// 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 dell'utilizzo totale della memoria viene restituita nel campo bytes. Questo valore dipende fortemente dall'implementazione e non può essere confrontato tra browser. Potrebbe anche cambiare tra le diverse versioni dello stesso browser. Il valore include la memoria JavaScript e DOM di tutti gli iframe, le finestre correlate e i 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 da un 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 delle ipotesi in base a un determinato browser. Ad esempio, alcuni browser potrebbero restituire un valore breakdown vuoto o uno attribution vuoto. Altri browser potrebbero restituire più voci in attribution, a indicare che non sono in grado di distinguere quale di queste voci è proprietaria della memoria.

Feedback

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

Parlaci della progettazione dell'API

C'è qualcosa nell'API che non funziona come previsto? Oppure mancano delle proprietà di cui hai bisogno per implementare la tua idea? Segnala un problema di specifica sul repository GitHub di performance.measureUserAgentSpecificMemory() o aggiungi le tue opinioni a un problema esistente.

Segnala 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, di fornire istruzioni semplici per riprodurre il bug e di impostare i componenti su Blink>PerformanceAPIs. Glitch funziona benissimo per condividere riproduzioni rapide e semplici.

Mostra assistenza

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

Link utili

Ringraziamenti

Grazie di cuore a Domenic Denicola, Yoav Weiss, Mathias Bynens per le revisioni del design delle API e Dominik Inführ, Hannes Payer, Kentaro Hara e 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 notevolmente migliorato l'API.

Immagine hero di Harrison Broadbent su Unsplash