Expose BootTime in TraceProcessor

112 Views Asked by At

I see that Microsoft.Windows.EventTracing.Interop.Metadata.NativeTraceLogfileHeader contains a value for BootTime. That could be useful in some cases. Any chance that will be exposed via the ITraceMetaData interface or can that be somehow else accessed?

// Microsoft.Windows.EventTracing.Metadata.ITraceMetadata
using Microsoft.Windows.EventTracing;

public interface ITraceMetadata
{
    Version OSVersion
    bool Is32Bit
    FrequencyValue ProcessorSpeed
    TraceClockType ClockType
    FrequencyValue PerformanceCounterFrequency
    TraceTimestampValue? ReferenceTimestampValue
    FrequencyValue ProcessorUsageTimerFrequency
    TraceTimestamp FirstAnalyzerDisplayedEventTime
    TraceTimestamp LastEventTime
    TraceDuration AnalyzerDisplayedDuration    
    long LostBufferCount
    long LostEventCount
    string TracePath
    DateTimeOffset StartTime    
    DateTimeOffset StopTime    
    int ProcessorCount    
    int KernelEventVersion
}

Update

I have added the suggested code of dmatsion to ETWAnalyzer. Now you can do

ETWAnalyzer -dump stats

enter image description here

1

There are 1 best solutions below

1
dmatson On

I'll let a current team member answer regarding what changes can be made. Conceptually, before the v1 API shipped, I think it would have made sense to add this data to ITraceMetadata.

I don't recall adding this data to anything else in the API. (I checked ISystemMetadata and didn't see it there.) The only workaround I'm aware of would be to use IEventConsumer/IFilteredEventConsumer to parse the payload of the event containing this data (always the first event in the trace, I think, with whatever ProviderId/Id that event has; I don't recall off-hand).

If I recall correctly, the payload is just the TRACE_LOGFILE_HEADER structure. But note that there are both 32-bit and 64-bit versions of that event due to the LoggerName/LogFileName pointers in it (based on the bitness of the trace, not the bitness of the machine processing the trace).

EDIT:

Here's a sample exe that gets the boot time via the trace header event. (I'm using trace.Use(), but IFilteredEventConsumer would be equivalent.)

using Microsoft.Windows.EventTracing;
using System;
using System.Runtime.InteropServices;

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

        string tracePath = args[0];

        using (ITraceProcessor trace = TraceProcessor.Create(tracePath))
        {
            DateTime? bootTime = null;

            Guid eventTraceProviderId = new Guid("68fdd900-4a3e-11d1-84f4-0000f80464e3");
            trace.Use(new[] { eventTraceProviderId }, e =>
            {
                if (e.Event.Id != 0 || e.Event.Version != 2)
                {
                    return;
                }

                var data = e.Event.Data;
                long rawBootTime;

                if (e.Event.Is32Bit)
                {
                    if (data.Length < Marshal.SizeOf<NativeTraceHeaderEvent32>())
                    {
                        throw new InvalidOperationException("Invalid 32-bit trace header event.");
                    }

                    // FYI - Inefficient / lots of copies, but doesn't require compiling with /unsafe.
                    IntPtr pointer = Marshal.AllocHGlobal(data.Length);
                    Marshal.Copy(data.ToArray(), 0, pointer, data.Length);
                    NativeTraceHeaderEvent32 typedData = Marshal.PtrToStructure<NativeTraceHeaderEvent32>(pointer);
                    Marshal.FreeHGlobal(pointer);
                    rawBootTime = typedData.BootTime;
                }
                else
                {
                    if (data.Length < Marshal.SizeOf<NativeTraceHeaderEvent64>())
                    {
                        throw new InvalidOperationException("Invalid 64-bit trace header event.");
                    }

                    // FYI - Inefficient / lots of copies, but doesn't require compiling with /unsafe.
                    IntPtr pointer = Marshal.AllocHGlobal(data.Length);
                    Marshal.Copy(data.ToArray(), 0, pointer, data.Length);
                    NativeTraceHeaderEvent64 typedData = Marshal.PtrToStructure<NativeTraceHeaderEvent64>(pointer);
                    Marshal.FreeHGlobal(pointer);
                    rawBootTime = typedData.BootTime;
                }

                // See https://learn.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-trace_logfile_header:
                // BootTime is ticks since midnight, January 1, 1601 and is apparently UTC (despite documentation to the
                // contrary).
                DateTime epoch = new DateTime(1601, 1, 1, 0, 0, 0, DateTimeKind.Utc);
                bootTime = epoch.AddTicks(rawBootTime);

                e.Cancel();
            });

            trace.Process();

            Console.WriteLine(bootTime);
        }

        return 0;
    }

    // https://learn.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-trace_logfile_header
    [StructLayout(LayoutKind.Sequential)]
    struct NativeTraceHeaderEvent64
    {
        public uint BufferSize;
        public byte MajorVersion;
        public byte MinorVersion;
        public byte SubVersion;
        public byte SubMinorVersion;
        public uint ProviderVersion;
        public uint NumberOfProcessors;
        public long EndTime;
        public uint TimerResolution;
        public uint MaximumFileSize;
        public uint LogFileMode;
        public uint BuffersWritten;
        public uint StartBuffers;
        public uint PointerSize;
        public uint EventsLost;
        public uint CpuSpeedInMHz;
        public ulong LoggerName;
        public ulong LogFileName;
        public NativeTimeZoneInformation TimeZone;
        public long BootTime;
        public long PerfFreq;
        public long StartTime;
        public uint ReservedFlags;
        public uint BuffersLost;
    }

    // https://learn.microsoft.com/en-us/windows/win32/api/evntrace/ns-evntrace-trace_logfile_header
    [StructLayout(LayoutKind.Sequential)]
    struct NativeTraceHeaderEvent32
    {
        public uint BufferSize;
        public byte MajorVersion;
        public byte MinorVersion;
        public byte SubVersion;
        public byte SubMinorVersion;
        public uint ProviderVersion;
        public uint NumberOfProcessors;
        public long EndTime;
        public uint TimerResolution;
        public uint MaximumFileSize;
        public uint LogFileMode;
        public uint BuffersWritten;
        public uint StartBuffers;
        public uint PointerSize;
        public uint EventsLost;
        public uint CpuSpeedInMHz;
        public uint LoggerName;
        public uint LogFileName;
        public NativeTimeZoneInformation TimeZone;
        public long BootTime;
        public long PerfFreq;
        public long StartTime;
        public uint ReservedFlags;
        public uint BuffersLost;
    }

    // https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/ns-timezoneapi-time_zone_information
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    struct NativeTimeZoneInformation
    {
        public int Bias;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
        public char[] StandardName;

        public NativeSystemTime StandardDate;

        public int StandardBias;

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
        public char[] DaylightName;

        public NativeSystemTime DaylightDate;

        public int DaylightBias;
    }

    // https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-systemtime
    [StructLayout(LayoutKind.Sequential)]
    struct NativeSystemTime
    {
        public short wYear;
        public short wMonth;
        public short wDayOfWeek;
        public short wDay;
        public short wHour;
        public short wMinute;
        public short wSecond;
        public short wMilliseconds;
    }
}