Bibliothèque de client de diagnostics

Cet article s’applique à : ✔️ Kit de développement logiciel (SDK) .NET Core 3.0 et versions ultérieures pour les applications cibles, .NET Standard 2.0 pour utiliser la bibliothèque.

Microsoft.Diagnostics.NETCore.Client (également appelée bibliothèque de client de diagnostics) est une bibliothèque managée qui vous permet d’interagir avec le runtime .NET Core (CoreCLR) pour diverses tâches liées au diagnostic, telles que le suivi via EventPipe, la demande d’une image mémoire ou l’attachement d’un ICorProfiler. Cette bibliothèque est la bibliothèque de stockage derrière de nombreux outils de diagnostic tels que dotnet-counters, dotnet-trace, dotnet-gcdump, dotnet-dump et dotnet-monitor. À l’aide de cette bibliothèque, vous pouvez écrire vos propres outils de diagnostic personnalisés pour votre scénario particulier.

Vous pouvez acquérir Microsoft.Diagnostics.NETCore.Client en ajoutant un PackageReference à votre projet. Le package est hébergé sur NuGet.org.

Les exemples des sections suivantes montrent comment utiliser la bibliothèque Microsoft.Diagnostics.NETCore.Client. Certains de ces exemples montrent également l’analyse des charges utiles d’événement à l’aide de la bibliothèque TraceEvent .

Attacher à un processus et imprimer tous les événements GC

Cet extrait de code montre comment démarrer une session EventPipe à l’aide du fournisseur runtime de .NET avec le mot clé GC au niveau informationnel. Il montre également comment utiliser la classe EventPipeEventSource fournie par la bibliothèque TraceEvent pour analyser les événements entrants et imprimer leurs noms sur la console en temps réel.

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

Écrire une image mémoire principale

Cet exemple montre comment déclencher la collection d’une image mémoire principale à l’aide de 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");
    }
}

Déclencher une image mémoire principale lorsque l’utilisation du processeur dépasse un seuil

Cet exemple montre comment surveiller le compteur cpu-usage publié par le runtime .NET et demander une image mémoire lorsque l’utilisation du processeur augmente au-delà d’un certain seuil.

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

Déclencher une trace processeur pendant un nombre de secondes donné

Cet exemple montre comment déclencher une session EventPipe pendant une certaine période avec le mot clé de trace CLR par défaut ainsi que l’exemple de profileur. Ensuite, il lit le flux de sortie et écrit les octets dans un fichier. Il s’agit essentiellement de ce que dotnet-trace utilise en interne pour écrire un fichier de 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();
        }
    }
}

Cet exemple montre comment utiliser l’API DiagnosticsClient.GetPublishedProcesses pour imprimer le nom des processus .NET qui ont publié un canal IPC de diagnostic.

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

Analyser les événements en temps réel

Cet exemple où nous créons deux tâches, l’une qui analyse les événements en direct avec EventPipeEventSource et l’autre qui lit l’entrée de console pour qu’une entrée utilisateur signale la fin du programme. Si l’application cible se ferme avant que l’utilisateur appuie sur Entrée, l’application se ferme correctement. Dans le cas contraire, inputTask envoie la commande Arrêter au canal et se ferme correctement.

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

Attacher un profileur ICorProfiler

Cet exemple montre comment attacher un ICorProfiler à un processus via l’attachement du profileur.

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