Real-time trace sessions with Tx

Spread the love

I recently showed an example of how to use TraceEvent to consume events from real-time trace sessions. Today I will show another way using the Tx.Windows NuGet package.

Tx (LINQ to Logs and Traces) is an offshoot of the Rx (Reactive Extensions) project. (Rx, to quote MSDN, “is a library to compose asynchronous and event-based programs using observable collections and LINQ-style query operators.”) Tx.Windows specifically adds an IObservable<T> implementation for a real-time ETW session (EtwObservable.FromSession) and code for consuming event data (EtwNativeEvent and others).

To show the code in action, I revisited the TraceSample project I created previously and reimplemented it using Tx in the TxSample project. The new project incorporates all of the core code from TraceSample (minus anything TraceEvent-dependent) as well as the real-time trace session code from PlaSample.

The main differences are in the KernelProcessSession class. We begin by starting and subscribing to the event stream:

RealTimeTraceCollectorInfo info = new RealTimeTraceCollectorInfo(this.name);
info.Providers.Add(new ProviderInfo(KernelProcessProviderId) { KeywordsAll = 0x10, Level = 4 });
this.session = info.Create();
this.session.Start();

IObservable<EtwNativeEvent> stream = EtwObservable.FromSession(this.name);
this.subscription = stream.Subscribe(e => this.OnNext(e));

The OnNext method is called each time a trace event arrives. Note that in Tx the core trace event type is a struct, so I’ve opted to pass it by reference to any inner functions — it’s generally a good idea to treat your event consumer code as performance critical to avoid missing events.

private void OnNext(EtwNativeEvent traceEvent)
{
    // An event is uniquely identified by 3 values -- provider ID, event ID, event version
    if (traceEvent.ProviderId == KernelProcessProviderId)
    {
        switch (traceEvent.Id)
        {
            case ProcessStartId:
                this.ReadProcessStartEvent(ref traceEvent);
                break;
            case ProcessStopId:
                this.ReadProcessStopEvent(ref traceEvent);
                break;
        }
    }
}

EtwNativeEvent allows access to the user data payload in the style of a forward-only reader, providing Read[DataType] methods for most supported types. Here is an example from the code to read process start events:

private void ReadProcessStartEvent(ref EtwNativeEvent traceEvent)
{
    if (traceEvent.Version == 0)
    {
        // <data name="ProcessID" inType="win:UInt32" outType="win:PID"></data>
        // <data name="CreateTime" inType="win:FILETIME" outType="xs:dateTime"></data>
        // <data name="ParentProcessID" inType="win:UInt32" outType="win:PID"></data>
        // <data name="SessionID" inType="win:UInt32" outType="xs:unsignedInt"></data>
        // <data name="ImageName" inType="win:UnicodeString" outType="xs:string"></data>
        EventHandler<ProcessEventArgs> handler = this.ProcessStarted;
        if (handler != null)
        {
            int processId = (int)traceEvent.ReadUInt32();
            DateTime createTime = traceEvent.ReadFileTime();
            traceEvent.ReadUInt32(); // ignore
            traceEvent.ReadUInt32(); // ignore
            string imageName = traceEvent.ReadUnicodeString();
            ProcessEventArgs e = new ProcessEventArgs()
            {
                Id = processId,
                ImageName = imageName,
                Timestamp = createTime
            };

            handler(this, e);
        }
    }
}

To stop delivering events, the subscription can simply be Dispose()‘d.

Run the TxSample app (elevated, as always!) and open and close a few processes. The behavior should be exactly the same as TraceSample. Sample output:

[000.002] Starting session...
[000.032] Running.
[010.032] Stopping session...
[010.034] Stopped.
[010.036] Dumping event data...
Process ID 284780 with image name 'cmd.exe' started at 1/17/2014 12:29:18 PM and exited at 1/17/2014 12:29:19 PM with code 255.
Process ID 407984 with image name 'conhost.exe' started at 1/17/2014 12:29:18 PM and exited at 1/17/2014 12:29:19 PM with code 0.
Process ID 396676 with image name 'calc.exe' started at 1/17/2014 12:29:21 PM and exited at 1/17/2014 12:29:22 PM with code 0.
Process ID 29648 with image name 'dllhost.exe' started at 1/17/2014 12:29:18 PM and exited at 1/17/2014 12:29:23 PM with code 0.

Leave a Reply

Your email address will not be published. Required fields are marked *