Découvrez comment mesurer l'utilisation de la mémoire de votre page Web en production pour détecter les régressions.
Les navigateurs gèrent automatiquement la mémoire des pages Web. Chaque fois qu'une page Web crée un objet, le navigateur alloue un bloc de mémoire "en arrière-plan" pour le stocker. La mémoire étant une ressource limitée, le navigateur effectue une récupération de mémoire pour détecter quand un objet n'est plus nécessaire et libérer le bloc de mémoire sous-jacent.
La détection n'est toutefois pas parfaite, et le problème de l'arrêt d'Alan Turing a prouvé qu'une détection parfaite est impossible. Par conséquent, les navigateurs approximent la notion d'"objet nécessaire" avec la notion d'"objet accessible". Si la page Web ne peut pas accéder à un objet via ses variables et les champs d'autres objets accessibles, le navigateur peut le récupérer en toute sécurité. La différence entre ces deux notions entraîne des fuites de mémoire, comme l'illustre l'exemple suivant.
const object = {a: new Array(1000), b: new Array(2000)};
setInterval(() => console.log(object.a), 1000);
Ici, le plus grand tableau b n'est plus nécessaire, mais le navigateur ne le
récupère pas, car il est toujours accessible via object.b dans le rappel. La mémoire du plus grand tableau est donc perdue.
Les fuites de mémoire sont fréquentes sur le Web, comme le montre cette étude. Il est facile d'en introduire une en oubliant de désinscrire un écouteur d'événement, en capturant accidentellement des objets à partir d'un iframe, en ne fermant pas un nœud de calcul, en accumulant des objets dans des tableaux, etc. Si une page Web présente des fuites de mémoire, son utilisation de la mémoire augmente au fil du temps et la page Web semble lente et volumineuse pour les utilisateurs.
La première étape pour résoudre ce problème consiste à le mesurer. La nouvelle
performance.measureUserAgentSpecificMemory() API permet aux développeurs de
mesurer l'utilisation de la mémoire de leurs pages Web en production et de détecter ainsi les fuites de mémoire qui passent inaperçues lors des tests locaux.
En quoi performance.measureUserAgentSpecificMemory() diffère-t-elle de l'ancienne API performance.memory ?
Si vous connaissez l'API performance.memory non standard existante, vous vous demandez peut-être en quoi la nouvelle API diffère de celle-ci. La principale différence est que l'ancienne API renvoie la taille du tas JavaScript, tandis que la nouvelle API estime la mémoire utilisée par la page Web. Cette différence devient importante lorsque Chrome partage le même tas de mémoire avec plusieurs pages Web (ou plusieurs instances de la même page Web). Dans ce cas, le résultat de l'ancienne API peut être arbitrairement désactivé. Étant donné que l'ancienne API est définie en termes spécifiques à l'implémentation, tels que "tas", il est impossible de la normaliser.
Une autre différence est que la nouvelle API effectue la mesure de la mémoire lors de la récupération de mémoire. Cela réduit le bruit dans les résultats, mais leur production peut prendre un certain temps. Notez que d'autres navigateurs peuvent décider d'implémenter la nouvelle API sans s'appuyer sur la récupération de mémoire.
Cas d'utilisation suggérés
L'utilisation de la mémoire d'une page Web dépend du timing des événements, des actions de l'utilisateur et des récupérations de mémoire. C'est pourquoi l'API de mesure de la mémoire est destinée à agréger les données d'utilisation de la mémoire à partir de la production. Les résultats des appels individuels sont moins utiles. Exemples d'utilisation :
- Détection de régression lors du déploiement d'une nouvelle version de la page Web pour détecter de nouvelles fuites de mémoire.
- Test A/B d'une nouvelle fonctionnalité pour évaluer son impact sur la mémoire et détecter les fuites de mémoire.
- Corrélation de l'utilisation de la mémoire avec la durée de la session pour vérifier la présence ou l'absence de fuites de mémoire.
- Corrélation de l'utilisation de la mémoire avec les métriques utilisateur pour comprendre l'impact global de l'utilisation de la mémoire.
Compatibilité du navigateur
Actuellement, l'API n'est compatible qu'avec les navigateurs basés sur Chromium, à partir de Chrome 89. Le résultat de l'API dépend fortement de l'implémentation, car les navigateurs ont différentes manières de représenter les objets en mémoire et différentes manières d'estimer l'utilisation de la mémoire. Les navigateurs peuvent exclure certaines régions de mémoire de la comptabilité si la comptabilité appropriée est trop coûteuse ou impossible. Par conséquent, les résultats ne peuvent pas être comparés entre les navigateurs. Il n'est utile de comparer les résultats que pour le même navigateur.
Utiliser performance.measureUserAgentSpecificMemory()
Détection de fonctionnalités
La fonction performance.measureUserAgentSpecificMemory ne sera pas disponible ou peut
échouer avec une SecurityError si l'environnement d'exécution ne répond pas
aux exigences de sécurité pour empêcher les fuites d'informations inter-origines.
Elle repose sur l'isolation inter-origines, qu'une page Web peut activer
en définissant des en-têtes COOP+COEP.
La compatibilité peut être détectée au moment de l'exécution :
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 local
Chrome effectue la mesure de la mémoire lors de la récupération de mémoire, ce qui signifie que l'API ne résout pas immédiatement la promesse de résultat et attend la prochaine récupération de mémoire.
L'appel de l'API force une récupération de mémoire après un délai d'expiration, actuellement défini sur 20 secondes, mais qui peut se produire plus tôt. Le démarrage de Chrome avec l'
--enable-blink-features='ForceEagerMeasureMemory' indicateur de ligne de commande réduit
le délai d'expiration à zéro et est utile pour le débogage et les tests locaux.
Exemple
L'utilisation recommandée de l'API consiste à définir un moniteur de mémoire global qui échantillonne l'utilisation de la mémoire de l'ensemble de la page Web et envoie les résultats à un serveur pour agrégation et analyse. La méthode la plus simple consiste à effectuer un échantillonnage périodique, par exemple toutes les M minutes. Toutefois, cela introduit un biais dans les données, car des pics de mémoire peuvent se produire entre les échantillons.
L'exemple suivant montre comment effectuer des mesures de mémoire non biaisées à l'aide d'un processus de Poisson, qui garantit que les échantillons sont tout aussi susceptibles de se produire à tout moment (démo, source).
Commencez par définir une fonction qui planifie la prochaine mesure de la mémoire à l'aide de setTimeout() avec un intervalle aléatoire.
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 fonction measurementInterval() calcule un intervalle aléatoire en millisecondes de sorte qu'en moyenne, une mesure soit effectuée toutes les cinq minutes. Consultez la distribution
exponentielle si vous êtes intéressé par les mathématiques sous-jacentes à la fonction.
function measurementInterval() {
const MEAN_INTERVAL_IN_MS = 5 * 60 * 1000;
return -Math.log(Math.random()) * MEAN_INTERVAL_IN_MS;
}
Enfin, la fonction asynchrone performMeasurement() appelle l'API, enregistre le résultat et planifie la mesure suivante.
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();
}
Enfin, commencez la mesure.
// Start measurements.
scheduleMeasurement();
Le résultat peut se présenter comme suit :
// 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']
},
],
}
L'estimation totale de l'utilisation de la mémoire est renvoyée dans le champ bytes. Cette valeur dépend fortement de l'implémentation et ne peut pas être comparée entre les navigateurs. Elle peut même varier entre différentes versions du même navigateur. La valeur inclut la mémoire JavaScript et DOM de tous les iframes, fenêtres associées et nœuds de calcul Web du processus actuel.
La liste breakdown fournit des informations supplémentaires sur la mémoire utilisée. Chaque entrée décrit une partie de la mémoire et l'attribue à un ensemble de fenêtres, d'iframes et de nœuds de calcul identifiés par une URL. Le champ types répertorie les types de mémoire spécifiques à l'implémentation associés à la mémoire.
Il est important de traiter toutes les listes de manière générique et de ne pas coder en dur des hypothèses basées sur un navigateur particulier. Par exemple, certains navigateurs peuvent renvoyer un breakdown vide ou une attribution vide. D'autres navigateurs peuvent renvoyer plusieurs entrées dans attribution, indiquant qu'ils n'ont pas pu distinguer laquelle de ces entrées possède la mémoire.
Commentaires
Le groupe de la communauté Web Performance et l'équipe Chrome aimeraient
connaître votre avis et votre expérience avec
performance.measureUserAgentSpecificMemory().
Parlez-nous de la conception de l'API
Y a-t-il quelque chose dans l'API qui ne fonctionne pas comme prévu ? Ou manque-t-il des propriétés dont vous avez besoin pour implémenter votre idée ? Signalez un problème de spécification dans le dépôt GitHub performance.measureUserAgentSpecificMemory() ou ajoutez vos commentaires à un problème existant.
Signaler un problème lié à l'implémentation
Avez-vous trouvé un bug dans l'implémentation de Chrome ? Ou l'implémentation est-elle différente de la spécification ? Signalez un bug sur new.crbug.com. Veillez à
inclure autant de détails que possible, à fournir des instructions simples pour reproduire
le bug et à définir Components sur Blink>PerformanceAPIs.
Afficher votre soutien
Prévoyez-vous d'utiliser performance.measureUserAgentSpecificMemory() ? Votre soutien public aide l'équipe Chrome à hiérarchiser les fonctionnalités et montre aux autres fournisseurs de navigateurs à quel point il est essentiel de les prendre en charge. Envoyez un tweet à @ChromiumDev
et dites-nous où et comment vous l'utilisez.
Liens utiles
- Vidéo explicative
- Démo | Source de la démo
- Bug de suivi
- Entrée ChromeStatus.com
- Modifications depuis l'API de l'Origin Trial
- Origin Trial terminée
Remerciements
Un grand merci à Domenic Denicola, Yoav Weiss et Mathias Bynens pour leurs commentaires sur la conception de l'API, ainsi qu'à Dominik Inführ, Hannes Payer, Kentaro Hara et Michael Lippautz pour leurs commentaires sur le code dans Chrome. Je remercie également Per Parker, Philipp Weis, Olga Belomestnykh, Matthew Bolohan et Neil Mckay pour leurs précieux commentaires d'utilisateurs qui ont grandement amélioré l'API.