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.
Ż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 iCache
).workerStart
oznacza moment, w którym żądanie zaczyna być obsługiwane w obiekciefetch
obsługującym zdarzenie. 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 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
,redirectEnd
iredirectCount
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.unloadEventStart
iunloadEventEnd
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
,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ą 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ć:
- Navigation Timing Spec.
- Specyfikacja pomiaru czasu zasobów.
- Czas korzystania z zasobów w praktyce
- Navigation Timing API (MDN)
- Interfejs Resource Timing API (MDN)
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.