Monitoruj całkowite wykorzystanie pamięci przez strony internetowe za pomocą narzędziameasureUserAgentSpecMemory()

Dowiedz się, jak mierzyć wykorzystanie pamięci przez stronę internetową w środowisku produkcyjnym, aby wykrywać regresje.

Brendan Kenny
Brendan Kenny
Ułan Degenbajew
Ułan Degenbajew

Przeglądarki automatycznie zarządzają pamięcią stron internetowych. Za każdym razem, gdy strona internetowa tworzy obiekt, przeglądarka przydziela „wewnętrzną” część pamięci do jego przechowywania. Pamięć jest zasobem skończonym, dlatego przeglądarka przeprowadza czyszczenie pamięci, aby wykryć, że obiekt nie jest już potrzebny, i zwolnić bazowy fragment pamięci.

Wykrywanie nie jest jednak idealne i udowodniono, że idealne wykrywanie nie jest możliwe. Przeglądarki w odniesieniu do terminu „obiekt jest potrzebny” działają w przybliżeniu „obiekt jest osiągalny”. Jeśli strona internetowa nie może dotrzeć do obiektu przez zmienne i pola innych osiągalnych obiektów, przeglądarka może bezpiecznie odzyskać obiekt. Różnica między tymi 2 pojęciami prowadzi do wycieku pamięci, jak pokazano w tym przykładzie.

const object = {a: new Array(1000), b: new Array(2000)};
setInterval(() => console.log(object.a), 1000);

W tym przypadku większa tablica b nie jest już potrzebna, ale przeglądarka jej nie pobiera, bo nadal jest osiągalna przez interfejs object.b w wywołaniu zwrotnym. W ten sposób wycieknie pamięci większej tablicy.

Wycieki pamięci najczęściej występują w internecie. Łatwo jest wprowadzić taki schemat, pilnując, aby detektor zdarzeń został wyrejestrowany, przypadkowo przechwytując obiekty z elementu iframe, nie zamykając instancji roboczej, gromadząc obiekty w tablicach itd. Jeśli na stronie internetowej nastąpi wyciek pamięci, jej wykorzystanie pamięci wzrasta z czasem, a strona działa wolno i jest zbyt wielka.

Pierwszym krokiem do rozwiązania tego problemu jest pomiar go. Nowy interfejs performance.measureUserAgentSpecificMemory() API umożliwia deweloperom pomiar wykorzystania pamięci przez strony internetowe w środowisku produkcyjnym oraz wykrywanie wycieków pamięci, które przechodzą podczas testów lokalnych.

Czym różni się interfejs performance.measureUserAgentSpecificMemory() od starszego interfejsu API performance.memory?

Jeśli znasz dotychczasowy niestandardowy interfejs API performance.memory, możesz zastanawiać się, czym nowy interfejs API różni się od niego. Główna różnica polega na tym, że stary interfejs API zwraca rozmiar sterty JavaScriptu, a nowy interfejs API szacuje ilość pamięci wykorzystywanej przez stronę internetową. Ta różnica staje się ważna, gdy Chrome udostępnia tę samą stertę wielu stronom internetowym (lub wielu wystąpień tej samej strony). W takich przypadkach wynik starego interfejsu API może być w sposób dowolny. Ponieważ stary interfejs jest zdefiniowany w terminach związanych z implementacją, takich jak „stera”, ustandaryzowanie go bez powodzenia.

Kolejna różnica polega na tym, że nowy interfejs API wykonuje pomiar pamięci podczas czyszczenia pamięci. Zmniejsza to szum w wynikach, ale uzyskanie wyników może trochę potrwać. Pamiętaj, że inne przeglądarki mogą wdrożyć nowy interfejs API bez konieczności korzystania z funkcji czyszczenia pamięci.

Sugerowane przypadki użycia

Wykorzystanie pamięci strony internetowej zależy od czasu zdarzeń, działań użytkownika i odśmiecania pamięci. Dlatego interfejs API do pomiaru pamięci służy do agregacji danych o użyciu pamięci w środowisku produkcyjnym. Wyniki poszczególnych połączeń są mniej przydatne. Przykładowe przypadki użycia:

  • Wykrywanie regresji podczas wdrażania nowej wersji strony internetowej w celu wykrywania nowych wycieków pamięci.
  • Testy A/B nowej funkcji służące do oceny jej wpływu na pamięć i wykrywania wycieków pamięci.
  • Koordynowanie wykorzystania pamięci z czasem trwania sesji w celu weryfikacji obecności lub braku wycieków pamięci.
  • Koordynowanie wykorzystania pamięci ze wskaźnikami o użytkownikach w celu poznania ogólnego wpływu wykorzystania pamięci.

Zgodność z przeglądarką

Obsługa przeglądarek

  • 89
  • 89
  • x
  • x

Źródło

Obecnie ten interfejs API jest obsługiwany tylko w przeglądarkach opartych na Chromium, począwszy od Chrome 89. Wynik działania interfejsu API jest w dużym stopniu zależny od implementacji, ponieważ przeglądarki przedstawiają obiekty w pamięci w różny sposób i mają różne sposoby szacowania wykorzystania pamięci. Jeśli właściwa księgowość jest zbyt kosztowna lub niewykonalna, przeglądarki mogą wykluczać z rachunku niektóre regiony pamięci. W związku z tym nie można porównywać wyników w różnych przeglądarkach. Porównanie ma znaczenie tylko w przypadku wyników w przypadku tej samej przeglądarki.

Jak korzystać z aplikacji performance.measureUserAgentSpecificMemory()

Wykrywanie funkcji

Funkcja performance.measureUserAgentSpecificMemory będzie niedostępna lub może wystąpić błąd SecurityError, jeśli środowisko wykonawcze nie spełnia wymagań bezpieczeństwa dotyczących zapobiegania wyciekom informacji z innych domen. Wykorzystuje izolację zasobów z innych domen, którą strona internetowa może aktywować, ustawiając nagłówki COOP+COEP.

Pomoc jest wykrywana w czasie działania:

if (!window.crossOriginIsolated) {
  console.log('performance.measureUserAgentSpecificMemory() is only available in cross-origin-isolated pages');
} else if (!performance.measureUserAgentSpecificMemory) {
  console.log('performance.measureUserAgentSpecificMemory() is not available in this browser');
} else {
  let result;
  try {
    result = await performance.measureUserAgentSpecificMemory();
  } catch (error) {
    if (error instanceof DOMException && error.name === 'SecurityError') {
      console.log('The context is not secure.');
    } else {
      throw error;
    }
  }
  console.log(result);
}

Testy lokalne

Chrome wykonuje pomiar pamięci podczas czyszczenia pamięci, co oznacza, że interfejs API nie rozwiązuje od razu obietnicy wyniku, tylko czeka na kolejne czyszczenie pamięci.

Wywołanie interfejsu API wymusza usunięcie pamięci po upływie określonego czasu oczekiwania, który obecnie jest ustawiony na 20 sekund, choć może nastąpić wcześniej. Uruchamianie Chrome z flagą wiersza poleceń --enable-blink-features='ForceEagerMeasureMemory' skraca czas oczekiwania do zera i jest przydatne podczas lokalnego debugowania i testowania.

Przykład

Zalecane zastosowanie interfejsu API polega na zdefiniowaniu globalnego monitora pamięci, który będzie próbował wykorzystać pamięć całej strony internetowej i wysyła wyniki do serwera w celu agregacji i analizy. Najprostszym sposobem jest okresowe próbkowanie, na przykład co M min. Powoduje to jednak odchylenie danych, ponieważ między próbkami mogą wystąpić skoki pamięci.

Poniższy przykład pokazuje, jak za pomocą procesu Poissona przeprowadzać obiektywne pomiary pamięci za pomocą procesu Poissona, który gwarantuje, że próbki mają równe szanse w dowolnym momencie (demonstracja, źródło).

Najpierw zdefiniuj funkcję, która planuje kolejny pomiar pamięci za pomocą funkcji setTimeout() z losowym odstępem.

function scheduleMeasurement() {
  // Check measurement API is available.
  if (!window.crossOriginIsolated) {
    console.log('performance.measureUserAgentSpecificMemory() is only available in cross-origin-isolated pages');
    console.log('See https://web.dev/coop-coep/ to learn more')
    return;
  }
  if (!performance.measureUserAgentSpecificMemory) {
    console.log('performance.measureUserAgentSpecificMemory() is not available in this browser');
    return;
  }
  const interval = measurementInterval();
  console.log(`Running next memory measurement in ${Math.round(interval / 1000)} seconds`);
  setTimeout(performMeasurement, interval);
}

Funkcja measurementInterval() oblicza losowy odstęp czasu w milisekundach, tak aby średnio co 5 minut odbywał się jeden pomiar. Jeśli chcesz poznać działanie matematyczne tej funkcji, zapoznaj się z sekcją Rozkład wykładniczy.

function measurementInterval() {
  const MEAN_INTERVAL_IN_MS = 5 * 60 * 1000;
  return -Math.log(Math.random()) * MEAN_INTERVAL_IN_MS;
}

Na koniec funkcja asynchroniczna performMeasurement() wywołuje interfejs API, rejestruje wynik i planuje kolejny pomiar.

async function performMeasurement() {
  // 1. Invoke performance.measureUserAgentSpecificMemory().
  let result;
  try {
    result = await performance.measureUserAgentSpecificMemory();
  } catch (error) {
    if (error instanceof DOMException && error.name === 'SecurityError') {
      console.log('The context is not secure.');
      return;
    }
    // Rethrow other errors.
    throw error;
  }
  // 2. Record the result.
  console.log('Memory usage:', result);
  // 3. Schedule the next measurement.
  scheduleMeasurement();
}

Na koniec rozpocznij pomiary.

// Start measurements.
scheduleMeasurement();

Wynik może wyglądać tak:

// Console output:
{
  bytes: 60_100_000,
  breakdown: [
    {
      bytes: 40_000_000,
      attribution: [{
        url: 'https://example.com/',
        scope: 'Window',
      }],
      types: ['JavaScript']
    },

    {
      bytes: 20_000_000,
      attribution: [{
          url: 'https://example.com/iframe',
          container: {
            id: 'iframe-id-attribute',
            src: '/iframe',
          },
          scope: 'Window',
      }],
      types: ['JavaScript']
    },

    {
      bytes: 100_000,
      attribution: [],
      types: ['DOM']
    },
  ],
}

Szacowane całkowite wykorzystanie pamięci jest zwracane w polu bytes. Wartość ta zależy w dużym stopniu od implementacji i nie można jej porównywać w różnych przeglądarkach. Może nawet zmieniać się w poszczególnych wersjach tej samej przeglądarki. Wartość obejmuje pamięć JavaScript i DOM wszystkich elementów iframe, powiązanych okien i instancji roboczych w bieżącym procesie.

Lista breakdown zawiera więcej informacji o używanej pamięci. Każdy wpis opisuje część pamięci i przypisuje ją do zbioru okien, elementów iframe i instancji roboczych określonych na podstawie adresu URL. Pole types zawiera listę typów pamięci specyficznych dla implementacji powiązanych z pamięcią.

Ważne jest, aby traktować wszystkie listy w sposób ogólny i nie kodować na stałe założeń dotyczących konkretnej przeglądarki. Na przykład niektóre przeglądarki mogą zwracać pustą wartość breakdown lub pustą attribution. Inne przeglądarki mogą zwrócić wiele wpisów w polu attribution, co oznacza, że nie mogą rozróżnić, które z nich są właścicielami pamięci.

Prześlij opinię

Grupa społecznościowa ds. skuteczności w internecie oraz zespół Chrome chętnie poznają Twoje opinie i doświadczenia związane z usługą performance.measureUserAgentSpecificMemory().

Opowiedz nam o projekcie interfejsu API

Czy jest coś, co nie działa w interfejsie API zgodnie z oczekiwaniami? A może brakuje w nim właściwości, które są niezbędne do realizacji pomysłu? Zgłoś problem ze specyfikacją w repozytorium GitHub performance.measureUserAgentSpecificMemory() lub dodaj uwagi o istniejącym problemie.

Zgłoś problem z implementacją

Czy wystąpił błąd związany z implementacją przeglądarki Chrome? A może implementacja różni się od specyfikacji? Zgłoś błąd na stronie new.crbug.com. Podaj jak najwięcej szczegółów i proste instrukcje odtworzenia błędu oraz ustaw Komponenty na Blink>PerformanceAPIs. Usterki to świetny sposób na udostępnianie szybkich i łatwych replik.

Okaż wsparcie

Czy zamierzasz używać usługi performance.measureUserAgentSpecificMemory()? Twoja publiczna pomoc pomaga zespołowi Chrome priorytetowo traktować funkcje i pokazuje innym dostawcom przeglądarek, jak ważne jest ich wsparcie. Wyślij tweeta na adres @ChromiumDev, by poinformować nas, gdzie i w jaki sposób używasz tego narzędzia.

Przydatne linki

Podziękowania

Dziękujemy Domenica Denicoli, Yoav Weissa, Mathiasa Bynensa za recenzje projektowania interfejsów API oraz Dominikowi Inführ, Hannesowi Payerowi, Kentaro Hara, Michaelowi Lippautzowi za weryfikację kodu w Chrome. Dziękuję też Per Parkerowi, Philippowi Weisowi, Oldze Belomestnykh, Matthew Bolohanowi i Neilowi Mckayowi za przekazanie cennych opinii, które znacznie usprawniły interfejs API.

Baner powitalny autorstwa Harrison Broadbent w filmie Unsplash