Diagnostics クライアント ライブラリ

この記事の対象: ✔️ ターゲット アプリ用に .NET Core 3.0 SDK 以降のバージョン、ライブラリを使用するには .NET Standard 2.0。

Microsoft.Diagnostics.NETCore.Client (Diagnostics クライアント ライブラリとも呼ばれる) はマネージド ライブラリであり、EventPipe を使用したトレース、ダンプの要求、ICorProfiler のアタッチなど、さまざまな診断関連タスクについて .NET Core ランタイム (CoreCLR) と対話できます。 このライブラリは、dotnet-countersdotnet-tracedotnet-gcdumpdotnet-dumpdotnet-monitor など、多くの診断ツールの背後にあるバッキング ライブラリです。 このライブラリを使用すると、特定のシナリオに合わせてカスタマイズされた独自の診断ツールを作成できます。

プロジェクトに PackageReference を追加することで、Microsoft.Diagnostics.NETCore.Client を取得できます。 パッケージは NuGet.org でホストされています。

次のセクションのサンプルでは、Microsoft.Diagnostics.NETCore.Client ライブラリを使用する方法を示します。 これらの例の一部では、TraceEvent ライブラリを使用したイベント ペイロードの解析も示されています。

プロセスにアタッチし、すべての GC イベントを出力する

このスニペットでは、情報レベルの GC キーワードを指定した .NET ランタイム プロバイダーを使用して EventPipe セッションを開始する方法を示します。 また、TraceEvent ライブラリによって提供される EventPipeEventSource クラスを使用して受信イベントを解析し、その名前をコンソールにリアルタイムで出力する方法も示されています。

using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.EventPipe;
using Microsoft.Diagnostics.Tracing.Parsers;
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;

public class RuntimeGCEventsPrinter
{
    public static void PrintRuntimeGCEvents(int processId)
    {
        var providers = new List<EventPipeProvider>()
        {
            new EventPipeProvider("Microsoft-Windows-DotNETRuntime",
                EventLevel.Informational, (long)ClrTraceEventParser.Keywords.GC)
        };

        var client = new DiagnosticsClient(processId);
        using (EventPipeSession session = client.StartEventPipeSession(providers, false))
        {
            var source = new EventPipeEventSource(session.EventStream);

            source.Clr.All += (TraceEvent obj) => Console.WriteLine(obj.ToString());

            try
            {
                source.Process();
            }
            catch (Exception e)
            {
                Console.WriteLine("Error encountered while processing events");
                Console.WriteLine(e.ToString());
            }
        }
    }
}

コア ダンプを書き込む

このサンプルでは、DiagnosticsClient を使用してコア ダンプの収集をトリガーする方法を示します。

using Microsoft.Diagnostics.NETCore.Client;

public partial class Dumper
{
    public static void TriggerCoreDump(int processId)
    {
        var client = new DiagnosticsClient(processId);
        client.WriteDump(DumpType.Normal, "/tmp/minidump.dmp");
    }
}

CPU 使用率がしきい値を超えたらコア ダンプをトリガーする

このサンプルでは、.NET ランタイムによって発行された cpu-usage カウンターを監視し、CPU 使用率が特定のしきい値を超えたらダンプを要求する方法を示します。

using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.EventPipe;
using Microsoft.Diagnostics.Tracing.Parsers;
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;

public partial class Dumper
{
    public static void TriggerDumpOnCpuUsage(int processId, int threshold)
    {
        var providers = new List<EventPipeProvider>()
        {
            new EventPipeProvider(
                "System.Runtime",
                EventLevel.Informational,
                (long)ClrTraceEventParser.Keywords.None,
                new Dictionary<string, string>
                {
                    ["EventCounterIntervalSec"] = "1"
                }
            )
        };
        var client = new DiagnosticsClient(processId);
        using (var session = client.StartEventPipeSession(providers))
        {
            var source = new EventPipeEventSource(session.EventStream);
            source.Dynamic.All += (TraceEvent obj) =>
            {
                if (obj.EventName.Equals("EventCounters"))
                {
                    var payloadVal = (IDictionary<string, object>)(obj.PayloadValue(0));
                    var payloadFields = (IDictionary<string, object>)(payloadVal["Payload"]);
                    if (payloadFields["Name"].ToString().Equals("cpu-usage"))
                    {
                        double cpuUsage = Double.Parse(payloadFields["Mean"].ToString());
                        if (cpuUsage > (double)threshold)
                        {
                            client.WriteDump(DumpType.Normal, "/tmp/minidump.dmp");
                        }
                    }
                }
            };
            try
            {
                source.Process();
            }
            catch (Exception) {}
        }
    }
}

指定した秒数だけ CPU トレースをトリガーする

このサンプルでは、既定の CLR トレース キーワードとサンプル プロファイラーを使用して、一定の期間だけ EventPipe セッションをトリガーする方法を示します。 その後、出力ストリームを読み取り、バイトをファイルに書き込みます。 基本的に、これはトレース ファイルを書き込むために dotnet-trace によって内部的に使用されているものです。

using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.NETCore.Client;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.IO;
using System.Threading.Tasks;

public partial class Tracer
{
    public void TraceProcessForDuration(int processId, int duration, string traceName)
    {
        var cpuProviders = new List<EventPipeProvider>()
        {
            new EventPipeProvider("Microsoft-Windows-DotNETRuntime", EventLevel.Informational, (long)ClrTraceEventParser.Keywords.Default),
            new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational, (long)ClrTraceEventParser.Keywords.None)
        };
        var client = new DiagnosticsClient(processId);
        using (var traceSession = client.StartEventPipeSession(cpuProviders))
        {
            Task copyTask = Task.Run(async () =>
            {
                using (FileStream fs = new FileStream(traceName, FileMode.Create, FileAccess.Write))
                {
                    await traceSession.EventStream.CopyToAsync(fs);
                }
            });

            Task.WhenAny(copyTask, Task.Delay(TimeSpan.FromMilliseconds(duration * 1000)));
            traceSession.Stop();
        }
    }
}

このサンプルでは、DiagnosticsClient.GetPublishedProcesses API を使用して、診断 IPC チャネルを発行した .NET プロセスの名前を出力する方法を示します。

using Microsoft.Diagnostics.NETCore.Client;
using System;
using System.Diagnostics;
using System.Linq;

public class ProcessTracker
{
    public static void PrintProcessStatus()
    {
        var processes = DiagnosticsClient.GetPublishedProcesses()
            .Select(Process.GetProcessById)
            .Where(process => process != null);

        foreach (var process in processes)
        {
            Console.WriteLine($"{process.ProcessName}");
        }
    }
}

イベントをリアルタイムで解析する

このサンプルでは、2 つのタスクを作成する例を示します。1 つは EventPipeEventSource でライブに発生するイベントを解析し、もう 1 つはプログラムを終了するユーザー入力のコンソール入力を読み取ります。 ユーザーが Enter キーを押す前にターゲット アプリが存在する場合、アプリは正常に存在します。 それ以外の場合、inputTask は Stop コマンドをパイプに送信して正常に終了します。

using Microsoft.Diagnostics.NETCore.Client;
using Microsoft.Diagnostics.Tracing;
using Microsoft.Diagnostics.Tracing.EventPipe;
using Microsoft.Diagnostics.Tracing.Parsers;
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.Threading.Tasks;

public partial class Tracer
{
    public static void PrintEventsLive(int processId)
    {
        var providers = new List<EventPipeProvider>()
        {
            new EventPipeProvider("Microsoft-Windows-DotNETRuntime",
                EventLevel.Informational, (long)ClrTraceEventParser.Keywords.Default)
        };
        var client = new DiagnosticsClient(processId);
        using (var session = client.StartEventPipeSession(providers, false))
        {

            Task streamTask = Task.Run(() =>
            {
                var source = new EventPipeEventSource(session.EventStream);
                source.Clr.All += (TraceEvent obj) => Console.WriteLine(obj.EventName);
                try
                {
                    source.Process();
                }
                // NOTE: This exception does not currently exist. It is something that needs to be added to TraceEvent.
                catch (Exception e)
                {
                    Console.WriteLine("Error encountered while processing events");
                    Console.WriteLine(e.ToString());
                }
            });

            Task inputTask = Task.Run(() =>
            {
                Console.WriteLine("Press Enter to exit");
                while (Console.ReadKey().Key != ConsoleKey.Enter)
                {
                    Task.Delay(TimeSpan.FromMilliseconds(100));
                }
                session.Stop();
            });

            Task.WaitAny(streamTask, inputTask);
        }
    }
}

ICorProfiler プロファイラーをアタッチする

このサンプルでは、プロファイラーのアタッチを使用してプロセスに ICorProfiler をアタッチする方法を示します。

using System;
using Microsoft.Diagnostics.NETCore.Client;

public class Profiler
{
    public static void AttachProfiler(int processId, Guid profilerGuid, string profilerPath)
    {
        var client = new DiagnosticsClient(processId);
        client.AttachProfiler(TimeSpan.FromSeconds(10), profilerGuid, profilerPath);
    }
}