From 1f60afdba8670a366e45bfb0707d18997a53fe4c Mon Sep 17 00:00:00 2001 From: Brian Robbins Date: Wed, 14 Mar 2018 19:18:38 -0700 Subject: [PATCH] Add EventSource TraceLogging Support for EventPipe (#16861) --- src/mscorlib/System.Private.CoreLib.csproj | 1 + .../System/Diagnostics/Tracing/EventDescriptor.cs | 8 + .../System/Diagnostics/Tracing/EventProvider.cs | 3 +- .../System/Diagnostics/Tracing/EventSource.cs | 125 ++----- .../Tracing/TraceLogging/InvokeTypeInfo.cs | 2 +- .../Diagnostics/Tracing/TraceLogging/NameInfo.cs | 45 +++ .../TraceLogging/TraceLoggingEventSource.cs | 26 +- .../Tracing/TraceLogging/TraceLoggingEventTypes.cs | 20 ++ .../Diagnostics/Eventing/EventPipeEventProvider.cs | 9 + .../Eventing/EventPipeMetadataGenerator.cs | 376 +++++++++++++++++++++ tests/issues.targets | 3 + .../tracelogging/EventSourceTest.cs | 161 +++++++++ .../tracevalidation/tracelogging/TraceLogging.cs | 126 +++++++ .../tracelogging/tracelogging.csproj | 31 ++ 14 files changed, 838 insertions(+), 98 deletions(-) create mode 100644 src/mscorlib/src/System/Diagnostics/Eventing/EventPipeMetadataGenerator.cs create mode 100644 tests/src/tracing/tracevalidation/tracelogging/EventSourceTest.cs create mode 100644 tests/src/tracing/tracevalidation/tracelogging/TraceLogging.cs create mode 100644 tests/src/tracing/tracevalidation/tracelogging/tracelogging.csproj diff --git a/src/mscorlib/System.Private.CoreLib.csproj b/src/mscorlib/System.Private.CoreLib.csproj index f0c4556..79b36c3 100644 --- a/src/mscorlib/System.Private.CoreLib.csproj +++ b/src/mscorlib/System.Private.CoreLib.csproj @@ -525,6 +525,7 @@ + diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/EventDescriptor.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/EventDescriptor.cs index b036b28..0fed7b5 100644 --- a/src/mscorlib/shared/System/Diagnostics/Tracing/EventDescriptor.cs +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/EventDescriptor.cs @@ -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)) diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/EventProvider.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/EventProvider.cs index f278aa9..c1e4298 100644 --- a/src/mscorlib/shared/System/Diagnostics/Tracing/EventProvider.cs +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/EventProvider.cs @@ -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, diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/EventSource.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/EventSource.cs index 70c65a0..751b0fd 100644 --- a/src/mscorlib/shared/System/Diagnostics/Tracing/EventSource.cs +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/EventSource.cs @@ -537,7 +537,7 @@ namespace System.Diagnostics.Tracing } } - #region protected +#region protected /// /// 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 /// internal int Reserved { get { return m_Reserved; } set { m_Reserved = value; } } - #region private +#region private /// /// 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 } /// @@ -1300,9 +1234,9 @@ namespace System.Diagnostics.Tracing WriteEventVarargs(eventId, &relatedActivityId, args); } - #endregion +#endregion - #region IDisposable Members +#region IDisposable Members /// /// Disposes of an EventSource. /// @@ -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 diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/InvokeTypeInfo.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/InvokeTypeInfo.cs index 3e5997b..2a7113e 100644 --- a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/InvokeTypeInfo.cs +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/InvokeTypeInfo.cs @@ -21,7 +21,7 @@ namespace System.Diagnostics.Tracing /// internal sealed class InvokeTypeInfo : TraceLoggingTypeInfo { - private readonly PropertyAnalysis[] properties; + internal readonly PropertyAnalysis[] properties; public InvokeTypeInfo( Type type, diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/NameInfo.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/NameInfo.cs index 668043a..cf8379f 100644 --- a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/NameInfo.cs +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/NameInfo.cs @@ -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 } } diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs index ed2690a..4ebe3a3 100644 --- a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs @@ -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), diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTypes.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTypes.cs index 3c775a3..8887714 100644 --- a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTypes.cs +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTypes.cs @@ -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 } } diff --git a/src/mscorlib/src/System/Diagnostics/Eventing/EventPipeEventProvider.cs b/src/mscorlib/src/System/Diagnostics/Eventing/EventPipeEventProvider.cs index 663a876..adda1b6 100644 --- a/src/mscorlib/src/System/Diagnostics/Eventing/EventPipeEventProvider.cs +++ b/src/mscorlib/src/System/Diagnostics/Eventing/EventPipeEventProvider.cs @@ -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 index 0000000..a2b6095 --- /dev/null +++ b/src/mscorlib/src/System/Diagnostics/Eventing/EventPipeMetadataGenerator.cs @@ -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 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(); + } + + // 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 +} diff --git a/tests/issues.targets b/tests/issues.targets index 873a33a..670a935 100644 --- a/tests/issues.targets +++ b/tests/issues.targets @@ -1719,6 +1719,9 @@ 15494 + + 15494 + diff --git a/tests/src/tracing/tracevalidation/tracelogging/EventSourceTest.cs b/tests/src/tracing/tracevalidation/tracelogging/EventSourceTest.cs new file mode 100644 index 0000000..4ef9eb2 --- /dev/null +++ b/tests/src/tracing/tracevalidation/tracelogging/EventSourceTest.cs @@ -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 m_eventSources = new List(); + private List m_tests = new List(); + 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 index 0000000..19a8ea4 --- /dev/null +++ b/tests/src/tracing/tracevalidation/tracelogging/TraceLogging.cs @@ -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 index 0000000..c68d6e9 --- /dev/null +++ b/tests/src/tracing/tracevalidation/tracelogging/tracelogging.csproj @@ -0,0 +1,31 @@ + + + + + Debug + AnyCPU + 2.0 + {8E3244CB-407F-4142-BAAB-E7A55901A5FA} + Exe + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + ..\..\ + BuildAndRun + $(DefineConstants);STATIC + true + 0 + + + + + + + False + + + + + + + + + -- 2.7.4