Dane o użytkownikach, 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 niestandardowego kodu.
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.
- Opóźnienie zdarzeń kliknięcia lub klawiatury w grze.
Interfejsy API do pomiaru niestandardowych danych
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).
Pierwszą zasadą skutecznego pomiaru skuteczności jest dopilnowanie, by zastosowane techniki pomiaru skuteczności nie powodowały problemów. Dlatego w przypadku dowolnych danych niestandardowych, które gromadzisz w swojej witrynie, najlepiej jest, jeśli to możliwe, używać jednego z tych interfejsów API.
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ą usługi PerformanceObserver
możesz pasywnie subskrybować zdarzenia związane ze skutecznoś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 nasłuchiwać obserwator:
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 sekcjach poniżej znajdziesz listę wszystkich różnych typów wpisów, które można obserwować. W nowszych przeglądarkach możesz sprawdzić, jakie typy wpisów są dostępne przy użyciu właściwości statycznej PerformanceObserver.supportedEntryTypes
.
Obserwuj wpisy, które już istnieją
Domyślnie obiekty PerformanceObserver
mogą tylko obserwować wpisy w miarę ich pojawiania się. Może to powodować problemy, jeśli chcesz leniwie ładować kod analizy wydajności, 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
User Timing API to interfejs API do ogólnego przeznaczenia do pomiaru danych opartych na czasie. 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 Narzędzia deweloperskie w Chrome wizualizują pomiary czasu działań użytkownika w panelu Skuteczność. Wielu dostawców rozwiązań analitycznych może też automatycznie śledzić przeprowadzane przez Ciebie pomiary i wysyłać dane o czasie trwania do swoich systemów analitycznych.
Aby zgłaszać pomiary czasu użytkownika, możesz użyć metody PerformanceObserver i zarejestrować się do obserwowania 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 dowiedzieć się, kiedy wątek główny przeglądarki jest zablokowany na tyle długo, że może to wpłynąć na liczbę klatek lub opóźnienie sygnału wejściowego. Interfejs API zgłasza wszystkie zadania wykonywane dłużej niż przez 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 i czas całkowitego blokowania).
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
Dane Największe wyrenderowanie treści (LCP) pozwalają dowiedzieć się, kiedy na ekranie został wyrenderowany największy obraz lub blok tekstowy. W niektórych przypadkach warto jednak zmierzyć czas renderowania innego elementu.
W takich przypadkach użyj interfejsu Element Timing API. Interfejs LCP API jest w istocie oparty na interfejsie Element Timing API i dodaje automatyczne raportowanie największego elementu z treściami, ale możesz też tworzyć raporty o innych elementach, dodając do nich jawnie atrybut elementtiming
i rejestrując obiekt 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 Od interakcji do kolejnego wyrenderowania (INP) oceniają ogólną responsywność strony przez obserwację wszystkich kliknięć, dotknięć i klawiatury w całym cyklu życia strony. Wartość INP strony to najczęściej interakcja, której wykonanie zajęło najdłużej – od momentu zainicjowania przez użytkownika interakcji do czasu, w którym przeglądarka wyrenderuje kolejną klatkę, która pokazuje wynik działania użytkownika.
Dane INP są dostępne dzięki interfejsowi Event Timing API. Ten interfejs API udostępnia różne sygnatury czasowe występujące w trakcie cyklu życia zdarzenia, w tym:
startTime
: czas odebrania zdarzenia przez przeglądarkę.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 są ograniczone tylko do danych o czasie (chociaż ich jest dużo). Inne dane, do których masz dostęp, to:
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 wymagana ponowna weryfikacja pamięci podręcznej).
Właściwości transferSize
w rekordach czasu zasobów możesz użyć do pomiaru wskaźnika 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 poniższym przykładzie rejestrowane są wszystkie zasoby żądane przez stronę, a także wskazuje, czy poszczególne zasoby zostały uzupełnione 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 zgłasza 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});
Innym wskaźnikiem, który mogą być ważne dla deweloperów korzystających z skryptu service worker, jest czas uruchamiania tego skryptu w przypadku żądań nawigacji. To czas potrzebny przeglądarce na uruchomienie wątku usługi, zanim zacznie przechwytywać zdarzenia pobierania.
Czas uruchomienia skryptu service worker dla określonego żądania nawigacji można określić na podstawie różnicy między entry.responseStart
a 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 dotyczące czasu 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});