Поделиться через


Использование потоковой передачи с помощью TraceProcessor

По умолчанию TraceProcessor обращается к данным, загружая их в память при обработке трассировки. Такой подход к буферизации прост в использовании, но может занимать большой объем памяти.

TraceProcessor также предоставляет trace.UseStreaming(), который поддерживает доступ к нескольким типам данных трассировки в потоковом режиме (обработка данных при считывании из файла трассировки, а не буферизация этих данных в памяти). Например, трассировка системных вызовов может быть достаточно большой, и буферизация всего списка системных вызовов в трассировке потребует много ресурсов.

Доступ к буферизованным данным

В следующем коде показан доступ к данным системных вызовов в стандартном буферном режиме с помощью trace.UseSyscalls():

using Microsoft.Windows.EventTracing;
using Microsoft.Windows.EventTracing.Processes;
using Microsoft.Windows.EventTracing.Syscalls;
using System;
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        if (args.Length != 1)
        {
            Console.Error.WriteLine("Usage: <trace.etl>");
            return;
        }

        using (ITraceProcessor trace = TraceProcessor.Create(args[0]))
        {
            IPendingResult<ISyscallDataSource> pendingSyscallData = trace.UseSyscalls();

            trace.Process();

            ISyscallDataSource syscallData = pendingSyscallData.Result;

            Dictionary<IProcess, int> syscallsPerCommandLine = new Dictionary<IProcess, int>();

            foreach (ISyscall syscall in syscallData.Syscalls)
            {
                IProcess process = syscall.Thread?.Process;

                if (process == null)
                {
                    continue;
                }

                if (!syscallsPerCommandLine.ContainsKey(process))
                {
                    syscallsPerCommandLine.Add(process, 0);
                }

                ++syscallsPerCommandLine[process];
            }

            Console.WriteLine("Process Command Line: Syscalls Count");

            foreach (IProcess process in syscallsPerCommandLine.Keys)
            {
                Console.WriteLine($"{process.CommandLine}: {syscallsPerCommandLine[process]}");
            }
        }
    }
}

Доступ к потоковым данным

При большой трассировке системных вызовов попытка буферизации данных в памяти может требовать много ресурсов или даже будет невозможной. В следующем коде показано, как получить доступ к тем же данным системных вызовов в потоковом режиме, заменив trace.UseSyscalls() на trace.UseStreaming().UseSyscalls():

using Microsoft.Windows.EventTracing;
using Microsoft.Windows.EventTracing.Processes;
using Microsoft.Windows.EventTracing.Syscalls;
using System;
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        if (args.Length != 1)
        {
            Console.Error.WriteLine("Usage: <trace.etl>");
            return;
        }

        using (ITraceProcessor trace = TraceProcessor.Create(args[0]))
        {
            IPendingResult<IThreadDataSource> pendingThreadData = trace.UseThreads();

            Dictionary<IProcess, int> syscallsPerCommandLine = new Dictionary<IProcess, int>();

            trace.UseStreaming().UseSyscalls(ConsumerSchedule.SecondPass, context =>
            {
                Syscall syscall = context.Data;
                IProcess process = syscall.GetThread(pendingThreadData.Result)?.Process;

                if (process == null)
                {
                    return;
                }

                if (!syscallsPerCommandLine.ContainsKey(process))
                {
                    syscallsPerCommandLine.Add(process, 0);
                }

                ++syscallsPerCommandLine[process];
            });

            trace.Process();

            Console.WriteLine("Process Command Line: Syscalls Count");

            foreach (IProcess process in syscallsPerCommandLine.Keys)
            {
                Console.WriteLine($"{process.CommandLine}: {syscallsPerCommandLine[process]}");
            }
        }
    }
}

Как работает потоковая передача

По умолчанию все потоковые данные предоставляются во время первого прохода по трассировке, а буферизованные данные из других источников недоступны. В приведенном выше примере показано, как объединить потоковую передачу с буферизацией — потоковые данные буферизуются до потоковой передачи данных системных вызовов. В результате трассировка должна быть прочитана дважды — один раз для получения данных буферизованного потока, а второй раз — для доступа к потоковой передаче данных системных вызовов с помощью буферизованных данных потоков. Чтобы объединить потоковую передачу и буферизацию таким образом, в примере передается ConsumerSchedule.SecondPass в trace.UseStreaming().UseSyscalls(). В результате обработка системных вызовов происходит во второй проход по трассировке. Выполняя второй проход, обратный вызов системных вызовов может получить доступ к ожидающему результату из trace.UseThreads() при обработке каждого системного вызова. Без этого необязательного аргумента потоковая передача системных вызовов была бы выполнена в первом проходе через трассировку (был бы только один проход), и отложенный результат trace.UseThreads() был бы недоступен. В этом случае обратный вызов по-прежнему будет иметь доступ к ThreadId из системного вызова, но у него не будет доступа к процессу потока (поскольку поток для обработки данных связывания предоставляется через другие события, которые, возможно, еще не обработаны).

Некоторые ключевые отличия в использовании между буферизацией и потоковой обработкой:

  1. Буферизация возвращает IPendingResult<T>, и результат доступен только перед обработкой трассировки. После обработки трассировки результаты можно перечислить с помощью таких методов, как foreach и LINQ.
  2. Потоковая передача возвращает пустое значение и принимает аргумент обратного вызова. Она вызывает обратный вызов по мере того, как каждый элемент становится доступным. Так как данные не буферизуются, список результатов для перечисления с помощью foreach или LINQ не создается — потоковый обратный вызов должен буферизовать ту часть данных, которую нужно сохранить для использования после завершения обработки.
  3. Код для обработки буферизованных данных появляется после вызова функции trace.Process(), если ожидающие результаты доступны.
  4. Код для обработки потоковых данных отображается перед вызовом функции trace.Process() в качестве обратного вызова метода trace.UseStreaming.Use...().
  5. Потребитель потоковой передачи может выбрать обработку только части потока и отменить последующие обратные вызовы, вызвав context.Cancel(). Потребитель буферизации всегда предоставляет полный буферизованный список.

Коррелированные данные потоковой передачи

Иногда данные трассировки поступают в последовательность событий, например, системные вызовы регистрируются с помощью отдельных событий входа и выхода, но объединенные данные из обоих событий могут быть более полезными. Метод trace.UseStreaming().UseSyscalls() сопоставляет данные обоих этих событий и предоставляет их по мере появления пар. При использовании trace.UseStreaming() доступно несколько типов коррелированных данных:

Код Описание
trace.UseStreaming().UseContextSwitchData() Данные переключения контекста, коррелированные с потоками (из сжатых и несжатых событий с более точными SwitchInThreadId, чем для необработанных несжатых событий).
trace.UseStreaming().UseScheduledTasks() Данные о запланированных задачах, коррелированные с потоками.
trace.UseStreaming().UseSyscalls() Данные о системных вызовах, коррелированные с потоками.
trace.UseStreaming().UseWindowInFocus() Данные окна в фокусе, коррелированные с потоками.

Отдельные события потоковой передачи

Кроме того, trace.UseStreaming() предоставляет проанализированные события для нескольких отдельных типов событий:

Код Описание
trace.UseStreaming().UseLastBranchRecordEvents() События последней записи ветви, проанализированные в потоках.
trace.UseStreaming().UseReadyThreadEvents() События готовности цепочек, проанализированные в потоках.
trace.UseStreaming().UseThreadCreateEvents() События создания цепочек, проанализированные в потоках.
trace.UseStreaming().UseThreadExitEvents() События выхода из цепочек, проанализированные в потоках.
trace.UseStreaming().UseThreadRundownStartEvents() События начала замедления цепочек, проанализированные в потоках.
trace.UseStreaming().UseThreadRundownStopEvents() События остановки замедления цепочек, проанализированные в потоках.
trace.UseStreaming().UseThreadSetNameEvents() События задания имен цепочек, проанализированные в потоках.

Базовые события потоковой передачи для коррелированных данных

Наконец, trace.UseStreaming() предоставляет базовые события, используемые для корреляции данных в приведенном выше списке. Это следующие базовые события:

Код Описание Входит в состав
trace.UseStreaming().UseCompactContextSwitchEvents() События переключения сжатого контекста, проанализированные потоками. trace.UseStreaming().UseContextSwitchData()
trace.UseStreaming().UseContextSwitchEvents() События переключения контекста, проанализированные потоками. В некоторых случаях SwitchInThreadIds могут быть неточными. trace.UseStreaming().UseContextSwitchData()
trace.UseStreaming().UseFocusChangeEvents() События смены фокуса окна, проанализированные потоками. trace.UseStreaming().UseWindowInFocus()
trace.UseStreaming().UseScheduledTaskStartEvents() События начала запланированной задачи, проанализированные потоками. trace.UseStreaming().UseScheduledTasks()
trace.UseStreaming().UseScheduledTaskStopEvents() События окончания запланированной задачи, проанализированные потоками. trace.UseStreaming().UseScheduledTasks()
trace.UseStreaming().UseScheduledTaskTriggerEvents() События запуска запланированной задачи, проанализированные потоками. trace.UseStreaming().UseScheduledTasks()
trace.UseStreaming().UseSessionLayerSetActiveWindowEvents() События задания активного окна на уровне сеанса, проанализированные потоками. trace.UseStreaming().UseWindowInFocus()
trace.UseStreaming().UseSyscallEnterEvents() События входа системных вызовов, проанализированные потоками. trace.UseStreaming().UseSyscalls()
trace.UseStreaming().UseSyscallExitEvents() События выхода системных вызовов, проанализированные потоками. trace.UseStreaming().UseSyscalls()

Next Steps

Из этого руководства вы узнали, как использовать потоковую передачу для немедленного доступа к данным трассировки и экономии памяти.

Следующий шаг — доступ к нужным данным из трассировок. Ознакомьтесь с примерами, чтобы получить представление. Не все трассировки включают в себя все поддерживаемые типы данных.