Chronologie

Cet article nécessite une relecture technique. Voici comment vous pouvez aider.

La chronologie vous donne des indications sur les opérations qu'effectue le navigateur lorsqu'il fait tourner une page web ou une application. Cet outil se base sur l'idée que les opérations qu'effectue un navigateur peuvent être divisées en plusieurs types : exécution du JavaScript, mise à jour du layout, et ainsi de suite... Ainsi que l'idée qu'à un point donné dans le temps, le navigateur effectue l'une de ces actions.

Donc, si vous voyez un problème de performance - une chute du frame rate par exemple - vous pouvez utiliser la chronologie pour voir ce que le navigateur faisait au moment de l'enregistrement.

L'axe X représente le temps. Les opérations enregistrées, appelées marqueurs sont affichées sous la forme de barres de couleur horizontales. Ces marqueurs sont disposés en cascade pour refléter le caractère séquentiel de l'exécution du navigateur.

Lorsqu'un marqueur est sélectionné, des informations supplémentaires sur celui-ci s'affichent dans le panneau de droite. Ces informations intègrent la durée du marqueur et quelques informations spécifiques de son type.

Marqueurs

Les marqueurs possèdent un code couleur et un libellé. Les opérations suivantes sont enregistrées :

Nom et description Couleur Informations détaillées

Évènements DOM

Le code JavaScript qui est exécuté en réponse à un évènement DOM.

Type de l'évènement
Par exemple, "click" ou "message".
Phase de l'évènement
Par exemple "Target" ou "Capture".

Les fonctions JavaScript exécutées dans la page ont le libellé de la raison pour laquelle la fonction à été appelée :

Script Tag
setInterval
setTimeout
requestAnimationFrame
Promise Callback
Promise Init
Worker
JavaScript URI
Event Handler

Pile
La pile d'appels avec des liens vers les fonctions.

Parse HTML

Le temps passé à parser le HTML de la page.

Pile
La pile d'appels avec des liens vers les fonctions.

Parse XML

Le temps passé à parser le XML de la page.

Pile
La pile d'appels avec des liens vers les fonctions.

Recalcul des styles

Le calcul des styles qui s'appliquent aux éléments de la page.

Causes du recalcul
Une chaine de caractères indiquant quel type de recalcul est nécessaire. Elle peut prendre les valeurs suivantes :
Self
Subtree
LaterSiblings
CSSTransitions
CSSAnimations
SVGAttrAnimations
StyleAttribute
StyleAttribute_Animations
Force
ForceDescendants

Layout

Le calcul des positions et de la taille des éléments de la page. Cette opération est parfois appelée "reflow".

 

Paint

Affichage des pixels à l'écran

 

Ramasse-miettes (non incrémentiel)

évènement de garbage collection. Les évènements GC non incrémentaux sont nommés "non incrémentiel".

Raison
Une chaine de caractères indiquant la raison pour laquelle le ramasse-miettes a été effectué.
Raison du cycle non incrémentiel
Si l'évènement n'était pas incrémentiel, une chaine expliquant pourquoi la GC a été effectuée.

Console

La période entre les appels à console.time() et console.timeEnd().

Nom du timer
L'argument passé aux fonctions console.
Pile au début
La pile d'appels à console.time(), avec des liens vers les fonctions.
Pile de fin
(Nouveau dans Firefox 41). La pile d'appel console.timeEnd(). S'il s'agit d'un appel dans un callback de Promise, cela montrera également la "Async stack".

Timestamp

Un appel unique à console.timeStamp().

Label
L'argument passé à timeStamp().

Les marqueurs et leurs couleurs sont les mêmes dans la chronologie que dans la vue d'ensemble de la chronologie.

Filtrer les marqueurs

Il est possible de contrôler quels marqueurs sont affichés en utilisant le bouton dans la barre d'outils :

Motifs de la chronologie

Le contenu de l´enregistrement dépend énormément des opérations réalisées par le site : Les sites utilisant beaucoup de JavaScript auront un profil à dominance orange, alors que les sites à contenu visuel dynamique auront beaucoup de violet et de vert. Cependant certains motifs communs peuvent vous indiquer de possibles problèmes de performance.

Chronologie de rendu

Le motif suivant est très courant dans la vue de la chronologie :

C'est une visualisation de l´algorithme de base qu'utilise le navigateur pour mettre à jour la page en réponse à un évènement :

  1. JavaScript Function Call: Un évènement quelconque - par exemple un évènement DOM - a pour effet de lancer du JavaScript. Le code change du DOM ou du CSSOM.
  2. Recalculate Style: Si le navigateur pense que des styles calculés de la page ont changé, il les recalcule.
  3. Layout: Puis, la navigateur utilise les styles calculés pour déterminer la position et la géométrie des éléments. Cette opération est étiquetée "layout" mais on l'appelle aussi « reflow ».
  4. Paint: Enfin, le navigateur a besoin de réafficher les éléments à l'écran. Une dernière étape n'est pas représentée dans cette séquence: La page peut être divisée en couches, qui sont affichées indépendamment puis assemblées lors d'un processus appelé "Composition".

Cette séquence doit se terminer en une seule image, puisque l'écran n'est pas mis à jour avant qu'elle soit terminée. Il est communément admis que 60 images/secondes est la vitesse à laquelle les animations apparaîtront fluides. Pour un taux de 60 images/secondes, cela laisse au navigateur 16,7 millisecondes pour exécuter le flow complet.

Il est important pour la réactivité à ce que le navigateur n´ait pas à passer par toutes ces étapes à chaque fois :

  • Les animations CSS mettent à jour la page sans avoir besoin d'exécuter du JavaScript.
  • Toutes les propriétés CSS modifiées ne causent pas de « reflow ». Modifier des propriétés qui changent la position ou la géométrie d'un objet, telles que width, display, font-size, ou top, provoqueront un « reflow ». Par contre, modifier des propriétés qui ne changent par la géométrie ou le positionnement, telles que color ou opacity, n'en feront rien.
  • Toutes les propriétés CSS modifiées n'amènent pas à un repaint. En particulier, si vous animez un élément en utilisant la propriété transform, le navigateur utilisera une couche séparée pour l'élément transformé, et n'aura peut-être même pas à faire un repaint si l'élément est déplacé : Sa nouvelle position est traitée dans la composition.

L'article Animer des propriétés CSS montre qu'animer différentes propriétés CSS peut donner des résultats de performance différents, et comment la chronologie peut aider à le voir.

JavaScript bloquant

Par défaut, le JavaScript des pages web est exécuté sur le même thread que celui que le navigateur utilise pour les mises à jour de layout, les repaints, les évènements DOM et ainsi de suite. Cela signifie que des fonctions JavaScript qui mettent longtemps à s´exécuter peuvent causer des ralentissements (jank). Les animations ne seront pas fluides et le site pourra même freezer.

En utilisant l'outil frame rate et la « chronologie », il est facile de voir lorsque des fonctions JavaScript posent problème. Dans la capture d´écran ci-dessous, un zoom a été fait sur une fonction JS qui cause une chute du frame rate :

L'article JavaScript Intensif montre comment la « chronologie » peut mettre en évidence les problèmes de réactivité causés par des fonctions JavaScript trop gourmandes, et comment utiliser des méthodes asynchrones pour garder le thread principal réactif.

« Décorations » coûteuses

Certains effets tels que box-shadow, peuvent être coûteux ; surtout lors de transitions où le navigateur doit recalculer cet effet à chaque image. Si vous remarquez des chutes de frame rate surtout lors d'opérations et transitions graphiques couteuses, recherchez des longs marqueurs verts (repaint) dans la chronologie.

Ramasse-miettes (Garbage Collection)

Les marqueurs rouges dans la chronologie représentent le passage du ramasse-miettes (GC), pour lequel SpiderMonkey (le moteur JavaScript de Firefox) parcourt la pile à la recherche d´éléments en mémoire qui ne sont plus accessibles pour les libérer. La GC est un indice de performance, car lorsqu’elle « tourne », le moteur JavaScript doit être en pause et donc le programme est mis en pause et ne répondra pas.

Pour aider à réduire la durée de ces pauses, SpiderMonkey implémente une GC morcelée. Cela signifie qu´il peut faire de la garbage collection par petites « passes », permettant au programme de tourner entre celles-ci. Parfois, le moteur à besoin de réaliser une garbage collection complète et non par morceaux et alors, le programme a besoin d'attendre la fin de cette opération.

Lorsque la Chronologie enregistre un marqueur GC, cela indique :

  • Si le ramasse-miettes passait de manière morcelée ou pas.
  • La raison de l'événement GC qui a eu lieu.
  • Si la passe du ramasse-miettes était complète, et la raison pour laquelle elle l'était.

Pour essayer d’éviter les évènements GC et surtout les passages complets, il est sage de ne pas essayer d'optimiser pour l'implémentation spécifique du moteur JavaScript. SpiderMonkey utilise un ensemble complexe d'heuristiques pour déterminer quand la GC est nécessaire, et quand une GC non incrémentale en particulier est nécessaire. En général cependant :

  • La GC est nécessaire lorsque beaucoup de mémoire est allouée.
  • La GC complète est généralement nécessaire lorsque le taux d'allocation mémoire est assez haut pour que SpiderMonkey puisse être à court de mémoire durant une GC morcelée.

Ajouter des marqueurs avec la console API

Deux marqueurs peuvent être contrôlés par des appels à la console API : "Console" et "Timestamp".

Marqueurs de console

Ces marqueurs permettent de marquer une section spécifique de l'enregistrement.

Pour faire un marqueur console, il faut appeler console.time() au début d'une section, et console.timeEnd() à la fin. Ces fonctions prennent un argument qui est le nom de la section.

Par exemple si nous avons un code comme ceci :

var iterations = 70;
var multiplier = 1000000000;

function calculatePrimes() {

  console.time("calculating...");

  var primes = [];
  for (var i = 0; i < iterations; i++) {
    var candidate = i * (multiplier * Math.random());
    var isPrime = true;
    for (var c = 2; c <= Math.sqrt(candidate); ++c) {
      if (candidate % c === 0) {
          // not prime
          isPrime = false;
          break;
       }
    }
    if (isPrime) {
      primes.push(candidate);
    }
  }

  console.timeEnd("calculating...");

  return primes;
}

La sortie de la Chronologie ressemblera à ceci :

Le marqueur est nommé par l'argument passé à console.time(), et lorsque le marqueur est sélectionné, il est possible de voir la pile du programme dans l'encadré sur le coté droit.

Tache Async

Nouvautée de Firefox 41.

À partir de Firefox 41, l'encadré de droite affichera également la stack à la fin de la période. C´est à dire au moment où console.timeEnd() a été appelé. Si console.timeEnd() a été appelé par la résolution d'une Promise, l'encadré affichera également le label "(Async: Promise)", sous lequel sera affiché la "async stack" : Il s'agit de la pile d'appel au moment où la promise a été faite.

Par exemple, avec ce code :

var timerButton = document.getElementById("timer");
timerButton.addEventListener("click", handleClick, false);

function handleClick() {
  console.time("timer");
  runTimer(1000).then(timerFinished);
}

function timerFinished() {
  console.timeEnd("timer");
  console.log("ready!");
}

function runTimer(t) {
  return new Promise(function(resolve) {
    setTimeout(resolve, t);
  });
}

La Chronologie affichera un marqueur pour la période entre time() et timeEnd(), et s’il est sélectionné, la pile async apparaitra dans l'encadré :

Marqueurs de temps

Les Timestamps permettent de marquer un instant dans l'enregistrement.

Pour faire un timestamp, il faut appeler console.timeStamp(). Il est possible de passer un argument pour nommer le timestamp.

Par exemple, supposons que nous adaptions le code au-dessus pour faire un timestamp toutes les 10 itérations de la boucle ayant pour nom le nombre de l'itération :

var iterations = 70;
var multiplier = 1000000000;

function calculatePrimes() {
  console.time("calculating...");

  var primes = [];
  for (var i = 0; i < iterations; i++) {

    if (i % 10 == 0) {
      console.timeStamp(i.toString());
    }
    
    var candidate = i * (multiplier * Math.random());
    var isPrime = true;
    for (var c = 2; c <= Math.sqrt(candidate); ++c) {
      if (candidate % c === 0) {
          // not prime
          isPrime = false;
          break;
       }
    }
    if (isPrime) {
      primes.push(candidate);
    }
  }
  console.timeEnd("calculating...");
  return primes;
}

Dans la Chronologie, vous verrez quelque chose comme ceci :

 

Étiquettes et contributeurs liés au document

 Contributeurs à cette page : P45QU10U, maximelore
 Dernière mise à jour par : P45QU10U,