Eine Einführung in die Performance-API
Veröffentlicht: 2022-08-10Die Performance-API misst die Reaktionsfähigkeit Ihrer Live-Webanwendung auf echten Benutzergeräten und Netzwerkverbindungen. Es kann helfen, Engpässe in Ihrem clientseitigen und serverseitigen Code zu identifizieren, mit:
- Benutzertiming: Benutzerdefinierte Messung der clientseitigen JavaScript-Funktionsleistung
- Paint-Timing: Browser-Rendering-Metriken
- Ressourcen-Timing: Ladeleistung von Assets und Ajax-Aufrufen
- Navigationstiming: Metriken zum Laden von Seiten, einschließlich Weiterleitungen, DNS-Lookups, DOM-Bereitschaft und mehr
Die API behebt mehrere Probleme im Zusammenhang mit einer typischen Leistungsbewertung:
- Entwickler testen Anwendungen oft auf High-End-PCs, die mit einem schnellen Netzwerk verbunden sind. DevTools kann langsamere Geräte emulieren, zeigt jedoch nicht immer reale Probleme auf, wenn die Mehrheit der Clients ein zwei Jahre altes Mobiltelefon mit Verbindung zum Flughafen-WLAN ausführt.
- Optionen von Drittanbietern wie Google Analytics werden oft blockiert, was zu verzerrten Ergebnissen und Annahmen führt. In einigen Ländern können auch Auswirkungen auf den Datenschutz auftreten.
- Die Leistungs-API kann verschiedene Metriken besser genau messen als Methoden wie
Date()
.
In den folgenden Abschnitten werden Möglichkeiten beschrieben, wie Sie die Performance-API verwenden können. Einige Kenntnisse über JavaScript und Metriken zum Laden von Seiten werden empfohlen.
Leistungs-API-Verfügbarkeit
Die meisten modernen Browser unterstützen die Performance-API – einschließlich IE10 und IE11 (selbst IE9 hat eingeschränkte Unterstützung). Sie können das Vorhandensein der API erkennen, indem Sie Folgendes verwenden:
if ('performance' in window) { // use Performance API }
Es ist nicht möglich, die API vollständig mit Polyfill zu füllen, seien Sie also vorsichtig bei fehlenden Browsern. Wenn 90 % Ihrer Benutzer gerne mit Internet Explorer 8 surfen, würden Sie nur 10 % der Clients mit leistungsfähigeren Anwendungen messen.
Die API kann in Web Workern verwendet werden, die eine Möglichkeit bieten, komplexe Berechnungen in einem Hintergrundthread auszuführen, ohne den Browserbetrieb anzuhalten.
Die meisten API-Methoden können in serverseitigem Node.js mit dem Standardmodul perf_hooks verwendet werden:
// Node.js performance import { performance } from 'node:perf_hooks'; // or in Common JS: const { performance } = require('node:perf_hooks'); console.log( performance.now() );
Deno bietet die standardmäßige Performance-API:
// Deno performance console.log( performance.now() );
Sie müssen Skripte mit der --allow-hrtime
, um die hochauflösende Zeitmessung zu aktivieren:
deno run --allow-hrtime index.js
Die serverseitige Leistung ist normalerweise einfacher zu bewerten und zu verwalten, da sie von Last, CPUs, RAM, Festplatten und Cloud-Service-Limits abhängt. Hardware-Upgrades oder Prozessverwaltungsoptionen wie PM2, Clustering und Kubernetes können effektiver sein als das Refactoring von Code.
Aus diesem Grund konzentrieren sich die folgenden Abschnitte auf die clientseitige Performance.
Benutzerdefinierte Leistungsmessung
Die Leistungs-API kann verwendet werden, um die Ausführungsgeschwindigkeit Ihrer Anwendungsfunktionen zu timen. Möglicherweise haben Sie Timing-Funktionen mit Date()
verwendet oder sind darauf gestoßen:
const timeStart = new Date(); runMyCode(); const timeTaken = new Date() - timeStart; console.log(`runMyCode() executed in ${ timeTaken }ms`);
Die Performance API bietet zwei Hauptvorteile:
- Bessere Genauigkeit:
Date()
misst auf die nächste Millisekunde, aber die Performance-API kann Bruchteile einer Millisekunde messen (je nach Browser). - Bessere Zuverlässigkeit: Der Benutzer oder das Betriebssystem kann die Systemzeit ändern, sodass
Date()
-basierte Metriken nicht immer genau sind. Das bedeutet, dass Ihre Funktionen besonders langsam erscheinen könnten, wenn die Uhren vorrücken!
Das Date()
Äquivalent ist performance.now()
, das einen hochauflösenden Zeitstempel zurückgibt, der auf Null gesetzt wird, wenn der für die Erstellung des Dokuments verantwortliche Prozess beginnt (die Seite wurde geladen):
const timeStart = performance.now(); runMyCode(); const timeTaken = performance.now() - timeStart; console.log(`runMyCode() executed in ${ timeTaken }ms`);
Eine nicht standardmäßige performance.timeOrigin
-Eigenschaft kann auch einen Zeitstempel vom 1. Januar 1970 zurückgeben, obwohl dies in IE und Deno nicht verfügbar ist.
performance.now()
wird unpraktisch, wenn mehr als ein paar Messungen durchgeführt werden. Die Performance-API bietet einen Puffer, in dem Sie Ereignisse zur späteren Analyse aufzeichnen können, indem Sie einen Bezeichnungsnamen an performance.mark()
übergeben:
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');
Ein Array aller Markierungsobjekte im Performance-Puffer kann extrahiert werden mit:
const mark = performance.getEntriesByType('mark');
Beispielergebnis:
[ { 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 }, ... ]
Die Methode performance.measure()
berechnet die Zeit zwischen zwei Markierungen und speichert sie auch im Performance-Puffer. Sie übergeben einen neuen Taktnamen, den Namen der Startmarkierung (oder null, um vom Laden der Seite zu messen) und den Namen der Endmarkierung (oder null, um die aktuelle Zeit zu messen):
performance.measure('init', 'start:init', 'end:init');
Ein PerformanceMeasure-Objekt wird mit der berechneten Zeitdauer an den Puffer angehängt. Um diesen Wert zu erhalten, können Sie entweder ein Array aller Kennzahlen anfordern:
const measure = performance.getEntriesByType('measure');
oder fordern Sie eine Maßnahme mit ihrem Namen an:
performance.getEntriesByName('init');
Beispielergebnis:
[ { detail: null duration: 99 entryType: "measure" name: "init" startTime: 1001 } ]
Verwenden des Leistungspuffers
Neben Markierungen und Maßen wird der Performance-Puffer verwendet, um automatisch das Navigations-Timing, das Ressourcen-Timing und das Paint-Timing (auf das wir später noch eingehen werden) aufzuzeichnen. Sie können ein Array aller Einträge im Puffer erhalten:
performance.getEntries();
Standardmäßig bieten die meisten Browser einen Puffer, der bis zu 150 Ressourcenmetriken speichert. Dies sollte für die meisten Bewertungen ausreichen, aber Sie können das Pufferlimit bei Bedarf erhöhen oder verringern:
// record 500 metrics performance.setResourceTimingBufferSize(500);
Markierungen können nach Namen gelöscht werden, oder Sie können einen leeren Wert angeben, um alle Markierungen zu löschen:
performance.clearMarks('start:init');
Auf ähnliche Weise können Kennzahlen nach Namen oder einem leeren Wert gelöscht werden, um alles zu löschen:
performance.clearMeasures();
Überwachen von Leistungspufferaktualisierungen
Ein PerformanceObserver kann Änderungen am Leistungspuffer überwachen und eine Funktion ausführen, wenn bestimmte Ereignisse eintreten. Die Syntax wird Ihnen vertraut sein, wenn Sie MutationObserver verwendet haben, um auf DOM-Aktualisierungen zu reagieren, oder IntersectionObserver , um zu erkennen, wenn Elemente in den Ansichtsbereich gescrollt werden.
Sie müssen eine Beobachterfunktion mit zwei Parametern definieren:
- eine Reihe von Beobachtereinträgen, die erkannt wurden, und
- das Beobachterobjekt. Bei Bedarf kann seine Methode
disconnect()
aufgerufen werden, um den Beobachter zu stoppen.
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 }`); }); }
Die Funktion wird an ein neues PerformanceObserver-Objekt übergeben. Seiner Observe observe()
Methode wird ein Array von Performance-Puffer-Eintragstypen zum Beobachten übergeben:
let observer = new PerformanceObserver( performanceCallback ); observer.observe({ entryTypes: ['mark', 'measure'] });
In diesem Beispiel wird beim Hinzufügen einer neuen Markierung oder Kennzahl die Funktion performanceCallback()
ausgeführt. Während es hier nur Nachrichten protokolliert, könnte es verwendet werden, um einen Datenupload auszulösen oder weitere Berechnungen durchzuführen.
Messung der Lackleistung
Die Paint Timing API ist nur in clientseitigem JavaScript verfügbar und zeichnet automatisch zwei Metriken auf, die für Core Web Vitals wichtig sind:
- first-paint: Der Browser hat mit dem Zeichnen der Seite begonnen.
- first-contentful-paint: Der Browser hat das erste signifikante Element des DOM-Inhalts gezeichnet, z. B. eine Überschrift oder ein Bild.
Diese können aus dem Performance-Puffer in ein Array extrahiert werden:
const paintTimes = performance.getEntriesByType('paint');
Seien Sie vorsichtig, wenn Sie dies ausführen, bevor die Seite vollständig geladen ist. die Werte werden nicht bereit sein. Warten Sie entweder auf das window.load
Ereignis oder verwenden Sie einen PerformanceObserver
, um paint
entryTypes zu überwachen.
Beispielergebnis:
[ { "name": "first-paint", "entryType": "paint", "startTime": 812, "duration": 0 }, { "name": "first-contentful-paint", "entryType": "paint", "startTime": 856, "duration": 0 } ]
Ein langsamer First-Paint wird oft durch CSS oder JavaScript verursacht, die das Rendern blockieren. Die Lücke zum First-Contentful-Paint könnte groß sein, wenn der Browser ein großes Bild herunterladen oder komplexe Elemente rendern muss.
Messung der Ressourcenleistung
Netzwerk-Timings für Ressourcen wie Bilder, Stylesheets und JavaScript-Dateien werden automatisch im Leistungspuffer aufgezeichnet. Obwohl Sie wenig tun können, um Probleme mit der Netzwerkgeschwindigkeit zu lösen (außer der Verringerung der Dateigröße), kann es helfen, Probleme mit größeren Assets, langsamen Ajax-Antworten oder schlecht funktionierenden Skripts von Drittanbietern hervorzuheben.
Ein Array von PerformanceResourceTiming-Metriken kann aus dem Puffer extrahiert werden mit:
const resources = performance.getEntriesByType('resource');
Alternativ können Sie Metriken für ein Asset abrufen, indem Sie seine vollständige URL übergeben:
const resource = performance.getEntriesByName('https://test.com/script.js');
Beispielergebnis:
[ { 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 } ]
Folgende Eigenschaften können untersucht werden:
- name : Ressourcen-URL
- entryType : „Ressource“
- initiatorType : Wie die Ressource initiiert wurde, z. B. „Skript“ oder „Link“
- serverTiming : Ein Array von
PerformanceServerTiming
-Objekten, die vom Server im HTTP-Server-Timing-Header übergeben werden (Ihre serverseitige Anwendung könnte Metriken zur weiteren Analyse an den Client senden). - startTime : Zeitstempel, wann der Abruf gestartet wurde
- nextHopProtocol : Verwendetes Netzwerkprotokoll
- workerStart : Zeitstempel vor dem Start eines Progressive Web App Service Workers (0, wenn die Anfrage nicht von einem Service Worker abgefangen wird)
- UmleitungStart : Zeitstempel, wann eine Umleitung gestartet wurde
- Umleitungsende : Zeitstempel nach dem letzten Byte der letzten Umleitungsantwort
- fetchStart : Zeitstempel vor dem Ressourcenabruf
- domainLookupStart : Zeitstempel vor einer DNS-Suche
- domainLookupEnd : Zeitstempel nach der DNS-Suche
- connectStart : Zeitstempel vor dem Aufbau einer Serververbindung
- connectEnd : Zeitstempel nach Aufbau einer Serververbindung
- secureConnectionStart : Zeitstempel vor dem SSL-Handshake
- requestStart : Zeitstempel, bevor der Browser die Ressource anfordert
- responseStart : Zeitstempel, wenn der Browser das erste Datenbyte empfängt
- responseEnd : Zeitstempel nach Empfang des letzten Bytes oder Schließen der Verbindung
- Dauer : Die Differenz zwischen startTime und responseEnd
- transferSize : Die Ressourcengröße in Bytes, einschließlich Header und komprimiertem Text
- encodedBodySize : Der Ressourcentext in Byte vor dem Dekomprimieren
- decodedBodySize : Der Ressourcentext in Bytes nach dem Dekomprimieren
Dieses Beispielskript ruft alle Ajax-Anforderungen ab, die von der Fetch-API initiiert wurden, und gibt die Gesamtübertragungsgröße und -dauer zurück:
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 } );
Messung der Navigationsleistung
Netzwerkzeiten zum Entladen der vorherigen Seite und zum Laden der aktuellen Seite werden automatisch als einzelnes PerformanceNavigationTiming
-Objekt im Leistungspuffer aufgezeichnet.
Extrahieren Sie es in ein Array mit:
const pageTime = performance.getEntriesByType('navigation');
…oder indem Sie die Seiten-URL an .getEntriesByName()
:
const pageTiming = performance.getEntriesByName(window.location);
Die Metriken sind identisch mit denen für Ressourcen, beinhalten aber auch seitenspezifische Werte:
- entryType : zB „navigation“
- type : Entweder „navigate“, „reload“, „back_forward“ oder „prerender“
- RedirectCount : Die Anzahl der Weiterleitungen
- unloadEventStart : Zeitstempel vor dem Entladeereignis des vorherigen Dokuments
- unloadEventEnd : Zeitstempel nach dem Entladeereignis des vorherigen Dokuments
- domInteractive : Zeitstempel, wenn der Browser den HTML-Code analysiert und das DOM erstellt hat
- domContentLoadedEventStart : Zeitstempel, bevor das DOMContentLoaded-Ereignis des Dokuments ausgelöst wird
- domContentLoadedEventEnd : Zeitstempel nach Abschluss des DOMContentLoaded-Ereignisses des Dokuments
- domComplete : Zeitstempel, nachdem die DOM-Konstruktion und DOMContentLoaded-Ereignisse abgeschlossen wurden
- loadEventStart : Zeitstempel vor dem Auslösen des Seitenladeereignisses
- loadEventEnd : Zeitstempel nach dem Seitenladeereignis und alle Assets sind verfügbar
Typische Probleme sind:
- Eine lange Verzögerung zwischen unloadEventEnd und domInteractive . Dies könnte auf eine langsame Serverantwort hindeuten.
- Eine lange Verzögerung zwischen domContentLoadedEventStart und domComplete . Dies könnte darauf hindeuten, dass Seitenstartskripte zu langsam sind.
- Eine lange Verzögerung zwischen domComplete und loadEventEnd . Dies könnte darauf hindeuten, dass die Seite zu viele Assets hat oder das Laden einiger zu lange dauert.
Leistungsaufzeichnung und -analyse
Mit der Performance-API können Sie reale Nutzungsdaten sammeln und zur weiteren Analyse auf einen Server hochladen. Sie könnten einen Drittanbieterdienst wie Google Analytics verwenden, um die Daten zu speichern, aber es besteht die Gefahr, dass das Skript des Drittanbieters blockiert wird oder neue Leistungsprobleme einführt. Ihre eigene Lösung kann an Ihre Anforderungen angepasst werden, um sicherzustellen, dass die Überwachung andere Funktionen nicht beeinträchtigt.
Seien Sie vorsichtig bei Situationen, in denen keine Statistiken erstellt werden können – vielleicht, weil Benutzer alte Browser verwenden, JavaScript blockieren oder sich hinter einem Unternehmens-Proxy befinden. Zu verstehen, welche Daten fehlen, kann fruchtbarer sein, als Annahmen auf der Grundlage unvollständiger Informationen zu treffen.
Idealerweise wirken sich Ihre Analyseskripte nicht negativ auf die Leistung aus, indem sie komplexe Berechnungen ausführen oder große Datenmengen hochladen. Erwägen Sie den Einsatz von Web Workern und minimieren Sie die Verwendung synchroner localStorage-Aufrufe. Es ist jederzeit möglich, Rohdaten später im Batch zu verarbeiten.
Achten Sie schließlich auf Ausreißer wie sehr schnelle oder sehr langsame Geräte und Verbindungen, die sich negativ auf die Statistiken auswirken. Wenn beispielsweise neun Benutzer eine Seite in zwei Sekunden laden, der zehnte jedoch einen Download von 60 Sekunden erlebt, beträgt die durchschnittliche Latenz fast 8 Sekunden. Eine realistischere Metrik ist der Medianwert (2 Sekunden) oder das 90. Perzentil (9 von 10 Benutzern erleben eine Ladezeit von 2 Sekunden oder weniger).
Zusammenfassung
Die Web-Performance bleibt ein kritischer Faktor für Entwickler. Benutzer erwarten, dass Websites und Anwendungen auf den meisten Geräten reagieren. Auch die Suchmaschinenoptimierung kann beeinträchtigt werden, wenn langsamere Seiten in Google herabgestuft werden.
Es gibt viele Tools zur Leistungsüberwachung, aber die meisten bewerten serverseitige Ausführungsgeschwindigkeiten oder verwenden eine begrenzte Anzahl fähiger Clients, um das Browser-Rendering zu beurteilen. Die Performance-API bietet eine Möglichkeit, echte Benutzermetriken zu sammeln, die auf andere Weise nicht berechnet werden könnten.