Benutzerdefinierte Messwerte

nutzerbezogene Messwerte, die sich auf jeder Website universell messen lassen, sind äußerst nützlich. Mit diesen Messwerten können Sie:

  • Verstehen, wie echte Nutzende das Web als Ganzes erleben.
  • Vergleichen Sie Ihre Website mit der eines Mitbewerbers.
  • Verfolgen Sie nützliche und umsetzbare Daten in Ihren Analysetools, ohne benutzerdefinierten Code schreiben zu müssen.

Universelle Messwerte bieten eine gute Grundlage. In vielen Fällen müssen jedoch mehr als nur diese Messwerte erfasst werden, um den gesamten Nutzerkomfort für eine bestimmte Website zu erfassen.

Mit benutzerdefinierten Messwerten können Sie die Nutzererfahrung auf Ihrer Website ermitteln, die möglicherweise nur für sie gilt, z. B.:

  • Wie lange es dauert, bis eine Single-Page-Anwendung (SPA) von einer Seite zur anderen wechselt.
  • Wie lange es dauert, bis auf einer Seite Daten angezeigt werden, die aus einer Datenbank für angemeldete Nutzer abgerufen wurden.
  • Gibt an, wie lange es dauert, bis eine SSR-App (Server-Side-Rendering) hydratisiert.
  • Die Cache-Trefferquote für Ressourcen, die von wiederkehrenden Besuchern geladen wurden.
  • Die Ereignislatenz von Klick- oder Tastaturereignissen in einem Spiel.

APIs zum Messen benutzerdefinierter Messwerte

Bisher hatten Webentwickler nicht viele Low-Level-APIs zur Leistungsmessung und mussten daher auf Hacks zurückgreifen, um die Leistung einer Website zu messen.

Sie können beispielsweise bestimmen, ob der Hauptthread aufgrund von JavaScript-Aufgaben mit langer Ausführungszeit blockiert ist, indem Sie eine requestAnimationFrame-Schleife ausführen und das Delta zwischen den einzelnen Frames berechnen. Wenn das Delta deutlich länger ist als die Framerate der Anzeige, können Sie dies als lange Aufgabe melden. Solche Hackerangriffe werden jedoch nicht empfohlen, da sie sich in Wirklichkeit selbst auf die Leistung auswirken, z. B. indem sie den Akku entladen.

Die erste Regel für eine effektive Leistungsmessung besteht darin, sicherzustellen, dass Ihre Techniken zur Leistungsmessung selbst keine Leistungsprobleme verursachen. Daher sollten Sie für alle benutzerdefinierten Messwerte, die Sie auf Ihrer Website messen, nach Möglichkeit eine der folgenden APIs verwenden.

Performance Observer API

Unterstützte Browser

  • 52
  • 79
  • 57
  • 11

Quelle

Mit der Performance Observer API werden Daten aus allen anderen auf dieser Seite beschriebenen Leistungs-APIs erfasst und angezeigt. Es ist entscheidend, sie zu verstehen, um aussagekräftige Daten zu erhalten.

Mit PerformanceObserver können Sie leistungsbezogene Ereignisse passiv abonnieren. Dadurch können API-Callbacks während Inaktivität ausgelöst werden, was bedeutet, dass sie in der Regel die Seitenleistung nicht beeinträchtigen.

Übergeben Sie zum Erstellen von PerformanceObserver einen Callback, der immer dann ausgeführt wird, wenn neue Leistungseinträge gesendet werden. Anschließend teilen Sie dem Beobachter mithilfe der Methode observe() mit, welche Arten von Einträgen überwacht werden soll:

// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  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'});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

In den folgenden Abschnitten sind alle Eintragstypen aufgeführt, die zur Beobachtung verfügbar sind. In neueren Browsern können Sie jedoch prüfen, welche Eintragstypen über die statische Eigenschaft PerformanceObserver.supportedEntryTypes verfügbar sind.

Bereits erfolgte Einträge beobachten

Standardmäßig können PerformanceObserver-Objekte nur Einträge beobachten, während sie auftreten. Dies kann zu Problemen führen, wenn Sie den Code für die Leistungsanalyse per Lazy Loading laden möchten, damit Ressourcen mit höherer Priorität nicht blockiert werden.

Wenn Sie ältere Einträge abrufen möchten (nachdem sie erfasst wurden), setzen Sie das Flag buffered auf true, wenn Sie observe() aufrufen. Beim ersten Aufruf des PerformanceObserver-Callbacks schließt der Browser bisherige Einträge aus seinem Zwischenspeicher für Leistungseinträge ein.

po.observe({
  type: 'some-entry-type',
  buffered: true,
});

Zu vermeidende Legacy-Performance-APIs

Vor der Performance Observer API konnten Entwickler auf Leistungseinträge mit den folgenden drei Methoden zugreifen, die für das Objekt performance definiert wurden:

Obwohl diese APIs weiterhin unterstützt werden, wird ihre Verwendung nicht empfohlen, da sie nicht zulassen, dass Sie darauf warten, wenn neue Einträge ausgegeben werden. Außerdem werden viele neue APIs (z. B. lange Aufgaben) nicht über das Objekt performance verfügbar gemacht, sondern nur über PerformanceObserver.

Sofern Sie keine Kompatibilität mit Internet Explorer benötigen, sollten Sie diese Methoden in Ihrem Code vermeiden und ab sofort PerformanceObserver verwenden.

Nutzertiming-API

Die User Timing API ist eine allgemeine Mess-API für zeitbasierte Messwerte. Damit können Sie beliebig 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 liegt jedoch darin, dass sie sich gut in Leistungstools einbinden lässt. In den Chrome-Entwicklertools werden beispielsweise Messungen des Nutzertimings im Bereich „Leistung“ visualisiert. Viele Analyseanbieter erfassen außerdem automatisch alle von Ihnen vorgenommenen Messungen und senden die Daten zur Dauer an ihr Analyse-Backend.

Wenn Sie Nutzertiming-Messungen in Berichte aufnehmen möchten, können Sie PerformanceObserver verwenden und sich registrieren, um Einträge vom Typ measure zu beobachten:

// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // 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});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

Long Tasks API

Unterstützte Browser

  • 58
  • 79
  • x
  • x

Quelle

Mit der Long Tasks API können Sie feststellen, wann der Hauptthread des Browsers so lange blockiert ist, dass sich dies auf die Framerate oder die Eingabelatenz auswirkt. Die API meldet alle Aufgaben, die länger als 50 Millisekunden ausgeführt werden.

Jedes Mal, wenn Sie teuren Code ausführen oder große Skripts laden und ausführen müssen, ist es hilfreich zu verfolgen, ob dieser Code den Hauptthread blockiert. Tatsächlich basieren viele übergeordnete Messwerte auf der Long Tasks API selbst, z. B. Time to Interactive (TTI) und Total Blocking Time (TBT)).

Um zu ermitteln, wann lange Aufgaben stattfinden, können Sie PerformanceObserver verwenden und sich registrieren, um Einträge vom Typ longtask zu beobachten:

// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // 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});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

Element Timing API

Unterstützte Browser

  • 77
  • 79
  • x
  • x

Quelle

Der Messwert Largest Contentful Paint (LCP) gibt Aufschluss darüber, wann das größte Bild oder der größte Textblock auf den Bildschirm gezeichnet 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 tatsächlich auf der Element Timing API und fügt automatisch das größte inhaltsorientierte Element hinzu. Sie können aber auch Berichte zu anderen Elementen erstellen, 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>
  // Catch errors since some browsers throw when using the new `type` option.
  // https://bugs.webkit.org/show_bug.cgi?id=209216
  try {
    // Create the performance observer.
    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});
  } catch (e) {
    // Do nothing if the browser doesn't support this API.
  }
</script>

Event Timing API

Mit dem Messwert Interaction to Next Paint (INP) wird die Reaktionsfähigkeit einer Seite insgesamt bewertet. Dabei werden alle Interaktionen per Klick, Tippen und über die Tastatur während der gesamten Lebensdauer einer Seite erfasst. Der INP einer Seite ist meist 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 rendert, der das visuelle Ergebnis der Nutzereingabe anzeigt.

Der INP-Messwert wird durch die Event Timing API ermöglicht. Diese API zeigt eine Reihe von Zeitstempeln an, die während des Ereignislebenszyklus auftreten, darunter:

  • startTime: die Uhrzeit, zu der 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 beendet, der von Event-Handlern für dieses Ereignis initiiert wurde.
  • duration: die Zeit (aus Sicherheitsgründen auf 8 Millisekunden gerundet) zwischen dem Empfang des Ereignisses im Browser, bis der nächste Frame dargestellt werden kann, nachdem der gesamte synchrone Code ausgeführt wurde, der von den Event-Handlern initiiert wurde.

Das folgende Beispiel zeigt, wie diese Werte zum Erstellen benutzerdefinierter Messungen verwendet werden:

// Catch errors some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  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 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 time (ms): ${processingTime}`);
      console.log(`Total event duration (ms): ${duration}`);
      console.log(`Event type: ${eventType}`);
      console.log(target);
    });
  });

  // A durationThreshold of 16ms is necessary to surface more
  // interactions, since the default is 104ms. The minimum
  // durationThreshold is 16ms.
  po.observe({type: 'event', buffered: true, durationThreshold: 16});
} catch (error) {
  // Do nothing if the browser doesn't support this API.
}

Resource Timing API

Die Resource Timing API bietet Entwicklern detaillierte Informationen darüber, wie Ressourcen für eine bestimmte Seite geladen wurden. Trotz des Namens der API sind die von ihr bereitgestellten Informationen nicht nur auf Zeitdaten beschränkt (obwohl es zahlreiche davon gibt). Sie können unter anderem auf folgende Daten zugreifen:

  • initiatorType: Wie die Ressource abgerufen wurde, beispielsweise über ein <script>- oder <link>-Tag oder einen fetch()-Aufruf.
  • nextHopProtocol: Das Protokoll, mit dem die Ressource abgerufen wird, z. B. h2 oder quic.
  • 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 die Ressourcen vom Cache bedient werden, kann dieser Wert viel kleiner als der encodedBodySize sein. In einigen Fällen kann er null sein (wenn keine erneute Cache-Validierung erforderlich ist).

Sie können das Attribut transferSize von Ressourcenzeiteinträgen verwenden, um einen Messwert für die Cache-Trefferquote oder die Gesamtgröße der im Cache gespeicherten Ressourcen zu messen. Dies kann hilfreich sein, um zu verstehen, wie sich Ihre Caching-Strategie für Ressourcen auf die Leistung wiederkehrender Besucher auswirkt.

Im folgenden Beispiel werden alle von der Seite angeforderten Ressourcen protokolliert und es wird angegeben, ob jede Ressource vom Cache erfüllt wurde.

// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  const po = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // If transferSize is 0, the resource was fulfilled via the cache.
      console.log(entry.name, entry.transferSize === 0);
    }
  });

  // Start listening for `resource` entries to be dispatched.
  po.observe({type: 'resource', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

Unterstützte Browser

  • 57
  • 12
  • 58
  • 15

Quelle

Die Navigation Timing API ähnelt der Resource Timing API, meldet jedoch nur Navigationsanfragen. Der Eintragstyp navigation ähnelt ebenfalls dem Eintragstyp resource, enthält jedoch 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 verfolgen, um die Serverantwortzeit (Time to First Byte (TTFB)) zu verstehen, ist mit der Navigation Timing API verfügbar, insbesondere mit dem Zeitstempel responseStart des Eintrags.

// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // Create the performance observer.
  const po = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // If transferSize is 0, the resource was fulfilled via the cache.
      console.log('Time to first byte', entry.responseStart);
    }
  });

  // Start listening for `navigation` entries to be dispatched.
  po.observe({type: 'navigation', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

Ein weiterer Messwert, den Entwickler, die Service Worker verwenden, interessieren könnte, ist die Service Worker-Startzeit für Navigationsanfragen. Dies ist die Zeit, die der Browser benötigt, um den Service Worker-Thread zu starten, bevor er Abrufereignisse abfangen kann.

Die Service Worker-Startzeit für eine bestimmte Navigationsanfrage kann aus der Differenz zwischen entry.responseStart und entry.workerStart ermittelt werden.

// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // 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});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

Server Timing API

Mit der Server Timing API kannst du anfragespezifische Zeitdaten von deinem Server über Antwortheader an den Browser weitergeben. Sie können beispielsweise angeben, wie lange es gedauert hat, Daten in einer Datenbank für eine bestimmte Anfrage nachzuschlagen. Dies kann beim Beheben von Leistungsproblemen hilfreich sein, die durch langsame Serveraktivitäten verursacht werden.

Für Entwickler, die Analyseanbieter von Drittanbietern nutzen, ist die Server Timing API die einzige Möglichkeit, Daten zur Serverleistung mit anderen Geschäftsmesswerten zu korrelieren, die von diesen Analysetools möglicherweise gemessen werden.

Wenn Sie in Ihren Antworten Serverzeitdaten 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 der Resource Timing API und der Navigation Timing API lesen.

// Catch errors since some browsers throw when using the new `type` option.
// https://bugs.webkit.org/show_bug.cgi?id=209216
try {
  // 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});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}