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.