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
Ulan Degenbaev
Ulan Degenbaev

Przeglądarki automatycznie zarządzają pamięcią stron internetowych. Gdy strona internetowa tworzy obiekt, przeglądarka przydziela „pod maską” fragment pamięci do przechowywania tego obiektu. Pamięć jest ograniczonym zasobem, dlatego przeglądarka przeprowadza odśmiecanie pamięci, aby wykryć, kiedy obiekt nie jest już potrzebny, i zwolnić odpowiadający mu fragment pamięci.

Wykrywanie nie jest jednak doskonałe, a problem zatrzymania Alana Turinga dowodzi, że doskonałe wykrywanie jest niemożliwe. Dlatego przeglądarki przybliżają pojęcie „obiekt jest potrzebny” do pojęcia „obiekt jest osiągalny”. Jeśli strona internetowa nie może dotrzeć do obiektu za pomocą jego zmiennych i pól 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 ten 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 odzyskuje, ponieważ jest ona nadal osiągalna za pomocą object.b w wywołaniu zwrotnym. W ten sposób dochodzi do wycieku pamięci większej tablicy.

Wycieki pamięci są powszechne w internecie, co widać z tego badania. Łatwo je spowodować, zapominając o wyrejestrowaniu detektora zdarzeń, przypadkowo przechwytując obiekty z elementu iframe, nie zamykając instancji roboczej, gromadząc obiekty w tablicach itp. Jeśli strona internetowa ma wycieki pamięci, jej wykorzystanie pamięci rośnie z czasem, a strona wydaje się użytkownikom powolna i przeładowana.

Pierwszym krokiem w rozwiązaniu tego problemu jest jego pomiar. Nowy performance.measureUserAgentSpecificMemory() interfejs API umożliwia programistom mierzenie wykorzystania pamięci przez strony internetowe w środowisku produkcyjnym, 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 API?

Jeśli znasz istniejący niestandardowy interfejs performance.memory API, możesz się zastanawiać, czym różni się od niego nowy interfejs API. Główna różnica polega na tym, że stary interfejs API zwraca rozmiar sterty JavaScript, a nowy interfejs API szacuje ilość pamięci używanej przez stronę internetową. Ta różnica staje się ważna, gdy Chrome współdzieli tę samą stertę z kilkoma stronami internetowymi (lub kilkoma instancjami tej samej strony internetowej). W takich przypadkach wynik starego interfejsu API może być dowolnie nieprawidłowy. Ponieważ stary interfejs API jest zdefiniowany w kategoriach specyficznych dla implementacji, takich jak „sterta”, jego standaryzacja jest niemożliwa.

Kolejna różnica polega na tym, że nowy interfejs API wykonuje pomiar pamięci podczas odśmiecania pamięci. Zmniejsza to szum w wynikach, ale może minąć trochę czasu, zanim wyniki zostaną wygenerowane. Pamiętaj, że inne przeglądarki mogą zdecydować się na wdrożenie nowego interfejsu API bez polegania na odśmiecaniu.

Sugerowane przypadki użycia

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

  • Wykrywanie regresji podczas wdrażania nowej wersji strony internetowej, aby wykryć nowe wycieki pamięci.
  • Testy A/B nowej funkcji, aby ocenić jej wpływ na pamięć i wykryć wycieki pamięci.
  • Korelacja wykorzystania pamięci z czasem trwania sesji, aby sprawdzić, czy występują wycieki pamięci.
  • Korelacja wykorzystania pamięci z danymi użytkowników, aby poznać ogólny wpływ wykorzystania pamięci.

Zgodność z przeglądarką

Browser Support

  • Chrome: 89.
  • Edge: 89.
  • Firefox: not supported.
  • Safari: not supported.

Source

Obecnie interfejs API jest obsługiwany tylko w przeglądarkach opartych na Chromium, począwszy od Chrome 89. Wynik interfejsu API jest w dużym stopniu zależny 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ą wykluczyć niektóre regiony pamięci z rozliczeń, jeśli prawidłowe rozliczenie jest zbyt kosztowne lub niemożliwe. Dlatego nie można porównywać wyników w różnych przeglądarkach. Porównywanie wyników ma sens tylko w przypadku tej samej przeglądarki.

Korzystanie z interfejsu performance.measureUserAgentSpecificMemory()

Wykrywanie funkcji

Funkcja performance.measureUserAgentSpecificMemory będzie niedostępna lub może się nie powieść z powodu błędu SecurityError, jeśli środowisko wykonawcze nie spełnia wymagań bezpieczeństwa dotyczących zapobiegania wyciekom informacji z różnych źródeł. Opiera się ona na izolacji z różnych źródeł, którą strona internetowa może aktywować ustawiając nagłówki COOP i COEP.

Obsługę 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 odśmiecania pamięci, co oznacza, że interfejs API nie rozwiązuje obietnicy wyniku od razu, ale czeka na następne odśmiecanie pamięci.

Wywołanie interfejsu API wymusza odśmiecanie pamięci po upływie określonego czasu oczekiwania, który obecnie wynosi 20 sekund, ale może nastąpić wcześniej. Uruchomienie Chrome z flagą wiersza poleceń --enable-blink-features='ForceEagerMeasureMemory' zmniejsza limit czasu do zera i jest przydatne do lokalnego debugowania i testowania.

Przykład

Zalecane użycie interfejsu API polega na zdefiniowaniu 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 okresowe próbkowanie, np. co M minut. Wprowadza to jednak błąd do danych, ponieważ szczyty wykorzystania pamięci mogą występować między próbkami.

Poniższy przykład pokazuje, jak wykonywać nieobciążone pomiary pamięci za pomocą procesu Poissona, który gwarantuje, że próbki mogą wystąpić w dowolnym momencie (prezentacja, źródło).

Najpierw zdefiniuj funkcję, która planuje następny pomiar 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 wykonywany był 1 pomiar. Jeśli interesuje Cię matematyka stojąca za tą funkcją, zapoznaj się z rozkładem wykładniczym.

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 pomiar.

// 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']
    },
  ],
}

Szacunkowe łączne wykorzystanie pamięci jest zwracane w polu bytes. Ta wartość jest w dużym stopniu 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 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 zidentyfikowanych przez adres 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ń opartych na konkretnej przeglądarce. Na przykład niektóre przeglądarki mogą zwracać pustą listę breakdown lub pustą listę attribution. Inne przeglądarki mogą zwracać wiele wpisów w attribution, co oznacza, że nie udało im się rozróżnić, który z tych wpisów jest właścicielem pamięci.

Prześlij opinię

Grupa społecznościowa ds. wydajności w internecie i zespół Chrome chętnie poznają Twoje opinie i doświadczenia związane z interfejsem Web Performance Community Group i zespołem Chrome chętnie poznają Twoje opinie i doświadczenia związane z interfejsem to hear about your thoughts and experiences with performance.measureUserAgentSpecificMemory().

Podziel się opinią o projekcie interfejsu API

Czy coś w interfejsie API nie działa zgodnie z oczekiwaniami? A może brakuje właściwości, które są potrzebne do zrealizowania Twojego pomysłu? Zgłoś problem ze specyfikacją w repozytorium GitHub performance.measureUserAgentSpecificMemory() lub dodaj swoje uwagi do istniejącego problemu.

Zgłoś problem z implementacją

Czy udało Ci się znaleźć błąd w implementacji 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, proste instrukcje odtworzenia błędu i ustaw Komponenty na Blink>PerformanceAPIs.

Okaż wsparcie

Czy planujesz używać interfejsu performance.measureUserAgentSpecificMemory()? Twoje publiczne wsparcie pomaga zespołowi Chrome ustalać priorytety funkcji i pokazuje innym dostawcom przeglądarek, jak ważne jest ich wsparcie. Wyślij tweeta do @ChromiumDev i daj nam znać, gdzie i jak używasz tej funkcji.

Przydatne linki

Podziękowania

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