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

Dowiedz się, jak mierzyć wykorzystanie pamięci przez stronę internetową w wersji produkcyjnej w celu wykrywania regresji.

Brendan Kenny
Brendan Kenny
Ulan Degenbaev
Ulan Degenbaev

Przeglądarki automatycznie zarządzają pamięcią stron internetowych. Za każdym razem, gdy strona internetowa tworzy obiekt, przeglądarka przydzieli mu fragment pamięci „w środku”, aby go zapisać. Pamięć jest skończonym zasobem, więc przeglądarka przeprowadza funkcję czyszczenia pamięci, która wykrywa, kiedy obiekt nie jest już potrzebny, i pozwala zwolnić bazowy fragment pamięci.

Wykrywanie nie jest jednak doskonałe i udowodniono, że idealne wykrywanie jest niemożliwe. Dlatego przeglądarki utożsamiają się z pojęciem „potrzebny obiekt” jako „obiekt jest osiągalny”. 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, co ilustruje 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 nie odzyskuje jej, ponieważ nadal jest dostępna poprzez wywołanie zwrotne object.b. W ten sposób dochodzi do wycieku pamięci większej tablicy.

Wycieki pamięci są powszechne w internecie. Aby łatwo wprowadzić taki test, zapomnisz wyrejestrować detektor zdarzeń, przez przypadek przechwytując obiekty z elementu iframe, przez niezamykanie instancji roboczej, przez gromadzenie obiektów w tablicach itd. Jeśli dochodzi do wycieku pamięci na stronie internetowej, jej wykorzystanie pamięci wzrasta z czasem, a użytkownicy wydają się jej zbyt wolni.

Pierwszym krokiem do rozwiązania tego problemu jest jego pomiar. Nowy interfejs performance.measureUserAgentSpecificMemory() API umożliwia programistom pomiar wykorzystania pamięci przez strony internetowe w środowisku produkcyjnym i wykrywanie luk w pamięci, które mogą zostać wykryte podczas lokalnych testów.

Czym performance.measureUserAgentSpecificMemory() różni się od starszej wersji interfejsu API 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 JavaScriptu, a nowy interfejs API szacuje ilość pamięci wykorzystywanej przez stronę internetową. Ta różnica nabiera znaczenia, gdy Chrome współdzieli tę samą stertę z wieloma stronami internetowymi (lub wieloma wystąpieniami tej samej strony). W takich przypadkach wynik starego interfejsu API może być dowolnie wyłączony. Stary interfejs API jest zdefiniowany w terminach związanych z implementacją, takich jak „sterta”, jego standaryzowanie jest bezcelowe.

Kolejna różnica polega na tym, że nowy interfejs API dokonuje pomiaru pamięci podczas czyszczenia pamięci. Pozwala to zmniejszyć szum w wynikach, ale uzyskanie wyników może trochę potrwać. Pamiętaj, że inne przeglądarki mogą zdecydować się na wdrożenie nowego interfejsu API bez konieczności czyszczenia pamięci.

Sugerowane zastosowania

Wykorzystanie pamięci przez stronę internetową zależy od czasu wystąpienia zdarzeń, działań użytkowników i odśmiecania. Dlatego interfejs API do pomiaru pamięci jest przeznaczony do agregacji danych o wykorzystaniu pamięci ze środowiska produkcyjnego. Wyniki poszczególnych wywołań są mniej przydatne. Przykładowe przypadki użycia:

  • wykrywanie regresji podczas wdrażania nowej wersji strony internetowej w celu wychwytywania nowych wycieków pamięci;
  • Testy A/B nowej funkcji pozwalające ocenić jej wpływ na pamięć i wykryć jej wycieki.
  • Korelowanie wykorzystania pamięci z czasem trwania sesji w celu sprawdzenia obecności lub braku wycieków pamięci.
  • Powiązanie 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 (od Chrome 89). Wynik działania interfejsu API w dużym stopniu zależy od implementacji, ponieważ przeglądarki mają różne sposoby reprezentowania obiektów w pamięci i różne sposoby szacowania wykorzystania pamięci. Przeglądarki mogą wykluczać z uwzględniania niektóre regiony pamięci, jeśli odpowiednie naliczanie jest zbyt kosztowne lub niemożliwe. Dlatego nie można porównywać wyników z różnych przeglądarek. 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 nie przynieść błędu SecurityError, jeśli środowisko wykonawcze nie spełnia wymagań dotyczących zabezpieczeń zapobiegających wyciekom informacji z innych domen. Opiera się na izolacji zasobów z innych domen, które strona internetowa może aktywować, ustawiając nagłówki COOP+COEP.

Wsparcie można wykryć 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 realizuje od razu obietnicy wyniku i czeka na zakończenie czyszczenia pamięci.

Wywołanie interfejsu API wymusza usuwanie czyszczenia po pewnym czasie oczekiwania, który jest obecnie ustawiony na 20 sekund, ale może nastąpić wcześniej. Uruchamianie Chrome przy użyciu flagi wiersza poleceń --enable-blink-features='ForceEagerMeasureMemory' skraca czas oczekiwania do zera i jest przydatny przy lokalnym debugowaniu i testowaniu.

Przykład

Zalecanym zastosowaniem interfejsu API jest zdefiniowanie globalnego monitora pamięci, który próbkuje wykorzystanie pamięci przez całą stronę internetową i wysyła wyniki do serwera w celu agregacji i analizy. Najprostszym sposobem jest próbkowanie okresowo, np. co M min. Powoduje to jednak dyskryminację danych, ponieważ między próbkami mogą wystąpić 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ępny pomiar pamięci za pomocą funkcji setTimeout() z losowymi odstępami.

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 że średnio 1 pomiar odbywa się co 5 minut. Jeśli interesuje Cię funkcja matematyczna, zobacz 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 asynchroniczna funkcja 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']
    },
  ],
}

Szacowane całkowite wykorzystanie pamięci jest zwracane w polu bytes. Wartość ta w dużym stopniu zależy od implementacji i nie można jej porównywać w różnych przeglądarkach. Może się nawet zmieniać w różnych wersjach tej samej przeglądarki. Wartość obejmuje pamięć JavaScriptu i DOM wszystkich elementów 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 część pamięci i przypisuje ją do zestawu okien, elementów iframe i instancji roboczych określonych przez adres URL. Pole types zawiera listę typów pamięci związanych z pamięcią w poszczególnych implementacjach.

Wszystkie listy należy traktować jako ogólne i nie zakodować 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 Społeczności ds. wydajności witryn i zespół Chrome chętnie poznają Twoje przemyślenia i doświadczenia związane z usługą 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 tym celu właściwości, których potrzebujesz, aby zrealizować swój pomysł? Zgłoś problem ze specyfikacją w repozytorium GitHub Performance.measureUserAgentSpecificMemory() lub dodaj swoje przemyślenia do istniejącego problemu.

Zgłoś problem z implementacją

Czy wystąpił błąd z implementacją 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, podaj proste instrukcje odtworzenia błędu i ustaw opcję Komponenty na Blink>PerformanceAPIs. Usługa Glitch świetnie nadaje się do szybkiego i łatwego udostępniania poprawek.

Pokaż wsparcie

Czy zamierzasz korzystać z usługi 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 na adres @ChromiumDev, aby poinformować nas, gdzie i jak go używasz.

Przydatne linki

Podziękowania

Wielkie dzięki dla Domenic Denicola, Yoava Weissa, Mathiasa Bynensa za recenzje projektów API oraz Dominikowi Inführowi, Hannesowi Payerowi, Kentaro Hara i Michaelowi Lippautz za weryfikację kodu w Chrome. Dziękuję również Per Parker, Philipp Weis, Olga Belomestnykh, Matthew Bolohan i Neil Mckay za cenne opinie użytkowników, które znacznie usprawniły interfejs API.

Baner powitalny, autor: Harrison Broadbent, Unsplash