From a56e30c8d33048216567753d9d3fefc2152af8ac Mon Sep 17 00:00:00 2001 From: Jiyoung Yun Date: Thu, 13 Apr 2017 14:17:19 +0900 Subject: Imported Upstream version 2.0.0.11353 --- .../Tracing/TraceLogging/ArrayTypeInfo.cs | 63 ++ .../Tracing/TraceLogging/ConcurrentSet.cs | 127 +++ .../Tracing/TraceLogging/ConcurrentSetItem.cs | 25 + .../Tracing/TraceLogging/DataCollector.cs | 318 ++++++++ .../Tracing/TraceLogging/EmptyStruct.cs | 17 + .../Diagnostics/Tracing/TraceLogging/EnumHelper.cs | 30 + .../Tracing/TraceLogging/EnumerableTypeInfo.cs | 64 ++ .../Tracing/TraceLogging/EventDataAttribute.cs | 146 ++++ .../Tracing/TraceLogging/EventFieldAttribute.cs | 76 ++ .../Tracing/TraceLogging/EventFieldFormat.cs | 130 +++ .../Tracing/TraceLogging/EventIgnoreAttribute.cs | 25 + .../Tracing/TraceLogging/EventPayload.cs | 155 ++++ .../Tracing/TraceLogging/EventSourceActivity.cs | 321 ++++++++ .../Tracing/TraceLogging/EventSourceOptions.cs | 130 +++ .../Tracing/TraceLogging/FieldMetadata.cs | 231 ++++++ .../Tracing/TraceLogging/InvokeTypeInfo.cs | 96 +++ .../Diagnostics/Tracing/TraceLogging/NameInfo.cs | 79 ++ .../Tracing/TraceLogging/PropertyAnalysis.cs | 39 + .../Tracing/TraceLogging/PropertyValue.cs | 252 ++++++ .../Tracing/TraceLogging/SimpleEventTypes.cs | 39 + .../Tracing/TraceLogging/SimpleTypeInfos.cs | 297 +++++++ .../Diagnostics/Tracing/TraceLogging/Statics.cs | 727 +++++++++++++++++ .../TraceLogging/TraceLoggingDataCollector.cs | 104 +++ .../Tracing/TraceLogging/TraceLoggingDataType.cs | 349 ++++++++ .../TraceLogging/TraceLoggingEventSource.cs | 890 +++++++++++++++++++++ .../TraceLogging/TraceLoggingEventTraits.cs | 28 + .../Tracing/TraceLogging/TraceLoggingEventTypes.cs | 262 ++++++ .../TraceLogging/TraceLoggingMetadataCollector.cs | 370 +++++++++ .../Tracing/TraceLogging/TraceLoggingTypeInfo.cs | 209 +++++ .../Tracing/TraceLogging/TypeAnalysis.cs | 103 +++ 30 files changed, 5702 insertions(+) create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ArrayTypeInfo.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSet.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSetItem.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/DataCollector.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EmptyStruct.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EnumHelper.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EnumerableTypeInfo.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventDataAttribute.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldAttribute.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldFormat.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventIgnoreAttribute.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventPayload.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceActivity.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceOptions.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/FieldMetadata.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/InvokeTypeInfo.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/NameInfo.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyAnalysis.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyValue.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleEventTypes.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleTypeInfos.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/Statics.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataCollector.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataType.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTraits.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTypes.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingMetadataCollector.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingTypeInfo.cs create mode 100644 src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TypeAnalysis.cs (limited to 'src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging') diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ArrayTypeInfo.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ArrayTypeInfo.cs new file mode 100644 index 0000000000..5771354f67 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ArrayTypeInfo.cs @@ -0,0 +1,63 @@ +// 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.Collections.Generic; + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + internal sealed class ArrayTypeInfo : TraceLoggingTypeInfo + { + private readonly TraceLoggingTypeInfo elementInfo; + + public ArrayTypeInfo(Type type, TraceLoggingTypeInfo elementInfo) + : base(type) + { + this.elementInfo = elementInfo; + } + + public override void WriteMetadata( + TraceLoggingMetadataCollector collector, + string name, + EventFieldFormat format) + { + collector.BeginBufferedArray(); + this.elementInfo.WriteMetadata(collector, name, format); + collector.EndBufferedArray(); + } + + public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value) + { + var bookmark = collector.BeginBufferedArray(); + + var count = 0; + Array array = (Array)value.ReferenceValue; + if (array != null) + { + count = array.Length; + for (int i = 0; i < array.Length; i++) + { + this.elementInfo.WriteData(collector, elementInfo.PropertyValueFactory(array.GetValue(i))); + } + } + + collector.EndBufferedArray(bookmark, count); + } + + public override object GetData(object value) + { + var array = (Array)value; + var serializedArray = new object[array.Length]; + for (int i = 0; i < array.Length; i++) + { + serializedArray[i] = this.elementInfo.GetData(array.GetValue(i)); + } + return serializedArray; + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSet.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSet.cs new file mode 100644 index 0000000000..76c01c6c06 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSet.cs @@ -0,0 +1,127 @@ +// 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 Interlocked = System.Threading.Interlocked; + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// TraceLogging: A very simple lock-free add-only dictionary. + /// Warning: this is a copy-by-value type. Copying performs a snapshot. + /// Accessing a readonly field always makes a copy of the field, so the + /// GetOrAdd method will not work as expected if called on a readonly field. + /// + /// + /// The type of the key, used for TryGet. + /// + /// + /// The type of the item, used for GetOrAdd. + /// + internal struct ConcurrentSet + where ItemType : ConcurrentSetItem + { + private ItemType[] items; + + public ItemType TryGet(KeyType key) + { + ItemType item; + var oldItems = this.items; + + if (oldItems != null) + { + var lo = 0; + var hi = oldItems.Length; + do + { + int i = (lo + hi) / 2; + item = oldItems[i]; + + int cmp = item.Compare(key); + if (cmp == 0) + { + goto Done; + } + else if (cmp < 0) + { + lo = i + 1; + } + else + { + hi = i; + } + } + while (lo != hi); + } + + item = null; + + Done: + + return item; + } + + public ItemType GetOrAdd(ItemType newItem) + { + ItemType item; + var oldItems = this.items; + ItemType[] newItems; + + Retry: + + if (oldItems == null) + { + newItems = new ItemType[] { newItem }; + } + else + { + var lo = 0; + var hi = oldItems.Length; + do + { + int i = (lo + hi) / 2; + item = oldItems[i]; + + int cmp = item.Compare(newItem); + if (cmp == 0) + { + goto Done; + } + else if (cmp < 0) + { + lo = i + 1; + } + else + { + hi = i; + } + } + while (lo != hi); + + int oldLength = oldItems.Length; + newItems = new ItemType[oldLength + 1]; + Array.Copy(oldItems, 0, newItems, 0, lo); + newItems[lo] = newItem; + Array.Copy(oldItems, lo, newItems, lo + 1, oldLength - lo); + } + + newItems = Interlocked.CompareExchange(ref this.items, newItems, oldItems); + if (oldItems != newItems) + { + oldItems = newItems; + goto Retry; + } + + item = newItem; + + Done: + + return item; + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSetItem.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSetItem.cs new file mode 100644 index 0000000000..558dbf670b --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSetItem.cs @@ -0,0 +1,25 @@ +// 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; + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// TraceLogging: Abstract base class that must be inherited by items in a + /// ConcurrentSet. + /// + /// Type of the set's key. + /// Type of the derived class. + internal abstract class ConcurrentSetItem + where ItemType : ConcurrentSetItem + { + public abstract int Compare(ItemType other); + public abstract int Compare(KeyType key); + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/DataCollector.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/DataCollector.cs new file mode 100644 index 0000000000..27aae820e9 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/DataCollector.cs @@ -0,0 +1,318 @@ +// 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.Runtime.InteropServices; +using System.Security; + +#if ES_BUILD_STANDALONE +using Environment = Microsoft.Diagnostics.Tracing.Internal.Environment; +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// TraceLogging: This is the implementation of the DataCollector + /// functionality. To enable safe access to the DataCollector from + /// untrusted code, there is one thread-local instance of this structure + /// per thread. The instance must be Enabled before any data is written to + /// it. The instance must be Finished before the data is passed to + /// EventWrite. The instance must be Disabled before the arrays referenced + /// by the pointers are freed or unpinned. + /// + internal unsafe struct DataCollector + { + [ThreadStatic] + internal static DataCollector ThreadInstance; + + private byte* scratchEnd; + private EventSource.EventData* datasEnd; + private GCHandle* pinsEnd; + private EventSource.EventData* datasStart; + private byte* scratch; + private EventSource.EventData* datas; + private GCHandle* pins; + private byte[] buffer; + private int bufferPos; + private int bufferNesting; // We may merge many fields int a single blob. If we are doing this we increment this. + private bool writingScalars; + + internal void Enable( + byte* scratch, + int scratchSize, + EventSource.EventData* datas, + int dataCount, + GCHandle* pins, + int pinCount) + { + this.datasStart = datas; + this.scratchEnd = scratch + scratchSize; + this.datasEnd = datas + dataCount; + this.pinsEnd = pins + pinCount; + this.scratch = scratch; + this.datas = datas; + this.pins = pins; + this.writingScalars = false; + } + + internal void Disable() + { + this = new DataCollector(); + } + + /// + /// Completes the list of scalars. Finish must be called before the data + /// descriptor array is passed to EventWrite. + /// + /// + /// A pointer to the next unused data descriptor, or datasEnd if they were + /// all used. (Descriptors may be unused if a string or array was null.) + /// + internal EventSource.EventData* Finish() + { + this.ScalarsEnd(); + return this.datas; + } + + internal void AddScalar(void* value, int size) + { + var pb = (byte*)value; + if (this.bufferNesting == 0) + { + var scratchOld = this.scratch; + var scratchNew = scratchOld + size; + if (this.scratchEnd < scratchNew) + { + throw new IndexOutOfRangeException(Resources.GetResourceString("EventSource_AddScalarOutOfRange")); + } + + this.ScalarsBegin(); + this.scratch = scratchNew; + + for (int i = 0; i != size; i++) + { + scratchOld[i] = pb[i]; + } + } + else + { + var oldPos = this.bufferPos; + this.bufferPos = checked(this.bufferPos + size); + this.EnsureBuffer(); + for (int i = 0; i != size; i++, oldPos++) + { + this.buffer[oldPos] = pb[i]; + } + } + } + + internal void AddBinary(string value, int size) + { + if (size > ushort.MaxValue) + { + size = ushort.MaxValue - 1; + } + + if (this.bufferNesting != 0) + { + this.EnsureBuffer(size + 2); + } + + this.AddScalar(&size, 2); + + if (size != 0) + { + if (this.bufferNesting == 0) + { + this.ScalarsEnd(); + this.PinArray(value, size); + } + else + { + var oldPos = this.bufferPos; + this.bufferPos = checked(this.bufferPos + size); + this.EnsureBuffer(); + fixed (void* p = value) + { + Marshal.Copy((IntPtr)p, buffer, oldPos, size); + } + } + } + } + + internal void AddBinary(Array value, int size) + { + this.AddArray(value, size, 1); + } + + internal void AddArray(Array value, int length, int itemSize) + { + if (length > ushort.MaxValue) + { + length = ushort.MaxValue; + } + + var size = length * itemSize; + if (this.bufferNesting != 0) + { + this.EnsureBuffer(size + 2); + } + + this.AddScalar(&length, 2); + + if (length != 0) + { + if (this.bufferNesting == 0) + { + this.ScalarsEnd(); + this.PinArray(value, size); + } + else + { + var oldPos = this.bufferPos; + this.bufferPos = checked(this.bufferPos + size); + this.EnsureBuffer(); + Buffer.BlockCopy(value, 0, this.buffer, oldPos, size); + } + } + } + + /// + /// Marks the start of a non-blittable array or enumerable. + /// + /// Bookmark to be passed to EndBufferedArray. + internal int BeginBufferedArray() + { + this.BeginBuffered(); + this.bufferPos += 2; // Reserve space for the array length (filled in by EndEnumerable) + return this.bufferPos; + } + + /// + /// Marks the end of a non-blittable array or enumerable. + /// + /// The value returned by BeginBufferedArray. + /// The number of items in the array. + internal void EndBufferedArray(int bookmark, int count) + { + this.EnsureBuffer(); + this.buffer[bookmark - 2] = unchecked((byte)count); + this.buffer[bookmark - 1] = unchecked((byte)(count >> 8)); + this.EndBuffered(); + } + + /// + /// Marks the start of dynamically-buffered data. + /// + internal void BeginBuffered() + { + this.ScalarsEnd(); + this.bufferNesting += 1; + } + + /// + /// Marks the end of dynamically-buffered data. + /// + internal void EndBuffered() + { + this.bufferNesting -= 1; + + if (this.bufferNesting == 0) + { + /* + TODO (perf): consider coalescing adjacent buffered regions into a + single buffer, similar to what we're already doing for adjacent + scalars. In addition, if a type contains a buffered region adjacent + to a blittable array, and the blittable array is small, it would be + more efficient to buffer the array instead of pinning it. + */ + + this.EnsureBuffer(); + this.PinArray(this.buffer, this.bufferPos); + this.buffer = null; + this.bufferPos = 0; + } + } + + private void EnsureBuffer() + { + var required = this.bufferPos; + if (this.buffer == null || this.buffer.Length < required) + { + this.GrowBuffer(required); + } + } + + private void EnsureBuffer(int additionalSize) + { + var required = this.bufferPos + additionalSize; + if (this.buffer == null || this.buffer.Length < required) + { + this.GrowBuffer(required); + } + } + + private void GrowBuffer(int required) + { + var newSize = this.buffer == null ? 64 : this.buffer.Length; + + do + { + newSize *= 2; + } + while (newSize < required); + + Array.Resize(ref this.buffer, newSize); + } + + private void PinArray(object value, int size) + { + var pinsTemp = this.pins; + if (this.pinsEnd <= pinsTemp) + { + throw new IndexOutOfRangeException(Resources.GetResourceString("EventSource_PinArrayOutOfRange")); + } + + var datasTemp = this.datas; + if (this.datasEnd <= datasTemp) + { + throw new IndexOutOfRangeException(Resources.GetResourceString("EventSource_DataDescriptorsOutOfRange")); + } + + this.pins = pinsTemp + 1; + this.datas = datasTemp + 1; + + *pinsTemp = GCHandle.Alloc(value, GCHandleType.Pinned); + datasTemp->m_Ptr = (long)(ulong)(UIntPtr)(void*)pinsTemp->AddrOfPinnedObject(); + datasTemp->m_Size = size; + } + + private void ScalarsBegin() + { + if (!this.writingScalars) + { + var datasTemp = this.datas; + if (this.datasEnd <= datasTemp) + { + throw new IndexOutOfRangeException(Resources.GetResourceString("EventSource_DataDescriptorsOutOfRange")); + } + + datasTemp->m_Ptr = (long)(ulong)(UIntPtr)this.scratch; + this.writingScalars = true; + } + } + + private void ScalarsEnd() + { + if (this.writingScalars) + { + var datasTemp = this.datas; + datasTemp->m_Size = checked((int)(this.scratch - (byte*)datasTemp->m_Ptr)); + this.datas = datasTemp + 1; + this.writingScalars = false; + } + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EmptyStruct.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EmptyStruct.cs new file mode 100644 index 0000000000..bc7fb8c346 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EmptyStruct.cs @@ -0,0 +1,17 @@ +// 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. + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// TraceLogging: Empty struct indicating no payload data. + /// + internal struct EmptyStruct + { + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EnumHelper.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EnumHelper.cs new file mode 100644 index 0000000000..7a23378bb1 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EnumHelper.cs @@ -0,0 +1,30 @@ +// 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. +#if EVENTSOURCE_GENERICS +?using System; +using System.Reflection; + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// Provides support for casting enums to their underlying type + /// from within generic context. + /// + /// + /// The underlying type of the enum. + /// + internal static class EnumHelper + { + public static UnderlyingType Cast(ValueType value) + { + return (UnderlyingType)(object)value; + } + } + +} +#endif diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EnumerableTypeInfo.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EnumerableTypeInfo.cs new file mode 100644 index 0000000000..74a3fa27b2 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EnumerableTypeInfo.cs @@ -0,0 +1,64 @@ +// 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.Collections; +using System.Collections.Generic; + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + internal sealed class EnumerableTypeInfo : TraceLoggingTypeInfo + { + private readonly TraceLoggingTypeInfo elementInfo; + + public EnumerableTypeInfo(Type type, TraceLoggingTypeInfo elementInfo) + : base(type) + { + this.elementInfo = elementInfo; + } + + public override void WriteMetadata( + TraceLoggingMetadataCollector collector, + string name, + EventFieldFormat format) + { + collector.BeginBufferedArray(); + this.elementInfo.WriteMetadata(collector, name, format); + collector.EndBufferedArray(); + } + + public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value) + { + var bookmark = collector.BeginBufferedArray(); + + var count = 0; + IEnumerable enumerable = (IEnumerable)value.ReferenceValue; + if (enumerable != null) + { + foreach (var element in enumerable) + { + this.elementInfo.WriteData(collector, elementInfo.PropertyValueFactory(element)); + count++; + } + } + + collector.EndBufferedArray(bookmark, count); + } + + public override object GetData(object value) + { + var iterType = (IEnumerable)value; + List serializedEnumerable = new List(); + foreach (var element in iterType) + { + serializedEnumerable.Add(elementInfo.GetData(element)); + } + return serializedEnumerable.ToArray(); + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventDataAttribute.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventDataAttribute.cs new file mode 100644 index 0000000000..cdedf13c64 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventDataAttribute.cs @@ -0,0 +1,146 @@ +// 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; + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// Used when authoring types that will be passed to EventSource.Write. + /// EventSource.Write<T> only works when T is either an anonymous type + /// or a type with an [EventData] attribute. In addition, the properties + /// of T must be supported property types. Supported property types include + /// simple built-in types (int, string, Guid, DateTime, DateTimeOffset, + /// KeyValuePair, etc.), anonymous types that only contain supported types, + /// types with an [EventData] attribute, arrays of the above, and IEnumerable + /// of the above. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)] + public class EventDataAttribute + : Attribute + { + private EventLevel level = (EventLevel)(-1); + private EventOpcode opcode = (EventOpcode)(-1); + + /// + /// Gets or sets the name to use if this type is used for an + /// implicitly-named event or an implicitly-named property. + /// + /// Example 1: + /// + /// EventSource.Write(null, new T()); // implicitly-named event + /// + /// The name of the event will be determined as follows: + /// + /// if (T has an EventData attribute and attribute.Name != null) + /// eventName = attribute.Name; + /// else + /// eventName = typeof(T).Name; + /// + /// Example 2: + /// + /// EventSource.Write(name, new { _1 = new T() }); // implicitly-named field + /// + /// The name of the field will be determined as follows: + /// + /// if (T has an EventData attribute and attribute.Name != null) + /// fieldName = attribute.Name; + /// else + /// fieldName = typeof(T).Name; + /// + public string Name + { + get; + set; + } + + /// + /// Gets or sets the level to use for the event. + /// Invalid levels (outside the range 0..255) are treated as unset. + /// Note that the Level attribute can bubble-up, i.e. if a type contains + /// a sub-object (a field or property), and the sub-object's type has a + /// TraceLoggingEvent attribute, the Level from the sub-object's attribute + /// can affect the event's level. + /// + /// Example: for EventSource.Write(name, options, data), the level of the + /// event will be determined as follows: + /// + /// if (options.Level has been set) + /// eventLevel = options.Level; + /// else if (data.GetType() has a TraceLoggingEvent attribute and attribute.Level has been set) + /// eventLevel = attribute.Level; + /// else if (a field/property contained in data has a TraceLoggingEvent attribute and attribute.Level has been set) + /// eventLevel = attribute.Level; + /// else + /// eventLevel = EventLevel.LogAlways; + /// + internal EventLevel Level + { + get { return this.level; } + set { this.level = value; } + } + + /// + /// Gets or sets the opcode to use for the event. + /// Invalid opcodes (outside the range 0..255) are treated as unset. + /// Note that the Opcode attribute can bubble-up, i.e. if a type contains + /// a sub-object (a field or property), and the sub-object's type has a + /// TraceLoggingEvent attribute, the Opcode from the sub-object's attribute + /// can affect the event's opcode. + /// + /// Example: for EventSource.Write(name, options, data), the opcode of the + /// event will be determined as follows: + /// + /// if (options.Opcode has been set) + /// eventOpcode = options.Opcode; + /// else if (data.GetType() has a TraceLoggingEvent attribute and attribute.Opcode has been set) + /// eventOpcode = attribute.Opcode; + /// else if (a field/property contained in data has a TraceLoggingEvent attribute and attribute.Opcode has been set) + /// eventOpcode = attribute.Opcode; + /// else + /// eventOpcode = EventOpcode.Info; + /// + internal EventOpcode Opcode + { + get { return this.opcode; } + set { this.opcode = value; } + } + + /// + /// Gets or sets the keywords to use for the event. + /// Note that the Keywords attribute can bubble-up, i.e. if a type contains + /// a sub-object (a field or property), and the sub-object's type has a + /// TraceLoggingEvent attribute, the Keywords from the sub-object's attribute + /// can affect the event's keywords. + /// + /// Example: for EventSource.Write(name, options, data), the keywords of the + /// event will be determined as follows: + /// + /// eventKeywords = options.Keywords; + /// if (data.GetType() has a TraceLoggingEvent attribute) + /// eventKeywords |= attribute.Keywords; + /// if (a field/property contained in data has a TraceLoggingEvent attribute) + /// eventKeywords |= attribute.Keywords; + /// + internal EventKeywords Keywords + { + get; + set; + } + + /// + /// Gets or sets the flags for an event. These flags are ignored by ETW, + /// but can have meaning to the event consumer. + /// + internal EventTags Tags + { + get; + set; + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldAttribute.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldAttribute.cs new file mode 100644 index 0000000000..1a298c2851 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldAttribute.cs @@ -0,0 +1,76 @@ +// 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; + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// Tags are flags that are not interpreted by EventSource but are passed along + /// to the EventListener. The EventListener determines the semantics of the flags. + /// + [Flags] + public enum EventFieldTags + { + /// + /// No special traits are added to the field. + /// + None = 0, + + /* Bits below 0x10000 are available for any use by the provider. */ + /* Bits at or above 0x10000 are reserved for definition by Microsoft. */ + } + + /// + /// TraceLogging: used when authoring types that will be passed to EventSource.Write. + /// Controls how a field or property is handled when it is written as a + /// field in a TraceLogging event. Apply this attribute to a field or + /// property if the default handling is not correct. (Apply the + /// TraceLoggingIgnore attribute if the property should not be + /// included as a field in the event.) + /// The default for Name is null, which means that the name of the + /// underlying field or property will be used as the event field's name. + /// The default for PiiTag is 0, which means that the event field does not + /// contain personally-identifiable information. + /// + [AttributeUsage(AttributeTargets.Property)] + public class EventFieldAttribute + : Attribute + { + /// + /// User defined options for the field. These are not interpreted by the EventSource + /// but are available to the Listener. See EventFieldSettings for details + /// + public EventFieldTags Tags + { + get; + set; + } + + /// + /// Gets or sets the name to use for the field. This defaults to null. + /// If null, the name of the corresponding property will be used + /// as the event field's name. + /// TODO REMOVE + /// + internal string Name + { + get; + set; + } + + /// + /// Gets or sets a field formatting hint. + /// + public EventFieldFormat Format + { + get; + set; + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldFormat.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldFormat.cs new file mode 100644 index 0000000000..fd77b07965 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldFormat.cs @@ -0,0 +1,130 @@ +// 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. + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// Provides a hint that may be used by an event listener when formatting + /// an event field for display. Note that the event listener may ignore the + /// hint if it does not recognize a particular combination of type and format. + /// Similar to TDH_OUTTYPE. + /// + public enum EventFieldFormat + { + /// + /// Field receives default formatting based on the field's underlying type. + /// + Default = 0, +#if false + /// + /// Field should not be displayed. + /// + NoPrint = 1, +#endif + /// + /// Field should be formatted as character or string data. + /// Typically applied to 8-bit or 16-bit integers. + /// This is the default format for String and Char types. + /// + String = 2, + + /// + /// Field should be formatted as boolean data. Typically applied to 8-bit + /// or 32-bit integers. This is the default format for the Boolean type. + /// + Boolean = 3, + + /// + /// Field should be formatted as hexadecimal data. Typically applied to + /// integer types. + /// + Hexadecimal = 4, + +#if false + /// + /// Field should be formatted as a process identifier. Typically applied to + /// 32-bit integer types. + /// + ProcessId = 5, + + /// + /// Field should be formatted as a thread identifier. Typically applied to + /// 32-bit integer types. + /// + ThreadId = 6, + + /// + /// Field should be formatted as an Internet port. Typically applied to 16-bit integer + /// types. + /// + Port = 7, + /// + /// Field should be formatted as an Internet Protocol v4 address. Typically applied to + /// 32-bit integer types. + /// + Ipv4Address = 8, + + /// + /// Field should be formatted as an Internet Protocol v6 address. Typically applied to + /// byte[] types. + /// + Ipv6Address = 9, + /// + /// Field should be formatted as a SOCKADDR. Typically applied to byte[] types. + /// + SocketAddress = 10, +#endif + /// + /// Field should be formatted as XML string data. Typically applied to + /// strings or arrays of 8-bit or 16-bit integers. + /// + Xml = 11, + + /// + /// Field should be formatted as JSON string data. Typically applied to + /// strings or arrays of 8-bit or 16-bit integers. + /// + Json = 12, +#if false + /// + /// Field should be formatted as a Win32 error code. Typically applied to + /// 32-bit integer types. + /// + Win32Error = 13, + + /// + /// Field should be formatted as an NTSTATUS code. Typically applied to + /// 32-bit integer types. + /// + NTStatus = 14, +#endif + /// + /// Field should be formatted as an HRESULT code. Typically applied to + /// 32-bit integer types. + /// + HResult = 15, +#if false + /// + /// Field should be formatted as a FILETIME. Typically applied to 64-bit + /// integer types. This is the default format for DateTime types. + /// + FileTime = 16, + /// + /// When applied to a numeric type, indicates that the type should be formatted + /// as a signed integer. This is the default format for signed integer types. + /// + Signed = 17, + + /// + /// When applied to a numeric type, indicates that the type should be formatted + /// as an unsigned integer. This is the default format for unsigned integer types. + /// + Unsigned = 18, +#endif + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventIgnoreAttribute.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventIgnoreAttribute.cs new file mode 100644 index 0000000000..769345f78e --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventIgnoreAttribute.cs @@ -0,0 +1,25 @@ +// 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; + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// Used when authoring types that will be passed to EventSource.Write. + /// By default, EventSource.Write will write all of an object's public + /// properties to the event payload. Apply [EventIgnore] to a public + /// property to prevent EventSource.Write from including the property in + /// the event. + /// + [AttributeUsage(AttributeTargets.Property)] + public class EventIgnoreAttribute + : Attribute + { + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventPayload.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventPayload.cs new file mode 100644 index 0000000000..5967ad6ab5 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventPayload.cs @@ -0,0 +1,155 @@ +// 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.Collections.Generic; +using System.Collections; +using System.Diagnostics; + +#if !ES_BUILD_AGAINST_DOTNET_V35 +using Contract = System.Diagnostics.Contracts.Contract; +#else +using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract; +#endif + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// EventPayload class holds the list of parameters and their corresponding values for user defined types passed to + /// EventSource APIs. + /// Preserving the order of the elements as they were found inside user defined types is the most important characteristic of this class. + /// + internal class EventPayload : IDictionary + { + internal EventPayload(List payloadNames, List payloadValues) + { + Debug.Assert(payloadNames.Count == payloadValues.Count); + + m_names = payloadNames; + m_values = payloadValues; + } + + public ICollection Keys { get { return m_names; } } + public ICollection Values { get { return m_values; } } + + public object this[string key] + { + get + { + if (key == null) + throw new System.ArgumentNullException(nameof(key)); + + int position = 0; + foreach(var name in m_names) + { + if (name == key) + { + return m_values[position]; + } + position++; + } + + throw new System.Collections.Generic.KeyNotFoundException(); + } + set + { + throw new System.NotSupportedException(); + } + } + + public void Add(string key, object value) + { + throw new System.NotSupportedException(); + } + + public void Add(KeyValuePair payloadEntry) + { + throw new System.NotSupportedException(); + } + + public void Clear() + { + throw new System.NotSupportedException(); + } + + public bool Contains(KeyValuePair entry) + { + return ContainsKey(entry.Key); + } + + public bool ContainsKey(string key) + { + if (key == null) + throw new System.ArgumentNullException(nameof(key)); + + foreach (var item in m_names) + { + if (item == key) + return true; + } + return false; + } + + public int Count { get { return m_names.Count; } } + + public bool IsReadOnly { get { return true; } } + + public IEnumerator> GetEnumerator() + { + for (int i = 0; i < Keys.Count; i++) + { + yield return new KeyValuePair(this.m_names[i], this.m_values[i]); + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + var instance = this as IEnumerable>; + return instance.GetEnumerator(); + } + + public void CopyTo(KeyValuePair[] payloadEntries, int count) + { + throw new System.NotSupportedException(); + } + + public bool Remove(string key) + { + throw new System.NotSupportedException(); + } + + public bool Remove(KeyValuePair entry) + { + throw new System.NotSupportedException(); + } + + public bool TryGetValue(string key, out object value) + { + if (key == null) + throw new System.ArgumentNullException(nameof(key)); + + int position = 0; + foreach (var name in m_names) + { + if (name == key) + { + value = m_values[position]; + return true; + } + position++; + } + + value = default(object); + return false; + } + + #region private + private List m_names; + private List m_values; + #endregion + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceActivity.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceActivity.cs new file mode 100644 index 0000000000..38c1767462 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceActivity.cs @@ -0,0 +1,321 @@ +// 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; + +#if !ES_BUILD_AGAINST_DOTNET_V35 +using Contract = System.Diagnostics.Contracts.Contract; +#else +using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract; +#endif + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// Provides support for EventSource activities by marking the start and + /// end of a particular operation. + /// + internal sealed class EventSourceActivity + : IDisposable + { + /// + /// Initializes a new instance of the EventSourceActivity class that + /// is attached to the specified event source. The new activity will + /// not be attached to any related (parent) activity. + /// The activity is created in the Initialized state. + /// + /// + /// The event source to which the activity information is written. + /// + public EventSourceActivity(EventSource eventSource) + { + if (eventSource == null) + throw new ArgumentNullException(nameof(eventSource)); + Contract.EndContractBlock(); + + this.eventSource = eventSource; + } + + /// + /// You can make an activity out of just an EventSource. + /// + public static implicit operator EventSourceActivity(EventSource eventSource) { return new EventSourceActivity(eventSource); } + + /* Properties */ + /// + /// Gets the event source to which this activity writes events. + /// + public EventSource EventSource + { + get { return this.eventSource; } + } + + /// + /// Gets this activity's unique identifier, or the default Guid if the + /// event source was disabled when the activity was initialized. + /// + public Guid Id + { + get { return this.activityId; } + } + +#if false // don't expose RelatedActivityId unless there is a need. + /// + /// Gets the unique identifier of this activity's related (parent) + /// activity. + /// + public Guid RelatedId + { + get { return this.relatedActivityId; } + } +#endif + + /// + /// Writes a Start event with the specified name and data. If the start event is not active (because the provider + /// is not on or keyword-level indiates the event is off, then the returned activity is simply the 'this' poitner + /// and it is effectively like the Start d + /// + /// A new activityID GUID is generated and the returned + /// EventSourceActivity remembers this activity and will mark every event (including the start stop and any writes) + /// with this activityID. In addition the Start activity will log a 'relatedActivityID' that was the activity + /// ID before the start event. This way event processors can form a linked list of all the activities that + /// caused this one (directly or indirectly). + /// + /// + /// The name to use for the event. It is strongly suggested that this name end in 'Start' (e.g. DownloadStart). + /// If you do this, then the Stop() method will automatically replace the 'Start' suffix with a 'Stop' suffix. + /// + /// Allow options (keywords, level) to be set for the write associated with this start + /// These will also be used for the stop event. + /// The data to include in the event. + public EventSourceActivity Start(string eventName, EventSourceOptions options, T data) + { + return this.Start(eventName, ref options, ref data); + } + /// + /// Shortcut version see Start(string eventName, EventSourceOptions options, T data) Options is empty (no keywords + /// and level==Info) Data payload is empty. + /// + public EventSourceActivity Start(string eventName) + { + var options = new EventSourceOptions(); + var data = new EmptyStruct(); + return this.Start(eventName, ref options, ref data); + } + /// + /// Shortcut version see Start(string eventName, EventSourceOptions options, T data). Data payload is empty. + /// + public EventSourceActivity Start(string eventName, EventSourceOptions options) + { + var data = new EmptyStruct(); + return this.Start(eventName, ref options, ref data); + } + /// + /// Shortcut version see Start(string eventName, EventSourceOptions options, T data) Options is empty (no keywords + /// and level==Info) + /// + public EventSourceActivity Start(string eventName, T data) + { + var options = new EventSourceOptions(); + return this.Start(eventName, ref options, ref data); + } + + /// + /// Writes a Stop event with the specified data, and sets the activity + /// to the Stopped state. The name is determined by the eventName used in Start. + /// If that Start event name is suffixed with 'Start' that is removed, and regardless + /// 'Stop' is appended to the result to form the Stop event name. + /// May only be called when the activity is in the Started state. + /// + /// The data to include in the event. + public void Stop(T data) + { + this.Stop(null, ref data); + } + /// + /// Used if you wish to use the non-default stop name (which is the start name with Start replace with 'Stop') + /// This can be useful to indicate unusual ways of stoping (but it is still STRONGLY recommeded that + /// you start with the same prefix used for the start event and you end with the 'Stop' suffix. + /// + public void Stop(string eventName) + { + var data = new EmptyStruct(); + this.Stop(eventName, ref data); + } + /// + /// Used if you wish to use the non-default stop name (which is the start name with Start replace with 'Stop') + /// This can be useful to indicate unusual ways of stoping (but it is still STRONGLY recommeded that + /// you start with the same prefix used for the start event and you end with the 'Stop' suffix. + /// + public void Stop(string eventName, T data) + { + this.Stop(eventName, ref data); + } + + /// + /// Writes an event associated with this activity to the eventSource associted with this activity. + /// May only be called when the activity is in the Started state. + /// + /// + /// The name to use for the event. If null, the name is determined from + /// data's type. + /// + /// + /// The options to use for the event. + /// + /// The data to include in the event. + public void Write(string eventName, EventSourceOptions options, T data) + { + this.Write(this.eventSource, eventName, ref options, ref data); + } + /// + /// Writes an event associated with this activity. + /// May only be called when the activity is in the Started state. + /// + /// + /// The name to use for the event. If null, the name is determined from + /// data's type. + /// + /// The data to include in the event. + public void Write(string eventName, T data) + { + var options = new EventSourceOptions(); + this.Write(this.eventSource, eventName, ref options, ref data); + } + /// + /// Writes a trivial event associated with this activity. + /// May only be called when the activity is in the Started state. + /// + /// + /// The name to use for the event. Must not be null. + /// + /// + /// The options to use for the event. + /// + public void Write(string eventName, EventSourceOptions options) + { + var data = new EmptyStruct(); + this.Write(this.eventSource, eventName, ref options, ref data); + } + /// + /// Writes a trivial event associated with this activity. + /// May only be called when the activity is in the Started state. + /// + /// + /// The name to use for the event. Must not be null. + /// + public void Write(string eventName) + { + var options = new EventSourceOptions(); + var data = new EmptyStruct(); + this.Write(this.eventSource, eventName, ref options, ref data); + } + /// + /// Writes an event to a arbitrary eventSource stamped with the activity ID of this activity. + /// + public void Write(EventSource source, string eventName, EventSourceOptions options, T data) + { + this.Write(source, eventName, ref options, ref data); + } + + /// + /// Releases any unmanaged resources associated with this object. + /// If the activity is in the Started state, calls Stop(). + /// + public void Dispose() + { + if (this.state == State.Started) + { + var data = new EmptyStruct(); + this.Stop(null, ref data); + } + } + + #region private + private EventSourceActivity Start(string eventName, ref EventSourceOptions options, ref T data) + { + if (this.state != State.Started) + throw new InvalidOperationException(); + + // If the source is not on at all, then we don't need to do anything and we can simply return ourselves. + if (!this.eventSource.IsEnabled()) + return this; + + var newActivity = new EventSourceActivity(eventSource); + if (!this.eventSource.IsEnabled(options.Level, options.Keywords)) + { + // newActivity.relatedActivityId = this.Id; + Guid relatedActivityId = this.Id; + newActivity.activityId = Guid.NewGuid(); + newActivity.startStopOptions = options; + newActivity.eventName = eventName; + newActivity.startStopOptions.Opcode = EventOpcode.Start; + this.eventSource.Write(eventName, ref newActivity.startStopOptions, ref newActivity.activityId, ref relatedActivityId, ref data); + } + else + { + // If we are not active, we don't set the eventName, which basically also turns off the Stop event as well. + newActivity.activityId = this.Id; + } + + return newActivity; + } + + private void Write(EventSource eventSource, string eventName, ref EventSourceOptions options, ref T data) + { + if (this.state != State.Started) + throw new InvalidOperationException(); // Write after stop. + if (eventName == null) + throw new ArgumentNullException(); + + eventSource.Write(eventName, ref options, ref this.activityId, ref s_empty, ref data); + } + + private void Stop(string eventName, ref T data) + { + if (this.state != State.Started) + throw new InvalidOperationException(); + + // If start was not fired, then stop isn't as well. + if (!StartEventWasFired) + return; + + this.state = State.Stopped; + if (eventName == null) + { + eventName = this.eventName; + if (eventName.EndsWith("Start")) + eventName = eventName.Substring(0, eventName.Length - 5); + eventName = eventName + "Stop"; + } + this.startStopOptions.Opcode = EventOpcode.Stop; + this.eventSource.Write(eventName, ref this.startStopOptions, ref this.activityId, ref s_empty, ref data); + } + + private enum State + { + Started, + Stopped + } + + /// + /// If eventName is non-null then we logged a start event + /// + private bool StartEventWasFired { get { return eventName != null; } } + + private readonly EventSource eventSource; + private EventSourceOptions startStopOptions; + internal Guid activityId; + // internal Guid relatedActivityId; + private State state; + private string eventName; + + static internal Guid s_empty; + #endregion + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceOptions.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceOptions.cs new file mode 100644 index 0000000000..26305a5708 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceOptions.cs @@ -0,0 +1,130 @@ +// 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; + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// Used when calling EventSource.Write. + /// Optional overrides for event settings such as Level, Keywords, or Opcode. + /// If overrides are not provided for a setting, default values will be used. + /// + public struct EventSourceOptions + { + internal EventKeywords keywords; + internal EventTags tags; + internal EventActivityOptions activityOptions; + internal byte level; + internal byte opcode; + internal byte valuesSet; + + internal const byte keywordsSet = 0x1; + internal const byte tagsSet = 0x2; + internal const byte levelSet = 0x4; + internal const byte opcodeSet = 0x8; + internal const byte activityOptionsSet = 0x10; + + /// + /// Gets or sets the level to use for the specified event. If this property + /// is unset, the event's level will be 5 (Verbose). + /// + public EventLevel Level + { + get + { + return (EventLevel)this.level; + } + + set + { + this.level = checked((byte)value); + this.valuesSet |= levelSet; + } + } + + /// + /// Gets or sets the opcode to use for the specified event. If this property + /// is unset, the event's opcode will 0 (Info). + /// + public EventOpcode Opcode + { + get + { + return (EventOpcode)this.opcode; + } + + set + { + this.opcode = checked((byte)value); + this.valuesSet |= opcodeSet; + } + } + + internal bool IsOpcodeSet + { + get + { + return (this.valuesSet & opcodeSet) != 0; + } + } + + /// + /// Gets or sets the keywords to use for the specified event. If this + /// property is unset, the event's keywords will be 0. + /// + public EventKeywords Keywords + { + get + { + return this.keywords; + } + + set + { + this.keywords = value; + this.valuesSet |= keywordsSet; + } + } + + /// + /// Gets or sets the tags to use for the specified event. If this property is + /// unset, the event's tags will be 0. + /// + public EventTags Tags + { + get + { + return this.tags; + } + + set + { + this.tags = value; + this.valuesSet |= tagsSet; + } + } + + /// + /// Gets or sets the activity options for this specified events. If this property is + /// unset, the event's activity options will be 0. + /// + public EventActivityOptions ActivityOptions + { + get + { + return this.activityOptions; + } + set + { + this.activityOptions = value; + this.valuesSet |= activityOptionsSet; + } + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/FieldMetadata.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/FieldMetadata.cs new file mode 100644 index 0000000000..309226b84d --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/FieldMetadata.cs @@ -0,0 +1,231 @@ +// 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 Encoding = System.Text.Encoding; + +#if ES_BUILD_STANDALONE +using Environment = Microsoft.Diagnostics.Tracing.Internal.Environment; +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// TraceLogging: Contains the information needed to generate tracelogging + /// metadata for an event field. + /// + internal class FieldMetadata + { + /// + /// Name of the field + /// + private readonly string name; + + /// + /// The number of bytes in the UTF8 Encoding of 'name' INCLUDING a null terminator. + /// + private readonly int nameSize; + private readonly EventFieldTags tags; + private readonly byte[] custom; + + /// + /// ETW supports fixed sized arrays. If inType has the InTypeFixedCountFlag then this is the + /// statically known count for the array. It is also used to encode the number of bytes of + /// custom meta-data if InTypeCustomCountFlag set. + /// + private readonly ushort fixedCount; + + private byte inType; + private byte outType; + + /// + /// Scalar or variable-length array. + /// + public FieldMetadata( + string name, + TraceLoggingDataType type, + EventFieldTags tags, + bool variableCount) + : this( + name, + type, + tags, + variableCount ? Statics.InTypeVariableCountFlag : (byte)0, + 0, + null) + { + return; + } + + /// + /// Fixed-length array. + /// + public FieldMetadata( + string name, + TraceLoggingDataType type, + EventFieldTags tags, + ushort fixedCount) + : this( + name, + type, + tags, + Statics.InTypeFixedCountFlag, + fixedCount, + null) + { + return; + } + + /// + /// Custom serializer + /// + public FieldMetadata( + string name, + TraceLoggingDataType type, + EventFieldTags tags, + byte[] custom) + : this( + name, + type, + tags, + Statics.InTypeCustomCountFlag, + checked((ushort)(custom == null ? 0 : custom.Length)), + custom) + { + return; + } + + private FieldMetadata( + string name, + TraceLoggingDataType dataType, + EventFieldTags tags, + byte countFlags, + ushort fixedCount = 0, + byte[] custom = null) + { + if (name == null) + { + throw new ArgumentNullException( + nameof(name), + "This usually means that the object passed to Write is of a type that" + + " does not support being used as the top-level object in an event," + + " e.g. a primitive or built-in type."); + } + + Statics.CheckName(name); + var coreType = (int)dataType & Statics.InTypeMask; + this.name = name; + this.nameSize = Encoding.UTF8.GetByteCount(this.name) + 1; + this.inType = (byte)(coreType | countFlags); + this.outType = (byte)(((int)dataType >> 8) & Statics.OutTypeMask); + this.tags = tags; + this.fixedCount = fixedCount; + this.custom = custom; + + if (countFlags != 0) + { + if (coreType == (int)TraceLoggingDataType.Nil) + { + throw new NotSupportedException(Resources.GetResourceString("EventSource_NotSupportedArrayOfNil")); + } + if (coreType == (int)TraceLoggingDataType.Binary) + { + throw new NotSupportedException(Resources.GetResourceString("EventSource_NotSupportedArrayOfBinary")); + } +#if !BROKEN_UNTIL_M3 + if (coreType == (int)TraceLoggingDataType.Utf16String || + coreType == (int)TraceLoggingDataType.MbcsString) + { + throw new NotSupportedException(Resources.GetResourceString("EventSource_NotSupportedArrayOfNullTerminatedString")); + } +#endif + } + + if (((int)this.tags & 0xfffffff) != 0) + { + this.outType |= Statics.OutTypeChainFlag; + } + + if (this.outType != 0) + { + this.inType |= Statics.InTypeChainFlag; + } + } + + public void IncrementStructFieldCount() + { + this.inType |= Statics.InTypeChainFlag; + this.outType++; + if ((this.outType & Statics.OutTypeMask) == 0) + { + throw new NotSupportedException(Resources.GetResourceString("EventSource_TooManyFields")); + } + } + + /// + /// This is the main routine for FieldMetaData. Basically it will serialize the data in + /// this structure as TraceLogging style meta-data into the array 'metaArray' starting at + /// 'pos' (pos is updated to reflect the bytes written). + /// + /// Note that 'metaData' can be null, in which case it only updates 'pos'. This is useful + /// for a 'two pass' approach where you figure out how big to make the array, and then you + /// fill it in. + /// + public void Encode(ref int pos, byte[] metadata) + { + // Write out the null terminated UTF8 encoded name + if (metadata != null) + { + Encoding.UTF8.GetBytes(this.name, 0, this.name.Length, metadata, pos); + } + pos += this.nameSize; + + // Write 1 byte for inType + if (metadata != null) + { + metadata[pos] = this.inType; + } + pos += 1; + + // If InTypeChainFlag set, then write out the outType + if (0 != (this.inType & Statics.InTypeChainFlag)) + { + if (metadata != null) + { + metadata[pos] = this.outType; + } + pos += 1; + + // If OutTypeChainFlag set, then write out tags + if (0 != (this.outType & Statics.OutTypeChainFlag)) + { + Statics.EncodeTags((int)this.tags, ref pos, metadata); + } + } + + // If InTypeFixedCountFlag set, write out the fixedCount (2 bytes little endian) + if (0 != (this.inType & Statics.InTypeFixedCountFlag)) + { + if (metadata != null) + { + metadata[pos + 0] = unchecked((byte)this.fixedCount); + metadata[pos + 1] = (byte)(this.fixedCount >> 8); + } + pos += 2; + + // If InTypeCustomCountFlag set, write out the blob of custom meta-data. + if (Statics.InTypeCustomCountFlag == (this.inType & Statics.InTypeCountMask) && + this.fixedCount != 0) + { + if (metadata != null) + { + Buffer.BlockCopy(this.custom, 0, metadata, pos, this.fixedCount); + } + pos += this.fixedCount; + } + } + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/InvokeTypeInfo.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/InvokeTypeInfo.cs new file mode 100644 index 0000000000..3e5997bc9b --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/InvokeTypeInfo.cs @@ -0,0 +1,96 @@ +// 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.Collections.Generic; + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// TraceLogging: An implementation of TraceLoggingTypeInfo that works + /// for arbitrary types. It writes all public instance properties of + /// the type. + /// + /// + /// Type from which to read values. + /// + internal sealed class InvokeTypeInfo : TraceLoggingTypeInfo + { + private readonly PropertyAnalysis[] properties; + + public InvokeTypeInfo( + Type type, + TypeAnalysis typeAnalysis) + : base( + type, + typeAnalysis.name, + typeAnalysis.level, + typeAnalysis.opcode, + typeAnalysis.keywords, + typeAnalysis.tags) + { + if (typeAnalysis.properties.Length != 0) + this.properties = typeAnalysis.properties; + } + + public override void WriteMetadata( + TraceLoggingMetadataCollector collector, + string name, + EventFieldFormat format) + { + var groupCollector = collector.AddGroup(name); + if (this.properties != null) + { + foreach (var property in this.properties) + { + var propertyFormat = EventFieldFormat.Default; + var propertyAttribute = property.fieldAttribute; + if (propertyAttribute != null) + { + groupCollector.Tags = propertyAttribute.Tags; + propertyFormat = propertyAttribute.Format; + } + + property.typeInfo.WriteMetadata( + groupCollector, + property.name, + propertyFormat); + } + } + } + + public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value) + { + if (this.properties != null) + { + foreach (var property in this.properties) + { + property.typeInfo.WriteData(collector, property.getter(value)); + } + } + } + + public override object GetData(object value) + { + if (this.properties != null) + { + var membersNames = new List(); + var memebersValues = new List(); + for (int i = 0; i < this.properties.Length; i++) + { + var propertyValue = properties[i].propertyInfo.GetValue(value); + membersNames.Add(properties[i].name); + memebersValues.Add(properties[i].typeInfo.GetData(propertyValue)); + } + return new EventPayload(membersNames, memebersValues); + } + + return null; + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/NameInfo.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/NameInfo.cs new file mode 100644 index 0000000000..668043ae68 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/NameInfo.cs @@ -0,0 +1,79 @@ +// 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.Collections.Generic; +using Interlocked = System.Threading.Interlocked; + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// TraceLogging: Stores the metadata and event identifier corresponding + /// to a tracelogging event type+name+tags combination. + /// + internal sealed class NameInfo + : ConcurrentSetItem, NameInfo> + { + /// + /// Insure that eventIds strictly less than 'eventId' will not be + /// used by the SelfDescribing events. + /// + internal static void ReserveEventIDsBelow(int eventId) + { + for(;;) + { + int snapshot = lastIdentity; + int newIdentity = (lastIdentity & ~0xFFFFFF) + eventId; + newIdentity = Math.Max(newIdentity, snapshot); // Should be redundant. as we only create descriptors once. + if (Interlocked.CompareExchange(ref lastIdentity, newIdentity, snapshot) == snapshot) + break; + } + } + + private static int lastIdentity = Statics.TraceLoggingChannel << 24; + internal readonly string name; + internal readonly EventTags tags; + internal readonly int identity; + internal readonly byte[] nameMetadata; + + public NameInfo(string name, EventTags tags, int typeMetadataSize) + { + this.name = name; + this.tags = tags & Statics.EventTagsMask; + this.identity = Interlocked.Increment(ref lastIdentity); + + int tagsPos = 0; + Statics.EncodeTags((int)this.tags, ref tagsPos, null); + + this.nameMetadata = Statics.MetadataForString(name, tagsPos, 0, typeMetadataSize); + + tagsPos = 2; + Statics.EncodeTags((int)this.tags, ref tagsPos, this.nameMetadata); + } + + public override int Compare(NameInfo other) + { + return this.Compare(other.name, other.tags); + } + + public override int Compare(KeyValuePair key) + { + return this.Compare(key.Key, key.Value & Statics.EventTagsMask); + } + + private int Compare(string otherName, EventTags otherTags) + { + int result = StringComparer.Ordinal.Compare(this.name, otherName); + if (result == 0 && this.tags != otherTags) + { + result = this.tags < otherTags ? -1 : 1; + } + return result; + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyAnalysis.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyAnalysis.cs new file mode 100644 index 0000000000..1f07539b52 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyAnalysis.cs @@ -0,0 +1,39 @@ +// 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; + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// TraceLogging: stores the per-property information obtained by + /// reflecting over a type. + /// + internal sealed class PropertyAnalysis + { + internal readonly string name; + internal readonly PropertyInfo propertyInfo; + internal readonly Func getter; + internal readonly TraceLoggingTypeInfo typeInfo; + internal readonly EventFieldAttribute fieldAttribute; + + public PropertyAnalysis( + string name, + PropertyInfo propertyInfo, + TraceLoggingTypeInfo typeInfo, + EventFieldAttribute fieldAttribute) + { + this.name = name; + this.propertyInfo = propertyInfo; + this.getter = PropertyValue.GetPropertyGetter(propertyInfo); + this.typeInfo = typeInfo; + this.fieldAttribute = fieldAttribute; + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyValue.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyValue.cs new file mode 100644 index 0000000000..3ea781252f --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyValue.cs @@ -0,0 +1,252 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using System.Diagnostics; + +#if !ES_BUILD_AGAINST_DOTNET_V35 +using Contract = System.Diagnostics.Contracts.Contract; +#else +using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract; +#endif + +namespace System.Diagnostics.Tracing +{ + /// + /// Holds property values of any type. For common value types, we have inline storage so that we don't need + /// to box the values. For all other types, we store the value in a single object reference field. + /// + /// To get the value of a property quickly, use a delegate produced by . + /// + internal unsafe struct PropertyValue + { + /// + /// Union of well-known value types, to avoid boxing those types. + /// + [StructLayout(LayoutKind.Explicit)] + public struct Scalar + { + [FieldOffset(0)] + public Boolean AsBoolean; + [FieldOffset(0)] + public Byte AsByte; + [FieldOffset(0)] + public SByte AsSByte; + [FieldOffset(0)] + public Char AsChar; + [FieldOffset(0)] + public Int16 AsInt16; + [FieldOffset(0)] + public UInt16 AsUInt16; + [FieldOffset(0)] + public Int32 AsInt32; + [FieldOffset(0)] + public UInt32 AsUInt32; + [FieldOffset(0)] + public Int64 AsInt64; + [FieldOffset(0)] + public UInt64 AsUInt64; + [FieldOffset(0)] + public IntPtr AsIntPtr; + [FieldOffset(0)] + public UIntPtr AsUIntPtr; + [FieldOffset(0)] + public Single AsSingle; + [FieldOffset(0)] + public Double AsDouble; + [FieldOffset(0)] + public Guid AsGuid; + [FieldOffset(0)] + public DateTime AsDateTime; + [FieldOffset(0)] + public DateTimeOffset AsDateTimeOffset; + [FieldOffset(0)] + public TimeSpan AsTimeSpan; + [FieldOffset(0)] + public Decimal AsDecimal; + } + + // Anything not covered by the Scalar union gets stored in this reference. + readonly object _reference; + readonly Scalar _scalar; + readonly int _scalarLength; + + private PropertyValue(object value) + { + _reference = value; + _scalar = default(Scalar); + _scalarLength = 0; + } + + private PropertyValue(Scalar scalar, int scalarLength) + { + _reference = null; + _scalar = scalar; + _scalarLength = scalarLength; + } + + private PropertyValue(Boolean value) : this(new Scalar() { AsBoolean = value }, sizeof(Boolean)) { } + private PropertyValue(Byte value) : this(new Scalar() { AsByte = value }, sizeof(Byte)) { } + private PropertyValue(SByte value) : this(new Scalar() { AsSByte = value }, sizeof(SByte)) { } + private PropertyValue(Char value) : this(new Scalar() { AsChar = value }, sizeof(Char)) { } + private PropertyValue(Int16 value) : this(new Scalar() { AsInt16 = value }, sizeof(Int16)) { } + private PropertyValue(UInt16 value) : this(new Scalar() { AsUInt16 = value }, sizeof(UInt16)) { } + private PropertyValue(Int32 value) : this(new Scalar() { AsInt32 = value }, sizeof(Int32)) { } + private PropertyValue(UInt32 value) : this(new Scalar() { AsUInt32 = value }, sizeof(UInt32)) { } + private PropertyValue(Int64 value) : this(new Scalar() { AsInt64 = value }, sizeof(Int64)) { } + private PropertyValue(UInt64 value) : this(new Scalar() { AsUInt64 = value }, sizeof(UInt64)) { } + private PropertyValue(IntPtr value) : this(new Scalar() { AsIntPtr = value }, sizeof(IntPtr)) { } + private PropertyValue(UIntPtr value) : this(new Scalar() { AsUIntPtr = value }, sizeof(UIntPtr)) { } + private PropertyValue(Single value) : this(new Scalar() { AsSingle = value }, sizeof(Single)) { } + private PropertyValue(Double value) : this(new Scalar() { AsDouble = value }, sizeof(Double)) { } + private PropertyValue(Guid value) : this(new Scalar() { AsGuid = value }, sizeof(Guid)) { } + private PropertyValue(DateTime value) : this(new Scalar() { AsDateTime = value }, sizeof(DateTime)) { } + private PropertyValue(DateTimeOffset value) : this(new Scalar() { AsDateTimeOffset = value }, sizeof(DateTimeOffset)) { } + private PropertyValue(TimeSpan value) : this(new Scalar() { AsTimeSpan = value }, sizeof(TimeSpan)) { } + private PropertyValue(Decimal value) : this(new Scalar() { AsDecimal = value }, sizeof(Decimal)) { } + + public static Func GetFactory(Type type) + { + if (type == typeof(Boolean)) return value => new PropertyValue((Boolean)value); + if (type == typeof(Byte)) return value => new PropertyValue((Byte)value); + if (type == typeof(SByte)) return value => new PropertyValue((SByte)value); + if (type == typeof(Char)) return value => new PropertyValue((Char)value); + if (type == typeof(Int16)) return value => new PropertyValue((Int16)value); + if (type == typeof(UInt16)) return value => new PropertyValue((UInt16)value); + if (type == typeof(Int32)) return value => new PropertyValue((Int32)value); + if (type == typeof(UInt32)) return value => new PropertyValue((UInt32)value); + if (type == typeof(Int64)) return value => new PropertyValue((Int64)value); + if (type == typeof(UInt64)) return value => new PropertyValue((UInt64)value); + if (type == typeof(IntPtr)) return value => new PropertyValue((IntPtr)value); + if (type == typeof(UIntPtr)) return value => new PropertyValue((UIntPtr)value); + if (type == typeof(Single)) return value => new PropertyValue((Single)value); + if (type == typeof(Double)) return value => new PropertyValue((Double)value); + if (type == typeof(Guid)) return value => new PropertyValue((Guid)value); + if (type == typeof(DateTime)) return value => new PropertyValue((DateTime)value); + if (type == typeof(DateTimeOffset)) return value => new PropertyValue((DateTimeOffset)value); + if (type == typeof(TimeSpan)) return value => new PropertyValue((TimeSpan)value); + if (type == typeof(Decimal)) return value => new PropertyValue((Decimal)value); + + return value => new PropertyValue(value); + } + + + public object ReferenceValue + { + get + { + Debug.Assert(_scalarLength == 0, "This ReflectedValue refers to an unboxed value type, not a reference type or boxed value type."); + return _reference; + } + } + + public Scalar ScalarValue + { + get + { + Debug.Assert(_scalarLength > 0, "This ReflectedValue refers to a reference type or boxed value type, not an unboxed value type"); + return _scalar; + } + } + + public int ScalarLength + { + get + { + Debug.Assert(_scalarLength > 0, "This ReflectedValue refers to a reference type or boxed value type, not an unboxed value type"); + return _scalarLength; + } + } + + /// + /// Gets a delegate that gets the value of a given property. + /// + public static Func GetPropertyGetter(PropertyInfo property) + { + if (property.DeclaringType.GetTypeInfo().IsValueType) + return GetBoxedValueTypePropertyGetter(property); + else + return GetReferenceTypePropertyGetter(property); + } + + /// + /// Gets a delegate that gets the value of a property of a value type. We unfortunately cannot avoid boxing the value type, + /// without making this generic over the value type. That would result in a large number of generic instantiations, and furthermore + /// does not work correctly on .Net Native (we cannot express the needed instantiations in an rd.xml file). We expect that user-defined + /// value types will be rare, and in any case the boxing only happens for events that are actually enabled. + /// + private static Func GetBoxedValueTypePropertyGetter(PropertyInfo property) + { + var type = property.PropertyType; + + if (type.GetTypeInfo().IsEnum) + type = Enum.GetUnderlyingType(type); + + var factory = GetFactory(type); + + return container => factory(property.GetValue(container.ReferenceValue)); + } + + /// + /// For properties of reference types, we use a generic helper class to get the value. This enables us to use MethodInfo.CreateDelegate + /// to build a fast getter. We can get away with this on .Net Native, because we really only need one runtime instantiation of the + /// generic type, since it's only instantiated over reference types (and thus all instances are shared). + /// + /// + /// + private static Func GetReferenceTypePropertyGetter(PropertyInfo property) + { + var helper = (TypeHelper)Activator.CreateInstance(typeof(ReferenceTypeHelper<>).MakeGenericType(property.DeclaringType)); + return helper.GetPropertyGetter(property); + } + + private abstract class TypeHelper + { + public abstract Func GetPropertyGetter(PropertyInfo property); + + protected Delegate GetGetMethod(PropertyInfo property, Type propertyType) + { + return property.GetMethod.CreateDelegate(typeof(Func<,>).MakeGenericType(property.DeclaringType, propertyType)); + } + } + + private sealed class ReferenceTypeHelper : TypeHelper where TContainer : class + { + public override Func GetPropertyGetter(PropertyInfo property) + { + var type = property.PropertyType; + + if (!Statics.IsValueType(type)) + { + var getter = (Func)GetGetMethod(property, type); + return container => new PropertyValue(getter((TContainer)container.ReferenceValue)); + } + else + { + if (type.GetTypeInfo().IsEnum) + type = Enum.GetUnderlyingType(type); + + if (type == typeof(Boolean)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + if (type == typeof(Byte)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + if (type == typeof(SByte)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + if (type == typeof(Char)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + if (type == typeof(Int16)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + if (type == typeof(UInt16)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + if (type == typeof(Int32)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + if (type == typeof(UInt32)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + if (type == typeof(Int64)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + if (type == typeof(UInt64)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + if (type == typeof(IntPtr)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + if (type == typeof(UIntPtr)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + if (type == typeof(Single)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + if (type == typeof(Double)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + if (type == typeof(Guid)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + if (type == typeof(DateTime)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + if (type == typeof(DateTimeOffset)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + if (type == typeof(TimeSpan)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + if (type == typeof(Decimal)) { var f = (Func)GetGetMethod(property, type); return container => new PropertyValue(f((TContainer)container.ReferenceValue)); } + + return container => new PropertyValue(property.GetValue(container.ReferenceValue)); + } + } + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleEventTypes.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleEventTypes.cs new file mode 100644 index 0000000000..cdced968f0 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleEventTypes.cs @@ -0,0 +1,39 @@ +// 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 Interlocked = System.Threading.Interlocked; + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// TraceLogging: Contains the metadata needed to emit an event, optimized + /// for events with one top-level compile-time-typed payload object. + /// + /// + /// Type of the top-level payload object. Should be EmptyStruct if the + /// event has no payload. + /// + internal static class SimpleEventTypes + { + private static TraceLoggingEventTypes instance; + + public static TraceLoggingEventTypes Instance + { + get { return instance ?? InitInstance(); } + } + + private static TraceLoggingEventTypes InitInstance() + { + var info = TraceLoggingTypeInfo.GetInstance(typeof(T), null); + var newInstance = new TraceLoggingEventTypes(info.Name, info.Tags, new TraceLoggingTypeInfo[] { info }); + Interlocked.CompareExchange(ref instance, newInstance, null); + return instance; + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleTypeInfos.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleTypeInfos.cs new file mode 100644 index 0000000000..901a0ed1a2 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleTypeInfos.cs @@ -0,0 +1,297 @@ +// 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.Collections.Generic; +using System.Reflection; +using System.Diagnostics; + +#if !ES_BUILD_AGAINST_DOTNET_V35 +using Contract = System.Diagnostics.Contracts.Contract; +#else +using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract; +#endif + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// TraceLogging: Type handler for empty or unsupported types. + /// + internal sealed class NullTypeInfo : TraceLoggingTypeInfo + { + public NullTypeInfo() : base(typeof(EmptyStruct)) { } + + public override void WriteMetadata( + TraceLoggingMetadataCollector collector, + string name, + EventFieldFormat format) + { + collector.AddGroup(name); + } + + public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value) + { + return; + } + + public override object GetData(object value) + { + return null; + } + } + + /// + /// Type handler for simple scalar types. + /// + sealed class ScalarTypeInfo : TraceLoggingTypeInfo + { + Func formatFunc; + TraceLoggingDataType nativeFormat; + + private ScalarTypeInfo( + Type type, + Func formatFunc, + TraceLoggingDataType nativeFormat) + : base(type) + { + this.formatFunc = formatFunc; + this.nativeFormat = nativeFormat; + } + + public override void WriteMetadata(TraceLoggingMetadataCollector collector, string name, EventFieldFormat format) + { + collector.AddScalar(name, formatFunc(format, nativeFormat)); + } + + public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value) + { + collector.AddScalar(value); + } + + public static TraceLoggingTypeInfo Boolean() { return new ScalarTypeInfo(typeof(Boolean), Statics.Format8, TraceLoggingDataType.Boolean8); } + public static TraceLoggingTypeInfo Byte() { return new ScalarTypeInfo(typeof(Byte), Statics.Format8, TraceLoggingDataType.UInt8); } + public static TraceLoggingTypeInfo SByte() { return new ScalarTypeInfo(typeof(SByte), Statics.Format8, TraceLoggingDataType.Int8); } + public static TraceLoggingTypeInfo Char() { return new ScalarTypeInfo(typeof(Char), Statics.Format16, TraceLoggingDataType.Char16); } + public static TraceLoggingTypeInfo Int16() { return new ScalarTypeInfo(typeof(Int16), Statics.Format16, TraceLoggingDataType.Int16); } + public static TraceLoggingTypeInfo UInt16() { return new ScalarTypeInfo(typeof(UInt16), Statics.Format16, TraceLoggingDataType.UInt16); } + public static TraceLoggingTypeInfo Int32() { return new ScalarTypeInfo(typeof(Int32), Statics.Format32, TraceLoggingDataType.Int32); } + public static TraceLoggingTypeInfo UInt32() { return new ScalarTypeInfo(typeof(UInt32), Statics.Format32, TraceLoggingDataType.UInt32); } + public static TraceLoggingTypeInfo Int64() { return new ScalarTypeInfo(typeof(Int64), Statics.Format64, TraceLoggingDataType.Int64); } + public static TraceLoggingTypeInfo UInt64() { return new ScalarTypeInfo(typeof(UInt64), Statics.Format64, TraceLoggingDataType.UInt64); } + public static TraceLoggingTypeInfo IntPtr() { return new ScalarTypeInfo(typeof(IntPtr), Statics.FormatPtr, Statics.IntPtrType); } + public static TraceLoggingTypeInfo UIntPtr() { return new ScalarTypeInfo(typeof(UIntPtr), Statics.FormatPtr, Statics.UIntPtrType); } + public static TraceLoggingTypeInfo Single() { return new ScalarTypeInfo(typeof(Single), Statics.Format32, TraceLoggingDataType.Float); } + public static TraceLoggingTypeInfo Double() { return new ScalarTypeInfo(typeof(Double), Statics.Format64, TraceLoggingDataType.Double); } + public static TraceLoggingTypeInfo Guid() { return new ScalarTypeInfo(typeof(Guid), (f, t) => Statics.MakeDataType(TraceLoggingDataType.Guid, f), TraceLoggingDataType.Guid); } + } + + + /// + /// Type handler for arrays of scalars + /// + internal sealed class ScalarArrayTypeInfo : TraceLoggingTypeInfo + { + Func formatFunc; + TraceLoggingDataType nativeFormat; + int elementSize; + + private ScalarArrayTypeInfo( + Type type, + Func formatFunc, + TraceLoggingDataType nativeFormat, + int elementSize) + : base(type) + { + this.formatFunc = formatFunc; + this.nativeFormat = nativeFormat; + this.elementSize = elementSize; + } + + public override void WriteMetadata(TraceLoggingMetadataCollector collector, string name, EventFieldFormat format) + { + collector.AddArray(name, formatFunc(format, nativeFormat)); + } + + public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value) + { + collector.AddArray(value, elementSize); + } + + public static TraceLoggingTypeInfo Boolean() { return new ScalarArrayTypeInfo(typeof(Boolean[]), Statics.Format8, TraceLoggingDataType.Boolean8, sizeof(Boolean)); } + public static TraceLoggingTypeInfo Byte() { return new ScalarArrayTypeInfo(typeof(Byte[]), Statics.Format8, TraceLoggingDataType.UInt8, sizeof(Byte)); } + public static TraceLoggingTypeInfo SByte() { return new ScalarArrayTypeInfo(typeof(SByte[]), Statics.Format8, TraceLoggingDataType.Int8, sizeof(SByte)); } + public static TraceLoggingTypeInfo Char() { return new ScalarArrayTypeInfo(typeof(Char[]), Statics.Format16, TraceLoggingDataType.Char16, sizeof(Char)); } + public static TraceLoggingTypeInfo Int16() { return new ScalarArrayTypeInfo(typeof(Int16[]), Statics.Format16, TraceLoggingDataType.Int16, sizeof(Int16)); } + public static TraceLoggingTypeInfo UInt16() { return new ScalarArrayTypeInfo(typeof(UInt16[]), Statics.Format16, TraceLoggingDataType.UInt16, sizeof(UInt16)); } + public static TraceLoggingTypeInfo Int32() { return new ScalarArrayTypeInfo(typeof(Int32[]), Statics.Format32, TraceLoggingDataType.Int32, sizeof(Int32)); } + public static TraceLoggingTypeInfo UInt32() { return new ScalarArrayTypeInfo(typeof(UInt32[]), Statics.Format32, TraceLoggingDataType.UInt32, sizeof(UInt32)); } + public static TraceLoggingTypeInfo Int64() { return new ScalarArrayTypeInfo(typeof(Int64[]), Statics.Format64, TraceLoggingDataType.Int64, sizeof(Int64)); } + public static TraceLoggingTypeInfo UInt64() { return new ScalarArrayTypeInfo(typeof(UInt64[]), Statics.Format64, TraceLoggingDataType.UInt64, sizeof(UInt64)); } + public static TraceLoggingTypeInfo IntPtr() { return new ScalarArrayTypeInfo(typeof(IntPtr[]), Statics.FormatPtr, Statics.IntPtrType, System.IntPtr.Size); } + public static TraceLoggingTypeInfo UIntPtr() { return new ScalarArrayTypeInfo(typeof(UIntPtr[]), Statics.FormatPtr, Statics.UIntPtrType, System.IntPtr.Size); } + public static TraceLoggingTypeInfo Single() { return new ScalarArrayTypeInfo(typeof(Single[]), Statics.Format32, TraceLoggingDataType.Float, sizeof(Single)); } + public static TraceLoggingTypeInfo Double() { return new ScalarArrayTypeInfo(typeof(Double[]), Statics.Format64, TraceLoggingDataType.Double, sizeof(Double)); } + public unsafe static TraceLoggingTypeInfo Guid() { return new ScalarArrayTypeInfo(typeof(Guid), (f, t) => Statics.MakeDataType(TraceLoggingDataType.Guid, f), TraceLoggingDataType.Guid, sizeof(Guid)); } + } + + /// + /// TraceLogging: Type handler for String. + /// + internal sealed class StringTypeInfo : TraceLoggingTypeInfo + { + public StringTypeInfo() : base(typeof(string)) { } + + public override void WriteMetadata( + TraceLoggingMetadataCollector collector, + string name, + EventFieldFormat format) + { + collector.AddBinary(name, Statics.MakeDataType(TraceLoggingDataType.CountedUtf16String, format)); + } + + public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value) + { + collector.AddBinary((string)value.ReferenceValue); + } + + public override object GetData(object value) + { + if(value == null) + { + return ""; + } + + return value; + } + } + + /// + /// TraceLogging: Type handler for DateTime. + /// + internal sealed class DateTimeTypeInfo : TraceLoggingTypeInfo + { + public DateTimeTypeInfo() : base(typeof(DateTime)) { } + + public override void WriteMetadata( + TraceLoggingMetadataCollector collector, + string name, + EventFieldFormat format) + { + collector.AddScalar(name, Statics.MakeDataType(TraceLoggingDataType.FileTime, format)); + } + + public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value) + { + var ticks = value.ScalarValue.AsDateTime.Ticks; + collector.AddScalar(ticks < 504911232000000000 ? 0 : ticks - 504911232000000000); + } + } + + /// + /// TraceLogging: Type handler for DateTimeOffset. + /// + internal sealed class DateTimeOffsetTypeInfo : TraceLoggingTypeInfo + { + public DateTimeOffsetTypeInfo() : base(typeof(DateTimeOffset)) { } + + public override void WriteMetadata(TraceLoggingMetadataCollector collector, string name, EventFieldFormat format) + { + var group = collector.AddGroup(name); + group.AddScalar("Ticks", Statics.MakeDataType(TraceLoggingDataType.FileTime, format)); + group.AddScalar("Offset", TraceLoggingDataType.Int64); + } + + public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value) + { + var dateTimeOffset = value.ScalarValue.AsDateTimeOffset; + var ticks = dateTimeOffset.Ticks; + collector.AddScalar(ticks < 504911232000000000 ? 0 : ticks - 504911232000000000); + collector.AddScalar(dateTimeOffset.Offset.Ticks); + } + } + + /// + /// TraceLogging: Type handler for TimeSpan. + /// + internal sealed class TimeSpanTypeInfo : TraceLoggingTypeInfo + { + public TimeSpanTypeInfo() : base(typeof(TimeSpan)) { } + + public override void WriteMetadata( + TraceLoggingMetadataCollector collector, + string name, + EventFieldFormat format) + { + collector.AddScalar(name, Statics.MakeDataType(TraceLoggingDataType.Int64, format)); + } + + public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value) + { + collector.AddScalar(value.ScalarValue.AsTimeSpan.Ticks); + } + } + + /// + /// TraceLogging: Type handler for Decimal. (Note: not full-fidelity, exposed as Double.) + /// + internal sealed class DecimalTypeInfo : TraceLoggingTypeInfo + { + public DecimalTypeInfo() : base(typeof(Decimal)) { } + + public override void WriteMetadata( + TraceLoggingMetadataCollector collector, + string name, + EventFieldFormat format) + { + collector.AddScalar(name, Statics.MakeDataType(TraceLoggingDataType.Double, format)); + } + + public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value) + { + collector.AddScalar((double)value.ScalarValue.AsDecimal); + } + } + + /// + /// TraceLogging: Type handler for Nullable. + /// + internal sealed class NullableTypeInfo : TraceLoggingTypeInfo + { + private readonly TraceLoggingTypeInfo valueInfo; + private readonly Func hasValueGetter; + private readonly Func valueGetter; + + public NullableTypeInfo(Type type, List recursionCheck) + : base(type) + { + var typeArgs = type.GenericTypeArguments; + Debug.Assert(typeArgs.Length == 1); + this.valueInfo = TraceLoggingTypeInfo.GetInstance(typeArgs[0], recursionCheck); + this.hasValueGetter = PropertyValue.GetPropertyGetter(type.GetTypeInfo().GetDeclaredProperty("HasValue")); + this.valueGetter = PropertyValue.GetPropertyGetter(type.GetTypeInfo().GetDeclaredProperty("Value")); + } + + public override void WriteMetadata( + TraceLoggingMetadataCollector collector, + string name, + EventFieldFormat format) + { + var group = collector.AddGroup(name); + group.AddScalar("HasValue", TraceLoggingDataType.Boolean8); + this.valueInfo.WriteMetadata(group, "Value", format); + } + + public override void WriteData(TraceLoggingDataCollector collector, PropertyValue value) + { + var hasValue = hasValueGetter(value); + collector.AddScalar(hasValue); + var val = hasValue.ScalarValue.AsBoolean ? valueGetter(value) : valueInfo.PropertyValueFactory(Activator.CreateInstance(valueInfo.DataType)); + this.valueInfo.WriteData(collector, val); + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/Statics.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/Statics.cs new file mode 100644 index 0000000000..9fa776753d --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/Statics.cs @@ -0,0 +1,727 @@ +// 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.Collections.Generic; +using System.Reflection; +using System.Runtime.CompilerServices; +using Encoding = System.Text.Encoding; + +using Microsoft.Reflection; + +#if ES_BUILD_STANDALONE +using Environment = Microsoft.Diagnostics.Tracing.Internal.Environment; +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// TraceLogging: Constants and utility functions. + /// + internal static class Statics + { + #region Constants + + public const byte DefaultLevel = 5; + public const byte TraceLoggingChannel = 0xb; + public const byte InTypeMask = 31; + public const byte InTypeFixedCountFlag = 32; + public const byte InTypeVariableCountFlag = 64; + public const byte InTypeCustomCountFlag = 96; + public const byte InTypeCountMask = 96; + public const byte InTypeChainFlag = 128; + public const byte OutTypeMask = 127; + public const byte OutTypeChainFlag = 128; + public const EventTags EventTagsMask = (EventTags)0xfffffff; + + public static readonly TraceLoggingDataType IntPtrType = IntPtr.Size == 8 + ? TraceLoggingDataType.Int64 + : TraceLoggingDataType.Int32; + public static readonly TraceLoggingDataType UIntPtrType = IntPtr.Size == 8 + ? TraceLoggingDataType.UInt64 + : TraceLoggingDataType.UInt32; + public static readonly TraceLoggingDataType HexIntPtrType = IntPtr.Size == 8 + ? TraceLoggingDataType.HexInt64 + : TraceLoggingDataType.HexInt32; + + #endregion + + #region Metadata helpers + + /// + /// A complete metadata chunk can be expressed as: + /// length16 + prefix + null-terminated-utf8-name + suffix + additionalData. + /// We assume that excludedData will be provided by some other means, + /// but that its size is known. This function returns a blob containing + /// length16 + prefix + name + suffix, with prefix and suffix initialized + /// to 0's. The length16 value is initialized to the length of the returned + /// blob plus additionalSize, so that the concatenation of the returned blob + /// plus a blob of size additionalSize constitutes a valid metadata blob. + /// + /// + /// The name to include in the blob. + /// + /// + /// Amount of space to reserve before name. For provider or field blobs, this + /// should be 0. For event blobs, this is used for the tags field and will vary + /// from 1 to 4, depending on how large the tags field needs to be. + /// + /// + /// Amount of space to reserve after name. For example, a provider blob with no + /// traits would reserve 0 extra bytes, but a provider blob with a single GroupId + /// field would reserve 19 extra bytes. + /// + /// + /// Amount of additional data in another blob. This value will be counted in the + /// blob's length field, but will not be included in the returned byte[] object. + /// The complete blob would then be the concatenation of the returned byte[] object + /// with another byte[] object of length additionalSize. + /// + /// + /// A byte[] object with the length and name fields set, with room reserved for + /// prefix and suffix. If additionalSize was 0, the byte[] object is a complete + /// blob. Otherwise, another byte[] of size additionalSize must be concatenated + /// with this one to form a complete blob. + /// + public static byte[] MetadataForString( + string name, + int prefixSize, + int suffixSize, + int additionalSize) + { + Statics.CheckName(name); + int metadataSize = Encoding.UTF8.GetByteCount(name) + 3 + prefixSize + suffixSize; + var metadata = new byte[metadataSize]; + ushort totalSize = checked((ushort)(metadataSize + additionalSize)); + metadata[0] = unchecked((byte)totalSize); + metadata[1] = unchecked((byte)(totalSize >> 8)); + Encoding.UTF8.GetBytes(name, 0, name.Length, metadata, 2 + prefixSize); + return metadata; + } + + /// + /// Serialize the low 28 bits of the tags value into the metadata stream, + /// starting at the index given by pos. Updates pos. Writes 1 to 4 bytes, + /// depending on the value of the tags variable. Usable for event tags and + /// field tags. + /// + /// Note that 'metadata' can be null, in which case it only updates 'pos'. + /// This is useful for a two pass approach where you figure out how big to + /// make the array, and then you fill it in. + /// + public static void EncodeTags(int tags, ref int pos, byte[] metadata) + { + // We transmit the low 28 bits of tags, high bits first, 7 bits at a time. + var tagsLeft = tags & 0xfffffff; + bool more; + do + { + byte current = (byte)((tagsLeft >> 21) & 0x7f); + more = (tagsLeft & 0x1fffff) != 0; + current |= (byte)(more ? 0x80 : 0x00); + tagsLeft = tagsLeft << 7; + + if (metadata != null) + { + metadata[pos] = current; + } + pos += 1; + } + while (more); + } + + public static byte Combine( + int settingValue, + byte defaultValue) + { + unchecked + { + return (byte)settingValue == settingValue + ? (byte)settingValue + : defaultValue; + } + } + + public static byte Combine( + int settingValue1, + int settingValue2, + byte defaultValue) + { + unchecked + { + return (byte)settingValue1 == settingValue1 + ? (byte)settingValue1 + : (byte)settingValue2 == settingValue2 + ? (byte)settingValue2 + : defaultValue; + } + } + + public static int Combine( + int settingValue1, + int settingValue2) + { + unchecked + { + return (byte)settingValue1 == settingValue1 + ? settingValue1 + : settingValue2; + } + } + + public static void CheckName(string name) + { + if (name != null && 0 <= name.IndexOf('\0')) + { + throw new ArgumentOutOfRangeException(nameof(name)); + } + } + + public static bool ShouldOverrideFieldName(string fieldName) + { + return (fieldName.Length <= 2 && fieldName[0] == '_'); + } + + public static TraceLoggingDataType MakeDataType( + TraceLoggingDataType baseType, + EventFieldFormat format) + { + return (TraceLoggingDataType)(((int)baseType & 0x1f) | ((int)format << 8)); + } + + /// + /// Adjusts the native type based on format. + /// - If format is default, return native. + /// - If format is recognized, return the canonical type for that format. + /// - Otherwise remove existing format from native and apply the requested format. + /// + public static TraceLoggingDataType Format8( + EventFieldFormat format, + TraceLoggingDataType native) + { + switch (format) + { + case EventFieldFormat.Default: + return native; + case EventFieldFormat.String: + return TraceLoggingDataType.Char8; + case EventFieldFormat.Boolean: + return TraceLoggingDataType.Boolean8; + case EventFieldFormat.Hexadecimal: + return TraceLoggingDataType.HexInt8; +#if false + case EventSourceFieldFormat.Signed: + return TraceLoggingDataType.Int8; + case EventSourceFieldFormat.Unsigned: + return TraceLoggingDataType.UInt8; +#endif + default: + return MakeDataType(native, format); + } + } + + /// + /// Adjusts the native type based on format. + /// - If format is default, return native. + /// - If format is recognized, return the canonical type for that format. + /// - Otherwise remove existing format from native and apply the requested format. + /// + public static TraceLoggingDataType Format16( + EventFieldFormat format, + TraceLoggingDataType native) + { + switch (format) + { + case EventFieldFormat.Default: + return native; + case EventFieldFormat.String: + return TraceLoggingDataType.Char16; + case EventFieldFormat.Hexadecimal: + return TraceLoggingDataType.HexInt16; +#if false + case EventSourceFieldFormat.Port: + return TraceLoggingDataType.Port; + case EventSourceFieldFormat.Signed: + return TraceLoggingDataType.Int16; + case EventSourceFieldFormat.Unsigned: + return TraceLoggingDataType.UInt16; +#endif + default: + return MakeDataType(native, format); + } + } + + /// + /// Adjusts the native type based on format. + /// - If format is default, return native. + /// - If format is recognized, return the canonical type for that format. + /// - Otherwise remove existing format from native and apply the requested format. + /// + public static TraceLoggingDataType Format32( + EventFieldFormat format, + TraceLoggingDataType native) + { + switch (format) + { + case EventFieldFormat.Default: + return native; + case EventFieldFormat.Boolean: + return TraceLoggingDataType.Boolean32; + case EventFieldFormat.Hexadecimal: + return TraceLoggingDataType.HexInt32; +#if false + case EventSourceFieldFormat.Ipv4Address: + return TraceLoggingDataType.Ipv4Address; + case EventSourceFieldFormat.ProcessId: + return TraceLoggingDataType.ProcessId; + case EventSourceFieldFormat.ThreadId: + return TraceLoggingDataType.ThreadId; + case EventSourceFieldFormat.Win32Error: + return TraceLoggingDataType.Win32Error; + case EventSourceFieldFormat.NTStatus: + return TraceLoggingDataType.NTStatus; +#endif + case EventFieldFormat.HResult: + return TraceLoggingDataType.HResult; +#if false + case EventSourceFieldFormat.Signed: + return TraceLoggingDataType.Int32; + case EventSourceFieldFormat.Unsigned: + return TraceLoggingDataType.UInt32; +#endif + default: + return MakeDataType(native, format); + } + } + + /// + /// Adjusts the native type based on format. + /// - If format is default, return native. + /// - If format is recognized, return the canonical type for that format. + /// - Otherwise remove existing format from native and apply the requested format. + /// + public static TraceLoggingDataType Format64( + EventFieldFormat format, + TraceLoggingDataType native) + { + switch (format) + { + case EventFieldFormat.Default: + return native; + case EventFieldFormat.Hexadecimal: + return TraceLoggingDataType.HexInt64; +#if false + case EventSourceFieldFormat.FileTime: + return TraceLoggingDataType.FileTime; + case EventSourceFieldFormat.Signed: + return TraceLoggingDataType.Int64; + case EventSourceFieldFormat.Unsigned: + return TraceLoggingDataType.UInt64; +#endif + default: + return MakeDataType(native, format); + } + } + + /// + /// Adjusts the native type based on format. + /// - If format is default, return native. + /// - If format is recognized, return the canonical type for that format. + /// - Otherwise remove existing format from native and apply the requested format. + /// + public static TraceLoggingDataType FormatPtr( + EventFieldFormat format, + TraceLoggingDataType native) + { + switch (format) + { + case EventFieldFormat.Default: + return native; + case EventFieldFormat.Hexadecimal: + return HexIntPtrType; +#if false + case EventSourceFieldFormat.Signed: + return IntPtrType; + case EventSourceFieldFormat.Unsigned: + return UIntPtrType; +#endif + default: + return MakeDataType(native, format); + } + } + + #endregion + + #region Reflection helpers + + /* + All TraceLogging use of reflection APIs should go through wrappers here. + This helps with portability, and it also makes it easier to audit what + kinds of reflection operations are being done. + */ + + public static object CreateInstance(Type type, params object[] parameters) + { + return Activator.CreateInstance(type, parameters); + } + + public static bool IsValueType(Type type) + { + bool result = type.IsValueType(); + return result; + } + + public static bool IsEnum(Type type) + { + bool result = type.IsEnum(); + return result; + } + + public static IEnumerable GetProperties(Type type) + { + IEnumerable result = type.GetProperties(); + return result; + } + + public static MethodInfo GetGetMethod(PropertyInfo propInfo) + { + MethodInfo result = propInfo.GetGetMethod(); + return result; + } + + public static MethodInfo GetDeclaredStaticMethod(Type declaringType, string name) + { + MethodInfo result; +#if (ES_BUILD_PCL || ES_BUILD_PN) + result = declaringType.GetTypeInfo().GetDeclaredMethod(name); +#else + result = declaringType.GetMethod( + name, + BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.NonPublic); +#endif + return result; + } + + public static bool HasCustomAttribute( + PropertyInfo propInfo, + Type attributeType) + { + bool result; +#if (ES_BUILD_PCL || ES_BUILD_PN) + result = propInfo.IsDefined(attributeType); +#else + var attributes = propInfo.GetCustomAttributes( + attributeType, + false); + result = attributes.Length != 0; +#endif + return result; + } + + public static AttributeType GetCustomAttribute(PropertyInfo propInfo) + where AttributeType : Attribute + { + AttributeType result = null; +#if (ES_BUILD_PCL || ES_BUILD_PN) + foreach (var attrib in propInfo.GetCustomAttributes(false)) + { + result = attrib; + break; + } +#else + var attributes = propInfo.GetCustomAttributes(typeof(AttributeType), false); + if (attributes.Length != 0) + { + result = (AttributeType)attributes[0]; + } +#endif + return result; + } + + public static AttributeType GetCustomAttribute(Type type) + where AttributeType : Attribute + { + AttributeType result = null; +#if (ES_BUILD_PCL || ES_BUILD_PN) + foreach (var attrib in type.GetTypeInfo().GetCustomAttributes(false)) + { + result = attrib; + break; + } +#else + var attributes = type.GetCustomAttributes(typeof(AttributeType), false); + if (attributes.Length != 0) + { + result = (AttributeType)attributes[0]; + } +#endif + return result; + } + + public static Type[] GetGenericArguments(Type type) + { + return type.GetGenericArguments(); + } + + public static Type FindEnumerableElementType(Type type) + { + Type elementType = null; + + if (IsGenericMatch(type, typeof(IEnumerable<>))) + { + elementType = GetGenericArguments(type)[0]; + } + else + { +#if (ES_BUILD_PCL || ES_BUILD_PN) + var ifaceTypes = type.GetTypeInfo().ImplementedInterfaces; +#else + var ifaceTypes = type.FindInterfaces(IsGenericMatch, typeof(IEnumerable<>)); +#endif + + foreach (var ifaceType in ifaceTypes) + { +#if (ES_BUILD_PCL || ES_BUILD_PN) + if (!IsGenericMatch(ifaceType, typeof(IEnumerable<>))) + { + continue; + } +#endif + + if (elementType != null) + { + // ambiguous match. report no match at all. + elementType = null; + break; + } + + elementType = GetGenericArguments(ifaceType)[0]; + } + } + + return elementType; + } + + public static bool IsGenericMatch(Type type, object openType) + { + return type.IsGenericType() && type.GetGenericTypeDefinition() == (Type)openType; + } + + public static Delegate CreateDelegate(Type delegateType, MethodInfo methodInfo) + { + Delegate result; +#if (ES_BUILD_PCL || ES_BUILD_PN) + result = methodInfo.CreateDelegate( + delegateType); +#else + result = Delegate.CreateDelegate( + delegateType, + methodInfo); +#endif + return result; + } + + public static TraceLoggingTypeInfo CreateDefaultTypeInfo( + Type dataType, + List recursionCheck) + { + TraceLoggingTypeInfo result; + + if (recursionCheck.Contains(dataType)) + { + throw new NotSupportedException(Resources.GetResourceString("EventSource_RecursiveTypeDefinition")); + } + + recursionCheck.Add(dataType); + + var eventAttrib = Statics.GetCustomAttribute(dataType); + if (eventAttrib != null || + Statics.GetCustomAttribute(dataType) != null || + IsGenericMatch(dataType, typeof(KeyValuePair<,>))) + { + var analysis = new TypeAnalysis(dataType, eventAttrib, recursionCheck); + result = new InvokeTypeInfo(dataType, analysis); + } + else if (dataType.IsArray) + { + var elementType = dataType.GetElementType(); + if (elementType == typeof(Boolean)) + { + result = ScalarArrayTypeInfo.Boolean(); + } + else if (elementType == typeof(Byte)) + { + result = ScalarArrayTypeInfo.Byte(); + } + else if (elementType == typeof(SByte)) + { + result = ScalarArrayTypeInfo.SByte(); + } + else if (elementType == typeof(Int16)) + { + result = ScalarArrayTypeInfo.Int16(); + } + else if (elementType == typeof(UInt16)) + { + result = ScalarArrayTypeInfo.UInt16(); + } + else if (elementType == typeof(Int32)) + { + result = ScalarArrayTypeInfo.Int32(); + } + else if (elementType == typeof(UInt32)) + { + result = ScalarArrayTypeInfo.UInt32(); + } + else if (elementType == typeof(Int64)) + { + result = ScalarArrayTypeInfo.Int64(); + } + else if (elementType == typeof(UInt64)) + { + result = ScalarArrayTypeInfo.UInt64(); + } + else if (elementType == typeof(Char)) + { + result = ScalarArrayTypeInfo.Char(); + } + else if (elementType == typeof(Double)) + { + result = ScalarArrayTypeInfo.Double(); + } + else if (elementType == typeof(Single)) + { + result = ScalarArrayTypeInfo.Single(); + } + else if (elementType == typeof(IntPtr)) + { + result = ScalarArrayTypeInfo.IntPtr(); + } + else if (elementType == typeof(UIntPtr)) + { + result = ScalarArrayTypeInfo.UIntPtr(); + } + else if (elementType == typeof(Guid)) + { + result = ScalarArrayTypeInfo.Guid(); + } + else + { + result = new ArrayTypeInfo(dataType, TraceLoggingTypeInfo.GetInstance(elementType, recursionCheck)); + } + } + else + { + if (Statics.IsEnum(dataType)) + dataType = Enum.GetUnderlyingType(dataType); + + if (dataType == typeof(String)) + { + result = new StringTypeInfo(); + } + else if (dataType == typeof(Boolean)) + { + result = ScalarTypeInfo.Boolean(); + } + else if (dataType == typeof(Byte)) + { + result = ScalarTypeInfo.Byte(); + } + else if (dataType == typeof(SByte)) + { + result = ScalarTypeInfo.SByte(); + } + else if (dataType == typeof(Int16)) + { + result = ScalarTypeInfo.Int16(); + } + else if (dataType == typeof(UInt16)) + { + result = ScalarTypeInfo.UInt16(); + } + else if (dataType == typeof(Int32)) + { + result = ScalarTypeInfo.Int32(); + } + else if (dataType == typeof(UInt32)) + { + result = ScalarTypeInfo.UInt32(); + } + else if (dataType == typeof(Int64)) + { + result = ScalarTypeInfo.Int64(); + } + else if (dataType == typeof(UInt64)) + { + result = ScalarTypeInfo.UInt64(); + } + else if (dataType == typeof(Char)) + { + result = ScalarTypeInfo.Char(); + } + else if (dataType == typeof(Double)) + { + result = ScalarTypeInfo.Double(); + } + else if (dataType == typeof(Single)) + { + result = ScalarTypeInfo.Single(); + } + else if (dataType == typeof(DateTime)) + { + result = new DateTimeTypeInfo(); + } + else if (dataType == typeof(Decimal)) + { + result = new DecimalTypeInfo(); + } + else if (dataType == typeof(IntPtr)) + { + result = ScalarTypeInfo.IntPtr(); + } + else if (dataType == typeof(UIntPtr)) + { + result = ScalarTypeInfo.UIntPtr(); + } + else if (dataType == typeof(Guid)) + { + result = ScalarTypeInfo.Guid(); + } + else if (dataType == typeof(TimeSpan)) + { + result = new TimeSpanTypeInfo(); + } + else if (dataType == typeof(DateTimeOffset)) + { + result = new DateTimeOffsetTypeInfo(); + } + else if (dataType == typeof(EmptyStruct)) + { + result = new NullTypeInfo(); + } + else if (IsGenericMatch(dataType, typeof(Nullable<>))) + { + result = new NullableTypeInfo(dataType, recursionCheck); + } + else + { + var elementType = FindEnumerableElementType(dataType); + if (elementType != null) + { + result = new EnumerableTypeInfo(dataType, TraceLoggingTypeInfo.GetInstance(elementType, recursionCheck)); + } + else + { + throw new ArgumentException(Resources.GetResourceString("EventSource_NonCompliantTypeError", dataType.Name)); + } + } + } + + return result; + } + + #endregion + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataCollector.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataCollector.cs new file mode 100644 index 0000000000..04a047fb35 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataCollector.cs @@ -0,0 +1,104 @@ +// 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.Security; + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// TraceLogging: Used when implementing a custom TraceLoggingTypeInfo. + /// The instance of this type is provided to the TypeInfo.WriteData method. + /// All operations are forwarded to the current thread's DataCollector. + /// Note that this abstraction would allow us to expose the custom + /// serialization system to partially-trusted code. If we end up not + /// making custom serialization public, or if we only expose it to + /// full-trust code, this abstraction is unnecessary (though it probably + /// doesn't hurt anything). + /// + internal unsafe class TraceLoggingDataCollector + { + internal static readonly TraceLoggingDataCollector Instance = new TraceLoggingDataCollector(); + + private TraceLoggingDataCollector() + { + return; + } + + /// + /// Marks the start of a non-blittable array or enumerable. + /// + /// Bookmark to be passed to EndBufferedArray. + public int BeginBufferedArray() + { + return DataCollector.ThreadInstance.BeginBufferedArray(); + } + + /// + /// Marks the end of a non-blittable array or enumerable. + /// + /// The value returned by BeginBufferedArray. + /// The number of items in the array. + public void EndBufferedArray(int bookmark, int count) + { + DataCollector.ThreadInstance.EndBufferedArray(bookmark, count); + } + + /// + /// Adds the start of a group to the event. + /// This has no effect on the event payload, but is provided to allow + /// WriteMetadata and WriteData implementations to have similar + /// sequences of calls, allowing for easier verification of correctness. + /// + public TraceLoggingDataCollector AddGroup() + { + return this; + } + + public void AddScalar(PropertyValue value) + { + var scalar = value.ScalarValue; + DataCollector.ThreadInstance.AddScalar(&scalar, value.ScalarLength); + } + + /// + /// Adds an Int64 value to the event payload. + /// + /// Value to be added. + public void AddScalar(long value) + { + DataCollector.ThreadInstance.AddScalar(&value, sizeof(long)); + } + + /// + /// Adds a Double value to the event payload. + /// + /// Value to be added. + public void AddScalar(double value) + { + DataCollector.ThreadInstance.AddScalar(&value, sizeof(double)); + } + + /// + /// Adds a counted String value to the event payload. + /// + /// + /// Value to be added. A null value is treated as a zero-length string. + /// + public void AddBinary(string value) + { + DataCollector.ThreadInstance.AddBinary(value, value == null ? 0 : value.Length * 2); + } + + public void AddArray(PropertyValue value, int elementSize) + { + Array array = (Array)value.ReferenceValue; + DataCollector.ThreadInstance.AddArray(array, array == null ? 0 : array.Length, elementSize); + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataType.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataType.cs new file mode 100644 index 0000000000..529948daf8 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataType.cs @@ -0,0 +1,349 @@ +// 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; + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// TraceLogging: Used when implementing a custom TraceLoggingTypeInfo. + /// These are passed to metadataCollector.Add to specify the low-level + /// type of a field in the event payload. Note that a "formatted" + /// TraceLoggingDataType consists of a core TraceLoggingDataType value + /// (a TraceLoggingDataType with a value less than 32) plus an OutType. + /// Any combination of TraceLoggingDataType + OutType is valid, but not + /// all are useful. In particular, combinations not explicitly listed + /// below are unlikely to be recognized by decoders, and will typically + /// be decoded as the corresponding core type (i.e. the decoder will + /// mask off any unrecognized OutType value). + /// + internal enum TraceLoggingDataType + { + /// + /// Core type. + /// Data type with no value (0-length payload). + /// NOTE: arrays of Nil are illegal. + /// NOTE: a fixed-length array of Nil is interpreted by the decoder as + /// a struct (obsolete but retained for backwards-compatibility). + /// + Nil = 0, + + /// + /// Core type. + /// Encoding assumes null-terminated Char16 string. + /// Decoding treats as UTF-16LE string. + /// + Utf16String = 1, + + /// + /// Core type. + /// Encoding assumes null-terminated Char8 string. + /// Decoding treats as MBCS string. + /// + MbcsString = 2, + + /// + /// Core type. + /// Encoding assumes 8-bit value. + /// Decoding treats as signed integer. + /// + Int8 = 3, + + /// + /// Core type. + /// Encoding assumes 8-bit value. + /// Decoding treats as unsigned integer. + /// + UInt8 = 4, + + /// + /// Core type. + /// Encoding assumes 16-bit value. + /// Decoding treats as signed integer. + /// + Int16 = 5, + + /// + /// Core type. + /// Encoding assumes 16-bit value. + /// Decoding treats as unsigned integer. + /// + UInt16 = 6, + + /// + /// Core type. + /// Encoding assumes 32-bit value. + /// Decoding treats as signed integer. + /// + Int32 = 7, + + /// + /// Core type. + /// Encoding assumes 32-bit value. + /// Decoding treats as unsigned integer. + /// + UInt32 = 8, + + /// + /// Core type. + /// Encoding assumes 64-bit value. + /// Decoding treats as signed integer. + /// + Int64 = 9, + + /// + /// Core type. + /// Encoding assumes 64-bit value. + /// Decoding treats as unsigned integer. + /// + UInt64 = 10, + + /// + /// Core type. + /// Encoding assumes 32-bit value. + /// Decoding treats as Float. + /// + Float = 11, + + /// + /// Core type. + /// Encoding assumes 64-bit value. + /// Decoding treats as Double. + /// + Double = 12, + + /// + /// Core type. + /// Encoding assumes 32-bit value. + /// Decoding treats as Boolean. + /// + Boolean32 = 13, + + /// + /// Core type. + /// Encoding assumes 16-bit bytecount followed by binary data. + /// Decoding treats as binary data. + /// + Binary = 14, + + /// + /// Core type. + /// Encoding assumes 16-byte value. + /// Decoding treats as GUID. + /// + Guid = 15, + + /// + /// Core type. + /// Encoding assumes 64-bit value. + /// Decoding treats as FILETIME. + /// + FileTime = 17, + + /// + /// Core type. + /// Encoding assumes 16-byte value. + /// Decoding treats as SYSTEMTIME. + /// + SystemTime = 18, + + /// + /// Core type. + /// Encoding assumes 32-bit value. + /// Decoding treats as hexadecimal unsigned integer. + /// + HexInt32 = 20, + + /// + /// Core type. + /// Encoding assumes 64-bit value. + /// Decoding treats as hexadecimal unsigned integer. + /// + HexInt64 = 21, + + /// + /// Core type. + /// Encoding assumes 16-bit bytecount followed by Char16 data. + /// Decoding treats as UTF-16LE string. + /// + CountedUtf16String = 22, + + /// + /// Core type. + /// Encoding assumes 16-bit bytecount followed by Char8 data. + /// Decoding treats as MBCS string. + /// + CountedMbcsString = 23, + + /// + /// Core type. + /// Special case: Struct indicates that this field plus the the + /// subsequent N logical fields are to be considered as one logical + /// field (i.e. a nested structure). The OutType is used to encode N. + /// The maximum value for N is 127. This field has no payload by + /// itself, but logically contains the payload of the following N + /// fields. It is legal to have an array of Struct. + /// + Struct = 24, + + /// + /// Formatted type. + /// Encoding assumes 16-bit value. + /// Decoding treats as UTF-16LE character. + /// + Char16 = UInt16 + (EventFieldFormat.String << 8), + + /// + /// Formatted type. + /// Encoding assumes 8-bit value. + /// Decoding treats as character. + /// + Char8 = UInt8 + (EventFieldFormat.String << 8), + + /// + /// Formatted type. + /// Encoding assumes 8-bit value. + /// Decoding treats as Boolean. + /// + Boolean8 = UInt8 + (EventFieldFormat.Boolean << 8), + + /// + /// Formatted type. + /// Encoding assumes 8-bit value. + /// Decoding treats as hexadecimal unsigned integer. + /// + HexInt8 = UInt8 + (EventFieldFormat.Hexadecimal << 8), + + /// + /// Formatted type. + /// Encoding assumes 16-bit value. + /// Decoding treats as hexadecimal unsigned integer. + /// + HexInt16 = UInt16 + (EventFieldFormat.Hexadecimal << 8), + +#if false + /// + /// Formatted type. + /// Encoding assumes 32-bit value. + /// Decoding treats as process identifier. + /// + ProcessId = UInt32 + (EventSourceFieldFormat.ProcessId << 8), + + /// + /// Formatted type. + /// Encoding assumes 32-bit value. + /// Decoding treats as thread identifier. + /// + ThreadId = UInt32 + (EventSourceFieldFormat.ThreadId << 8), + + /// + /// Formatted type. + /// Encoding assumes 16-bit value. + /// Decoding treats as IP port. + /// + Port = UInt16 + (EventSourceFieldFormat.Port << 8), + + /// + /// Formatted type. + /// Encoding assumes 32-bit value. + /// Decoding treats as IPv4 address. + /// + Ipv4Address = UInt32 + (EventSourceFieldFormat.Ipv4Address << 8), + + /// + /// Formatted type. + /// Encoding assumes 16-bit bytecount followed by binary data. + /// Decoding treats as IPv6 address. + /// + Ipv6Address = Binary + (EventSourceFieldFormat.Ipv6Address << 8), + + /// + /// Formatted type. + /// Encoding assumes 16-bit bytecount followed by binary data. + /// Decoding treats as SOCKADDR. + /// + SocketAddress = Binary + (EventSourceFieldFormat.SocketAddress << 8), +#endif + /// + /// Formatted type. + /// Encoding assumes null-terminated Char16 string. + /// Decoding treats as UTF-16LE XML string. + /// + Utf16Xml = Utf16String + (EventFieldFormat.Xml << 8), + + /// + /// Formatted type. + /// Encoding assumes null-terminated Char8 string. + /// Decoding treats as MBCS XML string. + /// + MbcsXml = MbcsString + (EventFieldFormat.Xml << 8), + + /// + /// Formatted type. + /// Encoding assumes 16-bit bytecount followed by Char16 data. + /// Decoding treats as UTF-16LE XML. + /// + CountedUtf16Xml = CountedUtf16String + (EventFieldFormat.Xml << 8), + + /// + /// Formatted type. + /// Encoding assumes 16-bit bytecount followed by Char8 data. + /// Decoding treats as MBCS XML. + /// + CountedMbcsXml = CountedMbcsString + (EventFieldFormat.Xml << 8), + + /// + /// Formatted type. + /// Encoding assumes null-terminated Char16 string. + /// Decoding treats as UTF-16LE JSON string. + /// + Utf16Json = Utf16String + (EventFieldFormat.Json << 8), + + /// + /// Formatted type. + /// Encoding assumes null-terminated Char8 string. + /// Decoding treats as MBCS JSON string. + /// + MbcsJson = MbcsString + (EventFieldFormat.Json << 8), + + /// + /// Formatted type. + /// Encoding assumes 16-bit bytecount followed by Char16 data. + /// Decoding treats as UTF-16LE JSON. + /// + CountedUtf16Json = CountedUtf16String + (EventFieldFormat.Json << 8), + + /// + /// Formatted type. + /// Encoding assumes 16-bit bytecount followed by Char8 data. + /// Decoding treats as MBCS JSON. + /// + CountedMbcsJson = CountedMbcsString + (EventFieldFormat.Json << 8), +#if false + /// + /// Formatted type. + /// Encoding assumes 32-bit value. + /// Decoding treats as Win32 error. + /// + Win32Error = UInt32 + (EventSourceFieldFormat.Win32Error << 8), + + /// + /// Formatted type. + /// Encoding assumes 32-bit value. + /// Decoding treats as NTSTATUS. + /// + NTStatus = UInt32 + (EventSourceFieldFormat.NTStatus << 8), +#endif + /// + /// Formatted type. + /// Encoding assumes 32-bit value. + /// Decoding treats as HRESULT. + /// + HResult = Int32 + (EventFieldFormat.HResult << 8) + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs new file mode 100644 index 0000000000..e73339949a --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs @@ -0,0 +1,890 @@ +// 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. + +// This program uses code hyperlinks available as part of the HyperAddin Visual Studio plug-in. +// It is available from http://www.codeplex.com/hyperAddin + +#if PLATFORM_WINDOWS +#define FEATURE_MANAGED_ETW + +#if !ES_BUILD_STANDALONE +#define FEATURE_ACTIVITYSAMPLING +#endif +#endif // PLATFORM_WINDOWS + +#if ES_BUILD_STANDALONE +#define FEATURE_MANAGED_ETW_CHANNELS +// #define FEATURE_ADVANCED_MANAGED_ETW_CHANNELS +#endif + +#if ES_BUILD_STANDALONE +using Environment = Microsoft.Diagnostics.Tracing.Internal.Environment; +using EventDescriptor = Microsoft.Diagnostics.Tracing.EventDescriptor; +#endif + +using System; +using System.Runtime.InteropServices; +using System.Security; +using System.Collections.ObjectModel; + +#if !ES_BUILD_AGAINST_DOTNET_V35 +using Contract = System.Diagnostics.Contracts.Contract; +using System.Collections.Generic; +using System.Text; +#else +using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract; +using System.Collections.Generic; +using System.Text; +#endif + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + public partial class EventSource + { +#if FEATURE_MANAGED_ETW + private byte[] providerMetadata; +#endif + + /// + /// Construct an EventSource with a given name for non-contract based events (e.g. those using the Write() API). + /// + /// + /// The name of the event source. Must not be null. + /// + public EventSource( + string eventSourceName) + : this(eventSourceName, EventSourceSettings.EtwSelfDescribingEventFormat) + { } + + /// + /// Construct an EventSource with a given name for non-contract based events (e.g. those using the Write() API). + /// + /// + /// The name of the event source. Must not be null. + /// + /// + /// Configuration options for the EventSource as a whole. + /// + public EventSource( + string eventSourceName, + EventSourceSettings config) + : this(eventSourceName, config, null) { } + + /// + /// Construct an EventSource with a given name for non-contract based events (e.g. those using the Write() API). + /// + /// Also specify a list of key-value pairs called traits (you must pass an even number of strings). + /// The first string is the key and the second is the value. These are not interpreted by EventSource + /// itself but may be interprated the listeners. Can be fetched with GetTrait(string). + /// + /// + /// The name of the event source. Must not be null. + /// + /// + /// Configuration options for the EventSource as a whole. + /// + /// A collection of key-value strings (must be an even number). + public EventSource( + string eventSourceName, + EventSourceSettings config, + params string[] traits) + : this( + eventSourceName == null ? new Guid() : GenerateGuidFromName(eventSourceName.ToUpperInvariant()), + eventSourceName, + config, traits) + { + if (eventSourceName == null) + { + throw new ArgumentNullException(nameof(eventSourceName)); + } + Contract.EndContractBlock(); + } + + /// + /// Writes an event with no fields and default options. + /// (Native API: EventWriteTransfer) + /// + /// The name of the event. Must not be null. + public unsafe void Write(string eventName) + { + if (eventName == null) + { + throw new ArgumentNullException(nameof(eventName)); + } + + Contract.EndContractBlock(); + + if (!this.IsEnabled()) + { + return; + } + + var options = new EventSourceOptions(); + this.WriteImpl(eventName, ref options, null, null, null, SimpleEventTypes.Instance); + } + + /// + /// Writes an event with no fields. + /// (Native API: EventWriteTransfer) + /// + /// The name of the event. Must not be null. + /// + /// Options for the event, such as the level, keywords, and opcode. Unset + /// options will be set to default values. + /// + public unsafe void Write(string eventName, EventSourceOptions options) + { + if (eventName == null) + { + throw new ArgumentNullException(nameof(eventName)); + } + + Contract.EndContractBlock(); + + if (!this.IsEnabled()) + { + return; + } + + this.WriteImpl(eventName, ref options, null, null, null, SimpleEventTypes.Instance); + } + + /// + /// Writes an event. + /// (Native API: EventWriteTransfer) + /// + /// + /// The type that defines the event and its payload. This must be an + /// anonymous type or a type with an [EventData] attribute. + /// + /// + /// The name for the event. If null, the event name is automatically + /// determined based on T, either from the Name property of T's EventData + /// attribute or from typeof(T).Name. + /// + /// + /// The object containing the event payload data. The type T must be + /// an anonymous type or a type with an [EventData] attribute. The + /// public instance properties of data will be written recursively to + /// create the fields of the event. + /// + public unsafe void Write( + string eventName, + T data) + { + if (!this.IsEnabled()) + { + return; + } + + var options = new EventSourceOptions(); + this.WriteImpl(eventName, ref options, data, null, null, SimpleEventTypes.Instance); + } + + /// + /// Writes an event. + /// (Native API: EventWriteTransfer) + /// + /// + /// The type that defines the event and its payload. This must be an + /// anonymous type or a type with an [EventData] attribute. + /// + /// + /// The name for the event. If null, the event name is automatically + /// determined based on T, either from the Name property of T's EventData + /// attribute or from typeof(T).Name. + /// + /// + /// Options for the event, such as the level, keywords, and opcode. Unset + /// options will be set to default values. + /// + /// + /// The object containing the event payload data. The type T must be + /// an anonymous type or a type with an [EventData] attribute. The + /// public instance properties of data will be written recursively to + /// create the fields of the event. + /// + public unsafe void Write( + string eventName, + EventSourceOptions options, + T data) + { + if (!this.IsEnabled()) + { + return; + } + + this.WriteImpl(eventName, ref options, data, null, null, SimpleEventTypes.Instance); + } + + /// + /// Writes an event. + /// This overload is for use with extension methods that wish to efficiently + /// forward the options or data parameter without performing an extra copy. + /// (Native API: EventWriteTransfer) + /// + /// + /// The type that defines the event and its payload. This must be an + /// anonymous type or a type with an [EventData] attribute. + /// + /// + /// The name for the event. If null, the event name is automatically + /// determined based on T, either from the Name property of T's EventData + /// attribute or from typeof(T).Name. + /// + /// + /// Options for the event, such as the level, keywords, and opcode. Unset + /// options will be set to default values. + /// + /// + /// The object containing the event payload data. The type T must be + /// an anonymous type or a type with an [EventData] attribute. The + /// public instance properties of data will be written recursively to + /// create the fields of the event. + /// + public unsafe void Write( + string eventName, + ref EventSourceOptions options, + ref T data) + { + if (!this.IsEnabled()) + { + return; + } + + this.WriteImpl(eventName, ref options, data, null, null, SimpleEventTypes.Instance); + } + + /// + /// Writes an event. + /// This overload is meant for clients that need to manipuate the activityId + /// and related ActivityId for the event. + /// + /// + /// The type that defines the event and its payload. This must be an + /// anonymous type or a type with an [EventData] attribute. + /// + /// + /// The name for the event. If null, the event name is automatically + /// determined based on T, either from the Name property of T's EventData + /// attribute or from typeof(T).Name. + /// + /// + /// Options for the event, such as the level, keywords, and opcode. Unset + /// options will be set to default values. + /// + /// + /// The GUID of the activity associated with this event. + /// + /// + /// The GUID of another activity that is related to this activity, or Guid.Empty + /// if there is no related activity. Most commonly, the Start operation of a + /// new activity specifies a parent activity as its related activity. + /// + /// + /// The object containing the event payload data. The type T must be + /// an anonymous type or a type with an [EventData] attribute. The + /// public instance properties of data will be written recursively to + /// create the fields of the event. + /// + public unsafe void Write( + string eventName, + ref EventSourceOptions options, + ref Guid activityId, + ref Guid relatedActivityId, + ref T data) + { + if (!this.IsEnabled()) + { + return; + } + + fixed (Guid* pActivity = &activityId, pRelated = &relatedActivityId) + { + this.WriteImpl( + eventName, + ref options, + data, + pActivity, + relatedActivityId == Guid.Empty ? null : pRelated, + SimpleEventTypes.Instance); + } + } + + /// + /// Writes an extended event, where the values of the event are the + /// combined properties of any number of values. This method is + /// intended for use in advanced logging scenarios that support a + /// dynamic set of event context providers. + /// This method does a quick check on whether this event is enabled. + /// + /// + /// The name for the event. If null, the name from eventTypes is used. + /// (Note that providing the event name via the name parameter is slightly + /// less efficient than using the name from eventTypes.) + /// + /// + /// Optional overrides for the event, such as the level, keyword, opcode, + /// activityId, and relatedActivityId. Any settings not specified by options + /// are obtained from eventTypes. + /// + /// + /// Information about the event and the types of the values in the event. + /// Must not be null. Note that the eventTypes object should be created once and + /// saved. It should not be recreated for each event. + /// + /// + /// A pointer to the activity ID GUID to log + /// + /// + /// A pointer to the child activity ID to log (can be null) + /// + /// The values to include in the event. Must not be null. The number and types of + /// the values must match the number and types of the fields described by the + /// eventTypes parameter. + /// + private unsafe void WriteMultiMerge( + string eventName, + ref EventSourceOptions options, + TraceLoggingEventTypes eventTypes, + Guid* activityID, + Guid* childActivityID, + params object[] values) + { + if (!this.IsEnabled()) + { + return; + } + byte level = (options.valuesSet & EventSourceOptions.levelSet) != 0 + ? options.level + : eventTypes.level; + EventKeywords keywords = (options.valuesSet & EventSourceOptions.keywordsSet) != 0 + ? options.keywords + : eventTypes.keywords; + + if (this.IsEnabled((EventLevel)level, keywords)) + { + WriteMultiMergeInner(eventName, ref options, eventTypes, activityID, childActivityID, values); + } + } + + /// + /// Writes an extended event, where the values of the event are the + /// combined properties of any number of values. This method is + /// intended for use in advanced logging scenarios that support a + /// dynamic set of event context providers. + /// Attention: This API does not check whether the event is enabled or not. + /// Please use WriteMultiMerge to avoid spending CPU cycles for events that are + /// not enabled. + /// + /// + /// The name for the event. If null, the name from eventTypes is used. + /// (Note that providing the event name via the name parameter is slightly + /// less efficient than using the name from eventTypes.) + /// + /// + /// Optional overrides for the event, such as the level, keyword, opcode, + /// activityId, and relatedActivityId. Any settings not specified by options + /// are obtained from eventTypes. + /// + /// + /// Information about the event and the types of the values in the event. + /// Must not be null. Note that the eventTypes object should be created once and + /// saved. It should not be recreated for each event. + /// + /// + /// A pointer to the activity ID GUID to log + /// + /// + /// A pointer to the child activity ID to log (can be null) + /// + /// + /// The values to include in the event. Must not be null. The number and types of + /// the values must match the number and types of the fields described by the + /// eventTypes parameter. + /// + private unsafe void WriteMultiMergeInner( + string eventName, + ref EventSourceOptions options, + TraceLoggingEventTypes eventTypes, + Guid* activityID, + Guid* childActivityID, + params object[] values) + { +#if FEATURE_MANAGED_ETW + int identity = 0; + byte level = (options.valuesSet & EventSourceOptions.levelSet) != 0 + ? options.level + : eventTypes.level; + byte opcode = (options.valuesSet & EventSourceOptions.opcodeSet) != 0 + ? options.opcode + : eventTypes.opcode; + EventTags tags = (options.valuesSet & EventSourceOptions.tagsSet) != 0 + ? options.tags + : eventTypes.Tags; + EventKeywords keywords = (options.valuesSet & EventSourceOptions.keywordsSet) != 0 + ? options.keywords + : eventTypes.keywords; + + var nameInfo = eventTypes.GetNameInfo(eventName ?? eventTypes.Name, tags); + if (nameInfo == null) + { + return; + } + identity = nameInfo.identity; + EventDescriptor descriptor = new EventDescriptor(identity, level, opcode, (long)keywords); + + var pinCount = eventTypes.pinCount; + var scratch = stackalloc byte[eventTypes.scratchSize]; + var descriptors = stackalloc EventData[eventTypes.dataCount + 3]; + var pins = stackalloc GCHandle[pinCount]; + + fixed (byte* + pMetadata0 = this.providerMetadata, + pMetadata1 = nameInfo.nameMetadata, + pMetadata2 = eventTypes.typeMetadata) + { + descriptors[0].SetMetadata(pMetadata0, this.providerMetadata.Length, 2); + descriptors[1].SetMetadata(pMetadata1, nameInfo.nameMetadata.Length, 1); + descriptors[2].SetMetadata(pMetadata2, eventTypes.typeMetadata.Length, 1); + +#if (!ES_BUILD_PCL && !ES_BUILD_PN) + System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions(); +#endif + try + { + DataCollector.ThreadInstance.Enable( + scratch, + eventTypes.scratchSize, + descriptors + 3, + eventTypes.dataCount, + pins, + pinCount); + + for (int i = 0; i < eventTypes.typeInfos.Length; i++) + { + var info = eventTypes.typeInfos[i]; + info.WriteData(TraceLoggingDataCollector.Instance, info.PropertyValueFactory(values[i])); + } + + this.WriteEventRaw( + eventName, + ref descriptor, + activityID, + childActivityID, + (int)(DataCollector.ThreadInstance.Finish() - descriptors), + (IntPtr)descriptors); + } + finally + { + this.WriteCleanup(pins, pinCount); + } + } +#endif // FEATURE_MANAGED_ETW + } + + /// + /// Writes an extended event, where the values of the event have already + /// been serialized in "data". + /// + /// + /// The name for the event. If null, the name from eventTypes is used. + /// (Note that providing the event name via the name parameter is slightly + /// less efficient than using the name from eventTypes.) + /// + /// + /// Optional overrides for the event, such as the level, keyword, opcode, + /// activityId, and relatedActivityId. Any settings not specified by options + /// are obtained from eventTypes. + /// + /// + /// Information about the event and the types of the values in the event. + /// Must not be null. Note that the eventTypes object should be created once and + /// saved. It should not be recreated for each event. + /// + /// + /// A pointer to the activity ID GUID to log + /// + /// + /// A pointer to the child activity ID to log (can be null) + /// + /// + /// The previously serialized values to include in the event. Must not be null. + /// The number and types of the values must match the number and types of the + /// fields described by the eventTypes parameter. + /// + internal unsafe void WriteMultiMerge( + string eventName, + ref EventSourceOptions options, + TraceLoggingEventTypes eventTypes, + Guid* activityID, + Guid* childActivityID, + EventData* data) + { +#if FEATURE_MANAGED_ETW + if (!this.IsEnabled()) + { + return; + } + + fixed (EventSourceOptions* pOptions = &options) + { + EventDescriptor descriptor; + var nameInfo = this.UpdateDescriptor(eventName, eventTypes, ref options, out descriptor); + if (nameInfo == null) + { + return; + } + + // 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 descriptors = stackalloc EventData[eventTypes.dataCount + eventTypes.typeInfos.Length * 2 + 3]; + + fixed (byte* + pMetadata0 = this.providerMetadata, + pMetadata1 = nameInfo.nameMetadata, + pMetadata2 = eventTypes.typeMetadata) + { + descriptors[0].SetMetadata(pMetadata0, this.providerMetadata.Length, 2); + descriptors[1].SetMetadata(pMetadata1, nameInfo.nameMetadata.Length, 1); + descriptors[2].SetMetadata(pMetadata2, eventTypes.typeMetadata.Length, 1); + int numDescrs = 3; + + for (int i = 0; i < eventTypes.typeInfos.Length; i++) + { + // Until M3, we need to morph strings to a counted representation + // When TDH supports null terminated strings, we can remove this. + if (eventTypes.typeInfos[i].DataType == typeof(string)) + { + // Write out the size of the string + descriptors[numDescrs].m_Ptr = (long)&descriptors[numDescrs + 1].m_Size; + descriptors[numDescrs].m_Size = 2; + numDescrs++; + + descriptors[numDescrs].m_Ptr = data[i].m_Ptr; + descriptors[numDescrs].m_Size = data[i].m_Size - 2; // Remove the null terminator + numDescrs++; + } + else + { + descriptors[numDescrs].m_Ptr = data[i].m_Ptr; + descriptors[numDescrs].m_Size = data[i].m_Size; + + // old conventions for bool is 4 bytes, but meta-data assumes 1. + if (data[i].m_Size == 4 && eventTypes.typeInfos[i].DataType == typeof(bool)) + descriptors[numDescrs].m_Size = 1; + + numDescrs++; + } + } + + this.WriteEventRaw( + eventName, + ref descriptor, + activityID, + childActivityID, + numDescrs, + (IntPtr)descriptors); + } + } +#endif // FEATURE_MANAGED_ETW + } + + private unsafe void WriteImpl( + string eventName, + ref EventSourceOptions options, + object data, + Guid* pActivityId, + Guid* pRelatedActivityId, + TraceLoggingEventTypes eventTypes) + { + try + { + fixed (EventSourceOptions* pOptions = &options) + { + EventDescriptor descriptor; + options.Opcode = options.IsOpcodeSet ? options.Opcode : GetOpcodeWithDefault(options.Opcode, eventName); + var nameInfo = this.UpdateDescriptor(eventName, eventTypes, ref options, out descriptor); + if (nameInfo == null) + { + return; + } + +#if FEATURE_MANAGED_ETW + var pinCount = eventTypes.pinCount; + var scratch = stackalloc byte[eventTypes.scratchSize]; + var descriptors = stackalloc EventData[eventTypes.dataCount + 3]; + var pins = stackalloc GCHandle[pinCount]; + + fixed (byte* + pMetadata0 = this.providerMetadata, + pMetadata1 = nameInfo.nameMetadata, + pMetadata2 = eventTypes.typeMetadata) + { + descriptors[0].SetMetadata(pMetadata0, this.providerMetadata.Length, 2); + descriptors[1].SetMetadata(pMetadata1, nameInfo.nameMetadata.Length, 1); + descriptors[2].SetMetadata(pMetadata2, eventTypes.typeMetadata.Length, 1); +#endif // FEATURE_MANAGED_ETW + +#if (!ES_BUILD_PCL && !ES_BUILD_PN) + System.Runtime.CompilerServices.RuntimeHelpers.PrepareConstrainedRegions(); +#endif + EventOpcode opcode = (EventOpcode)descriptor.Opcode; + + Guid activityId = Guid.Empty; + Guid relatedActivityId = Guid.Empty; + if (pActivityId == null && pRelatedActivityId == null && + ((options.ActivityOptions & EventActivityOptions.Disable) == 0)) + { + if (opcode == EventOpcode.Start) + { + m_activityTracker.OnStart(m_name, eventName, 0, ref activityId, ref relatedActivityId, options.ActivityOptions); + } + else if (opcode == EventOpcode.Stop) + { + m_activityTracker.OnStop(m_name, eventName, 0, ref activityId); + } + if (activityId != Guid.Empty) + pActivityId = &activityId; + if (relatedActivityId != Guid.Empty) + pRelatedActivityId = &relatedActivityId; + } + + try + { +#if FEATURE_MANAGED_ETW + DataCollector.ThreadInstance.Enable( + scratch, + eventTypes.scratchSize, + descriptors + 3, + eventTypes.dataCount, + pins, + pinCount); + + var info = eventTypes.typeInfos[0]; + info.WriteData(TraceLoggingDataCollector.Instance, info.PropertyValueFactory(data)); + + this.WriteEventRaw( + eventName, + ref descriptor, + pActivityId, + pRelatedActivityId, + (int)(DataCollector.ThreadInstance.Finish() - descriptors), + (IntPtr)descriptors); +#endif // FEATURE_MANAGED_ETW + + // TODO enable filtering for listeners. + if (m_Dispatchers != null) + { + var eventData = (EventPayload)(eventTypes.typeInfos[0].GetData(data)); + WriteToAllListeners(eventName, ref descriptor, nameInfo.tags, pActivityId, eventData); + } + + } + catch (Exception ex) + { + if (ex is EventSourceException) + throw; + else + ThrowEventSourceException(eventName, ex); + } +#if FEATURE_MANAGED_ETW + finally + { + this.WriteCleanup(pins, pinCount); + } + } +#endif // FEATURE_MANAGED_ETW + } + } + catch (Exception ex) + { + if (ex is EventSourceException) + throw; + else + ThrowEventSourceException(eventName, ex); + } + } + + private unsafe void WriteToAllListeners(string eventName, ref EventDescriptor eventDescriptor, EventTags tags, Guid* pActivityId, EventPayload payload) + { + EventWrittenEventArgs eventCallbackArgs = new EventWrittenEventArgs(this); + eventCallbackArgs.EventName = eventName; + eventCallbackArgs.m_level = (EventLevel) eventDescriptor.Level; + eventCallbackArgs.m_keywords = (EventKeywords) eventDescriptor.Keywords; + eventCallbackArgs.m_opcode = (EventOpcode) eventDescriptor.Opcode; + eventCallbackArgs.m_tags = tags; + + // Self described events do not have an id attached. We mark it internally with -1. + eventCallbackArgs.EventId = -1; + if (pActivityId != null) + eventCallbackArgs.RelatedActivityId = *pActivityId; + + if (payload != null) + { + eventCallbackArgs.Payload = new ReadOnlyCollection((IList)payload.Values); + eventCallbackArgs.PayloadNames = new ReadOnlyCollection((IList)payload.Keys); + } + + DispatchToAllListeners(-1, pActivityId, eventCallbackArgs); + } + +#if (!ES_BUILD_PCL && !ES_BUILD_PN) + [System.Runtime.ConstrainedExecution.ReliabilityContract( + System.Runtime.ConstrainedExecution.Consistency.WillNotCorruptState, + System.Runtime.ConstrainedExecution.Cer.Success)] +#endif + [NonEvent] + private unsafe void WriteCleanup(GCHandle* pPins, int cPins) + { + DataCollector.ThreadInstance.Disable(); + + for (int i = 0; i != cPins; i++) + { + if (IntPtr.Zero != (IntPtr)pPins[i]) + { + pPins[i].Free(); + } + } + } + + private void InitializeProviderMetadata() + { +#if FEATURE_MANAGED_ETW + if (m_traits != null) + { + List traitMetaData = new List(100); + for (int i = 0; i < m_traits.Length - 1; i += 2) + { + if (m_traits[i].StartsWith("ETW_", StringComparison.Ordinal)) + { + string etwTrait = m_traits[i].Substring(4); + byte traitNum; + if (!byte.TryParse(etwTrait, out traitNum)) + { + if (etwTrait == "GROUP") + { + traitNum = 1; + } + else + { + throw new ArgumentException(Resources.GetResourceString("UnknownEtwTrait", etwTrait), "traits"); + } + } + string value = m_traits[i + 1]; + int lenPos = traitMetaData.Count; + traitMetaData.Add(0); // Emit size (to be filled in later) + traitMetaData.Add(0); + traitMetaData.Add(traitNum); // Emit Trait number + var valueLen = AddValueToMetaData(traitMetaData, value) + 3; // Emit the value bytes +3 accounts for 3 bytes we emited above. + traitMetaData[lenPos] = unchecked((byte)valueLen); // Fill in size + traitMetaData[lenPos + 1] = unchecked((byte)(valueLen >> 8)); + } + } + providerMetadata = Statics.MetadataForString(this.Name, 0, traitMetaData.Count, 0); + int startPos = providerMetadata.Length - traitMetaData.Count; + foreach (var b in traitMetaData) + providerMetadata[startPos++] = b; + } + else + providerMetadata = Statics.MetadataForString(this.Name, 0, 0, 0); +#endif //FEATURE_MANAGED_ETW + } + + private static int AddValueToMetaData(List metaData, string value) + { + if (value.Length == 0) + return 0; + + int startPos = metaData.Count; + char firstChar = value[0]; + + if (firstChar == '@') + metaData.AddRange(Encoding.UTF8.GetBytes(value.Substring(1))); + else if (firstChar == '{') + metaData.AddRange(new Guid(value).ToByteArray()); + else if (firstChar == '#') + { + for (int i = 1; i < value.Length; i++) + { + if (value[i] != ' ') // Skip spaces between bytes. + { + if (!(i + 1 < value.Length)) + { + throw new ArgumentException(Resources.GetResourceString("EvenHexDigits"), "traits"); + } + metaData.Add((byte)(HexDigit(value[i]) * 16 + HexDigit(value[i + 1]))); + i++; + } + } + } + else if ('A' <= firstChar || ' ' == firstChar) // Is it alphabetic or space (excludes digits and most punctuation). + { + metaData.AddRange(Encoding.UTF8.GetBytes(value)); + } + else + { + throw new ArgumentException(Resources.GetResourceString("IllegalValue", value), "traits"); + } + + return metaData.Count - startPos; + } + + /// + /// Returns a value 0-15 if 'c' is a hexadecimal digit. If it throws an argument exception. + /// + private static int HexDigit(char c) + { + if ('0' <= c && c <= '9') + { + return (c - '0'); + } + if ('a' <= c) + { + c = unchecked((char)(c - ('a' - 'A'))); // Convert to lower case + } + if ('A' <= c && c <= 'F') + { + return (c - 'A' + 10); + } + + throw new ArgumentException(Resources.GetResourceString("BadHexDigit", c), "traits"); + } + + private NameInfo UpdateDescriptor( + string name, + TraceLoggingEventTypes eventInfo, + ref EventSourceOptions options, + out EventDescriptor descriptor) + { + NameInfo nameInfo = null; + int identity = 0; + byte level = (options.valuesSet & EventSourceOptions.levelSet) != 0 + ? options.level + : eventInfo.level; + byte opcode = (options.valuesSet & EventSourceOptions.opcodeSet) != 0 + ? options.opcode + : eventInfo.opcode; + EventTags tags = (options.valuesSet & EventSourceOptions.tagsSet) != 0 + ? options.tags + : eventInfo.Tags; + EventKeywords keywords = (options.valuesSet & EventSourceOptions.keywordsSet) != 0 + ? options.keywords + : eventInfo.keywords; + + if (this.IsEnabled((EventLevel)level, keywords)) + { + nameInfo = eventInfo.GetNameInfo(name ?? eventInfo.Name, tags); + identity = nameInfo.identity; + } + + descriptor = new EventDescriptor(identity, level, opcode, (long)keywords); + return nameInfo; + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTraits.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTraits.cs new file mode 100644 index 0000000000..e808a8823c --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTraits.cs @@ -0,0 +1,28 @@ +// 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; + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// Tags are flags that are not interpreted by EventSource but are passed along + /// to the EventListener. The EventListener determines the semantics of the flags. + /// + [Flags] + public enum EventTags + { + /// + /// No special traits are added to the event. + /// + None = 0, + + /* Bits below 0x10000 are available for any use by the provider. */ + /* Bits at or above 0x10000 are reserved for definition by Microsoft. */ + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTypes.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTypes.cs new file mode 100644 index 0000000000..c2239671bb --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTypes.cs @@ -0,0 +1,262 @@ +// 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.Collections.Generic; +using Interlocked = System.Threading.Interlocked; + +#if !ES_BUILD_AGAINST_DOTNET_V35 +using Contract = System.Diagnostics.Contracts.Contract; +#else +using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract; +#endif + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// TraceLogging: Used when calling EventSource.WriteMultiMerge. + /// Stores the type information to use when writing the event fields. + /// + public class TraceLoggingEventTypes + { + internal readonly TraceLoggingTypeInfo[] typeInfos; + internal readonly string name; + internal readonly EventTags tags; + internal readonly byte level; + internal readonly byte opcode; + internal readonly EventKeywords keywords; + internal readonly byte[] typeMetadata; + internal readonly int scratchSize; + internal readonly int dataCount; + internal readonly int pinCount; + private ConcurrentSet, NameInfo> nameInfos; + + /// + /// Initializes a new instance of TraceLoggingEventTypes corresponding + /// to the name, flags, and types provided. Always uses the default + /// TypeInfo for each Type. + /// + /// + /// The name to use when the name parameter passed to + /// EventSource.Write is null. This value must not be null. + /// + /// + /// Tags to add to the event if the tags are not set via options. + /// + /// + /// The types of the fields in the event. This value must not be null. + /// + internal TraceLoggingEventTypes( + string name, + EventTags tags, + params Type[] types) + : this(tags, name, MakeArray(types)) + { + return; + } + + /// + /// Returns a new instance of TraceLoggingEventInfo corresponding to the name, + /// flags, and typeInfos provided. + /// + /// + /// The name to use when the name parameter passed to + /// EventSource.Write is null. This value must not be null. + /// + /// + /// Tags to add to the event if the tags are not set via options. + /// + /// + /// The types of the fields in the event. This value must not be null. + /// + /// + /// An instance of TraceLoggingEventInfo with DefaultName set to the specified name + /// and with the specified typeInfos. + /// + internal TraceLoggingEventTypes( + string name, + EventTags tags, + params TraceLoggingTypeInfo[] typeInfos) + : this(tags, name, MakeArray(typeInfos)) + { + return; + } + + internal TraceLoggingEventTypes( + string name, + EventTags tags, + System.Reflection.ParameterInfo[] paramInfos) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + Contract.EndContractBlock(); + + this.typeInfos = MakeArray(paramInfos); + this.name = name; + this.tags = tags; + this.level = Statics.DefaultLevel; + + var collector = new TraceLoggingMetadataCollector(); + for (int i = 0; i < typeInfos.Length; ++i) + { + var typeInfo = typeInfos[i]; + this.level = Statics.Combine((int)typeInfo.Level, this.level); + this.opcode = Statics.Combine((int)typeInfo.Opcode, this.opcode); + this.keywords |= typeInfo.Keywords; + var paramName = paramInfos[i].Name; + if (Statics.ShouldOverrideFieldName(paramName)) + { + paramName = typeInfo.Name; + } + typeInfo.WriteMetadata(collector, paramName, EventFieldFormat.Default); + } + + this.typeMetadata = collector.GetMetadata(); + this.scratchSize = collector.ScratchSize; + this.dataCount = collector.DataCount; + this.pinCount = collector.PinCount; + } + + private TraceLoggingEventTypes( + EventTags tags, + string defaultName, + TraceLoggingTypeInfo[] typeInfos) + { + if (defaultName == null) + { + throw new ArgumentNullException(nameof(defaultName)); + } + + Contract.EndContractBlock(); + + this.typeInfos = typeInfos; + this.name = defaultName; + this.tags = tags; + this.level = Statics.DefaultLevel; + + var collector = new TraceLoggingMetadataCollector(); + foreach (var typeInfo in typeInfos) + { + this.level = Statics.Combine((int)typeInfo.Level, this.level); + this.opcode = Statics.Combine((int)typeInfo.Opcode, this.opcode); + this.keywords |= typeInfo.Keywords; + typeInfo.WriteMetadata(collector, null, EventFieldFormat.Default); + } + + this.typeMetadata = collector.GetMetadata(); + this.scratchSize = collector.ScratchSize; + this.dataCount = collector.DataCount; + this.pinCount = collector.PinCount; + } + + /// + /// Gets the default name that will be used for events with this descriptor. + /// + internal string Name + { + get { return this.name; } + } + + /// + /// Gets the default level that will be used for events with this descriptor. + /// + internal EventLevel Level + { + get { return (EventLevel)this.level; } + } + + /// + /// Gets the default opcode that will be used for events with this descriptor. + /// + internal EventOpcode Opcode + { + get { return (EventOpcode)this.opcode; } + } + + /// + /// Gets the default set of keywords that will added to events with this descriptor. + /// + internal EventKeywords Keywords + { + get { return (EventKeywords)this.keywords; } + } + + /// + /// Gets the default tags that will be added events with this descriptor. + /// + internal EventTags Tags + { + get { return this.tags; } + } + + internal NameInfo GetNameInfo(string name, EventTags tags) + { + var ret = this.nameInfos.TryGet(new KeyValuePair(name, tags)); + if (ret == null) + { + ret = this.nameInfos.GetOrAdd(new NameInfo(name, tags, this.typeMetadata.Length)); + } + + return ret; + } + + private TraceLoggingTypeInfo[] MakeArray(System.Reflection.ParameterInfo[] paramInfos) + { + if (paramInfos == null) + { + throw new ArgumentNullException(nameof(paramInfos)); + } + + Contract.EndContractBlock(); + + var recursionCheck = new List(paramInfos.Length); + var result = new TraceLoggingTypeInfo[paramInfos.Length]; + for (int i = 0; i < paramInfos.Length; ++i) + { + result[i] = TraceLoggingTypeInfo.GetInstance(paramInfos[i].ParameterType, recursionCheck); + } + + return result; + } + + private static TraceLoggingTypeInfo[] MakeArray(Type[] types) + { + if (types == null) + { + throw new ArgumentNullException(nameof(types)); + } + + Contract.EndContractBlock(); + + var recursionCheck = new List(types.Length); + var result = new TraceLoggingTypeInfo[types.Length]; + for (int i = 0; i < types.Length; i++) + { + result[i] = TraceLoggingTypeInfo.GetInstance(types[i], recursionCheck); + } + + return result; + } + + private static TraceLoggingTypeInfo[] MakeArray( + TraceLoggingTypeInfo[] typeInfos) + { + if (typeInfos == null) + { + throw new ArgumentNullException(nameof(typeInfos)); + } + + Contract.EndContractBlock(); + + return (TraceLoggingTypeInfo[])typeInfos.Clone(); ; + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingMetadataCollector.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingMetadataCollector.cs new file mode 100644 index 0000000000..41225c8626 --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingMetadataCollector.cs @@ -0,0 +1,370 @@ +// 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.Collections.Generic; + +#if ES_BUILD_STANDALONE +using Environment = Microsoft.Diagnostics.Tracing.Internal.Environment; +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// TraceLogging: used when implementing a custom TraceLoggingTypeInfo. + /// An instance of this type is provided to the TypeInfo.WriteMetadata method. + /// + internal class TraceLoggingMetadataCollector + { + private readonly Impl impl; + private readonly FieldMetadata currentGroup; + private int bufferedArrayFieldCount = int.MinValue; + + /// + /// Creates a root-level collector. + /// + internal TraceLoggingMetadataCollector() + { + this.impl = new Impl(); + } + + /// + /// Creates a collector for a group. + /// + /// Parent collector + /// The field that starts the group + private TraceLoggingMetadataCollector( + TraceLoggingMetadataCollector other, + FieldMetadata group) + { + this.impl = other.impl; + this.currentGroup = group; + } + + /// + /// The field tags to be used for the next field. + /// This will be reset to None each time a field is written. + /// + internal EventFieldTags Tags + { + get; + set; + } + + internal int ScratchSize + { + get { return this.impl.scratchSize; } + } + + internal int DataCount + { + get { return this.impl.dataCount; } + } + + internal int PinCount + { + get { return this.impl.pinCount; } + } + + private bool BeginningBufferedArray + { + get { return this.bufferedArrayFieldCount == 0; } + } + + /// + /// Call this method to add a group to the event and to return + /// a new metadata collector that can be used to add fields to the + /// group. After all of the fields in the group have been written, + /// switch back to the original metadata collector to add fields + /// outside of the group. + /// Special-case: if name is null, no group is created, and AddGroup + /// returns the original metadata collector. This is useful when + /// adding the top-level group for an event. + /// Note: do not use the original metadata collector while the group's + /// metadata collector is in use, and do not use the group's metadata + /// collector after switching back to the original. + /// + /// + /// The name of the group. If name is null, the call to AddGroup is a + /// no-op (collector.AddGroup(null) returns collector). + /// + /// + /// A new metadata collector that can be used to add fields to the group. + /// + public TraceLoggingMetadataCollector AddGroup(string name) + { + TraceLoggingMetadataCollector result = this; + + if (name != null || // Normal. + this.BeginningBufferedArray) // Error, FieldMetadata's constructor will throw the appropriate exception. + { + var newGroup = new FieldMetadata( + name, + TraceLoggingDataType.Struct, + this.Tags, + this.BeginningBufferedArray); + this.AddField(newGroup); + result = new TraceLoggingMetadataCollector(this, newGroup); + } + + return result; + } + + /// + /// Adds a scalar field to an event. + /// + /// + /// The name to use for the added field. This value must not be null. + /// + /// + /// The type code for the added field. This must be a fixed-size type + /// (e.g. string types are not supported). + /// + public void AddScalar(string name, TraceLoggingDataType type) + { + int size; + switch ((TraceLoggingDataType)((int)type & Statics.InTypeMask)) + { + case TraceLoggingDataType.Int8: + case TraceLoggingDataType.UInt8: + case TraceLoggingDataType.Char8: + size = 1; + break; + case TraceLoggingDataType.Int16: + case TraceLoggingDataType.UInt16: + case TraceLoggingDataType.Char16: + size = 2; + break; + case TraceLoggingDataType.Int32: + case TraceLoggingDataType.UInt32: + case TraceLoggingDataType.HexInt32: + case TraceLoggingDataType.Float: + case TraceLoggingDataType.Boolean32: + size = 4; + break; + case TraceLoggingDataType.Int64: + case TraceLoggingDataType.UInt64: + case TraceLoggingDataType.HexInt64: + case TraceLoggingDataType.Double: + case TraceLoggingDataType.FileTime: + size = 8; + break; + case TraceLoggingDataType.Guid: + case TraceLoggingDataType.SystemTime: + size = 16; + break; + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } + + this.impl.AddScalar(size); + this.AddField(new FieldMetadata(name, type, this.Tags, this.BeginningBufferedArray)); + } + + /// + /// Adds a binary-format field to an event. + /// Compatible with core types: Binary, CountedUtf16String, CountedMbcsString. + /// Compatible with dataCollector methods: AddBinary(string), AddArray(Any8bitType[]). + /// + /// + /// The name to use for the added field. This value must not be null. + /// + /// + /// The type code for the added field. This must be a Binary or CountedString type. + /// + public void AddBinary(string name, TraceLoggingDataType type) + { + switch ((TraceLoggingDataType)((int)type & Statics.InTypeMask)) + { + case TraceLoggingDataType.Binary: + case TraceLoggingDataType.CountedMbcsString: + case TraceLoggingDataType.CountedUtf16String: + break; + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } + + this.impl.AddScalar(2); + this.impl.AddNonscalar(); + this.AddField(new FieldMetadata(name, type, this.Tags, this.BeginningBufferedArray)); + } + + /// + /// Adds an array field to an event. + /// + /// + /// The name to use for the added field. This value must not be null. + /// + /// + /// The type code for the added field. This must be a fixed-size type + /// or a string type. In the case of a string type, this adds an array + /// of characters, not an array of strings. + /// + public void AddArray(string name, TraceLoggingDataType type) + { + switch ((TraceLoggingDataType)((int)type & Statics.InTypeMask)) + { + case TraceLoggingDataType.Utf16String: + case TraceLoggingDataType.MbcsString: + case TraceLoggingDataType.Int8: + case TraceLoggingDataType.UInt8: + case TraceLoggingDataType.Int16: + case TraceLoggingDataType.UInt16: + case TraceLoggingDataType.Int32: + case TraceLoggingDataType.UInt32: + case TraceLoggingDataType.Int64: + case TraceLoggingDataType.UInt64: + case TraceLoggingDataType.Float: + case TraceLoggingDataType.Double: + case TraceLoggingDataType.Boolean32: + case TraceLoggingDataType.Guid: + case TraceLoggingDataType.FileTime: + case TraceLoggingDataType.HexInt32: + case TraceLoggingDataType.HexInt64: + case TraceLoggingDataType.Char16: + case TraceLoggingDataType.Char8: + break; + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } + + if (this.BeginningBufferedArray) + { + throw new NotSupportedException(Resources.GetResourceString("EventSource_NotSupportedNestedArraysEnums")); + } + + this.impl.AddScalar(2); + this.impl.AddNonscalar(); + this.AddField(new FieldMetadata(name, type, this.Tags, true)); + } + + public void BeginBufferedArray() + { + if (this.bufferedArrayFieldCount >= 0) + { + throw new NotSupportedException(Resources.GetResourceString("EventSource_NotSupportedNestedArraysEnums")); + } + + this.bufferedArrayFieldCount = 0; + this.impl.BeginBuffered(); + } + + public void EndBufferedArray() + { + if (this.bufferedArrayFieldCount != 1) + { + throw new InvalidOperationException(Resources.GetResourceString("EventSource_IncorrentlyAuthoredTypeInfo")); + } + + this.bufferedArrayFieldCount = int.MinValue; + this.impl.EndBuffered(); + } + + /// + /// Adds a custom-serialized field to an event. + /// + /// + /// The name to use for the added field. This value must not be null. + /// + /// The encoding type for the field. + /// Additional information needed to decode the field, if any. + public void AddCustom(string name, TraceLoggingDataType type, byte[] metadata) + { + if (this.BeginningBufferedArray) + { + throw new NotSupportedException(Resources.GetResourceString("EventSource_NotSupportedCustomSerializedData")); + } + + this.impl.AddScalar(2); + this.impl.AddNonscalar(); + this.AddField(new FieldMetadata( + name, + type, + this.Tags, + metadata)); + } + + internal byte[] GetMetadata() + { + var size = this.impl.Encode(null); + var metadata = new byte[size]; + this.impl.Encode(metadata); + return metadata; + } + + private void AddField(FieldMetadata fieldMetadata) + { + this.Tags = EventFieldTags.None; + this.bufferedArrayFieldCount++; + this.impl.fields.Add(fieldMetadata); + + if (this.currentGroup != null) + { + this.currentGroup.IncrementStructFieldCount(); + } + } + + private class Impl + { + internal readonly List fields = new List(); + internal short scratchSize; + internal sbyte dataCount; + internal sbyte pinCount; + private int bufferNesting; + private bool scalar; + + public void AddScalar(int size) + { + if (this.bufferNesting == 0) + { + if (!this.scalar) + { + this.dataCount = checked((sbyte)(this.dataCount + 1)); + } + + this.scalar = true; + this.scratchSize = checked((short)(this.scratchSize + size)); + } + } + + public void AddNonscalar() + { + if (this.bufferNesting == 0) + { + this.scalar = false; + this.pinCount = checked((sbyte)(this.pinCount + 1)); + this.dataCount = checked((sbyte)(this.dataCount + 1)); + } + } + + public void BeginBuffered() + { + if (this.bufferNesting == 0) + { + this.AddNonscalar(); + } + + this.bufferNesting++; + } + + public void EndBuffered() + { + this.bufferNesting--; + } + + public int Encode(byte[] metadata) + { + int size = 0; + + foreach (var field in this.fields) + { + field.Encode(ref size, metadata); + } + + return size; + } + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingTypeInfo.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingTypeInfo.cs new file mode 100644 index 0000000000..d68e106b0b --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingTypeInfo.cs @@ -0,0 +1,209 @@ +// 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.Collections.Generic; + +#if !ES_BUILD_AGAINST_DOTNET_V35 +using Contract = System.Diagnostics.Contracts.Contract; +#else +using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract; +#endif + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// TraceLogging: used when implementing a custom TraceLoggingTypeInfo. + /// Non-generic base class for TraceLoggingTypeInfo<DataType>. Do not derive + /// from this class. Instead, derive from TraceLoggingTypeInfo<DataType>. + /// + internal abstract class TraceLoggingTypeInfo + { + private readonly string name; + private readonly EventKeywords keywords; + private readonly EventLevel level = (EventLevel)(-1); + private readonly EventOpcode opcode = (EventOpcode)(-1); + private readonly EventTags tags; + private readonly Type dataType; + private readonly Func propertyValueFactory; + + internal TraceLoggingTypeInfo(Type dataType) + { + if (dataType == null) + { + throw new ArgumentNullException(nameof(dataType)); + } + + Contract.EndContractBlock(); + + this.name = dataType.Name; + this.dataType = dataType; + this.propertyValueFactory = PropertyValue.GetFactory(dataType); + } + + internal TraceLoggingTypeInfo( + Type dataType, + string name, + EventLevel level, + EventOpcode opcode, + EventKeywords keywords, + EventTags tags) + { + if (dataType == null) + { + throw new ArgumentNullException(nameof(dataType)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + Contract.EndContractBlock(); + + Statics.CheckName(name); + + this.name = name; + this.keywords = keywords; + this.level = level; + this.opcode = opcode; + this.tags = tags; + this.dataType = dataType; + this.propertyValueFactory = PropertyValue.GetFactory(dataType); + } + + /// + /// Gets the name to use for the event if this type is the top-level type, + /// or the name to use for an implicitly-named field. + /// Never null. + /// + public string Name + { + get { return this.name; } + } + + /// + /// Gets the event level associated with this type. Any value in the range 0..255 + /// is an associated event level. Any value outside the range 0..255 is invalid and + /// indicates that this type has no associated event level. + /// + public EventLevel Level + { + get { return this.level; } + } + + /// + /// Gets the event opcode associated with this type. Any value in the range 0..255 + /// is an associated event opcode. Any value outside the range 0..255 is invalid and + /// indicates that this type has no associated event opcode. + /// + public EventOpcode Opcode + { + get { return this.opcode; } + } + + /// + /// Gets the keyword(s) associated with this type. + /// + public EventKeywords Keywords + { + get { return this.keywords; } + } + + /// + /// Gets the event tags associated with this type. + /// + public EventTags Tags + { + get { return this.tags; } + } + + internal Type DataType + { + get { return this.dataType; } + } + + internal Func PropertyValueFactory + { + get { return this.propertyValueFactory; } + } + + /// + /// When overridden by a derived class, writes the metadata (schema) for + /// this type. Note that the sequence of operations in WriteMetadata should be + /// essentially identical to the sequence of operations in + /// WriteData/WriteObjectData. Otherwise, the metadata and data will not match, + /// which may cause trouble when decoding the event. + /// + /// + /// The object that collects metadata for this object's type. Metadata is written + /// by calling methods on the collector object. Note that if the type contains + /// sub-objects, the implementation of this method may need to call the + /// WriteMetadata method for the type of the sub-object, e.g. by calling + /// TraceLoggingTypeInfo<SubType>.Instance.WriteMetadata(...). + /// + /// + /// The name of the property that contains an object of this type, or null if this + /// object is being written as a top-level object of an event. Typical usage + /// is to pass this value to collector.AddGroup. + /// + /// + /// The format attribute for the field that contains an object of this type. + /// + public abstract void WriteMetadata( + TraceLoggingMetadataCollector collector, + string name, + EventFieldFormat format); + + /// + /// Refer to TraceLoggingTypeInfo.WriteObjectData for information about this + /// method. + /// + /// + /// Refer to TraceLoggingTypeInfo.WriteObjectData for information about this + /// method. + /// + /// + /// Refer to TraceLoggingTypeInfo.WriteObjectData for information about this + /// method. + /// + public abstract void WriteData( + TraceLoggingDataCollector collector, + PropertyValue value); + + /// + /// Fetches the event parameter data for internal serialization. + /// + /// + /// + public virtual object GetData(object value) + { + return value; + } + + [ThreadStatic] // per-thread cache to avoid synchronization + private static Dictionary threadCache; + + public static TraceLoggingTypeInfo GetInstance(Type type, List recursionCheck) + { + var cache = threadCache ?? (threadCache = new Dictionary()); + + TraceLoggingTypeInfo instance; + if (!cache.TryGetValue(type, out instance)) + { + if (recursionCheck == null) + recursionCheck = new List(); + var recursionCheckCount = recursionCheck.Count; + instance = Statics.CreateDefaultTypeInfo(type, recursionCheck); + cache[type] = instance; + recursionCheck.RemoveRange(recursionCheckCount, recursionCheck.Count - recursionCheckCount); + } + return instance; + } + } +} diff --git a/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TypeAnalysis.cs b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TypeAnalysis.cs new file mode 100644 index 0000000000..42cdde5f6a --- /dev/null +++ b/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TypeAnalysis.cs @@ -0,0 +1,103 @@ +// 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.Collections.Generic; +using System.Reflection; + + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// TraceLogging: stores the per-type information obtained by reflecting over a type. + /// + internal sealed class TypeAnalysis + { + internal readonly PropertyAnalysis[] properties; + internal readonly string name; + internal readonly EventKeywords keywords; + internal readonly EventLevel level = (EventLevel)(-1); + internal readonly EventOpcode opcode = (EventOpcode)(-1); + internal readonly EventTags tags; + + public TypeAnalysis( + Type dataType, + EventDataAttribute eventAttrib, + List recursionCheck) + { + var propertyInfos = Statics.GetProperties(dataType); + var propertyList = new List(); + + foreach (var propertyInfo in propertyInfos) + { + if (Statics.HasCustomAttribute(propertyInfo, typeof(EventIgnoreAttribute))) + { + continue; + } + + if (!propertyInfo.CanRead || + propertyInfo.GetIndexParameters().Length != 0) + { + continue; + } + + MethodInfo getterInfo = Statics.GetGetMethod(propertyInfo); + if (getterInfo == null) + { + continue; + } + + if (getterInfo.IsStatic || !getterInfo.IsPublic) + { + continue; + } + + var propertyType = propertyInfo.PropertyType; + var propertyTypeInfo = TraceLoggingTypeInfo.GetInstance(propertyType, recursionCheck); + var fieldAttribute = Statics.GetCustomAttribute(propertyInfo); + + string propertyName = + fieldAttribute != null && fieldAttribute.Name != null + ? fieldAttribute.Name + : Statics.ShouldOverrideFieldName(propertyInfo.Name) + ? propertyTypeInfo.Name + : propertyInfo.Name; + propertyList.Add(new PropertyAnalysis( + propertyName, + propertyInfo, + propertyTypeInfo, + fieldAttribute)); + } + + this.properties = propertyList.ToArray(); + + foreach (var property in this.properties) + { + var typeInfo = property.typeInfo; + this.level = (EventLevel)Statics.Combine((int)typeInfo.Level, (int)this.level); + this.opcode = (EventOpcode)Statics.Combine((int)typeInfo.Opcode, (int)this.opcode); + this.keywords |= typeInfo.Keywords; + this.tags |= typeInfo.Tags; + } + + if (eventAttrib != null) + { + this.level = (EventLevel)Statics.Combine((int)eventAttrib.Level, (int)this.level); + this.opcode = (EventOpcode)Statics.Combine((int)eventAttrib.Opcode, (int)this.opcode); + this.keywords |= eventAttrib.Keywords; + this.tags |= eventAttrib.Tags; + this.name = eventAttrib.Name; + } + + if (this.name == null) + { + this.name = dataType.Name; + } + } + } +} -- cgit v1.2.3