Dane dotyczące użytkowników, które możesz mierzyć w dowolnej witrynie, są bardzo przydatne. Te dane umożliwiają:
- poznawać wrażenia użytkowników dotyczące Internetu jako całości;
- Porównaj swoją witrynę z witryną konkurencji.
- Śledź przydatne i przydatne dane w narzędziach analitycznych bez konieczności pisania kodu niestandardowego.
Dane uniwersalne stanowią dobrą podstawę, ale w wielu przypadkach musisz mierzyć więcej niż tylko te dane, aby uzyskać pełny obraz działania konkretnej witryny.
Dane niestandardowe umożliwiają pomiar aspektów wrażeń związanych z witryną, które mogą dotyczyć tylko Twojej witryny, np.:
- Czas potrzebny aplikacji jednostronicowej (SPA) na przejście z jednej „strony” na inną.
- Czas potrzebny na wyświetlenie danych pobranych z bazy danych dla zalogowanych użytkowników.
- Czas potrzebny na hydratację aplikacji renderowanej po stronie serwera (SSR).
- Współczynnik trafień w pamięci podręcznej w przypadku zasobów wczytywanych przez powracających użytkowników.
- Czas oczekiwania na zdarzenie kliknięcia lub zdarzenia klawiatury w grze.
Interfejsy API do pomiaru danych niestandardowych
W przeszłości deweloperzy stron internetowych nie mieli wielu interfejsów API na niskim poziomie do pomiaru wydajności, dlatego musieli uciekać się do obejść, aby sprawdzić, czy witryna działa dobrze.
Można na przykład określić, czy wątek główny jest zablokowany z powodu długotrwałych zadań JavaScript, wykonując pętlę requestAnimationFrame
i oblicając różnicę między kolejnymi klatkami. Jeśli delta jest znacznie dłuższa niż częstotliwość wyświetlania klatek, możesz zgłosić to jako długie zadanie. Nie zalecamy jednak stosowania takich rozwiązań, ponieważ mogą one wpływać na wydajność (np. przez rozładowywanie baterii).
Pierwsza zasada skutecznego pomiaru wydajności polega na sprawdzeniu, czy techniki pomiaru wydajności nie powodują problemów z wydajnością. Dlatego w przypadku dowolnych danych niestandardowych, które gromadzisz w swojej witrynie, najlepiej jest używać jednego z tych interfejsów API (jeśli to możliwe).
Interfejs Performance Observer API
Interfejs Performance Observer API to mechanizm, który zbiera i wyświetla dane ze wszystkich innych interfejsów API dotyczących skuteczności omawianych na tej stronie. Zrozumienie tego procesu jest kluczowe dla uzyskania dobrych danych.
Za pomocą PerformanceObserver
możesz biernie subskrybować zdarzenia związane z wydajnością. Dzięki temu wywołania zwrotne interfejsu API są wykonywane w okresach bezczynności, co oznacza, że zwykle nie wpływają na wydajność strony.
Aby utworzyć obiekt PerformanceObserver
, prześlij do niego funkcję wywołania zwrotnego, która ma być wykonywana za każdym razem, gdy wysyłane są nowe wpisy dotyczące skuteczności. Następnie za pomocą metody observe()
określasz, jakie typy wpisów ma ona nasłuchiwać:
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
po.observe({type: 'some-entry-type'});
W następnych sekcjach wymieniono wszystkie typy wpisów, które można obserwować, ale w nowszych przeglądarkach możesz sprawdzić, jakie typy wpisów są dostępne za pomocą statycznej właściwości PerformanceObserver.supportedEntryTypes
.
Obserwowanie wpisów, które już się pojawiły
Domyślnie obiekty PerformanceObserver
mogą tylko obserwować wpisy w miarę ich pojawiania się. Może to powodować problemy, jeśli chcesz ładować kod analityki wydajności z opóźnieniem, aby nie blokował zasobów o wyższym priorytecie.
Aby uzyskać wpisy historyczne (po ich wystąpieniu), ustaw flagę buffered
na true
, gdy wywołasz funkcję observe()
. Podczas pierwszego wywołania funkcji PerformanceObserver
callback przeglądarka uwzględnia wpisy historyczne z bufora danych o wydajności, aż do maksymalnego rozmiaru bufora dla danego typu.
po.observe({
type: 'some-entry-type',
buffered: true,
});
Starszych interfejsów API dotyczących wydajności, których należy unikać
Przed wprowadzeniem interfejsu Performance Observer API deweloperzy mogli uzyskiwać dostęp do wpisów dotyczących wydajności za pomocą 3 metod zdefiniowanych w obiekcie performance
:
Te interfejsy API są nadal obsługiwane, ale ich używanie nie jest zalecane, ponieważ nie pozwalają na odsłuchiwanie nowych wpisów. Ponadto wiele nowych interfejsów API (takich jak largest-contentful-paint
) nie jest udostępnianych za pomocą obiektu performance
, tylko za pomocą obiektu PerformanceObserver
.
Jeśli nie potrzebujesz zgodności z Internet Explorerem, w kodzie unikaj tych metod i używaj w ich miejsce PerformanceObserver
.
Interfejs User Timing API
Interfejs User Timing API to ogólny interfejs API do pomiarów na podstawie czasu. Umożliwia ona dowolne oznaczanie punktów w czasie, a potem pomiar czasu między tymi punktami.
// Record the time immediately before running a task.
performance.mark('myTask:start');
await doMyTask();
// Record the time immediately after running a task.
performance.mark('myTask:end');
// Measure the delta between the start and end of the task
performance.measure('myTask', 'myTask:start', 'myTask:end');
Interfejsy API, takie jak Date.now()
czy performance.now()
, dają podobne możliwości, ale zaletą interfejsu User Timing API jest to, że dobrze integruje się z narzędziami do pomiaru skuteczności. Na przykład w Chrome DevTools dane o czasie trwania interakcji z użytkownikiem znajdziesz w panelu Performance. Wielu dostawców usług analitycznych automatycznie śledzi wszystkie pomiary i wysyła dane o czasie trwania do swojego backendu analitycznego.
Aby zgłaszać pomiary czasu użytkownika, możesz użyć rozszerzenia PerformanceObserver i zarejestrować obserwowanie wpisów typu measure
:
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `measure` entries to be dispatched.
po.observe({type: 'measure', buffered: true});
Long Tasks API
Interfejs Long Tasks API pozwala określić, kiedy wątek główny przeglądarki jest zablokowany na tyle długo, że wpływa to na częstotliwość wyświetlania klatek lub opóźnienie wprowadzania danych. Interfejs API będzie raportować wszystkie zadania, które trwają dłużej niż 50 milisekund.
Za każdym razem, gdy musisz uruchomić kosztowny kod lub wczytać i wykonywać duże skrypty, warto sprawdzić, czy kod nie blokuje wątku głównego. Właściwie wiele wskaźników na wyższym poziomie jest tworzonych na podstawie interfejsu Long Tasks API (np. czas do pełnej interaktywności (TTI) i całkowity czas blokowania (TBT)).
Aby określić, kiedy występują długie zadania, możesz użyć PerformanceObserver i zarejestrować się, aby obserwować wpisy typu longtask
:
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `longtask` entries to be dispatched.
po.observe({type: 'longtask', buffered: true});
Long Animation Frames API
Interfejs Long Animation Frames API to nowa wersja interfejsu Long Tasks API, która obsługuje długie klatki (a nie długie zadania) o długości ponad 50 ms. Rozwiązaliśmy w nim niektóre problemy z interfejsem API Long Tasks, w tym lepsze przypisywanie i szerszy zakres potencjalnie problematycznych opóźnień.
Aby określić, kiedy występują długie klatki, możesz użyć PerformanceObserver i zarejestrować się, aby obserwować wpisy typu long-animation-frame
:
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `long-animation-frame` entries to be dispatched.
po.observe({type: 'long-animation-frame', buffered: true});
Interfejs Element Timing API
Wartość największego wyrenderowania treści (LCP) pozwala określić, kiedy na ekranie pojawił się największy obraz lub blok tekstu, ale w niektórych przypadkach warto zmierzyć czas renderowania innego elementu.
W takich przypadkach użyj interfejsu Element Timing API. Interfejs LCP API jest zbudowany na podstawie interfejsu Element Timing API i dodaje automatyczne raportowanie największego elementu z treścią, ale możesz też tworzyć raporty o innych elementach, dodając do nich jawnie atrybut elementtiming
i rejestrując obserwatora PerformanceObserver, aby obserwować typ wpisu element
.
<img elementtiming="hero-image" />
<p elementtiming="important-paragraph">This is text I care about.</p>
<!-- ... -->
<script>
const po = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `element` entries to be dispatched.
po.observe({type: 'element', buffered: true});
</script>
Interfejs Event Timing API
Dane Interakcja do kolejnego wyrenderowania (INP) oceniają ogólną responsywność strony na podstawie obserwacji wszystkich kliknięć, dotknięć i interakcji z klawiaturą w całym okresie istnienia strony. INP strony to najczęściej interakcja, która zajęła najwięcej czasu od momentu jej rozpoczęcia do momentu wyrenderowania przez przeglądarkę kolejnego obrazu pokazującego wizualny wynik działania użytkownika.
Dane INP są dostępne dzięki interfejsowi Event Timing API. Ten interfejs API udostępnia kilka sygnatur czasowych występujących w cyklu życia zdarzenia, w tym:
startTime
: czas, w którym przeglądarka odbiera zdarzenie.processingStart
: czas, w którym przeglądarka może rozpocząć przetwarzanie modułów obsługi zdarzeń dla danego zdarzenia.processingEnd
: czas, w którym przeglądarka kończy wykonywanie całego kodu synchronicznego zainicjowanego przez metody obsługi zdarzeń dla tego zdarzenia.duration
: czas (zaokrąglony do 8 milisekund ze względów bezpieczeństwa) od momentu, gdy przeglądarka otrzyma zdarzenie, do momentu, gdy będzie mogła narysować następny kadr po zakończeniu wykonywania całego kodu synchronicznego wywołanego przez moduły obsługi zdarzeń.
Przykład poniżej pokazuje, jak używać tych wartości do tworzenia pomiarów niestandardowych:
const po = new PerformanceObserver((entryList) => {
// Get the last interaction observed:
const entries = Array.from(entryList.getEntries()).forEach((entry) => {
// Get various bits of interaction data:
const inputDelay = entry.processingStart - entry.startTime;
const processingTime = entry.processingEnd - entry.processingStart;
const presentationDelay = entry.startTime + entry.duration - entry.processingEnd;
const duration = entry.duration;
const eventType = entry.name;
const target = entry.target || "(not set)"
console.log("----- INTERACTION -----");
console.log(`Input delay (ms): ${inputDelay}`);
console.log(`Event handler processing time (ms): ${processingTime}`);
console.log(`Presentation delay (ms): ${presentationDelay}`);
console.log(`Total event duration (ms): ${duration}`);
console.log(`Event type: ${eventType}`);
console.log(target);
});
});
// A durationThreshold of 16ms is necessary to include more
// interactions, since the default is 104ms. The minimum
// durationThreshold is 16ms.
po.observe({type: 'event', buffered: true, durationThreshold: 16});
Interfejs Resource Timing API
Interfejs Resource Timing API zapewnia deweloperom szczegółowe informacje o sposobie wczytywania zasobów na konkretnej stronie. Pomimo nazwy interfejsu API informacje, które on udostępnia, nie ograniczają się tylko do danych o czasie (chociaż ich jest dużo). Inne dane, do których masz dostęp:
initiatorType
: sposób pobierania zasobu: z tagu<script>
lub<link>
albo z wywołaniafetch()
.nextHopProtocol
: protokół używany do pobierania zasobu, np.h2
lubquic
.encodedBodySize
/decodedBodySize]: rozmiar zasobu w postaci zakodowanej lub odkodowanej (odpowiednio).transferSize
: rozmiar zasobu, który został faktycznie przesłany przez sieć. Gdy zasoby są dostarczane przez pamięć podręczną, ta wartość może być znacznie mniejsza niżencodedBodySize
, a w niektórych przypadkach może wynosić 0 (jeśli nie jest wymagane ponowne sprawdzanie poprawności pamięci podręcznej).
Właściwości transferSize
w rekordach czasu zasobów możesz używać do pomiaru danych częstości trafień do pamięci podręcznej lub łącznej wielkości zasobów w pamięci podręcznej, co może być przydatne do zrozumienia, jak strategia buforowania zasobów wpływa na skuteczność w przypadku powracających użytkowników.
W tym przykładzie odnotowuje się wszystkie zasoby żądane przez stronę i wskazywane, czy każdy z nich został spełniony przez pamięć podręczną.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// If transferSize is 0, the resource was fulfilled using the cache.
console.log(entry.name, entry.transferSize === 0);
}
});
// Start listening for `resource` entries to be dispatched.
po.observe({type: 'resource', buffered: true});
Interfejs API Czas trwania nawigacji
Interfejs Navigation Timing API jest podobny do interfejsu Resource Timing API, ale raportuje tylko żądania nawigacji. Typ wpisu navigation
jest też podobny do typu resource
, ale zawiera dodatkowe informacje dotyczące tylko żądań nawigacji (np. gdy występują zdarzenia DOMContentLoaded
i load
).
Dane, które wielu programistów śledzi, aby poznać czas odpowiedzi serwera (czas do pierwszego bajta (TTFB)), są dostępne za pomocą interfejsu API Czas trwania nawigacji, a w szczególności jego sygnału czasu responseStart
.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// If transferSize is 0, the resource was fulfilled using the cache.
console.log('Time to first byte', entry.responseStart);
}
});
// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});
Inną miarą, która może być ważna dla deweloperów korzystających z serwisów worker, jest czas uruchamiania serwisu workera w przypadku żądań nawigacji. To czas potrzebny przeglądarce na uruchomienie wątku usługi, zanim zacznie przechwytywać zdarzenia pobierania.
Czas uruchomienia usługi dla konkretnego żądania nawigacji można określić na podstawie różnicy między wartościami entry.responseStart
i entry.workerStart
.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('Service Worker startup time:',
entry.responseStart - entry.workerStart);
}
});
// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});
Interfejs Server Timing API
Interfejs Server Timing API umożliwia przekazywanie danych o czasie przetwarzania po stronie serwera do przeglądarki za pomocą nagłówków odpowiedzi. Możesz na przykład wskazać, ile czasu zajęło wyszukiwanie danych w bazie danych w przypadku konkretnego zapytania. Może to być przydatne podczas debugowania problemów z wydajnością spowodowanych przez powolność serwera.
W przypadku deweloperów, którzy korzystają z usług dostawców zewnętrznych, interfejs Server Timing API jest jedynym sposobem na powiązanie danych o wydajności serwera z innymi danymi biznesowymi, które mogą być mierzone przez te narzędzia analityczne.
Aby określić w odpowiedziach dane o czasie działania serwera, możesz użyć nagłówka odpowiedzi Server-Timing
. Oto przykład.
HTTP/1.1 200 OK
Server-Timing: miss, db;dur=53, app;dur=47.2
Następnie na stronach możesz odczytać te dane w przypadku wpisów resource
lub navigation
z interfejsów API Resource Timing i Navigation Timing.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Logs all server timing data for this response
console.log('Server Timing', entry.serverTiming);
}
});
// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});