Add EventSource TraceLogging Support for EventPipe (#16861)
authorBrian Robbins <brianrob@microsoft.com>
Thu, 15 Mar 2018 02:18:38 +0000 (19:18 -0700)
committerGitHub <noreply@github.com>
Thu, 15 Mar 2018 02:18:38 +0000 (19:18 -0700)
14 files changed:
src/mscorlib/System.Private.CoreLib.csproj
src/mscorlib/shared/System/Diagnostics/Tracing/EventDescriptor.cs
src/mscorlib/shared/System/Diagnostics/Tracing/EventProvider.cs
src/mscorlib/shared/System/Diagnostics/Tracing/EventSource.cs
src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/InvokeTypeInfo.cs
src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/NameInfo.cs
src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs
src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTypes.cs
src/mscorlib/src/System/Diagnostics/Eventing/EventPipeEventProvider.cs
src/mscorlib/src/System/Diagnostics/Eventing/EventPipeMetadataGenerator.cs [new file with mode: 0644]
tests/issues.targets
tests/src/tracing/tracevalidation/tracelogging/EventSourceTest.cs [new file with mode: 0644]
tests/src/tracing/tracevalidation/tracelogging/TraceLogging.cs [new file with mode: 0644]
tests/src/tracing/tracevalidation/tracelogging/tracelogging.csproj [new file with mode: 0644]

index f0c4556..79b36c3 100644 (file)
     <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\FrameworkEventSource.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventPipe.cs" />
     <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventPipeEventProvider.cs" />
+    <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Eventing\EventPipeMetadataGenerator.cs" />
   </ItemGroup>
   <ItemGroup>
     <Compile Include="$(BclSourcesRoot)\System\Diagnostics\Contracts\Contracts.cs" />
index b036b28..0fed7b5 100644 (file)
@@ -168,6 +168,14 @@ namespace System.Diagnostics.Tracing
             }
         }
 
+        internal int TraceLoggingId
+        {
+            get
+            {
+                return m_traceloggingId;
+            }
+        }
+
         public override bool Equals(object obj)
         {
             if (!(obj is EventDescriptor))
index f278aa9..c1e4298 100644 (file)
@@ -1157,6 +1157,7 @@ namespace System.Diagnostics.Tracing
         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")]
         internal unsafe bool WriteEventRaw(
             ref EventDescriptor eventDescriptor,
+            IntPtr eventHandle,
             Guid* activityID,
             Guid* relatedActivityID,
             int dataCount,
@@ -1167,7 +1168,7 @@ namespace System.Diagnostics.Tracing
             status = m_eventProvider.EventWriteTransferWrapper(
                 m_regHandle,
                 ref eventDescriptor,
-                IntPtr.Zero,
+                eventHandle,
                 activityID,
                 relatedActivityID,
                 dataCount,
index 70c65a0..751b0fd 100644 (file)
@@ -537,7 +537,7 @@ namespace System.Diagnostics.Tracing
             }
         }
 
-        #region protected
+#region protected
         /// <summary>
         /// This is the constructor that most users will use to create their eventSource.   It takes 
         /// no parameters.  The ETW provider name and GUID of the EventSource are determined by the EventSource 
@@ -606,6 +606,13 @@ namespace System.Diagnostics.Tracing
         // typed event methods. Dynamically defined events (that use Write) hare defined on the fly and are handled elsewhere.
         private unsafe void DefineEventPipeEvents()
         {
+            // If the EventSource is set to emit all events as TraceLogging events, skip this initialization.
+            // Events will be defined when they are emitted for the first time.
+            if(SelfDescribingEvents)
+            {
+                return;
+            }
+
             Debug.Assert(m_eventData != null);
             Debug.Assert(m_provider != null);
             int cnt = m_eventData.Length;
@@ -615,102 +622,29 @@ namespace System.Diagnostics.Tracing
                 if (eventID == 0)
                     continue;
 
+                byte[] metadata = EventPipeMetadataGenerator.Instance.GenerateEventMetadata(m_eventData[i]);
+
                 string eventName = m_eventData[i].Name;
                 Int64 keywords = m_eventData[i].Descriptor.Keywords;
                 uint eventVersion = m_eventData[i].Descriptor.Version;
                 uint level = m_eventData[i].Descriptor.Level;
 
-                // evnetID          : 4 bytes
-                // eventName        : (eventName.Length + 1) * 2 bytes
-                // keywords         : 8 bytes
-                // eventVersion     : 4 bytes
-                // level            : 4 bytes
-                // parameterCount   : 4 bytes
-                uint metadataLength = 24 + ((uint)eventName.Length + 1) * 2;
-
-                // Increase the metadataLength for the types of all parameters.
-                metadataLength += (uint)m_eventData[i].Parameters.Length * 4;
-
-                // Increase the metadataLength for the names of all parameters.
-                foreach (var parameter in m_eventData[i].Parameters)
-                {
-                    string parameterName = parameter.Name;
-                    metadataLength = metadataLength + ((uint)parameterName.Length + 1) * 2;
-                }
-
-                byte[] metadata = new byte[metadataLength];
-
-                // Write metadata: evnetID, eventName, keywords, eventVersion, level, parameterCount, param1 type, param1 name...
                 fixed (byte *pMetadata = metadata)
                 {
-                    uint offset = 0;
-                    WriteToBuffer(pMetadata, metadataLength, ref offset, eventID);
-                    fixed(char *pEventName = eventName)
-                    {
-                        WriteToBuffer(pMetadata, metadataLength, ref offset, (byte *)pEventName, ((uint)eventName.Length + 1) * 2);
-                    }
-                    WriteToBuffer(pMetadata, metadataLength, ref offset, keywords);
-                    WriteToBuffer(pMetadata, metadataLength, ref offset, eventVersion);
-                    WriteToBuffer(pMetadata, metadataLength, ref offset, level);
-                    WriteToBuffer(pMetadata, metadataLength, ref offset, (uint)m_eventData[i].Parameters.Length);
-                    foreach (var parameter in m_eventData[i].Parameters)
-                    {
-                        // Write parameter type.
-                        WriteToBuffer(pMetadata, metadataLength, ref offset, (uint)GetTypeCodeExtended(parameter.ParameterType));
-                        
-                        // Write parameter name.
-                        string parameterName = parameter.Name;
-                        fixed (char *pParameterName = parameterName)
-                        {
-                            WriteToBuffer(pMetadata, metadataLength, ref offset, (byte *)pParameterName, ((uint)parameterName.Length + 1) * 2);
-                        }
-                    }
-                    Debug.Assert(metadataLength == offset);
-                    IntPtr eventHandle = m_provider.m_eventProvider.DefineEventHandle(eventID, eventName, keywords, eventVersion, level, pMetadata, metadataLength);
-                    m_eventData[i].EventHandle = eventHandle; 
+                    IntPtr eventHandle = m_provider.m_eventProvider.DefineEventHandle(
+                        eventID,
+                        eventName,
+                        keywords,
+                        eventVersion,
+                        level,
+                        pMetadata,
+                        (uint)metadata.Length);
+
+                    Debug.Assert(eventHandle != IntPtr.Zero);
+                    m_eventData[i].EventHandle = eventHandle;
                 }
             }
         }
-
-        // Copy src to buffer and modify the offset.
-        // Note: We know the buffer size ahead of time to make sure no buffer overflow.
-        private static unsafe void WriteToBuffer(byte *buffer, uint bufferLength, ref uint offset, byte *src, uint srcLength)
-        {
-            Debug.Assert(bufferLength >= (offset + srcLength));
-            for (int i = 0; i < srcLength; i++)
-            {
-                *(byte *)(buffer + offset + i) = *(byte *)(src + i);
-            }
-            offset += srcLength;
-        }
-
-        // Copy uint value to buffer.
-        private static unsafe void WriteToBuffer(byte *buffer, uint bufferLength, ref uint offset, uint value)
-        {
-            Debug.Assert(bufferLength >= (offset + 4));
-            *(uint *)(buffer + offset) = value;
-            offset += 4;
-        }
-
-        // Copy long value to buffer.
-        private static unsafe void WriteToBuffer(byte *buffer, uint bufferLength, ref uint offset, long value)
-        {
-            Debug.Assert(bufferLength >= (offset + 8));
-            *(long *)(buffer + offset) = value;
-            offset += 8;
-        }
-
-        private static TypeCode GetTypeCodeExtended(Type parameterType)
-        {
-            // Guid is not part of TypeCode, we decided to use 17 to represent it, as it's the "free slot" 
-            // see https://github.com/dotnet/coreclr/issues/16105#issuecomment-361749750 for more
-            const TypeCode GuidTypeCode = (TypeCode)17;
-
-            if (parameterType == typeof(Guid)) // Guid is not a part of TypeCode enum
-                return GuidTypeCode;
-
-            return Type.GetTypeCode(parameterType);
-        }
 #endif
 
         internal virtual void GetMetadata(out Guid eventSourceGuid, out string eventSourceName, out EventMetadata[] eventData, out byte[] manifestBytes)
@@ -1112,7 +1046,7 @@ namespace System.Diagnostics.Tracing
             /// </summary>
             internal int Reserved { get { return m_Reserved; } set { m_Reserved = value; } }
 
-            #region private
+#region private
             /// <summary>
             /// Initializes the members of this EventData object to point at a previously-pinned
             /// tracelogging-compatible metadata blob.
@@ -1134,7 +1068,7 @@ namespace System.Diagnostics.Tracing
 #pragma warning disable 0649
             internal int m_Reserved;       // Used to pad the size to match the Win32 API
 #pragma warning restore 0649
-            #endregion
+#endregion
         }
 
         /// <summary>
@@ -1300,9 +1234,9 @@ namespace System.Diagnostics.Tracing
             WriteEventVarargs(eventId, &relatedActivityId, args);
         }
 
-        #endregion
+#endregion
 
-        #region IDisposable Members
+#region IDisposable Members
         /// <summary>
         /// Disposes of an EventSource.
         /// </summary>
@@ -1357,13 +1291,14 @@ namespace System.Diagnostics.Tracing
         {
             this.Dispose(false);
         }
-        #endregion
+#endregion
 
-        #region private
+#region private
 
         private unsafe void WriteEventRaw(
             string eventName,
             ref EventDescriptor eventDescriptor,
+            IntPtr eventHandle,
             Guid* activityID,
             Guid* relatedActivityID,
             int dataCount,
@@ -1376,7 +1311,7 @@ namespace System.Diagnostics.Tracing
             }
             else
             {
-                if (!m_provider.WriteEventRaw(ref eventDescriptor, activityID, relatedActivityID, dataCount, data))
+                if (!m_provider.WriteEventRaw(ref eventDescriptor, eventHandle, activityID, relatedActivityID, dataCount, data))
                     ThrowEventSourceException(eventName);
             }
 #endif // FEATURE_MANAGED_ETW
@@ -2732,7 +2667,7 @@ namespace System.Diagnostics.Tracing
 
             Debug.Assert(!SelfDescribingEvents);
 
-#if FEATURE_MANAGED_ETW 
+#if FEATURE_MANAGED_ETW
             fixed (byte* dataPtr = rawManifest)
             {
                 // we don't want the manifest to show up in the event log channels so we specify as keywords 
index 3e5997b..2a7113e 100644 (file)
@@ -21,7 +21,7 @@ namespace System.Diagnostics.Tracing
     /// </typeparam>
     internal sealed class InvokeTypeInfo : TraceLoggingTypeInfo
     {
-        private readonly PropertyAnalysis[] properties;
+        internal readonly PropertyAnalysis[] properties;
 
         public InvokeTypeInfo(
             Type type,
index 668043a..cf8379f 100644 (file)
@@ -41,6 +41,11 @@ namespace System.Diagnostics.Tracing
         internal readonly int identity;
         internal readonly byte[] nameMetadata;
 
+#if FEATURE_PERFTRACING
+        private IntPtr eventHandle = IntPtr.Zero;
+        private readonly object eventHandleCreationLock = new object();
+#endif
+
         public NameInfo(string name, EventTags tags, int typeMetadataSize)
         {
             this.name = name;
@@ -75,5 +80,45 @@ namespace System.Diagnostics.Tracing
             }
             return result;
         }
+
+#if FEATURE_PERFTRACING
+        public IntPtr GetOrCreateEventHandle(EventProvider provider, EventDescriptor descriptor, TraceLoggingEventTypes eventTypes)
+        {
+            if (eventHandle == IntPtr.Zero)
+            {
+                lock (eventHandleCreationLock)
+                {
+                    if (eventHandle == IntPtr.Zero)
+                    {
+                        byte[] metadataBlob = EventPipeMetadataGenerator.Instance.GenerateEventMetadata(
+                            descriptor.EventId,
+                            name,
+                            (EventKeywords)descriptor.Keywords,
+                            (EventLevel)descriptor.Level,
+                            descriptor.Version,
+                            eventTypes);
+
+                        unsafe
+                        {
+                            fixed (byte* pMetadataBlob = metadataBlob)
+                            {
+                                // Define the event.
+                                eventHandle = provider.m_eventProvider.DefineEventHandle(
+                                    (uint)descriptor.EventId,
+                                    name,
+                                    descriptor.Keywords,
+                                    descriptor.Version,
+                                    descriptor.Level,
+                                    pMetadataBlob,
+                                    (uint)metadataBlob.Length);
+                            }
+                        }
+                    }
+                }
+            }
+
+            return eventHandle;
+        }
+#endif
     }
 }
index ed2690a..4ebe3a3 100644 (file)
@@ -431,6 +431,13 @@ namespace System.Diagnostics.Tracing
             identity = nameInfo.identity;
             EventDescriptor descriptor = new EventDescriptor(identity, level, opcode, (long)keywords);
 
+#if FEATURE_PERFTRACING
+            IntPtr eventHandle = nameInfo.GetOrCreateEventHandle(m_provider, descriptor, eventTypes);
+            Debug.Assert(eventHandle != IntPtr.Zero);
+#else
+            IntPtr eventHandle = IntPtr.Zero;
+#endif
+
             var pinCount = eventTypes.pinCount;
             var scratch = stackalloc byte[eventTypes.scratchSize];
             var descriptors = stackalloc EventData[eventTypes.dataCount + 3];
@@ -472,6 +479,7 @@ namespace System.Diagnostics.Tracing
                     this.WriteEventRaw(
                         eventName,
                         ref descriptor,
+                        eventHandle,
                         activityID,
                         childActivityID,
                         (int)(DataCollector.ThreadInstance.Finish() - descriptors),
@@ -538,6 +546,13 @@ namespace System.Diagnostics.Tracing
                     return;
                 }
 
+#if FEATURE_PERFTRACING
+                    IntPtr eventHandle = nameInfo.GetOrCreateEventHandle(m_provider, descriptor, eventTypes);
+                    Debug.Assert(eventHandle != IntPtr.Zero);
+#else
+                    IntPtr eventHandle = IntPtr.Zero;
+#endif
+
                 // We make a descriptor for each EventData, and because we morph strings to counted strings
                 // we may have 2 for each arg, so we allocate enough for this.  
                 var descriptorsLength = eventTypes.dataCount + eventTypes.typeInfos.Length * 2 + 3;
@@ -570,6 +585,7 @@ namespace System.Diagnostics.Tracing
                     this.WriteEventRaw(
                         eventName,
                         ref descriptor,
+                        eventHandle,
                         activityID,
                         childActivityID,
                         numDescrs,
@@ -599,6 +615,13 @@ namespace System.Diagnostics.Tracing
                         return;
                     }
 
+#if FEATURE_PERFTRACING
+                    IntPtr eventHandle = nameInfo.GetOrCreateEventHandle(m_provider, descriptor, eventTypes);
+                    Debug.Assert(eventHandle != IntPtr.Zero);
+#else
+                    IntPtr eventHandle = IntPtr.Zero;
+#endif
+
 #if FEATURE_MANAGED_ETW
                     var pinCount = eventTypes.pinCount;
                     var scratch = stackalloc byte[eventTypes.scratchSize];
@@ -621,7 +644,7 @@ namespace System.Diagnostics.Tracing
 #endif // FEATURE_MANAGED_ETW
 
 #if (!ES_BUILD_PCL && !ES_BUILD_PN)
-                        System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions();
+                    System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions();
 #endif
                         EventOpcode opcode = (EventOpcode)descriptor.Opcode;
 
@@ -661,6 +684,7 @@ namespace System.Diagnostics.Tracing
                             this.WriteEventRaw(
                                 eventName,
                                 ref descriptor,
+                                eventHandle,
                                 pActivityId,
                                 pRelatedActivityId,
                                 (int)(DataCollector.ThreadInstance.Finish() - descriptors),
index 3c775a3..8887714 100644 (file)
@@ -25,6 +25,9 @@ namespace System.Diagnostics.Tracing
     public class TraceLoggingEventTypes
     {
         internal readonly TraceLoggingTypeInfo[] typeInfos;
+#if FEATURE_PERFTRACING
+        internal readonly string[] paramNames;
+#endif
         internal readonly string name;
         internal readonly EventTags tags;
         internal readonly byte level;
@@ -98,6 +101,9 @@ namespace System.Diagnostics.Tracing
             }
 
             this.typeInfos = MakeArray(paramInfos);
+#if FEATURE_PERFTRACING
+            this.paramNames = MakeParamNameArray(paramInfos);
+#endif
             this.name = name;
             this.tags = tags;
             this.level = Statics.DefaultLevel;
@@ -248,5 +254,19 @@ namespace System.Diagnostics.Tracing
 
             return (TraceLoggingTypeInfo[])typeInfos.Clone(); ;
         }
+
+#if FEATURE_PERFTRACING
+        private static string[] MakeParamNameArray(
+            System.Reflection.ParameterInfo[] paramInfos)
+        {
+            string[] paramNames = new string[paramInfos.Length];
+            for (int i = 0; i < paramNames.Length; i++)
+            {
+                paramNames[i] = paramInfos[i].Name;
+            }
+
+            return paramNames;
+        }
+#endif
     }
 }
index 663a876..adda1b6 100644 (file)
@@ -66,6 +66,15 @@ namespace System.Diagnostics.Tracing
                     return 0;
                 }
 
+                // If Channel == 11, this is a TraceLogging event.
+                // The first 3 descriptors contain event metadata that is emitted for ETW and should be discarded on EventPipe.
+                // EventPipe metadata is provided via the EventPipeEventProvider.DefineEventHandle.
+                if (eventDescriptor.Channel == 11)
+                {
+                    userData = userData + 3;
+                    userDataCount = userDataCount - 3;
+                    Debug.Assert(userDataCount >= 0);
+                }
                 EventPipeInternal.WriteEventData(eventHandle, eventID, &userData, (uint) userDataCount, activityId, relatedActivityId);
             }
             return 0;
diff --git a/src/mscorlib/src/System/Diagnostics/Eventing/EventPipeMetadataGenerator.cs b/src/mscorlib/src/System/Diagnostics/Eventing/EventPipeMetadataGenerator.cs
new file mode 100644 (file)
index 0000000..a2b6095
--- /dev/null
@@ -0,0 +1,376 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using System;
+using System.Reflection;
+using EventMetadata = System.Diagnostics.Tracing.EventSource.EventMetadata;
+
+namespace System.Diagnostics.Tracing
+{
+#if FEATURE_PERFTRACING
+
+    internal sealed class EventPipeMetadataGenerator
+    {
+        public static EventPipeMetadataGenerator Instance = new EventPipeMetadataGenerator();
+
+        private EventPipeMetadataGenerator() { }
+
+        public unsafe byte[] GenerateEventMetadata(EventMetadata eventMetadata)
+        {
+            ParameterInfo[] parameters = eventMetadata.Parameters;
+            EventParameterInfo[] eventParams = new EventParameterInfo[parameters.Length];
+            for(int i=0; i<parameters.Length; i++)
+            {
+                eventParams[i].SetInfo(parameters[i].Name, parameters[i].ParameterType);
+            }
+
+            return GenerateMetadata(
+                eventMetadata.Descriptor.EventId,
+                eventMetadata.Name,
+                eventMetadata.Descriptor.Keywords,
+                eventMetadata.Descriptor.Level,
+                eventMetadata.Descriptor.Version,
+                eventParams);
+        }
+
+        public unsafe byte[] GenerateEventMetadata(
+            int eventId,
+            string eventName,
+            EventKeywords keywords,
+            EventLevel level,
+            uint version,
+            TraceLoggingEventTypes eventTypes)
+        {
+            TraceLoggingTypeInfo[] typeInfos = eventTypes.typeInfos;
+            string[] paramNames = eventTypes.paramNames;
+            EventParameterInfo[] eventParams = new EventParameterInfo[typeInfos.Length];
+            for(int i=0; i<typeInfos.Length; i++)
+            {
+                string paramName = string.Empty;
+                if(paramNames != null)
+                {
+                    paramName = paramNames[i];
+                }
+                eventParams[i].SetInfo(paramName, typeInfos[i].DataType, typeInfos[i]);
+            }
+
+            return GenerateMetadata(eventId, eventName, (long)keywords, (uint)level, version, eventParams);
+        }
+
+        private unsafe byte[] GenerateMetadata(
+            int eventId,
+            string eventName,
+            long keywords,
+            uint level,
+            uint version,
+            EventParameterInfo[] parameters)
+        {
+            // eventID          : 4 bytes
+            // eventName        : (eventName.Length + 1) * 2 bytes
+            // keywords         : 8 bytes
+            // eventVersion     : 4 bytes
+            // level            : 4 bytes
+            // parameterCount   : 4 bytes
+            uint metadataLength = 24 + ((uint)eventName.Length + 1) * 2;
+
+            // Check for an empty payload.
+            // Write<T> calls with no arguments by convention have a parameter of
+            // type NullTypeInfo which is serialized as nothing.
+            if((parameters.Length == 1) && (parameters[0].ParameterType == typeof(EmptyStruct)))
+            {
+                parameters = Array.Empty<EventParameterInfo>();
+            }
+
+            // Increase the metadataLength for parameters.
+            foreach (var parameter in parameters)
+            {
+                metadataLength = metadataLength + parameter.GetMetadataLength();
+            }
+
+            byte[] metadata = new byte[metadataLength];
+
+            // Write metadata: eventID, eventName, keywords, eventVersion, level, parameterCount, param1 type, param1 name...
+            fixed (byte *pMetadata = metadata)
+            {
+                uint offset = 0;
+                WriteToBuffer(pMetadata, metadataLength, ref offset, (uint)eventId);
+                fixed(char *pEventName = eventName)
+                {
+                    WriteToBuffer(pMetadata, metadataLength, ref offset, (byte *)pEventName, ((uint)eventName.Length + 1) * 2);
+                }
+                WriteToBuffer(pMetadata, metadataLength, ref offset, keywords);
+                WriteToBuffer(pMetadata, metadataLength, ref offset, version);
+                WriteToBuffer(pMetadata, metadataLength, ref offset, level);
+                WriteToBuffer(pMetadata, metadataLength, ref offset, (uint)parameters.Length);
+                foreach (var parameter in parameters)
+                {
+                    parameter.GenerateMetadata(pMetadata, ref offset, metadataLength);
+                }
+                Debug.Assert(metadataLength == offset);
+            }
+
+            return metadata;
+        }
+
+        // Copy src to buffer and modify the offset.
+        // Note: We know the buffer size ahead of time to make sure no buffer overflow.
+        internal static unsafe void WriteToBuffer(byte *buffer, uint bufferLength, ref uint offset, byte *src, uint srcLength)
+        {
+            Debug.Assert(bufferLength >= (offset + srcLength));
+            for (int i = 0; i < srcLength; i++)
+            {
+                *(byte *)(buffer + offset + i) = *(byte *)(src + i);
+            }
+            offset += srcLength;
+        }
+
+        // Copy uint value to buffer.
+        internal static unsafe void WriteToBuffer(byte *buffer, uint bufferLength, ref uint offset, uint value)
+        {
+            Debug.Assert(bufferLength >= (offset + 4));
+            *(uint *)(buffer + offset) = value;
+            offset += 4;
+        }
+
+        // Copy long value to buffer.
+        internal static unsafe void WriteToBuffer(byte *buffer, uint bufferLength, ref uint offset, long value)
+        {
+            Debug.Assert(bufferLength >= (offset + 8));
+            *(long *)(buffer + offset) = value;
+            offset += 8;
+        }
+
+        // Copy char value to buffer.
+        internal static unsafe void WriteToBuffer(byte *buffer, uint bufferLength, ref uint offset, char value)
+        {
+            Debug.Assert(bufferLength >= (offset + 2));
+            *(char *)(buffer + offset) = value;
+            offset += 2;
+        }
+
+    }
+
+    internal struct EventParameterInfo
+    {
+        internal string ParameterName;
+        internal Type ParameterType;
+        internal TraceLoggingTypeInfo TypeInfo;
+
+        internal void SetInfo(string name, Type type, TraceLoggingTypeInfo typeInfo = null)
+        {
+            ParameterName = name;
+            ParameterType = type;
+            TypeInfo = typeInfo;
+        }
+
+        internal unsafe void GenerateMetadata(byte* pMetadataBlob, ref uint offset, uint blobSize)
+        {
+            TypeCode typeCode = GetTypeCodeExtended(ParameterType);
+            if(typeCode == TypeCode.Object)
+            {
+                // Each nested struct is serialized as:
+                //     TypeCode.Object              : 4 bytes
+                //     Number of properties         : 4 bytes
+                //     Property description 0...N
+                //     Nested struct property name  : NULL-terminated string.
+                EventPipeMetadataGenerator.WriteToBuffer(pMetadataBlob, blobSize, ref offset, (uint)TypeCode.Object);
+
+                InvokeTypeInfo invokeTypeInfo = TypeInfo as InvokeTypeInfo;
+                if(invokeTypeInfo == null)
+                {
+                    throw new NotSupportedException();
+                }
+
+                // Get the set of properties to be serialized.
+                PropertyAnalysis[] properties = invokeTypeInfo.properties;
+                if(properties != null)
+                {
+                    // Write the count of serializable properties.
+                    EventPipeMetadataGenerator.WriteToBuffer(pMetadataBlob, blobSize, ref offset, (uint)properties.Length);
+
+                    foreach(PropertyAnalysis prop in properties)
+                    {
+                        GenerateMetadataForProperty(prop, pMetadataBlob, ref offset, blobSize);
+                    }
+                }
+                else
+                {
+                    // This struct has zero serializable properties so we just write the property count.
+                    EventPipeMetadataGenerator.WriteToBuffer(pMetadataBlob, blobSize, ref offset, (uint)0);
+                }
+
+                // Top-level structs don't have a property name, but for simplicity we write a NULL-char to represent the name.
+                EventPipeMetadataGenerator.WriteToBuffer(pMetadataBlob, blobSize, ref offset, '\0');
+               
+            }
+            else
+            {
+                // Write parameter type.
+                EventPipeMetadataGenerator.WriteToBuffer(pMetadataBlob, blobSize, ref offset, (uint)typeCode);
+
+                // Write parameter name.
+                fixed (char *pParameterName = ParameterName)
+                {
+                    EventPipeMetadataGenerator.WriteToBuffer(pMetadataBlob, blobSize, ref offset, (byte *)pParameterName, ((uint)ParameterName.Length + 1) * 2);
+                }
+            }
+        }
+
+        private static unsafe void GenerateMetadataForProperty(PropertyAnalysis property, byte* pMetadataBlob, ref uint offset, uint blobSize)
+        {
+            Debug.Assert(property != null);
+            Debug.Assert(pMetadataBlob != null);
+
+            // Check if this property is a nested struct.
+            InvokeTypeInfo invokeTypeInfo = property.typeInfo as InvokeTypeInfo;
+            if(invokeTypeInfo != null)
+            {
+                // Each nested struct is serialized as:
+                //     TypeCode.Object              : 4 bytes
+                //     Number of properties         : 4 bytes
+                //     Property description 0...N
+                //     Nested struct property name  : NULL-terminated string.
+                EventPipeMetadataGenerator.WriteToBuffer(pMetadataBlob, blobSize, ref offset, (uint)TypeCode.Object);
+
+                // Get the set of properties to be serialized.
+                PropertyAnalysis[] properties = invokeTypeInfo.properties;
+                if(properties != null)
+                {
+                    // Write the count of serializable properties.
+                    EventPipeMetadataGenerator.WriteToBuffer(pMetadataBlob, blobSize, ref offset, (uint)properties.Length);
+
+                    foreach(PropertyAnalysis prop in properties)
+                    {
+                        GenerateMetadataForProperty(prop, pMetadataBlob, ref offset, blobSize);
+                    }
+                }
+                else
+                {
+                    // This struct has zero serializable properties so we just write the property count.
+                    EventPipeMetadataGenerator.WriteToBuffer(pMetadataBlob, blobSize, ref offset, (uint)0);
+                }
+
+                // Write the property name.
+                fixed(char *pPropertyName = property.name)
+                {
+                    EventPipeMetadataGenerator.WriteToBuffer(pMetadataBlob, blobSize, ref offset, (byte *)pPropertyName, ((uint)property.name.Length + 1) * 2);
+                }
+            }
+            else
+            {
+                // Each primitive type is serialized as:
+                //     TypeCode : 4 bytes
+                //     PropertyName : NULL-terminated string
+                TypeCode typeCode = GetTypeCodeExtended(property.typeInfo.DataType);
+                Debug.Assert(typeCode != TypeCode.Object);
+
+                // Write the type code.
+                EventPipeMetadataGenerator.WriteToBuffer(pMetadataBlob, blobSize, ref offset, (uint)typeCode);
+
+                // Write the property name.
+                fixed(char *pPropertyName = property.name)
+                {
+                    EventPipeMetadataGenerator.WriteToBuffer(pMetadataBlob, blobSize, ref offset, (byte *)pPropertyName, ((uint)property.name.Length + 1) * 2);
+                }
+            }
+        }
+
+
+        internal unsafe uint GetMetadataLength()
+        {
+            uint ret = 0;
+
+            TypeCode typeCode = GetTypeCodeExtended(ParameterType);
+            if(typeCode == TypeCode.Object)
+            {
+                InvokeTypeInfo typeInfo = TypeInfo as InvokeTypeInfo;
+                if(typeInfo == null)
+                {
+                    throw new NotSupportedException();
+                }
+
+                // Each nested struct is serialized as:
+                //     TypeCode.Object      : 4 bytes
+                //     Number of properties : 4 bytes
+                //     Property description 0...N
+                //     Nested struct property name  : NULL-terminated string.
+                ret += sizeof(uint)  // TypeCode
+                     + sizeof(uint); // Property count
+
+                // Get the set of properties to be serialized.
+                PropertyAnalysis[] properties = typeInfo.properties;
+                if(properties != null)
+                {
+                    foreach(PropertyAnalysis prop in properties)
+                    {
+                        ret += GetMetadataLengthForProperty(prop);
+                    }
+                }
+
+                // For simplicity when writing a reader, we write a NULL char
+                // after the metadata for a top-level struct (for its name) so that 
+                // readers don't have do special case the outer-most struct.
+                ret += sizeof(char);
+            }
+            else
+            {
+                ret += (uint)(sizeof(uint) + ((ParameterName.Length + 1) * 2));
+            }
+
+            return ret;
+        }
+
+        private static uint GetMetadataLengthForProperty(PropertyAnalysis property)
+        {
+            Debug.Assert(property != null);
+
+            uint ret = 0;
+
+            // Check if this property is a nested struct.
+            InvokeTypeInfo invokeTypeInfo = property.typeInfo as InvokeTypeInfo;
+            if(invokeTypeInfo != null)
+            {
+                // Each nested struct is serialized as:
+                //     TypeCode.Object      : 4 bytes
+                //     Number of properties : 4 bytes
+                //     Property description 0...N
+                //     Nested struct property name  : NULL-terminated string.
+                ret += sizeof(uint)  // TypeCode
+                     + sizeof(uint); // Property count
+
+                // Get the set of properties to be serialized.
+                PropertyAnalysis[] properties = invokeTypeInfo.properties;
+                if(properties != null)
+                {
+                    foreach(PropertyAnalysis prop in properties)
+                    {
+                        ret += GetMetadataLengthForProperty(prop);
+                    }
+                }
+
+                // Add the size of the property name.
+                ret += (uint)((property.name.Length + 1) * 2);
+            }
+            else
+            {
+                ret += (uint)(sizeof(uint) + ((property.name.Length + 1) * 2));
+            }
+
+            return ret;
+        }
+
+        private static TypeCode GetTypeCodeExtended(Type parameterType)
+        {
+            // Guid is not part of TypeCode, we decided to use 17 to represent it, as it's the "free slot"
+            // see https://github.com/dotnet/coreclr/issues/16105#issuecomment-361749750 for more
+            const TypeCode GuidTypeCode = (TypeCode)17;
+
+            if (parameterType == typeof(Guid)) // Guid is not a part of TypeCode enum
+                return GuidTypeCode;
+
+            return Type.GetTypeCode(parameterType);
+        }
+    }
+
+#endif // FEATURE_PERFTRACING
+}
index 873a33a..670a935 100644 (file)
         <ExcludeList Include="$(XunitTestBinBase)\tracing\eventsourcetrace\eventsourcetrace\eventsourcetrace.cmd">
             <Issue>15494</Issue>
         </ExcludeList>
+        <ExcludeList Include="$(XunitTestBinBase)\tracing\tracevalidation\tracelogging\tracelogging\tracelogging.cmd">
+            <Issue>15494</Issue>
+        </ExcludeList>
     </ItemGroup>
 
     <!-- Failures while testing via ILLINK -->
diff --git a/tests/src/tracing/tracevalidation/tracelogging/EventSourceTest.cs b/tests/src/tracing/tracevalidation/tracelogging/EventSourceTest.cs
new file mode 100644 (file)
index 0000000..4ef9eb2
--- /dev/null
@@ -0,0 +1,161 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Tracing;
+using Microsoft.Diagnostics.Tracing;
+using Microsoft.Diagnostics.Tracing.Etlx;
+using Microsoft.Diagnostics.Tracing.Parsers;
+
+namespace Tracing.Tests.Common
+{
+    public sealed class EventSourceTestSuite
+    {
+        private NetPerfFile m_file;
+        private List<EventSource> m_eventSources = new List<EventSource>();
+        private List<EventSourceTest> m_tests = new List<EventSourceTest>();
+        private int m_nextTestVerificationIndex = 0;
+
+        public EventSourceTestSuite(
+            NetPerfFile file)
+        {
+            if(file == null)
+            {
+                throw new ArgumentNullException(nameof(file));
+            }
+
+            m_file = file;
+        }
+
+        public void AddEventSource(EventSource source)
+        {
+            if(source == null)
+            {
+                throw new ArgumentNullException(nameof(source));
+            }
+
+            m_eventSources.Add(source);
+        }
+
+        public void AddTest(EventSourceTest test)
+        {
+            if(test == null)
+            {
+                throw new ArgumentNullException(nameof(test));
+            }
+            m_tests.Add(test);
+        }
+
+        public void RunTests()
+        {
+            // Get the configuration and start tracing.
+            TraceConfiguration traceConfig = GenerateConfiguration();
+            TraceControl.Enable(traceConfig);
+
+            // Run the tests.
+            foreach(EventSourceTest test in m_tests)
+            {
+                test.LogEvent();
+            }
+
+            // Stop tracing.
+            TraceControl.Disable();
+
+            // Open the trace file.
+            string traceLogPath = TraceLog.CreateFromEventPipeDataFile(m_file.Path);
+            using(TraceLog traceLog = new TraceLog(traceLogPath))
+            {
+                TraceEventDispatcher dispatcher = traceLog.Events.GetSource();
+
+                dispatcher.Dynamic.All += delegate(TraceEvent data)
+                {
+                    if(data.ProviderName.EndsWith("Rundown"))
+                    {
+                        return;
+                    }
+
+                    Assert.True($"m_nextTestVerificationIndex({m_nextTestVerificationIndex}) < m_tests.Count({m_tests.Count})", m_nextTestVerificationIndex < m_tests.Count);
+                    try
+                    {
+                        Console.WriteLine($"Verifying Event: {data.ToString()}");
+                        m_tests[m_nextTestVerificationIndex].VerifyEvent(data);
+                    }
+                    catch
+                    {
+                        Console.WriteLine($"Failure during test '{m_tests[m_nextTestVerificationIndex].Name}'.");
+                        throw;
+                    }
+
+                    m_nextTestVerificationIndex++;
+                };
+
+                dispatcher.Process();
+                Assert.Equal("Test Count", m_tests.Count, m_nextTestVerificationIndex);
+            }
+        }
+
+        private TraceConfiguration GenerateConfiguration()
+        {
+            uint circularBufferMB = 1024; // 1 GB
+
+            TraceConfiguration traceConfig = new TraceConfiguration(m_file.Path, circularBufferMB);
+
+            // Add each of the registered EventSources.
+            foreach(EventSource source in m_eventSources)
+            {
+                traceConfig.EnableProvider(
+                    source.Name,        // ProviderName
+                    0xFFFFFFFFFFFFFFFF, // Keywords
+                    5);                 // Level
+            }
+
+            return traceConfig;
+        }
+    }
+
+    public delegate void LogEventDelegate();
+    public delegate void VerifyEventDelegate(TraceEvent eventData);
+
+    public sealed class EventSourceTest
+    {
+        private string m_name;
+        private LogEventDelegate m_logEvent;
+        private VerifyEventDelegate m_verifyEvent;
+
+        public EventSourceTest(
+            string name,
+            LogEventDelegate logEvent,
+            VerifyEventDelegate verifyEvent)
+        {
+            if(String.IsNullOrEmpty(name))
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+            if(logEvent == null)
+            {
+                throw new ArgumentNullException(nameof(logEvent));
+            }
+            if(verifyEvent == null)
+            {
+                throw new ArgumentNullException(nameof(verifyEvent));
+            }
+
+            m_name = name;
+            m_logEvent = logEvent;
+            m_verifyEvent = verifyEvent;
+        }
+        
+        public string Name
+        {
+            get { return m_name; }
+        }
+
+        public void LogEvent()
+        {
+            m_logEvent();
+        }
+
+        public void VerifyEvent(TraceEvent eventData)
+        {
+            m_verifyEvent(eventData);
+        }
+    }
+}
diff --git a/tests/src/tracing/tracevalidation/tracelogging/TraceLogging.cs b/tests/src/tracing/tracevalidation/tracelogging/TraceLogging.cs
new file mode 100644 (file)
index 0000000..19a8ea4
--- /dev/null
@@ -0,0 +1,126 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using Tracing.Tests.Common;
+using System.Diagnostics.Tracing;
+using Microsoft.Diagnostics.Tracing;
+using Microsoft.Diagnostics.Tracing.Etlx;
+using Microsoft.Diagnostics.Tracing.Parsers;
+
+namespace Tracing.Tests
+{
+    [EventSource(Name = "ManifestEventSource")]
+    public class ManifestEventSource : EventSource
+    {
+        public static ManifestEventSource Log = new ManifestEventSource();
+
+        private ManifestEventSource()
+            : base(true)
+        {
+        }
+
+        [Event(1)]
+        public void EmptyEvent()
+        {
+            WriteEvent(1);
+        }
+
+        [Event(2)]
+        public void IntStringEvent(int i, string s)
+        {
+            WriteEvent(2, i, s);
+        }
+    }
+
+    [EventSource(Name = "TraceLoggingEventSource")]
+    public class TraceLoggingEventSource : EventSource
+    {
+        public static TraceLoggingEventSource Log = new TraceLoggingEventSource();
+
+        private TraceLoggingEventSource()
+            : base(EventSourceSettings.EtwSelfDescribingEventFormat)
+        {
+        }
+
+        [Event(1)]
+        public void EmptyEvent()
+        {
+            WriteEvent(1);
+        }
+
+        [Event(2)]
+        public void IntStringEvent(int i, string s)
+        {
+            WriteEvent(2, i, s);
+        }
+    }
+
+    public static class TraceLogging
+    {
+        public static int Main(string[] args)
+        {
+            using (NetPerfFile file = NetPerfFile.Create(args))
+            {
+                EventSourceTestSuite suite = new EventSourceTestSuite(file);
+                suite.AddEventSource(ManifestEventSource.Log);
+                suite.AddEventSource(TraceLoggingEventSource.Log);
+
+                suite.AddTest(new EventSourceTest("ManifestEmptyEvent",
+                    delegate()
+                    {
+                        ManifestEventSource.Log.EmptyEvent();
+                    },
+                    delegate(TraceEvent eventData)
+                    {
+                        Assert.Equal("ProviderName", ManifestEventSource.Log.Name, eventData.ProviderName);
+                        Assert.Equal("EventName", "EmptyEvent", eventData.EventName);
+                        Assert.Equal("PayloadCount", 0, eventData.PayloadNames.Length);
+                    }));
+
+                suite.AddTest(new EventSourceTest("TraceLoggingEmptyEvent",
+                    delegate()
+                    {
+                        TraceLoggingEventSource.Log.EmptyEvent();
+                    },
+                    delegate(TraceEvent eventData)
+                    {
+                        Assert.Equal("ProviderName", TraceLoggingEventSource.Log.Name, eventData.ProviderName);
+                        Assert.Equal("EventName", "EmptyEvent", eventData.EventName);
+                        Assert.Equal("PayloadCount", 0, eventData.PayloadNames.Length);
+                    }));
+
+                suite.AddTest(new EventSourceTest("ManifestIntString",
+                    delegate()
+                    {
+                        ManifestEventSource.Log.IntStringEvent(42, "Hello World!");
+                    },
+                    delegate(TraceEvent eventData)
+                    {
+                        Assert.Equal("ProviderName", ManifestEventSource.Log.Name, eventData.ProviderName);
+                        Assert.Equal("EventName", "IntStringEvent", eventData.EventName);
+                        Assert.Equal("PayloadCount", 2, eventData.PayloadNames.Length);
+                        Assert.Equal("i", 42, (int)eventData.PayloadValue(0));
+                        Assert.Equal("s", "Hello World!", (string)eventData.PayloadValue(1));
+                    }));
+
+                suite.AddTest(new EventSourceTest("TraceLoggingIntString",
+                    delegate()
+                    {
+                        TraceLoggingEventSource.Log.IntStringEvent(42, "Hello World!");
+                    },
+                    delegate(TraceEvent eventData)
+                    {
+                        Assert.Equal("ProviderName", TraceLoggingEventSource.Log.Name, eventData.ProviderName);
+                        Assert.Equal("EventName", "IntStringEvent", eventData.EventName);
+                        Assert.Equal("PayloadCount", 2, eventData.PayloadNames.Length);
+                        Assert.Equal("i", 42, (int)eventData.PayloadValue(0));
+                        Assert.Equal("s", "Hello World!", (string)eventData.PayloadValue(1));
+                    }));
+
+                suite.RunTests();
+            }
+
+            return 100;
+        }
+    }
+}
diff --git a/tests/src/tracing/tracevalidation/tracelogging/tracelogging.csproj b/tests/src/tracing/tracevalidation/tracelogging/tracelogging.csproj
new file mode 100644 (file)
index 0000000..c68d6e9
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{8E3244CB-407F-4142-BAAB-E7A55901A5FA}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+    <CLRTestKind>BuildAndRun</CLRTestKind>
+    <DefineConstants>$(DefineConstants);STATIC</DefineConstants>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <CLRTestPriority>0</CLRTestPriority>
+  </PropertyGroup>
+  <!-- Default configurations to help VS understand the configurations -->
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'"></PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'"></PropertyGroup>
+  <ItemGroup>
+    <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+      <Visible>False</Visible>
+    </CodeAnalysisDependentAssemblyPaths>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="EventSourceTest.cs" />
+    <Compile Include="TraceLogging.cs" />
+    <ProjectReference Include="../../common/common.csproj" />
+  </ItemGroup>
+  <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+</Project>