檢視原始碼 seq_trace (核心 v10.2)

資訊傳輸的循序追蹤。

循序追蹤可以追蹤因一次初始資訊傳輸而在不同程序之間產生的資訊流。循序追蹤與 Erlang 中由 erlang:trace/3 BIF 控制的普通追蹤是獨立的。有關循序追蹤是什麼以及如何使用它的更多資訊,請參閱 循序追蹤 章節。

seq_trace 提供控制循序追蹤所有方面的功能。有啟用、停用、檢查和收集追蹤輸出的功能。

傳送至系統追蹤器的追蹤訊息

訊息格式如下列之一,取決於追蹤令牌的 timestamp 旗標是否設定為 truefalse

{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}。當資訊在程序之間傳遞時,追蹤令牌會被不可見地傳遞。在大多數情況下,資訊是在程序之間的普通訊息中傳遞的,但是資訊也可以透過其他方式在程序之間傳遞。例如,透過產生一個新程序。兩個程序之間的資訊傳輸由一個發送事件和一個接收事件表示,無論它是如何傳遞的。

要啟動循序追蹤,使用者必須顯式設定將在序列中發送第一個資訊的程序的追蹤令牌。

每次程序接收到資訊時都會設定程序的追蹤令牌。這通常是在程序根據接收到的訊息攜帶的追蹤令牌(無論是否為空)在 receive 陳述式中匹配訊息時。

在每個 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 時。

    演算法與上述發送的演算法相同。

  • 當接收到的資訊也包含非空的追蹤令牌時。例如,當在 receive 表示式中比對出訊息時,或當產生新程序時。

    程序追蹤令牌設定為訊息中的追蹤令牌。

    讓追蹤令牌的序列為 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。傳回元件的先前值。

類型

-type component() :: label | serial | flag().
-type flag() :: send | 'receive' | print | timestamp | monotonic_timestamp | strict_monotonic_timestamp.
-type token() :: {integer(), boolean(), _, _, _}.

一個不透明的詞彙(元組),代表追蹤令牌。

-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。所有時間戳記標誌都會被記住,因此如果傳遞兩個標誌,且稍後停用優先順序較高的標誌,則另一個標誌將會變為啟用狀態。