L’API Performance mesure la réactivité de votre application web en direct sur des périphériques d’utilisateurs et des connexions réseau réels. Elle peut aider à identifier les goulots d’étranglement dans votre code côté client et côté serveur avec :

  • luser timing : Mesure personnalisée des performances des fonctions JavaScript côté client
  • paint timing : Mesures du rendu du navigateur
  • resource timing : Performances de chargement des ressources et des appels Ajax
  • navigation timing : Mesures du chargement de la page, y compris les redirections, les consultations DNS, la disponibilité du DOM, etc

L’API aborde plusieurs problèmes associés à l’évaluation typique des performances :

  1. Les développeurs testent souvent les applications sur des PC haut de gamme connectés à un réseau rapide. DevTools peut émuler des appareils plus lents, mais il ne mettra pas toujours en évidence les problèmes du monde réel lorsque la majorité des clients utilisent un mobile de deux ans connecté au WiFi de l’aéroport.
  2. Les options tierces telles que Google Analytics sont souvent bloquées, ce qui entraîne des résultats et des hypothèses biaisés. Vous pouvez également rencontrer des problèmes de confidentialité dans certains pays.
  3. L’API de performance peut évaluer avec précision diverses mesures mieux que des méthodes telles que Date().


Les sections suivantes décrivent les façons dont vous pouvez utiliser l’API Performance. Une certaine connaissance de JavaScript et des mesures de chargement de pages est recommandée.

Disponibilité de l’API Performance

La plupart des navigateurs modernes prennent en charge l’API Performance, y compris IE10 et IE11 (même IE9 a une prise en charge limitée). Vous pouvez détecter la présence de l’API en utilisant :

if ('performance' in window) {
  // use Performance API
}

Il n’est pas possible de polyfilliser entièrement l’API, alors méfiez-vous des navigateurs manquants. Si 90 % de vos utilisateurs naviguent allègrement avec Internet Explorer 8, vous ne mesurerez que 10 % des clients avec des applications plus performantes.

L’API peut être utilisée dans les Web Workers, qui offrent un moyen d’exécuter des calculs complexes dans un thread d’arrière-plan sans interrompre les opérations du navigateur.

La plupart des méthodes API peuvent être utilisées dans Node.js côté serveur avec le module perf_hooks standard :

// Node.js performance
import { performance } from 'node:perf_hooks';
// or in Common JS: const { performance } = require('node:perf_hooks');

console.log( performance.now() );

Deno fournit l’API Performance standard :

// Deno performance
console.log( performance.now() );

Vous devrez exécuter des scripts avec la permission de --allow-hrtime pour activer la mesure du temps à haute résolution :

deno run --allow-hrtime index.js

Les performances côté serveur sont généralement plus faciles à évaluer et à gérer car elles dépendent de la charge, des processeurs, de la RAM, des disques durs et des limites des services cloud. Les mises à niveau matérielles ou les options de gestion des processus telles que PM2, le clustering et Kubernetes peuvent être plus efficaces que la refactorisation du code.

C’est pour cette raison que les sections suivantes se concentrent sur les performances côté client.

Mesure personnalisée des performances

L’API Performance peut être utilisée pour chronométrer la vitesse d’exécution des fonctions de votre application. Vous avez peut-être utilisé ou rencontré des fonctions de chronométrage utilisant Date():

const timeStart = new Date();
runMyCode();
const timeTaken = new Date() - timeStart;

console.log(`runMyCode() executed in ${ timeTaken }ms`);

L’API Performance offre deux avantages principaux :

  1. Meilleure précision : Date() mesure à la milliseconde près, mais l’API Performance peut mesurer des fractions de milliseconde (selon le navigateur).
  2. Meilleure fiabilité : L’utilisateur ou le système d’exploitation peut modifier l’heure du système. Les mesures basées sur Date() ne seront donc pas toujours exactes. Cela signifie que vos fonctions pourraient sembler particulièrement lentes lorsque les horloges avancent !

L’équivalent de Date() est performance.now() qui renvoie un horodatage haute résolution qui est mis à zéro lorsque le processus responsable de la création du document démarre (la page s’est chargée) :

const timeStart = performance.now();
runMyCode();
const timeTaken = performance.now() - timeStart;

console.log(`runMyCode() executed in ${ timeTaken }ms`);

Une propriété non standard performance.timeOrigin peut également renvoyer un horodatage à partir du 1er janvier 1970, bien que cela ne soit pas disponible dans IE et Deno.

performance.now() devient peu pratique lorsque l’on effectue plus de quelques mesures. L’API Performance fournit un tampon où vous pouvez enregistrer l’événement pour une analyse ultérieure en passant un nom de label à performance.mark():

performance.mark('start:app');
performance.mark('start:init');

init(); // run initialization functions

performance.mark('end:init');
performance.mark('start:funcX');

funcX(); // run another function

performance.mark('end:funcX');
performance.mark('end:app');

Un tableau de tous les objets de marque dans le tampon Performance peut être extrait en utilisant :

const mark = performance.getEntriesByType('mark');

Exemple de résultat :

[

  {
    detail: null
    duration: 0
    entryType: "mark"
    name: "start:app"
    startTime: 1000
  },
  {
    detail: null
    duration: 0
    entryType: "mark"
    name: "start:init"
    startTime: 1001
  },
  {
    detail: null
    duration: 0
    entryType: "mark"
    name: "end:init"
    startTime: 1100
  },
...
]

La méthode performance.measure() calcule le temps entre deux marques et le stocke également dans le tampon Performance. Vous passez un nouveau nom de mesure, le nom de la marque de début (ou null pour mesurer à partir du chargement de la page), et le nom de la marque de fin (ou null pour mesurer à l’heure actuelle) :

performance.measure('init', 'start:init', 'end:init');

Un objet PerformanceMeasure est ajouté au tampon avec la durée calculée. Pour obtenir cette valeur, vous pouvez soit demander un tableau de toutes les mesures :

const measure = performance.getEntriesByType('measure');

soit demander une mesure par son nom :

performance.getEntriesByName('init');

Exemple de résultat :

[
  {
    detail: null
    duration: 99
    entryType: "measure"
    name: "init"
    startTime: 1001
  }
]

Utilisation du tampon de performance

En plus des marques et des mesures, le tampon Performance est utilisé pour enregistrer automatiquement le navigation timing, le resource timing et le paint timing (dont nous parlerons plus tard). Vous pouvez obtenir un tableau de toutes les entrées du tampon :

performance.getEntries();

Par défaut, la plupart des navigateurs fournissent un tampon qui stocke jusqu’à 150 mesures de ressources. Cela devrait être suffisant pour la plupart des évaluations, mais vous pouvez augmenter ou diminuer la limite du tampon si nécessaire :

// record 500 metrics
performance.setResourceTimingBufferSize(500);

Les marques peuvent être effacées par nom ou vous pouvez spécifier une valeur vide pour effacer toutes les marques :

performance.clearMarks('start:init');

De même, les mesures peuvent être effacées par leur nom ou par une valeur vide pour tout effacer :

performance.clearMeasures();

Suivi des mises à jour du tampon de performance

PerformanceObserver peut surveiller les modifications du tampon de performance et exécuter une fonction lorsque des événements spécifiques se produisent. La syntaxe vous sera familière si vous avez utilisé MutationObserver pour répondre aux mises à jour du DOM ou IntersectionObserver pour détecter lorsque des éléments défilent dans la fenêtre d’affichage.

Vous devez définir une fonction d’observateur avec deux paramètres :

  1. un tableau d’entrées d’observateurs qui ont été détectées, et
  2. l’objet observateur. Si nécessaire, sa méthode disconnect() peut être appelée pour arrêter l’observateur.
function performanceCallback(list, observer) {

  list.getEntries().forEach(entry => {
    console.log(`name    : ${ entry.name }`);
    console.log(`type    : ${ entry.type }`);
    console.log(`start   : ${ entry.startTime }`);
    console.log(`duration: ${ entry.duration }`);
  });

}

La fonction est transmise à un nouvel objet PerformanceObserver. Sa méthode observe() reçoit un tableau de Performance buffer entryTypes à observer :

let observer = new PerformanceObserver( performanceCallback );
observer.observe({ entryTypes: ['mark', 'measure'] });

Dans cet exemple, l’ajout d’une nouvelle marque ou mesure exécute la fonction performanceCallback(). Bien qu’elle ne fasse qu’enregistrer des messages ici, elle pourrait être utilisée pour déclencher un téléchargement de données ou effectuer d’autres calculs.

Mesure de Paint Performance

L’API Paint Timing n’est disponible que dans le JavaScript côté client et enregistre automatiquement deux mesures importantes pour Core Web Vitals :

  1. first-paint : Le navigateur a commencé à dessiner la page.
  2. first-contentful-paint : Le navigateur a dessiné le premier élément significatif du contenu DOM, tel qu’un titre ou une image.

Celles-ci peuvent être extraites du tampon de performance vers un tableau :

const paintTimes = performance.getEntriesByType('paint');

Méfiez-vous de l’exécution de cette opération avant le chargement complet de la page ; les valeurs ne seront pas prêtes. Attendez l’événement window.load ou utilisez un PerformanceObserver pour surveiller paint entryTypes.

Exemple de résultat :

[
  {
    "name": "first-paint",
    "entryType": "paint",
    "startTime": 812,
    "duration": 0
  },
  {
    "name": "first-contentful-paint",
    "entryType": "paint",
    "startTime": 856,
    "duration": 0
  }
]

Une première peinture lente est souvent causée par des CSS ou JavaScript bloquant le rendu. L’écart par rapport au first-paint peut être important si le navigateur doit télécharger une grande image ou rendre des éléments complexes.

Mesure des performances des ressources

Les temps de réseau pour les ressources telles que les images, les feuilles de style et les fichiers JavaScript sont automatiquement enregistrés dans le tampon de performance. Bien qu’il n’y ait pas grand-chose à faire pour résoudre les problèmes de vitesse du réseau (à part réduire la taille des fichiers), cela peut aider à mettre en évidence les problèmes liés aux ressources volumineuses, aux réponses Ajax lentes ou aux scripts tiers qui fonctionnent mal.

Un tableau de mesures PerformanceResourceTiming peut être extrait du tampon en utilisant :

const resources = performance.getEntriesByType('resource');

Vous pouvez également récupérer les mesures d’une ressource en passant son URL complète :

const resource = performance.getEntriesByName('https://test.com/script.js');

Exemple de résultat :

[
  {
    connectEnd: 195,
    connectStart: 195,
    decodedBodySize: 0,
    domainLookupEnd: 195,
    domainLookupStart: 195,
    duration: 2,
    encodedBodySize: 0,
    entryType: "resource",
    fetchStart: 195,
    initiatorType: "script",
    name: "https://test.com/script.js",
    nextHopProtocol: "h3",
    redirectEnd: 0,
    redirectStart: 0,
    requestStart: 195,
    responseEnd: 197,
    responseStart: 197,
    secureConnectionStart: 195,
    serverTiming: [],
    startTime: 195,
    transferSize: 0,
    workerStart: 195
  }
]

Les propriétés suivantes peuvent être examinées :

  • name: URL de la ressource
  • entryType : « resource »
  • initiatorType : Comment la ressource a été initiée, par exemple « script » ou « link »
  • serverTiming : Un tableau d’objets PerformanceServerTiming objets transmis par le serveur dans l’en-tête HTTP Server-Timing (votre application côté serveur pourrait envoyer des mesures au client pour une analyse plus approfondie)
  • startTime : Horodatage du début de l’extraction
  • nextHopProtocol : Protocole réseau utilisé
  • workerStart : Horodatage avant le démarrage d’un Service Worker de Progressive Web App (0 si la requête n’est pas interceptée par un Service Worker)
  • redirectStart : Horodatage quand une redirection a commencé
  • redirectEnd : Horodatage après le dernier octet de la dernière réponse de redirection
  • fetchStart : Horodatage avant la récupération de la ressource
  • domainLookupStart : Horodatage avant une recherche DNS
  • domainLookupEnd : Horodatage après la recherche DNS
  • connectStart : Horodatage avant l’établissement d’une connexion au serveur
  • connectEnd : Horodatage après l’établissement d’une connexion au serveur
  • secureConnectionStart : Horodatage avant la poignée de main SSL
  • requestStart : Horodatage avant que le navigateur ne demande la ressource
  • responseStart : Horodatage lorsque le navigateur reçoit le premier octet de données
  • responseEnd : Horodatage après la réception du dernier octet ou la fermeture de la connexion
  • duration : La différence entre startTime et responseEnd
  • transferSize : La taille de la ressource en octets, y compris l’en-tête et le corps compressé
  • encodedBodySize : Le corps de la ressource en octets avant décompression
  • decodedBodySize : Le corps de la ressource en octets après décompression

Ce script d’exemple récupère toutes les requêtes Ajax initiées par l’API Fetch et renvoie la taille et la durée totales du transfert :

const fetchAll = performance.getEntriesByType('resource')
  .filter( r => r.initiatorType === 'fetch')
  .reduce( (sum, current) => {
    return {
      transferSize: sum.transferSize += current.transferSize,
      duration: sum.duration += current.duration
    }
  },
  { transferSize: 0, duration: 0 }
);

Mesure des performances de navigation

Les temps de réseau pour le déchargement de la page précédente et le chargement de la page actuelle sont automatiquement enregistrés dans le tampon des performances sous la forme d’un objet unique PerformanceNavigationTiming objet.

Vous pouvez l’extraire dans un tableau en utilisant :

const pageTime = performance.getEntriesByType('navigation');

…ou en passant l’URL de la page à .getEntriesByName():

const pageTiming = performance.getEntriesByName(window.location);

Les mesures sont identiques à celles des ressources mais incluent également des valeurs spécifiques à la page :

  • entryType : Par exemple, « navigation »
  • type : Soit « navigate », « reload », « back_forward » ou « prerender »
  • redirectCount : Le nombre de redirections
  • unloadEventStart : Horodatage avant l’événement de déchargement du document précédent
  • unloadEventEnd : Horodatage après l’événement de déchargement du document précédent
  • domInteractive : Horodatage lorsque le navigateur a analysé le HTML et construit le DOM
  • domContentLoadedEventStart : Horodatage avant le déclenchement de l’événement DOMContentLoaded du document
  • domContentLoadedEventEnd : Horodatage après la fin de l’événement DOMContentLoaded du document
  • domComplete : Horodatage après la fin de la construction du DOM et des événements DOMContentLoaded
  • loadEventStart : Horodatage avant que l’événement de chargement de page ne soit déclenché
  • loadEventEnd : Horodatage après l’événement de chargement de la page et que toutes les actifs sont disponibles

Les problèmes typiques comprennent :

  • Un long délai entre unloadEventEnd et domInteractive. Cela pourrait indiquer une réponse lente du serveur.
  • Un long délai entre domContentLoadedEventStart et domComplete. Cela pourrait indiquer que les scripts de démarrage de la page sont trop lents.
  • Un long délai entre domComplete et loadEventEnd. Cela peut indiquer que la page comporte trop de ressources ou que plusieurs d’entre elles mettent trop de temps à se charger.

Enregistrement et analyse des performances

L’API Performance vous permet de rassembler des données d’utilisation réelles et de les télécharger sur un serveur pour une analyse plus approfondie. Vous pourriez utiliser un service tiers tel que Google Analytics pour stocker les données, mais il y a un risque que le script tiers soit bloqué ou qu’il introduise de nouveaux problèmes de performance. Votre propre solution peut être adaptée à vos besoins pour garantir que la surveillance n’a pas d’impact sur les autres fonctionnalités.

Méfiez-vous des situations dans lesquelles les statistiques ne peuvent être déterminées – peut-être parce que les utilisateurs utilisent de vieux navigateurs, bloquent JavaScript ou se trouvent derrière un proxy d’entreprise. Comprendre les données manquantes peut être plus fructueux que de faire des hypothèses basées sur des informations incomplètes.

Idéalement, vos scripts d’analyse n’auront pas d’impact négatif sur les performances en exécutant des calculs complexes ou en téléchargeant de grandes quantités de données. Envisagez d’utiliser des workers web et de minimiser l’utilisation des appels synchrones de localStorage. Il est toujours possible de traiter les données brutes par lots ultérieurement.

Enfin, méfiez-vous des valeurs aberrantes telles que les appareils et les connexions très rapides ou très lentes qui ont un impact négatif sur les statistiques. Par exemple, si neuf utilisateurs chargent une page en deux secondes mais que le dixième subit un téléchargement de 60 secondes, la latence moyenne s’élève à près de 8 secondes. Une mesure plus réaliste est le chiffre médian (2 secondes) ou le 90e percentile (9 utilisateurs sur 10 connaissent un temps de chargement de 2 secondes ou moins).

Résumé

Les performances Web restent un facteur critique pour les développeurs. Les utilisateurs s’attendent à ce que les sites et les applications soient réactifs sur la plupart des appareils. L’optimisation des moteurs de recherche peut également être affectée car les sites plus lents sont déclassés dans Google.

Il existe de nombreux outils de surveillance des performances, mais la plupart évaluent les vitesses d’exécution côté serveur ou utilisent un nombre limité de clients capables pour juger du rendu du navigateur. L’API Performance offre un moyen de rassembler les mesures réelles des utilisateurs qu’il serait impossible de calculer autrement.

Craig Buckler

Freelance UK web developer, writer, and speaker. Has been around a long time and rants about standards and performance.