Biblioteka klienta diagnostyki

Ten artykuł dotyczy: ✔️ zestaw .NET Core 3.0 SDK i nowsze wersje dla aplikacji docelowych . NET Standard 2.0 do korzystania z biblioteki.

Microsoft.Diagnostics.NETCore.Client (znana również jako biblioteka klienta diagnostyki) to biblioteka zarządzana, która umożliwia interakcję ze środowiskiem uruchomieniowym platformy .NET Core (CoreCLR) na potrzeby różnych zadań związanych z diagnostyką, takich jak śledzenie za pośrednictwem usługi EventPipe, żądanie zrzutu lub dołączanie ICorProfilerelementu . Ta biblioteka jest biblioteką zapasową wielu narzędzi diagnostycznych, takich jak dotnet-counters, dotnet-trace, dotnet-gcdump, dotnet-dump i dotnet-monitor. Korzystając z tej biblioteki, możesz napisać własne narzędzia diagnostyczne dostosowane do konkretnego scenariusza.

Możesz uzyskać klienta Microsoft.Diagnostics.NETCore.Client , dodając element PackageReference do projektu. Pakiet jest hostowany w systemie NuGet.org.

Przykłady w poniższych sekcjach pokazują, jak używać biblioteki Microsoft.Diagnostics.NETCore.Client. Niektóre z tych przykładów pokazują również analizowanie ładunków zdarzeń przy użyciu biblioteki TraceEvent .

Dołączanie do procesu i wyświetlanie wszystkich zdarzeń GC

Ten fragment kodu pokazuje, jak uruchomić sesję eventPipe przy użyciu dostawcy środowiska uruchomieniowego platformy .NET ze słowem kluczowym GC na poziomie informacyjnym. Pokazano również, jak używać klasy dostarczonej EventPipeEventSourceprzez bibliotekę TraceEvent do analizowania zdarzeń przychodzących i drukowania ich nazw w konsoli w czasie rzeczywistym.

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());
            }
        }
    }
}

Pisanie zrzutu rdzenia

W tym przykładzie pokazano, jak wyzwolić kolekcję podstawowego zrzutu przy użyciu polecenia 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");
    }
}

Wyzwalanie zrzutu rdzenia, gdy użycie procesora CPU przekracza próg

W tym przykładzie pokazano, jak monitorować cpu-usage licznik opublikowany przez środowisko uruchomieniowe platformy .NET i żądać zrzutu, gdy użycie procesora CPU przekroczy określony próg.

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) {}
        }
    }
}

Wyzwalanie śledzenia procesora CPU przez daną liczbę sekund

W tym przykładzie pokazano, jak wyzwolić sesję eventPipe przez określony okres z domyślnym słowem kluczowym śledzenia CLR, a także przykładowym profilerem. Następnie odczytuje strumień wyjściowy i zapisuje bajty w pliku. Zasadniczo jest to to, co dotnet-trace używa wewnętrznie do zapisu pliku śledzenia.

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();
        }
    }
}

W tym przykładzie pokazano, jak używać DiagnosticsClient.GetPublishedProcesses interfejsu API do drukowania nazw procesów platformy .NET, które opublikowały kanał IPC diagnostyki.

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}");
        }
    }
}

Analizowanie zdarzeń w czasie rzeczywistym

W tym przykładzie pokazano przykład, w którym tworzymy dwa zadania, które analizuje zdarzenia przychodzące na żywo EventPipeEventSource , i jedno, które odczytuje dane wejściowe konsoli dla danych wejściowych użytkownika sygnalizujących zakończenie programu. Jeśli aplikacja docelowa zakończy działanie przed naciśnięciem klawisza Enter przez użytkownika, aplikacja zostanie bezpiecznie zakończona. inputTask W przeciwnym razie wyśle polecenie Stop do potoku i wyjdź bezpiecznie.

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);
        }
    }
}

Dołączanie profilera ICorProfiler

W tym przykładzie pokazano, jak dołączyć element ICorProfiler do procesu za pośrednictwem dołączania profilera.

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);
    }
}