Nutzerorientierte Messwerte, die sich universell auf jeder Website messen lassen, sind sehr wertvoll. Mit diesen Messwerten können Sie:
- Verstehen, wie echte Nutzer das Web insgesamt erleben
- Vergleichen Sie Ihre Website mit der eines Mitbewerbers.
- Sie können nützliche und umsetzbare Daten in Ihren Analysetools erfassen, ohne benutzerdefinierten Code schreiben zu müssen.
Universelle Messwerte bieten eine gute Ausgangsbasis. In vielen Fällen müssen Sie jedoch mehr als nur diese Messwerte erfassen, um die gesamte Nutzererfahrung Ihrer Website zu erfassen.
Mit benutzerdefinierten Messwerten können Sie Aspekte der Nutzerfreundlichkeit Ihrer Website messen, die möglicherweise nur für Ihre Website gelten, z. B.:
- Wie lange es dauert, bis eine Single-Page-App (SPA) von einer „Seite“ zur nächsten wechselt.
- Die Zeit, die vergeht, bis auf einer Seite Daten aus einer Datenbank für angemeldete Nutzer angezeigt werden.
- Wie lange dauert es, bis eine serverseitig gerenderte App (SSR) hydratisiert ist?
- Die Cache-Trefferquote für Ressourcen, die von wiederkehrenden Besuchern geladen werden.
- Die Ereignislatenz von Klick- oder Tastaturereignissen in einem Spiel.
APIs zum Erfassen benutzerdefinierter Messwerte
Bisher hatten Webentwickler nicht viele Low-Level-APIs zur Leistungsmessung. Daher mussten sie auf Hacks zurückgreifen, um zu messen, ob eine Website eine gute Leistung erbringt.
So können Sie beispielsweise feststellen, ob der Haupt-Thread aufgrund lang laufender JavaScript-Aufgaben blockiert ist, indem Sie eine requestAnimationFrame
-Schleife ausführen und das Delta zwischen den einzelnen Frames berechnen. Wenn das Delta deutlich länger als die Framerate des Displays ist, können Sie dies als lange Aufgabe melden. Solche Hacks werden jedoch nicht empfohlen, da sie sich selbst auf die Leistung auswirken (z. B. durch einen schnelleren Akkuverbrauch).
Die erste Regel für eine effektive Leistungsmessung besteht darin, dafür zu sorgen, dass Ihre Leistungsmesstechniken selbst keine Leistungsprobleme verursachen. Verwenden Sie daher nach Möglichkeit eine der folgenden APIs für alle benutzerdefinierten Messwerte, die Sie auf Ihrer Website erfassen.
Performance Observer API
Die Performance Observer API ist der Mechanismus, mit dem Daten aus allen anderen auf dieser Seite beschriebenen Leistungs-APIs erfasst und angezeigt werden. Es ist wichtig, dies zu verstehen, um gute Daten zu erhalten.
Mit PerformanceObserver
können Sie Leistungsbezogene Ereignisse passiv abonnieren. So können API-Callbacks während Inaktivitätszeiten ausgelöst werden, was bedeutet, dass sie in der Regel die Seitenleistung nicht beeinträchtigen.
Wenn Sie eine PerformanceObserver
erstellen möchten, übergeben Sie ihr einen Callback, der ausgeführt werden soll, wenn neue Leistungseinträge gesendet werden. Dann geben Sie mit der Methode observe()
an, nach welchen Arten von Einträgen der Beobachter suchen soll:
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
po.observe({type: 'some-entry-type'});
In den folgenden Abschnitten werden alle verschiedenen Eintragstypen aufgeführt, die beobachtet werden können. In neueren Browsern können Sie jedoch über die statische Eigenschaft PerformanceObserver.supportedEntryTypes
prüfen, welche Eintragstypen verfügbar sind.
Einträge beobachten, die bereits stattgefunden haben
Standardmäßig können PerformanceObserver
-Objekte Einträge nur bei Auftreten beobachten. Das kann zu Problemen führen, wenn Sie Ihren Code für Leistungsanalysen per Lazy Load laden möchten, damit er keine Ressourcen mit höherer Priorität blockiert.
Wenn Sie bisherige Einträge abrufen möchten (nachdem sie aufgetreten sind), setzen Sie das Flag buffered
auf true
, wenn Sie observe()
aufrufen. Beim ersten Aufruf des PerformanceObserver
-Callbacks fügt der Browser bisherige Einträge aus seinem Leistungseintrags-Puffer hinzu, bis die maximale Puffergröße für diesen Typ erreicht ist.
po.observe({
type: 'some-entry-type',
buffered: true,
});
Zu vermeidende Legacy-Leistungs-APIs
Vor der Performance Observer API konnten Entwickler mit den folgenden drei Methoden, die für das performance
-Objekt definiert sind, auf Leistungseinträge zugreifen:
Diese APIs werden zwar noch unterstützt, ihre Verwendung wird jedoch nicht empfohlen, da Sie damit nicht auf das Senden neuer Einträge warten können. Außerdem werden viele neue APIs (z. B. largest-contentful-paint
) nicht über das performance
-Objekt, sondern nur über PerformanceObserver
bereitgestellt.
Sofern Sie keine spezielle Kompatibilität mit dem Internet Explorer benötigen, sollten Sie diese Methoden in Ihrem Code vermeiden und stattdessen PerformanceObserver
verwenden.
User Timing API
Die User Timing API ist eine allgemeine Analyse-API für zeitbasierte Messwerte. Sie können beliebige Zeitpunkte markieren und die Dauer zwischen diesen Markierungen später messen.
// Record the time immediately before running a task.
performance.mark('myTask:start');
await doMyTask();
// Record the time immediately after running a task.
performance.mark('myTask:end');
// Measure the delta between the start and end of the task
performance.measure('myTask', 'myTask:start', 'myTask:end');
APIs wie Date.now()
oder performance.now()
bieten ähnliche Funktionen. Der Vorteil der User Timing API besteht darin, dass sie sich gut in Leistungstools integrieren lässt. In Chrome DevTools werden beispielsweise Nutzerzeitmessungen im Bereich „Leistung“ visualisiert. Viele Analyseanbieter erfassen auch automatisch alle von Ihnen durchgeführten Messungen und senden die Daten zur Dauer an ihr Analyse-Backend.
Wenn Sie Messungen zu Nutzerzeiträumen erfassen möchten, können Sie PerformanceObserver verwenden und sich für die Beobachtung von Einträgen vom Typ measure
registrieren:
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `measure` entries to be dispatched.
po.observe({type: 'measure', buffered: true});
Long Tasks API
Mit der Long Tasks API können Sie feststellen, wann der Hauptthread des Browsers lange genug blockiert ist, um die Framerate oder die Eingabelatenz zu beeinträchtigen. Die API meldet alle Aufgaben, die länger als 50 Millisekunden ausgeführt werden.
Wenn Sie Code mit hoher Auslastung ausführen oder große Scripts laden und ausführen müssen, ist es nützlich zu verfolgen, ob dieser Code den Haupt-Thread blockiert. Viele Messwerte der höheren Ebene basieren sogar auf der Long Tasks API selbst, z. B. Time to Interactive (TTI) und Total Blocking Time (TBT).
Wenn Sie feststellen möchten, wann lange Aufgaben ausgeführt werden, können Sie PerformanceObserver verwenden und sich für die Beobachtung von Einträgen vom Typ longtask
registrieren:
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `longtask` entries to be dispatched.
po.observe({type: 'longtask', buffered: true});
Long Animation Frames API
Die Long Animation Frames API ist eine neue Version der Long Tasks API, die lange Frames (nicht lange Aufgaben) von über 50 Millisekunden betrachtet. Dadurch werden einige Mängel der Long Tasks API behoben, darunter eine bessere Zuordnung und ein größerer Umfang potenziell problematischer Verzögerungen.
Wenn Sie feststellen möchten, wann lange Frames auftreten, können Sie PerformanceObserver verwenden und sich für Einträge vom Typ long-animation-frame
registrieren:
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `long-animation-frame` entries to be dispatched.
po.observe({type: 'long-animation-frame', buffered: true});
Element Timing API
Der Messwert Largest Contentful Paint (LCP) gibt Aufschluss darüber, wann das größte Bild oder der größte Textblock auf dem Bildschirm dargestellt wurde. In einigen Fällen möchten Sie jedoch die Renderingzeit eines anderen Elements messen.
Verwenden Sie in diesen Fällen die Element Timing API. Die LCP API basiert auf der Element Timing API und ermöglicht automatische Berichte zum größten Element mit Inhalt. Sie können aber auch Berichte zu anderen Elementen erstellen, indem Sie ihnen explizit das elementtiming
-Attribut hinzufügen und einen PerformanceObserver registrieren, um den element
-Eintragstyp zu beobachten.
<img elementtiming="hero-image" />
<p elementtiming="important-paragraph">This is text I care about.</p>
<!-- ... -->
<script>
const po = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `element` entries to be dispatched.
po.observe({type: 'element', buffered: true});
</script>
Event Timing API
Der Messwert Interaction to Next Paint (INP) bewertet die Reaktionsfähigkeit der Seite insgesamt, indem alle Klicks, Berührungen und Tastaturinteraktionen während der gesamten Lebensdauer der Seite erfasst werden. Der INP einer Seite ist in den meisten Fällen die Interaktion, die am längsten gedauert hat, vom Zeitpunkt, an dem der Nutzer die Interaktion gestartet hat, bis zum Zeitpunkt, an dem der Browser den nächsten Frame anzeigt, der das visuelle Ergebnis der Nutzereingabe zeigt.
Der Messwert „In-Page-Nutzer“ wird durch die Event Timing API ermöglicht. Diese API stellt eine Reihe von Zeitstempeln bereit, die während des Lebenszyklus des Ereignisses auftreten, darunter:
startTime
: der Zeitpunkt, zu dem der Browser das Ereignis empfängt.processingStart
: Zeitpunkt, zu dem der Browser mit der Verarbeitung von Ereignishandlern für das Ereignis beginnen kann.processingEnd
: Zeitpunkt, zu dem der Browser die Ausführung aller synchronen Codes beendet, die von Ereignishandlern für dieses Ereignis initiiert wurden.duration
: Die Zeit (aus Sicherheitsgründen auf 8 Millisekunden gerundet), die vergeht, bis der Browser das Ereignis empfängt und den nächsten Frame zeichnen kann, nachdem der gesamte synchrone Code ausgeführt wurde, der von den Ereignis-Handlern initiiert wurde.
Im folgenden Beispiel wird gezeigt, wie Sie anhand dieser Werte benutzerdefinierte Messwerte erstellen:
const po = new PerformanceObserver((entryList) => {
// Get the last interaction observed:
const entries = Array.from(entryList.getEntries()).forEach((entry) => {
// Get various bits of interaction data:
const inputDelay = entry.processingStart - entry.startTime;
const processingTime = entry.processingEnd - entry.processingStart;
const presentationDelay = entry.startTime + entry.duration - entry.processingEnd;
const duration = entry.duration;
const eventType = entry.name;
const target = entry.target || "(not set)"
console.log("----- INTERACTION -----");
console.log(`Input delay (ms): ${inputDelay}`);
console.log(`Event handler processing time (ms): ${processingTime}`);
console.log(`Presentation delay (ms): ${presentationDelay}`);
console.log(`Total event duration (ms): ${duration}`);
console.log(`Event type: ${eventType}`);
console.log(target);
});
});
// A durationThreshold of 16ms is necessary to include more
// interactions, since the default is 104ms. The minimum
// durationThreshold is 16ms.
po.observe({type: 'event', buffered: true, durationThreshold: 16});
Resource Timing API
Die Resource Timing API bietet Entwicklern detaillierte Informationen dazu, wie Ressourcen für eine bestimmte Seite geladen wurden. Trotz des Namens der API sind die bereitgestellten Informationen nicht nur auf Zeitdaten beschränkt (obwohl es davon viele gibt). Weitere Daten, auf die Sie zugreifen können:
initiatorType
: Gibt an, wie die Ressource abgerufen wurde, z. B. über ein<script>
- oder<link>
-Tag oder über einenfetch()
-Aufruf.nextHopProtocol
: das Protokoll, das zum Abrufen der Ressource verwendet wird, z. B.h2
oderquic
.encodedBodySize
/decodedBodySize: Die Größe der Ressource in ihrer codierten oder decodierten Form.transferSize
: Die Größe der Ressource, die tatsächlich über das Netzwerk übertragen wurde. Wenn Ressourcen aus dem Cache bereitgestellt werden, kann dieser Wert viel kleiner alsencodedBodySize
sein und in einigen Fällen sogar null (wenn keine Cache-Neuvalidierung erforderlich ist).
Mit der Property transferSize
von Ressourcenzeiteinträgen können Sie den Messwert Cache-Trefferquote oder den Messwert Größe der im Cache gespeicherten Ressourcen messen. So können Sie nachvollziehen, wie sich Ihre Ressourcen-Caching-Strategie auf die Leistung bei wiederkehrenden Besuchern auswirkt.
Im folgenden Beispiel werden alle von der Seite angeforderten Ressourcen protokolliert und es wird angegeben, ob die jeweilige Ressource aus dem Cache stammt oder nicht.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// If transferSize is 0, the resource was fulfilled using the cache.
console.log(entry.name, entry.transferSize === 0);
}
});
// Start listening for `resource` entries to be dispatched.
po.observe({type: 'resource', buffered: true});
Navigation Timing API
Die Navigation Timing API ähnelt der Resource Timing API, meldet aber nur Navigationsanfragen. Der navigation
-Eintragstyp ähnelt auch dem resource
-Eintragstyp, enthält aber einige zusätzliche Informationen, die nur für Navigationsanfragen gelten, z. B. wenn die Ereignisse DOMContentLoaded
und load
ausgelöst werden.
Ein Messwert, den viele Entwickler zur Analyse der Serverantwortzeit (Time to First Byte (TTFB)) verwenden, ist über die Navigation Timing API verfügbar. Dabei handelt es sich um den responseStart
-Zeitstempel des Eintrags.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// If transferSize is 0, the resource was fulfilled using the cache.
console.log('Time to first byte', entry.responseStart);
}
});
// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});
Ein weiterer Messwert, der für Entwickler von Service Workern von Bedeutung sein kann, ist die Startzeit des Service Workers für Navigationsanfragen. Das ist die Zeit, die der Browser benötigt, um den Service Worker-Thread zu starten, bevor er Fetch-Ereignisse abfangen kann.
Die Startzeit des Service Workers für eine bestimmte Navigationsanfrage kann anhand des Deltas zwischen entry.responseStart
und entry.workerStart
ermittelt werden.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('Service Worker startup time:',
entry.responseStart - entry.workerStart);
}
});
// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});
Server Timing API
Mit der Server Timing API können Sie anfragespezifische Zeitdaten über Antwortheader von Ihrem Server an den Browser weitergeben. Sie können beispielsweise angeben, wie lange es gedauert hat, Daten für eine bestimmte Anfrage in einer Datenbank abzurufen. Das kann beim Beheben von Leistungsproblemen hilfreich sein, die durch eine langsame Serverleistung verursacht werden.
Für Entwickler, die Analysetools von Drittanbietern verwenden, ist die Server Timing API die einzige Möglichkeit, Serverleistungsdaten mit anderen Geschäftsmesswerten in Beziehung zu setzen, die mit diesen Analysetools erfasst werden.
Wenn Sie Server-Timing-Daten in Ihren Antworten angeben möchten, können Sie den Antwortheader Server-Timing
verwenden. Hier ein Beispiel:
HTTP/1.1 200 OK
Server-Timing: miss, db;dur=53, app;dur=47.2
Anschließend können Sie diese Daten auf Ihren Seiten sowohl in resource
- als auch in navigation
-Einträgen aus den Resource Timing API und Navigation Timing API lesen.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Logs all server timing data for this response
console.log('Server Timing', entry.serverTiming);
}
});
// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});