查看源代码 seq_trace (内核 v10.2)

信息传输的顺序跟踪。

顺序跟踪使得可以跟踪由一个初始信息传输导致的过程之间的信息流。顺序跟踪独立于Erlang中的普通跟踪,后者由 erlang:trace/3 BIF 控制。有关顺序跟踪是什么以及如何使用它的更多信息,请参阅 顺序跟踪 部分。

seq_trace 提供了控制顺序跟踪各个方面的功能。它提供了激活、停用、检查以及收集跟踪输出的功能。

发送到系统跟踪器的跟踪消息

消息的格式如下,具体取决于跟踪令牌的标志 timestamp 设置为 true 还是 false

{seq_trace, Label, SeqTraceInfo, TimeStamp}

{seq_trace, Label, SeqTraceInfo}

其中

Label = int()
TimeStamp = {Seconds, Milliseconds, Microseconds}
  Seconds = Milliseconds = Microseconds = int()

SeqTraceInfo 可以具有以下格式

  • {send, Serial, From, To, Message} - 当进程 From 的跟踪令牌标志 send 设置为 true 时发送信息时使用。To 可以是进程标识符、表示为 {NameAtom, NodeAtom} 的节点上的注册名称,或表示为原子符号的节点名称。From 可以是进程标识符或表示为原子符号的节点名称。Message 包含在此信息传输中传递的信息。如果通过消息传递进行传输,则它是实际的消息。

  • {'receive', Serial, From, To, Message} - 当进程 To 接收到跟踪令牌标志 'receive' 设置为 true 的信息时使用。To 可以是进程标识符,或表示为原子符号的节点名称。From 可以是进程标识符或表示为原子符号的节点名称。Message 包含在此信息传输中传递的信息。如果通过消息传递进行传输,则它是实际的消息。

  • {print, Serial, From, _, Info} - 当进程 From 调用 seq_trace:print(Label, TraceInfo) 并且其跟踪令牌的标志 print 设置为 true,并且 label 设置为 Label 时使用。

Serial 是一个元组 {PreviousSerial, ThisSerial},其中

  • 整数 PreviousSerial 表示上次接收到的携带跟踪令牌的信息中传递的序列计数器。如果该进程是新顺序跟踪中的第一个进程,则 PreviousSerial 设置为进程内部“跟踪时钟”的值。
  • 整数 ThisSerial 是一个进程在传出消息中设置的序列计数器。它基于进程内部的“跟踪时钟”,该时钟在附加到消息中的跟踪令牌之前加一。

顺序跟踪

顺序跟踪是一种跟踪不同本地或远程进程之间的一系列信息传输的方法,其中该序列由单个传输启动。典型的信息传输是在两个进程之间传递的普通 Erlang 消息,但信息也可以通过其他方式传输。简而言之,它的工作原理如下

每个进程都有一个跟踪令牌,该令牌可以为空或不为空。当不为空时,跟踪令牌可以看作是元组 {Label, Flags, Serial, From}。当信息在进程之间传递时,跟踪令牌会隐式传递。在大多数情况下,信息在进程之间的普通消息中传递,但信息也通过其他方式在进程之间传递。例如,通过派生一个新进程。两个进程之间的信息传输由发送事件和接收事件表示,无论信息是如何传递的。

要启动顺序跟踪,用户必须显式设置将发送序列中第一个信息的进程中的跟踪令牌。

每次进程接收到信息时,都会设置进程的跟踪令牌。这通常是当进程在接收语句中匹配消息时,根据接收到的消息携带的跟踪令牌(无论是否为空)进行设置。

在每个 Erlang 节点上,可以设置一个进程作为系统跟踪器。每次发送或接收带有跟踪令牌的信息时(如果跟踪令牌标志 send'receive' 设置),此进程将接收跟踪消息。然后,系统跟踪器可以打印每个跟踪事件,将其写入文件,或进行任何其他合适的操作。

注意

系统跟踪器仅接收 Erlang 节点本地发生的那些跟踪事件。要获得涉及多个 Erlang 节点上的进程的顺序跟踪的完整画面,必须合并每个相关节点上的系统跟踪器的输出(离线)。

以下部分描述了顺序跟踪及其最基本的概念。

不同的信息传输

信息以多种不同的方式在进程之间流动。并非所有的信息流都会被顺序跟踪覆盖。一个例子是通过 ETS 表传递的信息。以下是顺序跟踪涵盖的信息路径列表

  • 消息传递 - 在 Erlang 进程之间传递的所有普通消息。

  • 退出信号 - 退出信号表示为 {'EXIT', Pid, Reason} 元组。

  • 进程派生 - 进程派生表示为多个信息传输。至少有一个派生请求和一个派生回复。实际的信息传输量取决于它是哪种类型的派生,并且也可能在未来的实现中发生变化。请注意,这或多或少是您正在窥视的内部协议。派生请求将表示为第一个元素包含原子符号 spawn_request 的元组,但这或多或少是您可以依赖的全部内容。

注意

如果您在系统上执行普通的 sendreceive 跟踪,您将只会看到普通的消息传递,而不会看到上面列出的其他信息传输。

注意

当发送事件和对应的接收事件不都对应于普通的 Erlang 消息时,跟踪消息的 Message 部分可能不相同。这是因为在生成跟踪消息时并非所有信息都可用。

跟踪令牌

每个进程都有一个当前的跟踪令牌,该令牌在创建进程时从父进程“隐式”传递。

进程的当前令牌通过以下两种方式之一设置

  • 通过进程本身,通过调用 seq_trace:set_token/1,2 显式设置
  • 当接收到信息时。这通常是在接收表达式中匹配接收到的消息时,但也包括通过其他方式接收到信息时。

在这两种情况下,都会设置当前令牌。特别是,如果接收到的消息的令牌为空,则进程的当前令牌将设置为空。

跟踪令牌包含一个标签和一组标志。标签和标志都在上面的两种替代方案中设置。

序列号

跟踪令牌包含一个名为 serial 的组件。它由两个整数组成,PreviousCurrent。目的是在跟踪序列中唯一标识每个跟踪事件,并按时间顺序和不同的分支(如果有)对消息进行排序。

更新 Serial 的算法可以描述如下

让每个进程都有两个计数器,prev_cntcurr_cnt,当在跟踪序列之外创建进程时,这两个计数器都设置为 0。计数器在以下情况下更新

  • 当进程即将将信息传递给另一个进程并且跟踪令牌不为空时。这通常在发送消息时发生,但也例如在派生另一个进程时发生。

    让跟踪令牌的序列号为 tprevtcurr

    curr_cnt := curr_cnt + 1
    tprev := prev_cnt
    tcurr := curr_cnt

    然后,将带有 tprevtcurr 的跟踪令牌与传递给另一个进程的信息一起传递。

  • 当进程调用 seq_trace:print(Label, Info)Label 与跟踪令牌的标签部分匹配,并且跟踪令牌打印标志为 true 时。

    该算法与上面的发送算法相同。

  • 当接收到的信息也包含非空的跟踪令牌时。例如,当在接收表达式中匹配出消息时,或者当派生新进程时。

    进程跟踪令牌设置为来自消息的跟踪令牌。

    让跟踪令牌的序列号为 tprevtcurr

    if (curr_cnt < tcurr )
       curr_cnt := tcurr
    prev_cnt := tcurr

每次进程参与顺序跟踪时,都会递增进程的 curr_cnt。如果进程寿命很长并且参与了很多顺序跟踪,则计数器可能会达到其限制(27 位)。如果计数器溢出,则无法使用序列号来排序跟踪事件。为防止计数器在顺序跟踪过程中溢出,可以调用函数 seq_trace:reset_trace/0 来重置 Erlang 节点中所有进程的 prev_cntcurr_cnt。此函数还会将进程及其消息队列中的所有跟踪令牌设置为空,从而停止所有正在进行的顺序跟踪。

性能考虑

只要没有激活跟踪,启用顺序跟踪的系统的性能下降可以忽略不计。当激活跟踪时,每个跟踪的消息都会产生额外的成本,但所有其他消息不受影响。

端口

顺序跟踪不会跨端口执行。

如果用户出于某种原因想要将跟踪令牌传递到端口,则必须在端口控制进程的代码中手动完成此操作。端口控制进程必须检查适当的顺序跟踪设置(如从 seq_trace:get_token/1 获得)并将跟踪信息包含在发送到其各自端口的消息数据中。

类似地,对于从端口接收到的消息,端口控制器必须检索特定于跟踪的信息,并通过调用 seq_trace:set_token/2 设置适当的顺序跟踪标志。

分布式

节点之间的顺序跟踪是透明执行的。这也适用于使用 Erl_Interface 构建的 C 节点。使用 Erl_Interface 构建的 C 节点仅维护一个跟踪令牌,这意味着从顺序跟踪的角度来看,C 节点看起来像一个进程。

使用示例

这个例子粗略地展示了如何使用新的原语以及它会产生什么样的输出。

假设你有一个初始进程,其 Pid == <0.30.0>,如下所示:

-module(seqex).
-compile(export_all).

loop(Port) ->
    receive
        {Port,Message} ->
            seq_trace:set_token(label,17),
            seq_trace:set_token('receive',true),
            seq_trace:set_token(print,true),
            seq_trace:print(17,"**** Trace Started ****"),
            call_server ! {self(),the_message};
        {ack,Ack} ->
            ok
    end,
    loop(Port).

以及一个已注册的进程 call_server,其 Pid == <0.31.0>,如下所示:

loop() ->
    receive
        {PortController,Message} ->
            Ack = {received, Message},
            seq_trace:print(17,"We are here now"),
            PortController ! {ack,Ack}
    end,
    loop().

系统 sequential_tracer 的一个可能输出如下所示:

17:<0.30.0> Info {0,1} WITH
"**** Trace Started ****"
17:<0.31.0> Received {0,2} FROM <0.30.0> WITH
{<0.30.0>,the_message}
17:<0.31.0> Info {2,3} WITH
"We are here now"
17:<0.30.0> Received {2,4} FROM <0.31.0> WITH
{ack,{received,the_message}}

生成此打印输出的系统跟踪器进程的实现可能如下所示:

tracer() ->
    receive
        {seq_trace,Label,TraceInfo} ->
           print_trace(Label,TraceInfo,false);
        {seq_trace,Label,TraceInfo,Ts} ->
           print_trace(Label,TraceInfo,Ts);
        _Other -> ignore
    end,
    tracer().

print_trace(Label,TraceInfo,false) ->
    io:format("~p:",[Label]),
    print_trace(TraceInfo);
print_trace(Label,TraceInfo,Ts) ->
    io:format("~p ~p:",[Label,Ts]),
    print_trace(TraceInfo).

print_trace({print,Serial,From,_,Info}) ->
    io:format("~p Info ~p WITH~n~p~n", [From,Serial,Info]);
print_trace({'receive',Serial,From,To,Message}) ->
    io:format("~p Received ~p FROM ~p WITH~n~p~n",
              [To,Serial,From,Message]);
print_trace({send,Serial,From,To,Message}) ->
    io:format("~p Sent ~p TO ~p WITH~n~p~n",
              [From,Serial,To,Message]).

创建运行此跟踪器函数并将该进程设置为系统跟踪器的代码可能如下所示:

start() ->
    Pid = spawn(?MODULE,tracer,[]),
    seq_trace:set_system_tracer(Pid), % set Pid as the system tracer
    ok.

使用像 test/0 这样的函数,可以启动整个示例:

test() ->
    P = spawn(?MODULE, loop, [port]),
    register(call_server, spawn(?MODULE, loop, [])),
    start(),
    P ! {port,message}.

摘要

类型

表示跟踪令牌的不透明项(一个元组)。

函数

返回当前系统跟踪器的 pid、端口标识符或跟踪器模块,如果未激活系统跟踪器,则返回 false

返回调用进程的跟踪令牌的值。如果返回 [],则表示跟踪未激活。返回的任何其他值都是活动跟踪令牌的值。返回的值可以用作 set_token/1 函数的输入。

返回跟踪令牌组件 Component 的值。有关 ComponentVal 的可能值,请参见 set_token/2

如果调用进程当前在顺序跟踪中执行,并且设置了跟踪令牌的 print 标志,则将 Erlang 项 TraceInfo 放入顺序跟踪输出中。

print/1 相同,但附加条件是,仅当 Label 等于跟踪令牌的标签组件时,才输出 TraceInfo

将本地节点上所有进程的跟踪令牌设置为空。用于创建跟踪令牌序列的进程内部计数器设置为 0。将消息队列中所有消息的跟踪令牌设置为空。总而言之,这将有效地停止本地节点中所有正在进行的顺序跟踪。

设置系统跟踪器。系统跟踪器可以是进程、端口或由 Tracer 表示的跟踪器模块。返回先前的值(如果未激活系统跟踪器,则可能为 false)。

将调用进程的跟踪令牌设置为 Token。如果 Token == [],则禁用跟踪;否则,Token 应该是从 get_token/0set_token/1 返回的 Erlang 项。set_token/1 可用于通过将跟踪令牌设置为空来临时排除消息传递的跟踪,如下所示:

将跟踪令牌的单个 Component 设置为 Val。返回组件的先前值。

类型

链接到此类型

component()

查看源代码 (未导出)
-type component() :: label | serial | flag().
-type flag() :: send | 'receive' | print | timestamp | monotonic_timestamp | strict_monotonic_timestamp.
-type token() :: {integer(), boolean(), _, _, _}.

表示跟踪令牌的不透明项(一个元组)。

链接到此类型

tracer()

查看源代码 (未导出)
-type tracer() :: (Pid :: pid()) | port() | (TracerModule :: {module(), term()}) | false.
-type value() ::
          (Label :: term()) |
          {Previous :: non_neg_integer(), Current :: non_neg_integer()} |
          (Bool :: boolean()).

函数

-spec get_system_tracer() -> Tracer when Tracer :: tracer().

返回当前系统跟踪器的 pid、端口标识符或跟踪器模块,如果未激活系统跟踪器,则返回 false

-spec get_token() -> [] | token().

返回调用进程的跟踪令牌的值。如果返回 [],则表示跟踪未激活。返回的任何其他值都是活动跟踪令牌的值。返回的值可以用作 set_token/1 函数的输入。

-spec get_token(Component) -> [] | {Component, Val} when Component :: component(), Val :: value().

返回跟踪令牌组件 Component 的值。有关 ComponentVal 的可能值,请参见 set_token/2

-spec print(TraceInfo) -> ok when TraceInfo :: term().

如果调用进程当前在顺序跟踪中执行,并且设置了跟踪令牌的 print 标志,则将 Erlang 项 TraceInfo 放入顺序跟踪输出中。

链接到此函数

print(Label, TraceInfo)

查看源代码
-spec print(Label, TraceInfo) -> ok when Label :: integer(), TraceInfo :: term().

print/1 相同,但附加条件是,仅当 Label 等于跟踪令牌的标签组件时,才输出 TraceInfo

-spec reset_trace() -> true.

将本地节点上所有进程的跟踪令牌设置为空。用于创建跟踪令牌序列的进程内部计数器设置为 0。将消息队列中所有消息的跟踪令牌设置为空。总而言之,这将有效地停止本地节点中所有正在进行的顺序跟踪。

链接到此函数

set_system_tracer(Tracer)

查看源代码
-spec set_system_tracer(Tracer) -> OldTracer when Tracer :: tracer(), OldTracer :: tracer().

设置系统跟踪器。系统跟踪器可以是进程、端口或由 Tracer 表示的跟踪器模块。返回先前的值(如果未激活系统跟踪器,则可能为 false)。

失败:如果 Pid 不是现有的本地 pid,则返回 {badarg, Info}}

-spec set_token(Token) -> PreviousToken | ok when Token :: [] | token(), PreviousToken :: [] | token().

将调用进程的跟踪令牌设置为 Token。如果 Token == [],则禁用跟踪;否则,Token 应该是从 get_token/0set_token/1 返回的 Erlang 项。set_token/1 可用于通过将跟踪令牌设置为空来临时排除消息传递的跟踪,如下所示:

OldToken = seq_trace:set_token([]), % set to empty and save
                                    % old value
% do something that should not be part of the trace
io:format("Exclude the signalling caused by this~n"),
seq_trace:set_token(OldToken), % activate the trace token again
...

返回跟踪令牌的先前值。

链接到此函数

set_token(Component, Val)

查看源代码
-spec set_token(Component, Val) -> OldVal
                   when Component :: component(), Val :: value(), OldVal :: value().

将跟踪令牌的单个 Component 设置为 Val。返回组件的先前值。

  • set_token(label, Label) - label 组件是一个术语,用于标识属于同一顺序跟踪的所有事件。如果可以同时激活多个顺序跟踪,则使用 label 来标识单独的跟踪。默认值为 0。

    警告

    在 OTP 21 之前,标签被限制为小的有符号整数(28 位)。如果跟踪令牌跨越到不支持标签的节点,则会静默丢弃该令牌。

  • set_token(serial, SerialValue) - SerialValue = {Previous, Current}serial 组件包含计数器,这些计数器使跟踪的消息能够被排序,永远不应由用户显式设置,因为这些计数器会自动更新。默认值为 {0, 0}

  • set_token(send, Bool) - 一个跟踪令牌标志(true | false),用于启用/禁用发送信息的跟踪。默认值为 false

  • set_token('receive', Bool) - 一个跟踪令牌标志(true | false),用于启用/禁用接收信息的跟踪。默认值为 false

  • set_token(print, Bool) - 一个跟踪令牌标志(true | false),用于启用/禁用对 seq_trace:print/1 的显式调用的跟踪。默认值为 false

  • set_token(timestamp, Bool) - 一个跟踪令牌标志(true | false),用于启用/禁用为每个跟踪事件生成时间戳。默认值为 false

  • set_token(strict_monotonic_timestamp, Bool) - 一个跟踪令牌标志(true | false),用于启用/禁用为每个跟踪事件生成严格单调的时间戳。默认值为 false。时间戳将由 Erlang 单调时间和一个单调递增的整数组成。时间戳的格式和值与 {erlang:monotonic_time(nanosecond), erlang:unique_integer([monotonic])} 生成的相同。

  • set_token(monotonic_timestamp, Bool) - 一个跟踪令牌标志(true | false),用于启用/禁用为每个跟踪事件生成严格单调的时间戳。默认值为 false。时间戳将使用 Erlang 单调时间。时间戳的格式和值与 erlang:monotonic_time(nanosecond) 生成的相同。

如果传递了多个时间戳标志,则 timestamp 优先于 strict_monotonic_timestamp,而 strict_monotonic_timestamp 又优先于 monotonic_timestamp。所有时间戳标志都会被记住,因此,如果传递了两个标志,并且稍后禁用了优先级较高的标志,则另一个标志将变为活动状态。