API User Timing

Comprendre votre application Web

Alex Danilo

Les applications Web hautes performances sont essentielles pour offrir une expérience utilisateur de qualité. Les applications Web devenant de plus en plus complexes, il est essentiel de comprendre l'impact des performances pour créer une expérience attrayante. Ces dernières années, plusieurs API ont été ajoutées au navigateur pour aider à analyser les performances du réseau, les temps de chargement, etc. Toutefois, elles ne fournissent pas nécessairement des détails précis avec une flexibilité suffisante pour identifier ce qui ralentit votre application. Saisissez l'API User Timing, qui fournit un mécanisme permettant d'instrumenter votre application Web afin d'identifier où elle passe son temps. Dans cet article, nous allons vous présenter l'API et vous donner des exemples d'utilisation.

Vous ne pouvez pas optimiser ce que vous ne pouvez pas mesurer

La première étape pour accélérer une application Web lente consiste à déterminer où le temps est consacré. Mesurer l'impact temporel des zones de code JavaScript est le moyen idéal d'identifier les points chauds, ce qui constitue la première étape pour trouver comment améliorer les performances. Heureusement, l'API User Timing vous permet d'insérer des appels d'API à différents endroits de votre code JavaScript, puis d'extraire des données de temps détaillées qui peuvent vous aider à optimiser votre site.

Délai de résolution élevé et now()

La précision est un élément fondamental de la mesure exacte du temps. Auparavant, nous utilisions une mesure du temps basée sur les millisecondes, ce qui est acceptable, mais pour créer un site à 60 FPS sans à-coups, chaque frame doit être dessinée en 16 ms. Ainsi, lorsque vous n'avez qu'une précision à la milliseconde, vous ne disposez pas de la précision nécessaire pour une bonne analyse. C'est là qu'intervient High Resolution Time, un nouveau type de chronométrage intégré aux navigateurs récents. High Resolution Time nous fournit des codes temporels à virgule flottante qui peuvent être précis à la microseconde près, soit mille fois mieux qu'auparavant.

Pour obtenir l'heure actuelle dans votre application Web, appelez la méthode now(), qui forme une extension de l'interface Performance. Le code suivant montre comment procéder :

var myTime = window.performance.now();

Il existe une autre interface appelée PerformanceTiming, qui fournit un certain nombre de temps différents liés au chargement de votre application Web. La méthode now() renvoie le temps écoulé depuis l'heure navigationStart dans PerformanceTiming.

Type DOMHighResTimeStamp

Pour chronométrer les applications Web, vous utilisiez auparavant une commande telle que Date.now(), qui renvoie un DOMTimeStamp. DOMTimeStamp renvoie un nombre entier de millisecondes comme valeur. Pour fournir la précision accrue requise pour l'heure haute résolution, un nouveau type appelé DOMHighResTimeStamp a été introduit. Ce type est une valeur à virgule flottante qui renvoie également l'heure en millisecondes. Mais comme il s'agit d'un nombre à virgule flottante, la valeur peut représenter des fractions de millisecondes et peut donc donner une précision d'un millième de milliseconde.

Interface User Timing

Maintenant que nous disposons d'horodatages haute résolution, utilisons l'interface User Timing pour extraire des informations de temps utilisateur.

L'interface User Timing fournit des fonctions qui nous permettent d'appeler des méthodes à différents endroits de notre application. Ces méthodes peuvent fournir un fil d'Ariane de style Hansel et Gretel pour nous permettre de suivre où le temps est passé.

Utiliser mark()

La méthode mark() est le principal outil de notre boîte à outils d'analyse temporelle. mark() stocke un code temporel pour nous. L'avantage de mark() est que nous pouvons nommer l'horodatage, et l'API mémorisera le nom et l'horodatage comme une seule unité.

En appelant mark() à différents endroits de votre application, vous pouvez déterminer le temps qu'il vous a fallu pour atteindre ce "repère" dans votre application Web.

La spécification propose un certain nombre de noms de repères qui pourraient être intéressants et qui sont assez explicites, tels que mark_fully_loaded, mark_fully_visible,mark_above_the_fold, etc.

Par exemple, nous pouvons définir une marque pour le moment où l'application est entièrement chargée à l'aide du code suivant :

window.performance.mark('mark_fully_loaded');

En définissant des repères nommés dans notre application Web, nous pouvons collecter un grand nombre de données de chronométrage et les analyser à notre guise pour déterminer ce que fait l'application et quand.

Calcul des mesures avec measure()

Une fois que vous avez défini plusieurs repères temporels, vous pouvez déterminer le temps écoulé entre eux. Pour ce faire, utilisez la méthode measure().

La méthode measure() calcule le temps écoulé entre les repères et peut également mesurer le temps entre votre repère et l'un des noms d'événements connus dans l'interface PerformanceTiming.

Par exemple, vous pouvez calculer le temps écoulé entre la fin du chargement du DOM et le chargement complet de l'état de votre application à l'aide d'un code de ce type :

window.performance.measure('measure_load_from_dom', 'domComplete', 'mark_fully_loaded');

Lorsque vous appelez measure(), il stocke le résultat indépendamment des repères que vous avez définis, ce qui vous permet de les récupérer ultérieurement. En stockant les temps d'exécution de votre application, celle-ci reste réactive. Vous pouvez ensuite vider toutes les données une fois que votre application a terminé une tâche, afin de les analyser ultérieurement.

Suppression des notes avec clearMarks()

Il est parfois utile de pouvoir supprimer un ensemble de repères que vous avez configurés. Par exemple, vous pouvez exécuter des traitements par lot sur votre application Web et vous souhaitez donc repartir de zéro à chaque exécution.

Il est très facile de supprimer les repères que vous avez définis en appelant clearMarks().

L'exemple de code ci-dessous supprime toutes les marques existantes, ce qui vous permet de configurer à nouveau une exécution de chronométrage si vous le souhaitez.

window.performance.clearMarks();

Bien sûr, il existe des scénarios dans lesquels vous ne souhaitez pas effacer toutes vos marques. Si vous souhaitez supprimer des repères spécifiques, il vous suffit de transmettre le nom du repère que vous souhaitez supprimer. Par exemple, le code ci-dessous :

window.performance.clearMarks('mark_fully_loaded');

supprime la marque que nous avons définie dans le premier exemple, tout en laissant les autres marques inchangées.

Vous pouvez également supprimer toutes les mesures que vous avez effectuées. Pour ce faire, utilisez la méthode correspondante appelée clearMeasures(). Il fonctionne exactement de la même manière que clearMarks(), mais sur les mesures que vous avez effectuées. Par exemple, le code :

window.performance.clearMeasures('measure_load_from_dom');

supprime la mesure que nous avons effectuée dans l'exemple measure() ci-dessus. Si vous souhaitez supprimer toutes les mesures, cela fonctionne de la même manière que clearMarks() : il vous suffit d'appeler clearMeasures() sans arguments.

Extraire les données de chronométrage

C'est bien beau de définir des repères et de mesurer des intervalles, mais à un moment donné, vous voudrez accéder à ces données de chronométrage pour effectuer une analyse. C'est également très simple : il vous suffit d'utiliser l'interface PerformanceTimeline.

Par exemple, la méthode getEntriesByType() nous permet d'obtenir tous nos temps de repère ou tous nos temps de mesure sous forme de liste afin de pouvoir les parcourir et d'assimiler les données. La liste est renvoyée par ordre chronologique, ce qui vous permet de voir les repères dans l'ordre où ils ont été atteints dans votre application Web.

Le code ci-dessous :

var items = window.performance.getEntriesByType('mark');

renvoie une liste de toutes les marques qui ont été atteintes dans notre application Web, tandis que le code :

var items = window.performance.getEntriesByType('measure');

renvoie une liste de toutes les mesures que nous avons effectuées.

Vous pouvez également récupérer une liste d'entrées en utilisant le nom spécifique que vous leur avez attribué. Par exemple, le code :

var items = window.performance.getEntriesByName('mark_fully_loaded');

renvoie une liste contenant un élément avec l'horodatage "mark_fully_loaded" dans la propriété startTime.

Chronométrer une requête XHR (exemple)

Maintenant que nous avons une bonne idée de l'API User Timing, nous pouvons l'utiliser pour analyser le temps que prennent toutes nos requêtes XMLHttpRequests dans notre application Web.

Nous allons d'abord modifier toutes nos requêtes send() pour émettre un appel de fonction qui configure les repères. En même temps, nous allons modifier nos rappels de réussite avec un appel de fonction qui définit un autre repère, puis génère une mesure de la durée de la requête.

Normalement, notre XMLHttpRequest ressemblerait à ceci :

var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
  do_something(e.responseText);
}
myReq.send();

Dans notre exemple, nous allons ajouter un compteur global pour suivre le nombre de requêtes et l'utiliser pour stocker une mesure pour chaque requête effectuée. Le code pour ce faire se présente comme suit :

var reqCnt = 0;

var myReq = new XMLHttpRequest();
myReq.open('GET', url, true);
myReq.onload = function(e) {
  window.performance.mark('mark_end_xhr');
  reqCnt++;
  window.performance.measure('measure_xhr_' + reqCnt, 'mark_start_xhr', 'mark_end_xhr');
  do_something(e.responseText);
}
window.performance.mark('mark_start_xhr');
myReq.send();

Le code ci-dessus génère une mesure avec une valeur de nom unique pour chaque XMLHttpRequest que nous envoyons. Nous partons du principe que les requêtes s'exécutent de manière séquentielle. Le code des requêtes parallèles devrait être un peu plus complexe pour gérer les requêtes qui renvoient des résultats dans le désordre. Nous laissons cela à titre d'exercice pour le lecteur.

Une fois que l'application Web a effectué un certain nombre de requêtes, nous pouvons toutes les vider dans la console à l'aide du code ci-dessous :

var items = window.performance.getEntriesByType('measure');
for (var i = 0; i < items.length; ++i) {
  var req = items[i];
  console.log('XHR ' + req.name + ' took ' + req.duration + 'ms');
}

Conclusion

L'API User Timing vous fournit de nombreux outils utiles que vous pouvez appliquer à n'importe quel aspect de votre application Web. Pour identifier les points sensibles de votre application, vous pouvez facilement ajouter des appels d'API à votre application Web et post-traiter les données de temps générées afin d'obtenir une image claire de l'utilisation du temps. Mais que se passe-t-il si votre navigateur n'est pas compatible avec cette API ? Pas de problème, vous trouverez un excellent polyfill ici qui émule très bien l'API et fonctionne également très bien avec webpagetest.org. Alors, qu'attendez-vous ? Essayez l'API User Timing sur vos applications dès maintenant. Vous découvrirez comment les accélérer, et vos utilisateurs vous remercieront d'avoir amélioré leur expérience.