Wskaźniki niestandardowe

Bardzo ważne jest posiadanie danych dotyczących użytkownika, które możesz mierzyć uniwersalnie w dowolnej witrynie. Dzięki tym danym:

  • Dowiedz się, jak prawdziwi użytkownicy postrzegają internet jako całość.
  • Porównaj swoją witrynę z witryną konkurencji.
  • Śledź przydatne i przydatne dane w narzędziach analitycznych bez konieczności pisania niestandardowego kodu.

Dane uniwersalne są dobrym punktem odniesienia, ale w wielu przypadkach konieczne jest mierzenie więcej niż tylko te dane, aby uzyskać pełny obraz wrażeń związanych z daną witryną.

Dzięki danym niestandardowym możesz mierzyć różne aspekty obsługi witryny, które mogą mieć zastosowanie tylko w niej, np.:

  • Czas potrzebny na przejście aplikacji jednej strony (SPA) z jednej „strony” na inną.
  • 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).
  • Współczynnik 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 niestandardowych danych

Dawniej programiści nie używali zbyt wielu interfejsów API niskopoziomowych do pomiaru wydajności, dlatego musieli uciekać się do ataków hakerskich, aby zmierzyć, czy strona działa dobrze.

Można na przykład określić, czy wątek główny jest blokowany przez długotrwałe zadania JavaScriptu, uruchamiając pętlę requestAnimationFrame i obliczając różnicę między poszczególnymi klatkami. Jeśli ta różnica jest znacznie dłuższa niż liczba klatek na ekranie, możesz zgłosić to jako długie zadanie. Nie są one jednak zalecane, ponieważ same w sobie wpływają na wydajność (np. przez wyczerpywanie się baterii).

Pierwszą zasadą skutecznego pomiaru skuteczności jest dopilnowanie, by zastosowane techniki pomiaru skuteczności nie powodowały problemów. Dlatego w przypadku niestandardowych danych zbieranych w witrynie najlepiej jest, jeśli to możliwe, korzystać z 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 dotyczących wydajności omówionych na tej stronie. Zrozumienie tego jest kluczowe do uzyskania dobrych danych.

Za pomocą usługi PerformanceObserver możesz pasywnie subskrybować zdarzenia związane ze skutecznością. Dzięki temu wywołania zwrotne interfejsu API mogą być uruchamiane w okresach bezczynności, co oznacza, że zwykle nie wpływają na wydajność strony.

Aby utworzyć PerformanceObserver, przekaż je wywołanie zwrotne, które zostanie uruchomione za każdym razem, gdy zostaną wysłane nowe wpisy wydajności. Następnie informujesz obserwatora, jakich wpisów ma słuchać, używając metody observe():

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

W sekcjach poniżej znajdziesz listę wszystkich różnych typów wpisów, które można obserwować. W nowszych przeglądarkach możesz sprawdzić, jakie typy wpisów są dostępne przy użyciu właściwości statycznej PerformanceObserver.supportedEntryTypes.

Obserwuj wpisy, które już istnieją

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

Aby pobrać wpisy historyczne (po ich wystąpieniu), ustaw flagę buffered na true przy wywołaniu funkcji observe(). Przy pierwszym wywołaniu wywołania zwrotnego PerformanceObserver przeglądarka uwzględni wpisy historyczne z bufora wpisów dotyczących wydajności.

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

Starsze interfejsy API wydajności, których należy unikać

Przed wprowadzeniem Performance Observer API deweloperzy mogli uzyskiwać dostęp do wpisów o wydajności za pomocą 3 metod zdefiniowanych w obiekcie performance:

Chociaż te interfejsy API są nadal obsługiwane, ich użycie nie jest zalecane, ponieważ nie pozwalają na nasłuchiwanie emisji nowych wpisów. Poza tym wiele nowych interfejsów API (np. Long Tasks) nie jest dostępnych przez obiekt performance. Są one udostępniane tylko przez PerformanceObserver.

Jeśli nie potrzebujesz zgodności z Internet Explorerem, najlepiej unikać tych metod w kodzie i od tej pory używać standardu PerformanceObserver.

Interfejs API czasu użytkownika

User Timing API to uniwersalny interfejs API do pomiarów danych opartych na czasie. Za jego pomocą możesz dowolnie oznaczać punkty w czasie, a później mierzyć czas między tymi oznaczeniami.

// 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(), zapewniają podobne możliwości, zaletą korzystania z interfejsu User Timing API jest jego integracja z narzędziami zwiększającymi skuteczność. Na przykład Narzędzia deweloperskie w Chrome wizualizują pomiary czasu działań użytkownika w panelu Skuteczność. Wielu dostawców usług analitycznych może też automatycznie śledzić przeprowadzane przez Ciebie pomiary i wysyłać dane o czasie trwania do swoich systemów analitycznych.

Aby raportować wyniki pomiarów czasu użytkownika, możesz użyć narzędzia PerformanceObserver i zarejestrować się w celu obserwowania wpisów typu measure:

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

Interfejs Long Tasks API

Obsługa przeglądarek

  • 58
  • 79
  • x
  • x

Źródło

Interfejs Long Tasks API pozwala dowiedzieć się, kiedy wątek główny przeglądarki jest blokowany na tyle długo, że może to wpłynąć na liczbę klatek lub opóźnienie sygnału wejściowego. Interfejs API zgłasza wszystkie zadania wykonywane dłużej niż przez 50 milisekund.

Za każdym razem, gdy musisz uruchomić drogi kod lub wczytać i wykonać duże skrypty, warto śledzić, czy ten kod nie blokuje wątku głównego. W rzeczywistości wiele wskaźników wyższego poziomu jest tworzonych na podstawie samych interfejsów Long Tasks API (takich jak Time to Interactive (TTI) i Total Block Time (TBT)).

Aby określić, kiedy pojawiają się długie zadania, możesz użyć narzędzia PerformanceObserver, a potem zarejestrować się i obserwować wpisy 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

Dane Największe wyrenderowanie treści (LCP) pozwalają dowiedzieć się, kiedy na ekranie został 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 jest oparty na interfejsie Element Timing API i dodaje automatyczne raportowanie największego elementu z treścią, ale możesz też tworzyć raporty dotyczące innych elementów, jawnie dodając do nich atrybut elementtiming i rejestrując funkcję PerformanceObserver w celu obserwowania 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 Event Timing API

Dane Od interakcji do kolejnego wyrenderowania (INP) oceniają ogólną responsywność strony przez obserwację wszystkich kliknięć, dotknięć i klawiatury w całym cyklu życia strony. Wartość INP strony to najczęściej interakcja, której wykonanie zajęło najdłużej – od momentu zainicjowania przez użytkownika interakcji do czasu, w którym przeglądarka wyrenderuje kolejną klatkę, która pokazuje wynik działania użytkownika.

Dane INP są możliwe dzięki interfejsowi Event Timing API. Ten interfejs API udostępnia różne sygnatury czasowe występujące w trakcie cyklu życia zdarzenia, w tym:

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

Poniższy przykład pokazuje, jak korzystać z tych wartości do tworzenia pomiarów niestandardowych:

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

Interfejs Resource Timing API zapewnia programistom szczegółowe informacje o tym, jak zostały wczytane zasoby konkretnej strony. Mimo nazwy interfejsu API zawarte w nim informacje nie ograniczają się tylko do danych o czasie (a jest tego mnóstwo). Inne dane, do których masz dostęp:

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

Możesz użyć właściwości transferSize wpisów o czasie zasobów, aby mierzyć wskaźnik współczynnika trafień w pamięci podręcznej lub wskaźnik 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.

W poniższym przykładzie rejestrowane są wszystkie zasoby żądane przez stronę i wskazuje, czy poszczególne zasoby zostały uzupełnione przez pamięć podręczną.

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

Obsługa przeglądarek

  • 57
  • 12
  • 58
  • 15

Źródło

Interfejs Navigation Timing API jest podobny do interfejsu Resource Timing API, ale zgłasza tylko żądania nawigacji. Typ wpisu navigation jest też podobny do typu wpisu resource, ale zawiera dodatkowe informacje dotyczące tylko żądań nawigacji (np. kiedy są uruchamiane zdarzenia DOMContentLoaded i load).

Jednym ze wskaźników używanych przez wielu deweloperów do analizowania czasu odpowiedzi serwera (Time to First Byte (TTFB)) jest dostępny przez Navigation Timing API – w szczególności jest to sygnatura czasowa responseStart wpisu.

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

Innym wskaźnikiem, który mogą być ważne dla deweloperów korzystających z skryptu service worker, jest czas uruchamiania tego skryptu w przypadku żądań nawigacji. Jest to czas potrzebny przeglądarce na uruchomienie wątku skryptu service worker, zanim zacznie przechwytywać zdarzenia pobierania.

Czas uruchomienia skryptu service worker dla określonego żądania nawigacji można określić na podstawie różnicy między entry.responseStart a entry.workerStart.

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

Interfejs Server Timing API umożliwia przekazywanie z serwera do przeglądarki danych o czasie związanych z żądaniem w nagłówkach odpowiedzi. Możesz na przykład określić, ile czasu zajęło wyszukiwanie danych w bazie danych dla określonego żądania. Może to być przydatne podczas debugowania problemów z wydajnością spowodowanych spowolnieniem serwera.

W przypadku deweloperów korzystających z usług zewnętrznych dostawców rozwiązań analitycznych interfejs Server Timing API to jedyny sposób skorelowania danych o wydajności serwera z innymi wskaźnikami biznesowymi, które mogą mierzyć te narzędzia analityczne.

Aby określić w odpowiedziach dane o czasie działania serwera, możesz użyć 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 ze swoich stron możesz odczytywać te dane we wpisach resource lub navigation z interfejsów API Resource Timing i Navigation Timing.

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