Jak ocenić wydajność wczytywania w terenie za pomocą interfejsów Navigation Timing i Resource Timing

Poznaj podstawy korzystania z interfejsów API dotyczącego nawigacji i czasu ładowania zasobów, aby ocenić wydajność wczytywania w warunkach rzeczywistych.

Opublikowano: 8 października 2021 r.

Jeśli zdarzyło Ci się stosować ograniczanie połączeń w panelu sieci w narzędziach dla programistów przeglądarki (lub Lighthouse w Chrome) do oceny wydajności wczytywania, wiesz, jak bardzo te narzędzia pomagają w dostosowywaniu wydajności. Dzięki stabilnej i ciągłej szybkości połączenia możesz szybko mierzyć wpływ optymalizacji wydajności. Jedynym problemem jest to, że są to testy syntetyczne, które generują dane laboratoryjne, a nie dane z terenu.

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

Interfejsy API ułatwiające ocenę wydajności wczytywania w warunkach rzeczywistych

Czas nawigacji i czas zasobów to 2 podobne interfejsy API, które się znacząco pokrywają i mierzą 2 różne rzeczy:

  • Navigation Timing (Czas nawigacji) mierzy szybkość żądań dokumentów HTML (czyli żądań nawigacji).
  • Czas wczytywania zasobów mierzy szybkość żądań zasobów zależnych od dokumentu, takich jak CSS, JavaScript, obrazy i inne typy zasobów.

Te interfejsy API udostępniają dane w buforze danych o skuteczności, do którego można uzyskać dostęp w przeglądarce za pomocą JavaScriptu. Zapytanie o bufor wydajności można przekazać na kilka sposobów, ale najczęściej jest to performance.getEntriesByType:

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

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

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

Ilość informacji dostarczanych przez te interfejsy API może być przytłaczająca, ale są one kluczowe do pomiaru wydajności wczytywania w polu, ponieważ możesz zbierać te czasy od użytkowników, którzy odwiedzają Twoją witrynę.

Czas trwania żądania sieciowego i czasu trwania

Gromadzenie i analizowanie informacji o czasie nawigacji i czasu zasobów działa w rodzaju archeologii – rekonstruowanie krótkiego czasu trwania żądania sieciowego po fakcie. Czasami warto wizualizować pojęcia, a w przypadku żądań sieciowych przydatne mogą być narzędzia programistyczne w przeglądarce.

Czasy dostępu do sieci widoczne w Narzędziach deweloperskich w Chrome. Kolorowe paski wskazują czasy oczekiwania na kolejkowanie żądania, negocjację połączenia, samo żądanie i odpowiedź.
Wizualizacja żądania sieciowego w panelu sieci w Narzędziach deweloperskich w Chrome

Czas życia żądania sieciowego składa się z różnych faz, takich jak wyszukiwanie DNS, nawiązywanie połączenia, negocjacje TLS i inne źródła opóźnień. Te wartości są reprezentowane jako DOMHighResTimestamp. W zależności od przeglądarki dokładność sygnatur czasowych może być z dokładnością do mikrosekundy lub zaokrąglona do milisekund. Warto dokładnie przeanalizować te fazy i ich związek z czasem nawigacji i czasem zasobów.

wyszukiwanie DNS

Gdy użytkownik otwiera adres URL, wysyłane jest do systemu nazw domenowych (DNS) żądanie przetłumaczenia domeny na adres IP. Ten proces może zająć dużo czasu – możesz nawet mierzyć czas w terenie. Sekcje Navigation Timing i Resource Timing udostępniają dwa czasy związane z DNS:

  • domainLookupStart to moment rozpoczęcia wyszukiwania DNS.
  • domainLookupEnd oznacza zakończenie wyszukiwania DNS.

Łączny czas wyszukiwania DNS można obliczyć, odejmując wartość początkową od danych końcowych:

// 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 negocjacja połączenia, czyli opóźnienie występujące podczas łączenia się z serwerem internetowym. Jeśli jest używany protokół HTTPS, proces ten obejmuje też czas negocjacji TLS. Faza połączenia składa się z 3 czasów:

  • connectStart to moment, w którym przeglądarka otwiera połączenie z serwerem internetowym.
  • secureConnectionStart oznacza moment, w którym klient rozpoczyna negocjacje TLS.
  • connectEnd oznacza, że połączenie z serwerem WWW zostało nawiązane.

Pomiar łącznego czasu połączenia jest podobny do pomiaru łącznego czasu wyszukiwania DNS: odejmujesz czas rozpoczęcia od czasu zakończenia. Dostępna jest 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 TLS, pamiętaj o tych kwestiach:

// 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 zaczynają obowiązywać czasy pobierania dokumentów i ich zależnych zasobów.

Żądania i odpowiedzi

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

  • Czynniki zewnętrzne: to m.in. czas oczekiwania i przepustowość. Oprócz wyboru firmy hostingowej i sieci CDN nie mamy takiej kontroli, ponieważ użytkownicy mogą korzystać z internetu z dowolnego miejsca.
  • Czynniki wbudowane: to takie kwestie jak architektury po stronie serwera i klienta, a także rozmiar zasobów i możliwość optymalizacji pod kątem tych elementów, na które mamy kontrolę.

Oba te czynniki wpływają na wydajność wczytywania. Czas związany z tymi czynnikami ma kluczowe znaczenie, ponieważ informuje o czasie potrzebnym na pobranie zasobów. Opcje czasu nawigacji i czasu zasobu opisują wydajność wczytywania za pomocą tych danych:

  • fetchStart oznacza moment, w którym przeglądarka zaczyna pobierać zasób (czas pobierania zasobu) lub dokument na potrzeby żądania nawigacji (czas nawigacji). Poprzedza ono rzeczywiste żądanie i jest punktem, w którym przeglądarka sprawdza pamięć podręczną (np. instancje HTTP i Cache).
  • workerStart oznacza, kiedy rozpoczyna się obsługa żądania w ramach modułu obsługi zdarzeń fetch skryptu service worker. Będzie to 0, gdy żaden skrypt service worker nie steruje bieżącą stroną.
  • requestStart to czas, w którym przeglądarka wysyła żądanie.
  • responseStart to moment, w którym dociera pierwszy bajt odpowiedzi.
  • responseEnd to czas, w którym dociera ostatni bajt odpowiedzi.

Te czasy umożliwiają pomiar wielu aspektów wydajności wczytywania, takich jak wyszukiwanie w pamięci podręcznej w ramach usługi i 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 i 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

Raporty Navigation Timing i Resource Timing są przydatne nie tylko w poprzednich przykładach. Oto kilka innych sytuacji, w których warto wziąć pod uwagę odpowiedni czas:

  • Przekierowania stron: przekierowania są często pomijanym źródłem opóźnień, zwłaszcza łańcuchy przekierowań. Czas oczekiwania jest dodawany na wiele sposobów, np. przeskoki HTTP do HTTP, a także przekierowania 302 i niebuforowane 301. Czasy redirectStart, redirectEndredirectCount są przydatne do oceny opóźnienia przekierowania.
  • Wyładowywanie dokumentów: na stronach, na których uruchamia się kod w module obsługi zdarzeń unload, przeglądarka musi wykonać ten kod, zanim przejdzie na następną stronę. Metody unloadEventStart i unloadEventEnd pozwalają na pomiar wyładowania dokumentów.
  • Przetwarzanie dokumentów: czas przetwarzania dokumentów może nie mieć znaczenia, chyba że Twoja witryna wysyła bardzo duże dane HTML. Jeśli ten opis pasuje do Twojej sytuacji, mogą Cię interesować terminy domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd i domComplete.
.

Jak uzyskać informacje o czasie w kodzie

Wszystkie przykłady pokazane do tej pory używają funkcji performance.getEntriesByType, ale można też zapytać o bufor wpisów dotyczących wydajności, korzystając na przykład z funkcji performance.getEntriesByName lub performance.getEntries. Te metody są przydatne, gdy potrzebna jest tylko analiza lekkiej. Jednak w innych sytuacjach mogą wprowadzić nadmierną ilość pracy w wątku głównym przez iterację dużej liczby wpisów, a nawet wielokrotne sondowanie bufora wydajności w celu znalezienia nowych wpisów.

Aby zbierać wpisy z bufora wpisów dotyczących wydajności, zalecamy użycie PerformanceObserver. PerformanceObserver nasłuchuje wpisów dotyczących wydajności i udostępnia 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 czasów może wydawać się niewygodna w porównaniu z bezpośrednim dostępem do bufora danych o wydajności, ale jest ona lepsza niż zajmowanie głównego wątku pracą, która nie służy do celów krytycznych ani nie jest widoczna dla użytkownika.

Jak zadzwonić na ekran główny?

Po zebraniu wszystkich potrzebnych czasów możesz wysłać je do punktu końcowego w celu dalszej analizy. Można to zrobić na 2 sposoby za pomocą narzędzia 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 w taki sposób, aby przetrwało bieżącą sesję na stronie:

// Check for navigator.sendBeacon support:
if ('sendBeacon' in navigator) {
  // 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());

  // Send the data!
  navigator.sendBeacon('/analytics', data);
}

W tym przykładzie ciąg JSON będzie docierać w postaci ładunku POST, który możesz dekodować, przetwarzać i przechowywać w backendzie aplikacji w miarę potrzeby.

Podsumowanie

Po zebraniu danych należy zastanowić się, jak je analizować. Podczas analizowania danych z pola należy przestrzegać kilku ogólnych zasad, aby wyciągać z nich wiarygodne wnioski:

  • Unikaj średnich, ponieważ nie reprezentują one żadnego wrażenia użytkownika i mogą być zniekształcone przez wyniki odstające.
  • Postaw na percentyle. W zbiorach danych dotyczących wydajności na podstawie czasu im niższa wartość, tym lepiej. Oznacza to, że gdy nadasz priorytet niskim wartościom percentylów, będziesz zwracać uwagę tylko na najszybsze wyniki.
  • Nadaj priorytet wartościom z długiego ogona. Gdy nadajesz priorytety sesjom o wartości równej 75-emu percentylowi lub wyższej, skupiasz się na tym, na czym trzeba: na najwolniejszych sesjach.

Ten przewodnik nie jest wyczerpującym źródłem informacji o nawigacji ani czasie zasobów, ale stanowi punkt wyjścia. Oto dodatkowe materiały, które mogą Ci się przydać:

Dzięki tym interfejsom API i dostępnym w nich danym łatwiej będzie Ci zrozumieć, jak wczytywanie działa w przypadku prawdziwych użytkowników. Pozwoli Ci to pewniej diagnozować i rozwiązywać problemy z wczytywaniem w praktyce.