Bardzo ważne jest posiadanie danych dotyczących użytkownika, które możesz mierzyć uniwersalnie w dowolnej witrynie. Dzięki tym danym:
- Dowiedz się, jak prawdziwi użytkownicy postrzegają internet jako całość.
- Porównaj swoją witrynę z witryną konkurencji.
- Śledź przydatne i przydatne dane w narzędziach analitycznych bez konieczności pisania niestandardowego kodu.
Dane uniwersalne są dobrym punktem odniesienia, ale w wielu przypadkach konieczne jest mierzenie więcej niż tylko te dane, aby uzyskać pełny obraz wrażeń związanych z daną witryną.
Dzięki danym niestandardowym możesz mierzyć różne aspekty obsługi witryny, które mogą mieć zastosowanie tylko w niej, np.:
- Czas potrzebny na przejście aplikacji jednostronicowej z jednej „strony” do innego.
- Czas potrzebny na wyświetlenie na stronie danych pobranych z bazy danych w przypadku zalogowanych użytkowników.
- Czas potrzebny na hydratację aplikacji renderowanej po stronie serwera (SSR).
- Współczynnik trafień w pamięci podręcznej dla zasobów wczytywanych przez powracających użytkowników.
- Opóźnienie zdarzeń kliknięcia lub klawiatury w grze.
Interfejsy API do pomiaru niestandardowych danych
Dawniej programiści nie używali zbyt wielu interfejsów API niskopoziomowych do pomiaru wydajności, dlatego musieli uciekać się do ataków hakerskich, aby zmierzyć, czy strona działa dobrze.
Można na przykład określić, czy wątek główny jest blokowany przez długotrwałe zadania JavaScriptu, uruchamiając pętlę requestAnimationFrame
i obliczając różnicę między poszczególnymi klatkami. Jeśli ta różnica jest znacznie dłuższa niż liczba klatek na ekranie, możesz zgłosić to jako długie zadanie. Nie są one jednak zalecane, ponieważ same w sobie wpływają na wydajność (np. przez wyczerpywanie się baterii).
Pierwszą zasadą skutecznego pomiaru skuteczności jest dopilnowanie, by zastosowane techniki pomiaru skuteczności nie powodowały problemów. Dlatego w przypadku niestandardowych danych zbieranych w witrynie najlepiej jest, jeśli to możliwe, korzystać z jednego z poniższych interfejsów API.
Interfejs Performance Observer API
Performance Observer API to mechanizm, który zbiera i wyświetla dane ze wszystkich innych interfejsów API dotyczących wydajności omówionych na tej stronie. Zrozumienie tego jest kluczowe do 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 mogą być uruchamiane w okresach bezczynności, co oznacza, że zwykle nie wpływają na wydajność strony.
Aby utworzyć PerformanceObserver
, przekaż je wywołanie zwrotne, które zostanie uruchomione za każdym razem, gdy zostaną wysłane nowe wpisy wydajności. Następnie informujesz obserwatora, jakich wpisów ma słuchać, używając metody observe()
:
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ą obserwować wpisy tylko w chwili ich wystąpienia. Może to powodować problemy, jeśli chcesz leniwie ładować kod analizy wydajności, aby nie blokował zasobów o wyższym priorytecie.
Aby pobrać wpisy historyczne (po ich wystąpieniu), ustaw flagę buffered
na true
przy wywołaniu funkcji observe()
. Przy pierwszym wywołaniu wywołania zwrotnego PerformanceObserver
przeglądarka uwzględni wpisy historyczne z bufora wpisów wydajności, aż do maksymalnego rozmiaru bufora dla danego typu.
po.observe({
type: 'some-entry-type',
buffered: true,
});
Starsze interfejsy API wydajności, których należy unikać
Przed wprowadzeniem Performance Observer API deweloperzy mogli uzyskiwać dostęp do wpisów o wydajności za pomocą 3 metod zdefiniowanych w obiekcie performance
:
Chociaż te interfejsy API są nadal obsługiwane, ich użycie nie jest zalecane, ponieważ nie pozwalają na nasłuchiwanie emisji nowych wpisów. Poza tym wiele nowych interfejsów API (np. largest-contentful-paint
) nie jest udostępnianych przez obiekt performance
. Są one udostępniane tylko przez PerformanceObserver
.
Jeśli nie potrzebujesz zgodności z Internet Explorerem, najlepiej unikać tych metod w kodzie i od tej pory używać standardu PerformanceObserver
.
Interfejs API czasu użytkownika
Interfejs User Timing API jest ogólny Measurement API do obsługi danych czasowych. Umożliwia dowolne oznaczanie punktów a potem zmierzyć czas między tymi oznaczeniami.
// 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');
Chociaż interfejsy API, takie jak Date.now()
czy performance.now()
, zapewniają podobne możliwości, zaletą korzystania z interfejsu User Timing API jest jego integracja z narzędziami zwiększającymi skuteczność. Na przykład Narzędzia deweloperskie w Chrome wizualizują pomiary czasu działań użytkownika w panelu Skuteczność. Wielu dostawców usług analitycznych może też automatycznie śledzić przeprowadzane przez Ciebie pomiary i wysyłać dane o czasie trwania do swoich systemów analitycznych.
Aby raportować wyniki pomiarów czasu użytkownika, możesz użyć narzędzia PerformanceObserver i zarejestrować się w celu 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});
Interfejs Long Tasks API
Interfejs Long Tasks API pozwala dowiedzieć się, kiedy wątek główny przeglądarki jest blokowany 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.
Zawsze, gdy musisz uruchomić drogi kod lub wczytać i wykonać duże skrypty, warto śledzić, czy ten kod blokuje wątek główny. W rzeczywistości wiele wskaźników wyższego poziomu jest tworzonych na podstawie samych interfejsów Long Tasks API (takich jak Time to Interactive (TTI) i Total Block Time (TBT)).
Aby określić, kiedy pojawiają się długie zadania, możesz użyć narzędzia PerformanceObserver, a potem zarejestrować się i 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});
Interfejs API Long Animation Frames
Interfejs Long Animation Frames API to nowa wersja interfejsu Long Tasks API, która analizuje długie klatki (a nie długie zadania) trwające ponad 50 milisekund. To rozwiązanie ma niektóre wady interfejsu Long Tasks API, takie jak poprawa atrybucji i większy zakres potencjalnych problemów z opóźnieniami.
Aby określić, kiedy pojawiają się długie klatki, możesz użyć narzędzia PerformanceObserver, zarejestrować się i 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 API Element Timing
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 oparty na interfejsie Element Timing API i dodaje automatyczne raportowanie największego elementu z treścią, ale możesz też tworzyć raporty dotyczące innych elementów, jawnie dodając do nich atrybut elementtiming
i rejestrując funkcję PerformanceObserver w celu obserwowania typu 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ą możliwe 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, o którym przeglądarka może rozpocząć przetwarzanie modułów obsługi zdarzeń dla zdarzenia.processingEnd
: czas zakończenia wykonywania przez przeglądarkę całego kodu synchronicznego zainicjowanego z modułów obsługi zdarzeń dla tego zdarzenia.duration
: czas (zaokrąglony do 8 milisekund ze względów bezpieczeństwa) od momentu otrzymania przez przeglądarkę zdarzenia do momentu, gdy przeglądarka będzie mogła wyrenderować kolejną klatkę po wykonaniu całego kodu synchronicznego zainicjowanego przez moduły obsługi zdarzeń.
Poniższy przykład pokazuje, jak korzystać z 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});
Resource Timing API
Interfejs Resource Timing API zapewnia programistom szczegółowe informacje o tym, jak zostały wczytane zasoby konkretnej strony. Mimo nazwy interfejsu API zawarte w nim informacje nie ograniczają się tylko do danych o czasie (a jest tego mnóstwo). Inne dane, do których masz dostęp:
initiatorType
: sposób pobrania zasobu, np. 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 zdekodowanej (odpowiednio)transferSize
: rozmiar zasobu, który został faktycznie przesłany przez sieć. Gdy zasoby są wypełniane przez pamięć podręczną, ta wartość może być znacznie mniejsza niżencodedBodySize
, a w niektórych przypadkach może wynosić 0 (jeśli ponowna weryfikacja pamięci podręcznej nie jest wymagana).
Możesz użyć właściwości transferSize
wpisów o czasie zasobów, aby mierzyć wskaźnik współczynnika trafień w pamięci podręcznej lub wskaźnik całkowitego rozmiaru zasobów w pamięci podręcznej. Dzięki temu możesz się dowiedzieć, jak strategia buforowania zasobów wpływa na wydajność w przypadku powracających użytkowników.
W poniższym przykładzie rejestrowane są wszystkie zasoby żądane przez stronę i 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});
Navigation Timing API
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 wpisu resource
, ale zawiera dodatkowe informacje dotyczące tylko żądań nawigacji (np. kiedy są uruchamiane zdarzenia DOMContentLoaded
i load
).
Jednym ze wskaźników używanych przez wielu deweloperów do analizowania czasu odpowiedzi serwera (Time to First Byte (TTFB)) jest dostępny przez Navigation Timing API – w szczególności jest to sygnatura czasowa responseStart
wpisu.
// 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. Jest to czas potrzebny przeglądarce na uruchomienie wątku skryptu service worker, 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});
Server Timing API
Interfejs Server Timing API umożliwia przekazywanie z serwera do przeglądarki danych o czasie związanych z żądaniem w nagłówkach odpowiedzi. Możesz na przykład określić, ile czasu zajęło wyszukiwanie danych w bazie danych dla określonego żądania. Może to być przydatne podczas debugowania problemów z wydajnością spowodowanych spowolnieniem serwera.
W przypadku deweloperów korzystających z usług zewnętrznych dostawców rozwiązań analitycznych interfejs Server Timing API to jedyny sposób skorelowania danych o wydajności serwera z innymi wskaźnikami biznesowymi, które mogą mierzyć 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 ze swoich stron możesz odczytywać te dane we wpisach 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});