Libreria client di diagnostica

Questo articolo si applica a: ✔️ .NET Core 3.0 SDK e versioni successive per le app di destinazione, .NET Standard 2.0 per usare la libreria.

Microsoft.Diagnostics.NETCore.Client (nota anche come libreria client di diagnostica) è una libreria gestita che ti consente di interagire con il runtime di .NET Core (CoreCLR) per varie attività correlate alla diagnostica, ad esempio il tracciamento tramite EventPipe, la richiesta di un dump o il collegamento di un oggetto ICorProfiler. Questa libreria è la libreria di backup dietro molti strumenti di diagnostica, ad esempio dotnet-counters, dotnet-trace, dotnet-gcdump, dotnet-dump e dotnet-monitor. Usando questa libreria, puoi scrivere strumenti di diagnostica personalizzati per uno scenario specifico.

Puoi acquisire Microsoft.Diagnostics.NETCore.Client aggiungendo un PackageReference al progetto. Il pacchetto è ospitato in NuGet.org.

Gli esempi delle sezioni seguenti illustrano come usare la libreria Microsoft.Diagnostics.NETCore.Client. Alcuni di questi esempi mostrano anche l'analisi dei payload degli eventi usando la libreria TraceEvent.

Collegarsi a un processo e stampare tutti gli eventi GC

Questo frammento di codice illustra come avviare una sessione EventPipe usando il provider di runtime .NET con la parola chiave GC a livello informativo. Illustra anche come usare la classe EventPipeEventSource fornita dalla libreria TraceEvent per analizzare gli eventi in ingresso e stamparne i nomi nella console in tempo reale.

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

Scrivere un dump core

Questo esempio illustra come attivare la raccolta di un dump core usando 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");
    }
}

Attivare un dump core quando l'utilizzo della CPU supera una determinata soglia

Questo esempio illustra come monitorare il contatore cpu-usage pubblicato dal runtime .NET e richiedere un dump quando l'utilizzo della CPU supera una determinata soglia.

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

Attivare una traccia della CPU per un determinato numero di secondi

Questo esempio illustra come attivare una sessione EventPipe per un determinato periodo di tempo con la parola chiave di traccia CLR predefinita e il profiler di esempio. Successivamente, legge il flusso di output e scrive i byte in un file. Essenzialmente questo è ciò che dotnet-trace usa internamente per scrivere un file di traccia.

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

Questo esempio illustra come usare l'API DiagnosticsClient.GetPublishedProcesses per stampare i nomi dei processi .NET che hanno pubblicato un canale IPC di diagnostica.

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

Analizzare gli eventi in tempo reale

Questo esempio mostra un esempio in cui vengono create due attività, una che analizza gli eventi in arrivo in tempo reale con EventPipeEventSource e una che legge l'input della console di un input utente che segnala il programma alla fine. Se l'app di destinazione viene chiusa prima che l'utente prema Invio, l'app viene chiusa normalmente. In caso contrario, inputTask invierà il comando Stop alla pipe e si chiuderà normalmente.

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

Collegare un profiler ICorProfiler

Questo esempio illustra come collegare un ICorProfiler a un processo tramite il collegamento del profiler.

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