Surveillez l'utilisation totale de la mémoire de votre page Web avec measureUserAgentSpecificMemory()

Découvrez comment mesurer l'utilisation de la mémoire de votre page Web en production pour détecter les régressions.

Brendan Kenny
Bndan Kenny
Oulan Degenbaev
Oulan Degenbaev

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 fragment de mémoire "en coulisses" pour stocker l'objet. La mémoire étant une ressource finie, le navigateur effectue une récupération de mémoire pour détecter le moment où un objet n'est plus nécessaire et pour libérer le fragment de mémoire sous-jacent.

Cependant, la détection n'est pas parfaite et il a été prouvé que la détection parfaite est une tâche impossible. Par conséquent, les navigateurs se rapprochent de la notion "un objet est nécessaire" par rapport à la notion "un objet est accessible". Si la page Web ne peut pas atteindre un objet via ses variables et les champs d'autres objets accessibles, le navigateur peut récupérer l'objet en toute sécurité. La différence entre ces deux notions entraîne des fuites de mémoire, comme illustré dans 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. Ainsi, la mémoire du plus grand tableau est divulguée.

Les fuites de mémoire sont fréquentes sur le Web. Il est facile d'en créer un en oubliant d'annuler l'enregistrement d'un écouteur d'événements, en capturant accidentellement des objets à partir d'un iFrame, en ne fermant pas un worker, en accumulant des objets dans des tableaux, etc. Si une page Web présente des fuites de mémoire, son utilisation de mémoire augmente avec le temps, et la page Web semble lente et surchargée pour les utilisateurs.

La première étape pour résoudre ce problème consiste à le mesurer. La nouvelle API performance.measureUserAgentSpecificMemory() 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 se glissent lors des tests en local.

En quoi performance.measureUserAgentSpecificMemory() est-il différent 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. La principale différence est que l'ancienne API renvoie la taille du tas de mémoire JavaScript, tandis que la nouvelle API estime la mémoire utilisée par la page Web. Cette différence est 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 incorrect. Étant donné que l'ancienne API est définie dans des termes spécifiques à l'implémentation, tels que "tas de mémoire", il est impossible de la normaliser.

Une autre différence est que la nouvelle API effectue des mesures de la mémoire lors de la récupération de mémoire. Cette approche réduit le bruit dans les résultats, mais peut prendre un certain temps avant que les résultats ne soient produits. Notez que d'autres navigateurs peuvent décider d'implémenter la nouvelle API sans recourir à 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 de la chronologie des événements, des actions des utilisateurs et de la récupération de mémoire. C'est pourquoi l'API de mesure de la mémoire est conçue pour agréger les données d'utilisation de la mémoire en production. Les résultats des appels individuels sont moins utiles. Exemples de cas d'utilisation :

  • Détection de la régression lors du déploiement d'une nouvelle version de la page Web pour détecter les nouvelles fuites de mémoire
  • Effectuer des tests A/B sur une nouvelle fonctionnalité pour évaluer son impact sur la mémoire et détecter les fuites de mémoire
  • Mettre en corrélation 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

Navigateurs pris en charge

  • 89
  • 89
  • x
  • x

Source

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 comptabilisation si une comptabilisation appropriée est trop coûteuse ou impossible. Ainsi, les résultats ne peuvent pas être comparés d'un navigateur à l'autre. Il est seulement pertinent de comparer les résultats pour un même navigateur.

Utiliser performance.measureUserAgentSpecificMemory()

Détection de fonctionnalités

La fonction performance.measureUserAgentSpecificMemory sera indisponible ou peut échouer avec une erreur SecurityError si l'environnement d'exécution ne respecte pas les exigences de sécurité permettant d'éviter les fuites d'informations multi-origines. Il repose sur l'isolation multi-origine, 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 la promesse de résultat immédiatement et qu'elle 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 certain délai, actuellement défini sur 20 secondes, mais cette opération peut se produire plus tôt. Le démarrage de Chrome avec l'indicateur de ligne de commande --enable-blink-features='ForceEagerMeasureMemory' réduit le délai d'inactivité à zéro et est utile pour le débogage et les tests en local.

Exemple

L'utilisation recommandée de l'API consiste à définir un moniteur de mémoire globale qui échantillonne l'utilisation de la mémoire de l'ensemble de la page Web et envoie les résultats à un serveur à des fins d'agrégation et d'analyse. Le moyen le plus simple consiste à effectuer un échantillonnage périodique, par exemple toutes les M minutes. Cependant, cela entraîne 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 ont la même probabilité de se produire à tout moment (demo, source).

Tout d'abord, définissez 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'il y ait en moyenne une mesure toutes les cinq minutes. Consultez la section Distribution exponentielle si vous vous intéressez aux calculs mathématiques de 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 à mesurer.

// Start measurements.
scheduleMeasurement();

Vous pouvez obtenir le résultat suivant:

// 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 de l'utilisation totale 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 sur plusieurs navigateurs. Elle peut même varier entre différentes versions d'un même navigateur. Cette valeur inclut la mémoire JavaScript et DOM de tous les iFrames, les fenêtres associées et les workers Web du processus en cours.

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 les hypothèses basées sur un navigateur particulier. Par exemple, certains navigateurs peuvent renvoyer une valeur breakdown ou attribution vide. D'autres navigateurs peuvent renvoyer plusieurs entrées dans attribution, indiquant qu'ils ne peuvent pas distinguer celle qui 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 concernant performance.measureUserAgentSpecificMemory().

Décrivez-nous 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 mettre en œuvre 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 d'implémentation

Avez-vous détecté un bug dans l'implémentation de Chrome ? Ou l'implémentation est-elle différente des spécifications ? Signalez un bug à l'adresse new.crbug.com. Veillez à inclure autant de détails que possible, à fournir des instructions simples pour reproduire le bug et à définir l'option Composants sur Blink>PerformanceAPIs. Glitch est idéal pour partager des reproductions simples et rapides.

Afficher le soutien

Comptez-vous utiliser performance.measureUserAgentSpecificMemory() ? Votre assistance publique 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 pour nous indiquer où et comment vous l'utilisez.

Liens utiles

Remerciements

Un grand merci à Domenic Denicola, Yoav Weiss et Mathias Bynens pour les examens de conception des API, et à Dominik Inführ, Hannes Payer, Kentaro Hara et Michael Lippautz pour les revues de code dans Chrome. Je remercie également Per Parker, Philipp Weis, Olga Belomestnykh, Matthew Bolohan et Neil Mckay pour les précieux commentaires des utilisateurs qui ont permis d'améliorer considérablement l'API.

Image principale de Harrison Broadbent sur Unsplash