查看源代码 高级示例

模拟 Mnesia 事务

前一章中运行模拟 Mnesia 事务示例的 Erlang 代码包含在 et/examples/et_demo.erl 文件中


sim_trans() ->
    sim_trans([]).

sim_trans(ExtraOptions) ->
    Options = [{dict_insert, {filter, mgr_actors}, fun mgr_actors/1}],
    {ok, Viewer} = et_viewer:start_link(Options ++ ExtraOptions),
    Collector = et_viewer:get_collector_pid(Viewer),
    et_collector:report_event(Collector, 60, my_shell, mnesia_tm, start_outer,
                              "Start outer transaction"),
    et_collector:report_event(Collector, 40, mnesia_tm, my_shell, new_tid,
                              "New transaction id is 4711"),
    et_collector:report_event(Collector, 20, my_shell, mnesia_locker, try_write_lock,
                              "Acquire write lock for {my_tab, key}"),
    et_collector:report_event(Collector, 10, mnesia_locker, my_shell, granted,
                              "You got the write lock for {my_tab, key}"),
    et_collector:report_event(Collector, 60, my_shell, do_commit,
                              "Perform  transaction commit"),
    et_collector:report_event(Collector, 40, my_shell, mnesia_locker, release_tid,
                              "Release all locks for transaction 4711"),
    et_collector:report_event(Collector, 60, my_shell, mnesia_tm, delete_transaction,
                              "End of outer transaction"),
    et_collector:report_event(Collector, 20, my_shell, end_outer,
                              "Transaction returned {atomic, ok}"),
    {collector, Collector}.

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}]}}.

如果您调用 et_demo:sim_trans() 函数,将会弹出一个 Viewer 窗口,并且序列跟踪将几乎与运行以下 Mnesia 事务相同

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

查看器窗口将如下所示

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>;}
2> et_demo:sim_trans().
{ok,{table_handle,<0.45.0>,24596,trace_ts,
     #Fun<et_collector.0.62831470>}}

A simulated Mnesia transaction which writes one record

Mnesia 事务示例中使用的一些便利函数

module_as_actor 过滤器转换 事件记录,使模块名称成为参与者,被调用的函数成为标签。如果有关调用者的信息存在,将显示从调用者到被调用者的箭头。dbg:tpl/2 函数的 [{message, {caller}}, {return_trace}] 选项将包含 Erlang 跟踪中必要的信息。以下是 module_as_actor 过滤器


module_as_actor(E) when is_record(E, event) ->
    case lists:keysearch(mfa, 1, E#event.contents) of
        {value, {mfa, {M, F, _A}}} ->
            case lists:keysearch(pam_result, 1, E#event.contents) of
                {value, {pam_result, {M2, _F2, _A2}}} ->
                    {true, E#event{label = F, from = M2, to = M}};
                _ ->
                    {true, E#event{label = F, from = M, to = M}}
            end;
        _ ->
            false
    end.

plain_process_info 过滤器不会更改 事件记录。它仅确保跳过与进程无关的事件


plain_process_info(E) when is_record(E, event) ->
    case E#event.label of
        send                          -> true;
        send_to_non_existing_process  -> true;
        'receive'                     -> true;
        spawn                         -> true;
        exit                          -> true;
        link                          -> true;
        unlink                        -> true;
        getting_linked                -> true;
        {seq_send, _Label}            -> true;
        {seq_receive, _Label}         -> true;
        {seq_print, _Label}           -> true;
        {drop, _N}                    -> true;
        _                             -> false
    end.

plain_process_info_nolink 过滤器不会更改 事件记录。它使用 plain_process_info,但也确保跳过与链接和取消链接相关的进程信息


plain_process_info_nolink(E) when is_record(E, event) ->
    (E#event.label /= link) and
    (E#event.label /= unlink) and
    (E#event.label /= getting_linked) and
    plain_process_info(E).

为了简化启动带有上述过滤器以及其他一些过滤器(也在 et/examples/et_demo.erl src/et_collector.erl 中找到)的 et_viewer 进程,可以使用 et_demo:start/0,1 函数


start() ->
    start([]).

start(ExtraOptions) ->
    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).

一个简单的单行代码即可启动该工具

          erl -pa ../examples -s et_demo

过滤器通过以下参数包含


filters() ->
    [{dict_insert, {filter, module_as_actor},
                   fun module_as_actor/1},
     {dict_insert, {filter, plain_process_info},
                   fun plain_process_info/1},
     {dict_insert, {filter, plain_process_info_nolink},
                   fun plain_process_info_nolink/1},
     {dict_insert, {filter, named_process_info},
                   fun named_process_info/1},
     {dict_insert, {filter, named_process_info_nolink},
                   fun named_process_info_nolink/1},
     {dict_insert, {filter, node_process_info},
                   fun node_process_info/1},
     {dict_insert, {filter, node_process_info_nolink},
                   fun node_process_info_nolink/1},
     {dict_insert, {filter, application_as_actor},
                   fun application_as_actor/1}
    ].

真实 Mnesia 事务的 Erlang 跟踪

以下代码段 et_demo:trace_mnesia/0 激活 Mnesia 应用程序中所有模块的本地和外部函数调用的调用跟踪。调用跟踪配置为覆盖所有进程(包括现有进程和将来生成的进程),并包含跟踪数据的时间戳。它还会为 Mnesia 的静态进程以及调用进程(即您的 shell)激活进程相关事件的跟踪。请注意,以下代码中的 whereis/1 调用要求被跟踪的 Mnesia 应用程序和 et_viewer 都在同一节点上运行


trace_mnesia() ->
    Modules = mnesia:ms(),
    Spec = [{message, {caller}}, {return_trace}],
    Flags = [send, 'receive', procs, timestamp],
    dbg:p(all, [call, timestamp]),
    [dbg:tpl(M, [{'_', [], Spec}]) || M <- Modules],
    LocallyRunningServers = [M || M <- Modules, whereis(M) /= undefined],
    [dbg:p(whereis(RS), Flags) || RS <- LocallyRunningServers],
    dbg:p(self(), Flags),
    LocallyRunningServers.

et_demo:live_trans/0 函数启动全局 Collector,启动 Viewer,启动 Mnesia,创建本地表,激活跟踪(如上所述),并将 shell 进程注册为 'my_shell' 以便清晰。最后,运行一个简单的 Mnesia 事务,该事务写入单个记录


live_trans() ->
    live_trans([]).

live_trans(ExtraOptions) ->
    Options = [{title, "Mnesia tracer"},
	       {hide_actions, true},
	       {active_filter, named_process_info_nolink}],
    et_demo:start(Options ++ ExtraOptions),
    mnesia:start(),
    mnesia:create_table(my_tab, [{ram_copies, [node()]}]),
    et_demo:trace_mnesia(),
    register(my_shell, self()),

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

现在我们运行 et_demo:live_trans/0 函数

erl -pa ../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> et_demo:live_trans().
{atomic,ok}

请探索不同的过滤器,以便从不同的角度查看被跟踪的事务

A real Mnesia transaction which writes one record

Megaco 启动的 Erlang 跟踪

事件跟踪器 (ET) 工具最初是为了演示如何通过 Megaco 协议发送消息而编写的。那是早些时候,在 IETFITU 的标准机构批准 Megaco (也称为 H.248) 作为国际标准之前。

在 Erlang/OTP 的 Megaco 应用程序中,代码经过精心设计,使用对 et:trace_me/5 的调用。为了简单地实现对跟踪级别的动态控制,为每个调用都给出了详细级别。

megaco_filter 模块实现了 Megaco 消息的自定义过滤器。它还结合使用 trace_globaltrace_pattern

-module(megaco_filter).
-export([start/0]).

start() ->
    Options =
        [{event_order, event_ts},
         {scale, 3},
         {max_actors, infinity},
         {trace_pattern, {megaco, max}},
         {trace_global, true},
         {dict_insert, {filter, megaco_filter}, fun filter/1},
         {active_filter, megaco_filter},
         {title, "Megaco tracer - Erlang/OTP"}],
    et_viewer:start(Options).

首先,我们启动一个带有全局 Collector 及其 Viewer 的 Erlang 节点。

erl -sname observer
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)
(observer@falco)1> megaco_filter:start().
{ok,<0.48.0>}

其次,我们启动另一个 Erlang 节点,我们将其连接到观察者节点,然后启动我们要跟踪的应用程序。在这种情况下,我们启动一个媒体网关控制器,该控制器在 Megaco 的文本和二进制端口上侦听 TCP 和 UDP

erl -sname mgc -pa ../../megaco/examples/simple
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)
(mgc@falco)1> net:ping(observer@falco).
pong
(mgc@falco)2> megaco:start().
ok
(mgc@falco)3> megaco_simple_mgc:start().
{ok,[{ok,2944,
         {megaco_receive_handle,{deviceName,"controller"},
                                megaco_pretty_text_encoder,[],megaco_tcp,dynamic}},
     {ok,2944,
         {megaco_receive_handle,{deviceName,"controller"},
                                megaco_pretty_text_encoder,[],megaco_udp,dynamic}},
     {ok,2945,
         {megaco_receive_handle,{deviceName,"controller"},
                                megaco_binary_encoder,[],megaco_tcp,dynamic}},
     {ok,2945,
         {megaco_receive_handle,{deviceName,"controller"},
                                megaco_binary_encoder,[],megaco_udp,dynamic}}]}

最后,我们启动一个用于媒体网关的 Erlang 节点,并连接到观察者节点。每个媒体网关都连接到控制器,并发送初始服务更改消息。控制器接受网关,并根据每个网关的偏好,使用相同的传输机制和消息编码向每个网关发送回复。这就是 TCP/IP 传输、UDP/IP 传输、文本编码和 ASN.1 BER 编码的所有组合

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)
(mg@falco)1> net:ping(observer@falco).
pong
(mg@falco)2> megaco_simple_mg:start().
[{{deviceName,"gateway_tt"},
  {error,{start_user,megaco_not_started}}},
 {{deviceName,"gateway_tb"},
  {error,{start_user,megaco_not_started}}},
 {{deviceName,"gateway_ut"},
  {error,{start_user,megaco_not_started}}},
 {{deviceName,"gateway_ub"},
  {error,{start_user,megaco_not_started}}}]
(mg@falco)3> megaco:start().
ok
(mg@falco)4> megaco_simple_mg:start().
[{{deviceName,"gateway_tt"},
  {1,
   {ok,[{'ActionReply',0,asn1_NOVALUE,asn1_NOVALUE,
            [{serviceChangeReply,
                 {'ServiceChangeReply',
                     [{megaco_term_id,false,["root"]}],
                     {serviceChangeResParms,
                         {'ServiceChangeResParm',
                             {deviceName,"controller"},
                             asn1_NOVALUE,asn1_NOVALUE,asn1_NOVALUE,
                             asn1_NOVALUE}}}}]}]}}},
 {{deviceName,"gateway_tb"},
  {1,
   {ok,[{'ActionReply',0,asn1_NOVALUE,asn1_NOVALUE,
            [{serviceChangeReply,
                 {'ServiceChangeReply',
                     [{megaco_term_id,false,["root"]}],
                     {serviceChangeResParms,
                         {'ServiceChangeResParm',
                             {deviceName,"controller"},
                             asn1_NOVALUE,asn1_NOVALUE,asn1_NOVALUE,
                             asn1_NOVALUE}}}}]}]}}},
 {{deviceName,"gateway_ut"},
  {1,
   {ok,[{'ActionReply',0,asn1_NOVALUE,asn1_NOVALUE,
            [{serviceChangeReply,
                 {'ServiceChangeReply',
                     [{megaco_term_id,false,["root"]}],
                     {serviceChangeResParms,
                         {'ServiceChangeResParm',
                             {deviceName,"controller"},
                             asn1_NOVALUE,asn1_NOVALUE,asn1_NOVALUE,
                             asn1_NOVALUE}}}}]}]}}},
 {{deviceName,"gateway_ub"},
  {1,
   {ok,[{'ActionReply',0,asn1_NOVALUE,asn1_NOVALUE,
            [{serviceChangeReply,
                 {'ServiceChangeReply',
                     [{megaco_term_id,false,["root"]}],
                     {serviceChangeResParms,
                         {'ServiceChangeResParm',
                             {deviceName,"controller"},
                             asn1_NOVALUE,asn1_NOVALUE,
                             asn1_NOVALUE,...}}}}]}]}}}]

当我们单击 [gateway_tt] 参与者名称以便仅显示有关该参与者的事件时,采用的 Megaco 查看器如下所示

The viewer adopted for Megaco

格式化的 Megaco 消息如下所示

A textual Megaco message

同一 Megaco 消息的相应内部形式如下所示

The internal form of a Megaco message