trace.h

Emscripten 跟踪 API 提供了一些有用的功能,可以更好地了解应用程序内部发生了什么,特别是关于内存使用情况(传统浏览器性能工具通常无法获取)。

跟踪 API 可以与自定义收集服务器通信(有关更多详细信息,请参阅 运行服务器),也可以与 Google Web Tracing Framework 通信。与 Google Web Tracing Framework 通信时,会收集一部分可用数据。

用法

编译器交互

使用跟踪 API 时,您应该在每个编译和链接阶段将 --tracing 传递给 emcc。这将自动包含 library_trace.js 库文件,并设置预处理器标志 __EMSCRIPTEN_TRACING__。如果您直接调用 clang 来构建您的 C/C++ 代码,那么您需要在构建代码时传递 -D__EMSCRIPTEN_TRACING__。当未定义预处理器标志 __EMSCRIPTEN_TRACING__ 时,跟踪 API 实现将由内联空存根提供。

此外,由于启用跟踪会修改 libc 实现中的 dlmalloc.c 的实现,因此建议您在切换到使用跟踪 API 之前手动清除缓存。如果您没有这样做,那么您将不会获得完整的分配详细信息记录。您可以使用以下 emcc 命令清除缓存

emcc --clear-cache

初始化和拆卸

要初始化跟踪 API,您需要调用 emscripten_trace_configure()

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

如果您只是要将跟踪 API 与 Google Web Tracing Framework 一起使用,那么您只需调用 emscripten_trace_configure_for_google_wtf() 即可

emscripten_trace_configure_for_google_wtf();

如果您有用户名概念或有其他方法来识别应用程序的特定用户,那么将该信息传递给跟踪 API 可以让您更容易在收集服务器中识别会话

emscripten_trace_set_session_username(username);

要在应用程序退出时将其关闭,您只需调用 emscripten_trace_close()

emscripten_trace_close();

上下文

上下文是一种告诉跟踪 API 应用程序的哪个部分当前正在运行的方法。上下文实际上作为当前上下文的堆栈维护。

上下文可能是像“运行物理”这样大的东西,也可能是像“更新实体 X 上的动画”这样小的东西。

上下文堆栈的粒度由检测其应用程序的团队决定。一些应用程序可能会发现细粒度的上下文更有用,而另一些应用程序则对更广泛的上下文感到更舒服。

我们通常可以查看当前上下文堆栈并记录该堆栈,而不是在每次跟踪调用时获取堆栈跟踪,这要便宜得多。

当上下文由服务器完全实现时,它们还将用于跟踪在每个上下文中花费的时间(一种原始分析机制),以及在上下文处于活动状态时分配和释放了多少内存。这应该有助于了解应用程序的哪些部分使用更多内存或创建大量 churn(并可能导致堆碎片)。

记录上下文进入和退出非常简单

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

记录您的帧或事件循环开始和结束的位置非常重要。这使跟踪 API 能够执行有用的其他分析。

记录事件循环的开始就像

emscripten_trace_record_frame_start();

记录事件循环的结束同样简单

emscripten_trace_record_frame_end();

标注分配

应记录每个分配和释放操作。理想情况下,数据类型名称也会被记录,但目前必须手动完成。

使用 --tracing 和已清除的缓存构建时,Emscripten 构建的 libc 将自动记录对 mallocreallocfree 的所有调用。

至于记录数据类型名称,在分配内存之后,您可以为地址添加注释

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

此外,一些应用程序可能希望将附加存储的大小与分配相关联。这可以通过 emscripten_trace_associate_storage_size() 完成

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

总体内存使用情况

应定期将总体堆布局和内存使用情况报告给跟踪 API。

这是通过 2 个调用完成的

emscripten_trace_report_memory_layout();
emscripten_trace_report_off_heap_data();

记录消息

可以通过 Emscripten 跟踪 API 记录和记录消息。这些消息可以具有通道和实际消息。通道名称将有助于在可视化界面中对消息进行分类和过滤。您应该避免在记录消息时在堆上分配内存。

emscripten_trace_log_message("Application", "Started");

随着时间的推移,可视化界面将得到改进,以帮助您更好地将这些日志消息与其他视图相关联,例如内存使用情况随时间的变化。记录可能会导致大量内存活动的事件的日志消息(例如加载新模型或游戏资源)在分析内存使用行为模式时非常有用。

任务

可以记录和分析特定任务。任务通常是不可重复的工作单元。它可能会由于异步执行的部分而被挂起或阻塞。

任务的一个示例是加载资源,这通常涉及回调链。

应用程序应该跟踪任务 ID(整数)并确保它们是唯一的。

任务 ID 不需要传递到涉及任务的每次跟踪调用,因为大多数调用都针对当前任务。

可以使用以下方法启动和停止任务

emscripten_trace_task_start(taskID, name);
emscripten_trace_task_end();

如果任务被挂起/阻塞,可以使用以下方法记录

emscripten_trace_task_suspend("loading via HTTP");

并在恢复时

emscripten_trace_task_resume(taskID, "parsing");

通常需要将其他数据与当前任务相关联,以便在以后检查任务数据时使用。这方面的一个例子是已加载资源的 URL

emscripten_trace_task_associate_data("url", url);

报告错误

应用程序遇到的错误可以作为辅助服务报告给跟踪 API

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

此功能包含在内,以表明 Emscripten 跟踪 API 的未来方向。

运行服务器

设计说明

客户端/服务器设计

Emscripten 追踪 API 收集来自已插桩代码的数据,并将其传输到收集服务器。服务器还执行数据分析,并提供 Web 界面用于查看收集的数据。

这种客户端/服务器设计旨在让工具在内存可能很宝贵的低端硬件(例如 32 位 Windows 机器)上运行,而不会干扰浏览器。

这种设计还允许运行单个服务器来收集来自各种客户端的数据。

数据批处理

数据被批处理并以块的形式发送到服务器,大约每秒一次或两次。这避免了为记录的每个事件打开与服务器的新连接。

不要扰乱堆

使用 Emscripten 追踪 API 时,您应该注意不要执行会扰乱堆的操作。例如,您不应该分配一个字符串来传递给 emscripten_trace_log_message(),因为这会导致分配被跟踪,并可能扰乱您尝试分析的行为或结果。

出于这个原因,Emscripten 追踪 API 还将所有自身数据保留在 Emscripten 堆之外,并且不执行对 Emscripten 堆的写入。

函数

void emscripten_trace_configure(const char *collector_url, const char *application)
参数
  • collector_url (const char*) – 收集服务器的基准 URL。

  • application (const char*) – 被追踪的应用程序的名称。

返回类型

void

配置与收集服务器的连接。

这应该是在应用程序启动后完成的最早操作之一。

在大多数情况下,collector_url 将是 http://127.0.0.1:5000/

void emscripten_trace_configure_for_google_wtf(void)
返回类型

void

配置追踪以与 Google Web Tracing Framework 通信。

并非所有追踪功能都可以在 Google WTF 工具中使用。(目前,只有上下文、日志消息和标记。)

void emscripten_trace_set_enabled(bool enabled)
参数
  • enabled (bool) – 追踪是否启用。

返回类型

void

设置追踪是否启用。使用此选项禁用追踪可能会导致收集到有关内存使用情况的不准确数据。

void emscripten_trace_set_session_username(const char *username)
参数
  • username (const char*) – 运行应用程序的人员的用户名。

返回类型

void

这在收集服务器被多人使用时很有用,并且您希望能够通过时间戳会话 ID 以外的其他方式识别单个会话。

这可以在追踪已经开始后设置,因此在用户完成登录或身份验证流程后设置它是可以的。

void emscripten_trace_record_frame_start(void)
返回类型

void

这应该在帧/事件循环开始时调用。

当前时间戳与这些数据相关联。

服务器使用它来跟踪帧时间(因此是每秒帧数),以及在帧处理期间发生的内存操作的核算。

void emscripten_trace_record_frame_end(void)
返回类型

void

这应该在帧/事件循环结束时调用。

当前时间戳与这些数据相关联。

服务器使用它来停止累积内存操作和帧的经过时间。

void emscripten_trace_log_message(const char *channel, const char *message)
参数
  • channel (const char*) – 正在发出时间轴事件的类别。

  • message (const char*) – 正在发出时间轴事件的描述。

返回类型

void

记录日志消息。这对于记录可能有利于与内存使用情况或帧率变化相关联的已发生事件或操作很有用。

当前时间戳与这些数据相关联。

服务器目前还没有对此数据做太多处理。这将在未来得到改进。

void emscripten_trace_mark(const char *message)
参数
  • message (const char *) – 正在发出的标记的名称。

返回类型

void

在时间轴中记录一个标记。这主要用于 Google Web Tracing Framework

当前时间戳与这些数据相关联。

void emscripten_trace_report_error(const char *error)
参数
  • error (const char*) – 正在报告的错误消息。

返回类型

void

API 将获取当前调用堆栈,并将其包含在发送到服务器的报告中。

当前时间戳与这些数据相关联。

这可用于各种事情,包括捕获 JavaScript 和 Web 工作线程错误,以及来自 C/C++ 代码内部的失败断言或其他运行时错误。

void emscripten_trace_record_allocation(const void *address, int32_t size)
参数
  • address (const void*) – 已分配的内存地址。

  • size (int32_t) – 分配的内存块的大小。

返回类型

void

这必须对每次内存分配调用。最适合执行此操作的位置是在 Emscripten 中的 dlmalloc 实现中。

当前时间戳与这些数据相关联。

void emscripten_trace_record_reallocation(const void *old_address, const void *new_address, int32_t size)
参数
  • old_address (const void*) – 已重新分配的内存块的旧地址。

  • new_address (const void*) – 已重新分配的内存块的新地址。

  • size (int32_t) – 重新分配的内存块的新大小。

返回类型

void

这必须对每次内存重新分配调用。最适合执行此操作的位置是在 Emscripten 中的 dlmalloc 实现中。

当前时间戳与这些数据相关联。

void emscripten_trace_record_free(const void *address)
参数
  • address (const void*) – 正在释放的内存地址。

返回类型

void

这必须对每次 free 操作调用。最适合执行此操作的位置是在 Emscripten 中的 dlmalloc 实现中。

当前时间戳与这些数据相关联。

同样重要的是,对单个 free 操作不要多次调用它。

void emscripten_trace_annotate_address_type(const void *address, const char *type)
参数
  • address (const void*) – 应该进行注释的内存地址。

  • type (const char*) – 正在分配的数据类型的名称。

返回类型

void

使用存储在那里的数据类型的名称对地址进行注释。服务器使用它来帮助分解内存中的内容。

void emscripten_trace_associate_storage_size(const void *address, int32_t size)
参数
  • address (const void*) – 应该进行注释的内存地址。

  • size (int32_t) – 与此分配关联的内存大小。

返回类型

void

将一定数量的额外存储与该地址关联。这并不代表分配本身的大小,而是应该在查看该对象的大小时考虑的关联内存。

这种关联的存储本质上是特定于应用程序的。

例如,当一个对象包含一个向量或字符串时,您可能希望在分析内存使用情况时了解这一点,并且这提供了一种让服务器了解该额外存储的方法。

void emscripten_trace_report_memory_layout(void)
返回类型

void

这应该定期调用,以报告正常 Emscripten 堆的使用情况。这提供了堆栈和动态内存使用情况以及总内存大小的详细信息。

当前时间戳与这些数据相关联。

void emscripten_trace_report_off_heap_data(void)
返回类型

void

这应该定期调用,以报告不属于正常 Emscripten 堆的内存使用情况。这目前用于报告 OpenAL 内存使用情况。

当前时间戳与这些数据相关联。

服务器目前还没有显示这些数据。

void emscripten_trace_enter_context(const char *name)
参数
  • name (const char*) – 上下文名称。

返回类型

void

当前时间戳与这些数据相关联。

void emscripten_trace_exit_context(void)
返回类型

void

当前时间戳与这些数据相关联。

void emscripten_trace_task_start(int task_id, const char *name);
参数
  • task_id (int) – 任务 ID

  • name (const char*) – 任务名称

返回类型

void

一个任务被启动。任务 ID 在应用程序的整个生命周期内应该是唯一的。它应该由应用程序管理/跟踪。

当前时间戳与这些数据相关联。

void emscripten_trace_task_associate_data(const char *key, const char *value);
参数
  • key (const char*) – 键

  • value (const char*) – 值

返回类型

void

将键/值对与当前任务关联。

void emscripten_trace_task_suspend(const char *explanation);
参数
  • explanation (const char*) – 任务暂停的原因。

返回类型

void

当前任务被暂停。

解释应该说明暂停任务的原因,以便在查看任务历史记录时可以获得此信息。

当前时间戳与这些数据相关联。

void emscripten_trace_task_resume(int task_id, const char *explanation);
参数
  • task_id (int) – 任务 ID

  • explanation (const char*) – 恢复任务的原因。

返回类型

void

task_id 标识的任务被恢复,并成为当前任务。

解释应说明恢复任务以执行的操作,以便在查看任务历史记录时可以获得此信息。

当前时间戳与这些数据相关联。

void emscripten_trace_task_end(void);
返回类型

void

当前任务结束。

当前时间戳与这些数据相关联。

void emscripten_trace_close(void)
返回类型

void

这应该在应用程序终止期间关闭。 它有助于确保刷新到服务器并终止跟踪代码。