진단 클라이언트 라이브러리

이 문서는 다음에 적용됩니다. 대상 앱에 대한 .NET Core 3.0 SDK 이상 버전, 라이브러리를 사용할 .NET Standard 2.0

Microsoft.Diagnostics.NETCore.Client(진단 클라이언트 라이브러리라고도 함)는 .NET Core 런타임(CoreCLR)과 상호 작용하여 EventPipe를 통한 추적, 덤프 요청 또는 ICorProfiler 연결 같은 다양한 진단 관련 작업을 수행할 수 있는 관리형 라이브러리입니다. 이 라이브러리는 dotnet-counters, dotnet-trace, dotnet-gcdump, dotnet-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}");
        }
    }
}

실시간으로 이벤트 구문 분석

이 샘플에서는 두 개의 작업을 만드는 예제를 보여 줍니다. 하나는 EventPipeEventSource로 사용 중인 이벤트를 구문 분석하고, 다른 하나는 프로그램 종료 신호를 보내는 사용자 입력에 대한 콘솔 입력을 읽는 것입니다. 사용자가 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);
    }
}