云计算百科
云计算领域专业知识百科平台

Emscripten tracing.h 追踪 API 深度指南:内存分配可视化与自定义收集服务器实战

  • 编译器
  • WebAssembly
  • 开发工具
  • 构建工具

【免费下载链接】emscripten

Emscripten: An LLVM-to-WebAssembly Compiler

项目地址:
https://gitcode.com/gh_mirrors/em/emscripten

点击查看 免费下载

导读

Emscripten 的 tracing API 为 WebAssembly 应用提供了一套端到端的运行时追踪能力,重点解决浏览器自带性能工具难以观测的堆内存分配与释放问题。它既可以把追踪数据发送给自建的 collector 服务器,也可以对接 Google Web Tracing Framework(WTF)。读完本文,你将掌握如何通过 emcc –tracing 编译开关、emscripten/trace.h 中二十余个 C 函数完成应用插桩,理解其"客户端-服务器 + Web Worker 批处理 + 不扰动堆"的设计原理,并能在实际项目中落地内存热点定位与帧率、上下文、任务级分析。

一、概述:tracing API 解决什么问题

传统的浏览器性能工具(如 Chrome DevTools 的 Performance 面板)对 JavaScript 与 WebAssembly 的调用、渲染、GC 有较好的可见性,但对 Wasm 线性内存内部的堆分配行为基本不可见——你很难回答"哪段逻辑分配了多少字节、何时释放、堆是否在碎片化"这类问题。Emscripten 的 tracing API 正是为此而生:它从被插桩的 C/C++ 代码中采集数据,并传输给两类后端:

  • 自定义 collector 服务器(emscripten-trace-collector):完整接收所有数据类型;
  • Google Web Tracing Framework(WTF):目前仅支持上下文(contexts)、日志消息(log messages)与标记(marks)三类数据的子集。

其声明在 system/include/emscripten/trace.h,核心实现为 src/lib/libtrace.js,并有对应测试 test/core/test_tracing.c。

二、编译器交互:如何开启追踪

2.1 编译与链接阶段

在每次编译和链接时向 emcc 传入 –tracing 标志:

emcc –tracing -o app.html app.c

这一开关会在链接阶段自动完成两件事(见 tools/link.py):

  • 注入 libtrace.js:add_system_js_lib('libtrace.js') 把 tracing 的 JS 运行时加入构建;
  • 定义预处理宏 __EMSCRIPTEN_TRACING__:C/C++ 侧据此启用真实实现。
  • 对应地,src/settings.js 中的 EMSCRIPTEN_TRACING 设置项由命令行开关驱动。

    如果跳过 emcc 而直接用 clang 编译 C/C++ 代码,则需要手动传入 -D__EMSCRIPTEN_TRACING__。

    2.2 无追踪时的空桩

    当 __EMSCRIPTEN_TRACING__ 未定义时,system/include/emscripten/trace.h 会给出全部 API 的内联空桩宏,例如:

    #define emscripten_trace_configure(collector_url, application) ((void)0)
    #define emscripten_trace_log_message(channel, message) ((void)0)

    这意味着你可以在源码中无条件地埋入追踪调用,日常构建零开销;只有开启 –tracing 时才真正生效。这为在大型代码库中长期保留插桩提供了便利。

    2.3 务必先清理缓存

    –tracing 会修改 libc 内 dlmalloc.c 的实现(在 malloc/realloc/free 内插入追踪记录,见下文"分配记录"一节)。由于 Emscripten 会缓存构建好的 libc,若此前已经构建过普通版本,需要先清理缓存,否则拿不到完整的分配细节:

    emcc –clear-cache

    从源码看,libc 会按 is_tracing 生成独立的 -tracing 变体(tools/system_libs.py),因此建议在开启/关闭 tracing 之间切换时都执行清缓存操作,确保拿到对应变体。

    三、初始化和关闭

    3.1 连接 collector 服务器

    在应用启动后尽早调用 emscripten_trace_configure:

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

    • collector_url:collector 服务器的基础 URL,默认本地开发通常为 http://127.0.0.1:5000/;
    • application:被追踪应用的名字,服务器用它区分来自不同应用的数据。

    3.2 对接 Google WTF

    如果只想使用 Google Web Tracing Framework,改为调用:

    emscripten_trace_configure_for_google_wtf();

    注意:WTF 模式下并非所有追踪特性都可用,当前仅支持上下文、日志消息与标记(marks)。

    3.3 会话身份

    若应用有用户名或某种用户标识概念,可将其传给 API,便于在 collector 服务器中区分不同会话(而不是只靠时间戳会话 ID):

    emscripten_trace_set_session_username(username);

    该调用可以在追踪开始之后再设置,因此放在登录/鉴权流程之后也完全没有问题。

    3.4 关闭

    应用退出时调用 emscripten_trace_close(),确保所有数据被 flush 到服务器并终止追踪代码。

    3.5 运行时开关

    emscripten_trace_set_enabled(bool enabled) 可动态启用/禁用追踪。需注意:禁用追踪会导致内存使用数据不准确(分配/释放记录会丢失),一般仅用于调试场景。

    3.6 实现细节:Worker 与 session

    在 src/lib/libtrace.js 中,configure 的实现值得注意:

    • 生成 session_id(时间戳 + 随机数);
    • 通过 fetch 从 collector 服务器拉取 worker.js,用 URL.createObjectURL 创建 Web Worker,此后所有追踪数据都由该 Worker 转发给服务器(借此规避 CORS 限制);
    • 向 Worker 发送 configure 指令并立即上报 application-name 与 session-name 事件。

    这也解释了为何 collector_url 必须指向一个能提供 worker.js 的服务器。

    四、上下文(Contexts):廉价的"栈轨迹"替代

    4.1 概念

    上下文用于告诉追踪 API 当前正在运行应用的哪一部分,内部以"当前上下文栈"的形式维护。一个上下文可以大到"物理引擎运行中",也可以小到"更新实体 X 的动画"。粒度完全由插桩团队自行决定——细粒度上下文更利于定位问题,粗粒度则开销更低、更易维护。

    引入上下文栈的关键动机是:与其在每次追踪调用时抓取完整调用栈,不如记录当前上下文栈——后者的成本远低于前者。当服务器完整实现后,上下文还将用于统计:

    • 每个上下文内消耗的时间(一种基础性能剖析机制);
    • 上下文激活期间分配与释放的内存量。

    这有助于回答"应用的哪些部分占用更多内存、产生大量内存 churn(进而可能造成堆碎片)"。

    4.2 使用方式

    emscripten_trace_enter_context("Physics Update");
    /* … 物理更新逻辑 … */
    emscripten_trace_exit_context();

    在 JS 实现中(src/lib/libtrace.js),进入/退出上下文会向服务器发送 enter-context / exit-context 事件;若同时启用了 Google WTF,还会调用 wtf.trace.events.createScope 并把 scope 压入自己的 scopeStack,退出时弹出并 leaveScope,从而在 WTF 时间线上呈现嵌套作用域。

    五、帧(Frames):标记事件循环边界

    记录帧/事件循环的起止非常重要,它让服务器可以进行额外的有用分析(如帧耗时、每秒帧数、帧内的内存操作归属):

    emscripten_trace_record_frame_start();
    /* … 渲染一帧 … */
    emscripten_trace_record_frame_end();

    服务器利用这两个带时间戳的事件来:

    • 计算帧时间(进而得到 FPS);
    • 把发生在帧处理期间的内存操作计入该帧的账目。

    六、记录分配:malloc / realloc / free 的全自动插桩

    6.1 自动记录

    在每次分配与释放操作上,理想情况下都应记录(包括数据类型名)。当使用 –tracing 构建且清除了缓存后,Emscripten 构建的 libc 会自动记录所有对 malloc、realloc、free 的调用。

    从源码看,这些钩子位于 system/lib/dlmalloc.c:

    • malloc 成功路径调用 emscripten_trace_record_allocation(mem, bytes)(约 L4762);
    • free 调用 emscripten_trace_record_free(mem)(约 L4782);
    • realloc 在扩展/收缩路径分别调用 emscripten_trace_record_reallocation(oldmem, mem, bytes)(约 L5312 与 L5358)。

    三个 JS 钩子还会触发 Module['onMalloc']、Module['onRealloc']、Module['onFree'] 回调(见 src/lib/libtrace.js),并在 postEnabled 时向服务器发送 allocate / reallocate / free 事件。

    6.2 手动注释数据类型

    数据类型名目前需要手动记录。分配内存后,用地址注释类型名:

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

    服务器据此对内存中的内容进行类型分解(annotate-type 事件)。

    6.3 关联附属存储

    有些应用还想把"额外存储"与某次分配关联起来。例如一个对象内部持有 vector 或 string,分析内存时应把这些附属内存一并计入对象大小:

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

    associate_storage_size 记录的不是分配本身的 size,而是与该地址关联的、应用自定义的附加存储量(associate-storage-size 事件)。

    6.4 其他底层事件

    除上述外,libtrace.js 还定义了 sbrk-grow 事件(emscripten_trace_sbrk_grow,同时触发 Module['onSbrkGrow']),用于记录程序断点上移。启用 tracing 时链接器还会自动把 sbrk 加入必需导出(tools/link.py),并在开启内存增长时补充导出 emscripten_stack_get_current/base/end 且默认包含 emscripten_trace_report_memory_layout(tools/link.py)。

    七、整体内存使用报告

    周期性地向追踪 API 汇报整体堆布局与内存使用,由两个调用完成:

    emscripten_trace_report_memory_layout();
    emscripten_trace_report_off_heap_data();

    • emscripten_trace_report_memory_layout:报告普通 Emscripten 堆的使用情况。实现中(src/lib/libtrace.js)会采集 static_base(GLOBAL_BASE)、stack_base、stack_current、stack_max、dynamic_top(sbrk(0))以及 total_memory(HEAP8.length),作为 memory-layout 事件上报,同时覆盖栈与动态内存的使用量及总内存大小。
    • emscripten_trace_report_off_heap_data:报告不在普通 Emscripten 堆上的内存。当前实现只统计 OpenAL 音频数据(遍历 AL.currentContext.buf 中每个 buffer 的所有 channel,按 length * 4 字节累加),作为 off-heap 事件上报。注意:服务器目前还不展示这些数据。

    八、日志消息

    可通过追踪 API 记录日志消息,消息包含通道(channel)与内容两部分:

    emscripten_trace_log_message("Application", "Started");

    通道名用于在可视化界面中分类与过滤消息。关键约束:记录日志消息时不要在该路径上分配堆内存(原因见"不扰动堆"一节)。为加载新模型、游戏资源等可能引发大量内存活动的行为记录日志,对分析内存使用模式非常有用:

    emscripten_trace_log_message("Asset", "Loading level1.model");

    此外,emscripten_trace_mark(message) 会在时间线上记录一个标记点,主要用于 Google WTF(内部以 MARK 通道发送 log-message 事件,并在 WTF 可用时调用 wtf.trace.mark)。

    九、任务(Tasks):异步工作单元的完整生命周期

    任务是非重复的工作单元,典型例子是加载资源——它通常包含一连串回调,可能因异步部分而挂起或阻塞。任务 ID 为整数,由应用自行维护,并须保证在应用生命周期内唯一;大多数任务相关调用作用于"当前任务",因此无需每次都传任务 ID。

    9.1 开始与结束

    emscripten_trace_task_start(taskID, name);
    /* … */
    emscripten_trace_task_end();

    9.2 挂起与恢复

    当任务因异步操作挂起/阻塞时:

    emscripten_trace_task_suspend("loading via HTTP");

    恢复时:

    emscripten_trace_task_resume(taskID, "parsing");

    explanation 参数应说明挂起原因/恢复后要做什么,这些信息会在查看任务历史时展示。

    9.3 关联附加数据

    常需要把额外数据关联到当前任务,供后续分析使用,例如资源 URL:

    emscripten_trace_task_associate_data("url", url);

    对应的事件为 task-start、task-suspend、task-resume、task-end、task-associate-data(见 src/lib/libtrace.js)。

    十、错误报告

    应用遇到的错误可作为辅助服务上报给追踪 API:

    emscripten_trace_report_error("Assertion failed: …");

    实现中(src/lib/libtrace.js)会自动获取当前调用栈((new Error).stack)一并发送 report-error 事件,可用于捕获 JavaScript / Web Worker 错误,以及 C/C++ 侧的断言失败或运行时错误。文档明确指出:该特性用于预示 Emscripten tracing API 的未来发展方向。

    十一、运行 collector 服务器

  • 获取 emscripten-trace-collector 服务器的副本;
  • 按照其 README.rst 中的说明启动与配置。
  • (该服务器独立于本仓库维护,负责接收、分析追踪数据并提供 Web 可视化界面。)

    十二、设计原理

    12.1 客户端 / 服务器架构

    Emscripten tracing API 从被插桩的代码中采集数据,传输给 collector 服务器;服务器负责数据分析并提供查看数据的 Web 界面。这种设计有两个目的:

    • 在内存紧张的低端硬件上(如 32 位 Windows 机器)运行时不干扰浏览器——分析负担被转移到服务器侧;
    • 一个服务器可以同时收集多个客户端的追踪数据,便于集中分析。

    12.2 数据批处理

    数据以块(chunk)为单位批量发送,大约每秒 1~2 次。这避免了为每一条记录事件都新建到服务器的连接(连接建立开销远大于数据本身)。

    12.3 不扰动堆(Do Not Perturb The Heap)

    使用 tracing API 时须格外小心,不要执行会扰动堆的操作。例如:不要把动态分配的字符串传给 emscripten_trace_log_message——那会导致该分配本身被追踪记录,从而干扰你正在分析的行为与结果。

    为此,tracing API 自身把它的所有数据保存在 Emscripten 堆之外,且从不向 Emscripten 堆写入任何内容(数据经由独立的 Web Worker 转发给服务器,见 src/lib/libtrace.js 的 EmscriptenTrace.worker 设计)。

    十三、完整 API 参考

    以下为 system/include/emscripten/trace.h 声明的全部函数(参数类型见头文件原文):

    函数说明
    emscripten_trace_configure(collector_url, application) 配置与 collector 服务器的连接,应在应用启动后尽早调用;collector_url 通常为 http://127.0.0.1:5000/
    emscripten_trace_configure_for_google_wtf() 配置为与 Google WTF 通信;仅上下文、日志消息与标记可用
    emscripten_trace_configure_for_test() 测试用配置(见 test/core/test_tracing.c),时间固定为 0 并直接输出追踪条目
    emscripten_trace_set_enabled(enabled) 设置追踪是否启用;禁用会得到不准确的内存数据
    emscripten_trace_set_session_username(username) 设置会话用户名,便于多用户共享服务器时区分会话;可在登录后设置
    emscripten_trace_record_frame_start() 帧/事件循环开始处调用,服务器据此计算帧时间与 FPS
    emscripten_trace_record_frame_end() 帧/事件循环结束处调用,停止向该帧累计内存操作与耗时
    emscripten_trace_log_message(channel, message) 记录日志消息(通道 + 描述),用于关联内存与帧率变化;服务器对该数据的利用未来会增强
    emscripten_trace_mark(message) 在时间线记录标记,主要用于 Google WTF
    emscripten_trace_report_error(error) 上报错误,自动附带当前调用栈;可捕获 JS/Worker 错误与 C/C++ 断言失败
    emscripten_trace_record_allocation(address, size) 记录一次分配,最佳插入位置是 dlmalloc 实现(已自动完成)
    emscripten_trace_record_reallocation(old_address, new_address, size) 记录一次重分配(同上)
    emscripten_trace_record_free(address) 记录一次释放;注意不要对同一次 free 重复调用
    emscripten_trace_annotate_address_type(address, type) 用数据类型名注释地址,服务器据此做内存内容分解
    emscripten_trace_associate_storage_size(address, size) 关联附加存储量(非分配本身大小),如对象内 vector/string 占用的内存
    emscripten_trace_report_memory_layout() 周期上报堆布局:栈、动态内存与总内存大小
    emscripten_trace_report_off_heap_data() 周期上报堆外内存(当前为 OpenAL 内存;服务器暂不展示)
    emscripten_trace_enter_context(name) 进入命名上下文(带时间戳)
    emscripten_trace_exit_context() 退出当前上下文
    emscripten_trace_task_start(task_id, name) 启动任务,任务 ID 须在应用生命周期内唯一
    emscripten_trace_task_associate_data(key, value) 为当前任务关联键值对
    emscripten_trace_task_suspend(explanation) 挂起当前任务,说明挂起原因
    emscripten_trace_task_resume(task_id, explanation) 恢复指定任务并设为当前任务,说明恢复目的
    emscripten_trace_task_end() 结束当前任务
    emscripten_trace_close() 应用终止时调用,确保数据 flush 到服务器并终止追踪代码
    emscripten_trace_sbrk_grow(old, new) 记录程序断点增长(内部使用,同时触发 onSbrkGrow 回调)

    13.1 参考测试:最小可运行示例

    仓库中的 test/core/test_tracing.c 提供了一个完整的迷你示例,展示测试模式下的标准用法:

    #include <emscripten/trace.h>

    int main(int argc, const char* argv[]) {
    emscripten_trace_configure_for_test();
    emscripten_trace_enter_context("Application Startup");
    emscripten_trace_log_message("Application", "starting up");
    emscripten_trace_exit_context();
    emscripten_trace_record_frame_start();
    emscripten_trace_record_frame_end();
    return 0;
    }

    实际接入 collector 服务器时,把 emscripten_trace_configure_for_test() 换成:

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

    十四、实战建议

  • 插桩顺序:configure → set_session_username(如有)→ 业务逻辑中的上下文/任务/帧/日志调用 → 退出前 close。
  • 内存热点定位:开启 –tracing 并 emcc –clear-cache 后,malloc/realloc/free 自动被记录;配合 annotate_address_type 与 associate_storage_size 可获得"什么类型占了多少内存"的分解视图。
  • 异步流程分析:对资源加载这类回调链,使用 task_start / task_suspend(如 "loading via HTTP")/ task_resume(如 "parsing")/ task_end 完整刻画生命周期,并用 task_associate_data 记录 URL 等上下文。
  • 低开销策略:上下文栈代替完整调用栈、Web Worker 转发 + 每秒 1~2 次批量上报、堆外缓冲——三者共同保证追踪对目标应用的侵入性降到最低。
  • 保持生产构建干净:__EMSCRIPTEN_TRACING__ 未定义时所有调用退化为空桩宏,插桩代码可安全常驻源码。
  • 延伸阅读

    • API 头文件与空桩定义:system/include/emscripten/trace.h
    • JS 运行时实现(事件协议、Worker 转发、WTF 桥接):src/lib/libtrace.js
    • 链接期注入与导出设置:tools/link.py
    • libc 追踪变体构建:tools/system_libs.py
    • dlmalloc 内自动插桩点:system/lib/dlmalloc.c
    • 官方 API 文档原文:site/source/docs/api_reference/trace.h.rst

    赞

    分享

    • 编译器
    • WebAssembly
    • 开发工具
    • 构建工具

    【免费下载链接】emscripten

    Emscripten: An LLVM-to-WebAssembly Compiler

    项目地址:
    https://gitcode.com/gh_mirrors/em/emscripten

    点击查看 免费下载

    上一篇:
    CodeMirror 5键盘映射终极指南:Emacs、Vim、Sublime风格快捷键设置详解

    下一篇:
    G-Helper 配置教程:用这款开源轻量替代 Armoury Crate 的调鼠标 DPI 与轮询率

    创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Emscripten tracing.h 追踪 API 深度指南:内存分配可视化与自定义收集服务器实战
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!