はじめに
dotnet-traceは、v5.0.160202から起動時からトレースログの収集を行えるようになった。
この記事では、どのようにその機能を実現しているかということを解説したいと思う。
dotnet-traceとは
詳しくは dotnet/diagnosticsのドキュメントを参照 してほしいが、要はEventSourceの情報を外部から収集するためのツールで、EventPipeの仕組みを使用している。
dotnet-traceはv5.0.152202までは、起動中のプロセスから選択して収集するという方法しか取れなかった。
というのも、使用しているIPCプロトコルの都合上、PIDが接続時に必要で、これはどうしても起動しないとわからなかったからである。
そこで、Diagnostic Portsという概念を導入して、起動時からの監視をできるようにしたというわけである。
Diagnostic Portsとは
Diagnostic IPC Protocolは、PIDを含んだ名前付きパイプ、あるいはunixドメインソケットで通信を行うが、前述したように、
デフォルトで使用されるポートはランタイム側でPIDを含めた形で作成される。
しかし、一部のシナリオではこの仕組みは不都合な点があったため、追加で以下のような動作をランタイム側に追加した。
- 環境変数に
DOTNET_DiagnosticPorts
があるか見る - もしあれば、スタートアップで動作を停止し、その名前のパイプが作成されるまで待つ
- 誰かが指定した名前のパイプを作成して、ランタイムが接続に成功したら、そこにAdvertiseメッセージを送る
- 以後はその確立した接続を使用して通常のEventPipeのやり取りを行う
図中にあるAdvertiseメッセージは以下のようなフォーマット
オフセット | サイズ | 名前 | 概要 |
---|---|---|---|
0 | 8 | magic | 固定データ(ADVR_V1\0 ) |
8 | 16 | runtimeCookie | IPC ProtocolのProcessInfoのruntimeCookieと同じGUID(同一PIDで異なるプロセスの割り込みを防ぐため?) |
24 | 8 | PID | プロセスID(リトルエンディアン) |
32 | 2 | reserved | 将来のための予約領域(現在は常に0) |
ProcessInfoに関しては、dotnet/diagnosticsのIPCプロトコルドキュメントを参考に
注意点
上記手順は現在のクライアントライブラリ(Microsoft.Diagnostics.NETCore.Client)では素直に実現はできない。
なぜならば、
- 図中で必要なResumeRuntimeメッセージを送るためのAPIがinternal
- Collector側のパイプを待ち受ける処理もinternal
dotnet-traceでこの辺りの処理ができているのは、クライアントライブラリでInternalsVisibleToされているからである。
1に関しては、一応パイプ待ち受け処理の中で手動でResumeRuntimeメッセージを送るという回避方法はある。
2に関してはAdvertiseメッセージにPIDが入っているため、これさえ取得できれば後はDiagnosticClientに渡せば通信可能。
この辺りがinternalな理由は、やはり一度publicにすると仕様変更が難しいためではないかと思う。
一応、dotnet-6.0のタイミングで対応を検討している模様なので、将来的には実装されるかもしれない。
終わりに
dotnet-traceで、起動時からEventSourceのログが取得できるようになったのは嬉しいことではある。
クライアントライブラリの方でもこの辺りの処理ができれば、夢が広がるというものである。