Es ist sehr nützlich, nutzerorientierte Messwerte zu haben, die Sie universell auf jeder Website messen können. Mit diesen Messwerten können Sie:
- Sie können nachvollziehen, wie echte Nutzer das Web insgesamt erleben.
- Ihre Website mit der eines Mitbewerbers vergleichen
- Nützliche und verwertbare Daten in Ihren Analysetools erfassen, ohne benutzerdefinierten Code schreiben zu müssen
Universelle Messwerte bieten eine gute Grundlage, aber in vielen Fällen müssen Sie mehr als nur diese Messwerte erfassen, um die gesamte Nutzererfahrung auf 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.
- Wie lange es dauert, bis auf einer Seite Daten angezeigt werden, die für angemeldete Nutzer aus einer Datenbank abgerufen wurden.
- Wie lange es dauert, bis eine serverseitig gerenderte (SSR) App hydriert wird.
- Die Cache-Trefferquote für Ressourcen, die von wiederkehrenden Besuchern geladen werden.
- Die Ereignislatenz von Klick- oder Tastaturereignissen in einem Spiel.
APIs zum Messen benutzerdefinierter Messwerte
Bisher hatten Webentwickler nicht viele Low-Level-APIs zur Verfügung, um die Leistung zu messen. Daher mussten sie auf Hacks zurückgreifen, um festzustellen, ob eine Website gut funktioniert.
So lässt sich beispielsweise ermitteln, ob der Hauptthread aufgrund von zeitaufwendigen JavaScript-Aufgaben blockiert wird. Dazu wird eine requestAnimationFrame-Schleife ausgeführt und das Delta zwischen den einzelnen Frames berechnet. Wenn das Delta deutlich länger als die Framerate des Displays ist, können Sie es als Long Task melden. Solche Hacks werden jedoch nicht empfohlen, da sie die Leistung selbst beeinträchtigen (z. B. durch Entladen des Akkus).
Die erste Regel für eine effektive Leistungsmessung besteht darin, sicherzustellen, dass Ihre Leistungsmessungstechniken selbst keine Leistungsprobleme verursachen. Für alle benutzerdefinierten Messwerte, die Sie auf Ihrer Website erfassen, sollten Sie daher nach Möglichkeit eine der folgenden APIs verwenden.
Performance Observer API
Die Performance Observer API ist der Mechanismus, mit dem Daten aus allen anderen Leistungs-APIs, die auf dieser Seite beschrieben werden, erfasst und angezeigt werden. Es ist wichtig, das zu verstehen, um gute Daten zu erhalten.
Mit PerformanceObserver können Sie Leistungsereignisse passiv abonnieren. Dadurch können API-Callbacks während Leerlaufzeiten ausgelöst werden, sodass sie in der Regel die Seitenleistung nicht beeinträchtigen.
Wenn Sie ein PerformanceObserver erstellen möchten, übergeben Sie einen Callback, der immer dann ausgeführt wird, wenn neue Leistungseinträge gesendet werden. Anschließend teilen Sie dem Observer mit der Methode observe() mit, auf welche Arten von Einträgen er achten 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 verfügbaren Eintragstypen aufgeführt. In neueren Browsern können Sie jedoch über die statische Eigenschaft PerformanceObserver.supportedEntryTypes prüfen, welche Eintragstypen verfügbar sind.
Bereits erfolgte Einträge ansehen
Standardmäßig können PerformanceObserver-Objekte Einträge nur beobachten, wenn sie auftreten. Das kann zu Problemen führen, wenn Sie Ihren Code für Leistungsanalysen verzögert laden möchten, damit er Ressourcen mit höherer Priorität nicht blockiert.
Wenn Sie historische Einträge (nachdem sie erfolgt sind) abrufen möchten, setzen Sie das Flag buffered auf true, wenn Sie observe() aufrufen. Der Browser enthält beim ersten Aufruf Ihres PerformanceObserver-Callbacks Verlaufsdaten aus seinem Performance-Eintragspuffer, bis zur maximalen Puffergröße für diesen Typ.
po.observe({
type: 'some-entry-type',
buffered: true,
});
Vermeidung von 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 weiterhin unterstützt, ihre Verwendung wird jedoch nicht empfohlen, da Sie damit nicht darauf warten können, bis neue Einträge ausgegeben werden. Außerdem werden viele neue APIs (z. B. largest-contentful-paint) nicht über das Objekt performance, sondern nur über PerformanceObserver bereitgestellt.
Sofern Sie nicht unbedingt Kompatibilität mit 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 universelle API zum Messen zeitbasierter Messwerte. Sie können damit beliebige Zeitpunkte markieren und später die Dauer zwischen diesen Markierungen 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 Möglichkeiten, aber die User Timing API lässt sich gut in Leistungstools einbinden. In den Chrome-Entwicklertools werden beispielsweise User Timing-Messungen im Bereich „Leistung“ visualisiert. Viele Analyseanbieter erfassen auch automatisch alle Messungen, die Sie vornehmen, und senden die Zeitmessungsdaten an ihr Analyse-Backend.
Um User-Timing-Messungen zu melden, können Sie PerformanceObserver verwenden und sich für Einträge 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
Die Long Tasks API ist nützlich, um zu ermitteln, wann der Hauptthread des Browsers lange genug blockiert wird, um sich auf die Framerate oder die Eingabelatenz auszuwirken. Die API meldet alle Aufgaben, die länger als 50 Millisekunden ausgeführt werden.
Immer wenn Sie rechenaufwendigen Code ausführen oder große Skripts laden und ausführen müssen, ist es hilfreich, zu prüfen, ob dieser Code den Hauptthread blockiert. Viele Messwerte auf höherer Ebene basieren auf der Long Tasks API, z. B. Time to Interactive (TTI) und Total Blocking Time (TBT).
Um zu ermitteln, wann lange Aufgaben ausgeführt werden, können Sie PerformanceObserver verwenden und sich für Einträge 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 sich mit langen Frames mit einer Dauer von über 50 Millisekunden befasst, anstatt mit langen Tasks. Damit werden einige Einschränkungen der Long Tasks API behoben, darunter eine bessere Zuordnung und ein größerer Umfang potenziell problematischer Verzögerungen.
Um zu ermitteln, 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) ist nützlich, um zu wissen, wann das größte Bild oder der größte Textblock auf dem Bildschirm gerendert 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 bietet automatisches Reporting des größten inhaltlichen Elements. Sie können aber auch andere Elemente erfassen, indem Sie ihnen explizit das Attribut elementtiming hinzufügen und einen PerformanceObserver registrieren, um den Eintragstyp element 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
Mit dem Messwert Interaction to Next Paint (INP) wird die allgemeine Reaktionszeit einer Seite bewertet. Dazu werden alle Klicks, Berührungen und Tastaturinteraktionen während der gesamten Lebensdauer einer Seite erfasst. Der INP einer Seite ist in den meisten Fällen die Interaktion, die am längsten gedauert hat – von dem Zeitpunkt, an dem der Nutzer die Interaktion initiiert hat, bis zu dem Zeitpunkt, an dem der Browser den nächsten Frame mit dem visuellen Ergebnis der Eingabe des Nutzers rendert.
Der INP-Messwert wird durch die Event Timing API ermöglicht. Diese API macht eine Reihe von Zeitstempeln verfügbar, die während des Ereignislebenszyklus auftreten, darunter:
startTime: der Zeitpunkt, an dem der Browser das Ereignis empfängt.processingStart: Die Zeit, zu der der Browser mit der Verarbeitung von Event-Handlern für das Ereignis beginnen kann.processingEnd: Zeitpunkt, zu dem der Browser die Ausführung des gesamten synchronen Codes abgeschlossen hat, der von Ereignishandlern für dieses Ereignis initiiert wurde.duration: Die Zeit (aus Sicherheitsgründen auf 8 Millisekunden gerundet) zwischen dem Empfang des Ereignisses durch den Browser und dem Rendern des nächsten Frames nach Ausführung des gesamten synchronen Codes, der von den Event-Handlern initiiert wurde.
Im folgenden Beispiel sehen Sie, wie Sie diese Werte verwenden, um benutzerdefinierte Messungen zu 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 beschränken sich die bereitgestellten Informationen nicht nur auf Zeitdaten (obwohl davon eine Menge vorhanden ist). 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.h2oderquic.encodedBodySize/decodedBodySize]: Die Größe der Ressource in ihrer codierten bzw. 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 alsencodedBodySizesein. In einigen Fällen kann er auch null sein, wenn keine Cache-Revalidierung erforderlich ist.
Mit der transferSize-Eigenschaft von Einträgen für das Ressourcen-Timing können Sie den Messwert Cache-Trefferquote oder Gesamtgröße der im Cache gespeicherten Ressourcen messen. Das kann hilfreich sein, um zu verstehen, wie sich Ihre Strategie für das Zwischenspeichern von Ressourcen auf die Leistung für wiederkehrende Besucher auswirkt.
Im folgenden Beispiel werden alle von der Seite angeforderten Ressourcen protokolliert und es wird angegeben, ob die jeweilige Ressource aus dem Cache bereitgestellt wurde.
// 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 Eintragstyp navigation ähnelt dem Eintragstyp resource, 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 Messung der Serverantwortzeit (Time to First Byte (TTFB)) erfassen, ist über die Navigation Timing API verfügbar, insbesondere über den Zeitstempel responseStart 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, die Service Worker verwenden, 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 aus der Differenz 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 Zeitmessungsdaten über Antwortheader von Ihrem Server an den Browser übergeben. Sie können beispielsweise angeben, wie lange es gedauert hat, Daten für eine bestimmte Anfrage in einer Datenbank zu suchen. Das kann hilfreich sein, um Leistungsprobleme zu beheben, die durch Langsamkeit auf dem Server verursacht werden.
Für Entwickler, die Analyseanbieter von Drittanbietern verwenden, ist die Server Timing API die einzige Möglichkeit, Serverleistungsdaten mit anderen Geschäftsmesswerten zu korrelieren, die von diesen Analysetools gemessen 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 für resource- als auch für navigation-Einträge aus der Resource Timing API und der 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});