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

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

Brendan Kenny
Brendan Kenny
Ulan Degenbaev
Ulan Degenbaev

Przeglądarki zarządzają pamięcią stron internetowych automatycznie. Gdy strona internetowa tworzy obiekt, przeglądarka przydziela mu część pamięci „pod maską”. Ponieważ pamięć jest ograniczonym zasobem, przeglądarka wykonuje zbiór elementów, aby wykryć, kiedy obiekt nie jest już potrzebny, i zwolnić blok pamięci.

Wykrywanie nie jest jednak idealne, a udowadniamy, że wykrywanie w 100% jest niemożliwe. Dlatego przeglądarki zastępują pojęcie „wymagany obiekt” pojęciem „dostępny obiekt”. Jeśli strona internetowa nie może dotrzeć do obiektu przez jego zmienne i pola innych osiągalnych obiektów, przeglądarka może bezpiecznie odzyskać ten obiekt. Różnica między tymi dwoma pojęciami prowadzi do wycieków pamięci, jak pokazuje poniższy przykład.

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 zwalnia, ponieważ jest nadal dostępna za pomocą object.b w funkcjach zwracanych. W ten sposób dochodzi do wycieku pamięci z większego tablicowego zbioru.

Wycieki pamięci są częste w internecie. Łatwo można wprowadzić błąd, zapominając zarejestrować odbiornika zdarzeń, przypadkowo przechwytując obiekty z ramki iframe, nie zamykając workera, gromadząc obiekty w tablicach itd. Jeśli strona internetowa ma wycieki pamięci, z czasem rośnie jej wykorzystanie pamięci, a użytkownicy mogą odczuwać jej spowolnienie i zawieszanie się.

Pierwszym krokiem do rozwiązania tego problemu jest jego pomiar. Nowy interfejs API performance.measureUserAgentSpecificMemory() umożliwia programistom pomiar wykorzystania pamięci przez ich strony internetowe w produkcji, a tym samym wykrywanie wycieków pamięci, które nie zostały wykryte podczas testów lokalnych.

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

Jeśli znasz już niestandardowy interfejs API performance.memory, możesz zastanawiać się, czym różni się on od nowego interfejsu. Główna różnica polega na tym, że stary interfejs API zwraca rozmiar stosu JavaScript, a nowy interfejs API szacuje pamięć używaną przez stronę internetową. Ta różnica staje się ważna, gdy Chrome udostępnia ten sam stos wielu stronom internetowym (lub wielu instancjom tej samej strony). W takich przypadkach wynik starego interfejsu API może być dowolnie nieprawidłowy. Ponieważ stary interfejs API jest definiowany w terminach specyficznych dla implementacji, takich jak „heap”, jego standaryzacja jest niemożliwa.

Kolejną różnicą jest to, że nowe API wykonuje pomiar pamięci podczas czyszczenia pamięci. Pozwala to ograniczyć szum w wynikach, ale ich wygenerowanie może zająć trochę czasu. Pamiętaj, że inne przeglądarki mogą zdecydować się na wdrożenie nowego interfejsu API bez polegania na zbieraniu elementów do usunięcia.

Sugerowane zastosowania

Korzystanie z pamięci przez stronę internetową zależy od czasu trwania zdarzeń, działań użytkownika i zbierania elementów zbędących. Dlatego interfejs API do pomiaru pamięci jest przeznaczony do agregowania danych o wykorzystaniu pamięci z wersji produkcyjnej. 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 w celu oceny jej wpływu na wykorzystanie pamięci i wykrywania wycieków pamięci;
  • powiązać wykorzystanie pamięci z czasem trwania sesji, aby sprawdzić, czy nie dochodzi do wycieków pamięci;
  • Powiązanie wykorzystania pamięci z danymi o użytkownikach, aby poznać ogólny wpływ wykorzystania pamięci.

Zgodność z przeglądarką

Obsługa przeglądarek

  • Chrome: 89.
  • Edge: 89.
  • Firefox: nieobsługiwane.
  • Safari: nieobsługiwane.

Źródło

Obecnie ten interfejs API jest obsługiwany tylko w przeglądarkach opartych na Chromium (od Chrome 89). Wynik działania interfejsu API zależy w dużej mierze od implementacji, ponieważ przeglądarki mają różne sposoby reprezentacji obiektów w pamięci i szacowania wykorzystania pamięci. Przeglądarki mogą wykluczać niektóre regiony pamięci z rozliczania, jeśli jest ono zbyt drogie lub niemożliwe. Dlatego wyników nie można porównywać w różnych przeglądarkach. Ma sens tylko porównywać wyniki w tej samej przeglądarce.

Jak korzystać z aplikacji performance.measureUserAgentSpecificMemory()

Wykrywanie cech

Funkcja performance.measureUserAgentSpecificMemory będzie niedostępna lub może zakończyć się błędem SecurityError, jeśli środowisko wykonawcze nie spełnia wymagań bezpieczeństwa zapobiegających wyciekom informacji między domenami. Opiera się na izolacji zasobów z innych domen, które strona internetowa może aktywować, ustawiając nagłówki COOP+COEP.

Obsługa może być 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);
}

Testowanie lokalne

Chrome wykonuje pomiar pamięci podczas czyszczenia pamięci, co oznacza, że interfejs API nie realizuje od razu obietnicy wyniku i czeka na zakończenie czyszczenia pamięci.

Wywołanie interfejsu API powoduje wymuszenie usuwania elementów zbędących po pewnym czasie, który jest obecnie ustawiony na 20 sekund, ale może nastąpić wcześniej. Uruchomienie Chrome z flagą wiersza poleceń --enable-blink-features='ForceEagerMeasureMemory' powoduje skrócenie limitu czasu do 0 i jest przydatne do debugowania i testowania lokalnego.

Przykład

Zalecane jest zdefiniowanie globalnego monitora pamięci, który pobiera próbki wykorzystania pamięci przez całą stronę internetową i wysyła wyniki do serwera w celu ich agregacji i analizy. Najprostszym sposobem jest okresowe próbkowanie, na przykład co M minut. Jednak powoduje to zafałszowanie danych, ponieważ między próbkami mogą występować szczyty pamięci.

Poniższy przykład pokazuje, jak przeprowadzać obiektywne pomiary pamięci przy użyciu procesu Poissona, który gwarantuje, że prawdopodobieństwo wystąpienia prób w dowolnym momencie jest jednakowe (demo, źródło).

Najpierw zdefiniuj funkcję, która planuje następne pomiary pamięci za pomocą funkcji setTimeout() z losowym interwałem.

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 interwał w milisekundach, tak aby średnio co 5 minut występowało jedno pomiar. Jeśli interesuje Cię matematyka stojąca za tą funkcją, zapoznaj się z artykułem 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 następny 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']
    },
  ],
}

Szacowany łączny rozmiar pamięci jest zwracany w polu bytes. Ta wartość jest w dużej mierze zależna od implementacji i nie można jej porównywać w różnych przeglądarkach. Może się ona nawet zmieniać w różnych wersjach tej samej przeglądarki. Wartość obejmuje pamięć JavaScript i DOM wszystkich ramek iframe, powiązanych okien i instancji roboczych w bieżącym procesie.

Lista breakdown zawiera dodatkowe informacje o używanej pamięci. Każdy wpis opisuje pewną część pamięci i przypisuje ją do zestawu okien, ramek iframe i instancji roboczych zidentyfikowanych za pomocą adresu URL. Pole types zawiera listę typów pamięci powiązanych z pamięcią w ramach implementacji.

Ważne jest, aby traktować wszystkie listy w ogólny sposób i nie stosować zaszytych na stałe założeń dotyczących konkretnej przeglądarki. Na przykład niektóre przeglądarki mogą zwracać puste pole breakdown lub attribution. Inne przeglądarki mogą zwrócić wiele wpisów w polu attribution, co oznacza, że nie mogą rozróżnić, do którego z nich należy pamięć.

Prześlij opinię

Grupa ds. wydajności stron internetowych i zespół Chrome chętnie poznają Twoje opinie i wrażenia dotyczące performance.measureUserAgentSpecificMemory().

Opowiedz nam o konstrukcji interfejsu API

Czy jest coś, co w interfejsie API nie działa zgodnie z oczekiwaniami? A może brakuje właściwości, których potrzebujesz do wdrożenia swojego pomysłu? Zgłoś problem ze specyfikacją w repozytorium GitHub Performance.measureUserAgentSpecificMemory() lub dodaj swoje przemyślenia do istniejącego problemu.

Zgłaszanie problemów z implementacją

Czy wystąpił błąd związany z implementacją Chrome? Czy implementacja różni się od specyfikacji? Zgłoś błąd na stronie new.crbug.com. Podaj jak najwięcej szczegółów, dołącz proste instrukcje odtwarzania błędu i ustaw Składniki na Blink>PerformanceAPIs. Glitch to świetne narzędzie do szybkiego i łatwego udostępniania informacji o powtarzających się problemach.

Pokaż pomoc

Czy planujesz używać performance.measureUserAgentSpecificMemory()? Publiczna pomoc pomaga zespołowi Chrome ustalać priorytety funkcji i pokazywać innym dostawcom przeglądarek, jak ważne jest, aby wspierać te funkcje. Wyślij tweeta do @ChromiumDev i powiedz nam, gdzie i jak z niego korzystasz.

Przydatne linki

Podziękowania

Dziękujemy Domenicowi Denicola, Yoavowi Weissowi i Mathiasowi Bynensowi za sprawdzenie projektu interfejsu API oraz Dominikowi Inführowi, Hannesowi Payerowi, Kentaro Hara i Michaelowi Lippautzowi za sprawdzenie kodu w Chrome. Dziękuję też Perowi Parkerowi, Philippowi Weisowi, Oldze Belomestnykh, Matthew Bolohanowi i Neilowi McKayowi za cenne opinie użytkowników, które znacznie poprawiły interfejs API.

Baner powitalny, autor: Harrison Broadbent, Unsplash