Biblioteca cliente de diagnósticos

Este artículo se aplica a: ✔️ SDK de .NET Core 3.0 y versiones posteriores para aplicaciones de destino y .NET Standard 2.0 para usar la biblioteca.

Microsoft.Diagnostics.NETCore.Client (también conocido como biblioteca cliente de diagnósticos) es una biblioteca administrada que permite interactuar con el runtime de .NET Core (CoreCLR) para diversas tareas relacionadas con el diagnóstico, como el seguimiento por medio de EventPipe, la solicitud de un volcado de memoria o la asociación de un ICorProfiler. Esta biblioteca es la que está detrás de muchas herramientas de diagnóstico, como dotnet-counters, dotnet-trace, dotnet-gcdump, dotnet-dump y dotnet-monitor. Con esta biblioteca, puede escribir sus propias herramientas de diagnóstico personalizadas para su escenario concreto.

Puede adquirir Microsoft.Diagnostics.NETCore.Client agregando un elemento PackageReference al proyecto. El paquete se hospeda en NuGet.org.

Los ejemplos de las secciones siguientes muestran cómo usar la biblioteca Microsoft.Diagnostics.NETCore.Client. Algunos de estos ejemplos también muestran el análisis de las cargas de eventos mediante la biblioteca TraceEvent.

Asociación a un proceso e impresión de todos los eventos de GC

Este fragmento de código muestra cómo iniciar una sesión de EventPipe mediante el proveedor del entorno de ejecución de .NET con la palabra clave GC a nivel informativo. También se muestra cómo usar la clase EventPipeEventSource proporcionada por la biblioteca TraceEvent para analizar los eventos entrantes e imprimir sus nombres en la consola en tiempo real.

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

Escritura de un volcado de memoria principal

En este ejemplo se muestra cómo desencadenar la recopilación de un volcado de memoria principal mediante 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");
    }
}

Desencadenamiento de un volcado de memoria principal cuando el uso de la CPU supera un umbral

En este ejemplo se muestra cómo supervisar el contador cpu-usage publicado por el entorno de ejecución de .NET y cómo solicitar un volcado de memoria cuando el uso de la CPU supera un umbral determinado.

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

Desencadenamiento de un seguimiento de CPU durante un número determinado de segundos

En este ejemplo se muestra cómo desencadenar una sesión de EventPipe durante un período de tiempo determinado con la palabra clave trace de CLR predeterminada, así como el generador de perfiles de ejemplo. Después, lee el flujo de salida y escribe los bytes en un archivo. Básicamente, esto es lo que dotnet-trace usa internamente para escribir un archivo de seguimiento.

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

En este ejemplo se muestra cómo usar la API DiagnosticsClient.GetPublishedProcesses para imprimir los nombres de los procesos de .NET que publicaron un canal IPC de diagnóstico.

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

Análisis de eventos en tiempo real

Este es un ejemplo donde se crean dos tareas, una que analiza los eventos que están en directo con EventPipeEventSource y otra que lee la entrada de la consola para una entrada de usuario que señala la finalización del programa. Si la aplicación de destino termina antes de que los usuarios presionen entrar, la aplicación termina correctamente. De lo contrario, inputTask enviará el comando Stop a la canalización y se cerrará correctamente.

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

Conexión de un generador de perfiles ICorProfiler

En este ejemplo se muestra cómo conectar un ICorProfiler a un proceso mediante la asociación del generador de perfiles.

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