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 raccolta dei rifiuti 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 il rilevamento perfetto è un'impresa 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ò recuperarlo in sicurezza. La differenza tra queste due nozioni porta a perdite di memoria, come illustrato dall'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. Di conseguenza, la memoria dell'array più grande viene persa.

Le fughe di memoria sono prevalenti sul web. È facile introdurne uno dimenticandosi di annullare la registrazione di un gestore 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 le perdite di memoria che sfuggono ai test locali.

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

Se hai familiarità con l'API performance.memory non standard esistente, ti starai chiedendo in cosa differisce la 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", non è possibile standardizzarla.

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 raccolta dei rifiuti.

Casi d'uso suggeriti

L'utilizzo della memoria di una pagina web dipende dalla tempistica di eventi, azioni utente e raccolte dei rifiuti. Ecco perché l'API di misurazione della memoria è progettata per aggregare i dati sull'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 valutarne l'impatto sulla memoria e rilevare perdite di memoria.
  • Correlare l'utilizzo della memoria con la durata della sessione per verificare la presenza o l'assenza di perdite di memoria.
  • Correlare l'utilizzo della memoria con le metriche utente per comprendere l'impatto complessivo dell'utilizzo della memoria.

Compatibilità del browser

Supporto dei browser

  • Chrome: 89.
  • Edge: 89.
  • Firefox: non supportato.
  • Safari: non supportato.

Origine

Al momento 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 per rappresentare gli oggetti in memoria e modi diversi per stimare l'utilizzo della memoria. I browser potrebbero escludere alcune regioni di memoria dal calcolo se la contabilità corretta è troppo costosa o non fattibile. Pertanto, i risultati non possono essere confrontati tra i browser. È significativo confrontare i risultati solo 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 la prevenzione delle fughe di informazioni cross-origin. Si basa sull'isolamento multiorigine, che una pagina web può attivare impostando le 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 raccolta dei rifiuti, il che significa che l'API non risolve immediatamente la promessa del risultato, ma attende la raccolta dei rifiuti successiva.

La chiamata all'API forza una raccolta dei rifiuti dopo un determinato timeout, attualmente impostato su 20 secondi, anche se 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 è definire un monitor della memoria globale che campionata l'utilizzo della memoria dell'intera pagina web e invia 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é tra i campioni possono verificarsi picchi di memoria.

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

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 in modo che in media venga eseguita 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 asincrona performMeasurement() invoca 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 è molto dipendente dall'implementazione e non può essere confrontato tra browser. Può persino cambiare da una versione all'altra 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 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 hardcode le supposizioni in base a un determinato browser. Ad esempio, alcuni browser possono restituire un breakdown o un attribution vuoto. Altri browser potrebbero restituire più voci in attribution, indicando che non è stato possibile 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 nell'API che non funziona come previsto? Oppure mancano delle proprietà che ti servono per implementare la tua idea? Invia una segnalazione relativa alle specifiche nel repository GitHub di performance.measureUserAgentSpecificMemory() o aggiungi il tuo parere a un problema esistente.

Segnalare un problema con l'implementazione

Hai trovato un bug nell'implementazione di Chrome? Oppure l'implementazione è diversa dalla specifica? Invia un bug all'indirizzo new.crbug.com. Assicurati di includere il maggior numero possibile di dettagli, di fornire istruzioni semplici per riprodurre il bug e di impostare Componenti su Blink>PerformanceAPIs. Glitch è ideale per condividere riproduzioni rapide e semplici.

Mostrare il proprio 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 importante supportarle. Invia un tweet all'indirizzo @ChromiumDev e facci sapere dove e come lo utilizzi.

Link utili

Ringraziamenti

Un grande ringraziamento a Domenic Denicola, Yoav Weiss, Mathias Bynens per le revisioni del design dell'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 contribuito a migliorare notevolmente l'API.

Immagine hero di Harrison Broadbent su Unsplash