Scopri come misurare la memoria utilizzata della tua pagina web in produzione per rilevare le regressioni.
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 "sotto il cofano" per archiviare l'oggetto. Poiché la memoria è una risorsa finita, il browser esegue la garbage collection per rilevare quando un oggetto non è più necessario e per liberare il blocco di memoria sottostante.
Il rilevamento non è perfetto e il problema di arresto 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 relative 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);
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. Pertanto, la memoria dell'array più grande viene persa.
Le perdite di memoria sono comuni sul web, come si può vedere in questo studio. È facile introdurne una 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 gonfia per gli utenti.
Il primo passo per risolvere questo problema è misurarlo. La nuova
performance.measureUserAgentSpecificMemory() API consente agli sviluppatori di
misurare la memoria utilizzata delle proprie pagine web in produzione e quindi rilevare le
perdite di memoria che sfuggono ai test locali.
In che modo performance.measureUserAgentSpecificMemory() è diversa dalla vecchia API performance.memory?
Se hai familiarità con l'API performance.memory non standard esistente, potresti chiederti in che modo la nuova API è diversa. 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 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. Ciò riduce il rumore nei risultati, ma potrebbe essere necessario un po' di tempo prima che vengano prodotti. 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 dell'utente e dalle garbage collection. Ecco perché l'API di misurazione della memoria è destinata all'aggregazione dei dati sulla memoria utilizzata dalla produzione. I risultati delle singole chiamate sono meno utili. Esempi di casi d'uso:
- Rilevamento delle regressioni durante il lancio di una nuova versione della pagina web per intercettare nuove perdite di memoria.
- Test A/B di una nuova funzionalità per valutarne l'impatto sulla memoria e rilevare le perdite di memoria.
- Correlazione della memoria utilizzata con la durata della sessione per verificare la presenza o l'assenza di perdite di memoria.
- Correlazione della memoria utilizzata con le metriche utente per comprendere l'impatto complessivo della memoria utilizzata.
Compatibilità del browser
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 di rappresentare gli oggetti in memoria e modi diversi di stimare la memoria utilizzata. I browser potrebbero escludere alcune regioni di memoria dalla contabilità se la contabilità corretta è troppo costosa o non fattibile. Pertanto, i risultati non possono essere confrontati tra i browser. Ha senso confrontare i risultati solo per lo stesso browser.
Utilizzo di performance.measureUserAgentSpecificMemory()
Rilevamento delle funzionalità
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 perdite di informazioni multiorigine.
Si basa sull'isolamento cross-origin, 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 successiva garbage collection.
La chiamata all'API forza una garbage collection dopo un timeout, attualmente impostato su 20 secondi, anche se potrebbe avvenire prima. L'avvio di Chrome con il
--enable-blink-features='ForceEagerMeasureMemory' flag della riga di comando riduce
il timeout a zero ed è utile per il debug e i test locali.
Esempio
L'utilizzo consigliato dell'API è definire un monitor di 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, ciò introduce un bias nei dati perché i picchi di memoria possono verificarsi tra i campioni.
L'esempio seguente mostra come eseguire misurazioni della memoria senza bias utilizzando un processo di Poisson, che garantisce che i campioni abbiano la stessa probabilità di verificarsi in qualsiasi momento (demo, origine).
Innanzitutto, definisci una funzione che pianifica la successiva misurazione della memoria utilizzando setTimeout() con un intervallo casuale.
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 utilizzata viene restituita nel campo bytes. Questo valore dipende molto dall'implementazione e non può essere confrontato tra i browser. Potrebbe persino 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 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 codificare le ipotesi in base a un browser specifico. Ad esempio, alcuni browser potrebbero restituire un breakdown vuoto o un attribution vuoto. Altri browser potrebbero restituire più voci in attribution, indicando che non sono riusciti a distinguere quale di queste voci possiede la memoria.
Feedback
Il gruppo della community Web Performance e il team di Chrome sarebbero lieti
di conoscere le tue opinioni ed esperienze con
performance.measureUserAgentSpecificMemory().
Parlaci della progettazione dell'API
C'è qualcosa nell'API che non funziona come previsto? Oppure mancano proprietà di cui hai bisogno per implementare la tua idea? Apri un problema di specifica nel repository GitHub performance.measureUserAgentSpecificMemory() o aggiungi i tuoi pensieri a un problema esistente.
Segnala un problema con l'implementazione
Hai trovato un bug nell'implementazione di Chrome? Oppure l'implementazione è diversa dalla specifica? Apri 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 Componenti su Blink>PerformanceAPIs.
Mostra il tuo supporto
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 sia fondamentale supportarle. Invia un tweet a @ChromiumDev
e comunicaci dove e come lo utilizzi.
Link utili
- Video esplicativo
- Demo | Origine della demo
- Bug di monitoraggio
- Voce di ChromeStatus.com
- Modifiche dall'API Origin Trial
- Origin Trial conclusa
Ringraziamenti
Un ringraziamento speciale a Domenic Denicola, Yoav Weiss, Mathias Bynens per le revisioni della progettazione 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 feedback preziosi degli utenti che hanno migliorato notevolmente l'API.