// -----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// -----------------------------------------------------------------------------
#pragma warning disable CA1707, CS3008, SA1300 // Identifiers should not contain underscores
namespace ____DiagHubTrace
#pragma warning restore CA1707, CS3008, SA1300
{
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
internal sealed class DiagHubTrace : IDisposable
{
public const ushort FirstValidId = 1;
public const ushort MaxValidId = 0x7fff;
public static readonly Guid DiagHubProviderId = new Guid("F9189F8A-0753-4A70-AD66-D622D88DB986");
private const int SuccessErrorCode = 0;
// The max size limit of an ETW payload is 64k, but that includes the header.
// We take off a bit for ourselves.
private const int MaxMsgSizeInBytes = 63 * 1024;
private long regHandle;
private bool started = false;
///
/// Initializes a new instance of the class.
///
public DiagHubTrace()
{
this.Initialize();
}
///
/// Insert mapping from ID to name
///
/// ID to name
/// Name of ID
[Conditional("DIAGHUB_ENABLE_TRACE_SYSTEM")]
public void DefineIdName(ushort id, string name)
{
if (this.started)
{
Debug.Assert(id <= MaxValidId, "Invalid ID");
this.FireUserEventIdNameMap(id, name);
}
}
///
/// Insert mark into the collection stream
///
/// ID of mark
[Conditional("DIAGHUB_ENABLE_TRACE_SYSTEM")]
public void InsertMark(ushort id)
{
if (this.started)
{
Debug.Assert(id >= FirstValidId && id <= MaxValidId, "Invalid ID");
this.FireUserEventWithString(id, string.Empty);
}
}
///
/// Insert mark with message into the collection stream
///
/// ID of mark
/// Message of mark
[Conditional("DIAGHUB_ENABLE_TRACE_SYSTEM")]
public void InsertMarkWithMessage(ushort id, string message)
{
if (this.started)
{
Debug.Assert(id >= FirstValidId && id <= MaxValidId, "Invalid ID");
this.FireUserEventWithString(id, message);
}
}
///
/// Insert message into the collection stream
///
/// Message to insert
[Conditional("DIAGHUB_ENABLE_TRACE_SYSTEM")]
public void InsertMessage(string message)
{
if (this.started)
{
this.FireUserEventWithString(0, message);
}
}
///
public void Dispose()
{
this.Shutdown();
}
[Conditional("DIAGHUB_ENABLE_TRACE_SYSTEM")]
private void Initialize()
{
Guid localId = DiagHubTrace.DiagHubProviderId;
if (NativeMethods.EventRegister(ref localId, IntPtr.Zero, IntPtr.Zero, ref this.regHandle) == DiagHubTrace.SuccessErrorCode)
{
this.started = true;
}
}
[Conditional("DIAGHUB_ENABLE_TRACE_SYSTEM")]
private void Shutdown()
{
if (this.started)
{
this.started = false;
_ = NativeMethods.EventUnregister(this.regHandle);
}
}
private void FireUserEventIdNameMap(ushort id, string name)
{
Debug.Assert(id <= MaxValidId, "Invalid ID");
Debug.Assert(name != null && (2 * name.Length) < MaxMsgSizeInBytes, "Invalid string argument");
NativeMethods.EVENT_DESCRIPTOR evtDesc = new NativeMethods.EVENT_DESCRIPTOR()
{
Id = 0xffff,
Version = 1,
Channel = 0,
Level = 4, // TRACE_LEVEL_INFORMATION
Opcode = 0,
Task = 0,
Keyword = 1
};
GCHandle idPinned = GCHandle.Alloc(id, GCHandleType.Pinned);
#pragma warning disable CS8602 // Dereference of a possibly null reference.
int nameLenInBytes = 2 * (name.Length + 1);
#pragma warning restore CS8602 // Dereference of a possibly null reference.
GCHandle namePinned = GCHandle.Alloc(name, GCHandleType.Pinned);
NativeMethods.EVENT_DATA_DESCRIPTOR[] userData = new NativeMethods.EVENT_DATA_DESCRIPTOR[2];
userData[0].DataPointer = idPinned.AddrOfPinnedObject().ToInt64();
userData[0].Size = sizeof(ushort);
userData[1].DataPointer = namePinned.AddrOfPinnedObject().ToInt64();
userData[1].Size = nameLenInBytes;
GCHandle userDataPinned = GCHandle.Alloc(userData, GCHandleType.Pinned);
_ = NativeMethods.EventWrite(this.regHandle, ref evtDesc, userData.Length, userDataPinned.AddrOfPinnedObject());
userDataPinned.Free();
namePinned.Free();
idPinned.Free();
}
private void FireUserEventWithString(ushort id, string? message)
{
Debug.Assert(id <= MaxValidId, "Invalid ID");
NativeMethods.EVENT_DESCRIPTOR evtDesc = new NativeMethods.EVENT_DESCRIPTOR()
{
Id = id,
Version = 1,
Channel = 0,
Level = 4, // TRACE_LEVEL_INFORMATION
Opcode = 0,
Task = 0,
Keyword = 1
};
int nameLenInBytes = message == null ? 0 : (2 * (message.Length + 1));
Debug.Assert(nameLenInBytes < MaxMsgSizeInBytes, "Invalid DiagHub mark");
GCHandle msgPinned = GCHandle.Alloc(message, GCHandleType.Pinned);
NativeMethods.EVENT_DATA_DESCRIPTOR userData = new NativeMethods.EVENT_DATA_DESCRIPTOR()
{
DataPointer = msgPinned.AddrOfPinnedObject().ToInt64(),
Size = nameLenInBytes,
};
GCHandle userDataPinned = GCHandle.Alloc(userData, GCHandleType.Pinned);
_ = NativeMethods.EventWrite(this.regHandle, ref evtDesc, 1, userDataPinned.AddrOfPinnedObject());
userDataPinned.Free();
msgPinned.Free();
}
private sealed class NativeMethods
{
[DllImport("Advapi32.dll", SetLastError = true)]
[DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
public static extern int EventRegister(
ref Guid guid,
[Optional] IntPtr enableCallback,
[Optional] IntPtr callbackContext,
[In][Out] ref long regHandle);
[DllImport("Advapi32.dll", SetLastError = true)]
[DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
public static extern int EventWrite(
[In] long regHandle,
[In] ref EVENT_DESCRIPTOR evtDesc,
[In] int userDataCount,
[In] IntPtr userData);
[DllImport("Advapi32.dll", SetLastError = true)]
[DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)]
public static extern int EventUnregister([In] long regHandle);
[StructLayout(LayoutKind.Explicit, Size = 16)]
public struct EVENT_DESCRIPTOR
{
[FieldOffset(0)] public ushort Id;
[FieldOffset(2)] public byte Version;
[FieldOffset(3)] public byte Channel;
[FieldOffset(4)] public byte Level;
[FieldOffset(5)] public byte Opcode;
[FieldOffset(6)] public ushort Task;
[FieldOffset(8)] public ulong Keyword;
}
[StructLayout(LayoutKind.Explicit, Size = 16)]
public struct EVENT_DATA_DESCRIPTOR
{
[FieldOffset(0)] public long DataPointer;
[FieldOffset(8)] public int Size;
[FieldOffset(12)] public int Reserved;
}
}
}
}