Erlang/OTP Event Tracer(ET)深度指南:用 et_collector 与 et_viewer 构建可视化序列图事件追踪
2026/9/23 19:54:25 网站建设 项目流程

Erlang/OTP Event Tracer(ET)深度指南:用 et_collector 与 et_viewer 构建可视化序列图事件追踪

【免费下载链接】otpErlang/OTP项目地址: https://gitcode.com/gh_mirrors/ot/otp

ET(Event Tracer)是 Erlang/OTP 自带的轻量级分布式追踪工具,它把 Erlang 运行时产生的 trace 数据、或应用代码中显式上报的事件,统一采集进一个事件仓库,并渲染成可交互的序列图(sequence chart)。本文基于 OTP 仓库中lib/et的实现与文档,讲解 ET 的两大核心组件(et_collector事件仓库与et_viewer图形查看器)、过滤器(Filter)与字典机制、Trace Client 接入、全局追踪(global tracing)以及两套图形窗口的完整操作手册。读完本文,你将能够自己启动 ET、上报/接入各类事件源、编写自定义视图过滤器,并把 ET 嵌入到多节点系统的实时追踪中。

一、整体架构:Collector 与 Viewer 两大组件

从 et.erl 模块头注释 可以清晰看到,ET 应用由以下几部分构成:

  • et_collector:事件采集与后端存储。它是一个gen_server进程,接收 trace 数据、经过过滤器后按时间戳有序存放,并为多个订阅进程(如多个 Viewer)提供字典服务与组通信;
  • et_viewer:图形化序列图查看器。它连接到一个 Collector,定期轮询获取新事件,并在显示前对每个事件应用用户自定义过滤器;
  • et_contents_viewer:单个事件的细节查看窗口,通常由et_viewer启动;
  • et_selector:底层库模块,负责把"细节级别(detail level)"翻译成 Erlang trace pattern(match spec),并实现默认过滤器——把原始 Erlang trace 数据转换成#event{}记录;
  • et:面向业务应用的极轻量上报 API(trace_me/4,5等)。

文档中的架构要点是:一个 Collector 可以作为多个 Viewer 的共享后端存储,每个 Viewer 可以对同一份 trace 数据展示不同的视角(例如不同的过滤器和缩放比例)。Collector 与 Viewer 之间的接口是公开的,因此理论上可以编写其它类型的 Viewer,但本文聚焦et_viewer的标准用法。

1.1 启动:et_viewer:start/1

主要启动函数是et_viewer:start/1,默认情况下它会同时启动一个et_collector和一个et_viewer

% erl -pa et/examples Erlang R13B03 (erts-5.7.4) [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false] Eshell V5.7.4 (abort with ^G) 1> {ok, Viewer} = et_viewer:start([]). {ok,<0.40.0>}

1.2 事件如何流动:轮询 + report_event/6

Viewer 会定期轮询其 Collector,获取更多待显示的事件。事件本身可以由任何进程通过et_collector:report_event/6上报,例如:

2> Collector = et_viewer:get_collector_pid(Viewer). <0.39.0> 3> et_collector:report_event(Collector, 60, my_shell, mnesia_tm, start_outer, 3> "Start outer transaction"), 3> et_collector:report_event(Collector, 40, mnesia_tm, my_shell, new_tid, 3> "New transaction id is 4711"), 3> et_collector:report_event(Collector, 20, my_shell, mnesia_locker, try_write_lock, 3> "Acquire write lock for {my_tab, key}"), 3> et_collector:report_event(Collector, 10, mnesia_locker, my_shell, granted, 3> "You got the write lock for {my_tab, key}"), 3> et_collector:report_event(Collector, 60, my_shell, do_commit, 3> "Perform transaction commit"), 3> et_collector:report_event(Collector, 40, my_shell, mnesia_locker, release_tid, 3> "Release all locks for transaction 4711"), 3> et_collector:report_event(Collector, 60, my_shell, mnesia_tm, delete_transaction, 3> "End of outer transaction"), 3> et_collector:report_event(Collector, 20, my_shell, end_outer, 3> "Transaction returned {atomic, ok}"). {ok,{table_handle,<0.39.0>,16402,trace_ts, #Fun<et_collector.0.62831470>}}

注意report_event/6的返回值:第一次调用以 Collector 的 pid 作为句柄,之后可以直接复用返回的table_handle记录(它内嵌了 Ets 表标识和过滤器 fun,从而省去每次向 Collector 进程请求句柄的往返开销,见下文源码走读)。

这段示例实际上是模拟了一次 Mnesia 本地表写事务产生的进程事件,真实事务等价于:

mnesia:transaction(fun() -> mnesia:write({my_tab, key, val}) end).

在 et_demo.erl 的sim_trans/0,1中可以看到同样的事件序列被封装为可复用函数。有了这批事件,图形界面中即可看到序列图:

在序列图中,参与事件的**演员(actor)**显示为带名字的竖直条。你可以按住鼠标左键拖动某个 actor 的名字标签并放到其它位置,从而调整 actor 的左右排列顺序:

一个事件既可能是单个 actor 的动作(蓝色文字标签),也可能涉及两个 actor,此时会渲染成从一个 actor 指向另一个 actor 的箭头(红色文字标签)。单击事件标签文字或箭头(按下并松开鼠标左键),会弹出Contents Viewer窗口展示该事件的细节:

二、事件记录:#event{} 的结构

ET 内部统一使用#event{}记录作为事件的中介格式,其字段定义在 et.hrl:

字段含义
detail_level细节级别,0..100。噪声(noise)事件的值偏高,关键事件值偏低
trace_tstrace 数据被生成的时间戳;若原始 trace 数据未携带时间戳,则等于event_ts
event_ts事件记录被创建(解析)的时间戳
from来源 actor,例如消息的发送方
to目标 actor,例如消息的接收方
label事件的简短摘要标签
contents事件的全部细节,建议使用[{Key, Value}]键值对列表

细节级别的上下限在 et_internal.hrl 中定义为0..100

三、过滤器(Filter)与字典机制

3.1 过滤器接口:与 lists:filtermap/2 一致

ET 在各种场景中大量使用命名过滤器。一个事件 trace 过滤器是一个 Erlang fun,接收 trace 数据作为输入,返回其可能的修改版本:

filter(TraceData) -> false | true | {true, NewEvent} TraceData = Event | erlang_trace_data() Event = #event{} NewEvent = #event{}

其语义与标准库lists:filtermap/2的过滤函数完全相同:

  • false:静默丢弃该 trace 数据;
  • true:trace 数据本身已经是#event{}记录,原样保留;
  • {true, NewEvent}:用NewEvent替换原始 trace 数据。

这样既可以去噪(丢弃不需要的事件),也可以改变事件在视图中的呈现方式。

3.2 Collector Filter 的特别之处

第一个接触到 trace 数据的过滤器是Collector Filter(字典中名字为all的那个过滤器,见 et_internal.hrl)。当事件通过et_collector:report/2(或report_event/5,6)上报时,首先会向 Collector 进程发送消息以获取一个句柄(其中包含 Collector Filter Fun 和 Ets 表标识);随后应用该过滤器,若返回true{true, NewEvent},事件即被写入 Ets 表。作为优化,后续report调用可以直接使用这个句柄,而不再向 Collector 进程请求。

所有过滤器(无论注册在 Collector 还是 Viewer 中)都必须能处理#event{}记录作为输入;而 Collector Filter(名为all)略有特殊——它的输入还可能是原始的 Erlang trace 数据

3.3 Collector 的字典服务与订阅传播

Collector 内部维护一个基于 key/value 的字典用于存放过滤器等条目。字典的每次更新都会被传播给所有订阅进程;当某个 Viewer 启动时,它会自动注册为字典更新的订阅者。相关 API(实现在 et_collector.erl):

  • et_collector:dict_insert(CollectorPid, Key, Val):插入条目并向所有订阅者发送{et, {dict_insert, Key, Val}}消息;若是新订阅者注册,会先把已存在的全部条目逐一发送给它;
  • et_collector:dict_lookup(CollectorPid, Key):查询条目;
  • et_collector:dict_delete(CollectorPid, Key):删除条目并广播;
  • et_collector:dict_match(CollectorPid, Pattern):按 Ets 匹配模式批量查询。

每个 Viewer 同时只有一个激活过滤器,从 Collector 取回的所有事件都会流过该过滤器;编写巧妙的过滤器即可自定义事件在查看器中的形态。

3.4 实战示例:mgr_actors 过滤器

下面这个过滤器来自 et_demo.erl,它把 actor 名字mnesia_tmmnesia_locker替换为语义化的trans_mgrlock_mgr,并把原始值备份到 contents 中,其余字段保持不变:

mgr_actors(E) when is_record(E, event) -> Actor = fun(A) -> case A of mnesia_tm -> trans_mgr; mnesia_locker -> lock_mgr; _ -> A end end, {true, E#event{from = Actor(E#event.from), to = Actor(E#event.to), contents = [{orig_from, E#event.from}, {orig_to, E#event.to}, {orig_contents, E#event.contents}]}}.

把它插入正在运行的 Collector:

4> Fun = fun(E) -> et_demo:mgr_actors(E) end. #Fun<erl_eval.6.13229925> 5> et_collector:dict_insert(Collector, {filter, mgr_actors}, Fun). ok

此时所有 Viewer 的Filter菜单都会出现一个新条目mgr_actors。选中它,会弹出一个新的 Viewer 窗口,用该过滤器渲染同一份 trace 数据:

如果想查看事件的"细枝末节",可以单击事件打开Contents Viewer;该窗口内同样有过滤器菜单,允许从其它视角检视同一个事件。例如点击new_tid事件,会弹出以mgr_actors视角展示该事件的 Contents Viewer:

在 Filters 菜单中选择all,则会弹出以 Collector 视角(即原始视图)展示同一事件的窗口:

四、Trace clients:把各种数据源接入 Collector

除了手动调用report_event/5,6,ET 还提供了一系列现成的 trace client,把各种来源的 trace 数据转发进 Collector:

  • 文件存取:Collector 中托管的事件可以保存到文件,之后通过 Viewer 的File菜单中的save/load,或通过et_collectorAPI 重新载入。实现上文件由disk_log承载:save_event_file/3支持existing | write | append(事件范围/写模式)与keep | clear(写完后是否清空事件表)等选项,load_event_file/2按 chunk 读取并逐条report(见 et_collector.erl);
  • Erlang 内建 trace:可以利用 Erlang 虚拟机内建的 trace 机制对运行中的系统做实时追踪,trace 数据可输出到文件或端口。相关接口参见erlang:trace/3erlang:trace_pattern/3dbgttb
  • 对应的 trace clientet_collector:start_trace_client/3会启动能读取上述 Erlang trace 文件/端口格式的客户端,并把数据重定向到 Collector(实现中对file类型会等待读到end_of_trace后返回file_loaded,对ip等端口类型返回{trace_client_pid, Pid})。

默认的 Collector Filter 负责把原始 Erlang trace 数据格式转换为#event{}记录。如果希望走不同的转换路径,可以完全自己编写 Collector Filter;但更省力的做法是先调用et_selector:parse_event/2应用默认过滤逻辑,再对其输出做二次加工(et_selector.erl 中parse_event/2即默认过滤器的实现核心)。

反过来,如果 trace 数据来自任何自有格式(文件、数据库、消息流……),你完全可以编写自定义 trace client 读取数据并喂给 Collector——只要在调用report/2之前把数据转成#event{}(或替换默认 Collector Filter 来完成转换)。

五、全局追踪(Global tracing):一键铺开多节点 trace

在多个节点上手工搭建 Erlang tracer、再把 trace client 接到各 tracer 的端口上,非常繁琐。ET 为此提供了全局追踪能力:

  • 启用后,et_collector进程会监控 Erlang 节点:每当有节点接入(nodeup),就在新节点上自动启动一个 Erlang tracer(通过 port tracer),同时在 Collector 所在节点启动对应的 trace client,把 trace 事件自动转发回 Collector;
  • 激活方式:给et_collectoret_viewer设置布尔参数trace_global = true
  • 并发匿名 Collector 的数量不受限制,但全局 Collector 全局唯一——因为它的名字注册在global中(源码中trace_global为 true 时以{global, ?MODULE}注册,见 et_collector.erl)。可通过et_collector:get_global_pid/0获取其 pid。

5.1 et:trace_me/4,5:为应用埋点的"免费"函数

为了进一步简化追踪,可以借助et:trace_me/4,5系列函数。它们设计为从其它应用的关键位置调用:

trace_me(DetailLevel, From, To, Label, Contents) -> hopefully_traced

从 et.erl 的实现 可见,这些函数极其轻量——除了返回原子hopefully_traced之外什么都不做。它们存在的意义就是被 trace:由于调用方显式提供了#event{}各字段的值,默认 Collector Filter 无需任何用户自定义过滤器,就能自动生成定制的事件记录。相关说明:

  • trace_me/4等价于trace_me/5FromTo都设为同一个 actor;
  • phone_home/4,5report_event/4,5目前为向后兼容保留,均委托给trace_me
  • DetailLevel取 0..100 的整数;Label建议用原子作简短摘要;Contents建议用[{Key, Value}]列表以方便后续加工;
  • 正常运行时这些调用几乎零开销;需要追踪时,可以显式对它们开启 trace,或组合使用trace_globaltrace_pattern

5.2 trace_pattern:控制全局细节级别

设置trace_pattern后,它会自动在所有已连接节点上激活。它的好处是提供了一种非常简单的方式来压低产生的 trace 数据量:你可以在源头就控制追踪的细节级别。

这一点与 Viewer 中的"Detail Level"滑块互补——滑块只控制显示哪些事件;而如果把trace_pattern的细节级别调低,大量 trace 数据根本不会生成,自然也就不会经 socket 发到 trace client、存进 Collector。

从 et_selector.erl 的 make_pattern/1 可以看到细节级别如何翻译成 match spec:

  • min:空 match spec[],即关闭et:trace_me/4,5的追踪;
  • max:匹配所有调用;
  • 整数X:匹配所有detail level < X的调用(match spec 条件{'<', '$1', DetailLevel})。

Collector 的change_pattern/2则通过rpc:multicall把新 pattern 同步到所有 trace 节点(et_collector.erl)。

5.3 实机演示:追踪真实的 Mnesia 事务

et_demo提供了两个级别的演示:sim_trans/0是纯模拟(手动report_event),而live_trans/0则做真实追踪:启动带trace_global的 Viewer、创建 Mnesia 表、调用trace_mnesia/0dbg对 Mnesia 模块设置调用追踪并开启send/receive/procs/timestamp标志,然后执行一次真实事务(et_demo.erl)。et_demo:start/1的默认选项也展示了常用的 Viewer 配置:

Options = [{trace_global, true}, {parent_pid, undefined}, {max_actors, infinity}, {max_events, 1000}, {active_filter, module_as_actor}], et_viewer:start_link(filters() ++ Options ++ ExtraOptions).

六、et_viewer 窗口:菜单与快捷键速查

et_viewer中几乎所有功能都有键盘快捷键,快捷键显示在菜单条目后的括号中。例如按键r等价于选择Viewer->Refresh

6.1 File 菜单

条目作用
Clear all events in the Collector删除 Collector 中存储的全部事件,并通知所有相连的 Viewer
Load events to the Collector from file从文件向 Collector 装载事件,并通知所有相连 Viewer
Save all events in the Collector to file把 Collector 中全部事件保存到文件
Print setup编辑打印设置(纸张、布局)
Print current page打印当前页的事件(页大小取决于所选纸张类型)
Print all pages打印全部事件
Close this Viewer关闭本 Viewer 窗口,保留其它 Viewer 窗口与 Collector 进程
Close other Viewers, but this保留本 Viewer 及其 Collector,关闭连接同一 Collector 的其它 Viewer
Close all Viewers and the Collector关闭 Collector 及所有相连 Viewer

6.2 Viewer 菜单

条目作用
First/Last本 Viewer 滚动到 Collector 中的第一个/最后一个事件
Prev/Next本 Viewer 向后/向前翻一页
Refresh(快捷键r清空本 Viewer 并重新从 Collector 读取事件
Up/Down向后/向前滚动少量事件
Display all actors.重置隐藏/高亮 actor 的设置

6.3 Collector 菜单

与 Viewer 菜单功能相同,但作用于所有Viewer:First/Last/Prev/Next让所有 Viewer 滚动,Refresh清空所有 Viewer 并重新读取。

6.4 Filters and scaling 菜单

条目作用
ActiveFilter (=)以当前激活过滤器和缩放比例新开一个 Viewer 窗口
ActiveFilter (+)以当前激活过滤器、更大缩放比例新开 Viewer 窗口
ActiveFilter (-)以当前激活过滤器、更小缩放比例新开 Viewer 窗口
all (0)以 Collector Filter 作为激活过滤器新开 Viewer,查看 Collector 中全部事件
AnotherFilter (2)字典中插入的更多过滤器会依次出现在此菜单;第二个过滤器快捷键为 2,第三个为 3,依此类推;条目按名字排序

6.5 滑块与单选按钮

控件作用
Hide From=To勾选后隐藏所有 from-actor 等于 to-actor 的事件(这类事件又称动作 actions)
Hide (excluded actors)勾选后隐藏所有 actor 被标记为排除的事件。被排除的 actor 正常显示时名字带圆括号()
Detail level控制 Viewer 的分辨率:只显示 detail level小于滑块值的事件(默认 100 = 最大,显示全部)

6.6 其它交互特性

  • 垂直滚动:鼠标滚轮与上下方向键小幅滚动;PageUp/PageDown、Home/End 大幅滚动;
  • 显示事件细节:在事件标签或箭头上单击鼠标左键,弹出 Contents Viewer 显示事件内容;
  • 高亮 actor(切换):在 actor 名字标签上单击左键,名字会被方括号[]括起。当有一个或多个 actor 被高亮时,只显示与这些 actor 相关的事件,其余全部隐藏;
  • 排除 actor(切换):在 actor 名字标签上单击右键,名字被圆括号()括起。被排除后,与该 actor 相关的所有事件被隐藏;若勾选了Hide (excluded actors),连名字标签和对应竖线也会被隐藏;
  • 移动 actor:在 actor 名字标签上按住鼠标左键拖动,到新位置松开即可;
  • 显示全部 actor:按键a,重置隐藏/高亮设置。

七、Contents Viewer 窗口:单事件检视与搜索

7.1 File 菜单

  • Close:关闭本窗口;
  • Save:把本窗口内容保存到文件。

7.2 Filters 菜单

  • ActiveFilter:以相同激活过滤器新开一个 Contents Viewer 窗口;
  • AnotherFilter (2):字典中新增的过滤器依次出现在此菜单(编号 2、3……,按名字排序)。

7.3 Hide 菜单

  • Hide actor in viewer:已知 actor 在 Viewer 中以命名竖条显示;隐藏 actor 后其竖条被移除,Viewer 自动刷新。注意:只有当max_actors阈值已到达时,隐藏 actor 才真正有意义——此时被隐藏的 actor 会以"UNKNOWN"形式显示;若阈值未到达,该 actor 仍会以竖条重新出现在 Viewer 中;
  • Show actor in viewer:把 actor 加为 Viewer 的已知 actor,并赋予其独立竖条。

7.4 Search 菜单

  • Forward from this event:把当前事件设为 Viewer 的第一个事件,并切换到正向搜索模式;该事件的 actor(from、to 或两者)会被加入选定 actor 列表;
  • Reverse from this event:把当前事件设为 Viewer 的第一个事件,并进入反向搜索模式;注意反向模式下事件按倒序显示;
  • Abort search. Display all:无论是否正在搜索,切换到显示全部事件的模式并中止搜索。

八、配置参数速查:事件排序与 Collector 选项

Collector 中 Ets 表里的事件按时间戳排序,用哪个时间戳由event_order参数控制(见 et_collector.erl 的start_link/1注释与 make_key/2 的实现):

  • trace_ts(默认):按 trace 数据被生成的时间排序;
  • event_ts:按 trace 数据被解析(转换为#event{})的时间排序。

若 trace 数据缺少时间戳,trace_ts会取event_ts的值。

et_collector:start_link/1支持的选项与默认值汇总如下:

选项默认值说明
{parent_pid, Pid}self()Collector 链接的父进程
{event_order, trace_ts \| event_ts}trace_ts事件排序时间戳
{dict_insert, {filter, all}, Fun}默认et_selector:parse_event/2设置 Collector Filter(可替换/追加命名过滤器)
{dict_insert, {subscriber, Pid}, Val}注册字典订阅者
{dict_insert, Key, Val}/{dict_delete, Key}字典读写
{trace_client, {event_file, File}}启动文件 trace client
{trace_client, {dbg_trace_type(), Params}}启动 dbg 格式 trace client(file/ip/follow_file 等)
{trace_global, boolean()}false是否启用全局追踪
{trace_pattern, Pattern}undefined全局 trace pattern(min/max/整数或 match spec)
{trace_port, integer()}4711全局追踪使用的端口基址(冲突时自动递增)
{trace_max_queue, integer()}50trace 端口队列上限

Viewer 侧常用选项(et_demo:start/1可作参考)还包括max_actors(如infinity)、max_eventsactive_filterhide_actionstitle等。

九、源码走读:Collector 内部是如何工作的

从 et_collector.erl 的实现可以确认文档描述的全部机制:

  1. 存储结构init_tables/1创建两张ordered_set类型的公共 Ets 表——et_events(事件表)与et_dict(字典表),键位置均为 1(L1128-L1131)。ordered_set保证事件可以按时间戳键前后迭代;
  2. 上报路径report/2先从 Collector 获取table_handle(内含事件表 id、event_order与过滤器 fun),随后应用过滤器:false直接丢弃,true/{true, NewEvent}则用make_key/2生成排序键后ets:insert;若返回其它异常值,会构造一个bad_filter事件存入(L474-L524);
  3. 句柄优化get_table_handle每次从字典中读取名为all的 Collector Filter 打包进句柄(L1188-L1194),这样后续report无需再与 Collector 进程同步交互;
  4. 订阅通知:字典插入/删除会向订阅者广播{et, Msg};新增订阅者会先收到全部既有条目再收到新条目;Collector 还会定期check_size/1,当事件表大小变化时向订阅者发送{more_events, Size}(L1526-L1536),这正是 Viewer"轮询到新事件"的通知机制;
  5. 遍历接口iterate/3,5支持从first/last或任意事件键出发正向/反向遍历,Limit为正整数、infinity、负整数或'-infinity'(L888-L1034);
  6. 全局追踪init_global/1trace_global为 true 时启动本地 dbg tracer、net_kernel:monitor_nodes(true)并枚举已有节点;handle_info({nodeup, Node}, ...)通过rpc:call在远端节点启动monitor_trace_port/2(port tracer),再在本地start_trace_client(self(), ip, {Host, Port})建立消费端,并在该节点上应用 trace pattern(L1133-L1146、L1307-L1383)。默认的全局 trace pattern 为max

十、进一步探索

  • 完整示例与过滤器集合:et_demo.erl(sim_trans模拟、live_trans实机 Mnesia 追踪、filters/0内置过滤器族);
  • 事件记录结构:et.hrl;
  • 上报 API:et.erl、采集存储 API:et_collector.erl;
  • pattern 与默认转换:et_selector.erl;
  • 图形界面实现:et_wx_viewer.erl 与 et_wx_contents_viewer.erl。

ET 的价值在于把"事件采集—存储—过滤—可视化"整条链路做成标准件:业务代码埋点近乎零成本,多节点全局追踪一键开启,而过滤器机制又让同一份 trace 数据可以衍生出无数种观察视角——这正是调试分布式系统、分析 Mnesia 等内部协议行为时最实用的工具箱。

【免费下载链接】otpErlang/OTP项目地址: https://gitcode.com/gh_mirrors/ot/otp

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询