Des Browsers Trickkiste

JavaScript-Applikationen mit Developer Tools debuggen und profilen [Teil 2]
Kommentare

Jeder Webentwickler kennt sie und doch nutzen nur wenige ihre Fähigkeiten. Die Rede ist von den Entwicklertools, die von den Webbrowsern zur Verfügung gestellt werden. Dieser Artikel soll Ihnen diese Sammlung äußerst mächtiger Werkzeuge näherbringen, können sie doch Großes leisten, wenn es um das Auffinden von Fehlern oder Performanceengpässen geht.

Im ersten Teil des Artikels haben wir die Browserkonsole und den Debugger betrachtet. Während die Konsole nützlich ist, um in ihr Ausgaben aus einer Applikation heraus zu erzeugen, können Sie ihre Anwendung mit Hilfe des Debuggers gezielt anhalten und sogar Schritt für Schritt durch ihren Code gehen. Doch auch diese nützlichen Tools stoßen an bestimmten Stellen an ihre Grenzen – im zweiten Teil stellen wir Ihnen darum weitere Funktionen der Entwicklertools vor, die hier weiterhelfen.

Die gute Nachricht zuerst: Solange Sie nicht auf einem Internet Explorer 6 arbeiten müssen, können Sie auf nahezu jedem gängigen Browser auf einen ähnlich umfangreichen Satz von Entwicklerwerkzeugen zurückgreifen. Im Zuge dieses Artikels stelle ich Ihnen stellvertretend die Chrome Developer Tools vor. Die Funktionalität finden Sie allerdings auch in ähnlicher Form in Firefox oder im Internet Explorer. Für die Details zu den jeweiligen Features möchte ich Sie allerdings auf die Dokumentation Ihres Browsers verweisen.

Netzwerkkommunikation

Wenn es um die Performance von Webseiten geht, lautet eine der Best Practices, dass so wenige Requests wie möglich notwendig sind, um die Seite darzustellen. Warum das so ist, lässt sich sehr leicht und anschaulich demonstrieren. Öffnen Sie zunächst Ihre Developer Tools und aktivieren Sie den Network-Tab. Danach öffnen Sie eine beliebige Seite und sehen, was passiert.

Im Network-Tab werden sämtliche Requests, die in Ihrem aktuellen Browserfenster abgesetzt werden, aufgezeichnet. Ein Request besteht dabei nicht nur aus den übermittelten Daten, sondern aus zahlreichen zusätzlichen Phasen:

  • Zunächst muss Ihr Browser auf eine freie Verbindung warten. Die Beschränkung der möglichen parallelen Verbindungen zu einer Domain unterscheidet sich von Browser zu Browser: von beispielsweise sechs Verbindungen in Chrome bis hin zu fünfzehn Verbindungen in Firefox. Weitere beschränkende Elemente können unter anderem Proxyserver auf dem Weg zum Zielserver sein.
  • Ist der Weg frei, wird es unter Umständen erforderlich, dass der Browser einen DNS-Lookup durchführt.
  • Sobald die Zieladresse vorliegt, beginnt die Verbindung zum Zielserver.
  • Nachdem die Verbindung steht, sendet der Browser seine Anfrage an den Server. Dies kann beispielsweise ein HTTP-GET-Request auf einen bestimmten Pfad mit gewissen HTTP-Header-Informationen sein.
  • Danach beginnt die Zeitspanne, in der der Browser auf die Antwort des Servers wartet. In dieser Zeit bearbeitet der Server die Anfrage, generiert die Antwort und sendet diese zurück zum Browser.
  • Sobald der Browser das erste Byte der Antwort erhält, beginnt die letzte Phase des Requests, nämlich das Empfangen der Daten.

Für jede Anfrage an einen Server fallen in der Regel alle diese Phasen an. Aus diesem Grund ist es erstrebenswert, möglichst wenige Anfragen zu stellen, da sich der Mehraufwand dann lediglich auf den letzten Teil beschränkt und die zu übermittelnden Nutzdaten mehr werden.

Um also die Performance Ihrer Webseite zu verbessern, sollten Sie alle Ressourcen in so wenige Dateien wie möglich zusammenfassen und aus diesen unnötige Zeichen entfernen. Für JavaScript bedeutet das, dass Sie Ihre Dateien mit einem Werkzeug wie UglifyJS kombinieren und minifizieren sollten. Ähnliche Tools existieren auch für CSS und HTML. Bei Bildern können Sie so genannte Sprites verwenden. Das sind Bilddateien, in denen mehrere Bilder gesammelt werden und lediglich ein Ausschnitt im Browser angezeigt wird. Außerdem sollten Sie den Cache Ihres Browsers verwenden, um Dateien dort speichern zu lassen und sie nur bei Änderungen erneut zu übermitteln.

Im Network-Tab können Sie verschiedenste Informationen wie beispielsweise die Art und Größe eines Requests ein- und ausblenden. Die wichtigsten Informationen finden Sie in der Timeline-Spalte. Richten Sie Ihren Mauszeiger auf eine Zeile, werden die Latenz- und Empfangszeiten eingeblendet. Bewegen Sie den Mauszeiger auf einen der farbigen Balken, werden Ihnen sämtliche Timinginformationen zu diesem Request in einem Pop-up-Fenster angezeigt.

In der Detailansicht eines Requests können Sie außerdem sämtliche verfügbare Informationen von den Anfragedaten über die Antwort bis hin zu den aufbereiteten Timinginformationen anzeigen lassen.

Sie können jedoch nicht nur über den Network-Tab auf diese Informationen zugreifen, sondern auch aus Ihrem JavaScript-Quellcode heraus. Mit einem Aufruf der Methode window.performance.getEntries erhalten Sie ein Array mit den detaillierten Performanceinformationen zu jedem Request.

An dieser Stelle gibt es nun mehrere Möglichkeiten, entweder Sie haben Performanceprobleme identifizieren und beheben können, oder Sie müssen feststellen, dass Ihre Schwierigkeiten gar nicht an der Kommunikation zum Server liegen. Sie müssen in diesem Fall also weiter suchen. Das nächste Hilfsmittel, das ich Ihnen nun vorstellen möchte, beschäftigt sich mit den Events, dem Speicherverbrauch und der Renderingperformance Ihrer Applikation über die Laufzeit.

Timeline

Der Timeline-Tab der Developer Tools (Abb. 2) beinhaltet nahezu alle Informationen, die Sie für eine Laufzeitbewertung Ihrer Applikation benötigen. So können Sie sehen, welche Events im Zeitverlauf aufgetreten sind, wie sich die Speichernutzung Ihrer Applikation entwickelt hat und wie es um die Renderingperformance Ihrer Applikation steht.

Im Timeline-Tab können Sie zwischen dem Frames-Mode und dem normalen Standard-Timeline-Mode über eine Schaltfläche hin- und herwechseln. Der Frames-Mode stellt die Renderinginformationen dar, der Standard-Modus beinhaltet die Speichernutzung und die Darstellung der Events.

Abb. 2: Der Timeline-Tab der Developer Tools

Events im Browser

Als Frontend-Entwickler denken Sie bestimmt zunächst an Maus- und Tastaturevents, wenn Sie Events im Browserkontext hören. Die Events, die Sie hier betrachten können, umfassen jedoch mehr Arten von Events und gehen dabei wesentlich tiefer in Ihren Browser – bis hin zur Garbage Collection und zur Berechnung des Layouts. Mit den hier vorgestellten Hilfsmitteln können Sie herausfinden, welche Events besonders lange gedauert haben und wodurch Sie hervorgerufen wurden. Zusätzlich sehen Sie, zu welchem Zeitpunkt die Events DOMContentLoaded (lila Maker) und Window load (roter Marker) sowie der erste Zeichenvorgang (grüner Maker) stattgefunden haben. Die Developer Tools unterscheiden insgesamt vier verschiedene Arten von Events mit eigener Farbcodierung: Loading-Events (blau), Scripting-Events (gelb), Rendering-Events (lila) und Painting-Events (grün). Ihre gewohnten Browserevents finden Sie übrigens unter der Kategorie Scripting-Events. Die Bedeutung der einzelnen Kategorien ist wie folgt:

  • Loading-Events beinhalten sämtliche Ereignisse, die beim Laden einer Seite auftreten, wie zum Beispiel das Senden von Requests und das Empfangen der entsprechenden Response oder das Parsen des HTML-Codes.
  • Scripting-Events repräsentieren sämtliche Logik, die im Rahmen Ihrer Applikation ausgeführt wird, von der Ausführung von JavaScript-Routinen über die Garbage Collection von JavaScript bis hin zu DOM-Events.
  • Rendering-Events sorgen für die Berechnung des Layouts, falls sich durch die Veränderung der Fensterproportionen oder das Einfügen neuer Elemente Änderungen ergeben.
  • Painting-Events schließlich werden ausgelöst, wenn der Browser Elemente für den Benutzer sichtbar darstellt.

Im mittleren Teil des Fensters sehen Sie die Record-View und können hier auf einzelne Events zugreifen. Der farbige Balken symbolisiert die Zeit, die das Event in Anspruch genommen hat. Der dunklere Anteil stellt die Zeit des Events selbst, der hellere die Zeit der Kind-Events dar. Je nach Typ des Events stehen Ihnen dann auch noch zusätzliche Informationen zur Verfügung. Die Daten Self Time und Start Time sehen Sie jedoch bei jedem Event. Start Time gibt den Zeitpunkt an, an dem das Event ausgelöst wurde, und Self Time bezeichnet die Zeitspanne, die das Event in Anspruch genommen hat. In dieser Zeit ist die Abarbeitung der Kind-Events nicht enthalten. Ein kleiner Pfeil vor der Bezeichnung des Events in der linken Spalte zeigt Ihnen an, ob das jeweilige Event über Kind-Events verfügt.

Wählen Sie ein bestimmtes Event aus, erhalten Sie in der rechten Spalte zusätzliche Informationen wie beispielsweise die Zeilennummer einer Skriptausführung oder den Statuscode einer HTTP-Response. Mit diesen Informationen erhalten Sie bereits einen ersten Überblick, in welcher Kategorie Ihre Applikation die meiste Zeit verbringt, was ein erster Anhaltspunkt für eine Verbesserung der Applikation sein kann.

Speicherauslastung

Nicht nur Events spielen eine wichtige Rolle, wenn es um die Performance Ihrer Applikation geht. Auch die Auslastung des verfügbaren Speichers ist relevant. Vor allem bei Single-Page-Applikationen führen Memory Leaks zu erheblichen Problemen. Sie sorgen dafür, dass der Arbeitsspeicher nicht mehr durch den Garbage Collector freigegeben wird, was das System in seiner Gesamtheit über längere Zeit langsam macht.

Im oberen Teil des Timeline-Tabs sehen Sie eine kombinierte Grafik von Events und Speicherauslastung. Dies lässt Rückschlüsse zu, welche Events den Speicherverbrauch ansteigen lassen. Identifizieren Sie eine solche Stelle, können Sie hier mit einem detaillierten Profiling beginnen. Die Timeline kann Ihnen lediglich für eine grobe Eingrenzung des Problems und als Einstieg ins Profiling dienen. In dieser Übersicht können Sie dann durch eine Selektion des Zeitraums genauere Informationen gewinnen.

Für den gewählten Zeitraum sehen Sie im unteren Drittel des Timeline-Tabs die Aufschlüsselung des Speicherverbrauchs auf JavaScript Heap, Dokumente, DOM-Knoten und EventListener. Sämtliche dieser Elemente belegen Speicherplatz und wurden noch nicht durch den Garbage Collector freigegeben. Sie haben allerdings die Möglichkeit, über eine Schaltfläche im Timeline-Tab den Garbage Collector manuell anzustoßen und so den Speicher aufräumen zu lassen.

Für eine flüssige Ausführung einer Applikation sind jedoch nicht nur Speicherverbrauch und Events von Bedeutung, sondern auch die Darstellung der Inhalte in der passenden Frequenz.

Frames

Die Metrik, die im Frames-Modus am wichtigsten ist, ist die Anzahl der Frames per Second oder kurz FPS. Bei einem normalen Bildschirm liegt diese Rate bei sechzig. Sie haben deshalb 1 000 Millisekunden/sechzig Frames, also 16,67 Millisekunden Zeit, einen Frame vorzubereiten und darzustellen, damit dem Benutzer das Gefühl einer flüssigen Animation vermittelt wird. Um dies zu prüfen, wechseln Sie in den Frames-Modus.

Die Übersicht im oberen Teil des Timeline-Tabs wechselt dann von der Darstellung des zeitlichen Verlaufs von Events und Speicherauslastung in ein Balkendiagramm. Quer zu diesen Balken verlaufen zwei graue Linien. Die untere Linie ist der Marker für 60 FPS, die obere stellt die 30-FPS-Marke dar. Ihr Ziel sollte es sein, stets unter der 60er-Marke zu bleiben. Stellen Sie bei der Durchsicht dieser Grafik erhebliche Abweichungen fest, können Sie wiederum den Zeitraum eingrenzen und erhalten in den beiden unteren Sektionen des Tabs die bereits beschriebenen Informationen, mit denen Sie herausfinden können, welche Events Ihre Renderingzeit in die Länge ziehen.

Die Farbkodierung bei den Balken ist die gleiche wie diejenige, die schon bei den Events zum Einsatz gekommen ist. Allerdings wird dieses Diagramm um zwei weitere Farben ergänzt: Ein grauer Balken bedeutet eine Aktion, die in keine der anderen Kategorien passt, und ein weißer Balken bedeutet, dass hier nichts zu tun war.

Wie bereits erwähnt, dient der Timeline-Tab lediglich zum Einstieg in das Profiling einer Applikation und zur Eingrenzung eines potenziellen Problems. Wesentlich weiter kommen Sie dann im Profiles-Tab.

Profiles

Im Profiles-Tab geht es ans Eingemachte (Abb. 3). Hier können Sie zwei verschiedene Profile Ihrer Applikation erstellen. Das ist zum einen das CPU-Profile, also welche Aktion wie viel Rechenzeit benötigt, und zum anderen das Memory-Profile.

Abb. 3: Der Profiles-Tab der Developer Tools

CPU-Profile

Die Aufzeichnung eines CPU-Profiles gestaltet sich relativ einfach. Sie wählen im Profiles-Tab „Collect JavaScript CPU Profile“ aus und aktivieren dann links oben den Record-Button. Danach können Sie entweder die Seite neu laden, um den Aufbau Ihrer Applikation zu profilen, oder eine bestimmte Aktion ausführen, um diese genauer zu untersuchen. Sobald Sie die gewünschte Aktion aufgenommen haben, sollten Sie die Aufnahme stoppen, um das Profil nicht zu groß und unübersichtlich werden zu lassen.

Die Anzeige des Profils gliedert sich in drei sortierbare Spalten. Die erste Spalte, Self Time, gibt an, wie lange es gedauert hat, die jeweilige Funktion abzuarbeiten. Hier ist nicht die Zeit enthalten, die von dieser Funktion aufgerufene Funktionen benötigt haben. Die Total Time summiert die Zeit, die die Funktion und alle daraus aufgerufenen Funktionen benötigt haben. In der dritten Spalte sehen Sie schließlich den Funktionsnamen und die Zeile, in der die Funktion im Quellcode zu finden ist. Beinhaltet der Funktionsaufruf weitere Funktionsaufrufe, können Sie diese über ein Pfeilsymbol vor dem Namen ausklappen.

Sie können das CPU-Profile auf drei verschiedene Arten anzeigen lassen. Die „Heavy“-Ansicht besteht aus den drei beschriebenen Spalten und ist von unten nach oben sortiert und enthält sehr viele Einträge. Die „Tree“-Ansicht enthält relativ wenige Einträge, in die Sie hineinnavigieren können. Die dritte Ansicht schließlich besteht aus einer grafischen Anzeige des CPU-Verlaufs. Hier können Sie wieder Ausschnitte betrachten und erhalten im unteren Teil des Fensters weitere Informationen über Funktionsaufrufe, wie die Self Time und Total Time, sowie aggregierte Versionen beider Zahlen. Die angezeigten farbigen Blöcke repräsentieren dabei den jeweiligen Callstack.

Memory-Profile

Mit dem Memory Profiler können Sie ein Abbild der aktuellen Speicherbelegung Ihrer Applikation anfertigen. Hierfür wählen Sie einfach „Take Heap Snapshot“ aus, warten kurz und sehen eine tabellarische Auflistung der Speichereinträge. Die Spalten sind jeweils sortierbar und enthalten die folgenden Werte:

  • Constructor: Dieser bezeichnet die Herkunft der Objekte. Dies kann entweder ein Sammelwert wie (system), (string) oder (array) sein oder aber konkrete Werte wie HTMLDivElement oder WeakSet.
  • Distance: Die Distance bezeichnet den Weg vom Objekt bis zum Wurzelelement, also die Schachtelungstiefe.
  • Objects Count: Dieser Wert stellt die Summe der unter diesem Überpunkt zusammengefassten Objekte dar.
  • Shallow Size: Dies ist der Wert, der durch das Objekt selbst belegt ist. Dieser Wert setzt sich aus der Beschreibung und den direkten Werten des Objekts zusammen. Besteht das Objekt aus anderen Objekten, belegen diese wiederum selbst Speicherplatz.
  • Retained Size: Dieser Wert ist für Sie als Entwickler besonders interessant, denn er gibt an, wie viel Speicherplatz freigegeben wird, wenn Sie dieses Objekt löschen. Er enthält die Objekte, die dann durch den Garbage Collector entfernt werden, da keine Referenzen mehr auf sie bestehen.

Gehen Sie von einer kleinen bis mittelgroßen Applikation aus, kann der Memory-Snapshot viele tausend Einträge enthalten und wird sehr schnell unübersichtlich. Das bedeutet, dass Sie schon sehr genau wissen müssen, wonach Sie suchen, um den Snapshot erfolgreich verwenden zu können.

Zum Auffinden von Memory Leaks existiert aus diesem Grund eine weitere Art des Profilings, nämlich die Aufzeichnung der Heap Allocations. Im Gegensatz zum Snapshot ist dies nicht nur eine Momentaufnahme, sondern eine Aufzeichnung über Zeit. Die Ansicht ist hier in drei Teile unterteilt. Im oberen Teil sehen Sie ein Balkendiagramm im Zeitverlauf mit den jeweiligen Ausschlägen, die den Speicherverbrauch angeben. Für die ausgewählte Zeitspanne werden in der Mitte des Fensters die gleichen Informationen angezeigt, wie Sie sie schon aus dem Heap-Snapshot kennen. Im unteren Teil sehen Sie schließlich die Detailinformationen, wenn Sie eine Zeile im mittleren Teil ausgewählt haben.

Sie sollten also im oberen Teil gezielt nach Ausschlägen im Balkendiagramm suchen und diese anschließend analysieren. In den meisten Fällen zeichnet sich dann schon die Codestelle ab, die das Speicherproblem verursacht.

Zusammenfassung

Wenn es darum geht, einen Fehler in einer bestehenden Applikation zu finden oder die Ausführung der Applikation zu beschleunigen, führt kein Weg um die Developer Tools der Browser vorbei. Diese Werkzeuge unterstützen Sie bei der Inspektion der Laufzeitumgebung Ihrer Applikation sowie bei der Analyse des Verbrauchs der Rechenleistung und Speicherbelegung. Je nachdem, wo Ihre Probleme liegen, gelangen Sie mithilfe der Browser Tools bis tief in die Internas Ihres Browsers.

Dieser Artikel stellt allerdings nur einen Einstieg in die erweiterten Möglichkeiten Ihres Browsers dar. Für den weiteren Weg in das Debugging und Profiling möchte ich Ihnen die meist sehr umfangreiche und detaillierte Dokumentation Ihres Browsers ans Herz legen.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -