Poznaj podstawy korzystania z interfejsów Navigation API i Resource Timing API do oceny skuteczności wczytywania w terenie.
Opublikowano: 8 października 2021 r.
Jeśli do oceny wydajności wczytywania używasz ograniczania przepustowości połączenia w panelu sieciowym w narzędziach deweloperskich przeglądarki (lub Lighthouse w Chrome), wiesz, jak przydatne są te narzędzia do dostrajania wydajności. Dzięki stałej i stabilnej podstawowej szybkości połączenia możesz szybko mierzyć wpływ optymalizacji wydajności. Jedynym problemem jest to, że są to testy syntetyczne, które dostarczają danych laboratoryjnych, a nie danych z terenu.
Testy syntetyczne nie są z założenia złe, ale nie odzwierciedlają tego, jak szybko Twoja witryna wczytuje się u rzeczywistych użytkowników. Wymaga to danych z terenu, które możesz zbierać za pomocą interfejsów API Navigation Timing i Resource Timing.
Interfejsy API, które pomogą Ci ocenić wydajność wczytywania w terenie
Navigation Timing i Resource Timing to 2 podobne interfejsy API, które w dużym stopniu się pokrywają, ale mierzą 2 różne rzeczy:
- Navigation Timing mierzy szybkość żądań dokumentów HTML (czyli żądań nawigacyjnych).
- Pomiar czasu wczytywania zasobów mierzy szybkość żądań dotyczących zasobów zależnych od dokumentu, takich jak CSS, JavaScript, obrazy i inne typy zasobów.
Te interfejsy API udostępniają dane w buforze wpisów dotyczących skuteczności, do którego można uzyskać dostęp w przeglądarce za pomocą JavaScriptu. Bufor wydajności można sprawdzać na wiele sposobów, ale najczęściej używa się do tego performance.getEntriesByType:
// Get Navigation Timing entries:
performance.getEntriesByType('navigation');
// Get Resource Timing entries:
performance.getEntriesByType('resource');
performance.getEntriesByType akceptuje ciąg znaków opisujący typ wpisów, które chcesz pobrać z bufora wpisów dotyczących skuteczności. 'navigation' i 'resource' pobierają odpowiednio czasy interfejsów Navigation Timing API i Resource Timing API.
Ilość informacji dostarczanych przez te interfejsy API może być przytłaczająca, ale są one kluczem do pomiaru wydajności wczytywania w terenie, ponieważ możesz zbierać te dane o czasie od użytkowników odwiedzających Twoją witrynę.
Cykl życia i czas trwania żądania sieciowego
Zbieranie i analizowanie czasu nawigacji i zasobów przypomina archeologię, ponieważ odtwarzasz ulotne życie żądania sieciowego po fakcie. Czasami warto zwizualizować sobie pewne koncepcje. W przypadku żądań sieciowych mogą Ci w tym pomóc narzędzia deweloperskie przeglądarki.
Żądanie sieciowe ma różne fazy, takie jak wyszukiwanie DNS, nawiązywanie połączenia, negocjowanie protokołu TLS i inne źródła opóźnień. Te czasy są przedstawiane jako DOMHighResTimestamp. W zależności od przeglądarki dokładność pomiarów może sięgać mikrosekund lub być zaokrąglana do milisekund. Warto szczegółowo przeanalizować te fazy i ich związek z interfejsami Navigation Timing i Resource Timing.
wyszukiwanie DNS
Gdy użytkownik przechodzi do adresu URL, system nazw domenowych (DNS) jest pytany o przetłumaczenie domeny na adres IP. Ten proces może zająć sporo czasu, który warto zmierzyć w terenie. Interfejsy Navigation Timing i Resource Timing udostępniają 2 rodzaje czasu związane z DNS:
domainLookupStartto moment rozpoczęcia wyszukiwania DNS.domainLookupEndto moment zakończenia 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;
Negocjowanie połączenia
Kolejnym czynnikiem wpływającym na szybkość wczytywania jest negocjacja połączenia, czyli opóźnienie występujące podczas łączenia się z serwerem WWW. Jeśli używasz protokołu HTTPS, ten proces obejmuje też czas negocjacji TLS. Faza połączenia obejmuje 3 rodzaje pomiarów czasu:
connectStartto moment, w którym przeglądarka zaczyna otwierać połączenie z serwerem WWW.secureConnectionStartoznacza moment, w którym klient rozpoczyna negocjacje TLS.connectEndoznacza, że połączenie z serwerem WWW zostało nawiązane.
Pomiar całkowitego czasu połączenia jest podobny do pomiaru całkowitego czasu wyszukiwania DNS: od czasu zakończenia odejmuje się czas rozpoczęcia. Istnieje jednak dodatkowa właściwość secureConnectionStart, która może mieć wartość 0, jeśli nie jest używany protokół HTTPS, lub jeśli połączenie jest trwałe. Jeśli chcesz zmierzyć 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 negocjacji połączenia zaczynają się czasy związane z pobieraniem dokumentów i ich zasobów zależnych.
Żądania i odpowiedzi
Na wydajność wczytywania wpływają 2 rodzaje czynników:
- Czynniki zewnętrzne: np. opóźnienie i przepustowość. Poza wyborem firmy hostingowej i ewentualnie sieci CDN nie mamy na nie (w większości przypadków) wpływu, ponieważ użytkownicy mogą uzyskać dostęp do internetu z dowolnego miejsca.
- Czynniki wewnętrzne: są to m.in. architektury po stronie serwera i po stronie klienta, a także rozmiar zasobów i nasza zdolność do ich optymalizacji, na które mamy wpływ.
Oba rodzaje czynników wpływają na wydajność wczytywania. Czasy związane z tymi czynnikami są bardzo ważne, ponieważ opisują, ile czasu zajmuje pobieranie zasobów. Zarówno interfejs Navigation Timing, jak i Resource Timing opisują szybkość wczytywania strony za pomocą tych danych:
fetchStartoznacza moment, w którym przeglądarka zaczyna pobierać zasób (Resource Timing) lub dokument na potrzeby żądania nawigacji (Navigation Timing). Dzieje się to przed faktycznym żądaniem i w tym momencie przeglądarka sprawdza pamięć podręczną (np. instancje HTTP iCache).workerStartoznacza moment, w którym żądanie zaczyna być obsługiwane wfetchprocedurze obsługi zdarzeń w usłudze Service Worker. Jeśli bieżącą stroną nie steruje żaden skrypt service worker, wartość tego pola to0.requestStartto moment, w którym przeglądarka wysyła żądanie.responseStartto moment, w którym dociera pierwszy bajt odpowiedzi.responseEndto moment, w którym dociera ostatni bajt odpowiedzi.
Te czasy umożliwiają pomiar różnych aspektów wydajności ładowania, takich jak wyszukiwanie w pamięci podręcznej w ramach skryptu service worker 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ć
Interfejsy Navigation Timing i Resource Timing są przydatne w wielu sytuacjach, nie tylko w tych, które zostały opisane w poprzednich przykładach. Oto inne sytuacje z odpowiednimi czasami, które warto zbadać:
- Przekierowania stron: przekierowania są często pomijanym źródłem dodatkowego opóźnienia, zwłaszcza łańcuchy przekierowań. Opóźnienie może być spowodowane różnymi czynnikami, np. przekierowaniami HTTP do HTTPS czy przekierowaniami 302 lub 301, które nie są zapisane w pamięci podręcznej. Czasy
redirectStart,redirectEndiredirectCountpomagają ocenić opóźnienie przekierowania. - Zwalnianie dokumentu: na stronach, które uruchamiają kod w
unloadprocedurze obsługi zdarzeń, przeglądarka musi wykonać ten kod, zanim będzie mogła przejść do następnej strony.unloadEventStartiunloadEventEndmierzą rozładunek dokumentów. - Przetwarzanie dokumentów: czas przetwarzania dokumentów może nie mieć znaczenia, chyba że Twoja witryna wysyła bardzo duże ładunki HTML. Jeśli tak jest w Twoim przypadku, mogą Cię zainteresować czasy
domInteractive,domContentLoadedEventStart,domContentLoadedEventEndidomComplete.
Jak uzyskać czasy w kodzie
Wszystkie dotychczasowe przykłady używają performance.getEntriesByType, ale istnieją inne sposoby wysyłania zapytań do bufora wpisów dotyczących wydajności, np. performance.getEntriesByName i performance.getEntries. Te metody są odpowiednie, gdy potrzebna jest tylko podstawowa analiza. W innych sytuacjach mogą jednak powodować nadmierne obciążenie wątku głównego, iterując po dużej liczbie wpisów lub nawet wielokrotnie odpytując bufor wydajności w celu znalezienia nowych wpisów.
Zalecamy zbieranie wpisów z bufora wpisów dotyczących wydajności za pomocą PerformanceObserver. PerformanceObserver nasłuchuje wpisów dotyczących wydajności i udostępnia je w miarę dodawania 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ę niewygodna w porównaniu z bezpośrednim dostępem do bufora wpisów dotyczących wydajności, ale jest lepsza niż zajmowanie głównego wątku pracą, która nie służy krytycznym celom i nie jest widoczna dla użytkownika.
Jak zadzwonić do domu
Po zebraniu wszystkich potrzebnych danych o czasie 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 sposób nieblokujący, a żądanie jest umieszczane w kolejce w taki sposób, aby w razie potrzeby przetrwać bieżącą sesję strony:
// 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 dotrze w POST ładunku, który możesz w razie potrzeby zdekodować, przetworzyć i zapisać w backendzie aplikacji.
Podsumowanie
Po zebraniu danych musisz samodzielnie określić, jak je analizować. Podczas analizowania danych z pola należy przestrzegać kilku ogólnych zasad, aby mieć pewność, że wyciągasz trafne wnioski:
- Unikaj średnich, ponieważ nie odzwierciedlają one wrażeń żadnego użytkownika i mogą być zniekształcone przez wartości odstające.
- Polegaj na percentylach. W przypadku zbiorów danych z danymi o skuteczności opartymi na czasie niższe wartości są lepsze. Oznacza to, że gdy priorytetowo traktujesz niskie wartości procentowe, zwracasz uwagę tylko na najszybsze działania.
- Nadaj priorytet długiemu ogonowi wartości. Gdy priorytetem są wartości na poziomie 75 percentyla lub wyższym, skupiasz się na tym, co najważniejsze, czyli na najwolniejszych działaniach.
Ten przewodnik nie jest wyczerpującym źródłem informacji o nawigacji ani o czasie wczytywania zasobów, ale stanowi punkt początkowy. Oto dodatkowe materiały, które mogą Ci się przydać:
- Specyfikacja Navigation Timing.
- Specyfikacja Resource Timing
- ResourceTiming w praktyce
- Navigation Timing API (MDN)
- Resource Timing API (MDN)
Dzięki tym interfejsom API i dostarczanym przez nie danym będziesz mieć lepsze możliwości zrozumienia, jak rzeczywistym użytkownikom wydaje się wydajność ładowania. Zyskasz też większą pewność w diagnozowaniu i rozwiązywaniu problemów z wydajnością ładowania w terenie.