Wskaźniki niestandardowe

Posiadanie uniwersalnych danych na temat użytkowników, które można mierzyć w dowolnej witrynie, może pomóc w zrozumieniu, jak użytkownicy korzystają z sieci, oraz w porównaniu Twojej witryny z konkurencją. W wielu przypadkach musisz jednak zmierzyć więcej niż tylko uniwersalne dane, aby w pełni wykorzystać możliwości witryny.

Niestandardowe dane umożliwiają pomiar różnych aspektów korzystania z Twojej witryny, które mogą dotyczyć tylko Twojej witryny, takich jak:

  • Czas potrzebny na przejście aplikacji jednostronicowej (SPA) z jednej „strony” na drugą.
  • Czas potrzebny na wyświetlenie na stronie danych pobranych z bazy danych w przypadku zalogowanych użytkowników.
  • Czas potrzebny na hydratację aplikacji renderowanej po stronie serwera (SSR).
  • Odsetek trafień w pamięci podręcznej dla zasobów wczytywanych przez powracających użytkowników.
  • Opóźnienie zdarzeń kliknięcia lub klawiatury w grze.

Interfejsy API do pomiaru wskaźników niestandardowych

Deweloperzy stron internetowych nie korzystali dotychczas z wielu niskopoziomowych interfejsów API do pomiaru wydajności, w związku z czym musieli korzystać z hakerów, aby mierzyć skuteczność witryny. Możesz np. sprawdzić, czy wątek główny jest blokowany przez długo trwające zadania JavaScriptu, uruchamiając pętlę requestAnimationFrame i obliczając różnicę między poszczególnymi klatkami. Jeśli delta jest znacznie dłuższa niż liczba klatek na sekundę wyświetlacza, możesz to zgłosić jako długie zadanie.

Takie ataki hakerskie mogą jednak pogorszyć wydajność witryny, np. rozładować baterię urządzenia. Jeśli Twoje techniki pomiaru skuteczności same w sobie powodują problemy ze skutecznością, pochodzące z nich dane będą niedokładne. Dlatego do tworzenia danych niestandardowych zalecamy użycie jednego z poniższych interfejsów API.

Interfejs Performance Observer API

Obsługa przeglądarek

  • 52
  • 79
  • 57
  • 11

Źródło

Performance Observer API to mechanizm, który zbiera i wyświetla dane ze wszystkich innych interfejsów API do pomiaru wydajności opisanych na tej stronie. Zrozumienie ich jest kluczowe dla uzyskania dobrych danych.

Aby pasywnie subskrybować zdarzenia związane z wydajnością, możesz używać PerformanceObserver. Dzięki temu wywołania zwrotne interfejsu API mogą być uruchamiane w okresach bezczynności, co oznacza, że zwykle nie wpływają one na wydajność strony.

Podczas tworzenia PerformanceObserver przekazuj do niego wywołanie zwrotne, które jest wykonywane przy każdym wysłaniu nowych wpisów dotyczących wydajności. Następnie użyj metody observe(), aby w ten sposób poinformować obserwatora, jakiego typu wpisów ma nasłuchiwać:

// Catch errors that 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.
}

Poniższe sekcje zawierają listę wszystkich typów wpisów, które możesz obserwować. W nowszych przeglądarkach możesz też sprawdzić dostępne typy wpisów za pomocą właściwości statycznej PerformanceObserver.supportedEntryTypes.

Obserwuj wpisy, które już się pojawiły

Domyślnie obiekty PerformanceObserver mogą obserwować tylko wpisy w miarę ich występowania. Może to powodować problemy, jeśli chcesz leniwie ładować kod analizy wydajności, aby nie blokować zasobów o wyższym priorytecie.

Aby uzyskać wpisy historyczne, wywołaj funkcję observe z flagą buffered ustawioną na true. Przy pierwszym wywołaniu wywołania zwrotnego PerformanceObserver przeglądarka uwzględni historyczne wpisy ze swojego bufora wpisów dotyczących wydajności.

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

Starsze interfejsy API do zarządzania wydajnością, których należy unikać

Przed wprowadzeniem interfejsu Performance Observer API deweloperzy mogli uzyskiwać dostęp do wpisów dotyczących wydajności, korzystając z podanych niżej metod zdefiniowanych w obiekcie performance. Nie zalecamy ich używania, bo nie pozwalają na nasłuchiwanie nowych wpisów.

Poza tym wiele nowych interfejsów API (takich jak Long Tasks) nie jest ujawnianych przez obiekt performance, tylko przez PerformanceObserver. Dlatego, o ile nie potrzebujesz zgodności z Internet Explorerem, najlepiej unikać tych metod w kodzie i używać w przyszłości metody PerformanceObserver.

Interfejs User Timing API

User Timing API to interfejs API ogólnego przeznaczenia służący do pomiarów danych opartych na czasie. Pozwala na dowolne zaznaczanie punktów w czasie, a później na pomiar czasu między tymi znacznikami.

// 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');

Chociaż interfejsy API takie jak Date.now() czy performance.now() oferują podobne możliwości, interfejs User Timing API jest lepiej zintegrowany z narzędziami do pomiaru wydajności. Na przykład Narzędzia deweloperskie w Chrome wizualizują pomiary czasu działań użytkownika w panelu Skuteczność, a wielu dostawców narzędzi analitycznych automatycznie śledzi wszystkie wykonane przez Ciebie pomiary i wysyła dane o czasie trwania do swojego backendu analitycznego.

Aby raportować pomiary czasu działań użytkowników, zarejestruj parametr PerformanceObserver w celu obserwowania wpisów typu measure:

// Catch errors 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.
  po.observe({type: 'measure', buffered: true});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

Interfejs API Long Tasks

Obsługa przeglądarek

  • 58
  • 79
  • x
  • x

Źródło

Interfejs Long Tasks API przydaje się do określania, kiedy wątek główny przeglądarki jest blokowany na tyle długo, aby miał wpływ na liczbę klatek lub opóźnienie wejściowe. Interfejs API zgłasza wszystkie zadania wykonywane przez ponad 50 milisekund (ms).

Za każdym razem, gdy musisz uruchomić kosztowny kod lub wczytać i wykonać duże skrypty, warto sprawdzić, czy kod blokuje wątek główny. Wiele wskaźników wyższego poziomu jest tworzonych na podstawie interfejsu Long Tasks API (np. Time to Interactive (TTI) i Total blok Time (TBT)).

Aby określić, kiedy występują długie zadania, zarejestruj PerformanceObserver do obserwowania wpisów typu longtask:

// 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.
}

Interfejs API Element Timing

Obsługa przeglądarek

  • 77
  • 79
  • x
  • x

Źródło

Wskaźnik największego wyrenderowania treści (LCP) przydaje się, gdy wiesz, kiedy na ekranie jest wyrenderowany największy obraz lub blok tekstowy. W niektórych przypadkach warto jednak zmierzyć czas renderowania innego elementu.

W takich przypadkach użyj interfejsu Element Timing API. Interfejs LCP API powstał na bazie interfejsu Element Timing API i udostępnia automatyczne raportowanie największego elementu treści. Możesz też generować raporty na temat innych elementów, dodając do nich atrybut elementtiming i rejestrując parametr PerformanceObserver, który umożliwia obserwowanie typu wpisu element.

<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>

Interfejs API Event Timing

Opóźnienie przy pierwszym działaniu (FID) mierzy czas od pierwszej interakcji użytkownika ze stroną do momentu, w którym w odpowiedzi na tę interakcję przeglądarka może rozpocząć przetwarzanie modułów obsługi zdarzeń. W niektórych przypadkach może być jednak przydatne pomiary samego czasu przetwarzania zdarzeń.

Jest to możliwe za pomocą interfejsu Event Timing API, który oprócz pomiaru FID udostępnia też różne sygnatury czasowe w cyklu życia zdarzenia, w tym:

  • startTime: czas odebrania zdarzenia przez przeglądarkę.
  • processingStart: czas, w którym przeglądarka może rozpocząć przetwarzanie modułów obsługi zdarzenia.
  • processingEnd: czas zakończenia wykonywania przez przeglądarkę całego kodu synchronicznego zainicjowanego z modułów obsługi zdarzeń tego zdarzenia.
  • duration: czas (zaokrąglony ze względów bezpieczeństwa, zaokrąglony do 8 ms) pomiędzy otrzymaniem zdarzenia przez przeglądarkę do momentu, gdy po zakończeniu wykonywania całego kodu synchronicznego zainicjowanego przez moduły obsługi zdarzeń będzie ona mogła wyrenderować następną klatkę.

Z przykładu poniżej dowiesz się, jak używać tych wartości do tworzenia niestandardowych pomiarów:

// 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) => {
    const firstInput = entryList.getEntries()[0];

    // Measure First Input Delay (FID).
    const firstInputDelay = firstInput.processingStart - firstInput.startTime;

    // Measure the time it takes to run all event handlers
    // Doesn't include work scheduled asynchronously using methods like
    // `requestAnimationFrame()` or `setTimeout()`.
    const firstInputProcessingTime = firstInput.processingEnd - firstInput.processingStart;

    // Measure the entire duration of the event, from when input is received by
    // the browser until the next frame can be painted after processing all
    // event handlers.
    // Doesn't include work scheduled asynchronously using
    // `requestAnimationFrame()` or `setTimeout()`.
    // For security reasons, this value is rounded to the nearest 8 ms.
    const firstInputDuration = firstInput.duration;

    // Log these values to the console.
    console.log({
      firstInputDelay,
      firstInputProcessingTime,
      firstInputDuration,
    });
  });

  po.observe({type: 'first-input', buffered: true});
} catch (error) {
  // Do nothing if the browser doesn't support this API.
}

Interfejs Resource Timing API

Interfejs Resource Timing API zapewnia programistom szczegółowe informacje o sposobie wczytywania zasobów konkretnej strony. Niezależnie od nazwy interfejsu API dostarczane przez niego informacje nie ograniczają się tylko do danych o czasie (choć jest ich mnóstwo). Inne dane, do których masz dostęp:

  • initiatorType: sposób pobrania zasobu, np. z tagu <script> lub <link> albo z fetch().
  • nextHopProtocol: protokół używany do pobierania zasobu, np. h2 lub quic.
  • encodedBodySize i decodedBodySize]: rozmiar zasobu w jego zakodowanej lub zdekodowanej postaci.
  • transferSize: rozmiar zasobu, który został rzeczywiście przesłany przez sieć. Gdy zasoby są wypełniane z wykorzystaniem pamięci podręcznej, ta wartość może być znacznie mniejsza niż wartość encodedBodySize. W niektórych przypadkach może wynosić 0, jeśli nie jest wymagana ponowna weryfikacja pamięci podręcznej.

Możesz użyć właściwości transferSize wpisów czasu zasobów do pomiaru częstotliwości trafień w pamięci podręcznej lub wskaźnika całkowitego rozmiaru zasobów w pamięci podręcznej. Dzięki temu możesz się dowiedzieć, jak strategia buforowania zasobów wpływa na wydajność w przypadku powracających użytkowników.

Poniższy przykład rejestruje wszystkie zasoby żądane przez stronę i wskazuje, czy poszczególne zasoby zostały wypełnione za pomocą pamięci podręcznej:

// Catch errors 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.
}

Obsługa przeglądarek

  • 57
  • 12
  • 58
  • 15

Źródło

Interfejs Nawigacja Timing API jest podobny do interfejsu Resource Timing API, ale raportuje tylko żądania nawigacji. Typ wpisu navigation jest również podobny do typu wpisu resource, ale zawiera dodatkowe informacje dotyczące tylko żądań nawigacji (np. zdarzenia DOMContentLoaded i load).

Jednym z rodzajów danych, które wielu programistów śledzi, na potrzeby zrozumienia czasu odpowiedzi serwera, czyli czas do pierwszego bajtu (TTFB), jest sygnatura czasowa responseStart w interfejsie Navigation Timing API.

// Catch errors since  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 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});
} catch (e) {
  // Do nothing if the browser doesn't support this API.
}

Dla deweloperów, którzy korzystają z mechanizmów Service Worker, mogą się też przydać czasy uruchamiania tych procesów w przypadku żądań nawigacji. Jest to czas potrzebny przeglądarce na uruchomienie wątku skryptu service worker, zanim rozpocznie się przechwytywanie zdarzeń pobierania.

Czas uruchomienia skryptu service worker dla określonego żądania nawigacji można określić na podstawie delta między entry.responseStart a entry.workerStart w ten sposób:

// Catch errors 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

Interfejs Server Timing API umożliwia przekazywanie danych o czasie w konkretnych żądaniach z serwera do przeglądarki za pomocą nagłówków odpowiedzi. Możesz na przykład określić czas potrzebny na wyszukiwanie danych w bazie danych dla konkretnego żądania, co może być przydatne przy debugowaniu problemów z wydajnością spowodowanych powolnym działaniem serwera.

Dla deweloperów, którzy korzystają z usług zewnętrznych dostawców analityki, korzystanie z interfejsu Server Timing API jest jedynym sposobem skorelowania danych o wydajności serwera z innymi danymi biznesowymi mierzonymi przez te narzędzia.

Aby określić w odpowiedziach dane o czasie serwera, użyj nagłówka odpowiedzi Server-Timing. Oto przykład:

HTTP/1.1 200 OK

Server-Timing: miss, db;dur=53, app;dur=47.2

Następnie dane te możesz odczytywać na stronach zarówno dla wpisów resource, jak i navigation za pomocą interfejsów Resource Timing API i Navigation Timing API.

// Catch errors 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.
}