始めに
前回の記事で、DiagnosticSourceからEventSourceへ出力する記事を書いたが、今回は、そのEventSourceのデータを処理するためのライブラリの使い方を書く。
これによって、コマンドラインで来たデータを加工したり、あるいはWPFで流す等の処理も書けるようになる。
前提条件
- dotnet sdk-3.0以降が必要
- 情報収集対象のアプリが、netcoreapp3.0以降
準備(イベント出力側の用意)
前回の記事でも作成したソースを使用する。
usingSystem.Diagnostics;usingSystem;usingSystem.Threading.Tasks;classC1{publicDiagnosticSource_D=newDiagnosticListener("Diag1");publicvoidA(){if(_D.IsEnabled("ev1")){_D.Write("ev1",new{X="str"});}}}classProgram{staticvoidMain(string[]args){while(true){// 1秒に一回"ev1"イベントが発生する// 停止機能はないので、Ctrl+C等を使用して止めることnewC1().A();Task.Delay(1000).Wait();}}}必要なライブラリ
Microsoft.Diagnostics.NETCore.ClientとMicrosoft.Diagnostics.Tracing.TraceEventが必要。
Microsoft.Diagnostics.NETCore.Clientについて
EventPipeプロトコルでdotnetプロセスにアタッチするためのライブラリ。dotnetプロセスが用意するEventPipe読み込みの口へ、PIDをキーにしてアクセスして、データを受け取る。
主に使用するクラスは以下(名前空間はMicrosoft.Diagnostics.NETCore.Clientからの相対位置)
- EventPipeProvider
- EventSourceの出力元の情報を保持するクラス
- 捕捉するイベントの種類、レベル、各種フラグ等を設定する
- DiagnosticClient
- EventProviderとPIDを元に、EventPipe接続を行う
StartEventPipeSessionpublic static IEnumerable<int> DiagnosticClient.GetPublishedProcesses()で、アタッチ可能なPIDの一覧が取得可能
- EventPipeSession
DiagnosticClient.StartEventPipeSessionから生成される、セッションオブジェクトEventPipeSession.EventStreamで生のバイトデータが取得できる- フォーマットは perfviewリポジトリのドキュメントに記載がある
- 使用後は、必ず
Stop()メソッドで動作を停止すること
さて、これだけではEventPipeの生データが取得できるだけで、中身はバイナリデータなので人に読めるものではない(いや、もしかしたら読める人もいるかもしれないが)。
これを解析してくれるライブラリが、Microsoft.Diagnostics.Tracing.TraceEventとなる
Microsoft.Diagnostics.Tracing.TraceEventについて
前述のEventPipeSessionから得たデータを解釈して、プログラム的に解析するためのライブラリ。起点はStreamから読み込むので、先のEventPipeSession.EventStreamをの内容をそのまま出力したファイルを使っても良い
EventStreamの解析に使用する場合、主に使用するクラスはMicrosoft.Diagnostics.Tracing.EventPipeEventSourceである。
EventPipeEventSourceについて
System.IO.Streamをコンストラクタにとる。Streamには、EventPipeSession.EventStreamから得たデータが入っている想定。
データ解析手順には非同期イベントパターン(EAP)を採用しているので、EventPipeEventSourceに付随するイベントを購読する。
また、IObservable<T>を返すインターフェイスも提供している。
イベントは、共通のヘッダ部分以外はイベント固有のデータが流れてくるため、そのイベント用の解析クラスを用意する必要がある。
このイベント解析用ベースクラスがTraceEventParserで、解析クラスはこれを継承して各イベントの解析処理を実装している。
例えば、CLRのイベント(GCの回収イベントとか)は、専用のパーサーが用意されており、EventPipeEventSource.Clrで専用のイベントが用意されている。
なお、汎用的な用途に使えるDynamicTraceEventParserも存在しており、ライブラリ側で用意されていない場合はこちらを使用することになるだろう。
そして、一通りのイベント設定が終わったら、EventPipeEventSource.Processで解析を開始する。
注意点として、Processを実行した時点でスレッドがロックされるため、途中キャンセル等をしたい場合は、
別スレッドを展開して、別のスレッドから、EventPipeEventSource.StopProcessingと、元のストリームのクローズ(EventPipeSessionからStreamを取得している場合は、EventPipeSession.Stopを実行する
具体的には以下
// EventPipeSession session;// 汎用的なイベントを購読evsrc.Dynamic.All+=(TraceEventevt)=>{// 解析処理};awaitTask.WhenAll(// 解析スレッドTask.Run(()=>evsrc.Process()),// 停止スレッド// エンターキーを押したら終了するTask.Run(()=>{Console.ReadLine();evsrc.StopProcessing();session.Stop();});なお、これを通さないAction<TraceEvent> EventPipeEventSource.AllEventsなるイベントもあるが、これを通すとPayloadNames(イベントデータプロパティの名前)など、主にPayload関連について、セットされないプロパティが出てくる。その場合は、TraceEvent.EventData()でバイトデータを取得して、自力でパースする必要がある。
EventPipeEventSource.Dynamic.Allの中の解析処理について
EventPipeEventSource.Dynamic.Allの中では、TraceEvent.PayloadNamesとTraceEvent.PayloadByName(string name)の組み合わせでデータを取得する。具体的には以下のようになる。
// TracEvent traceevt;foreach(varpnameintraceevt.PayloadNames){objectpobj=traceevt.PayloadByName(pname);Console.WriteLine($"{pname} = {pobj}");}pobjが何になるか、というところは、プリミティブ型(数値、文字列)ならばそのままキャストすればOK。しかし、それ以外のオブジェクト等がイベント発生元で渡された場合は少々工夫が必要。
ペイロードデータにオブジェクトで来た場合
この場合、実際の型はinternalで隠蔽されているが、取り出す時にはIDictionary<string, object>[]で取り出すことができる([]なことに注意)。IDictionary<string, object>には二つ要素が格納されており、それぞれのキーはKeyとValueで、値の方にそれぞれプロパティ名と、実際の値が格納されている
具体的には以下のように値を取り出す。
// object pobj = traceevt.PayloadByName(pname);if(pobjisIDictionary<string,object>[]pdicar){foreach(varpdicinpdicar){stringkey=(string)pdic["Key"];objectvalue=pdic["Value"];// 解析処理}}より複雑な型の場合はネストして処理が必要になるだろうが、そもそもEventSourceでそこまで複雑なデータは作らない方が良いと思う。
パーサー自体についての詳細なドキュメントは、 githubのmicrosoft/perfviewのドキュメントにある
解析処理の注意点
解析コールバックの中で例外をキャッチし損ねると、以後のイベントが流れなくなった。
なので、現時点ではコールバックの中ではtry-catchで囲った方が良い
終わりに
前回の記事の派生で、EventPipeについて調べてみたけど、取っ掛かりは思ったより簡単だなという感想だった。
ここから、自分なりに使いやすいモニタリングツールを作ってみるのもいいかもしれない。
また、EventPipe自体はローカル限定なので、ここから外部ネットワークに流す口を作るというのも面白い発想かもしれない。
今回紹介したのはイベントの取得のみだが、その他にもメモリダンプを取ることもできる等、トラブルシューティングに役立つ機能があるので、うまく活用していきたい。
参考資料
- dotnet/diagnostics: EventPipe等の記述など
- microsoft/perfview: 解析ツールのperfviewや、そのライブラリの
Microsoft.Diagnostics.Tracing.TraceEventのソース等- microsoft/perfview/documentation/TraceEvent: もう少し詳細なドキュメント
- Microsoft.Diagnostics.NETCore.Clientに関するドキュメント