Gesamtspeichernutzung Ihrer Webseite mit measureUserAgentSpecificMemory() überwachen

Hier erfahren Sie, wie Sie die Speichernutzung Ihrer Webseite in der Produktion messen, um Regressionen zu erkennen.

Brendan Kenny
Brendan Kenny
Ulan Degenbaev
Ulan Degenbaev

Browser verwalten den Arbeitsspeicher von Webseiten automatisch. Wenn eine Webseite ein Objekt erstellt, weist der Browser im Hintergrund einen Speicherblock zu, um das Objekt zu speichern. Da der Arbeitsspeicher eine endliche Ressource ist, führt der Browser eine automatische Speicherbereinigung durch, um zu erkennen, wann ein Objekt nicht mehr benötigt wird, und um den zugrunde liegenden Speicherblock freizugeben.

Die Erkennung ist jedoch nicht perfekt. Das Halteproblem von Alan Turing hat bewiesen, dass eine perfekte Erkennung unmöglich ist. Daher nähern Browser das Konzept „Ein Objekt wird benötigt“ dem Konzept „Ein Objekt ist erreichbar“ an. Wenn die Webseite ein Objekt nicht über seine Variablen und die Felder anderer erreichbarer Objekte erreichen kann, kann der Browser das Objekt sicher freigeben. Der Unterschied zwischen diesen beiden Konzepten führt zu Speicherlecks, wie im folgenden Beispiel veranschaulicht.

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

Hier wird das größere Array b nicht mehr benötigt, aber der Browser gibt es nicht frei, da es im Callback noch über object.b erreichbar ist. Der Speicher des größeren Arrays wird also nicht freigegeben.

Speicherlecks sind im Web weit verbreitet, wie diese Studie zeigt. Es ist leicht, ein Speicherleck zu verursachen, indem man vergisst, einen Event-Listener abzumelden, versehentlich Objekte aus einem iFrame erfasst, einen Worker nicht schließt, Objekte in Arrays ansammelt usw. Wenn eine Webseite Speicherlecks hat, steigt ihre Speichernutzung im Laufe der Zeit und die Webseite wirkt auf die Nutzer langsam und aufgebläht.

Der erste Schritt zur Lösung dieses Problems besteht darin, es zu messen. Mit der neuen performance.measureUserAgentSpecificMemory() API können Entwickler die Speichernutzung ihrer Webseiten in der Produktion messen und so Speicherlecks erkennen, die bei lokalen Tests nicht erkannt werden.

Wie unterscheidet sich performance.measureUserAgentSpecificMemory() von der alten performance.memory API?

Wenn Sie mit der vorhandenen, nicht standardmäßigen performance.memory API vertraut sind, fragen Sie sich vielleicht, wie sich die neue API davon unterscheidet. Der Hauptunterschied besteht darin, dass die alte API die Größe des JavaScript-Heaps zurückgibt, während die neue API den von der Webseite verwendeten Arbeitsspeicher schätzt. Dieser Unterschied wird wichtig, wenn Chrome denselben Heap mit mehreren Webseiten (oder mehreren Instanzen derselben Webseite) teilt. In solchen Fällen kann das Ergebnis der alten API beliebig abweichen. Da die alte API in implementierungsspezifischen Begriffen wie „Heap“ definiert ist, ist eine Standardisierung aussichtslos.

Ein weiterer Unterschied besteht darin, dass die neue API die Speichermessung während der automatischen Speicherbereinigung durchführt. Dadurch wird das Rauschen in den Ergebnissen reduziert, es kann jedoch eine Weile dauern, bis die Ergebnisse generiert werden. Andere Browser können die neue API auch ohne Garbage Collection implementieren.

Empfohlene Anwendungsfälle

Die Speichernutzung einer Webseite hängt vom Zeitpunkt der Ereignisse, Nutzeraktionen und Garbage Collections ab. Aus diesem Grund ist die Memory Measurement API für die Aggregation von Speichernutzungsdaten aus der Produktion vorgesehen. Die Ergebnisse einzelner Aufrufe sind weniger nützlich. Beispiele für Anwendungsfälle:

  • Regressionserkennung während der Einführung einer neuen Version der Webseite, um neue Speicherlecks zu erkennen.
  • A/B-Tests einer neuen Funktion, um die Auswirkungen auf den Arbeitsspeicher zu bewerten und Speicherlecks zu erkennen.
  • Korrelieren der Speichernutzung mit der Sitzungsdauer, um das Vorhandensein oder Fehlen von Speicherlecks zu überprüfen.
  • Korrelation der Arbeitsspeichernutzung mit Nutzermesswerten, um die Gesamtauswirkungen der Arbeitsspeichernutzung zu verstehen.

Browser-Kompatibilität

Browser Support

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

Source

Derzeit wird die API nur in Chromium-basierten Browsern ab Chrome 89 unterstützt. Das Ergebnis der API ist stark von der Implementierung abhängig, da Browser Objekte im Arbeitsspeicher unterschiedlich darstellen und die Speichernutzung unterschiedlich schätzen. Browser können einige Speicherbereiche von der Abrechnung ausschließen, wenn eine ordnungsgemäße Abrechnung zu teuer oder nicht möglich ist. Daher können die Ergebnisse nicht browserübergreifend verglichen werden. Es ist nur sinnvoll, die Ergebnisse für denselben Browser zu vergleichen.

performance.measureUserAgentSpecificMemory() verwenden

Feature-Erkennung

Die Funktion performance.measureUserAgentSpecificMemory ist nicht verfügbar oder schlägt möglicherweise mit einem SecurityError fehl, wenn die Ausführungsumgebung die Sicherheitsanforderungen zur Verhinderung von ursprungsübergreifenden Informationslecks nicht erfüllt. Sie basiert auf der ursprungsübergreifenden Isolation, die eine Webseite durch Festlegen von COOP- und COEP-Headern aktivieren kann.

Die Unterstützung kann zur Laufzeit erkannt werden:

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);
}

Lokales Testen

Chrome führt die Speichermessung während der automatischen Speicherbereinigung durch. Das bedeutet, dass die API das Ergebnis-Promise nicht sofort auflöst, sondern auf die nächste automatische Speicherbereinigung wartet.

Durch den Aufruf der API wird nach einer bestimmten Zeitüberschreitung, die derzeit auf 20 Sekunden festgelegt ist, eine automatische Speicherbereinigung erzwungen. Dies kann jedoch auch früher geschehen. Wenn Sie Chrome mit dem Befehlszeilen-Flag --enable-blink-features='ForceEagerMeasureMemory' starten, wird das Zeitlimit auf null reduziert. Das ist nützlich für lokales Debugging und Tests.

Beispiel

Die empfohlene Verwendung der API besteht darin, einen globalen Speichermonitor zu definieren, der die Speichernutzung der gesamten Webseite erfasst und die Ergebnisse zur Aggregation und Analyse an einen Server sendet. Die einfachste Methode ist die regelmäßige Stichprobenerhebung, z. B. alle M Minuten. Dies führt jedoch zu einer Verzerrung der Daten, da zwischen den Stichproben Speicherspitzen auftreten können.

Das folgende Beispiel zeigt, wie Sie unvoreingenommene Speichermessungen mit einem Poisson-Prozess durchführen, der dafür sorgt, dass Stichproben zu jedem Zeitpunkt gleich wahrscheinlich sind.

Definieren Sie zuerst eine Funktion, die die nächste Arbeitsspeichermessung mit setTimeout() in einem zufälligen Intervall plant.

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);
}

Die Funktion measurementInterval() berechnet ein zufälliges Intervall in Millisekunden, sodass im Durchschnitt alle fünf Minuten eine Messung erfolgt. Weitere Informationen zur Mathematik hinter der Funktion finden Sie unter Exponentialverteilung.

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

Schließlich ruft die asynchrone performMeasurement()-Funktion die API auf, zeichnet das Ergebnis auf und plant die nächste Messung.

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();
}

Beginne dann mit der Messung.

// Start measurements.
scheduleMeasurement();

Das Ergebnis könnte so aussehen:

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

Die Schätzung der gesamten Speichernutzung wird im Feld bytes zurückgegeben. Dieser Wert ist stark von der Implementierung abhängig und kann nicht browserübergreifend verglichen werden. Es kann sich sogar zwischen verschiedenen Versionen desselben Browsers ändern. Der Wert umfasst den JavaScript- und DOM-Arbeitsspeicher aller iFrames, zugehörigen Fenster und Web-Worker im aktuellen Prozess.

Die Liste breakdown enthält weitere Informationen zum verwendeten Arbeitsspeicher. Jeder Eintrag beschreibt einen Teil des Arbeitsspeichers und ordnet ihn einer Reihe von Fenstern, iFrames und Workern zu, die durch eine URL identifiziert werden. Im Feld types werden die implementierungsspezifischen Speichertypen aufgeführt, die mit dem Speicher verknüpft sind.

Es ist wichtig, alle Listen allgemein zu behandeln und keine Annahmen auf der Grundlage eines bestimmten Browsers fest zu programmieren. Einige Browser geben beispielsweise ein leeres breakdown oder ein leeres attribution zurück. Andere Browser geben möglicherweise mehrere Einträge in attribution zurück, was darauf hindeutet, dass sie nicht unterscheiden konnten, welcher dieser Einträge den Arbeitsspeicher besitzt.

Feedback

Die Web Performance Community Group und das Chrome-Team würden sich über Feedback zu Ihren Erfahrungen mit performance.measureUserAgentSpecificMemory() freuen.

Erzählen Sie uns etwas über das API-Design.

Gibt es etwas an der API, das nicht wie erwartet funktioniert? Oder fehlen Properties, die Sie für die Umsetzung Ihrer Idee benötigen? Melden Sie ein Problem mit der Spezifikation im GitHub-Repository „performance.measureUserAgentSpecificMemory()“ oder fügen Sie Ihre Gedanken zu einem bestehenden Problem hinzu.

Problem mit der Implementierung melden

Haben Sie einen Fehler in der Chrome-Implementierung gefunden? Oder unterscheidet sich die Implementierung von der Spezifikation? Melden Sie einen Fehler unter new.crbug.com. Geben Sie so viele Details wie möglich an, stellen Sie eine einfache Anleitung zum Reproduzieren des Fehlers bereit und legen Sie Komponenten auf Blink>PerformanceAPIs fest.

Unterstützung zeigen

Planen Sie, performance.measureUserAgentSpecificMemory() zu verwenden? Ihre öffentliche Unterstützung hilft dem Chrome-Team, Funktionen zu priorisieren, und zeigt anderen Browseranbietern, wie wichtig es ist, sie zu unterstützen. Senden Sie einen Tweet an @ChromiumDev und teilen Sie uns mit, wo und wie Sie es verwenden.

Nützliche Links

Danksagungen

Vielen Dank an Domenic Denicola, Yoav Weiss und Mathias Bynens für die Überprüfung des API-Designs sowie an Dominik Inführ, Hannes Payer, Kentaro Hara und Michael Lippautz für die Code Reviews in Chrome. Ich danke auch Per Parker, Philipp Weis, Olga Belomestnykh, Matthew Bolohan und Neil Mckay für das wertvolle Nutzerfeedback, das die API erheblich verbessert hat.