Клиентская библиотека диагностики

Эта статья относится к: ✔️ пакету SDK для .NET Core 3.0 и более поздних версий для целевых приложений и .NET Standard 2.0 для использования библиотеки.

Microsoft.Diagnostics.NETCore.Client(также известная как клиентская библиотека диагностики) — это управляемая библиотека, которая позволяет взаимодействовать с средой выполнения .NET Core (CoreCLR) для различных диагностика связанных задач, таких как трассировка через EventPipe, запрос дампа или присоединение.ICorProfiler Эта библиотека является резервной библиотекой за многими средствами диагностика, такими как dotnet-counters, dotnet-trace, dotnet-gcdump, dotnet-dump и dotnet-monitor. С помощью этой библиотеки можно создавать собственные средства диагностики, настроенные для конкретного сценария.

Вы можете получить Microsoft.Diagnostics.NETCore.Client, добавив PackageReference в свой проект. Пакет размещается в NuGet.org.

В примерах в следующих разделах показано, как использовать библиотеку Microsoft.Diagnostics.NETCore.Client. В некоторых из этих примеров также показан синтаксический анализ полезных данных событий с помощью библиотеки TraceEvent.

Присоединение к процессу и вывод всех событий сборки мусора

В этом фрагменте кода показано, как запустить сеанс EventPipe с помощью поставщика среды выполнения .NET с ключевым словом GC на информационном уровне. В нем также показано, как использовать класс EventPipeEventSource, предоставляемый библиотекой TraceEvent, для анализа входящих событий и вывода их имен в консоль в режиме реального времени.

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-usage, опубликованного средой выполнения .NET, и запрашивать дамп, когда загрузка ЦП превышает определенное пороговое значение.

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

Активация трассировки ЦП на заданное количество секунд

В этом примере показано, как запустить сеанс EventPipe на определенный период времени с помощью ключевого слова трассировки CLR по умолчанию, а также примера профилировщика. После этого он считывает выходной поток и записывает байты в файл. По сути, 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();
        }
    }
}

В этом примере показано, как использовать API DiagnosticsClient.GetPublishedProcesses для вывода имен процессов .NET, которые опубликовали IPC-канал диагностики.

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, а другой для считывания входные данные консоли для получения введенных пользователем данных, сигнализирующих о необходимости завершения работы программы. Если целевое приложение выходит, прежде чем пользователь нажимает ввод, приложение завершается корректно. В противном случае inputTask отправит команду остановки в канал и корректно завершит работу.

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