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 konsekwentnej szybkości połączenia bazowego możesz szybko mierzyć wpływ optymalizacji wydajności. Jedynym problemem jest to, że jest to testowanie syntetyczne, które dostarcza danych laboratoryjnych, a nie danych z pola.

Testy syntetyczne nie są z natury złe, ale nie pokazują, jak szybko Twoja witryna wczytuje się u użytkowników. Wymaga to danych polowych, które możesz zbierać z interfejsów API Czas nawigacji i Czas zasobów.

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

Czas nawigacji i czas dostępu do zasobu to 2 podobne interfejsy API, które się znacznie pokrywają i mierzą 2 różne rzeczy:

  • 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. Istnieje wiele sposobów wysyłania zapytań do bufora wydajności, ale najpopularniejszym z nich jest użycie 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 mają kluczowe znaczenie w pomiarze wydajności wczytywania w terenie, ponieważ zbierasz te dane o czasach, gdy użytkownicy odwiedzają Twoją witrynę.

Czas trwania i czas realizacji żądania sieciowego

Zbieranie i analizowanie czasu korzystania z nawigacji i zasobów jest trochę jak archeologia, ponieważ polega na odtwarzaniu ulotnego życia żądania sieci 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

Żywotność żądania sieciowego ma odrębne fazy, takie jak wyszukiwanie DNS, nawiązywanie połączenia, uzgadnianie TLS i inne źródła opóźnień. Są one wyświetlane 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. Trzeba dokładnie przyjrzeć się tym etapom i ich związku z czasem nawigacji i czasem zasobów.

wyszukiwanie DNS

Gdy użytkownik kliknie adres URL, system nazw domenowych (DNS) przekształca domenę w adres IP. Ten proces może zająć dużo czasu, który warto wykorzystać na pomiary w terenie. Czas ładowania strony i czas ładowania zasobów podają 2 wartości związane z DNS:

  • domainLookupStart to moment rozpoczęcia wyszukiwania DNS.
  • domainLookupEnd to koniec wyszukiwania DNS.

Aby obliczyć łączny czas wyszukiwania DNS, odejmij wartość początkową od wartości końcowej:

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

Negocjacje dotyczące połączenia

Kolejnym czynnikiem, który wpływa na wydajność wczytywania, są negocjowanie połączenia, które wiąże się z opóźnieniem podczas nawiązywania połączenia z serwerem WWW. Jeśli jest używany protokół HTTPS, proces ten obejmuje też czas negocjacji TLS. Faza połączenia składa się z 3 elementów:

  • connectStart to moment, w którym przeglądarka rozpoczyna nawiązywanie połączenia z serwerem WWW.
  • secureConnectionStart oznacza moment rozpoczęcia negocjacji TLS przez klienta.
  • 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: opóźnienia i przepustowość. Oprócz wyboru firmy hostingowej i możliwie sieci CDN nie mamy (w większości) kontroli nad tymi elementami, ponieważ użytkownicy mogą uzyskiwać dostęp do 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. Zarówno interfejs Navigation Timing, jak i interfejs Resources Timing opisują szybkość 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). Przed faktycznym żądaniem przeglądarka sprawdza pamięci podręczne (np. instancje HTTP i Cache).
  • workerStart oznacza moment, w którym żądanie zaczyna być obsługiwane w obiekcie fetch obsługującym zdarzenie. 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 pomiary, które możesz wykonać

Czasy nawigacji i czasy zasobów są przydatne nie tylko w przypadkach opisanych w poprzednich przykładach. Oto kilka innych sytuacji z odpowiednimi ustawieniami czasu, które warto sprawdzić:

  • Przekierowania stron: przekierowania są często pomijanym źródłem opóźnień, zwłaszcza łańcuchy przekierowań. Czas oczekiwania jest zwiększany na wiele sposobów, np. przez przejścia z HTTP na HTTPs oraz przez przekierowania 302/301 bez pamięci podręcznej. Czasy redirectStart, redirectEndredirectCount są przydatne do oceny opóźnienia przekierowania.
  • Wyładowywanie dokumentu: na stronach, które uruchamiają kod w unload obiekcie obsługującym zdarzenie, przeglądarka musi wykonać ten kod, zanim przejdzie do następnej strony. unloadEventStartunloadEventEnd mierzą rozładowanie dokumentu.
  • 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 to Twój przypadek, mogą Cię zainteresować informacje o czasach domInteractive, domContentLoadedEventStart, domContentLoadedEventEnddomComplete.

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ą odpowiednie, gdy potrzebna jest tylko lekka analiza. W innych sytuacjach mogą one jednak powodować nadmierne obciążenie wątku głównego przez iterację nad dużą liczbą wpisów lub nawet wielokrotne sprawdzanie bufora wydajności w celu znalezienia nowych wpisów.

Zalecane podejście do zbierania wpisów z bufora wpisów dotyczących skuteczności polega na użyciu PerformanceObserver. PerformanceObserver nasłuchuje wpisów dotyczących wydajności i przekazuje je, gdy są dodawane 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 gromadzenia danych czasowych może wydawać się nieco niezręczna w porównaniu z bezpośrednim dostępem do bufora wejścia na wydajność, ale lepiej jest powiązać wątek główny z pracą, która nie ma znaczenia krytycznego i 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żesz to zrobić na 2 sposoby: za pomocą 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 zostanie dostarczony w ładunku POST, który możesz zdekodować, przetworzyć i zapisać w backendzie aplikacji.

Podsumowanie

Po zebraniu danych należy zastanowić się, jak je analizować. Podczas analizy danych pól należy przestrzegać kilku ogólnych zasad, aby mieć pewność, że wyciągasz miarodajne wnioski:

  • Unikaj średnich wartości, ponieważ nie są one reprezentatywne dla żadnego użytkownika i mogą być zafałszowane przez wartości odstające.
  • Używaj percentyli. W zbiorach danych z danymi o skuteczności opartymi na czasie im mniej, 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 priorytetyzujesz doświadczenia na 75. centylu lub wyższym, koncentrujesz się tam, gdzie jest to możliwe: na najwolniejszych momentach.

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

Dzięki tym interfejsom API i udostępnionym przez nie danym lepiej zrozumiesz, jak prawdziwi użytkownicy postrzegają szybkość wczytywania, co pozwoli Ci skuteczniej diagnozować i rozwiązywać problemy z wydajnością wczytywania w terenie.