Source/Tx.Windows/EtwNative/EtwFileReader.cs (210 lines of code) (raw):
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
namespace Tx.Windows
{
/// <summary>
/// Observable that will read .etl files and push the
/// and produce the events in the ETW native format
/// </summary>
internal class EtwFileReader : IDisposable
{
private readonly GCHandle[] _logFileHandles;
private readonly EVENT_TRACE_LOGFILE[] _logFiles;
private readonly IObserver<EtwNativeEvent> _observer;
private readonly Thread _thread;
private readonly Guid _system = new Guid("{68fdd900-4a3e-11d1-84f4-0000f80464e3}");
private readonly DateTime _startTime;
private readonly DateTime _endTime;
private bool _disposed;
private ulong[] _handles;
/// <summary>
/// Constructor
/// </summary>
/// <param name="observer">Observer to push events into</param>
/// <param name="etlFiles">.etl (Event Trace Log) files to read. Up to 63 files are supported</param>
public EtwFileReader(IObserver<EtwNativeEvent> observer, params string[] etlFiles) :
this(observer, false, DateTime.MinValue, DateTime.MaxValue, etlFiles)
{
}
public EtwFileReader(IObserver<EtwNativeEvent> observer, bool sequential, params string[] etlFiles) :
this(observer, sequential, DateTime.MinValue, DateTime.MaxValue, etlFiles)
{
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="observer">Observer to push events into</param>
/// <param name="sequential">set sequential to true to sequentially stream the logs</param>
/// <param name="startTime">start time for the events from logs</param>
/// <param name="endTime">end time for the events from logs</param>
/// <param name="etlFiles">.etl (Event Trace Log) files to read. Up to 63 files are supported in non sequential mode.
/// Theoritically no limits on number of files in sequential mode.</param>
public EtwFileReader(IObserver<EtwNativeEvent> observer, bool sequential, DateTime startTime, DateTime endTime, params string[] etlFiles)
{
_observer = observer;
_startTime = startTime;
_endTime = endTime;
// pin the strings in memory, allowing pointers to be passed in the event callback
_logFiles = new EVENT_TRACE_LOGFILE[etlFiles.Length];
_logFileHandles = new GCHandle[etlFiles.Length];
for (int i = 0; i < _logFileHandles.Length; i++)
{
_logFiles[i] = new EVENT_TRACE_LOGFILE
{
ProcessTraceMode = EtwNativeMethods.ProcessTraceModeEventRecord,
LogFileName = Path.GetFullPath(etlFiles[i]),
EventRecordCallback = EtwCallback
};
_logFileHandles[i] = GCHandle.Alloc(_logFiles[i]);
}
if (sequential == true)
{
_thread = new Thread(ProcessTracesInSequence) { Name = "EtwFileObservable" };
}
else
{
_thread = new Thread(MergeTracesAndProcess) { Name = "EtwFileObservable" };
}
_thread.Start();
}
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
for (int i = 0; i < _handles.Length; i++)
{
EtwNativeMethods.CloseTrace(_handles[i]);
_logFileHandles[i].Free();
}
}
}
private static bool TryConvertToFILETIME(DateTime dateTime, ref System.Runtime.InteropServices.ComTypes.FILETIME fileTime)
{
if (dateTime == DateTime.MinValue)
return false;
if (dateTime == DateTime.MaxValue)
return false;
long lfileTime = dateTime.ToFileTime();
fileTime.dwHighDateTime = (int)(lfileTime >> 32);
fileTime.dwLowDateTime = (int)(lfileTime & 0xFFFFFFFF);
return true;
}
private static IntPtr ConvertDateTime(DateTime dateTime)
{
System.Runtime.InteropServices.ComTypes.FILETIME fileTime =
new System.Runtime.InteropServices.ComTypes.FILETIME();
if (TryConvertToFILETIME(dateTime, ref fileTime))
{
int sizeOfFileTime = Marshal.SizeOf(typeof(System.Runtime.InteropServices.ComTypes.FILETIME));
IntPtr fileTimePtr = Marshal.AllocHGlobal(sizeOfFileTime);
Marshal.StructureToPtr(fileTime, fileTimePtr, false);
return fileTimePtr;
}
return IntPtr.Zero;
}
private void ProcessTracesInSequence()
{
int error = 0;
_handles = new ulong[_logFiles.Length];
IntPtr startTime = ConvertDateTime(_startTime);
IntPtr endTime = ConvertDateTime(_endTime);
try
{
for (int i = 0; i < _logFiles.Length; i++)
{
_handles[i] = EtwNativeMethods.OpenTrace(ref _logFiles[i]);
if (_handles[i] == EtwNativeMethods.InvalidHandle)
{
error = Marshal.GetLastWin32Error();
if (error == EtwNativeMethods.ErrorNotFound)
{
_observer.OnError(new FileNotFoundException("Could not find file " + _logFiles[i].LogFileName));
return;
}
_observer.OnError(new Win32Exception(error));
return;
}
error = EtwNativeMethods.ProcessTrace(new ulong[] { _handles[i] }, (uint)1, startTime, endTime);
}
}
catch (Exception ex)
{
_observer.OnError(ex);
return;
}
finally
{
if (startTime != IntPtr.Zero)
{
Marshal.FreeHGlobal(startTime);
startTime = IntPtr.Zero;
}
if (endTime != IntPtr.Zero)
{
Marshal.FreeHGlobal(endTime);
endTime = IntPtr.Zero;
}
}
if (error != 0)
{
_observer.OnError(new Win32Exception(error));
return;
}
_observer.OnCompleted();
}
private void MergeTracesAndProcess()
{
int error;
_handles = new ulong[_logFiles.Length];
IntPtr startTime = ConvertDateTime(_startTime);
IntPtr endTime = ConvertDateTime(_endTime);
for (int i = 0; i < _logFiles.Length; i++)
{
_handles[i] = EtwNativeMethods.OpenTrace(ref _logFiles[i]);
if (_handles[i] == EtwNativeMethods.InvalidHandle)
{
error = Marshal.GetLastWin32Error();
if (error == EtwNativeMethods.ErrorNotFound)
{
_observer.OnError(new FileNotFoundException("Could not find file " + _logFiles[i].LogFileName));
return;
}
_observer.OnError(new Win32Exception(error));
return;
}
}
try
{
error = EtwNativeMethods.ProcessTrace(_handles, (uint)_handles.Length, startTime, endTime);
}
catch (Exception ex)
{
_observer.OnError(ex);
return;
}
finally
{
if (startTime != IntPtr.Zero)
{
Marshal.FreeHGlobal(startTime);
startTime = IntPtr.Zero;
}
if (endTime != IntPtr.Zero)
{
Marshal.FreeHGlobal(endTime);
endTime = IntPtr.Zero;
}
}
if (error != 0)
{
_observer.OnError(new Win32Exception(error));
return;
}
_observer.OnCompleted();
}
private unsafe void EtwCallback(ref EVENT_RECORD record)
{
if (record.EventHeader.ProviderId == _system)
return;
fixed (EVENT_RECORD* p = &record)
{
EtwNativeEvent evt;
evt.record = p;
evt._data = (byte*) record.UserData.ToPointer();
evt._end = evt._data + record.UserDataLength;
evt._length = 0;
_observer.OnNext(evt);
}
}
~EtwFileReader()
{
Dispose();
}
}
}