trace.h

Die Emscripten Tracing API bietet einige nützliche Funktionen, um besser zu sehen, was in Ihrer Anwendung vor sich geht, insbesondere im Hinblick auf die Speichernutzung (die ansonsten traditionellen Browser-Performance-Tools nicht zur Verfügung steht).

Die Tracing API kann mit einem benutzerdefinierten Sammlungsserver (weitere Details finden Sie unter Server ausführen) oder mit dem Google Web Tracing Framework kommunizieren. Bei der Kommunikation mit dem Google Web Tracing Framework wird eine Teilmenge der verfügbaren Daten gesammelt.

Verwendung

Compiler-Interaktion

Wenn Sie die Tracing-API verwenden, sollten Sie bei jedem Kompilier- und Linkschritt --tracing an emcc übergeben. Dies schließt automatisch die Bibliotheksdatei library_trace.js ein und setzt das Präprozessor-Flag __EMSCRIPTEN_TRACING__. Wenn Sie clang direkt aufrufen, um Ihren C-/C++-Code zu erstellen, dann sollten Sie -D__EMSCRIPTEN_TRACING__ beim Erstellen des Codes übergeben. Wenn das Präprozessor-Flag __EMSCRIPTEN_TRACING__ nicht definiert ist, wird die Implementierung der Tracing-API durch inline-leere Stubs bereitgestellt.

Da das Aktivieren des Tracings die Implementierung von dlmalloc.c in der libc-Implementierung ändert, wird außerdem empfohlen, den Cache manuell zu leeren, bevor Sie zur Verwendung der Tracing-API wechseln. Wenn Sie dies nicht tun, erhalten Sie keine vollständigen Aufteilungsdetails. Sie können den Cache mit diesem emcc-Befehl leeren

emcc --clear-cache

Initialisierung und Beendigung

Zur Initialisierung der Tracing-API rufen Sie emscripten_trace_configure() auf

emscripten_trace_configure("http://127.0.0.1:5000/", "MyApplication");

Wenn Sie die Tracing-API einfach mit dem Google Web Tracing Framework verwenden möchten, können Sie stattdessen einfach emscripten_trace_configure_for_google_wtf() aufrufen.

emscripten_trace_configure_for_google_wtf();

Wenn Sie das Konzept eines Benutzernamens haben oder auf andere Weise einen bestimmten Benutzer der Anwendung identifizieren können, kann die Weitergabe dieser Informationen an die Tracing-API die Identifizierung von Sitzungen auf dem Collector-Server erleichtern.

emscripten_trace_set_session_username(username);

Um sie beim Beenden der Anwendung herunterzufahren, rufen Sie einfach emscripten_trace_close() auf.

emscripten_trace_close();

Kontexte

Kontexte sind eine Möglichkeit, der Tracing-API mitzuteilen, welcher Teil Ihrer Anwendung gerade ausgeführt wird. Kontexte werden effektiv als Stapel von aktuellen Kontexten verwaltet.

Ein Kontext könnte etwas so Großes wie „Physik laufen lassen“ oder so Kleines wie „Animationen für Entität X aktualisieren“ sein.

Die Granularität des Kontextstapels liegt im Ermessen des Teams, das seine Anwendung instrumentiert. Einige Anwendungen finden fein granulierte Kontexte nützlicher, während andere mit größeren Kontexten bequemer sind.

Anstatt bei jedem Tracing-Aufruf einen Stack-Trace zu erhalten, können wir oft den aktuellen Kontextstapel betrachten und diesen stattdessen aufzeichnen, was viel billiger ist.

Wenn Kontexte vollständig vom Server implementiert sind, werden sie auch verwendet, um zu verfolgen, wie viel Zeit in jedem Kontext verbracht wird (ein primitiver Profiling-Mechanismus), sowie wie viel Speicher zugewiesen und freigegeben wurde, während der Kontext aktiv war. Dies sollte eine gute Vorstellung davon geben, welche Teile Ihrer Anwendung mehr Speicher verwenden oder viel Fluktuation (und möglicherweise Heap-Fragmentierung) erzeugen.

Das Aufzeichnen von Kontext-Eintritt und -Austritt ist einfach.

emscripten_trace_enter_context("Physics Update");
...
emscripten_trace_exit_context();

Frames

Es ist wichtig, den Beginn und das Ende Ihres Frames oder Event-Loops zu erfassen. Dies ermöglicht der Tracing-API, nützliche zusätzliche Analysen durchzuführen.

Das Starten einer Ereignisschleife ist so einfach wie

emscripten_trace_record_frame_start();

Und das Ende der Ereignisschleife zu notieren ist genauso einfach

emscripten_trace_record_frame_end();

Anmerkungen zu Zuweisungen

Jede Zuweisungs- und Freigabeoperation sollte aufgezeichnet werden. Idealerweise wird auch der Datentypname aufgezeichnet, dies muss jedoch derzeit manuell erfolgen.

Beim Bauen mit --tracing und einem geleerten Cache zeichnet die von Emscripten gebaute libc automatisch alle Aufrufe von malloc, realloc und free auf.

Was die Aufzeichnung des Datentypnamens betrifft, so können Sie, nachdem Sie den Speicher zugewiesen haben, die Adresse kommentieren.

emscripten_trace_annotate_address_type(model, "UI::Model");

Zusätzlich möchten einige Anwendungen die Größe des zusätzlichen Speichers einer Zuweisung zuordnen. Dies kann über emscripten_trace_associate_storage_size() erfolgen.

emscripten_trace_associate_storage_size(mesh, mesh->GetTotalMemoryUsage());

Gesamtspeichernutzung

Periodisch sollten das gesamte Heap-Layout und die Speichernutzung an die Trace-API gemeldet werden.

Dies geschieht mit 2 Aufrufen.

emscripten_trace_report_memory_layout();
emscripten_trace_report_off_heap_data();

Nachrichten protokollieren

Nachrichten können über die Emscripten Tracing API protokolliert und aufgezeichnet werden. Diese Nachrichten können sowohl einen Kanal als auch die eigentliche Nachricht enthalten. Der Kanalname hilft, Nachrichten innerhalb der Visualisierungsoberfläche zu kategorisieren und zu filtern. Sie sollten vermeiden, Speicher auf dem Heap zuzuweisen, während Sie eine Nachricht protokollieren.

emscripten_trace_log_message("Application", "Started");

Im Laufe der Zeit wird die Visualisierungsoberfläche verbessert, um Ihnen zu helfen, diese Protokollnachrichten besser mit anderen Ansichten, wie z.B. der Speichernutzung über die Zeit, zu korrelieren. Das Protokollieren von Nachrichten für Dinge, die große Mengen an Speicheraktivität verursachen können, wie das Laden eines neuen Modells oder Spielassets, ist sehr nützlich bei der Analyse von Speichernutzungsverhaltensmustern.

Aufgaben

Spezifische Aufgaben können aufgezeichnet und analysiert werden. Eine Aufgabe ist typischerweise eine Arbeitseinheit, die sich nicht wiederholt. Sie kann ausgesetzt oder blockiert werden, da Teile asynchron ausgeführt werden.

Ein Beispiel für eine Aufgabe ist das Laden eines Assets, das normalerweise Ketten von Callbacks beinhaltet.

Die Anwendung sollte Aufgaben-IDs (Ganzzahlen) verfolgen und sicherstellen, dass sie eindeutig sind.

Die Aufgaben-ID muss nicht an jeden Trace-Aufruf übergeben werden, der Aufgaben beinhaltet, da die meisten Aufrufe auf der aktuellen Aufgabe arbeiten.

Aufgaben können gestartet und beendet werden mit

emscripten_trace_task_start(taskID, name);
emscripten_trace_task_end();

Wenn eine Aufgabe angehalten/blockiert wird, kann dies über notiert werden.

emscripten_trace_task_suspend("loading via HTTP");

Und wenn es fortgesetzt wird

emscripten_trace_task_resume(taskID, "parsing");

Es ist üblich, zusätzliche Daten mit der aktuellen Aufgabe zu verknüpfen, um sie später bei der Untersuchung von Aufgabendaten zu verwenden. Ein Beispiel hierfür wäre die URL eines geladenen Assets.

emscripten_trace_task_associate_data("url", url);

Fehlerberichterstattung

Von der Anwendung auftretende Fehler können der Tracing-API als unterstützender Dienst gemeldet werden.

emscripten_trace_report_error("Assertion failed: ...");

Diese Funktion ist als Hinweis auf die zukünftige Ausrichtung der Emscripten Tracing API enthalten.

Den Server ausführen

Design-Notizen

Client / Server Design

Die Emscripten Tracing API sammelt Daten aus instrumentiertem Code und übermittelt sie an einen Collector-Server. Der Server führt auch Datenanalysen durch und bietet eine Weboberfläche zur Anzeige der gesammelten Daten.

Dieses Client-/Server-Design soll es dem Tool ermöglichen, auf Hardware mit geringerer Leistung, bei der Speicher knapp sein könnte, wie z.B. 32-Bit-Windows-Maschinen, ohne Beeinträchtigung des Browsers zu laufen.

Dieses Design ermöglicht auch den Betrieb eines einzelnen Servers zur Datenerfassung von verschiedenen Clients.

Daten-Batching

Daten werden gebündelt und etwa ein- bis zweimal pro Sekunde in Blöcken an den Server gesendet. Dadurch muss nicht für jedes einzelne aufgezeichnete Ereignis eine neue Verbindung zum Server geöffnet werden.

Den Heap nicht stören

Bei der Verwendung der Emscripten Tracing API sollten Sie darauf achten, dass Sie keine Operationen ausführen, die den Heap stören würden. Sie sollten beispielsweise keinen String zuweisen, um ihn an emscripten_trace_log_message() zu übergeben, da dies dazu führen würde, dass die Zuweisung verfolgt wird und möglicherweise das Verhalten oder die Ergebnisse stört, die Sie analysieren möchten.

Aus diesem Grund hält die Emscripten Tracing API auch alle ihre eigenen Daten außerhalb des Emscripten Heaps und führt keine Schreibvorgänge in den Emscripten Heap durch.

Funktionen

void emscripten_trace_configure(const char *collector_url, const char *application)
Parameter
  • collector_url (const char*) – Die Basis-URL für den Collector-Server.

  • application (const char*) – Der Name der Anwendung, die getraced wird.

Rückgabetyp

void

Konfiguriert die Verbindung zum Collector-Server.

Dies sollte eines der ersten Dinge sein, die nach dem Start der Anwendung erledigt werden.

In den meisten Fällen wird die collector_url http://127.0.0.1:5000/ sein.

void emscripten_trace_configure_for_google_wtf(void)
Rückgabetyp

void

Konfigurieren Sie das Tracing so, dass es mit dem Google Web Tracing Framework kommuniziert.

Nicht alle Funktionen des Tracings sind in den Google WTF-Tools verfügbar. (Derzeit nur Kontexte, Protokollmeldungen und Markierungen.)

void emscripten_trace_set_enabled(bool enabled)
Parameter
  • enabled (bool) – Ob das Tracing aktiviert ist oder nicht.

Rückgabetyp

void

Legt fest, ob das Tracing aktiviert ist oder nicht. Die Verwendung dieser Option zum Deaktivieren des Tracings führt wahrscheinlich zu ungenauen Daten über die Speichernutzung.

void emscripten_trace_set_session_username(const char *username)
Parameter
  • username (const char*) – Der Benutzername der Person, die die Anwendung ausführt.

Rückgabetyp

void

Dies ist nützlich, wenn ein Collector-Server von mehreren Personen verwendet wird und Sie einzelne Sitzungen auf andere Weise als über ihre zeitgestempelte Sitzungs-ID identifizieren möchten.

Dies kann nach dem Start des Tracings festgelegt werden, es ist also in Ordnung, dies festzulegen, nachdem der Benutzer einen Anmelde- oder Authentifizierungsprozess durchlaufen hat.

void emscripten_trace_record_frame_start(void)
Rückgabetyp

void

Dies sollte am Anfang der Frame-/Ereignisschleife aufgerufen werden.

Der aktuelle Zeitstempel ist mit diesen Daten verknüpft.

Der Server verwendet dies, um Frame-Zeiten (und damit Frames pro Sekunde) zu verfolgen, sowie um Speicheroperationen zu erfassen, die während der Frame-Verarbeitung stattfinden.

void emscripten_trace_record_frame_end(void)
Rückgabetyp

void

Dies sollte am Ende der Frame-/Ereignisschleife aufgerufen werden.

Der aktuelle Zeitstempel ist mit diesen Daten verknüpft.

Der Server verwendet dies, um das Sammeln von Speicheroperationen und die verstrichene Zeit für den Frame zu beenden.

void emscripten_trace_log_message(const char *channel, const char *message)
Parameter
  • channel (const char*) – Die Kategorie des ausgegebenen Timeline-Ereignisses.

  • message (const char*) – Die Beschreibung des ausgegebenen Timeline-Ereignisses.

Rückgabetyp

void

Protokollieren Sie eine Nachricht. Dies ist nützlich, um Ereignisse oder Aktionen zu vermerken, die aufgetreten sind und die vorteilhaft mit der Speichernutzung oder Änderungen der Bildrate korreliert werden könnten.

Der aktuelle Zeitstempel ist mit diesen Daten verknüpft.

Der Server macht noch nicht genug mit diesen Daten. Dies wird sich in Zukunft verbessern.

void emscripten_trace_mark(const char *message)
Parameter
  • message (const char *) – Der Name der Markierung, die ausgegeben wird.

Rückgabetyp

void

Einen Marker in der Zeitleiste setzen. Dies ist hauptsächlich für die Verwendung mit dem Google Web Tracing Framework vorgesehen.

Der aktuelle Zeitstempel ist mit diesen Daten verknüpft.

void emscripten_trace_report_error(const char *error)
Parameter
  • error (const char*) – Die Fehlermeldung, die gemeldet wird.

Rückgabetyp

void

Die API ruft den aktuellen Aufrufstapel ab und fügt diesen in den Bericht an den Server ein.

Der aktuelle Zeitstempel ist mit diesen Daten verknüpft.

Dies könnte für verschiedene Dinge verwendet werden, einschließlich der Erfassung von JavaScript- und Web-Worker-Fehlern, sowie fehlgeschlagenen Zusicherungen oder anderen Laufzeitfehlern aus dem C/C++-Code.

void emscripten_trace_record_allocation(const void *address, int32_t size)
Parameter
  • address (const void*) – Zugewiesene Speicheradresse.

  • size (int32_t) – Größe des zugewiesenen Speicherblocks.

Rückgabetyp

void

Dies muss für jede einzelne Speicherzuweisung aufgerufen werden. Der beste Ort dafür ist die dlmalloc-Implementierung in Emscripten.

Der aktuelle Zeitstempel ist mit diesen Daten verknüpft.

void emscripten_trace_record_reallocation(const void *old_address, const void *new_address, int32_t size)
Parameter
  • old_address (const void*) – Alte Adresse des neu zugewiesenen Speicherblocks.

  • new_address (const void*) – Neue Adresse des neu zugewiesenen Speicherblocks.

  • size (int32_t) – Neue Größe des neu zugewiesenen Speicherblocks.

Rückgabetyp

void

Dies muss für jede einzelne Speicher-Reallokation aufgerufen werden. Der beste Ort dafür ist die dlmalloc-Implementierung in Emscripten.

Der aktuelle Zeitstempel ist mit diesen Daten verknüpft.

void emscripten_trace_record_free(const void *address)
Parameter
  • address (const void*) – Speicheradresse, die freigegeben wird.

Rückgabetyp

void

Dies muss für jede free-Operation aufgerufen werden. Der beste Ort dafür ist die dlmalloc-Implementierung in Emscripten.

Der aktuelle Zeitstempel ist mit diesen Daten verknüpft.

Es ist auch wichtig, dass dies nicht mehrmals für eine einzelne free-Operation aufgerufen wird.

void emscripten_trace_annotate_address_type(const void *address, const char *type)
Parameter
  • address (const void*) – Die Speicheradresse, die annotiert werden soll.

  • type (const char*) – Der Name des zugewiesenen Datentyps.

Rückgabetyp

void

Kommentieren Sie eine Adresse mit dem Namen des dort gespeicherten Datentyps. Dies wird vom Server verwendet, um zu analysieren, was sich im Speicher befindet.

void emscripten_trace_associate_storage_size(const void *address, int32_t size)
Parameter
  • address (const void*) – Die Speicheradresse, die annotiert werden soll.

  • size (int32_t) – Größe des dieser Zuweisung zugeordneten Speichers.

Rückgabetyp

void

Ordnen Sie dieser Adresse eine zusätzliche Speichermenge zu. Dies stellt nicht die Größe der Zuweisung selbst dar, sondern den zugehörigen Speicher, der bei der Betrachtung der Größe dieses Objekts berücksichtigt werden sollte.

Dieser zugeordnete Speicher ist anwendungsspezifisch.

Ein Beispiel ist, wenn ein Objekt einen Vektor oder String enthält, möchten Sie dies möglicherweise bei der Analyse der Speichernutzung berücksichtigen, und dies bietet eine Möglichkeit, den Server auf diesen zusätzlichen Speicher aufmerksam zu machen.

void emscripten_trace_report_memory_layout(void)
Rückgabetyp

void

Dies sollte regelmäßig aufgerufen werden, um die Nutzung des normalen Emscripten-Heaps zu melden. Dies liefert Details sowohl zum Stack als auch zur dynamischen Speichernutzung sowie zur gesamten Speichergröße.

Der aktuelle Zeitstempel ist mit diesen Daten verknüpft.

void emscripten_trace_report_off_heap_data(void)
Rückgabetyp

void

Dies sollte regelmäßig aufgerufen werden, um die Speichernutzung außerhalb des normalen Emscripten-Heaps zu melden. Dies wird derzeit verwendet, um die OpenAL-Speichernutzung zu melden.

Der aktuelle Zeitstempel ist mit diesen Daten verknüpft.

Der Server zeigt diese Daten noch nicht an.

void emscripten_trace_enter_context(const char *name)
Parameter
  • name (const char*) – Kontextname.

Rückgabetyp

void

Der aktuelle Zeitstempel ist mit diesen Daten verknüpft.

void emscripten_trace_exit_context(void)
Rückgabetyp

void

Der aktuelle Zeitstempel ist mit diesen Daten verknüpft.

void emscripten_trace_task_start(int task_id, const char *name);
Parameter
  • task_id (int) – Aufgaben-ID

  • name (const char*) – Aufgabenname

Rückgabetyp

void

Eine Aufgabe wird gestartet. Die Aufgaben-ID sollte über die gesamte Lebensdauer der Anwendung hinweg eindeutig sein. Sie sollte von der Anwendung verwaltet/verfolgt werden.

Der aktuelle Zeitstempel ist mit diesen Daten verknüpft.

void emscripten_trace_task_associate_data(const char *key, const char *value);
Parameter
  • key (const char*) – Schlüssel

  • value (const char*) – Wert

Rückgabetyp

void

Ordnet ein Schlüssel/Wert-Paar der aktuellen Aufgabe zu.

void emscripten_trace_task_suspend(const char *explanation);
Parameter
  • explanation (const char*) – Warum die Aufgabe unterbrochen wird.

Rückgabetyp

void

Die aktuelle Aufgabe wird unterbrochen.

Die Erklärung sollte den Grund für die Unterbrechung der Aufgabe angeben, damit diese Informationen beim Betrachten des Aufgabenverlaufs verfügbar gemacht werden können.

Der aktuelle Zeitstempel ist mit diesen Daten verknüpft.

void emscripten_trace_task_resume(int task_id, const char *explanation);
Parameter
  • task_id (int) – Aufgaben-ID

  • explanation (const char*) – Warum die Aufgabe fortgesetzt wird.

Rückgabetyp

void

Die durch task_id identifizierte Aufgabe wird fortgesetzt und zur aktuellen Aufgabe gemacht.

Die Erklärung sollte angeben, wozu die Aufgabe fortgesetzt wird, damit diese Informationen beim Betrachten des Aufgabenverlaufs verfügbar gemacht werden können.

Der aktuelle Zeitstempel ist mit diesen Daten verknüpft.

void emscripten_trace_task_end(void);
Rückgabetyp

void

Die aktuelle Aufgabe wird beendet.

Der aktuelle Zeitstempel ist mit diesen Daten verknüpft.

void emscripten_trace_close(void)
Rückgabetyp

void

Dies sollte während der Anwendungsbeendigung geschlossen werden. Es stellt sicher, dass es an den Server gespült wird und beendet den Tracing-Code.