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 deviennent de plus en plus complexes, et il est essentiel de comprendre l'impact sur les performances pour créer une expérience convaincante. Au cours des dernières années, un certain nombre d'API différentes ont été ajoutées au navigateur pour aider à analyser les performances du réseau, les temps de chargement, etc., mais elles ne fournissent pas nécessairement des détails précis avec une flexibilité suffisante pour identifier ce qui ralentit votre application. Entrez l'API User Timing, qui fournit un mécanisme que vous pouvez utiliser pour instrumenter votre application Web afin d'identifier où elle passe son temps. Dans cet article, nous allons 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 dépensé. 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 dans différentes parties de votre code JavaScript, puis d'extraire des données temporelles détaillées qui peuvent vous aider à optimiser votre code.

Durée de la haute résolution et now()

La précision est un élément essentiel de la mesure précise du temps. Auparavant, nous utilisions des mesures en millisecondes pour le timing, ce qui est acceptable. Toutefois, pour créer un site à 60 FPS sans à-coups, chaque frame doit être dessiné en 16 ms. Par conséquent, si vous n'avez qu'une précision de milliseconde, vous n'avez pas la précision nécessaire pour une bonne analyse. Saisissez High Resolution Time (Temps haute résolution), un nouveau type de synchronisation intégré aux navigateurs modernes. La date et l'heure haute résolution nous fournissent des codes temporels à virgule flottante dont la précision peut atteindre la microseconde, soit mille fois mieux qu'auparavant.

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

var myTime = window.performance.now();

Une autre interface appelée PerformanceTiming fournit un certain nombre de temps différents liés à la façon dont votre application Web est chargée. La méthode now() renvoie le temps écoulé depuis l'heure navigationStart dans PerformanceTiming.

Type DOMHighResTimeStamp

Auparavant, pour chronométrer des applications Web, vous utilisiez Date.now(), qui renvoie un DOMTimeStamp. DOMTimeStamp renvoie un nombre entier de millisecondes comme valeur. Afin d'améliorer la précision nécessaire pour le rendu en haute résolution, un nouveau type appelé DOMHighResTimeStamp a été introduit. Il s'agit d'une valeur à virgule flottante qui renvoie également l'heure en millisecondes. Toutefois, comme il s'agit d'un nombre à virgule flottante, la valeur peut représenter des millisecondes fractionnaires et peut donc fournir une précision au millième de milliseconde.

Interface User Timing

Maintenant que nous disposons de codes temporels haute résolution, utilisons l'interface User Timing (Temps utilisateur) pour extraire des informations temporelles.

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

mark() utilisé(s)

La méthode mark() est l'outil principal de notre boîte à outils d'analyse temporelle. mark() stocke un code temporel pour nous. mark() est très utile, car nous pouvons nommer le code temporel. L'API mémorisera le nom et le code temporel en tant qu'unité unique.

Appeler mark() à différents endroits de votre application vous permet de calculer le temps qu'il vous a fallu pour atteindre cette "marque" dans votre application Web.

La spécification propose un certain nombre de noms suggérés pour des marques susceptibles d'être intéressantes et relativement explicites, telles que mark_fully_loaded, mark_fully_visible,mark_above_the_fold, etc.

Par exemple, nous pouvons définir une marque pour le chargement complet de l'application à 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 temporelles et les analyser à notre guise pour déterminer ce que l'application fait et quand.

Calculer des mesures avec measure()

Une fois que vous avez défini plusieurs repères temporels, vous devez déterminer la durée écoulée 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 écoulé entre votre repère et l'un des noms d'événement bien connus dans l'interface PerformanceTiming.

Par exemple, vous pouvez déterminer le délai entre l'achèvement du DOM et le chargement complet de l'état de votre application à l'aide d'un code comme:

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

Lorsque vous appelez measure(), il stocke le résultat indépendamment des marques que vous définissez afin que vous puissiez les récupérer plus tard. En stockant les temps d'absence pendant l'exécution de votre application, celle-ci reste réactive et vous pouvez vider toutes les données une fois que l'application a terminé un travail afin qu'elles puissent être analysées ultérieurement.

Supprimer des repères avec clearMarks()

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

Il est facile de supprimer toutes les marques que vous avez configurées en appelant clearMarks().

Ainsi, l'exemple de code ci-dessous supprimerait toutes les marques existantes, afin que vous puissiez reconfigurer une exécution temporelle si vous le souhaitez.

window.performance.clearMarks();

Bien sûr, il peut arriver que vous ne souhaitiez pas effacer toutes vos marques. Ainsi, si vous voulez supprimer des marques spécifiques, il vous suffit de leur transmettre leur nom. 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 clearMeasures(). Il fonctionne exactement de la même manière que clearMarks(), mais sur toutes les mesures que vous avez effectuées. Par exemple, le code:

window.performance.clearMeasures('measure_load_from_dom');

supprimera la mesure que nous avons effectuée dans l'exemple measure() ci-dessus. Si vous souhaitez supprimer toutes les mesures, cela fonctionne exactement comme clearMarks(), c'est-à-dire que vous appelez simplement clearMeasures() sans arguments.

Extraire les données de synchronisation

Il est bien de définir des repères et de mesurer des intervalles, mais à un moment donné, vous devrez accéder à ces données temporelles pour effectuer des analyses. C'est aussi très simple. Il vous suffit d'utiliser l'interface PerformanceTimeline.

Par exemple, la méthode getEntriesByType() nous permet d'obtenir toutes nos marques de temps ou toutes nos mesures sous forme de liste afin de pouvoir les itérer et d'analyser les données. L'avantage est que la liste est renvoyée dans l'ordre chronologique. Vous pouvez ainsi voir les repères dans l'ordre dans lequel ils ont été atteints dans votre application Web.

Le code ci-dessous :

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

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

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

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

Vous pouvez également obtenir 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 seul élément contenant le code temporel "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 la durée de toutes nos requêtes XMLHttpRequest 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, et en même temps remplacer nos rappels de réussite par 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.

Ainsi, notre XMLHttpRequest devrait normalement ressembler à ceci:

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

Pour 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 à utiliser 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 supposons 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 ne sont pas renvoyées dans l'ordre. Nous laissons cela au lecteur comme exercice.

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. Vous pouvez facilement identifier les points chauds de votre application en insérant des appels d'API dans votre application Web et en post-traitant les données temporelles générées pour 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 ? Aucun problème. Vous trouverez ici un polyfill de qualité qui émule très bien l'API et s'adapte parfaitement à webpagetest.org. Alors, qu'attendez-vous ? Essayez dès maintenant l'API User Timing dans vos applications. Vous allez découvrir comment les accélérer, et vos utilisateurs vous remercieront d'améliorer leur expérience.