Ocena szybkości wczytywania w polu za pomocą tagów czasu nawigacji i zasobów (Resource Timing)

Poznaj podstawy korzystania z interfejsów API Navigation i Resource Timing do oceny szybkości ładowania w tym polu.

Jeśli ograniczanie przepustowości sieci jest używane w panelu sieci w narzędziach dla programistów w przeglądarce (lub w Lighthouse w Chrome), aby ocenić szybkość wczytywania, wiesz, jak wygodne są te narzędzia do dostrajania wydajności. Możesz szybko zmierzyć wpływ optymalizacji wydajności, korzystając ze stałej i stabilnej podstawowej szybkości połączenia. Jedyny problem polega na tym, że są to testy syntetyczne, które generują dane laboratoryjne, a nie dane zgromadzone.

Testy syntetyczne nie są z natury złe, ale nie pokazują, jak szybko wczytuje się Twoja witryna. Wymaga to danych pól, które możesz zebrać z interfejsów Navigation Timing i Resource Timing API.

Interfejsy API pomocne w ocenie wydajności wczytywania w terenie

Czas nawigacji i czas zasobów to 2 podobne interfejsy API, które w znacznym stopniu pokrywają się z pomiarami 2 odmiennych aspektów:

  • Nawigacja Timing służy do pomiaru szybkości wysyłania żądań dokumentów HTML (czyli żądań nawigacji).
  • Wartość Resource Timing mierzy szybkość żądań dotyczących zasobów zależnych od dokumentu, takich jak CSS, JavaScript, obrazy itd.

Te interfejsy API udostępniają swoje dane w buforze danych o wydajności, do którego można uzyskać dostęp w przeglądarce za pomocą JavaScriptu. Istnieje wiele sposobów wysyłania zapytań do bufora wydajności na wiele sposobów. Popularnym sposobem jest użycie funkcji performance.getEntriesByType:

// Get Navigation Timing entries:
performance.getEntriesByType('navigation');

// Get Resource Timing entries:
performance.getEntriesByType('resource');

Funkcja performance.getEntriesByType akceptuje ciąg opisujący typ wpisów, które chcesz pobrać z bufora wpisów dotyczących wydajności. Parametry 'navigation' i 'resource' pobierają odpowiednio kody czasowe dla interfejsów Navigation Timing i Resource Timing API.

Ilość informacji dostarczanych przez interfejsy API może być przytłaczająca, ale to klucz do pomiaru wydajności wczytywania w terenie, ponieważ możesz zbierać dane o tym, kiedy użytkownicy odwiedzają Twoją witrynę.

Czas życia żądania sieciowego i czas jego trwania

Zbieranie i analizowanie czasu nawigacji oraz zasobów jest jak archeologia, ponieważ odtwarzasz przelotny czas życia żądania sieciowego po fakcie. Czasami pomaga zwizualizować pojęcia, a w przypadku żądań sieciowych pomocne mogą być narzędzia dla programistów w przeglądarce.

Schemat działania sieci widoczny w Narzędziach deweloperskich w Chrome. Przedstawione czasy odnoszą się do kolejki żądań, negocjowania połączenia, samego żądania oraz odpowiedzi na kolorowych paskach.
Wizualizacja żądania sieciowego w panelu sieci Narzędzi deweloperskich w Chrome

Żywotność żądania sieciowego składa się z różnych etapów, takich jak wyszukiwanie DNS, nawiązywanie połączenia, negocjacje TLS itd. Są one oznaczone jako DOMHighResTimestamp. W zależności od przeglądarki dokładność licznika czasu może być mniejsza do mikrosekundy lub zaokrąglona do milisekund. Przyjrzyjmy się szczegółowo tym etapom i ich związek z czasem nawigacji i czasem wykorzystania zasobów.

wyszukiwanie DNS

Gdy użytkownik otwiera adres URL, do systemu nazw domenowych (DNS) wysyłane jest zapytanie w celu przetłumaczenia domeny na adres IP. Ten proces może zająć dużo czasu, a nawet mierzyć czas w terenie. Czasy nawigacji i czas zasobu ujawniają 2 czasy związane z DNS:

  • domainLookupStart rozpoczyna wyszukiwanie DNS.
  • domainLookupEnd oznacza zakończenie wyszukiwania DNS.

Łączny czas wyszukiwania DNS można obliczyć, odejmując wskaźnik początkowy od wskaźnika końcowego:

// Measuring DNS lookup time
const [pageNav] = performance.getEntriesByType('navigation');
const totalLookupTime = pageNav.domainLookupEnd - pageNav.domainLookupStart;

Negocjowanie połączenia

Kolejnym czynnikiem wpływającym na wydajność wczytywania jest negocjowanie połączenia, czyli czas oczekiwania na połączenie z serwerem WWW. Jeśli korzystasz z protokołu HTTPS, proces ten obejmuje też czas negocjacji TLS. Faza połączenia obejmuje 3 czasy:

  • connectStart – gdy przeglądarka zaczyna nawiązywać połączenie z serwerem WWW.
  • secureConnectionStart oznacza rozpoczęcie przez klienta negocjacji TLS.
  • connectEnd oznacza nawiązanie połączenia z serwerem WWW.

Pomiar łącznego czasu połączenia jest podobny do pomiaru łącznego czasu wyszukiwania DNS: czas rozpoczęcia jest odejmowany od czasu zakończenia. Istnieje jednak dodatkowa właściwość secureConnectionStart, która może mieć wartość 0, jeśli nie jest używany protokół HTTPS lub połączenie jest trwałe. Jeśli chcesz mierzyć czas negocjacji protokołu TLS, pamiętaj o tym:

// Quantifying total connection time
const [pageNav] = performance.getEntriesByType('navigation');
const connectionTime = pageNav.connectEnd - pageNav.connectStart;
let tlsTime = 0; // <-- Assume 0 to start with

// Was there TLS negotiation?
if (pageNav.secureConnectionStart > 0) {
  // Awesome! Calculate it!
  tlsTime = pageNav.connectEnd - pageNav.secureConnectionStart;
}

Po zakończeniu wyszukiwania DNS i negocjowania połączenia zaczną obowiązywać czasy pobierania dokumentów i ich zasobów zależnych.

Żądania i odpowiedzi

Na szybkość wczytywania wpływają 2 rodzaje czynników:

  • Czynniki zewnętrzne: opóźnienie i przepustowość. Poza wyborem firmy hostingowej i sieci CDN w większości przypadków nie jesteśmy pod naszą kontrolą, ponieważ użytkownicy mogą korzystać z internetu z dowolnego miejsca.
  • Czynniki wewnętrzne: są to np. architektury po stronie serwera i klienta, a także rozmiar zasobów i możliwość optymalizacji pod ich kątem, na które mamy wpływ.

Na wydajność wczytywania wpływają oba rodzaje czynników. Tempo związane z tymi czynnikami jest kluczowe, ponieważ informuje, jak długo trwa pobieranie zasobów. Zarówno czas nawigacji, jak i czas zasobów opisują wydajność wczytywania za pomocą tych danych:

  • fetchStart oznacza moment, w którym przeglądarka rozpoczyna pobieranie zasobu (Resource Timing) lub dokumentu dla żądania nawigacji (Navigation Timing). Wyprzedza to rzeczywiste żądanie i jest momentem, w którym przeglądarka sprawdza pamięci podręczne (np. wystąpienia HTTP i Cache).
  • workerStart oznacza moment rozpoczęcia obsługi żądania w module obsługi zdarzeń fetch skryptu service worker. To będzie 0, gdy żaden skrypt service worker nie kontroluje bieżącej strony.
  • requestStart oznacza, że przeglądarka wysyła żądanie.
  • responseStart oznacza moment, gdy pojawia się pierwszy bajt odpowiedzi.
  • responseEnd oznacza moment, gdy pojawia się ostatni bajt odpowiedzi.

Takie czasy umożliwiają pomiar wielu aspektów wydajności wczytywania, takich jak wyszukiwanie pamięci podręcznej w skrypcie service worker oraz czas pobierania:

// Cache seek plus response time of the current document
const [pageNav] = performance.getEntriesByType('navigation');
const fetchTime = pageNav.responseEnd - pageNav.fetchStart;

// Service worker time plus response time
let workerTime = 0;

if (pageNav.workerStart > 0) {
  workerTime = pageNav.responseEnd - pageNav.workerStart;
}

Możesz też mierzyć inne aspekty opóźnienia żądania/odpowiedzi:

const [pageNav] = performance.getEntriesByType('navigation');

// Request time only (excluding redirects, DNS, and connection/TLS time)
const requestTime = pageNav.responseStart - pageNav.requestStart;

// Response time only (download)
const responseTime = pageNav.responseEnd - pageNav.responseStart;

// Request + response time
const requestResponseTime = pageNav.responseEnd - pageNav.requestStart;

Inne dostępne pomiary

Czasy nawigacji i czas zasobów są przydatne bardziej w przypadku innych zastosowań niż przedstawiliśmy w powyższych przykładach. Oto kilka innych sytuacji, w których warto wziąć pod uwagę takie czynniki:

  • Przekierowania stron: przekierowania są pomijanym źródłem dodatkowego opóźnienia, zwłaszcza łańcuchów przekierowań. Czas oczekiwania jest zwiększany na różne sposoby, np. podczas przeskoków HTTP do HTTP oraz przekierowań 302 i 301 z pamięci podręcznej. Czasy redirectStart, redirectEnd i redirectCount przydają się przy ocenie czasu oczekiwania na przekierowanie.
  • Wyładowywanie dokumentu: na stronach, na których jest uruchamiany kod w module obsługi zdarzeń unload, przeglądarka musi wykonać ten kod, zanim przejdzie do następnej strony. unloadEventStart i unloadEventEnd – pomiar rozładowywania dokumentu.
  • Przetwarzanie dokumentu: czas przetwarzania dokumentu może nie być wtórny, chyba że witryna wysyła bardzo duże ładunki HTML. Jeśli tak jest w Twojej sytuacji, sprawdź harmonogramy domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd i domComplete.

Uzyskiwanie kodów czasowych w kodzie aplikacji

Wszystkie wyświetlone do tej pory przykłady używają parametru performance.getEntriesByType, ale istnieją też inne sposoby wysyłania zapytań do bufora wpisów dotyczących wydajności, np. performance.getEntriesByName i performance.getEntries. Te metody sprawdzają się, gdy potrzebna jest tylko lekka analiza. W innych sytuacjach mogą jednak wprowadzić nadmierną ilość pracy w wątku głównym przez powtarzanie dużej liczby wpisów lub nawet wielokrotne odpytywanie bufora wydajności w celu znalezienia nowych wpisów.

Zalecanym sposobem gromadzenia wpisów z bufora wpisów dotyczących wydajności jest użycie komponentu PerformanceObserver. PerformanceObserver wykrywa wpisy dotyczące skuteczności i wyświetla je w miarę dodawania ich do bufora:

// Create the performance observer:
const perfObserver = new PerformanceObserver((observedEntries) => {
  // Get all resource entries collected so far:
  const entries = observedEntries.getEntries();

  // Iterate over entries:
  for (let i = 0; i < entries.length; i++) {
    // Do the work!
  }
});

// Run the observer for Navigation Timing entries:
perfObserver.observe({
  type: 'navigation',
  buffered: true
});

// Run the observer for Resource Timing entries:
perfObserver.observe({
  type: 'resource',
  buffered: true
});

Ta metoda zbierania danych o czasie może wydawać się dziwna w porównaniu z bezpośrednim dostępem do bufora danych dotyczących wydajności, ale lepiej wiązać wątek główny z pracą, która nie służy celowi krytycznemu ani widocznemu dla użytkownika.

Dzwonię pod numer domowy

Po zebraniu wszystkich potrzebnych danych o czasie możesz je wysłać do punktu końcowego w celu dalszej analizy. Możesz to zrobić na 2 sposoby: użyj navigator.sendBeacon lub fetch z ustawioną opcją keepalive. Obie metody wysyłają żądanie do określonego punktu końcowego w nieblokujący sposób. W razie potrzeby żądanie zostanie umieszczone w kolejce tak, aby przetrwać czas trwania sesji strony:

// Caution: If you have lots of performance entries, don't
// do this. This is an example for illustrative purposes.
const data = JSON.stringify(performance.getEntries()));

// The endpoint to transmit the encoded data to
const endpoint = '/analytics';

// Check for fetch keepalive support
if ('keepalive' in Request.prototype) {
  fetch(endpoint, {
    method: 'POST',
    body: data,
    keepalive: true,
    headers: {
      'Content-Type': 'application/json'
    }
  });
} else if ('sendBeacon' in navigator) {
  // Use sendBeacon as a fallback
  navigator.sendBeacon(endpoint, data);
}

W tym przykładzie ciąg JSON trafi do ładunku POST, który możesz w razie potrzeby zdekodować i przetworzyć/przechowywać w backendzie aplikacji.

Podsumowanie

Gdy już zbierzesz wskaźniki, to Ty decydujesz, jak je analizować. Podczas analizy danych pamiętaj o kilku ogólnych zasadach, które pomogą Ci wyciągnąć konkretne wnioski:

  • Unikaj średnich wartości, ponieważ nie są one reprezentatywne dla wszystkich użytkowników, a ich wartości mogą być zniekształcone.
  • Zdaj się na percentyle. W zbiorach danych z danymi o skuteczności na podstawie czasu im niższa, tym lepsza. Oznacza to, że gdy priorytetowo traktujesz niskie centyle, zwracasz uwagę tylko na najszybsze działanie.
  • Nadaj priorytet długim ogonom wartości. Gdy priorytetowo traktujesz działania na poziomie 75 centyla lub wyższym, kładziesz nacisk na najwolniejsze działania.

Ten przewodnik nie jest wyczerpującym źródłem informacji na temat nawigacji ani czasu zasobów, ale stanowi punkt wyjścia. Poniżej znajdziesz dodatkowe materiały, które mogą Ci się przydać:

Te interfejsy API i dostarczane przez nie dane pozwolą Ci lepiej zrozumieć, jak z powodu wydajności ładowania korzystają użytkownicy, co z większym prawdopodobieństwem pozwoli Ci diagnozować i rozwiązywać w tej dziedzinie problemy z wydajnością wczytywania.