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.
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 iCache
).workerStart
oznacza, kiedy rozpoczyna się obsługa żądania w ramach modułu obsługi zdarzeńfetch
skryptu service worker. Będzie to0
, 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
,redirectEnd
iredirectCount
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ę. MetodyunloadEventStart
iunloadEventEnd
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
idomComplete
.
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ć:
- Specyfikacja czasu nawigacji.
- Specyfikacja pomiaru czasu zasobów.
- ResourceTiming w praktyce.
- Navigation Timing API (MDN)
- Interfejs Resource Timing API (MDN)
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.