From ec6d26db976399385ad7aa6c68e843965257ac4d Mon Sep 17 00:00:00 2001 From: Simon Nattress Date: Thu, 6 Apr 2017 18:02:03 -0700 Subject: [PATCH] Merge pull request dotnet/corertdotnet/coreclr#3226 from dotnet/nmirror Merge nmirror to master Signed-off-by: dotnet-bot Commit migrated from https://github.com/dotnet/coreclr/commit/157a0aee00e8623811c3b9b1ff962a2e2ae8bb67 --- .../shared/System.Private.CoreLib.Shared.projitems | 42 + .../System/Diagnostics/Tracing/ActivityTracker.cs | 665 ++ .../Diagnostics/Tracing/EventActivityOptions.cs | 39 + .../System/Diagnostics/Tracing/EventCounter.cs | 436 ++ .../System/Diagnostics/Tracing/EventDescriptor.cs | 195 + .../System/Diagnostics/Tracing/EventProvider.cs | 1207 ++++ .../System/Diagnostics/Tracing/EventSource.cs | 6929 ++++++++++++++++++++ .../Diagnostics/Tracing/EventSourceException.cs | 53 + .../Diagnostics/Tracing/FrameworkEventSource.cs | 662 ++ .../System/Diagnostics/Tracing/StubEnvironment.cs | 381 ++ .../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 + .../shared/System/Diagnostics/Tracing/Winmeta.cs | 196 + .../mscorlib/shared/System/IO/FileLoadException.cs | 102 + 42 files changed, 16609 insertions(+) create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/ActivityTracker.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventActivityOptions.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventCounter.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventDescriptor.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventProvider.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventSource.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventSourceException.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/FrameworkEventSource.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/StubEnvironment.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ArrayTypeInfo.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSet.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSetItem.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/DataCollector.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EmptyStruct.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EnumHelper.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EnumerableTypeInfo.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventDataAttribute.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldAttribute.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldFormat.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventIgnoreAttribute.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventPayload.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceActivity.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceOptions.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/FieldMetadata.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/InvokeTypeInfo.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/NameInfo.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyAnalysis.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyValue.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleEventTypes.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleTypeInfos.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/Statics.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataCollector.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataType.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTraits.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTypes.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingMetadataCollector.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingTypeInfo.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TypeAnalysis.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/Winmeta.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/IO/FileLoadException.cs diff --git a/src/coreclr/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems b/src/coreclr/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems index 7c6d039..c152fea 100644 --- a/src/coreclr/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/coreclr/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems @@ -186,6 +186,7 @@ + @@ -380,4 +381,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/ActivityTracker.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/ActivityTracker.cs new file mode 100644 index 0000000..e32abd2 --- /dev/null +++ b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/ActivityTracker.cs @@ -0,0 +1,665 @@ +// 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.Diagnostics; +using System.Threading; +#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 +using System.Threading.Tasks; +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// Tracks activities. This is meant to be a singleton (accessed by the ActivityTracer.Instance static property) + /// + /// Logically this is simply holds the m_current variable that holds the async local that holds the current ActivityInfo + /// An ActivityInfo is represents a activity (which knows its creator and thus knows its path). + /// + /// Most of the magic is in the async local (it gets copied to new tasks) + /// + /// On every start event call OnStart + /// + /// Guid activityID; + /// Guid relatedActivityID; + /// if (OnStart(activityName, out activityID, out relatedActivityID, ForceStop, options)) + /// // Log Start event with activityID and relatedActivityID + /// + /// On every stop event call OnStop + /// + /// Guid activityID; + /// if (OnStop(activityName, ref activityID ForceStop)) + /// // Stop event with activityID + /// + /// On any normal event log the event with activityTracker.CurrentActivityId + /// + internal class ActivityTracker + { + + /// + /// Called on work item begins. The activity name = providerName + activityName without 'Start' suffix. + /// It updates CurrentActivityId to track. + /// + /// It returns true if the Start should be logged, otherwise (if it is illegal recursion) it return false. + /// + /// The start event should use as its activity ID the CurrentActivityId AFTER calling this routine and its + /// RelatedActivityID the CurrentActivityId BEFORE calling this routine (the creator). + /// + /// If activity tracing is not on, then activityId and relatedActivityId are not set + /// + public void OnStart(string providerName, string activityName, int task, ref Guid activityId, ref Guid relatedActivityId, EventActivityOptions options) + { + if (m_current == null) // We are not enabled + { + // We used to rely on the TPL provider turning us on, but that has the disadvantage that you don't get Start-Stop tracking + // until you use Tasks for the first time (which you may never do). Thus we change it to pull rather tan push for whether + // we are enabled. + if (m_checkedForEnable) + return; + m_checkedForEnable = true; + if (TplEtwProvider.Log.IsEnabled(EventLevel.Informational, TplEtwProvider.Keywords.TasksFlowActivityIds)) + Enable(); + if (m_current == null) + return; + } + + + Debug.Assert((options & EventActivityOptions.Disable) == 0); + + var currentActivity = m_current.Value; + var fullActivityName = NormalizeActivityName(providerName, activityName, task); + + var etwLog = TplEtwProvider.Log; + if (etwLog.Debug) + { + etwLog.DebugFacilityMessage("OnStartEnter", fullActivityName); + etwLog.DebugFacilityMessage("OnStartEnterActivityState", ActivityInfo.LiveActivities(currentActivity)); + } + + if (currentActivity != null) + { + // Stop activity tracking if we reached the maximum allowed depth + if (currentActivity.m_level >= MAX_ACTIVITY_DEPTH) + { + activityId = Guid.Empty; + relatedActivityId = Guid.Empty; + if (etwLog.Debug) + etwLog.DebugFacilityMessage("OnStartRET", "Fail"); + return; + } + // Check for recursion, and force-stop any activities if the activity already started. + if ((options & EventActivityOptions.Recursive) == 0) + { + ActivityInfo existingActivity = FindActiveActivity(fullActivityName, currentActivity); + if (existingActivity != null) + { + OnStop(providerName, activityName, task, ref activityId); + currentActivity = m_current.Value; + } + } + } + + // Get a unique ID for this activity. + long id; + if (currentActivity == null) + id = Interlocked.Increment(ref m_nextId); + else + id = Interlocked.Increment(ref currentActivity.m_lastChildID); + + // The previous ID is my 'causer' and becomes my related activity ID + relatedActivityId = EventSource.CurrentThreadActivityId; + + // Add to the list of started but not stopped activities. + ActivityInfo newActivity = new ActivityInfo(fullActivityName, id, currentActivity, relatedActivityId, options); + m_current.Value = newActivity; + + // Remember the current ID so we can log it + activityId = newActivity.ActivityId; + + if (etwLog.Debug) + { + etwLog.DebugFacilityMessage("OnStartRetActivityState", ActivityInfo.LiveActivities(newActivity)); + etwLog.DebugFacilityMessage1("OnStartRet", activityId.ToString(), relatedActivityId.ToString()); + } + } + + /// + /// Called when a work item stops. The activity name = providerName + activityName without 'Stop' suffix. + /// It updates m_current variable to track this fact. The Stop event associated with stop should log the ActivityID associated with the event. + /// + /// If activity tracing is not on, then activityId and relatedActivityId are not set + /// + public void OnStop(string providerName, string activityName, int task, ref Guid activityId) + { + if (m_current == null) // We are not enabled + return; + + var fullActivityName = NormalizeActivityName(providerName, activityName, task); + + var etwLog = TplEtwProvider.Log; + if (etwLog.Debug) + { + etwLog.DebugFacilityMessage("OnStopEnter", fullActivityName); + etwLog.DebugFacilityMessage("OnStopEnterActivityState", ActivityInfo.LiveActivities(m_current.Value)); + } + + for (; ; ) // This is a retry loop. + { + ActivityInfo currentActivity = m_current.Value; + ActivityInfo newCurrentActivity = null; // if we have seen any live activities (orphans), at he first one we have seen. + + // Search to find the activity to stop in one pass. This insures that we don't let one mistake + // (stopping something that was not started) cause all active starts to be stopped + // By first finding the target start to stop we are more robust. + ActivityInfo activityToStop = FindActiveActivity(fullActivityName, currentActivity); + + // ignore stops where we can't find a start because we may have popped them previously. + if (activityToStop == null) + { + activityId = Guid.Empty; + // TODO add some logging about this. Basically could not find matching start. + if (etwLog.Debug) + etwLog.DebugFacilityMessage("OnStopRET", "Fail"); + return; + } + + activityId = activityToStop.ActivityId; + + // See if there are any orphans that need to be stopped. + ActivityInfo orphan = currentActivity; + while (orphan != activityToStop && orphan != null) + { + if (orphan.m_stopped != 0) // Skip dead activities. + { + orphan = orphan.m_creator; + continue; + } + if (orphan.CanBeOrphan()) + { + // We can't pop anything after we see a valid orphan, remember this for later when we update m_current. + if (newCurrentActivity == null) + newCurrentActivity = orphan; + } + else + { + orphan.m_stopped = 1; + Debug.Assert(orphan.m_stopped != 0); + } + orphan = orphan.m_creator; + } + + // try to Stop the activity atomically. Other threads may be trying to do this as well. + if (Interlocked.CompareExchange(ref activityToStop.m_stopped, 1, 0) == 0) + { + // I succeeded stopping this activity. Now we update our m_current pointer + + // If I haven't yet determined the new current activity, it is my creator. + if (newCurrentActivity == null) + newCurrentActivity = activityToStop.m_creator; + + m_current.Value = newCurrentActivity; + + if (etwLog.Debug) + { + etwLog.DebugFacilityMessage("OnStopRetActivityState", ActivityInfo.LiveActivities(newCurrentActivity)); + etwLog.DebugFacilityMessage("OnStopRet", activityId.ToString()); + } + return; + } + // We failed to stop it. We must have hit a race to stop it. Just start over and try again. + } + } + + /// + /// Turns on activity tracking. It is sticky, once on it stays on (race issues otherwise) + /// + public void Enable() + { + if (m_current == null) + { + // Catch the not Implemented + try + { + m_current = new AsyncLocal(ActivityChanging); + } + catch (NotImplementedException) { +#if (!ES_BUILD_PCL && ! ES_BUILD_PN) + // send message to debugger without delay + System.Diagnostics.Debugger.Log(0, null, "Activity Enabled() called but AsyncLocals Not Supported (pre V4.6). Ignoring Enable"); +#endif + } + } + } + + /// + /// An activity tracker is a singleton, this is how you get the one and only instance. + /// + public static ActivityTracker Instance { get { return s_activityTrackerInstance; } } + + + #region private + + /// + /// The current activity ID. Use this to log normal events. + /// + private Guid CurrentActivityId { get { return m_current.Value.ActivityId; } } + + /// + /// Searched for a active (nonstopped) activity with the given name. Returns null if not found. + /// + private ActivityInfo FindActiveActivity(string name, ActivityInfo startLocation) + { + var activity = startLocation; + while (activity != null) + { + if (name == activity.m_name && activity.m_stopped == 0) + return activity; + activity = activity.m_creator; + } + return null; + } + + /// + /// Strip out "Start" or "End" suffix from activity name and add providerName prefix. + /// If 'task' it does not end in Start or Stop and Task is non-zero use that as the name of the activity + /// + private string NormalizeActivityName(string providerName, string activityName, int task) + { + if (activityName.EndsWith(EventSource.s_ActivityStartSuffix, StringComparison.Ordinal)) + activityName = activityName.Substring(0, activityName.Length - EventSource.s_ActivityStartSuffix.Length); + else if (activityName.EndsWith(EventSource.s_ActivityStopSuffix, StringComparison.Ordinal)) + activityName = activityName.Substring(0, activityName.Length - EventSource.s_ActivityStopSuffix.Length); + else if (task != 0) + activityName = "task" + task.ToString(); + + // We use provider name to distinguish between activities from different providers. + return providerName + activityName; + } + + // ******************************************************************************* + /// + /// An ActivityInfo represents a particular activity. It is almost read-only. The only + /// fields that change after creation are + /// m_lastChildID - used to generate unique IDs for the children activities and for the most part can be ignored. + /// m_stopped - indicates that this activity is dead + /// This read-only-ness is important because an activity's m_creator chain forms the + /// 'Path of creation' for the activity (which is also its unique ID) but is also used as + /// the 'list of live parents' which indicate of those ancestors, which are alive (if they + /// are not marked dead they are alive). + /// + private class ActivityInfo + { + public ActivityInfo(string name, long uniqueId, ActivityInfo creator, Guid activityIDToRestore, EventActivityOptions options) + { + m_name = name; + m_eventOptions = options; + m_creator = creator; + m_uniqueId = uniqueId; + m_level = creator != null ? creator.m_level + 1 : 0; + m_activityIdToRestore = activityIDToRestore; + + // Create a nice GUID that encodes the chain of activities that started this one. + CreateActivityPathGuid(out m_guid, out m_activityPathGuidOffset); + } + + public Guid ActivityId + { + get + { + return m_guid; + } + } + + public static string Path(ActivityInfo activityInfo) + { + if (activityInfo == null) + return (""); + return Path(activityInfo.m_creator) + "/" + activityInfo.m_uniqueId.ToString(); + } + + public override string ToString() + { + return m_name + "(" + Path(this) + (m_stopped != 0 ? ",DEAD)" : ")"); + } + + public static string LiveActivities(ActivityInfo list) + { + if (list == null) + return ""; + return list.ToString() + ";" + LiveActivities(list.m_creator); + } + + public bool CanBeOrphan() + { + if ((m_eventOptions & EventActivityOptions.Detachable) != 0) + return true; + return false; + } + + #region private + + #region CreateActivityPathGuid + /// + /// Logically every activity Path (see Path()) that describes the activities that caused this + /// (rooted in an activity that predates activity tracking. + /// + /// We wish to encode this path in the Guid to the extent that we can. Many of the paths have + /// many small numbers in them and we take advantage of this in the encoding to output as long + /// a path in the GUID as possible. + /// + /// Because of the possibility of GUID collision, we only use 96 of the 128 bits of the GUID + /// for encoding the path. The last 32 bits are a simple checksum (and random number) that + /// identifies this as using the convention defined here. + /// + /// It returns both the GUID which has the path as well as the offset that points just beyond + /// the end of the activity (so it can be appended to). Note that if the end is in a nibble + /// (it uses nibbles instead of bytes as the unit of encoding, then it will point at the unfinished + /// byte (since the top nibble can't be zero you can determine if this is true by seeing if + /// this byte is nonZero. This offset is needed to efficiently create the ID for child activities. + /// + private unsafe void CreateActivityPathGuid(out Guid idRet, out int activityPathGuidOffset) + { + fixed (Guid* outPtr = &idRet) + { + int activityPathGuidOffsetStart = 0; + if (m_creator != null) + { + activityPathGuidOffsetStart = m_creator.m_activityPathGuidOffset; + idRet = m_creator.m_guid; + } + else + { + // TODO FIXME - differentiate between AD inside PCL + int appDomainID = 0; +#if (!ES_BUILD_STANDALONE && !ES_BUILD_PN) + appDomainID = System.Threading.Thread.GetDomainID(); +#endif + // We start with the appdomain number to make this unique among appdomains. + activityPathGuidOffsetStart = AddIdToGuid(outPtr, activityPathGuidOffsetStart, (uint)appDomainID); + } + + activityPathGuidOffset = AddIdToGuid(outPtr, activityPathGuidOffsetStart, (uint)m_uniqueId); + + + // If the path does not fit, Make a GUID by incrementing rather than as a path, keeping as much of the path as possible + if (12 < activityPathGuidOffset) + CreateOverflowGuid(outPtr); + } + } + + /// + /// If we can't fit the activity Path into the GUID we come here. What we do is simply + /// generate a 4 byte number (s_nextOverflowId). Then look for an ancestor that has + /// sufficient space for this ID. By doing this, we preserve the fact that this activity + /// is a child (of unknown depth) from that ancestor. + /// + private unsafe void CreateOverflowGuid(Guid* outPtr) + { + // Search backwards for an ancestor that has sufficient space to put the ID. + for (ActivityInfo ancestor = m_creator; ancestor != null; ancestor = ancestor.m_creator) + { + if (ancestor.m_activityPathGuidOffset <= 10) // we need at least 2 bytes. + { + uint id = unchecked((uint)Interlocked.Increment(ref ancestor.m_lastChildID)); // Get a unique ID + // Try to put the ID into the GUID + *outPtr = ancestor.m_guid; + int endId = AddIdToGuid(outPtr, ancestor.m_activityPathGuidOffset, id, true); + + // Does it fit? + if (endId <= 12) + break; + } + } + } + + /// + /// The encoding for a list of numbers used to make Activity GUIDs. Basically + /// we operate on nibbles (which are nice because they show up as hex digits). The + /// list is ended with a end nibble (0) and depending on the nibble value (Below) + /// the value is either encoded into nibble itself or it can spill over into the + /// bytes that follow. + /// + enum NumberListCodes : byte + { + End = 0x0, // ends the list. No valid value has this prefix. + LastImmediateValue = 0xA, + + PrefixCode = 0xB, // all the 'long' encodings go here. If the next nibble is MultiByte1-4 + // than this is a 'overflow' id. Unlike the hierarchical IDs these are + // allocated densely but don't tell you anything about nesting. we use + // these when we run out of space in the GUID to store the path. + + MultiByte1 = 0xC, // 1 byte follows. If this Nibble is in the high bits, it the high bits of the number are stored in the low nibble. + // commented out because the code does not explicitly reference the names (but they are logically defined). + // MultiByte2 = 0xD, // 2 bytes follow (we don't bother with the nibble optimization) + // MultiByte3 = 0xE, // 3 bytes follow (we don't bother with the nibble optimization) + // MultiByte4 = 0xF, // 4 bytes follow (we don't bother with the nibble optimization) + } + + /// Add the activity id 'id' to the output Guid 'outPtr' starting at the offset 'whereToAddId' + /// Thus if this number is 6 that is where 'id' will be added. This will return 13 (12 + /// is the maximum number of bytes that fit in a GUID) if the path did not fit. + /// If 'overflow' is true, then the number is encoded as an 'overflow number (which has a + /// special (longer prefix) that indicates that this ID is allocated differently + private static unsafe int AddIdToGuid(Guid* outPtr, int whereToAddId, uint id, bool overflow = false) + { + byte* ptr = (byte*)outPtr; + byte* endPtr = ptr + 12; + ptr += whereToAddId; + if (endPtr <= ptr) + return 13; // 12 means we might exactly fit, 13 means we definately did not fit + + if (0 < id && id <= (uint)NumberListCodes.LastImmediateValue && !overflow) + WriteNibble(ref ptr, endPtr, id); + else + { + uint len = 4; + if (id <= 0xFF) + len = 1; + else if (id <= 0xFFFF) + len = 2; + else if (id <= 0xFFFFFF) + len = 3; + + if (overflow) + { + if (endPtr <= ptr + 2) // I need at least 2 bytes + return 13; + + // Write out the prefix code nibble and the length nibble + WriteNibble(ref ptr, endPtr, (uint)NumberListCodes.PrefixCode); + } + // The rest is the same for overflow and non-overflow case + WriteNibble(ref ptr, endPtr, (uint)NumberListCodes.MultiByte1 + (len - 1)); + + // Do we have an odd nibble? If so flush it or use it for the 12 byte case. + if (ptr < endPtr && *ptr != 0) + { + // If the value < 4096 we can use the nibble we are otherwise just outputting as padding. + if (id < 4096) + { + // Indicate this is a 1 byte multicode with 4 high order bits in the lower nibble. + *ptr = (byte)(((uint)NumberListCodes.MultiByte1 << 4) + (id >> 8)); + id &= 0xFF; // Now we only want the low order bits. + } + ptr++; + } + + // Write out the bytes. + while (0 < len) + { + if (endPtr <= ptr) + { + ptr++; // Indicate that we have overflowed + break; + } + *ptr++ = (byte)id; + id = (id >> 8); + --len; + } + } + + // Compute the checksum + uint* sumPtr = (uint*)outPtr; + // We set the last DWORD the sum of the first 3 DWORDS in the GUID. This + sumPtr[3] = sumPtr[0] + sumPtr[1] + sumPtr[2] + 0x599D99AD; // This last number is a random number (it identifies us as us) + + return (int)(ptr - ((byte*)outPtr)); + } + + /// + /// Write a single Nible 'value' (must be 0-15) to the byte buffer represented by *ptr. + /// Will not go past 'endPtr'. Also it assumes that we never write 0 so we can detect + /// whether a nibble has already been written to ptr because it will be nonzero. + /// Thus if it is non-zero it adds to the current byte, otherwise it advances and writes + /// the new byte (in the high bits) of the next byte. + /// + private static unsafe void WriteNibble(ref byte* ptr, byte* endPtr, uint value) + { + Debug.Assert(0 <= value && value < 16); + Debug.Assert(ptr < endPtr); + + if (*ptr != 0) + *ptr++ |= (byte)value; + else + *ptr = (byte)(value << 4); + } + + #endregion // CreateGuidForActivityPath + + readonly internal string m_name; // The name used in the 'start' and 'stop' APIs to help match up + readonly long m_uniqueId; // a small number that makes this activity unique among its siblings + internal readonly Guid m_guid; // Activity Guid, it is basically an encoding of the Path() (see CreateActivityPathGuid) + internal readonly int m_activityPathGuidOffset; // Keeps track of where in m_guid the causality path stops (used to generated child GUIDs) + internal readonly int m_level; // current depth of the Path() of the activity (used to keep recursion under control) + readonly internal EventActivityOptions m_eventOptions; // Options passed to start. + internal long m_lastChildID; // used to create a unique ID for my children activities + internal int m_stopped; // This work item has stopped + readonly internal ActivityInfo m_creator; // My parent (creator). Forms the Path() for the activity. + readonly internal Guid m_activityIdToRestore; // The Guid to restore after a stop. + #endregion + } + + // This callback is used to initialize the m_current AsyncLocal Variable. + // Its job is to keep the ETW Activity ID (part of thread local storage) in sync + // with m_current.ActivityID + void ActivityChanging(AsyncLocalValueChangedArgs args) + { + ActivityInfo cur = args.CurrentValue; + ActivityInfo prev = args.PreviousValue; + + // Are we popping off a value? (we have a prev, and it creator is cur) + // Then check if we should use the GUID at the time of the start event + if (prev != null && prev.m_creator == cur) + { + // If the saved activity ID is not the same as the creator activity + // that takes precedence (it means someone explicitly did a SetActivityID) + // Set it to that and get out + if (cur == null || prev.m_activityIdToRestore != cur.ActivityId) + { + EventSource.SetCurrentThreadActivityId(prev.m_activityIdToRestore); + return; + } + } + + // OK we did not have an explicit SetActivityID set. Then we should be + // setting the activity to current ActivityInfo. However that activity + // might be dead, in which case we should skip it, so we never set + // the ID to dead things. + while (cur != null) + { + // We found a live activity (typically the first time), set it to that. + if (cur.m_stopped == 0) + { + EventSource.SetCurrentThreadActivityId(cur.ActivityId); + return; + } + cur = cur.m_creator; + } + // we can get here if there is no information on our activity stack (everything is dead) + // currently we do nothing, as that seems better than setting to Guid.Emtpy. + } + + /// + /// Async local variables have the property that the are automatically copied whenever a task is created and used + /// while that task is running. Thus m_current 'flows' to any task that is caused by the current thread that + /// last set it. + /// + /// This variable points a a linked list that represents all Activities that have started but have not stopped. + /// + AsyncLocal m_current; + bool m_checkedForEnable; + + // Singleton + private static ActivityTracker s_activityTrackerInstance = new ActivityTracker(); + + // Used to create unique IDs at the top level. Not used for nested Ids (each activity has its own id generator) + static long m_nextId = 0; + private const ushort MAX_ACTIVITY_DEPTH = 100; // Limit maximum depth of activities to be tracked at 100. + // This will avoid leaking memory in case of activities that are never stopped. + + #endregion + } + +#if ES_BUILD_STANDALONE || ES_BUILD_PN + /******************************** SUPPORT *****************************/ + /// + /// This is supplied by the framework. It is has the semantics that the value is copied to any new Tasks that is created + /// by the current task. Thus all causally related code gets this value. Note that reads and writes to this VARIABLE + /// (not what it points it) to this does not need to be protected by locks because it is inherently thread local (you always + /// only get your thread local copy which means that you never have races. + /// + /// +#if ES_BUILD_STANDALONE + [EventSource(Name = "Microsoft.Tasks.Nuget")] +#else + [EventSource(Name = "System.Diagnostics.Tracing.TplEtwProvider")] +#endif + internal class TplEtwProvider : EventSource + { + public class Keywords + { + public const EventKeywords TasksFlowActivityIds = (EventKeywords)0x80; + public const EventKeywords Debug = (EventKeywords)0x20000; + } + + public static TplEtwProvider Log = new TplEtwProvider(); + public bool Debug { get { return IsEnabled(EventLevel.Verbose, Keywords.Debug); } } + + public void DebugFacilityMessage(string Facility, string Message) { WriteEvent(1, Facility, Message); } + public void DebugFacilityMessage1(string Facility, string Message, string Arg) { WriteEvent(2, Facility, Message, Arg); } + public void SetActivityId(Guid Id) { WriteEvent(3, Id); } + } +#endif + +#if ES_BUILD_AGAINST_DOTNET_V35 || ES_BUILD_PCL || NO_ASYNC_LOCAL + // In these cases we don't have any Async local support. Do nothing. + internal sealed class AsyncLocalValueChangedArgs + { + public T PreviousValue { get { return default(T); } } + public T CurrentValue { get { return default(T); } } + + } + + internal sealed class AsyncLocal + { + public AsyncLocal(Action> valueChangedHandler) { + throw new NotImplementedException("AsyncLocal only available on V4.6 and above"); + } + public T Value + { + get { return default(T); } + set { } + } + } +#endif + +} diff --git a/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventActivityOptions.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventActivityOptions.cs new file mode 100644 index 0000000..782afbf --- /dev/null +++ b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventActivityOptions.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; + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// EventActivityOptions flags allow to specify different activity related characteristics. + /// + [Flags] + public enum EventActivityOptions + { + /// + /// No special options are added to the event. + /// + None = 0, + + /// + /// Disable Implicit Activity Tracking + /// + Disable = 0x2, + + /// + /// Allow activity event to call itself (directly or indirectly) + /// + Recursive = 0x4, + + /// + /// Allows event activity to live beyond its parent. + /// + Detachable = 0x8 + } +} diff --git a/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventCounter.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventCounter.cs new file mode 100644 index 0000000..b1f9464 --- /dev/null +++ b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventCounter.cs @@ -0,0 +1,436 @@ +// 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; +using System.Threading; +#if ES_BUILD_PCL + using System.Threading.Tasks; +#endif + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// Provides the ability to collect statistics through EventSource + /// + public class EventCounter + { + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The event source. + public EventCounter(string name, EventSource eventSource) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (eventSource == null) + { + throw new ArgumentNullException(nameof(eventSource)); + } + + InitializeBuffer(); + _name = name; + EventCounterGroup.AddEventCounter(eventSource, this); + } + + /// + /// Writes the metric. + /// + /// The value. + public void WriteMetric(float value) + { + Enqueue(value); + } + + #region private implementation + + private readonly string _name; + + #region Buffer Management + + // Values buffering + private const int BufferedSize = 10; + private const float UnusedBufferSlotValue = float.NegativeInfinity; + private const int UnsetIndex = -1; + private volatile float[] _bufferedValues; + private volatile int _bufferedValuesIndex; + + private void InitializeBuffer() + { + _bufferedValues = new float[BufferedSize]; + for (int i = 0; i < _bufferedValues.Length; i++) + { + _bufferedValues[i] = UnusedBufferSlotValue; + } + } + + private void Enqueue(float value) + { + // It is possible that two threads read the same bufferedValuesIndex, but only one will be able to write the slot, so that is okay. + int i = _bufferedValuesIndex; + while (true) + { + float result = Interlocked.CompareExchange(ref _bufferedValues[i], value, UnusedBufferSlotValue); + i++; + if (_bufferedValues.Length <= i) + { + // It is possible that two threads both think the buffer is full, but only one get to actually flush it, the other + // will eventually enter this code path and potentially calling Flushing on a buffer that is not full, and that's okay too. + lock (_bufferedValues) + { + Flush(); + } + i = 0; + } + + if (result == UnusedBufferSlotValue) + { + // CompareExchange succeeded + _bufferedValuesIndex = i; + return; + } + } + } + + private void Flush() + { + for (int i = 0; i < _bufferedValues.Length; i++) + { + var value = Interlocked.Exchange(ref _bufferedValues[i], UnusedBufferSlotValue); + if (value != UnusedBufferSlotValue) + { + OnMetricWritten(value); + } + } + + _bufferedValuesIndex = 0; + } + + #endregion // Buffer Management + + #region Statistics Calculation + + // Statistics + private int _count; + private float _sum; + private float _sumSquared; + private float _min; + private float _max; + + private void OnMetricWritten(float value) + { + _sum += value; + _sumSquared += value * value; + if (_count == 0 || value > _max) + { + _max = value; + } + + if (_count == 0 || value < _min) + { + _min = value; + } + + _count++; + } + + internal EventCounterPayload GetEventCounterPayload() + { + lock (_bufferedValues) + { + Flush(); + EventCounterPayload result = new EventCounterPayload(); + result.Name = _name; + result.Count = _count; + result.Mean = _sum / _count; + result.StandardDerivation = (float)Math.Sqrt(_sumSquared / _count - _sum * _sum / _count / _count); + result.Min = _min; + result.Max = _max; + ResetStatistics(); + return result; + } + } + + private void ResetStatistics() + { + _count = 0; + _sum = 0; + _sumSquared = 0; + _min = 0; + _max = 0; + } + + #endregion // Statistics Calculation + + #endregion // private implementation + } + + #region internal supporting classes + + [EventData] + internal class EventCounterPayload : IEnumerable> + { + public string Name { get; set; } + + public float Mean { get; set; } + + public float StandardDerivation { get; set; } + + public int Count { get; set; } + + public float Min { get; set; } + + public float Max { get; set; } + + public float IntervalSec { get; internal set; } + + #region Implementation of the IEnumerable interface + + public IEnumerator> GetEnumerator() + { + return ForEnumeration.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ForEnumeration.GetEnumerator(); + } + + private IEnumerable> ForEnumeration + { + get + { + yield return new KeyValuePair("Name", Name); + yield return new KeyValuePair("Mean", Mean); + yield return new KeyValuePair("StandardDerivation", StandardDerivation); + yield return new KeyValuePair("Count", Count); + yield return new KeyValuePair("Min", Min); + yield return new KeyValuePair("Max", Max); + } + } + + #endregion // Implementation of the IEnumerable interface + } + + internal class EventCounterGroup : IDisposable + { + private readonly EventSource _eventSource; + private readonly int _eventSourceIndex; + private readonly List _eventCounters; + + internal EventCounterGroup(EventSource eventSource, int eventSourceIndex) + { + _eventSource = eventSource; + _eventSourceIndex = eventSourceIndex; + _eventCounters = new List(); + RegisterCommandCallback(); + } + + private void Add(EventCounter eventCounter) + { + _eventCounters.Add(eventCounter); + } + + #region EventSource Command Processing + + private void RegisterCommandCallback() + { + _eventSource.EventCommandExecuted += OnEventSourceCommand; + } + + private void OnEventSourceCommand(object sender, EventCommandEventArgs e) + { + if (e.Command == EventCommand.Enable || e.Command == EventCommand.Update) + { + string valueStr; + float value; + if (e.Arguments.TryGetValue("EventCounterIntervalSec", out valueStr) && float.TryParse(valueStr, out value)) + { + EnableTimer(value); + } + } + } + + #endregion // EventSource Command Processing + + #region Global EventCounterGroup Array management + + private static EventCounterGroup[] s_eventCounterGroups; + + internal static void AddEventCounter(EventSource eventSource, EventCounter eventCounter) + { + int eventSourceIndex = EventListener.EventSourceIndex(eventSource); + + EventCounterGroup.EnsureEventSourceIndexAvailable(eventSourceIndex); + EventCounterGroup eventCounterGroup = GetEventCounterGroup(eventSource); + eventCounterGroup.Add(eventCounter); + } + + private static void EnsureEventSourceIndexAvailable(int eventSourceIndex) + { + if (EventCounterGroup.s_eventCounterGroups == null) + { + EventCounterGroup.s_eventCounterGroups = new EventCounterGroup[eventSourceIndex + 1]; + } + else if (eventSourceIndex >= EventCounterGroup.s_eventCounterGroups.Length) + { + EventCounterGroup[] newEventCounterGroups = new EventCounterGroup[eventSourceIndex + 1]; + Array.Copy(EventCounterGroup.s_eventCounterGroups, 0, newEventCounterGroups, 0, EventCounterGroup.s_eventCounterGroups.Length); + EventCounterGroup.s_eventCounterGroups = newEventCounterGroups; + } + } + + private static EventCounterGroup GetEventCounterGroup(EventSource eventSource) + { + int eventSourceIndex = EventListener.EventSourceIndex(eventSource); + EventCounterGroup result = EventCounterGroup.s_eventCounterGroups[eventSourceIndex]; + if (result == null) + { + result = new EventCounterGroup(eventSource, eventSourceIndex); + EventCounterGroup.s_eventCounterGroups[eventSourceIndex] = result; + } + + return result; + } + + #endregion // Global EventCounterGroup Array management + + #region Timer Processing + + private DateTime _timeStampSinceCollectionStarted; + private int _pollingIntervalInMilliseconds; + private Timer _pollingTimer; + + private void EnableTimer(float pollingIntervalInSeconds) + { + if (pollingIntervalInSeconds == 0) + { + if (_pollingTimer != null) + { + _pollingTimer.Dispose(); + _pollingTimer = null; + } + + _pollingIntervalInMilliseconds = 0; + } + else if (_pollingIntervalInMilliseconds == 0 || pollingIntervalInSeconds < _pollingIntervalInMilliseconds) + { + _pollingIntervalInMilliseconds = (int)(pollingIntervalInSeconds * 1000); + if (_pollingTimer != null) + { + _pollingTimer.Dispose(); + _pollingTimer = null; + } + + _timeStampSinceCollectionStarted = DateTime.Now; + _pollingTimer = new Timer(OnTimer, null, _pollingIntervalInMilliseconds, _pollingIntervalInMilliseconds); + } + } + + private void OnTimer(object state) + { + if (_eventSource.IsEnabled()) + { + DateTime now = DateTime.Now; + TimeSpan elapsed = now - _timeStampSinceCollectionStarted; + lock (_pollingTimer) + { + foreach (var eventCounter in _eventCounters) + { + EventCounterPayload payload = eventCounter.GetEventCounterPayload(); + payload.IntervalSec = (float)elapsed.TotalSeconds; + _eventSource.Write("EventCounters", new EventSourceOptions() { Level = EventLevel.LogAlways }, new { Payload = payload }); + } + + + _timeStampSinceCollectionStarted = now; + } + } + else + { + _pollingTimer.Dispose(); + _pollingTimer = null; + EventCounterGroup.s_eventCounterGroups[_eventSourceIndex] = null; + } + } + + #region PCL timer hack + +#if ES_BUILD_PCL + internal delegate void TimerCallback(object state); + + internal sealed class Timer : CancellationTokenSource, IDisposable + { + private int _period; + private TimerCallback _callback; + private object _state; + + internal Timer(TimerCallback callback, object state, int dueTime, int period) + { + _callback = callback; + _state = state; + _period = period; + Schedule(dueTime); + } + + private void Schedule(int dueTime) + { + Task.Delay(dueTime, Token).ContinueWith(OnTimer, null, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default); + } + + private void OnTimer(Task t, object s) + { + Schedule(_period); + _callback(_state); + } + + public new void Dispose() { base.Cancel(); } + } +#endif + #endregion // PCL timer hack + + #endregion // Timer Processing + + #region Implementation of the IDisposable interface + + private bool _disposed = false; + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + if (_pollingTimer != null) + { + _pollingTimer.Dispose(); + _pollingTimer = null; + } + } + + _disposed = true; + } + + #endregion // Implementation of the IDisposable interface + } + + #endregion // internal supporting classes +} diff --git a/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventDescriptor.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventDescriptor.cs new file mode 100644 index 0000000..a1dcf1c --- /dev/null +++ b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventDescriptor.cs @@ -0,0 +1,195 @@ +// 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; + +#if ES_BUILD_STANDALONE +using Environment = Microsoft.Diagnostics.Tracing.Internal.Environment; +#endif + +#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 +{ + [StructLayout(LayoutKind.Explicit, Size = 16)] +#if !CORECLR && !ES_BUILD_PN + [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)] +#endif // !CORECLR && !ES_BUILD_PN + internal struct EventDescriptor + { + # region private + [FieldOffset(0)] + private int m_traceloggingId; + [FieldOffset(0)] + private ushort m_id; + [FieldOffset(2)] + private byte m_version; + [FieldOffset(3)] + private byte m_channel; + [FieldOffset(4)] + private byte m_level; + [FieldOffset(5)] + private byte m_opcode; + [FieldOffset(6)] + private ushort m_task; + [FieldOffset(8)] + private long m_keywords; + #endregion + + public EventDescriptor( + int traceloggingId, + byte level, + byte opcode, + long keywords + ) + { + this.m_id = 0; + this.m_version = 0; + this.m_channel = 0; + this.m_traceloggingId = traceloggingId; + this.m_level = level; + this.m_opcode = opcode; + this.m_task = 0; + this.m_keywords = keywords; + } + + public EventDescriptor( + int id, + byte version, + byte channel, + byte level, + byte opcode, + int task, + long keywords + ) + { + if (id < 0) + { + throw new ArgumentOutOfRangeException(nameof(id), Resources.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + + if (id > ushort.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(id), Resources.GetResourceString("ArgumentOutOfRange_NeedValidId", 1, ushort.MaxValue)); + } + + m_traceloggingId = 0; + m_id = (ushort)id; + m_version = version; + m_channel = channel; + m_level = level; + m_opcode = opcode; + m_keywords = keywords; + + if (task < 0) + { + throw new ArgumentOutOfRangeException(nameof(task), Resources.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + + if (task > ushort.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(task), Resources.GetResourceString("ArgumentOutOfRange_NeedValidId", 1, ushort.MaxValue)); + } + + m_task = (ushort)task; + } + + public int EventId + { + get + { + return m_id; + } + } + public byte Version + { + get + { + return m_version; + } + } + public byte Channel + { + get + { + return m_channel; + } + } + public byte Level + { + get + { + return m_level; + } + } + public byte Opcode + { + get + { + return m_opcode; + } + } + public int Task + { + get + { + return m_task; + } + } + public long Keywords + { + get + { + return m_keywords; + } + } + + public override bool Equals(object obj) + { + if (!(obj is EventDescriptor)) + return false; + + return Equals((EventDescriptor) obj); + } + + public override int GetHashCode() + { + return m_id ^ m_version ^ m_channel ^ m_level ^ m_opcode ^ m_task ^ (int)m_keywords; + } + + public bool Equals(EventDescriptor other) + { + if ((m_id != other.m_id) || + (m_version != other.m_version) || + (m_channel != other.m_channel) || + (m_level != other.m_level) || + (m_opcode != other.m_opcode) || + (m_task != other.m_task) || + (m_keywords != other.m_keywords)) + { + return false; + } + return true; + } + + public static bool operator ==(EventDescriptor event1, EventDescriptor event2) + { + return event1.Equals(event2); + } + + public static bool operator !=(EventDescriptor event1, EventDescriptor event2) + { + return !event1.Equals(event2); + } + } +} diff --git a/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventProvider.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventProvider.cs new file mode 100644 index 0000000..e18574c --- /dev/null +++ b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventProvider.cs @@ -0,0 +1,1207 @@ +// 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 Microsoft.Win32; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Security; +#if !CORECLR && !ES_BUILD_PN +using System.Security.Permissions; +#endif // !CORECLR && !ES_BUILD_PN +using System.Threading; +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_AGAINST_DOTNET_V35 +using Microsoft.Internal; // for Tuple (can't define alias for open generic types so we "use" the whole namespace) +#endif + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + // New in CLR4.0 + internal enum ControllerCommand + { + // Strictly Positive numbers are for provider-specific commands, negative number are for 'shared' commands. 256 + // The first 256 negative numbers are reserved for the framework. + Update = 0, // Not used by EventPrividerBase. + SendManifest = -1, + Enable = -2, + Disable = -3, + }; + + /// + /// Only here because System.Diagnostics.EventProvider needs one more extensibility hook (when it gets a + /// controller callback) + /// +#if !CORECLR && !ES_BUILD_PN + [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)] +#endif // !CORECLR && !ES_BUILD_PN + internal partial class EventProvider : IDisposable + { + // This is the windows EVENT_DATA_DESCRIPTOR structure. We expose it because this is what + // subclasses of EventProvider use when creating efficient (but unsafe) version of + // EventWrite. We do make it a nested type because we really don't expect anyone to use + // it except subclasses (and then only rarely). + public struct EventData + { + internal unsafe ulong Ptr; + internal uint Size; + internal uint Reserved; + } + + /// + /// A struct characterizing ETW sessions (identified by the etwSessionId) as + /// activity-tracing-aware or legacy. A session that's activity-tracing-aware + /// has specified one non-zero bit in the reserved range 44-47 in the + /// 'allKeywords' value it passed in for a specific EventProvider. + /// + public struct SessionInfo + { + internal int sessionIdBit; // the index of the bit used for tracing in the "reserved" field of AllKeywords + internal int etwSessionId; // the machine-wide ETW session ID + + internal SessionInfo(int sessionIdBit_, int etwSessionId_) + { sessionIdBit = sessionIdBit_; etwSessionId = etwSessionId_; } + } + + private static bool m_setInformationMissing; + + UnsafeNativeMethods.ManifestEtw.EtwEnableCallback m_etwCallback; // Trace Callback function + private long m_regHandle; // Trace Registration Handle + private byte m_level; // Tracing Level + private long m_anyKeywordMask; // Trace Enable Flags + private long m_allKeywordMask; // Match all keyword + private List m_liveSessions; // current live sessions (Tuple) + private bool m_enabled; // Enabled flag from Trace callback + private Guid m_providerId; // Control Guid + internal bool m_disposed; // when true provider has unregistered + + [ThreadStatic] + private static WriteEventErrorCode s_returnCode; // The last return code + + private const int s_basicTypeAllocationBufferSize = 16; + private const int s_etwMaxNumberArguments = 128; + private const int s_etwAPIMaxRefObjCount = 8; + private const int s_maxEventDataDescriptors = 128; + private const int s_traceEventMaximumSize = 65482; + private const int s_traceEventMaximumStringSize = 32724; + + [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible")] + public enum WriteEventErrorCode : int + { + //check mapping to runtime codes + NoError = 0, + NoFreeBuffers = 1, + EventTooBig = 2, + NullInput = 3, + TooManyArgs = 4, + Other = 5, + }; + + // Because callbacks happen on registration, and we need the callbacks for those setup + // we can't call Register in the constructor. + // + // Note that EventProvider should ONLY be used by EventSource. In particular because + // it registers a callback from native code you MUST dispose it BEFORE shutdown, otherwise + // you may get native callbacks during shutdown when we have destroyed the delegate. + // EventSource has special logic to do this, no one else should be calling EventProvider. + internal EventProvider() + { + } + + /// + /// This method registers the controlGuid of this class with ETW. We need to be running on + /// Vista or above. If not a PlatformNotSupported exception will be thrown. If for some + /// reason the ETW Register call failed a NotSupported exception will be thrown. + /// + // + // + // + // + // + internal unsafe void Register(Guid providerGuid) + { + m_providerId = providerGuid; + uint status; + m_etwCallback = new UnsafeNativeMethods.ManifestEtw.EtwEnableCallback(EtwEnableCallBack); + + status = EventRegister(ref m_providerId, m_etwCallback); + if (status != 0) + { + throw new ArgumentException(Win32Native.GetMessage(unchecked((int)status))); + } + } + + // + // implement Dispose Pattern to early deregister from ETW insted of waiting for + // the finalizer to call deregistration. + // Once the user is done with the provider it needs to call Close() or Dispose() + // If neither are called the finalizer will unregister the provider anyway + // + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + // + // + // + protected virtual void Dispose(bool disposing) + { + // + // explicit cleanup is done by calling Dispose with true from + // Dispose() or Close(). The disposing arguement is ignored because there + // are no unmanaged resources. + // The finalizer calls Dispose with false. + // + + // + // check if the object has been allready disposed + // + if (m_disposed) return; + + // Disable the provider. + m_enabled = false; + + // Do most of the work under a lock to avoid shutdown race. + + long registrationHandle = 0; + lock (EventListener.EventListenersLock) + { + // Double check + if (m_disposed) + return; + + registrationHandle = m_regHandle; + m_regHandle = 0; + m_disposed = true; + } + + // We do the Unregistration outside the EventListenerLock because there is a lock + // inside the ETW routines. This lock is taken before ETW issues commands + // Thus the ETW lock gets taken first and then our EventListenersLock gets taken + // in SendCommand(), and also here. If we called EventUnregister after taking + // the EventListenersLock then the take-lock order is reversed and we can have + // deadlocks in race conditions (dispose racing with an ETW command). + // + // We solve by Unregistering after releasing the EventListenerLock. + if (registrationHandle != 0) + EventUnregister(registrationHandle); + + } + + /// + /// This method deregisters the controlGuid of this class with ETW. + /// + /// + public virtual void Close() + { + Dispose(); + } + + ~EventProvider() + { + Dispose(false); + } + + // + // + // + // + unsafe void EtwEnableCallBack( + [In] ref System.Guid sourceId, + [In] int controlCode, + [In] byte setLevel, + [In] long anyKeyword, + [In] long allKeyword, + [In] UnsafeNativeMethods.ManifestEtw.EVENT_FILTER_DESCRIPTOR* filterData, + [In] void* callbackContext + ) + { + // This is an optional callback API. We will therefore ignore any failures that happen as a + // result of turning on this provider as to not crash the app. + // EventSource has code to validate whether initialization it expected to occur actually occurred + try + { + ControllerCommand command = ControllerCommand.Update; + IDictionary args = null; + bool skipFinalOnControllerCommand = false; + if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_ENABLE_PROVIDER) + { + m_enabled = true; + m_level = setLevel; + m_anyKeywordMask = anyKeyword; + m_allKeywordMask = allKeyword; + + // ES_SESSION_INFO is a marker for additional places we #ifdeffed out to remove + // references to EnumerateTraceGuidsEx. This symbol is actually not used because + // today we use FEATURE_ACTIVITYSAMPLING to determine if this code is there or not. + // However we put it in the #if so that we don't lose the fact that this feature + // switch is at least partially independent of FEATURE_ACTIVITYSAMPLING + + List> sessionsChanged = GetSessions(); + foreach (var session in sessionsChanged) + { + int sessionChanged = session.Item1.sessionIdBit; + int etwSessionId = session.Item1.etwSessionId; + bool bEnabling = session.Item2; + + skipFinalOnControllerCommand = true; + args = null; // reinitialize args for every session... + + // if we get more than one session changed we have no way + // of knowing which one "filterData" belongs to + if (sessionsChanged.Count > 1) + filterData = null; + + // read filter data only when a session is being *added* + byte[] data; + int keyIndex; + if (bEnabling && + GetDataFromController(etwSessionId, filterData, out command, out data, out keyIndex)) + { + args = new Dictionary(4); + while (keyIndex < data.Length) + { + int keyEnd = FindNull(data, keyIndex); + int valueIdx = keyEnd + 1; + int valueEnd = FindNull(data, valueIdx); + if (valueEnd < data.Length) + { + string key = System.Text.Encoding.UTF8.GetString(data, keyIndex, keyEnd - keyIndex); + string value = System.Text.Encoding.UTF8.GetString(data, valueIdx, valueEnd - valueIdx); + args[key] = value; + } + keyIndex = valueEnd + 1; + } + } + + // execute OnControllerCommand once for every session that has changed. + OnControllerCommand(command, args, (bEnabling ? sessionChanged : -sessionChanged), etwSessionId); + } + } + else if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_DISABLE_PROVIDER) + { + m_enabled = false; + m_level = 0; + m_anyKeywordMask = 0; + m_allKeywordMask = 0; + m_liveSessions = null; + } + else if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_CAPTURE_STATE) + { + command = ControllerCommand.SendManifest; + } + else + return; // per spec you ignore commands you don't recognize. + + if (!skipFinalOnControllerCommand) + OnControllerCommand(command, args, 0, 0); + } + catch (Exception) + { + // We want to ignore any failures that happen as a result of turning on this provider as to + // not crash the app. + } + } + + // New in CLR4.0 + protected virtual void OnControllerCommand(ControllerCommand command, IDictionary arguments, int sessionId, int etwSessionId) { } + protected EventLevel Level { get { return (EventLevel)m_level; } set { m_level = (byte)value; } } + protected EventKeywords MatchAnyKeyword { get { return (EventKeywords)m_anyKeywordMask; } set { m_anyKeywordMask = unchecked((long)value); } } + protected EventKeywords MatchAllKeyword { get { return (EventKeywords)m_allKeywordMask; } set { m_allKeywordMask = unchecked((long)value); } } + + static private int FindNull(byte[] buffer, int idx) + { + while (idx < buffer.Length && buffer[idx] != 0) + idx++; + return idx; + } + + /// + /// Determines the ETW sessions that have been added and/or removed to the set of + /// sessions interested in the current provider. It does so by (1) enumerating over all + /// ETW sessions that enabled 'this.m_Guid' for the current process ID, and (2) + /// comparing the current list with a list it cached on the previous invocation. + /// + /// The return value is a list of tuples, where the SessionInfo specifies the + /// ETW session that was added or remove, and the bool specifies whether the + /// session was added or whether it was removed from the set. + /// + private List> GetSessions() + { + List liveSessionList = null; + + GetSessionInfo( + (int etwSessionId, long matchAllKeywords, ref List sessionList) => + GetSessionInfoCallback(etwSessionId, matchAllKeywords, ref sessionList), + ref liveSessionList); + + List> changedSessionList = new List>(); + + // first look for sessions that have gone away (or have changed) + // (present in the m_liveSessions but not in the new liveSessionList) + if (m_liveSessions != null) + { + foreach (SessionInfo s in m_liveSessions) + { + int idx; + if ((idx = IndexOfSessionInList(liveSessionList, s.etwSessionId)) < 0 || + (liveSessionList[idx].sessionIdBit != s.sessionIdBit)) + changedSessionList.Add(Tuple.Create(s, false)); + + } + } + // next look for sessions that were created since the last callback (or have changed) + // (present in the new liveSessionList but not in m_liveSessions) + if (liveSessionList != null) + { + foreach (SessionInfo s in liveSessionList) + { + int idx; + if ((idx = IndexOfSessionInList(m_liveSessions, s.etwSessionId)) < 0 || + (m_liveSessions[idx].sessionIdBit != s.sessionIdBit)) + changedSessionList.Add(Tuple.Create(s, true)); + } + } + + m_liveSessions = liveSessionList; + return changedSessionList; + } + + + /// + /// This method is the callback used by GetSessions() when it calls into GetSessionInfo(). + /// It updates a List{SessionInfo} based on the etwSessionId and matchAllKeywords that + /// GetSessionInfo() passes in. + /// + private static void GetSessionInfoCallback(int etwSessionId, long matchAllKeywords, + ref List sessionList) + { + uint sessionIdBitMask = (uint)SessionMask.FromEventKeywords(unchecked((ulong)matchAllKeywords)); + // an ETW controller that specifies more than the mandated bit for our EventSource + // will be ignored... + if (bitcount(sessionIdBitMask) > 1) + return; + + if (sessionList == null) + sessionList = new List(8); + + if (bitcount(sessionIdBitMask) == 1) + { + // activity-tracing-aware etw session + sessionList.Add(new SessionInfo(bitindex(sessionIdBitMask) + 1, etwSessionId)); + } + else + { + // legacy etw session + sessionList.Add(new SessionInfo(bitcount((uint)SessionMask.All) + 1, etwSessionId)); + } + } + + private delegate void SessionInfoCallback(int etwSessionId, long matchAllKeywords, ref List sessionList); + + /// + /// This method enumerates over all active ETW sessions that have enabled 'this.m_Guid' + /// for the current process ID, calling 'action' for each session, and passing it the + /// ETW session and the 'AllKeywords' the session enabled for the current provider. + /// + private unsafe void GetSessionInfo(SessionInfoCallback action, ref List sessionList) + { + // We wish the EventSource package to be legal for Windows Store applications. + // Currently EnumerateTraceGuidsEx is not an allowed API, so we avoid its use here + // and use the information in the registry instead. This means that ETW controllers + // that do not publish their intent to the registry (basically all controllers EXCEPT + // TraceEventSesion) will not work properly + + // However the framework version of EventSource DOES have ES_SESSION_INFO defined and thus + // does not have this issue. +#if ES_SESSION_INFO || !ES_BUILD_STANDALONE + int buffSize = 256; // An initial guess that probably works most of the time. + byte* buffer; + for (; ; ) + { + var space = stackalloc byte[buffSize]; + buffer = space; + var hr = 0; + + fixed (Guid* provider = &m_providerId) + { + hr = UnsafeNativeMethods.ManifestEtw.EnumerateTraceGuidsEx(UnsafeNativeMethods.ManifestEtw.TRACE_QUERY_INFO_CLASS.TraceGuidQueryInfo, + provider, sizeof(Guid), buffer, buffSize, ref buffSize); + } + if (hr == 0) + break; + if (hr != 122 /* ERROR_INSUFFICIENT_BUFFER */) + return; + } + + var providerInfos = (UnsafeNativeMethods.ManifestEtw.TRACE_GUID_INFO*)buffer; + var providerInstance = (UnsafeNativeMethods.ManifestEtw.TRACE_PROVIDER_INSTANCE_INFO*)&providerInfos[1]; + int processId = unchecked((int)Win32Native.GetCurrentProcessId()); + // iterate over the instances of the EventProvider in all processes + for (int i = 0; i < providerInfos->InstanceCount; i++) + { + if (providerInstance->Pid == processId) + { + var enabledInfos = (UnsafeNativeMethods.ManifestEtw.TRACE_ENABLE_INFO*)&providerInstance[1]; + // iterate over the list of active ETW sessions "listening" to the current provider + for (int j = 0; j < providerInstance->EnableCount; j++) + action(enabledInfos[j].LoggerId, enabledInfos[j].MatchAllKeyword, ref sessionList); + } + if (providerInstance->NextOffset == 0) + break; + Debug.Assert(0 <= providerInstance->NextOffset && providerInstance->NextOffset < buffSize); + var structBase = (byte*)providerInstance; + providerInstance = (UnsafeNativeMethods.ManifestEtw.TRACE_PROVIDER_INSTANCE_INFO*)&structBase[providerInstance->NextOffset]; + } +#else +#if !ES_BUILD_PCL && !FEATURE_PAL // TODO command arguments don't work on PCL builds... + // This code is only used in the Nuget Package Version of EventSource. because + // the code above is using APIs baned from UWP apps. + // + // TODO: In addition to only working when TraceEventSession enables the provider, this code + // also has a problem because TraceEvent does not clean up if the registry is stale + // It is unclear if it is worth keeping, but for now we leave it as it does work + // at least some of the time. + + // Determine our session from what is in the registry. + string regKey = @"\Microsoft\Windows\CurrentVersion\Winevt\Publishers\{" + m_providerId + "}"; + if (System.Runtime.InteropServices.Marshal.SizeOf(typeof(IntPtr)) == 8) + regKey = @"Software" + @"\Wow6432Node" + regKey; + else + regKey = @"Software" + regKey; + + var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(regKey); + if (key != null) + { + foreach (string valueName in key.GetValueNames()) + { + if (valueName.StartsWith("ControllerData_Session_", StringComparison.Ordinal)) + { + string strId = valueName.Substring(23); // strip of the ControllerData_Session_ + int etwSessionId; + if (int.TryParse(strId, out etwSessionId)) + { + // we need to assert this permission for partial trust scenarios + (new RegistryPermission(RegistryPermissionAccess.Read, regKey)).Assert(); + var data = key.GetValue(valueName) as byte[]; + if (data != null) + { + var dataAsString = System.Text.Encoding.UTF8.GetString(data); + int keywordIdx = dataAsString.IndexOf("EtwSessionKeyword", StringComparison.Ordinal); + if (0 <= keywordIdx) + { + int startIdx = keywordIdx + 18; + int endIdx = dataAsString.IndexOf('\0', startIdx); + string keywordBitString = dataAsString.Substring(startIdx, endIdx-startIdx); + int keywordBit; + if (0 < endIdx && int.TryParse(keywordBitString, out keywordBit)) + action(etwSessionId, 1L << keywordBit, ref sessionList); + } + } + } + } + } + } +#endif +#endif + } + + /// + /// Returns the index of the SesisonInfo from 'sessions' that has the specified 'etwSessionId' + /// or -1 if the value is not present. + /// + private static int IndexOfSessionInList(List sessions, int etwSessionId) + { + if (sessions == null) + return -1; + // for non-coreclr code we could use List.FindIndex(Predicate), but we need this to compile + // on coreclr as well + for (int i = 0; i < sessions.Count; ++i) + if (sessions[i].etwSessionId == etwSessionId) + return i; + + return -1; + } + + /// + /// Gets any data to be passed from the controller to the provider. It starts with what is passed + /// into the callback, but unfortunately this data is only present for when the provider is active + /// at the time the controller issues the command. To allow for providers to activate after the + /// controller issued a command, we also check the registry and use that to get the data. The function + /// returns an array of bytes representing the data, the index into that byte array where the data + /// starts, and the command being issued associated with that data. + /// + private unsafe bool GetDataFromController(int etwSessionId, + UnsafeNativeMethods.ManifestEtw.EVENT_FILTER_DESCRIPTOR* filterData, out ControllerCommand command, out byte[] data, out int dataStart) + { + data = null; + dataStart = 0; + if (filterData == null) + { +#if (!ES_BUILD_PCL && !ES_BUILD_PN && !FEATURE_PAL) + string regKey = @"\Microsoft\Windows\CurrentVersion\Winevt\Publishers\{" + m_providerId + "}"; + if (System.Runtime.InteropServices.Marshal.SizeOf(typeof(IntPtr)) == 8) + regKey = @"HKEY_LOCAL_MACHINE\Software" + @"\Wow6432Node" + regKey; + else + regKey = @"HKEY_LOCAL_MACHINE\Software" + regKey; + + string valueName = "ControllerData_Session_" + etwSessionId.ToString(CultureInfo.InvariantCulture); + + // we need to assert this permission for partial trust scenarios +#if !CORECLR + (new RegistryPermission(RegistryPermissionAccess.Read, regKey)).Assert(); +#endif + data = Microsoft.Win32.Registry.GetValue(regKey, valueName, null) as byte[]; + if (data != null) + { + // We only used the persisted data from the registry for updates. + command = ControllerCommand.Update; + return true; + } +#endif + } + else + { + if (filterData->Ptr != 0 && 0 < filterData->Size && filterData->Size <= 1024) + { + data = new byte[filterData->Size]; + Marshal.Copy((IntPtr)filterData->Ptr, data, 0, data.Length); + } + command = (ControllerCommand)filterData->Type; + return true; + } + + command = ControllerCommand.Update; + return false; + } + + /// + /// IsEnabled, method used to test if provider is enabled + /// + public bool IsEnabled() + { + return m_enabled; + } + + /// + /// IsEnabled, method used to test if event is enabled + /// + /// + /// Level to test + /// + /// + /// Keyword to test + /// + public bool IsEnabled(byte level, long keywords) + { + // + // If not enabled at all, return false. + // + if (!m_enabled) + { + return false; + } + + // This also covers the case of Level == 0. + if ((level <= m_level) || + (m_level == 0)) + { + + // + // Check if Keyword is enabled + // + + if ((keywords == 0) || + (((keywords & m_anyKeywordMask) != 0) && + ((keywords & m_allKeywordMask) == m_allKeywordMask))) + { + return true; + } + } + + return false; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] + public static WriteEventErrorCode GetLastWriteEventError() + { + return s_returnCode; + } + + // + // Helper function to set the last error on the thread + // + private static void SetLastError(int error) + { + switch (error) + { + case UnsafeNativeMethods.ManifestEtw.ERROR_ARITHMETIC_OVERFLOW: + case UnsafeNativeMethods.ManifestEtw.ERROR_MORE_DATA: + s_returnCode = WriteEventErrorCode.EventTooBig; + break; + case UnsafeNativeMethods.ManifestEtw.ERROR_NOT_ENOUGH_MEMORY: + s_returnCode = WriteEventErrorCode.NoFreeBuffers; + break; + } + } + + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + private static unsafe object EncodeObject(ref object data, ref EventData* dataDescriptor, ref byte* dataBuffer, ref uint totalEventSize) + /*++ + + Routine Description: + + This routine is used by WriteEvent to unbox the object type and + to fill the passed in ETW data descriptor. + + Arguments: + + data - argument to be decoded + + dataDescriptor - pointer to the descriptor to be filled (updated to point to the next empty entry) + + dataBuffer - storage buffer for storing user data, needed because cant get the address of the object + (updated to point to the next empty entry) + + Return Value: + + null if the object is a basic type other than string or byte[]. String otherwise + + --*/ + { + Again: + dataDescriptor->Reserved = 0; + + string sRet = data as string; + byte[] blobRet = null; + + if (sRet != null) + { + dataDescriptor->Size = ((uint)sRet.Length + 1) * 2; + } + else if ((blobRet = data as byte[]) != null) + { + // first store array length + *(int*)dataBuffer = blobRet.Length; + dataDescriptor->Ptr = (ulong)dataBuffer; + dataDescriptor->Size = 4; + totalEventSize += dataDescriptor->Size; + + // then the array parameters + dataDescriptor++; + dataBuffer += s_basicTypeAllocationBufferSize; + dataDescriptor->Size = (uint)blobRet.Length; + } + else if (data is IntPtr) + { + dataDescriptor->Size = (uint)sizeof(IntPtr); + IntPtr* intptrPtr = (IntPtr*)dataBuffer; + *intptrPtr = (IntPtr)data; + dataDescriptor->Ptr = (ulong)intptrPtr; + } + else if (data is int) + { + dataDescriptor->Size = (uint)sizeof(int); + int* intptr = (int*)dataBuffer; + *intptr = (int)data; + dataDescriptor->Ptr = (ulong)intptr; + } + else if (data is long) + { + dataDescriptor->Size = (uint)sizeof(long); + long* longptr = (long*)dataBuffer; + *longptr = (long)data; + dataDescriptor->Ptr = (ulong)longptr; + } + else if (data is uint) + { + dataDescriptor->Size = (uint)sizeof(uint); + uint* uintptr = (uint*)dataBuffer; + *uintptr = (uint)data; + dataDescriptor->Ptr = (ulong)uintptr; + } + else if (data is UInt64) + { + dataDescriptor->Size = (uint)sizeof(ulong); + UInt64* ulongptr = (ulong*)dataBuffer; + *ulongptr = (ulong)data; + dataDescriptor->Ptr = (ulong)ulongptr; + } + else if (data is char) + { + dataDescriptor->Size = (uint)sizeof(char); + char* charptr = (char*)dataBuffer; + *charptr = (char)data; + dataDescriptor->Ptr = (ulong)charptr; + } + else if (data is byte) + { + dataDescriptor->Size = (uint)sizeof(byte); + byte* byteptr = (byte*)dataBuffer; + *byteptr = (byte)data; + dataDescriptor->Ptr = (ulong)byteptr; + } + else if (data is short) + { + dataDescriptor->Size = (uint)sizeof(short); + short* shortptr = (short*)dataBuffer; + *shortptr = (short)data; + dataDescriptor->Ptr = (ulong)shortptr; + } + else if (data is sbyte) + { + dataDescriptor->Size = (uint)sizeof(sbyte); + sbyte* sbyteptr = (sbyte*)dataBuffer; + *sbyteptr = (sbyte)data; + dataDescriptor->Ptr = (ulong)sbyteptr; + } + else if (data is ushort) + { + dataDescriptor->Size = (uint)sizeof(ushort); + ushort* ushortptr = (ushort*)dataBuffer; + *ushortptr = (ushort)data; + dataDescriptor->Ptr = (ulong)ushortptr; + } + else if (data is float) + { + dataDescriptor->Size = (uint)sizeof(float); + float* floatptr = (float*)dataBuffer; + *floatptr = (float)data; + dataDescriptor->Ptr = (ulong)floatptr; + } + else if (data is double) + { + dataDescriptor->Size = (uint)sizeof(double); + double* doubleptr = (double*)dataBuffer; + *doubleptr = (double)data; + dataDescriptor->Ptr = (ulong)doubleptr; + } + else if (data is bool) + { + // WIN32 Bool is 4 bytes + dataDescriptor->Size = 4; + int* intptr = (int*)dataBuffer; + if (((bool)data)) + { + *intptr = 1; + } + else + { + *intptr = 0; + } + dataDescriptor->Ptr = (ulong)intptr; + } + else if (data is Guid) + { + dataDescriptor->Size = (uint)sizeof(Guid); + Guid* guidptr = (Guid*)dataBuffer; + *guidptr = (Guid)data; + dataDescriptor->Ptr = (ulong)guidptr; + } + else if (data is decimal) + { + dataDescriptor->Size = (uint)sizeof(decimal); + decimal* decimalptr = (decimal*)dataBuffer; + *decimalptr = (decimal)data; + dataDescriptor->Ptr = (ulong)decimalptr; + } + else if (data is DateTime) + { + const long UTCMinTicks = 504911232000000000; + long dateTimeTicks = 0; + // We cannot translate dates sooner than 1/1/1601 in UTC. + // To avoid getting an ArgumentOutOfRangeException we compare with 1/1/1601 DateTime ticks + if (((DateTime)data).Ticks > UTCMinTicks) + dateTimeTicks = ((DateTime)data).ToFileTimeUtc(); + dataDescriptor->Size = (uint)sizeof(long); + long* longptr = (long*)dataBuffer; + *longptr = dateTimeTicks; + dataDescriptor->Ptr = (ulong)longptr; + } + else + { + if (data is System.Enum) + { + Type underlyingType = Enum.GetUnderlyingType(data.GetType()); + if (underlyingType == typeof(int)) + { +#if !ES_BUILD_PCL + data = ((IConvertible)data).ToInt32(null); +#else + data = (int)data; +#endif + goto Again; + } + else if (underlyingType == typeof(long)) + { +#if !ES_BUILD_PCL + data = ((IConvertible)data).ToInt64(null); +#else + data = (long)data; +#endif + goto Again; + } + } + + // To our eyes, everything else is a just a string + if (data == null) + sRet = ""; + else + sRet = data.ToString(); + dataDescriptor->Size = ((uint)sRet.Length + 1) * 2; + } + + totalEventSize += dataDescriptor->Size; + + // advance buffers + dataDescriptor++; + dataBuffer += s_basicTypeAllocationBufferSize; + + return (object)sRet ?? (object)blobRet; + } + + /// + /// WriteEvent, method to write a parameters with event schema properties + /// + /// + /// Event Descriptor for this event. + /// + /// + /// A pointer to the activity ID GUID to log + /// + /// + /// childActivityID is marked as 'related' to the current activity ID. + /// + /// + /// Payload for the ETW event. + /// + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Performance-critical code")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] + internal unsafe bool WriteEvent(ref EventDescriptor eventDescriptor, Guid* activityID, Guid* childActivityID, params object[] eventPayload) + { + int status = 0; + + if (IsEnabled(eventDescriptor.Level, eventDescriptor.Keywords)) + { + int argCount = 0; + unsafe + { + argCount = eventPayload.Length; + + if (argCount > s_etwMaxNumberArguments) + { + s_returnCode = WriteEventErrorCode.TooManyArgs; + return false; + } + + uint totalEventSize = 0; + int index; + int refObjIndex = 0; + List refObjPosition = new List(s_etwAPIMaxRefObjCount); + List dataRefObj = new List(s_etwAPIMaxRefObjCount); + EventData* userData = stackalloc EventData[2 * argCount]; + EventData* userDataPtr = (EventData*)userData; + byte* dataBuffer = stackalloc byte[s_basicTypeAllocationBufferSize * 2 * argCount]; // Assume 16 chars for non-string argument + byte* currentBuffer = dataBuffer; + + // + // The loop below goes through all the arguments and fills in the data + // descriptors. For strings save the location in the dataString array. + // Calculates the total size of the event by adding the data descriptor + // size value set in EncodeObject method. + // + bool hasNonStringRefArgs = false; + for (index = 0; index < eventPayload.Length; index++) + { + if (eventPayload[index] != null) + { + object supportedRefObj; + supportedRefObj = EncodeObject(ref eventPayload[index], ref userDataPtr, ref currentBuffer, ref totalEventSize); + + if (supportedRefObj != null) + { + // EncodeObject advanced userDataPtr to the next empty slot + int idx = (int)(userDataPtr - userData - 1); + if (!(supportedRefObj is string)) + { + if (eventPayload.Length + idx + 1 - index > s_etwMaxNumberArguments) + { + s_returnCode = WriteEventErrorCode.TooManyArgs; + return false; + } + hasNonStringRefArgs = true; + } + dataRefObj.Add(supportedRefObj); + refObjPosition.Add(idx); + refObjIndex++; + } + } + else + { + s_returnCode = WriteEventErrorCode.NullInput; + return false; + } + } + + // update argCount based on actual number of arguments written to 'userData' + argCount = (int)(userDataPtr - userData); + + if (totalEventSize > s_traceEventMaximumSize) + { + s_returnCode = WriteEventErrorCode.EventTooBig; + return false; + } + + // the optimized path (using "fixed" instead of allocating pinned GCHandles + if (!hasNonStringRefArgs && (refObjIndex < s_etwAPIMaxRefObjCount)) + { + // Fast path: at most 8 string arguments + + // ensure we have at least s_etwAPIMaxStringCount in dataString, so that + // the "fixed" statement below works + while (refObjIndex < s_etwAPIMaxRefObjCount) + { + dataRefObj.Add(null); + ++refObjIndex; + } + + // + // now fix any string arguments and set the pointer on the data descriptor + // + fixed (char* v0 = (string)dataRefObj[0], v1 = (string)dataRefObj[1], v2 = (string)dataRefObj[2], v3 = (string)dataRefObj[3], + v4 = (string)dataRefObj[4], v5 = (string)dataRefObj[5], v6 = (string)dataRefObj[6], v7 = (string)dataRefObj[7]) + { + userDataPtr = (EventData*)userData; + if (dataRefObj[0] != null) + { + userDataPtr[refObjPosition[0]].Ptr = (ulong)v0; + } + if (dataRefObj[1] != null) + { + userDataPtr[refObjPosition[1]].Ptr = (ulong)v1; + } + if (dataRefObj[2] != null) + { + userDataPtr[refObjPosition[2]].Ptr = (ulong)v2; + } + if (dataRefObj[3] != null) + { + userDataPtr[refObjPosition[3]].Ptr = (ulong)v3; + } + if (dataRefObj[4] != null) + { + userDataPtr[refObjPosition[4]].Ptr = (ulong)v4; + } + if (dataRefObj[5] != null) + { + userDataPtr[refObjPosition[5]].Ptr = (ulong)v5; + } + if (dataRefObj[6] != null) + { + userDataPtr[refObjPosition[6]].Ptr = (ulong)v6; + } + if (dataRefObj[7] != null) + { + userDataPtr[refObjPosition[7]].Ptr = (ulong)v7; + } + + status = UnsafeNativeMethods.ManifestEtw.EventWriteTransferWrapper(m_regHandle, ref eventDescriptor, activityID, childActivityID, argCount, userData); + } + } + else + { + // Slow path: use pinned handles + userDataPtr = (EventData*)userData; + + GCHandle[] rgGCHandle = new GCHandle[refObjIndex]; + for (int i = 0; i < refObjIndex; ++i) + { + // below we still use "fixed" to avoid taking dependency on the offset of the first field + // in the object (the way we would need to if we used GCHandle.AddrOfPinnedObject) + rgGCHandle[i] = GCHandle.Alloc(dataRefObj[i], GCHandleType.Pinned); + if (dataRefObj[i] is string) + { + fixed (char* p = (string)dataRefObj[i]) + userDataPtr[refObjPosition[i]].Ptr = (ulong)p; + } + else + { + fixed (byte* p = (byte[])dataRefObj[i]) + userDataPtr[refObjPosition[i]].Ptr = (ulong)p; + } + } + + status = UnsafeNativeMethods.ManifestEtw.EventWriteTransferWrapper(m_regHandle, ref eventDescriptor, activityID, childActivityID, argCount, userData); + + for (int i = 0; i < refObjIndex; ++i) + { + rgGCHandle[i].Free(); + } + } + } + } + + if (status != 0) + { + SetLastError((int)status); + return false; + } + + return true; + } + + /// + /// WriteEvent, method to be used by generated code on a derived class + /// + /// + /// Event Descriptor for this event. + /// + /// + /// A pointer to the activity ID to log + /// + /// + /// If this event is generating a child activity (WriteEventTransfer related activity) this is child activity + /// This can be null for events that do not generate a child activity. + /// + /// + /// number of event descriptors + /// + /// + /// pointer do the event data + /// + // + // + // + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] + internal unsafe protected bool WriteEvent(ref EventDescriptor eventDescriptor, Guid* activityID, Guid* childActivityID, int dataCount, IntPtr data) + { + if (childActivityID != null) + { + // activity transfers are supported only for events that specify the Send or Receive opcode + Debug.Assert((EventOpcode)eventDescriptor.Opcode == EventOpcode.Send || + (EventOpcode)eventDescriptor.Opcode == EventOpcode.Receive || + (EventOpcode)eventDescriptor.Opcode == EventOpcode.Start || + (EventOpcode)eventDescriptor.Opcode == EventOpcode.Stop); + } + + int status = UnsafeNativeMethods.ManifestEtw.EventWriteTransferWrapper(m_regHandle, ref eventDescriptor, activityID, childActivityID, dataCount, (EventData*)data); + + if (status != 0) + { + SetLastError(status); + return false; + } + return true; + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] + internal unsafe bool WriteEventRaw( + ref EventDescriptor eventDescriptor, + Guid* activityID, + Guid* relatedActivityID, + int dataCount, + IntPtr data) + { + int status; + + status = UnsafeNativeMethods.ManifestEtw.EventWriteTransferWrapper( + m_regHandle, + ref eventDescriptor, + activityID, + relatedActivityID, + dataCount, + (EventData*)data); + + if (status != 0) + { + SetLastError(status); + return false; + } + return true; + } + + + // These are look-alikes to the Manifest based ETW OS APIs that have been shimmed to work + // either with Manifest ETW or Classic ETW (if Manifest based ETW is not available). + private unsafe uint EventRegister(ref Guid providerId, UnsafeNativeMethods.ManifestEtw.EtwEnableCallback enableCallback) + { + m_providerId = providerId; + m_etwCallback = enableCallback; + return UnsafeNativeMethods.ManifestEtw.EventRegister(ref providerId, enableCallback, null, ref m_regHandle); + } + + private uint EventUnregister(long registrationHandle) + { + return UnsafeNativeMethods.ManifestEtw.EventUnregister(registrationHandle); + } + + static int[] nibblebits = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 }; + private static int bitcount(uint n) + { + int count = 0; + for (; n != 0; n = n >> 4) + count += nibblebits[n & 0x0f]; + return count; + } + private static int bitindex(uint n) + { + Debug.Assert(bitcount(n) == 1); + int idx = 0; + while ((n & (1 << idx)) == 0) + idx++; + return idx; + } + } +} + diff --git a/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventSource.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventSource.cs new file mode 100644 index 0000000..6a17265 --- /dev/null +++ b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventSource.cs @@ -0,0 +1,6929 @@ +// 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_UNIX + +#define FEATURE_MANAGED_ETW + +#if !ES_BUILD_STANDALONE && !CORECLR && !ES_BUILD_PN +#define FEATURE_ACTIVITYSAMPLING +#endif // !ES_BUILD_STANDALONE + +#endif // !PLATFORM_UNIX + +#if ES_BUILD_STANDALONE +#define FEATURE_MANAGED_ETW_CHANNELS +// #define FEATURE_ADVANCED_MANAGED_ETW_CHANNELS +#endif + +/* DESIGN NOTES DESIGN NOTES DESIGN NOTES DESIGN NOTES */ +// DESIGN NOTES +// Over the years EventSource has become more complex and so it is important to understand +// the basic structure of the code to insure that it does not grow more complex. +// +// Basic Model +// +// PRINCIPLE: EventSource - ETW decoupling +// +// Conceptually and EventSouce is something takes event logging data from the source methods +// To the EventListener that can subscribe them. Note that CONCEPTUALLY EVENTSOURCES DON'T +// KNOW ABOUT ETW!. The MODEL of the system is that there is a special EventListern Which +// we will call the EtwEventListener, that forwards commands from ETW to EventSources and +// listeners to the EventSources and forwards on those events to ETW. THus the model should +// be that you DON'T NEED ETW. +// +// Now in actual practice, EventSouce have rather intimate knowledge of ETW and send events +// to it directly, but this can be VIEWED AS AN OPTIMIATION. +// +// Basic Event Data Flow: +// +// There are two ways for event Data to enter the system +// 1) WriteEvent* and friends. This is called the 'contract' based approach because +// you write a method per event which forms a contract that is know at compile time. +// In this scheme each event is given an EVENTID (small integer). which is its identity +// 2) Write methods. This is called the 'dynamic' approach because new events +// can be created on the fly. Event identity is determined by the event NAME, and these +// are not quite as efficient at runtime since you have at least a hash table lookup +// on every event write. +// +// EventSource-EventListener transfer fully support both ways of writing events (either contract +// based (WriteEvent*) or dynamic (Write). Both way fully support the same set of data +// types. It is suggested, however, that you use the contract based approach when the event scheme +// is known at compile time (that is whenever possible). It is more efficient, but more importantly +// it makes the contract very explicit, and centralizes all policy about logging. These are good +// things. The Write API is really meant for more ad-hoc +// +// Allowed Data. +// +// Note that EventSource-EventListeners have a conceptual serialization-deserialization that happens +// during the transfer. In particular object identity is not preserved, some objects are morphed, +// and not all data types are supported. In particular you can pass +// +// A Valid type to log to an EventSource include +// * Primitive data types +// * IEnumerable of valid types T (this include arrays) (* New for V4.6) +// * Explicitly Opted in class or struct with public property Getters over Valid types. (* New for V4.6) +// +// This set of types is roughly a generalization of JSON support (Basically primitives, bags, and arrays). +// +// Explicitly allowed structs include (* New for V4.6) +// * Marked with the EventData attribute +// * implicitly defined (e.g the C# new {x = 3, y = 5} syntax) +// * KeyValuePair (thus dictionaries can be passed since they are an IEnumerable of KeyValuePair) +// +// When classes are returned in an EventListener, what is returned is something that implements +// IDictionary. Thus when objects are passed to an EventSource they are transformed +// into a key-value bag (the IDictionary) for consumption in the listener. These +// are obvious NOT the original objects. +// +// ETWserialization formats: +// +// As mentioned conceptually EventSource's send data to EventListeners and there is a conceptual +// copy/morph of that data as described above. In addition the .NET framework supports a conceptual +// ETWListener that will send the data to then ETW stream. If you use this feature, the data needs +// to be serialized in a way that ETW supports. ETW supports the following serialization formats +// +// 1) Manifest Based serialization. +// 2) SelfDescribing serialization (TraceLogging style in the TraceLogging directory) +// +// A key factor is that the Write method, which support on the fly definition of events, can't +// support the manifest based serialization because the manifest needs the schema of all events +// to be known before any events are emitted. This implies the following +// +// If you use Write and the output goes to ETW it will use the SelfDescribing format. +// If you use the EventSource(string) constructor for an eventSource (in which you don't +// create a subclass), the default is also to use Self-Describing serialization. In addition +// you can use the EventSoruce(EventSourceSettings) constructor to also explicitly specify +// Self-Describing serialization format. These effect the WriteEvent* APIs going to ETW. +// +// Note that none of this ETW serialization logic affects EventListeners. Only the ETW listener. +// +// ************************************************************************************* +// *** INTERNALS: Event Propagation +// +// Data enters the system either though +// +// 1) A user defined method in the user defined subclass of EventSource which calls +// A) A typesafe type specific overload of WriteEvent(ID, ...) e.g. WriteEvent(ID, string, string) +// * which calls into the unsafe WriteEventCore(ID COUNT EventData*) WriteEventWithRelatedActivityIdCore() +// B) The typesafe overload WriteEvent(ID, object[]) which calls the private helper WriteEventVarargs(ID, Guid* object[]) +// C) Directly into the unsafe WriteEventCore(ID, COUNT EventData*) or WriteEventWithRelatedActivityIdCore() +// +// All event data eventually flows to one of +// * WriteEventWithRelatedActivityIdCore(ID, Guid*, COUNT, EventData*) +// * WriteEventVarargs(ID, Guid*, object[]) +// +// 2) A call to one of the overloads of Write. All these overloads end up in +// * WriteImpl(EventName, Options, Data, Guid*, Guid*) +// +// On output there are the following routines +// Writing to all listeners that are NOT ETW, we have the following routines +// * WriteToAllListeners(ID, Guid*, COUNT, EventData*) +// * WriteToAllListeners(ID, Guid*, object[]) +// * WriteToAllListeners(NAME, Guid*, EventPayload) +// +// EventPayload is the internal type that implements the IDictionary interface +// The EventListeners will pass back for serialized classes for nested object, but +// WriteToAllListeners(NAME, Guid*, EventPayload) unpacks this uses the fields as if they +// were parameters to a method. +// +// The first two are used for the WriteEvent* case, and the later is used for the Write case. +// +// Writing to ETW, Manifest Based +// EventProvider.WriteEvent(EventDescriptor, Guid*, COUNT, EventData*) +// EventProvider.WriteEvent(EventDescriptor, Guid*, object[]) +// Writing to ETW, Self-Describing format +// WriteMultiMerge(NAME, Options, Types, EventData*) +// WriteMultiMerge(NAME, Options, Types, object[]) +// WriteImpl has logic that knows how to serialize (like WriteMultiMerge) but also knows +// will write it to +// +// All ETW writes eventually call +// EventWriteTransfer (native PINVOKE wrapper) +// EventWriteTransferWrapper (fixes compat problem if you pass null as the related activityID) +// EventProvider.WriteEventRaw - sets last error +// EventSource.WriteEventRaw - Does EventSource exception handling logic +// WriteMultiMerge +// WriteImpl +// EventProvider.WriteEvent(EventDescriptor, Guid*, COUNT, EventData*) +// EventProvider.WriteEvent(EventDescriptor, Guid*, object[]) +// +// Serialization: We have a bit of a hodge-podge of serializers right now. Only the one for ETW knows +// how to deal with nested classes or arrays. I will call this serializer the 'TypeInfo' serializer +// since it is the TraceLoggingTypeInfo structure that knows how to do this. Effectively for a type you +// can call one of these +// WriteMetadata - transforms the type T into serialization meta data blob for that type +// WriteObjectData - transforms an object of T into serialization meta data blob for that type +// GetData - transforms an object of T into its deserialized form suitable for passing to EventListener. +// The first two are used to serialize something for ETW. The second one is used to transform the object +// for use by the EventListener. We also have a 'DecodeObject' method that will take a EventData* and +// deserialize to pass to an EventListener, but it only works on primitive types (types supported in version V4.5). +// +// It is an important observation that while EventSource does support users directly calling with EventData* +// blobs, we ONLY support that for the primitive types (V4.5 level support). Thus while there is a EventData* +// path through the system it is only for some types. The object[] path is the more general (but less efficient) path. +// +// TODO There is cleanup needed There should be no divergence until WriteEventRaw. +// +// TODO: We should have a single choke point (right now we always have this parallel EventData* and object[] path. This +// was historical (at one point we tried to pass object directly from EventSoruce to EventListener. That was always +// fragile and a compatibility headache, but we have finally been forced into the idea that there is always a transformation. +// This allows us to use the EventData* form to be the canonical data format in the low level APIs. This also gives us the +// opportunity to expose this format to EventListeners in the future. +// +using System; +using System.Runtime.CompilerServices; +#if FEATURE_ACTIVITYSAMPLING +using System.Collections.Concurrent; +#endif +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Reflection; +using System.Resources; +using System.Security; +#if !CORECLR && !ES_BUILD_PN +using System.Security.Permissions; +#endif // !CORECLR && !ES_BUILD_PN + +using System.Text; +using System.Threading; +using Microsoft.Win32; + +#if ES_BUILD_STANDALONE +using EventDescriptor = Microsoft.Diagnostics.Tracing.EventDescriptor; +#else +using System.Threading.Tasks; +#endif + +using Microsoft.Reflection; + +#if !ES_BUILD_AGAINST_DOTNET_V35 +using Contract = System.Diagnostics.Contracts.Contract; +#else +using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract; +#endif + +#if ES_BUILD_PN +using Internal.Runtime.Augments; +#endif + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// This class is meant to be inherited by a user-defined event source in order to define a managed + /// ETW provider. Please See DESIGN NOTES above for the internal architecture. + /// The minimal definition of an EventSource simply specifies a number of ETW event methods that + /// call one of the EventSource.WriteEvent overloads, , + /// or to log them. This functionality + /// is sufficient for many users. + /// + /// To achieve more control over the ETW provider manifest exposed by the event source type, the + /// [] attributes can be specified for the ETW event methods. + /// + /// For very advanced EventSources, it is possible to intercept the commands being given to the + /// eventSource and change what filtering is done (see EventListener.EnableEvents and + /// ) or cause actions to be performed by the eventSource, + /// e.g. dumping a data structure (see EventSource.SendCommand and + /// ). + /// + /// The eventSources can be turned on with Windows ETW controllers (e.g. logman), immediately. + /// It is also possible to control and intercept the data dispatcher programmatically. See + /// for more. + /// + /// + /// + /// This is a minimal definition for a custom event source: + /// + /// [EventSource(Name="Samples-Demos-Minimal")] + /// sealed class MinimalEventSource : EventSource + /// { + /// public static MinimalEventSource Log = new MinimalEventSource(); + /// public void Load(long ImageBase, string Name) { WriteEvent(1, ImageBase, Name); } + /// public void Unload(long ImageBase) { WriteEvent(2, ImageBase); } + /// private MinimalEventSource() {} + /// } + /// + /// + public partial class EventSource : IDisposable + { + +#if FEATURE_EVENTSOURCE_XPLAT + private static readonly EventListener persistent_Xplat_Listener = XplatEventLogger.InitializePersistentListener(); +#endif //FEATURE_EVENTSOURCE_XPLAT + + /// + /// The human-friendly name of the eventSource. It defaults to the simple name of the class + /// + public string Name { get { return m_name; } } + /// + /// Every eventSource is assigned a GUID to uniquely identify it to the system. + /// + public Guid Guid { get { return m_guid; } } + + /// + /// Returns true if the eventSource has been enabled at all. This is the prefered test + /// to be performed before a relatively expensive EventSource operation. + /// + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + public bool IsEnabled() + { + return m_eventSourceEnabled; + } + + /// + /// Returns true if events with greater than or equal 'level' and have one of 'keywords' set are enabled. + /// + /// Note that the result of this function is only an approximation on whether a particular + /// event is active or not. It is only meant to be used as way of avoiding expensive + /// computation for logging when logging is not on, therefore it sometimes returns false + /// positives (but is always accurate when returning false). EventSources are free to + /// have additional filtering. + /// + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + public bool IsEnabled(EventLevel level, EventKeywords keywords) + { + return IsEnabled(level, keywords, EventChannel.None); + } + + /// + /// Returns true if events with greater than or equal 'level' and have one of 'keywords' set are enabled, or + /// if 'keywords' specifies a channel bit for a channel that is enabled. + /// + /// Note that the result of this function only an approximation on whether a particular + /// event is active or not. It is only meant to be used as way of avoiding expensive + /// computation for logging when logging is not on, therefore it sometimes returns false + /// positives (but is always accurate when returning false). EventSources are free to + /// have additional filtering. + /// + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + public bool IsEnabled(EventLevel level, EventKeywords keywords, EventChannel channel) + { + if (!m_eventSourceEnabled) + return false; + + if (!IsEnabledCommon(m_eventSourceEnabled, m_level, m_matchAnyKeyword, level, keywords, channel)) + return false; + +#if !FEATURE_ACTIVITYSAMPLING + + return true; + +#else // FEATURE_ACTIVITYSAMPLING + + return true; + +#if OPTIMIZE_IS_ENABLED + //================================================================================ + // 2013/03/06 - The code below is a possible optimization for IsEnabled(level, kwd) + // in case activity tracing/sampling is enabled. The added complexity of this + // code however weighs against having it "on" until we know it's really needed. + // For now we'll have this #ifdef-ed out in case we see evidence this is needed. + //================================================================================ + + // At this point we believe the event is enabled, however we now need to check + // if we filter because of activity + + // Optimization, all activity filters also register a delegate here, so if there + // is no delegate, we know there are no activity filters, which means that there + // is no additional filtering, which means that we can return true immediately. + if (s_activityDying == null) + return true; + + // if there's at least one legacy ETW listener we can't filter this + if (m_legacySessions != null && m_legacySessions.Count > 0) + return true; + + // if any event ID that triggers a new activity, or "transfers" activities + // is covered by 'keywords' we can't filter this + if (unchecked(((long)keywords & m_keywordTriggers)) != 0) + return true; + + // See if all listeners have activity filters that would block the event. + for (int perEventSourceSessionId = 0; perEventSourceSessionId < SessionMask.MAX; ++perEventSourceSessionId) + { + EtwSession etwSession = m_etwSessionIdMap[perEventSourceSessionId]; + if (etwSession == null) + continue; + + ActivityFilter activityFilter = etwSession.m_activityFilter; + if (activityFilter == null || + ActivityFilter.GetFilter(activityFilter, this) == null) + { + // No activity filter for ETW, if event is active for ETW, we can't filter. + for (int i = 0; i < m_eventData.Length; i++) + if (m_eventData[i].EnabledForETW) + return true; + } + else if (ActivityFilter.IsCurrentActivityActive(activityFilter)) + return true; + } + + // for regular event listeners + var curDispatcher = m_Dispatchers; + while (curDispatcher != null) + { + ActivityFilter activityFilter = curDispatcher.m_Listener.m_activityFilter; + if (activityFilter == null) + { + // See if any event is enabled. + for (int i = 0; i < curDispatcher.m_EventEnabled.Length; i++) + if (curDispatcher.m_EventEnabled[i]) + return true; + } + else if (ActivityFilter.IsCurrentActivityActive(activityFilter)) + return true; + curDispatcher = curDispatcher.m_Next; + } + + // Every listener has an activity filter that is blocking writing the event, + // thus the event is not enabled. + return false; +#endif // OPTIMIZE_IS_ENABLED + +#endif // FEATURE_ACTIVITYSAMPLING + } + + /// + /// Returns the settings for the event source instance + /// + public EventSourceSettings Settings + { + get { return m_config; } + } + + // Manifest support + /// + /// Returns the GUID that uniquely identifies the eventSource defined by 'eventSourceType'. + /// This API allows you to compute this without actually creating an instance of the EventSource. + /// It only needs to reflect over the type. + /// + public static Guid GetGuid(Type eventSourceType) + { + if (eventSourceType == null) + throw new ArgumentNullException(nameof(eventSourceType)); + Contract.EndContractBlock(); + + EventSourceAttribute attrib = (EventSourceAttribute)GetCustomAttributeHelper(eventSourceType, typeof(EventSourceAttribute)); + string name = eventSourceType.Name; + if (attrib != null) + { + if (attrib.Guid != null) + { + Guid g = Guid.Empty; +#if !ES_BUILD_AGAINST_DOTNET_V35 + if (Guid.TryParse(attrib.Guid, out g)) + return g; +#else + try { return new Guid(attrib.Guid); } + catch (Exception) { } +#endif + } + + if (attrib.Name != null) + name = attrib.Name; + } + + if (name == null) + { + throw new ArgumentException(Resources.GetResourceString("Argument_InvalidTypeName"), nameof(eventSourceType)); + } + return GenerateGuidFromName(name.ToUpperInvariant()); // Make it case insensitive. + } + /// + /// Returns the official ETW Provider name for the eventSource defined by 'eventSourceType'. + /// This API allows you to compute this without actually creating an instance of the EventSource. + /// It only needs to reflect over the type. + /// + public static string GetName(Type eventSourceType) + { + return GetName(eventSourceType, EventManifestOptions.None); + } + + /// + /// Returns a string of the XML manifest associated with the eventSourceType. The scheme for this XML is + /// documented at in EventManifest Schema http://msdn.microsoft.com/en-us/library/aa384043(VS.85).aspx. + /// This is the preferred way of generating a manifest to be embedded in the ETW stream as it is fast and + /// the fact that it only includes localized entries for the current UI culture is an acceptable tradeoff. + /// + /// The type of the event source class for which the manifest is generated + /// The manifest XML fragment contains the string name of the DLL name in + /// which it is embedded. This parameter specifies what name will be used + /// The XML data string + public static string GenerateManifest(Type eventSourceType, string assemblyPathToIncludeInManifest) + { + return GenerateManifest(eventSourceType, assemblyPathToIncludeInManifest, EventManifestOptions.None); + } + /// + /// Returns a string of the XML manifest associated with the eventSourceType. The scheme for this XML is + /// documented at in EventManifest Schema http://msdn.microsoft.com/en-us/library/aa384043(VS.85).aspx. + /// Pass EventManifestOptions.AllCultures when generating a manifest to be registered on the machine. This + /// ensures that the entries in the event log will be "optimally" localized. + /// + /// The type of the event source class for which the manifest is generated + /// The manifest XML fragment contains the string name of the DLL name in + /// which it is embedded. This parameter specifies what name will be used + /// The flags to customize manifest generation. If flags has bit OnlyIfNeededForRegistration specified + /// this returns null when the eventSourceType does not require explicit registration + /// The XML data string or null + public static string GenerateManifest(Type eventSourceType, string assemblyPathToIncludeInManifest, EventManifestOptions flags) + { + if (eventSourceType == null) + throw new ArgumentNullException(nameof(eventSourceType)); + Contract.EndContractBlock(); + + byte[] manifestBytes = EventSource.CreateManifestAndDescriptors(eventSourceType, assemblyPathToIncludeInManifest, null, flags); + return (manifestBytes == null) ? null : Encoding.UTF8.GetString(manifestBytes, 0, manifestBytes.Length); + } + + // EventListener support + /// + /// returns a list (IEnumerable) of all sources in the appdomain). EventListeners typically need this. + /// + /// + public static IEnumerable GetSources() + { + var ret = new List(); + lock (EventListener.EventListenersLock) + { + foreach (WeakReference eventSourceRef in EventListener.s_EventSources) + { + EventSource eventSource = eventSourceRef.Target as EventSource; + if (eventSource != null && !eventSource.IsDisposed) + ret.Add(eventSource); + } + } + return ret; + } + + /// + /// Send a command to a particular EventSource identified by 'eventSource'. + /// Calling this routine simply forwards the command to the EventSource.OnEventCommand + /// callback. What the EventSource does with the command and its arguments are from + /// that point EventSource-specific. + /// + /// The instance of EventSource to send the command to + /// A positive user-defined EventCommand, or EventCommand.SendManifest + /// A set of (name-argument, value-argument) pairs associated with the command + public static void SendCommand(EventSource eventSource, EventCommand command, IDictionary commandArguments) + { + if (eventSource == null) + throw new ArgumentNullException(nameof(eventSource)); + + // User-defined EventCommands should not conflict with the reserved commands. + if ((int)command <= (int)EventCommand.Update && (int)command != (int)EventCommand.SendManifest) + { + throw new ArgumentException(Resources.GetResourceString("EventSource_InvalidCommand"), nameof(command)); + } + + eventSource.SendCommand(null, 0, 0, command, true, EventLevel.LogAlways, EventKeywords.None, commandArguments); + } + +#if !ES_BUILD_STANDALONE + /// + /// This property allows EventSource code to appropriately handle as "different" + /// activities started on different threads that have not had an activity created on them. + /// + internal static Guid InternalCurrentThreadActivityId + { + get + { + Guid retval = CurrentThreadActivityId; + if (retval == Guid.Empty) + { + retval = FallbackActivityId; + } + return retval; + } + } + + internal static Guid FallbackActivityId + { + get + { +#pragma warning disable 612, 618 + int threadID = AppDomain.GetCurrentThreadId(); + + // Managed thread IDs are more aggressively re-used than native thread IDs, + // so we'll use the latter... + return new Guid(unchecked((uint)threadID), + unchecked((ushort)s_currentPid), unchecked((ushort)(s_currentPid >> 16)), + 0x94, 0x1b, 0x87, 0xd5, 0xa6, 0x5c, 0x36, 0x64); +#pragma warning restore 612, 618 + } + } +#endif // !ES_BUILD_STANDALONE + + // Error APIs. (We don't throw by default, but you can probe for status) + /// + /// Because + /// + /// 1) Logging is often optional and thus should not generate fatal errors (exceptions) + /// 2) EventSources are often initialized in class constructors (which propagate exceptions poorly) + /// + /// The event source constructor does not throw exceptions. Instead we remember any exception that + /// was generated (it is also logged to Trace.WriteLine). + /// + public Exception ConstructionException { get { return m_constructionException; } } + + /// + /// EventSources can have arbitrary string key-value pairs associated with them called Traits. + /// These traits are not interpreted by the EventSource but may be interpreted by EventListeners + /// (e.g. like the built in ETW listener). These traits are specififed at EventSource + /// construction time and can be retrieved by using this GetTrait API. + /// + /// The key to look up in the set of key-value pairs passed to the EventSource constructor + /// The value string associated iwth key. Will return null if there is no such key. + public string GetTrait(string key) + { + if (m_traits != null) + { + for (int i = 0; i < m_traits.Length - 1; i += 2) + { + if (m_traits[i] == key) + return m_traits[i + 1]; + } + } + return null; + } + + /// + /// Displays the name and GUID for the eventSource for debugging purposes. + /// + public override string ToString() + { + return Resources.GetResourceString("EventSource_ToString", Name, Guid); + } + + /// + /// Fires when a Command (e.g. Enable) comes from a an EventListener. + /// + public event EventHandler EventCommandExecuted + { + add + { + m_eventCommandExecuted += value; + + // If we have an EventHandler attached to the EventSource before the first command arrives + // It should get a chance to handle the deferred commands. + EventCommandEventArgs deferredCommands = m_deferredCommands; + while (deferredCommands != null) + { + value(this, deferredCommands); + deferredCommands = deferredCommands.nextCommand; + } + } + remove + { + m_eventCommandExecuted -= value; + } + } + + #region protected + /// + /// This is the constructor that most users will use to create their eventSource. It takes + /// no parameters. The ETW provider name and GUID of the EventSource are determined by the EventSource + /// custom attribute (so you can determine these things declaratively). If the GUID for the eventSource + /// is not specified in the EventSourceAttribute (recommended), it is Generated by hashing the name. + /// If the ETW provider name of the EventSource is not given, the name of the EventSource class is used as + /// the ETW provider name. + /// + protected EventSource() + : this(EventSourceSettings.EtwManifestEventFormat) + { + } + + /// + /// By default calling the 'WriteEvent' methods do NOT throw on errors (they silently discard the event). + /// This is because in most cases users assume logging is not 'precious' and do NOT wish to have logging failures + /// crash the program. However for those applications where logging is 'precious' and if it fails the caller + /// wishes to react, setting 'throwOnEventWriteErrors' will cause an exception to be thrown if WriteEvent + /// fails. Note the fact that EventWrite succeeds does not necessarily mean that the event reached its destination + /// only that operation of writing it did not fail. These EventSources will not generate self-describing ETW events. + /// + /// For compatibility only use the EventSourceSettings.ThrowOnEventWriteErrors flag instead. + /// + // [Obsolete("Use the EventSource(EventSourceSettings) overload")] + protected EventSource(bool throwOnEventWriteErrors) + : this(EventSourceSettings.EtwManifestEventFormat | (throwOnEventWriteErrors ? EventSourceSettings.ThrowOnEventWriteErrors : 0)) + { } + + /// + /// Construct an EventSource with additional non-default settings (see EventSourceSettings for more) + /// + protected EventSource(EventSourceSettings settings) : this(settings, null) { } + + /// + /// Construct an EventSource with additional non-default settings. + /// + /// 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). + /// + /// See EventSourceSettings for more. + /// A collection of key-value strings (must be an even number). + protected EventSource(EventSourceSettings settings, params string[] traits) + { + m_config = ValidateSettings(settings); + + Guid eventSourceGuid; + string eventSourceName; + + EventMetadata[] eventDescriptors; + byte[] manifest; + GetMetadata(out eventSourceGuid, out eventSourceName, out eventDescriptors, out manifest); + + if (eventSourceGuid.Equals(Guid.Empty) || eventSourceName == null) + { + var myType = this.GetType(); + eventSourceGuid = GetGuid(myType); + eventSourceName = GetName(myType); + } + + Initialize(eventSourceGuid, eventSourceName, traits); + } + + internal virtual void GetMetadata(out Guid eventSourceGuid, out string eventSourceName, out EventMetadata[] eventData, out byte[] manifestBytes) + { + // + // In ProjectN subclasses need to override this method, and return the data from their EventSourceAttribute and EventAttribute annotations. + // On other architectures it is a no-op. + // + // eventDescriptors needs to contain one EventDescriptor for each event; the event's ID should be the same as its index in this array. + // manifestBytes is a UTF-8 encoding of the ETW manifest for the type. + // + // This will be implemented by an IL rewriter, so we can't make this method abstract or the initial build of the subclass would fail. + // + eventSourceGuid = Guid.Empty; + eventSourceName = null; + eventData = null; + manifestBytes = null; + + return; + } + + /// + /// This method is called when the eventSource is updated by the controller. + /// + protected virtual void OnEventCommand(EventCommandEventArgs command) { } + +#pragma warning disable 1591 + // optimized for common signatures (no args) + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + protected unsafe void WriteEvent(int eventId) + { + WriteEventCore(eventId, 0, null); + } + + // optimized for common signatures (ints) + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + protected unsafe void WriteEvent(int eventId, int arg1) + { + if (m_eventSourceEnabled) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[1]; + descrs[0].DataPointer = (IntPtr)(&arg1); + descrs[0].Size = 4; + WriteEventCore(eventId, 1, descrs); + } + } + + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + protected unsafe void WriteEvent(int eventId, int arg1, int arg2) + { + if (m_eventSourceEnabled) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[2]; + descrs[0].DataPointer = (IntPtr)(&arg1); + descrs[0].Size = 4; + descrs[1].DataPointer = (IntPtr)(&arg2); + descrs[1].Size = 4; + WriteEventCore(eventId, 2, descrs); + } + } + + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + protected unsafe void WriteEvent(int eventId, int arg1, int arg2, int arg3) + { + if (m_eventSourceEnabled) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[3]; + descrs[0].DataPointer = (IntPtr)(&arg1); + descrs[0].Size = 4; + descrs[1].DataPointer = (IntPtr)(&arg2); + descrs[1].Size = 4; + descrs[2].DataPointer = (IntPtr)(&arg3); + descrs[2].Size = 4; + WriteEventCore(eventId, 3, descrs); + } + } + + // optimized for common signatures (longs) + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + protected unsafe void WriteEvent(int eventId, long arg1) + { + if (m_eventSourceEnabled) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[1]; + descrs[0].DataPointer = (IntPtr)(&arg1); + descrs[0].Size = 8; + WriteEventCore(eventId, 1, descrs); + } + } + + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + protected unsafe void WriteEvent(int eventId, long arg1, long arg2) + { + if (m_eventSourceEnabled) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[2]; + descrs[0].DataPointer = (IntPtr)(&arg1); + descrs[0].Size = 8; + descrs[1].DataPointer = (IntPtr)(&arg2); + descrs[1].Size = 8; + WriteEventCore(eventId, 2, descrs); + } + } + + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + protected unsafe void WriteEvent(int eventId, long arg1, long arg2, long arg3) + { + if (m_eventSourceEnabled) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[3]; + descrs[0].DataPointer = (IntPtr)(&arg1); + descrs[0].Size = 8; + descrs[1].DataPointer = (IntPtr)(&arg2); + descrs[1].Size = 8; + descrs[2].DataPointer = (IntPtr)(&arg3); + descrs[2].Size = 8; + WriteEventCore(eventId, 3, descrs); + } + } + + // optimized for common signatures (strings) + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + protected unsafe void WriteEvent(int eventId, string arg1) + { + if (m_eventSourceEnabled) + { + if (arg1 == null) arg1 = ""; + fixed (char* string1Bytes = arg1) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[1]; + descrs[0].DataPointer = (IntPtr)string1Bytes; + descrs[0].Size = ((arg1.Length + 1) * 2); + WriteEventCore(eventId, 1, descrs); + } + } + } + + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + protected unsafe void WriteEvent(int eventId, string arg1, string arg2) + { + if (m_eventSourceEnabled) + { + if (arg1 == null) arg1 = ""; + if (arg2 == null) arg2 = ""; + fixed (char* string1Bytes = arg1) + fixed (char* string2Bytes = arg2) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[2]; + descrs[0].DataPointer = (IntPtr)string1Bytes; + descrs[0].Size = ((arg1.Length + 1) * 2); + descrs[1].DataPointer = (IntPtr)string2Bytes; + descrs[1].Size = ((arg2.Length + 1) * 2); + WriteEventCore(eventId, 2, descrs); + } + } + } + + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + protected unsafe void WriteEvent(int eventId, string arg1, string arg2, string arg3) + { + if (m_eventSourceEnabled) + { + if (arg1 == null) arg1 = ""; + if (arg2 == null) arg2 = ""; + if (arg3 == null) arg3 = ""; + fixed (char* string1Bytes = arg1) + fixed (char* string2Bytes = arg2) + fixed (char* string3Bytes = arg3) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[3]; + descrs[0].DataPointer = (IntPtr)string1Bytes; + descrs[0].Size = ((arg1.Length + 1) * 2); + descrs[1].DataPointer = (IntPtr)string2Bytes; + descrs[1].Size = ((arg2.Length + 1) * 2); + descrs[2].DataPointer = (IntPtr)string3Bytes; + descrs[2].Size = ((arg3.Length + 1) * 2); + WriteEventCore(eventId, 3, descrs); + } + } + } + + // optimized for common signatures (string and ints) + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + protected unsafe void WriteEvent(int eventId, string arg1, int arg2) + { + if (m_eventSourceEnabled) + { + if (arg1 == null) arg1 = ""; + fixed (char* string1Bytes = arg1) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[2]; + descrs[0].DataPointer = (IntPtr)string1Bytes; + descrs[0].Size = ((arg1.Length + 1) * 2); + descrs[1].DataPointer = (IntPtr)(&arg2); + descrs[1].Size = 4; + WriteEventCore(eventId, 2, descrs); + } + } + } + + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + protected unsafe void WriteEvent(int eventId, string arg1, int arg2, int arg3) + { + if (m_eventSourceEnabled) + { + if (arg1 == null) arg1 = ""; + fixed (char* string1Bytes = arg1) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[3]; + descrs[0].DataPointer = (IntPtr)string1Bytes; + descrs[0].Size = ((arg1.Length + 1) * 2); + descrs[1].DataPointer = (IntPtr)(&arg2); + descrs[1].Size = 4; + descrs[2].DataPointer = (IntPtr)(&arg3); + descrs[2].Size = 4; + WriteEventCore(eventId, 3, descrs); + } + } + } + + // optimized for common signatures (string and longs) + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + protected unsafe void WriteEvent(int eventId, string arg1, long arg2) + { + if (m_eventSourceEnabled) + { + if (arg1 == null) arg1 = ""; + fixed (char* string1Bytes = arg1) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[2]; + descrs[0].DataPointer = (IntPtr)string1Bytes; + descrs[0].Size = ((arg1.Length + 1) * 2); + descrs[1].DataPointer = (IntPtr)(&arg2); + descrs[1].Size = 8; + WriteEventCore(eventId, 2, descrs); + } + } + } + + // optimized for common signatures (long and string) + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + protected unsafe void WriteEvent(int eventId, long arg1, string arg2) + { + if (m_eventSourceEnabled) + { + if (arg2 == null) arg2 = ""; + fixed (char* string2Bytes = arg2) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[2]; + descrs[0].DataPointer = (IntPtr)(&arg1); + descrs[0].Size = 8; + descrs[1].DataPointer = (IntPtr)string2Bytes; + descrs[1].Size = ((arg2.Length + 1) * 2); + WriteEventCore(eventId, 2, descrs); + } + } + } + + // optimized for common signatures (int and string) + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + protected unsafe void WriteEvent(int eventId, int arg1, string arg2) + { + if (m_eventSourceEnabled) + { + if (arg2 == null) arg2 = ""; + fixed (char* string2Bytes = arg2) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[2]; + descrs[0].DataPointer = (IntPtr)(&arg1); + descrs[0].Size = 4; + descrs[1].DataPointer = (IntPtr)string2Bytes; + descrs[1].Size = ((arg2.Length + 1) * 2); + WriteEventCore(eventId, 2, descrs); + } + } + } + + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + protected unsafe void WriteEvent(int eventId, byte[] arg1) + { + if (m_eventSourceEnabled) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[2]; + if (arg1 == null || arg1.Length == 0) + { + int blobSize = 0; + descrs[0].DataPointer = (IntPtr)(&blobSize); + descrs[0].Size = 4; + descrs[1].DataPointer = (IntPtr)(&blobSize); // valid address instead of empty content + descrs[1].Size = 0; + WriteEventCore(eventId, 2, descrs); + } + else + { + int blobSize = arg1.Length; + fixed (byte* blob = &arg1[0]) + { + descrs[0].DataPointer = (IntPtr)(&blobSize); + descrs[0].Size = 4; + descrs[1].DataPointer = (IntPtr)blob; + descrs[1].Size = blobSize; + WriteEventCore(eventId, 2, descrs); + } + } + } + } + + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + protected unsafe void WriteEvent(int eventId, long arg1, byte[] arg2) + { + if (m_eventSourceEnabled) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[3]; + descrs[0].DataPointer = (IntPtr)(&arg1); + descrs[0].Size = 8; + if (arg2 == null || arg2.Length == 0) + { + int blobSize = 0; + descrs[1].DataPointer = (IntPtr)(&blobSize); + descrs[1].Size = 4; + descrs[2].DataPointer = (IntPtr)(&blobSize); // valid address instead of empty contents + descrs[2].Size = 0; + WriteEventCore(eventId, 3, descrs); + } + else + { + int blobSize = arg2.Length; + fixed (byte* blob = &arg2[0]) + { + descrs[1].DataPointer = (IntPtr)(&blobSize); + descrs[1].Size = 4; + descrs[2].DataPointer = (IntPtr)blob; + descrs[2].Size = blobSize; + WriteEventCore(eventId, 3, descrs); + } + } + } + } + +#pragma warning restore 1591 + + /// + /// Used to construct the data structure to be passed to the native ETW APIs - EventWrite and EventWriteTransfer. + /// + protected internal struct EventData + { + /// + /// Address where the one argument lives (if this points to managed memory you must ensure the + /// managed object is pinned. + /// + public IntPtr DataPointer { get { return (IntPtr)m_Ptr; } set { m_Ptr = unchecked((long)value); } } + /// + /// Size of the argument referenced by DataPointer + /// + public int Size { get { return m_Size; } set { m_Size = value; } } + + #region private + /// + /// Initializes the members of this EventData object to point at a previously-pinned + /// tracelogging-compatible metadata blob. + /// + /// Pinned tracelogging-compatible metadata blob. + /// The size of the metadata blob. + /// Value for reserved: 2 for per-provider metadata, 1 for per-event metadata + internal unsafe void SetMetadata(byte* pointer, int size, int reserved) + { + this.m_Ptr = (long)(ulong)(UIntPtr)pointer; + this.m_Size = size; + this.m_Reserved = reserved; // Mark this descriptor as containing tracelogging-compatible metadata. + } + + //Important, we pass this structure directly to the Win32 EventWrite API, so this structure must be layed out exactly + // the way EventWrite wants it. + internal long m_Ptr; + internal int m_Size; +#pragma warning disable 0649 + internal int m_Reserved; // Used to pad the size to match the Win32 API +#pragma warning restore 0649 + #endregion + } + + /// + /// This routine allows you to create efficient WriteEvent helpers, however the code that you use to + /// do this, while straightforward, is unsafe. + /// + /// + /// + /// protected unsafe void WriteEvent(int eventId, string arg1, long arg2) + /// { + /// if (IsEnabled()) + /// { + /// if (arg2 == null) arg2 = ""; + /// fixed (char* string2Bytes = arg2) + /// { + /// EventSource.EventData* descrs = stackalloc EventSource.EventData[2]; + /// descrs[0].DataPointer = (IntPtr)(&arg1); + /// descrs[0].Size = 8; + /// descrs[1].DataPointer = (IntPtr)string2Bytes; + /// descrs[1].Size = ((arg2.Length + 1) * 2); + /// WriteEventCore(eventId, 2, descrs); + /// } + /// } + /// } + /// + /// + [CLSCompliant(false)] + protected unsafe void WriteEventCore(int eventId, int eventDataCount, EventSource.EventData* data) + { + WriteEventWithRelatedActivityIdCore(eventId, null, eventDataCount, data); + } + + /// + /// This routine allows you to create efficient WriteEventWithRelatedActivityId helpers, however the code + /// that you use to do this, while straightforward, is unsafe. The only difference from + /// is that you pass the relatedActivityId from caller through to this API + /// + /// + /// + /// protected unsafe void WriteEventWithRelatedActivityId(int eventId, Guid relatedActivityId, string arg1, long arg2) + /// { + /// if (IsEnabled()) + /// { + /// if (arg2 == null) arg2 = ""; + /// fixed (char* string2Bytes = arg2) + /// { + /// EventSource.EventData* descrs = stackalloc EventSource.EventData[2]; + /// descrs[0].DataPointer = (IntPtr)(&arg1); + /// descrs[0].Size = 8; + /// descrs[1].DataPointer = (IntPtr)string2Bytes; + /// descrs[1].Size = ((arg2.Length + 1) * 2); + /// WriteEventWithRelatedActivityIdCore(eventId, relatedActivityId, 2, descrs); + /// } + /// } + /// } + /// + /// + [CLSCompliant(false)] + protected unsafe void WriteEventWithRelatedActivityIdCore(int eventId, Guid* relatedActivityId, int eventDataCount, EventSource.EventData* data) + { + if (m_eventSourceEnabled) + { + try + { + Debug.Assert(m_eventData != null); // You must have initialized this if you enabled the source. + if (relatedActivityId != null) + ValidateEventOpcodeForTransfer(ref m_eventData[eventId], m_eventData[eventId].Name); + + EventOpcode opcode = (EventOpcode)m_eventData[eventId].Descriptor.Opcode; + EventActivityOptions activityOptions = m_eventData[eventId].ActivityOptions; + Guid* pActivityId = null; + Guid activityId = Guid.Empty; + Guid relActivityId = Guid.Empty; + + if (opcode != EventOpcode.Info && relatedActivityId == null && + ((activityOptions & EventActivityOptions.Disable) == 0)) + { + if (opcode == EventOpcode.Start) + { + m_activityTracker.OnStart(m_name, m_eventData[eventId].Name, m_eventData[eventId].Descriptor.Task, ref activityId, ref relActivityId, m_eventData[eventId].ActivityOptions); + } + else if (opcode == EventOpcode.Stop) + { + m_activityTracker.OnStop(m_name, m_eventData[eventId].Name, m_eventData[eventId].Descriptor.Task, ref activityId); + } + + if (activityId != Guid.Empty) + pActivityId = &activityId; + if (relActivityId != Guid.Empty) + relatedActivityId = &relActivityId; + } + +#if FEATURE_MANAGED_ETW + if (m_eventData[eventId].EnabledForETW) + { + +#if FEATURE_ACTIVITYSAMPLING + // this code should be kept in sync with WriteEventVarargs(). + SessionMask etwSessions = SessionMask.All; + // only compute etwSessions if there are *any* ETW filters enabled... + if ((ulong)m_curLiveSessions != 0) + etwSessions = GetEtwSessionMask(eventId, relatedActivityId); + // OutputDebugString(string.Format("{0}.WriteEvent(id {1}) -> to sessions {2:x}", + // m_name, m_eventData[eventId].Name, (ulong) etwSessions)); + + if ((ulong)etwSessions != 0 || m_legacySessions != null && m_legacySessions.Count > 0) + { + if (!SelfDescribingEvents) + { + if (etwSessions.IsEqualOrSupersetOf(m_curLiveSessions)) + { + // OutputDebugString(string.Format(" (1) id {0}, kwd {1:x}", + // m_eventData[eventId].Name, m_eventData[eventId].Descriptor.Keywords)); + // by default the Descriptor.Keyword will have the perEventSourceSessionId bit + // mask set to 0x0f so, when all ETW sessions want the event we don't need to + // synthesize a new one + if (!m_provider.WriteEvent(ref m_eventData[eventId].Descriptor, pActivityId, relatedActivityId, eventDataCount, (IntPtr)data)) + ThrowEventSourceException(m_eventData[eventId].Name); + } + else + { + long origKwd = unchecked((long)((ulong)m_eventData[eventId].Descriptor.Keywords & ~(SessionMask.All.ToEventKeywords()))); + // OutputDebugString(string.Format(" (2) id {0}, kwd {1:x}", + // m_eventData[eventId].Name, etwSessions.ToEventKeywords() | (ulong) origKwd)); + // only some of the ETW sessions will receive this event. Synthesize a new + // Descriptor whose Keywords field will have the appropriate bits set. + // etwSessions might be 0, if there are legacy ETW listeners that want this event + var desc = new EventDescriptor( + m_eventData[eventId].Descriptor.EventId, + m_eventData[eventId].Descriptor.Version, + m_eventData[eventId].Descriptor.Channel, + m_eventData[eventId].Descriptor.Level, + m_eventData[eventId].Descriptor.Opcode, + m_eventData[eventId].Descriptor.Task, + unchecked((long)etwSessions.ToEventKeywords() | origKwd)); + + if (!m_provider.WriteEvent(ref desc, pActivityId, relatedActivityId, eventDataCount, (IntPtr)data)) + ThrowEventSourceException(m_eventData[eventId].Name); + } + } + else + { + TraceLoggingEventTypes tlet = m_eventData[eventId].TraceLoggingEventTypes; + if (tlet == null) + { + tlet = new TraceLoggingEventTypes(m_eventData[eventId].Name, + EventTags.None, + m_eventData[eventId].Parameters); + Interlocked.CompareExchange(ref m_eventData[eventId].TraceLoggingEventTypes, tlet, null); + + } + long origKwd = unchecked((long)((ulong)m_eventData[eventId].Descriptor.Keywords & ~(SessionMask.All.ToEventKeywords()))); + // TODO: activity ID support + EventSourceOptions opt = new EventSourceOptions + { + Keywords = (EventKeywords)unchecked((long)etwSessions.ToEventKeywords() | origKwd), + Level = (EventLevel)m_eventData[eventId].Descriptor.Level, + Opcode = (EventOpcode)m_eventData[eventId].Descriptor.Opcode + }; + + WriteMultiMerge(m_eventData[eventId].Name, ref opt, tlet, pActivityId, relatedActivityId, data); + } + } +#else + if (!SelfDescribingEvents) + { + if (!m_provider.WriteEvent(ref m_eventData[eventId].Descriptor, pActivityId, relatedActivityId, eventDataCount, (IntPtr)data)) + ThrowEventSourceException(m_eventData[eventId].Name); + } + else + { + TraceLoggingEventTypes tlet = m_eventData[eventId].TraceLoggingEventTypes; + if (tlet == null) + { + tlet = new TraceLoggingEventTypes(m_eventData[eventId].Name, + m_eventData[eventId].Tags, + m_eventData[eventId].Parameters); + Interlocked.CompareExchange(ref m_eventData[eventId].TraceLoggingEventTypes, tlet, null); + + } + EventSourceOptions opt = new EventSourceOptions + { + Keywords = (EventKeywords)m_eventData[eventId].Descriptor.Keywords, + Level = (EventLevel)m_eventData[eventId].Descriptor.Level, + Opcode = (EventOpcode)m_eventData[eventId].Descriptor.Opcode + }; + + WriteMultiMerge(m_eventData[eventId].Name, ref opt, tlet, pActivityId, relatedActivityId, data); + } +#endif // FEATURE_ACTIVITYSAMPLING + } +#endif // FEATURE_MANAGED_ETW + + if (m_Dispatchers != null && m_eventData[eventId].EnabledForAnyListener) + WriteToAllListeners(eventId, relatedActivityId, eventDataCount, data); + } + catch (Exception ex) + { + if (ex is EventSourceException) + throw; + else + ThrowEventSourceException(m_eventData[eventId].Name, ex); + } + } + } + + // fallback varags helpers. + /// + /// This is the varargs helper for writing an event. It does create an array and box all the arguments so it is + /// relatively inefficient and should only be used for relatively rare events (e.g. less than 100 / sec). If your + /// rates are faster than that you should use to create fast helpers for your particular + /// method signature. Even if you use this for rare events, this call should be guarded by an + /// check so that the varargs call is not made when the EventSource is not active. + /// + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + protected unsafe void WriteEvent(int eventId, params object[] args) + { + WriteEventVarargs(eventId, null, args); + } + + /// + /// This is the varargs helper for writing an event which also specifies a related activity. It is completely analogous + /// to corresponding WriteEvent (they share implementation). It does create an array and box all the arguments so it is + /// relatively inefficient and should only be used for relatively rare events (e.g. less than 100 / sec). If your + /// rates are faster than that you should use to create fast helpers for your + /// particular method signature. Even if you use this for rare events, this call should be guarded by an + /// check so that the varargs call is not made when the EventSource is not active. + /// + protected unsafe void WriteEventWithRelatedActivityId(int eventId, Guid relatedActivityId, params object[] args) + { + WriteEventVarargs(eventId, &relatedActivityId, args); + } + + #endregion + + #region IDisposable Members + /// + /// Disposes of an EventSource. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + /// + /// Disposes of an EventSource. + /// + /// + /// Called from Dispose() with disposing=true, and from the finalizer (~EventSource) with disposing=false. + /// Guidelines: + /// 1. We may be called more than once: do nothing after the first call. + /// 2. Avoid throwing exceptions if disposing is false, i.e. if we're being finalized. + /// + /// True if called from Dispose(), false if called from the finalizer. + protected virtual void Dispose(bool disposing) + { + if (disposing) + { +#if FEATURE_MANAGED_ETW + // Send the manifest one more time to ensure circular buffers have a chance to get to this information + // even in scenarios with a high volume of ETW events. + if (m_eventSourceEnabled) + { + try + { + SendManifest(m_rawManifest); + } + catch (Exception) + { } // If it fails, simply give up. + m_eventSourceEnabled = false; + } + if (m_provider != null) + { + m_provider.Dispose(); + m_provider = null; + } +#endif + } + m_eventSourceEnabled = false; + m_eventSourceDisposed = true; + } + /// + /// Finalizer for EventSource + /// + ~EventSource() + { + this.Dispose(false); + } + #endregion + + #region private +#if FEATURE_ACTIVITYSAMPLING + internal void WriteStringToListener(EventListener listener, string msg, SessionMask m) + { + Debug.Assert(listener == null || (uint)m == (uint)SessionMask.FromId(0)); + + if (m_eventSourceEnabled) + { + if (listener == null) + { + WriteEventString(0, unchecked((long)m.ToEventKeywords()), msg); + } + else + { + EventWrittenEventArgs eventCallbackArgs = new EventWrittenEventArgs(this); + eventCallbackArgs.EventId = 0; + eventCallbackArgs.Message = msg; + eventCallbackArgs.Payload = new ReadOnlyCollection(new List() { msg }); + eventCallbackArgs.PayloadNames = new ReadOnlyCollection(new List { "message" }); + eventCallbackArgs.EventName = "EventSourceMessage"; + listener.OnEventWritten(eventCallbackArgs); + } + } + } +#endif + + private unsafe void WriteEventRaw( + string eventName, + ref EventDescriptor eventDescriptor, + Guid* activityID, + Guid* relatedActivityID, + int dataCount, + IntPtr data) + { +#if FEATURE_MANAGED_ETW + if (m_provider == null) + { + ThrowEventSourceException(eventName); + } + else + { + if (!m_provider.WriteEventRaw(ref eventDescriptor, activityID, relatedActivityID, dataCount, data)) + ThrowEventSourceException(eventName); + } +#endif // FEATURE_MANAGED_ETW + } + + // FrameworkEventSource is on the startup path for the framework, so we have this internal overload that it can use + // to prevent the working set hit from looking at the custom attributes on the type to get the Guid. + internal EventSource(Guid eventSourceGuid, string eventSourceName) + : this(eventSourceGuid, eventSourceName, EventSourceSettings.EtwManifestEventFormat) + { } + + // Used by the internal FrameworkEventSource constructor and the TraceLogging-style event source constructor + internal EventSource(Guid eventSourceGuid, string eventSourceName, EventSourceSettings settings, string[] traits = null) + { + m_config = ValidateSettings(settings); + Initialize(eventSourceGuid, eventSourceName, traits); + } + + /// + /// This method is responsible for the common initialization path from our constructors. It must + /// not leak any exceptions (otherwise, since most EventSource classes define a static member, + /// "Log", such an exception would become a cached exception for the initialization of the static + /// member, and any future access to the "Log" would throw the cached exception). + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "guid")] + private unsafe void Initialize(Guid eventSourceGuid, string eventSourceName, string[] traits) + { + try + { + m_traits = traits; + if (m_traits != null && m_traits.Length % 2 != 0) + { + throw new ArgumentException(Resources.GetResourceString("TraitEven"), nameof(traits)); + } + + if (eventSourceGuid == Guid.Empty) + { + throw new ArgumentException(Resources.GetResourceString("EventSource_NeedGuid")); + } + + if (eventSourceName == null) + { + throw new ArgumentException(Resources.GetResourceString("EventSource_NeedName")); + } + + m_name = eventSourceName; + m_guid = eventSourceGuid; +#if FEATURE_ACTIVITYSAMPLING + m_curLiveSessions = new SessionMask(0); + m_etwSessionIdMap = new EtwSession[SessionMask.MAX]; +#endif // FEATURE_ACTIVITYSAMPLING + + //Enable Implicit Activity tracker + m_activityTracker = ActivityTracker.Instance; + +#if FEATURE_MANAGED_ETW + // Create and register our provider traits. We do this early because it is needed to log errors + // In the self-describing event case. + this.InitializeProviderMetadata(); + + // Register the provider with ETW + var provider = new OverideEventProvider(this); + provider.Register(eventSourceGuid); +#endif + // Add the eventSource to the global (weak) list. + // This also sets m_id, which is the index in the list. + EventListener.AddEventSource(this); + +#if FEATURE_MANAGED_ETW + // OK if we get this far without an exception, then we can at least write out error messages. + // Set m_provider, which allows this. + m_provider = provider; + +#if (!ES_BUILD_STANDALONE && !ES_BUILD_PN) + // API available on OS >= Win 8 and patched Win 7. + // Disable only for FrameworkEventSource to avoid recursion inside exception handling. + var osVer = Environment.OSVersion.Version.Major * 10 + Environment.OSVersion.Version.Minor; + if (this.Name != "System.Diagnostics.Eventing.FrameworkEventSource" || osVer >= 62) +#endif + { + int setInformationResult; + System.Runtime.InteropServices.GCHandle metadataHandle = + System.Runtime.InteropServices.GCHandle.Alloc(this.providerMetadata, System.Runtime.InteropServices.GCHandleType.Pinned); + IntPtr providerMetadata = metadataHandle.AddrOfPinnedObject(); + + setInformationResult = m_provider.SetInformation( + UnsafeNativeMethods.ManifestEtw.EVENT_INFO_CLASS.SetTraits, + providerMetadata, + (uint)this.providerMetadata.Length); + + metadataHandle.Free(); + } +#endif // FEATURE_MANAGED_ETW + + Debug.Assert(!m_eventSourceEnabled); // We can't be enabled until we are completely initted. + // We are logically completely initialized at this point. + m_completelyInited = true; + } + catch (Exception e) + { + if (m_constructionException == null) + m_constructionException = e; + ReportOutOfBandMessage("ERROR: Exception during construction of EventSource " + Name + ": " + e.Message, true); + } + + // Once m_completelyInited is set, you can have concurrency, so all work is under the lock. + lock (EventListener.EventListenersLock) + { + // If there are any deferred commands, we can do them now. + // This is the most likely place for exceptions to happen. + // Note that we are NOT resetting m_deferredCommands to NULL here, + // We are giving for EventHandler that will be attached later + EventCommandEventArgs deferredCommands = m_deferredCommands; + while (deferredCommands != null) + { + DoCommand(deferredCommands); // This can never throw, it catches them and reports the errors. + deferredCommands = deferredCommands.nextCommand; + } + } + } + + private static string GetName(Type eventSourceType, EventManifestOptions flags) + { + if (eventSourceType == null) + throw new ArgumentNullException(nameof(eventSourceType)); + Contract.EndContractBlock(); + + EventSourceAttribute attrib = (EventSourceAttribute)GetCustomAttributeHelper(eventSourceType, typeof(EventSourceAttribute), flags); + if (attrib != null && attrib.Name != null) + return attrib.Name; + + return eventSourceType.Name; + } + + /// + /// Implements the SHA1 hashing algorithm. Note that this + /// implementation is for hashing public information. Do not + /// use this code to hash private data, as this implementation does + /// not take any steps to avoid information disclosure. + /// + private struct Sha1ForNonSecretPurposes + { + private long length; // Total message length in bits + private uint[] w; // Workspace + private int pos; // Length of current chunk in bytes + + /// + /// Call Start() to initialize the hash object. + /// + public void Start() + { + if (this.w == null) + { + this.w = new uint[85]; + } + + this.length = 0; + this.pos = 0; + this.w[80] = 0x67452301; + this.w[81] = 0xEFCDAB89; + this.w[82] = 0x98BADCFE; + this.w[83] = 0x10325476; + this.w[84] = 0xC3D2E1F0; + } + + /// + /// Adds an input byte to the hash. + /// + /// Data to include in the hash. + public void Append(byte input) + { + this.w[this.pos / 4] = (this.w[this.pos / 4] << 8) | input; + if (64 == ++this.pos) + { + this.Drain(); + } + } + + /// + /// Adds input bytes to the hash. + /// + /// + /// Data to include in the hash. Must not be null. + /// + public void Append(byte[] input) + { + foreach (var b in input) + { + this.Append(b); + } + } + + /// + /// Retrieves the hash value. + /// Note that after calling this function, the hash object should + /// be considered uninitialized. Subsequent calls to Append or + /// Finish will produce useless results. Call Start() to + /// reinitialize. + /// + /// + /// Buffer to receive the hash value. Must not be null. + /// Up to 20 bytes of hash will be written to the output buffer. + /// If the buffer is smaller than 20 bytes, the remaining hash + /// bytes will be lost. If the buffer is larger than 20 bytes, the + /// rest of the buffer is left unmodified. + /// + public void Finish(byte[] output) + { + long l = this.length + 8 * this.pos; + this.Append(0x80); + while (this.pos != 56) + { + this.Append(0x00); + } + + unchecked + { + this.Append((byte)(l >> 56)); + this.Append((byte)(l >> 48)); + this.Append((byte)(l >> 40)); + this.Append((byte)(l >> 32)); + this.Append((byte)(l >> 24)); + this.Append((byte)(l >> 16)); + this.Append((byte)(l >> 8)); + this.Append((byte)l); + + int end = output.Length < 20 ? output.Length : 20; + for (int i = 0; i != end; i++) + { + uint temp = this.w[80 + i / 4]; + output[i] = (byte)(temp >> 24); + this.w[80 + i / 4] = temp << 8; + } + } + } + + /// + /// Called when this.pos reaches 64. + /// + private void Drain() + { + for (int i = 16; i != 80; i++) + { + this.w[i] = Rol1((this.w[i - 3] ^ this.w[i - 8] ^ this.w[i - 14] ^ this.w[i - 16])); + } + + unchecked + { + uint a = this.w[80]; + uint b = this.w[81]; + uint c = this.w[82]; + uint d = this.w[83]; + uint e = this.w[84]; + + for (int i = 0; i != 20; i++) + { + const uint k = 0x5A827999; + uint f = (b & c) | ((~b) & d); + uint temp = Rol5(a) + f + e + k + this.w[i]; e = d; d = c; c = Rol30(b); b = a; a = temp; + } + + for (int i = 20; i != 40; i++) + { + uint f = b ^ c ^ d; + const uint k = 0x6ED9EBA1; + uint temp = Rol5(a) + f + e + k + this.w[i]; e = d; d = c; c = Rol30(b); b = a; a = temp; + } + + for (int i = 40; i != 60; i++) + { + uint f = (b & c) | (b & d) | (c & d); + const uint k = 0x8F1BBCDC; + uint temp = Rol5(a) + f + e + k + this.w[i]; e = d; d = c; c = Rol30(b); b = a; a = temp; + } + + for (int i = 60; i != 80; i++) + { + uint f = b ^ c ^ d; + const uint k = 0xCA62C1D6; + uint temp = Rol5(a) + f + e + k + this.w[i]; e = d; d = c; c = Rol30(b); b = a; a = temp; + } + + this.w[80] += a; + this.w[81] += b; + this.w[82] += c; + this.w[83] += d; + this.w[84] += e; + } + + this.length += 512; // 64 bytes == 512 bits + this.pos = 0; + } + + private static uint Rol1(uint input) + { + return (input << 1) | (input >> 31); + } + + private static uint Rol5(uint input) + { + return (input << 5) | (input >> 27); + } + + private static uint Rol30(uint input) + { + return (input << 30) | (input >> 2); + } + } + + private static Guid GenerateGuidFromName(string name) + { + byte[] bytes = Encoding.BigEndianUnicode.GetBytes(name); + var hash = new Sha1ForNonSecretPurposes(); + hash.Start(); + hash.Append(namespaceBytes); + hash.Append(bytes); + Array.Resize(ref bytes, 16); + hash.Finish(bytes); + + bytes[7] = unchecked((byte)((bytes[7] & 0x0F) | 0x50)); // Set high 4 bits of octet 7 to 5, as per RFC 4122 + return new Guid(bytes); + } + + private unsafe object DecodeObject(int eventId, int parameterId, ref EventSource.EventData* data) + { + // TODO FIX : We use reflection which in turn uses EventSource, right now we carefully avoid + // the recursion, but can we do this in a robust way? + + IntPtr dataPointer = data->DataPointer; + // advance to next EventData in array + ++data; + + Type dataType = GetDataType(m_eventData[eventId], parameterId); + + Again: + if (dataType == typeof(IntPtr)) + { + return *((IntPtr*)dataPointer); + } + else if (dataType == typeof(int)) + { + return *((int*)dataPointer); + } + else if (dataType == typeof(uint)) + { + return *((uint*)dataPointer); + } + else if (dataType == typeof(long)) + { + return *((long*)dataPointer); + } + else if (dataType == typeof(ulong)) + { + return *((ulong*)dataPointer); + } + else if (dataType == typeof(byte)) + { + return *((byte*)dataPointer); + } + else if (dataType == typeof(sbyte)) + { + return *((sbyte*)dataPointer); + } + else if (dataType == typeof(short)) + { + return *((short*)dataPointer); + } + else if (dataType == typeof(ushort)) + { + return *((ushort*)dataPointer); + } + else if (dataType == typeof(float)) + { + return *((float*)dataPointer); + } + else if (dataType == typeof(double)) + { + return *((double*)dataPointer); + } + else if (dataType == typeof(decimal)) + { + return *((decimal*)dataPointer); + } + else if (dataType == typeof(bool)) + { + // The manifest defines a bool as a 32bit type (WIN32 BOOL), not 1 bit as CLR Does. + if (*((int*)dataPointer) == 1) + { + return true; + } + else + { + return false; + } + } + else if (dataType == typeof(Guid)) + { + return *((Guid*)dataPointer); + } + else if (dataType == typeof(char)) + { + return *((char*)dataPointer); + } + else if (dataType == typeof(DateTime)) + { + long dateTimeTicks = *((long*)dataPointer); + return DateTime.FromFileTimeUtc(dateTimeTicks); + } + else if (dataType == typeof(byte[])) + { + // byte[] are written to EventData* as an int followed by a blob + int cbSize = *((int*)dataPointer); + byte[] blob = new byte[cbSize]; + dataPointer = data->DataPointer; + data++; + for (int i = 0; i < cbSize; ++i) + blob[i] = *((byte*)(dataPointer + i)); + return blob; + } + else if (dataType == typeof(byte*)) + { + // TODO: how do we want to handle this? For now we ignore it... + return null; + } + else + { + if (m_EventSourcePreventRecursion && m_EventSourceInDecodeObject) + { + return null; + } + + try + { + m_EventSourceInDecodeObject = true; + + if (dataType.IsEnum()) + { + dataType = Enum.GetUnderlyingType(dataType); + goto Again; + } + + + // Everything else is marshaled as a string. + // ETW strings are NULL-terminated, so marshal everything up to the first + // null in the string. + //return System.Runtime.InteropServices.Marshal.PtrToStringUni(dataPointer); + if(dataPointer == IntPtr.Zero) + { + return null; + } + + return new string((char *)dataPointer); + + } + finally + { + m_EventSourceInDecodeObject = false; + } + } + } + + // Finds the Dispatcher (which holds the filtering state), for a given dispatcher for the current + // eventSource). + private EventDispatcher GetDispatcher(EventListener listener) + { + EventDispatcher dispatcher = m_Dispatchers; + while (dispatcher != null) + { + if (dispatcher.m_Listener == listener) + return dispatcher; + dispatcher = dispatcher.m_Next; + } + return dispatcher; + } + + private unsafe void WriteEventVarargs(int eventId, Guid* childActivityID, object[] args) + { + if (m_eventSourceEnabled) + { + try + { + Debug.Assert(m_eventData != null); // You must have initialized this if you enabled the source. + if (childActivityID != null) + { + ValidateEventOpcodeForTransfer(ref m_eventData[eventId], m_eventData[eventId].Name); + + // If you use WriteEventWithRelatedActivityID you MUST declare the first argument to be a GUID + // with the name 'relatedActivityID, and NOT pass this argument to the WriteEvent method. + // During manifest creation we modify the ParameterInfo[] that we store to strip out any + // first parameter that is of type Guid and named "relatedActivityId." Thus, if you call + // WriteEventWithRelatedActivityID from a method that doesn't name its first parameter correctly + // we can end up in a state where the ParameterInfo[] doesn't have its first parameter stripped, + // and this leads to a mismatch between the number of arguments and the number of ParameterInfos, + // which would cause a cryptic IndexOutOfRangeException later if we don't catch it here. + if (!m_eventData[eventId].HasRelatedActivityID) + { + throw new ArgumentException(Resources.GetResourceString("EventSource_NoRelatedActivityId")); + } + } + + LogEventArgsMismatches(m_eventData[eventId].Parameters, args); + + Guid* pActivityId = null; + Guid activityId = Guid.Empty; + Guid relatedActivityId = Guid.Empty; + EventOpcode opcode = (EventOpcode)m_eventData[eventId].Descriptor.Opcode; + EventActivityOptions activityOptions = m_eventData[eventId].ActivityOptions; + + if (childActivityID == null && + ((activityOptions & EventActivityOptions.Disable) == 0)) + { + if (opcode == EventOpcode.Start) + { + m_activityTracker.OnStart(m_name, m_eventData[eventId].Name, m_eventData[eventId].Descriptor.Task, ref activityId, ref relatedActivityId, m_eventData[eventId].ActivityOptions); + } + else if (opcode == EventOpcode.Stop) + { + m_activityTracker.OnStop(m_name, m_eventData[eventId].Name, m_eventData[eventId].Descriptor.Task, ref activityId); + } + + if (activityId != Guid.Empty) + pActivityId = &activityId; + if (relatedActivityId != Guid.Empty) + childActivityID = &relatedActivityId; + } + +#if FEATURE_MANAGED_ETW + if (m_eventData[eventId].EnabledForETW) + { +#if FEATURE_ACTIVITYSAMPLING + // this code should be kept in sync with WriteEventWithRelatedActivityIdCore(). + SessionMask etwSessions = SessionMask.All; + // only compute etwSessions if there are *any* ETW filters enabled... + if ((ulong)m_curLiveSessions != 0) + etwSessions = GetEtwSessionMask(eventId, childActivityID); + + if ((ulong)etwSessions != 0 || m_legacySessions != null && m_legacySessions.Count > 0) + { + if (!SelfDescribingEvents) + { + if (etwSessions.IsEqualOrSupersetOf(m_curLiveSessions)) + { + // by default the Descriptor.Keyword will have the perEventSourceSessionId bit + // mask set to 0x0f so, when all ETW sessions want the event we don't need to + // synthesize a new one + if (!m_provider.WriteEvent(ref m_eventData[eventId].Descriptor, pActivityId, childActivityID, args)) + ThrowEventSourceException(m_eventData[eventId].Name); + } + else + { + long origKwd = unchecked((long)((ulong)m_eventData[eventId].Descriptor.Keywords & ~(SessionMask.All.ToEventKeywords()))); + // only some of the ETW sessions will receive this event. Synthesize a new + // Descriptor whose Keywords field will have the appropriate bits set. + var desc = new EventDescriptor( + m_eventData[eventId].Descriptor.EventId, + m_eventData[eventId].Descriptor.Version, + m_eventData[eventId].Descriptor.Channel, + m_eventData[eventId].Descriptor.Level, + m_eventData[eventId].Descriptor.Opcode, + m_eventData[eventId].Descriptor.Task, + unchecked((long)etwSessions.ToEventKeywords() | origKwd)); + + if (!m_provider.WriteEvent(ref desc, pActivityId, childActivityID, args)) + ThrowEventSourceException(m_eventData[eventId].Name); + } + } + else + { + TraceLoggingEventTypes tlet = m_eventData[eventId].TraceLoggingEventTypes; + if (tlet == null) + { + tlet = new TraceLoggingEventTypes(m_eventData[eventId].Name, + EventTags.None, + m_eventData[eventId].Parameters); + Interlocked.CompareExchange(ref m_eventData[eventId].TraceLoggingEventTypes, tlet, null); + + } + long origKwd = unchecked((long)((ulong)m_eventData[eventId].Descriptor.Keywords & ~(SessionMask.All.ToEventKeywords()))); + // TODO: activity ID support + EventSourceOptions opt = new EventSourceOptions + { + Keywords = (EventKeywords)unchecked((long)etwSessions.ToEventKeywords() | origKwd), + Level = (EventLevel)m_eventData[eventId].Descriptor.Level, + Opcode = (EventOpcode)m_eventData[eventId].Descriptor.Opcode + }; + + WriteMultiMerge(m_eventData[eventId].Name, ref opt, tlet, pActivityId, childActivityID, args); + } + } +#else + if (!SelfDescribingEvents) + { + if (!m_provider.WriteEvent(ref m_eventData[eventId].Descriptor, pActivityId, childActivityID, args)) + ThrowEventSourceException(m_eventData[eventId].Name); + } + else + { + TraceLoggingEventTypes tlet = m_eventData[eventId].TraceLoggingEventTypes; + if (tlet == null) + { + tlet = new TraceLoggingEventTypes(m_eventData[eventId].Name, + EventTags.None, + m_eventData[eventId].Parameters); + Interlocked.CompareExchange(ref m_eventData[eventId].TraceLoggingEventTypes, tlet, null); + + } + // TODO: activity ID support + EventSourceOptions opt = new EventSourceOptions + { + Keywords = (EventKeywords)m_eventData[eventId].Descriptor.Keywords, + Level = (EventLevel)m_eventData[eventId].Descriptor.Level, + Opcode = (EventOpcode)m_eventData[eventId].Descriptor.Opcode + }; + + WriteMultiMerge(m_eventData[eventId].Name, ref opt, tlet, pActivityId, childActivityID, args); + } +#endif // FEATURE_ACTIVITYSAMPLING + } +#endif // FEATURE_MANAGED_ETW + if (m_Dispatchers != null && m_eventData[eventId].EnabledForAnyListener) + { +#if (!ES_BUILD_STANDALONE && !ES_BUILD_PN) + // Maintain old behavior - object identity is preserved + if (AppContextSwitches.PreserveEventListnerObjectIdentity) + { + WriteToAllListeners(eventId, childActivityID, args); + } + else +#endif // !ES_BUILD_STANDALONE + { + object[] serializedArgs = SerializeEventArgs(eventId, args); + WriteToAllListeners(eventId, childActivityID, serializedArgs); + } + } + } + catch (Exception ex) + { + if (ex is EventSourceException) + throw; + else + ThrowEventSourceException(m_eventData[eventId].Name, ex); + } + } + } + + unsafe private object[] SerializeEventArgs(int eventId, object[] args) + { + TraceLoggingEventTypes eventTypes = m_eventData[eventId].TraceLoggingEventTypes; + if (eventTypes == null) + { + eventTypes = new TraceLoggingEventTypes(m_eventData[eventId].Name, + EventTags.None, + m_eventData[eventId].Parameters); + Interlocked.CompareExchange(ref m_eventData[eventId].TraceLoggingEventTypes, eventTypes, null); + } + var eventData = new object[eventTypes.typeInfos.Length]; + for (int i = 0; i < eventTypes.typeInfos.Length; i++) + { + eventData[i] = eventTypes.typeInfos[i].GetData(args[i]); + } + return eventData; + } + + /// + /// We expect that the arguments to the Event method and the arguments to WriteEvent match. This function + /// checks that they in fact match and logs a warning to the debugger if they don't. + /// + /// + /// + private void LogEventArgsMismatches(ParameterInfo[] infos, object[] args) + { +#if (!ES_BUILD_PCL && !ES_BUILD_PN) + // It would be nice to have this on PCL builds, but it would be pointless since there isn't support for + // writing to the debugger log on PCL. + bool typesMatch = args.Length == infos.Length; + + int i = 0; + while (typesMatch && i < args.Length) + { + Type pType = infos[i].ParameterType; + + // Checking to see if the Parameter types (from the Event method) match the supplied argument types. + // Fail if one of two things hold : either the argument type is not equal to the parameter type, or the + // argument is null and the parameter type is non-nullable. + if ((args[i] != null && (args[i].GetType() != pType)) + || (args[i] == null && (!(pType.IsGenericType && pType.GetGenericTypeDefinition() == typeof(Nullable<>)))) + ) + { + typesMatch = false; + break; + } + + ++i; + } + + if (!typesMatch) + { + System.Diagnostics.Debugger.Log(0, null, Resources.GetResourceString("EventSource_VarArgsParameterMismatch") + "\r\n"); + } +#endif //!ES_BUILD_PCL + } + + private int GetParamLenghtIncludingByteArray(ParameterInfo[] parameters) + { + int sum = 0; + foreach (ParameterInfo info in parameters) + { + if (info.ParameterType == typeof(byte[])) + { + sum += 2; + } + else + { + sum++; + } + } + + return sum; + } + + unsafe private void WriteToAllListeners(int eventId, Guid* childActivityID, int eventDataCount, EventSource.EventData* data) + { + // We represent a byte[] as a integer denoting the length and then a blob of bytes in the data pointer. This causes a spurious + // warning because eventDataCount is off by one for the byte[] case since a byte[] has 2 items associated it. So we want to check + // that the number of parameters is correct against the byte[] case, but also we the args array would be one too long if + // we just used the modifiedParamCount here -- so we need both. + int paramCount = m_eventData[eventId].Parameters.Length; + int modifiedParamCount = GetParamLenghtIncludingByteArray(m_eventData[eventId].Parameters); + if (eventDataCount != modifiedParamCount) + { + ReportOutOfBandMessage(Resources.GetResourceString("EventSource_EventParametersMismatch", eventId, eventDataCount, paramCount), true); + paramCount = Math.Min(paramCount, eventDataCount); + } + + object[] args = new object[paramCount]; + + EventSource.EventData* dataPtr = data; + for (int i = 0; i < paramCount; i++) + args[i] = DecodeObject(eventId, i, ref dataPtr); + WriteToAllListeners(eventId, childActivityID, args); + } + + // helper for writing to all EventListeners attached the current eventSource. + unsafe private void WriteToAllListeners(int eventId, Guid* childActivityID, params object[] args) + { + EventWrittenEventArgs eventCallbackArgs = new EventWrittenEventArgs(this); + eventCallbackArgs.EventId = eventId; + if (childActivityID != null) + eventCallbackArgs.RelatedActivityId = *childActivityID; + eventCallbackArgs.EventName = m_eventData[eventId].Name; + eventCallbackArgs.Message = m_eventData[eventId].Message; + eventCallbackArgs.Payload = new ReadOnlyCollection(args); + + DispatchToAllListeners(eventId, childActivityID, eventCallbackArgs); + } + + private unsafe void DispatchToAllListeners(int eventId, Guid* childActivityID, EventWrittenEventArgs eventCallbackArgs) + { + Exception lastThrownException = null; + for (EventDispatcher dispatcher = m_Dispatchers; dispatcher != null; dispatcher = dispatcher.m_Next) + { + Debug.Assert(dispatcher.m_EventEnabled != null); + if (eventId == -1 || dispatcher.m_EventEnabled[eventId]) + { +#if FEATURE_ACTIVITYSAMPLING + var activityFilter = dispatcher.m_Listener.m_activityFilter; + // order below is important as PassesActivityFilter will "flow" active activities + // even when the current EventSource doesn't have filtering enabled. This allows + // interesting activities to be updated so that sources that do sample can get + // accurate data + if (activityFilter == null || + ActivityFilter.PassesActivityFilter(activityFilter, childActivityID, + m_eventData[eventId].TriggersActivityTracking > 0, + this, eventId) || + !dispatcher.m_activityFilteringEnabled) +#endif // FEATURE_ACTIVITYSAMPLING + { + try + { + dispatcher.m_Listener.OnEventWritten(eventCallbackArgs); + } + catch (Exception e) + { + ReportOutOfBandMessage("ERROR: Exception during EventSource.OnEventWritten: " + + e.Message, false); + lastThrownException = e; + } + } + } + } + + if (lastThrownException != null) + { + throw new EventSourceException(lastThrownException); + } + } + + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + private unsafe void WriteEventString(EventLevel level, long keywords, string msgString) + { +#if FEATURE_MANAGED_ETW + if (m_provider != null) + { + string eventName = "EventSourceMessage"; + if (SelfDescribingEvents) + { + EventSourceOptions opt = new EventSourceOptions + { + Keywords = (EventKeywords)unchecked(keywords), + Level = level + }; + var msg = new { message = msgString }; + var tlet = new TraceLoggingEventTypes(eventName, EventTags.None, new Type[] { msg.GetType() }); + WriteMultiMergeInner(eventName, ref opt, tlet, null, null, msg); + } + else + { + // We want the name of the provider to show up so if we don't have a manifest we create + // on that at least has the provider name (I don't define any events). + if (m_rawManifest == null && m_outOfBandMessageCount == 1) + { + ManifestBuilder manifestBuilder = new ManifestBuilder(Name, Guid, Name, null, EventManifestOptions.None); + manifestBuilder.StartEvent(eventName, new EventAttribute(0) { Level = EventLevel.LogAlways, Task = (EventTask)0xFFFE }); + manifestBuilder.AddEventParameter(typeof(string), "message"); + manifestBuilder.EndEvent(); + SendManifest(manifestBuilder.CreateManifest()); + } + + // We use this low level routine to to bypass the enabled checking, since the eventSource itself is only partially inited. + fixed (char* msgStringPtr = msgString) + { + EventDescriptor descr = new EventDescriptor(0, 0, 0, (byte)level, 0, 0, keywords); + EventProvider.EventData data = new EventProvider.EventData(); + data.Ptr = (ulong)msgStringPtr; + data.Size = (uint)(2 * (msgString.Length + 1)); + data.Reserved = 0; + m_provider.WriteEvent(ref descr, null, null, 1, (IntPtr)((void*)&data)); + } + } + } +#endif // FEATURE_MANAGED_ETW + } + + /// + /// Since this is a means of reporting errors (see ReportoutOfBandMessage) any failure encountered + /// while writing the message to any one of the listeners will be silently ignored. + /// + private void WriteStringToAllListeners(string eventName, string msg) + { + EventWrittenEventArgs eventCallbackArgs = new EventWrittenEventArgs(this); + eventCallbackArgs.EventId = 0; + eventCallbackArgs.Message = msg; + eventCallbackArgs.Payload = new ReadOnlyCollection(new List() { msg }); + eventCallbackArgs.PayloadNames = new ReadOnlyCollection(new List { "message" }); + eventCallbackArgs.EventName = eventName; + + for (EventDispatcher dispatcher = m_Dispatchers; dispatcher != null; dispatcher = dispatcher.m_Next) + { + bool dispatcherEnabled = false; + if (dispatcher.m_EventEnabled == null) + { + // if the listeners that weren't correctly initialized, we will send to it + // since this is an error message and we want to see it go out. + dispatcherEnabled = true; + } + else + { + // if there's *any* enabled event on the dispatcher we'll write out the string + // otherwise we'll treat the listener as disabled and skip it + for (int evtId = 0; evtId < dispatcher.m_EventEnabled.Length; ++evtId) + { + if (dispatcher.m_EventEnabled[evtId]) + { + dispatcherEnabled = true; + break; + } + } + } + try + { + if (dispatcherEnabled) + dispatcher.m_Listener.OnEventWritten(eventCallbackArgs); + } + catch + { + // ignore any exceptions thrown by listeners' OnEventWritten + } + } + } + +#if FEATURE_ACTIVITYSAMPLING + unsafe private SessionMask GetEtwSessionMask(int eventId, Guid* childActivityID) + { + SessionMask etwSessions = new SessionMask(); + + for (int i = 0; i < SessionMask.MAX; ++i) + { + EtwSession etwSession = m_etwSessionIdMap[i]; + if (etwSession != null) + { + ActivityFilter activityFilter = etwSession.m_activityFilter; + // PassesActivityFilter() will flow "interesting" activities, so make sure + // to perform this test first, before ORing with ~m_activityFilteringForETWEnabled + // (note: the first test for !m_activityFilteringForETWEnabled[i] ensures we + // do not fire events indiscriminately, when no filters are specified, but only + // if, in addition, the session did not also enable ActivitySampling) + if (activityFilter == null && !m_activityFilteringForETWEnabled[i] || + activityFilter != null && + ActivityFilter.PassesActivityFilter(activityFilter, childActivityID, + m_eventData[eventId].TriggersActivityTracking > 0, this, eventId) || + !m_activityFilteringForETWEnabled[i]) + { + etwSessions[i] = true; + } + } + } + // flow "interesting" activities for all legacy sessions in which there's some + // level of activity tracing enabled (even other EventSources) + if (m_legacySessions != null && m_legacySessions.Count > 0 && + (EventOpcode)m_eventData[eventId].Descriptor.Opcode == EventOpcode.Send) + { + // only calculate InternalCurrentThreadActivityId once + Guid* pCurrentActivityId = null; + Guid currentActivityId; + foreach (var legacyEtwSession in m_legacySessions) + { + if (legacyEtwSession == null) + continue; + + ActivityFilter activityFilter = legacyEtwSession.m_activityFilter; + if (activityFilter != null) + { + if (pCurrentActivityId == null) + { + currentActivityId = InternalCurrentThreadActivityId; + pCurrentActivityId = ¤tActivityId; + } + ActivityFilter.FlowActivityIfNeeded(activityFilter, pCurrentActivityId, childActivityID); + } + } + } + + return etwSessions; + } +#endif // FEATURE_ACTIVITYSAMPLING + + /// + /// Returns true if 'eventNum' is enabled if you only consider the level and matchAnyKeyword filters. + /// It is possible that eventSources turn off the event based on additional filtering criteria. + /// + private bool IsEnabledByDefault(int eventNum, bool enable, EventLevel currentLevel, EventKeywords currentMatchAnyKeyword) + { + if (!enable) + return false; + + EventLevel eventLevel = (EventLevel)m_eventData[eventNum].Descriptor.Level; + EventKeywords eventKeywords = unchecked((EventKeywords)((ulong)m_eventData[eventNum].Descriptor.Keywords & (~(SessionMask.All.ToEventKeywords())))); + +#if FEATURE_MANAGED_ETW_CHANNELS + EventChannel channel = unchecked((EventChannel)m_eventData[eventNum].Descriptor.Channel); +#else + EventChannel channel = EventChannel.None; +#endif + + return IsEnabledCommon(enable, currentLevel, currentMatchAnyKeyword, eventLevel, eventKeywords, channel); + } + + private bool IsEnabledCommon(bool enabled, EventLevel currentLevel, EventKeywords currentMatchAnyKeyword, + EventLevel eventLevel, EventKeywords eventKeywords, EventChannel eventChannel) + { + if (!enabled) + return false; + + // does is pass the level test? + if ((currentLevel != 0) && (currentLevel < eventLevel)) + return false; + + // if yes, does it pass the keywords test? + if (currentMatchAnyKeyword != 0 && eventKeywords != 0) + { +#if FEATURE_MANAGED_ETW_CHANNELS + // is there a channel with keywords that match currentMatchAnyKeyword? + if (eventChannel != EventChannel.None && this.m_channelData != null && this.m_channelData.Length > (int)eventChannel) + { + EventKeywords channel_keywords = unchecked((EventKeywords)(m_channelData[(int)eventChannel] | (ulong)eventKeywords)); + if (channel_keywords != 0 && (channel_keywords & currentMatchAnyKeyword) == 0) + return false; + } + else +#endif + { + if ((unchecked((ulong)eventKeywords & (ulong)currentMatchAnyKeyword)) == 0) + return false; + } + } + return true; + + } + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] + private void ThrowEventSourceException(string eventName, Exception innerEx = null) + { + // If we fail during out of band logging we may end up trying + // to throw another EventSourceException, thus hitting a StackOverflowException. + // Avoid StackOverflow by making sure we do not recursively call this method. + if (m_EventSourceExceptionRecurenceCount > 0) + return; + try + { + m_EventSourceExceptionRecurenceCount++; + + string errorPrefix = "EventSourceException"; + if (eventName != null) + { + errorPrefix += " while processing event \"" + eventName + "\""; + } + + // TODO Create variations of EventSourceException that indicate more information using the error code. + switch (EventProvider.GetLastWriteEventError()) + { + case EventProvider.WriteEventErrorCode.EventTooBig: + ReportOutOfBandMessage(errorPrefix + ": " + Resources.GetResourceString("EventSource_EventTooBig"), true); + if (ThrowOnEventWriteErrors) throw new EventSourceException(Resources.GetResourceString("EventSource_EventTooBig"), innerEx); + break; + case EventProvider.WriteEventErrorCode.NoFreeBuffers: + ReportOutOfBandMessage(errorPrefix + ": " + Resources.GetResourceString("EventSource_NoFreeBuffers"), true); + if (ThrowOnEventWriteErrors) throw new EventSourceException(Resources.GetResourceString("EventSource_NoFreeBuffers"), innerEx); + break; + case EventProvider.WriteEventErrorCode.NullInput: + ReportOutOfBandMessage(errorPrefix + ": " + Resources.GetResourceString("EventSource_NullInput"), true); + if (ThrowOnEventWriteErrors) throw new EventSourceException(Resources.GetResourceString("EventSource_NullInput"), innerEx); + break; + case EventProvider.WriteEventErrorCode.TooManyArgs: + ReportOutOfBandMessage(errorPrefix + ": " + Resources.GetResourceString("EventSource_TooManyArgs"), true); + if (ThrowOnEventWriteErrors) throw new EventSourceException(Resources.GetResourceString("EventSource_TooManyArgs"), innerEx); + break; + default: + if (innerEx != null) + ReportOutOfBandMessage(errorPrefix + ": " + innerEx.GetType() + ":" + innerEx.Message, true); + else + ReportOutOfBandMessage(errorPrefix, true); + if (ThrowOnEventWriteErrors) throw new EventSourceException(innerEx); + break; + } + } + finally + { + m_EventSourceExceptionRecurenceCount--; + } + } + + private void ValidateEventOpcodeForTransfer(ref EventMetadata eventData, string eventName) + { + if ((EventOpcode)eventData.Descriptor.Opcode != EventOpcode.Send && + (EventOpcode)eventData.Descriptor.Opcode != EventOpcode.Receive && + (EventOpcode)eventData.Descriptor.Opcode != EventOpcode.Start) + { + ThrowEventSourceException(eventName); + } + } + + internal static EventOpcode GetOpcodeWithDefault(EventOpcode opcode, string eventName) + { + if (opcode == EventOpcode.Info && eventName != null) + { + if (eventName.EndsWith(s_ActivityStartSuffix, StringComparison.Ordinal)) + { + return EventOpcode.Start; + } + else if (eventName.EndsWith(s_ActivityStopSuffix, StringComparison.Ordinal)) + { + return EventOpcode.Stop; + } + } + + return opcode; + } + +#if FEATURE_MANAGED_ETW + /// + /// This class lets us hook the 'OnEventCommand' from the eventSource. + /// + private class OverideEventProvider : EventProvider + { + public OverideEventProvider(EventSource eventSource) + { + this.m_eventSource = eventSource; + } + protected override void OnControllerCommand(ControllerCommand command, IDictionary arguments, + int perEventSourceSessionId, int etwSessionId) + { + // We use null to represent the ETW EventListener. + EventListener listener = null; + m_eventSource.SendCommand(listener, perEventSourceSessionId, etwSessionId, + (EventCommand)command, IsEnabled(), Level, MatchAnyKeyword, arguments); + } + private EventSource m_eventSource; + } +#endif + + /// + /// Used to hold all the static information about an event. This includes everything in the event + /// descriptor as well as some stuff we added specifically for EventSource. see the + /// code:m_eventData for where we use this. + /// + internal partial struct EventMetadata + { + public EventDescriptor Descriptor; + public EventTags Tags; + public bool EnabledForAnyListener; // true if any dispatcher has this event turned on + public bool EnabledForETW; // is this event on for the OS ETW data dispatcher? + + public bool HasRelatedActivityID; // Set if the event method's first parameter is a Guid named 'relatedActivityId' +#if !FEATURE_ACTIVITYSAMPLING +#pragma warning disable 0649 +#endif + public byte TriggersActivityTracking; // count of listeners that marked this event as trigger for start of activity logging. +#if !FEATURE_ACTIVITYSAMPLING +#pragma warning restore 0649 +#endif + public string Name; // the name of the event + public string Message; // If the event has a message associated with it, this is it. + public ParameterInfo[] Parameters; // TODO can we remove? + + public TraceLoggingEventTypes TraceLoggingEventTypes; + public EventActivityOptions ActivityOptions; + +#if ES_BUILD_PN + public EventParameterType[] ParameterTypes; +#endif + }; + + // This is the internal entry point that code:EventListeners call when wanting to send a command to a + // eventSource. The logic is as follows + // + // * if Command == Update + // * perEventSourceSessionId specifies the per-provider ETW session ID that the command applies + // to (if listener != null) + // perEventSourceSessionId = 0 - reserved for EventListeners + // perEventSourceSessionId = 1..SessionMask.MAX - reserved for activity tracing aware ETW sessions + // perEventSourceSessionId-1 represents the bit in the reserved field (bits 44..47) in + // Keywords that identifies the session + // perEventSourceSessionId = SessionMask.MAX+1 - reserved for legacy ETW sessions; these are + // discriminated by etwSessionId + // * etwSessionId specifies a machine-wide ETW session ID; this allows correlation of + // activity tracing across different providers (which might have different sessionIds + // for the same ETW session) + // * enable, level, matchAnyKeywords are used to set a default for all events for the + // eventSource. In particular, if 'enabled' is false, 'level' and + // 'matchAnyKeywords' are not used. + // * OnEventCommand is invoked, which may cause calls to + // code:EventSource.EnableEventForDispatcher which may cause changes in the filtering + // depending on the logic in that routine. + // * else (command != Update) + // * Simply call OnEventCommand. The expectation is that filtering is NOT changed. + // * The 'enabled' 'level', matchAnyKeyword' arguments are ignored (must be true, 0, 0). + // + // dispatcher == null has special meaning. It is the 'ETW' dispatcher. + internal void SendCommand(EventListener listener, int perEventSourceSessionId, int etwSessionId, + EventCommand command, bool enable, + EventLevel level, EventKeywords matchAnyKeyword, + IDictionary commandArguments) + { + var commandArgs = new EventCommandEventArgs(command, commandArguments, this, listener, perEventSourceSessionId, etwSessionId, enable, level, matchAnyKeyword); + lock (EventListener.EventListenersLock) + { + if (m_completelyInited) + { + // After the first command arrive after construction, we are ready to get rid of the deferred commands + this.m_deferredCommands = null; + // We are fully initialized, do the command + DoCommand(commandArgs); + } + else + { + // We can't do the command, simply remember it and we do it when we are fully constructed. + commandArgs.nextCommand = m_deferredCommands; + m_deferredCommands = commandArgs; + } + } + } + + /// + /// We want the eventSource to be fully initialized when we do commands because that way we can send + /// error messages and other logging directly to the event stream. Unfortunately we can get callbacks + /// when we are not fully initialized. In that case we store them in 'commandArgs' and do them later. + /// This helper actually does all actual command logic. + /// + internal void DoCommand(EventCommandEventArgs commandArgs) + { + // PRECONDITION: We should be holding the EventListener.EventListenersLock + // We defer commands until we are completely inited. This allows error messages to be sent. + Debug.Assert(m_completelyInited); + +#if FEATURE_MANAGED_ETW + if (m_provider == null) // If we failed to construct + return; +#endif // FEATURE_MANAGED_ETW + + m_outOfBandMessageCount = 0; + bool shouldReport = (commandArgs.perEventSourceSessionId > 0) && (commandArgs.perEventSourceSessionId <= SessionMask.MAX); + try + { + EnsureDescriptorsInitialized(); + Debug.Assert(m_eventData != null); + + // Find the per-EventSource dispatcher corresponding to registered dispatcher + commandArgs.dispatcher = GetDispatcher(commandArgs.listener); + if (commandArgs.dispatcher == null && commandArgs.listener != null) // dispatcher == null means ETW dispatcher + { + throw new ArgumentException(Resources.GetResourceString("EventSource_ListenerNotFound")); + } + + if (commandArgs.Arguments == null) + commandArgs.Arguments = new Dictionary(); + + if (commandArgs.Command == EventCommand.Update) + { + // Set it up using the 'standard' filtering bitfields (use the "global" enable, not session specific one) + for (int i = 0; i < m_eventData.Length; i++) + EnableEventForDispatcher(commandArgs.dispatcher, i, IsEnabledByDefault(i, commandArgs.enable, commandArgs.level, commandArgs.matchAnyKeyword)); + + if (commandArgs.enable) + { + if (!m_eventSourceEnabled) + { + // EventSource turned on for the first time, simply copy the bits. + m_level = commandArgs.level; + m_matchAnyKeyword = commandArgs.matchAnyKeyword; + } + else + { + // Already enabled, make it the most verbose of the existing and new filter + if (commandArgs.level > m_level) + m_level = commandArgs.level; + if (commandArgs.matchAnyKeyword == 0) + m_matchAnyKeyword = 0; + else if (m_matchAnyKeyword != 0) + m_matchAnyKeyword = unchecked(m_matchAnyKeyword | commandArgs.matchAnyKeyword); + } + } + + // interpret perEventSourceSessionId's sign, and adjust perEventSourceSessionId to + // represent 0-based positive values + bool bSessionEnable = (commandArgs.perEventSourceSessionId >= 0); + if (commandArgs.perEventSourceSessionId == 0 && commandArgs.enable == false) + bSessionEnable = false; + + if (commandArgs.listener == null) + { + if (!bSessionEnable) + commandArgs.perEventSourceSessionId = -commandArgs.perEventSourceSessionId; + // for "global" enable/disable (passed in with listener == null and + // perEventSourceSessionId == 0) perEventSourceSessionId becomes -1 + --commandArgs.perEventSourceSessionId; + } + + commandArgs.Command = bSessionEnable ? EventCommand.Enable : EventCommand.Disable; + + // perEventSourceSessionId = -1 when ETW sent a notification, but the set of active sessions + // hasn't changed. + // sesisonId = SessionMask.MAX when one of the legacy ETW sessions changed + // 0 <= perEventSourceSessionId < SessionMask.MAX for activity-tracing aware sessions + Debug.Assert(commandArgs.perEventSourceSessionId >= -1 && commandArgs.perEventSourceSessionId <= SessionMask.MAX); + + // Send the manifest if we are enabling an ETW session + if (bSessionEnable && commandArgs.dispatcher == null) + { + // eventSourceDispatcher == null means this is the ETW manifest + + // Note that we unconditionally send the manifest whenever we are enabled, even if + // we were already enabled. This is because there may be multiple sessions active + // and we can't know that all the sessions have seen the manifest. + if (!SelfDescribingEvents) + SendManifest(m_rawManifest); + } + +#if FEATURE_ACTIVITYSAMPLING + if (bSessionEnable && commandArgs.perEventSourceSessionId != -1) + { + bool participateInSampling = false; + string activityFilters; + int sessionIdBit; + + ParseCommandArgs(commandArgs.Arguments, out participateInSampling, + out activityFilters, out sessionIdBit); + + if (commandArgs.listener == null && commandArgs.Arguments.Count > 0 && commandArgs.perEventSourceSessionId != sessionIdBit) + { + throw new ArgumentException(Resources.GetResourceString("EventSource_SessionIdError", + commandArgs.perEventSourceSessionId + SessionMask.SHIFT_SESSION_TO_KEYWORD, + sessionIdBit + SessionMask.SHIFT_SESSION_TO_KEYWORD)); + } + + if (commandArgs.listener == null) + { + UpdateEtwSession(commandArgs.perEventSourceSessionId, commandArgs.etwSessionId, true, activityFilters, participateInSampling); + } + else + { + ActivityFilter.UpdateFilter(ref commandArgs.listener.m_activityFilter, this, 0, activityFilters); + commandArgs.dispatcher.m_activityFilteringEnabled = participateInSampling; + } + } + else if (!bSessionEnable && commandArgs.listener == null) + { + // if we disable an ETW session, indicate that in a synthesized command argument + if (commandArgs.perEventSourceSessionId >= 0 && commandArgs.perEventSourceSessionId < SessionMask.MAX) + { + commandArgs.Arguments["EtwSessionKeyword"] = (commandArgs.perEventSourceSessionId + SessionMask.SHIFT_SESSION_TO_KEYWORD).ToString(CultureInfo.InvariantCulture); + } + } +#endif // FEATURE_ACTIVITYSAMPLING + + // Turn on the enable bit before making the OnEventCommand callback This allows you to do useful + // things like log messages, or test if keywords are enabled in the callback. + if (commandArgs.enable) + { + Debug.Assert(m_eventData != null); + m_eventSourceEnabled = true; + } + + this.OnEventCommand(commandArgs); + var eventCommandCallback = this.m_eventCommandExecuted; + if (eventCommandCallback != null) + eventCommandCallback(this, commandArgs); + +#if FEATURE_ACTIVITYSAMPLING + if (commandArgs.listener == null && !bSessionEnable && commandArgs.perEventSourceSessionId != -1) + { + // if we disable an ETW session, complete disabling it + UpdateEtwSession(commandArgs.perEventSourceSessionId, commandArgs.etwSessionId, false, null, false); + } +#endif // FEATURE_ACTIVITYSAMPLING + + if (!commandArgs.enable) + { + // If we are disabling, maybe we can turn on 'quick checks' to filter + // quickly. These are all just optimizations (since later checks will still filter) + +#if FEATURE_ACTIVITYSAMPLING + // Turn off (and forget) any information about Activity Tracing. + if (commandArgs.listener == null) + { + // reset all filtering information for activity-tracing-aware sessions + for (int i = 0; i < SessionMask.MAX; ++i) + { + EtwSession etwSession = m_etwSessionIdMap[i]; + if (etwSession != null) + ActivityFilter.DisableFilter(ref etwSession.m_activityFilter, this); + } + m_activityFilteringForETWEnabled = new SessionMask(0); + m_curLiveSessions = new SessionMask(0); + // reset activity-tracing-aware sessions + if (m_etwSessionIdMap != null) + for (int i = 0; i < SessionMask.MAX; ++i) + m_etwSessionIdMap[i] = null; + // reset legacy sessions + if (m_legacySessions != null) + m_legacySessions.Clear(); + } + else + { + ActivityFilter.DisableFilter(ref commandArgs.listener.m_activityFilter, this); + commandArgs.dispatcher.m_activityFilteringEnabled = false; + } +#endif // FEATURE_ACTIVITYSAMPLING + + // There is a good chance EnabledForAnyListener are not as accurate as + // they could be, go ahead and get a better estimate. + for (int i = 0; i < m_eventData.Length; i++) + { + bool isEnabledForAnyListener = false; + for (EventDispatcher dispatcher = m_Dispatchers; dispatcher != null; dispatcher = dispatcher.m_Next) + { + if (dispatcher.m_EventEnabled[i]) + { + isEnabledForAnyListener = true; + break; + } + } + m_eventData[i].EnabledForAnyListener = isEnabledForAnyListener; + } + + // If no events are enabled, disable the global enabled bit. + if (!AnyEventEnabled()) + { + m_level = 0; + m_matchAnyKeyword = 0; + m_eventSourceEnabled = false; + } + } +#if FEATURE_ACTIVITYSAMPLING + UpdateKwdTriggers(commandArgs.enable); +#endif // FEATURE_ACTIVITYSAMPLING + } + else + { + if (commandArgs.Command == EventCommand.SendManifest) + { + // TODO: should we generate the manifest here if we hadn't already? + if (m_rawManifest != null) + SendManifest(m_rawManifest); + } + + // These are not used for non-update commands and thus should always be 'default' values + // Debug.Assert(enable == true); + // Debug.Assert(level == EventLevel.LogAlways); + // Debug.Assert(matchAnyKeyword == EventKeywords.None); + + this.OnEventCommand(commandArgs); + var eventCommandCallback = m_eventCommandExecuted; + if (eventCommandCallback != null) + eventCommandCallback(this, commandArgs); + } + +#if FEATURE_ACTIVITYSAMPLING + if (m_completelyInited && (commandArgs.listener != null || shouldReport)) + { + SessionMask m = SessionMask.FromId(commandArgs.perEventSourceSessionId); + ReportActivitySamplingInfo(commandArgs.listener, m); + } +#endif // FEATURE_ACTIVITYSAMPLING + } + catch (Exception e) + { + // When the ETW session is created after the EventSource has registered with the ETW system + // we can send any error messages here. + ReportOutOfBandMessage("ERROR: Exception in Command Processing for EventSource " + Name + ": " + e.Message, true); + // We never throw when doing a command. + } + } + +#if FEATURE_ACTIVITYSAMPLING + + internal void UpdateEtwSession( + int sessionIdBit, + int etwSessionId, + bool bEnable, + string activityFilters, + bool participateInSampling) + { + if (sessionIdBit < SessionMask.MAX) + { + // activity-tracing-aware etw session + if (bEnable) + { + var etwSession = EtwSession.GetEtwSession(etwSessionId, true); + ActivityFilter.UpdateFilter(ref etwSession.m_activityFilter, this, sessionIdBit, activityFilters); + m_etwSessionIdMap[sessionIdBit] = etwSession; + m_activityFilteringForETWEnabled[sessionIdBit] = participateInSampling; + } + else + { + var etwSession = EtwSession.GetEtwSession(etwSessionId); + m_etwSessionIdMap[sessionIdBit] = null; + m_activityFilteringForETWEnabled[sessionIdBit] = false; + if (etwSession != null) + { + ActivityFilter.DisableFilter(ref etwSession.m_activityFilter, this); + // the ETW session is going away; remove it from the global list + EtwSession.RemoveEtwSession(etwSession); + } + } + m_curLiveSessions[sessionIdBit] = bEnable; + } + else + { + // legacy etw session + if (bEnable) + { + if (m_legacySessions == null) + m_legacySessions = new List(8); + var etwSession = EtwSession.GetEtwSession(etwSessionId, true); + if (!m_legacySessions.Contains(etwSession)) + m_legacySessions.Add(etwSession); + } + else + { + var etwSession = EtwSession.GetEtwSession(etwSessionId); + if (etwSession != null) + { + if (m_legacySessions != null) + m_legacySessions.Remove(etwSession); + // the ETW session is going away; remove it from the global list + EtwSession.RemoveEtwSession(etwSession); + } + } + } + } + + internal static bool ParseCommandArgs( + IDictionary commandArguments, + out bool participateInSampling, + out string activityFilters, + out int sessionIdBit) + { + bool res = true; + participateInSampling = false; + string activityFilterString; + if (commandArguments.TryGetValue("ActivitySamplingStartEvent", out activityFilters)) + { + // if a start event is specified default the event source to participate in sampling + participateInSampling = true; + } + + if (commandArguments.TryGetValue("ActivitySampling", out activityFilterString)) + { + if (string.Compare(activityFilterString, "false", StringComparison.OrdinalIgnoreCase) == 0 || + activityFilterString == "0") + participateInSampling = false; + else + participateInSampling = true; + } + + string sSessionKwd; + int sessionKwd = -1; + if (!commandArguments.TryGetValue("EtwSessionKeyword", out sSessionKwd) || + !int.TryParse(sSessionKwd, out sessionKwd) || + sessionKwd < SessionMask.SHIFT_SESSION_TO_KEYWORD || + sessionKwd >= SessionMask.SHIFT_SESSION_TO_KEYWORD + SessionMask.MAX) + { + sessionIdBit = -1; + res = false; + } + else + { + sessionIdBit = sessionKwd - SessionMask.SHIFT_SESSION_TO_KEYWORD; + } + return res; + } + + internal void UpdateKwdTriggers(bool enable) + { + if (enable) + { + // recompute m_keywordTriggers + ulong gKeywords = unchecked((ulong)m_matchAnyKeyword); + if (gKeywords == 0) + gKeywords = 0xFFFFffffFFFFffff; + + m_keywordTriggers = 0; + for (int sessId = 0; sessId < SessionMask.MAX; ++sessId) + { + EtwSession etwSession = m_etwSessionIdMap[sessId]; + if (etwSession == null) + continue; + + ActivityFilter activityFilter = etwSession.m_activityFilter; + ActivityFilter.UpdateKwdTriggers(activityFilter, m_guid, this, unchecked((EventKeywords)gKeywords)); + } + } + else + { + m_keywordTriggers = 0; + } + } + +#endif // FEATURE_ACTIVITYSAMPLING + + /// + /// If 'value is 'true' then set the eventSource so that 'dispatcher' will receive event with the eventId + /// of 'eventId. If value is 'false' disable the event for that dispatcher. If 'eventId' is out of + /// range return false, otherwise true. + /// + internal bool EnableEventForDispatcher(EventDispatcher dispatcher, int eventId, bool value) + { + if (dispatcher == null) + { + if (eventId >= m_eventData.Length) + return false; +#if FEATURE_MANAGED_ETW + if (m_provider != null) + m_eventData[eventId].EnabledForETW = value; +#endif + } + else + { + if (eventId >= dispatcher.m_EventEnabled.Length) + return false; + dispatcher.m_EventEnabled[eventId] = value; + if (value) + m_eventData[eventId].EnabledForAnyListener = true; + } + return true; + } + + /// + /// Returns true if any event at all is on. + /// + private bool AnyEventEnabled() + { + for (int i = 0; i < m_eventData.Length; i++) + if (m_eventData[i].EnabledForETW || m_eventData[i].EnabledForAnyListener) + return true; + return false; + } + + private bool IsDisposed + { + get { return m_eventSourceDisposed; } + } + + private void EnsureDescriptorsInitialized() + { +#if !ES_BUILD_STANDALONE + Debug.Assert(Monitor.IsEntered(EventListener.EventListenersLock)); +#endif + if (m_eventData == null) + { + Guid eventSourceGuid = Guid.Empty; + string eventSourceName = null; + EventMetadata[] eventData = null; + byte[] manifest = null; + + // Try the GetMetadata provided by the ILTransform in ProjectN. The default sets all to null, and in that case we fall back + // to the reflection approach. + GetMetadata(out eventSourceGuid, out eventSourceName, out eventData, out manifest); + + if (eventSourceGuid.Equals(Guid.Empty) || eventSourceName == null || eventData == null || manifest == null) + { + // GetMetadata failed, so we have to set it via reflection. + Debug.Assert(m_rawManifest == null); + m_rawManifest = CreateManifestAndDescriptors(this.GetType(), Name, this); + Debug.Assert(m_eventData != null); + + } + else + { + // GetMetadata worked, so set the fields as appropriate. + m_name = eventSourceName; + m_guid = eventSourceGuid; + m_eventData = eventData; + m_rawManifest = manifest; + } + // TODO Enforce singleton pattern + foreach (WeakReference eventSourceRef in EventListener.s_EventSources) + { + EventSource eventSource = eventSourceRef.Target as EventSource; + if (eventSource != null && eventSource.Guid == m_guid && !eventSource.IsDisposed) + { + if (eventSource != this) + { + throw new ArgumentException(Resources.GetResourceString("EventSource_EventSourceGuidInUse", m_guid)); + } + } + } + + // Make certain all dispatchers also have their arrays initialized + EventDispatcher dispatcher = m_Dispatchers; + while (dispatcher != null) + { + if (dispatcher.m_EventEnabled == null) + dispatcher.m_EventEnabled = new bool[m_eventData.Length]; + dispatcher = dispatcher.m_Next; + } + } + if (s_currentPid == 0) + { +#if ES_BUILD_STANDALONE && !ES_BUILD_PCL && !CORECLR + // for non-BCL EventSource we must assert SecurityPermission + new SecurityPermission(PermissionState.Unrestricted).Assert(); +#endif + s_currentPid = Win32Native.GetCurrentProcessId(); + } + } + + // Send out the ETW manifest XML out to ETW + // Today, we only send the manifest to ETW, custom listeners don't get it. + private unsafe bool SendManifest(byte[] rawManifest) + { + bool success = true; + + if (rawManifest == null) + return false; + + Debug.Assert(!SelfDescribingEvents); + +#if FEATURE_MANAGED_ETW + fixed (byte* dataPtr = rawManifest) + { + // we don't want the manifest to show up in the event log channels so we specify as keywords + // everything but the first 8 bits (reserved for the 8 channels) + var manifestDescr = new EventDescriptor(0xFFFE, 1, 0, 0, 0xFE, 0xFFFE, 0x00ffFFFFffffFFFF); + ManifestEnvelope envelope = new ManifestEnvelope(); + + envelope.Format = ManifestEnvelope.ManifestFormats.SimpleXmlFormat; + envelope.MajorVersion = 1; + envelope.MinorVersion = 0; + envelope.Magic = 0x5B; // An unusual number that can be checked for consistency. + int dataLeft = rawManifest.Length; + envelope.ChunkNumber = 0; + + EventProvider.EventData* dataDescrs = stackalloc EventProvider.EventData[2]; + + dataDescrs[0].Ptr = (ulong)&envelope; + dataDescrs[0].Size = (uint)sizeof(ManifestEnvelope); + dataDescrs[0].Reserved = 0; + + dataDescrs[1].Ptr = (ulong)dataPtr; + dataDescrs[1].Reserved = 0; + + int chunkSize = ManifestEnvelope.MaxChunkSize; + TRY_AGAIN_WITH_SMALLER_CHUNK_SIZE: + envelope.TotalChunks = (ushort)((dataLeft + (chunkSize - 1)) / chunkSize); + while (dataLeft > 0) + { + dataDescrs[1].Size = (uint)Math.Min(dataLeft, chunkSize); + if (m_provider != null) + { + if (!m_provider.WriteEvent(ref manifestDescr, null, null, 2, (IntPtr)dataDescrs)) + { + // Turns out that if users set the BufferSize to something less than 64K then WriteEvent + // can fail. If we get this failure on the first chunk try again with something smaller + // The smallest BufferSize is 1K so if we get to 256 (to account for envelope overhead), we can give up making it smaller. + if (EventProvider.GetLastWriteEventError() == EventProvider.WriteEventErrorCode.EventTooBig) + { + if (envelope.ChunkNumber == 0 && chunkSize > 256) + { + chunkSize = chunkSize / 2; + goto TRY_AGAIN_WITH_SMALLER_CHUNK_SIZE; + } + } + success = false; + if (ThrowOnEventWriteErrors) + ThrowEventSourceException("SendManifest"); + break; + } + } + dataLeft -= chunkSize; + dataDescrs[1].Ptr += (uint)chunkSize; + envelope.ChunkNumber++; + + // For large manifests we want to not overflow any receiver's buffer. Most manifests will fit within + // 5 chunks, so only the largest manifests will hit the pause. + if ((envelope.ChunkNumber % 5) == 0) + { + RuntimeThread.Sleep(15); + } + } + } +#endif // FEATURE_MANAGED_ETW + return success; + } + +#if (ES_BUILD_PCL) + internal static Attribute GetCustomAttributeHelper(Type type, Type attributeType, EventManifestOptions flags = EventManifestOptions.None) + { + return GetCustomAttributeHelper(type.GetTypeInfo(), attributeType, flags); + } +#endif + + // Helper to deal with the fact that the type we are reflecting over might be loaded in the ReflectionOnly context. + // When that is the case, we have the build the custom assemblies on a member by hand. + internal static Attribute GetCustomAttributeHelper(MemberInfo member, Type attributeType, EventManifestOptions flags = EventManifestOptions.None) + { + if (!member.Module.Assembly.ReflectionOnly() && (flags & EventManifestOptions.AllowEventSourceOverride) == 0) + { + // Let the runtime to the work for us, since we can execute code in this context. + Attribute firstAttribute = null; + foreach (var attribute in member.GetCustomAttributes(attributeType, false)) + { + firstAttribute = (Attribute)attribute; + break; + } + return firstAttribute; + } + +#if (!ES_BUILD_PCL && !ES_BUILD_PN) + // In the reflection only context, we have to do things by hand. + string fullTypeNameToFind = attributeType.FullName; + +#if EVENT_SOURCE_LEGACY_NAMESPACE_SUPPORT + fullTypeNameToFind = fullTypeNameToFind.Replace("System.Diagnostics.Eventing", "System.Diagnostics.Tracing"); +#endif + + foreach (CustomAttributeData data in CustomAttributeData.GetCustomAttributes(member)) + { + if (AttributeTypeNamesMatch(attributeType, data.Constructor.ReflectedType)) + { + Attribute attr = null; + + Debug.Assert(data.ConstructorArguments.Count <= 1); + + if (data.ConstructorArguments.Count == 1) + { + attr = (Attribute)Activator.CreateInstance(attributeType, new object[] { data.ConstructorArguments[0].Value }); + } + else if (data.ConstructorArguments.Count == 0) + { + attr = (Attribute)Activator.CreateInstance(attributeType); + } + + if (attr != null) + { + Type t = attr.GetType(); + + foreach (CustomAttributeNamedArgument namedArgument in data.NamedArguments) + { + PropertyInfo p = t.GetProperty(namedArgument.MemberInfo.Name, BindingFlags.Public | BindingFlags.Instance); + object value = namedArgument.TypedValue.Value; + + if (p.PropertyType.IsEnum) + { + value = Enum.Parse(p.PropertyType, value.ToString()); + } + + p.SetValue(attr, value, null); + } + + return attr; + } + } + } + + return null; +#else // ES_BUILD_PCL && ES_BUILD_PN + // Don't use nameof here because the resource doesn't exist on some platforms, which results in a compilation error. + throw new ArgumentException(Resources.GetResourceString("EventSource", "EventSource_PCLPlatformNotSupportedReflection")); +#endif + } + + /// + /// Evaluates if two related "EventSource"-domain types should be considered the same + /// + /// The attribute type in the load context - it's associated with the running + /// EventSource type. This type may be different fromt he base type of the user-defined EventSource. + /// The attribute type in the reflection context - it's associated with + /// the user-defined EventSource, and is in the same assembly as the eventSourceType passed to + /// + /// True - if the types should be considered equivalent, False - otherwise + private static bool AttributeTypeNamesMatch(Type attributeType, Type reflectedAttributeType) + { + return + // are these the same type? + attributeType == reflectedAttributeType || + // are the full typenames equal? + string.Equals(attributeType.FullName, reflectedAttributeType.FullName, StringComparison.Ordinal) || + // are the typenames equal and the namespaces under "Diagnostics.Tracing" (typically + // either Microsoft.Diagnostics.Tracing or System.Diagnostics.Tracing)? + string.Equals(attributeType.Name, reflectedAttributeType.Name, StringComparison.Ordinal) && + attributeType.Namespace.EndsWith("Diagnostics.Tracing", StringComparison.Ordinal) && + (reflectedAttributeType.Namespace.EndsWith("Diagnostics.Tracing", StringComparison.Ordinal) +#if EVENT_SOURCE_LEGACY_NAMESPACE_SUPPORT + || reflectedAttributeType.Namespace.EndsWith("Diagnostics.Eventing", StringComparison.Ordinal) +#endif +); + } + + private static Type GetEventSourceBaseType(Type eventSourceType, bool allowEventSourceOverride, bool reflectionOnly) + { + // return false for "object" and interfaces + if (eventSourceType.BaseType() == null) + return null; + + // now go up the inheritance chain until hitting a concrete type ("object" at worse) + do + { + eventSourceType = eventSourceType.BaseType(); + } + while (eventSourceType != null && eventSourceType.IsAbstract()); + + if (eventSourceType != null) + { + if (!allowEventSourceOverride) + { + if (reflectionOnly && eventSourceType.FullName != typeof(EventSource).FullName || + !reflectionOnly && eventSourceType != typeof(EventSource)) + return null; + } + else + { + if (eventSourceType.Name != "EventSource") + return null; + } + } + return eventSourceType; + } + + // Use reflection to look at the attributes of a class, and generate a manifest for it (as UTF8) and + // return the UTF8 bytes. It also sets up the code:EventData structures needed to dispatch events + // at run time. 'source' is the event source to place the descriptors. If it is null, + // then the descriptors are not creaed, and just the manifest is generated. + private static byte[] CreateManifestAndDescriptors(Type eventSourceType, string eventSourceDllName, EventSource source, + EventManifestOptions flags = EventManifestOptions.None) + { + ManifestBuilder manifest = null; + bool bNeedsManifest = source != null ? !source.SelfDescribingEvents : true; + Exception exception = null; // exception that might get raised during validation b/c we couldn't/didn't recover from a previous error + byte[] res = null; + + if (eventSourceType.IsAbstract() && (flags & EventManifestOptions.Strict) == 0) + return null; + +#if DEBUG && ES_BUILD_STANDALONE + TestSupport.TestHooks.MaybeThrow(eventSourceType, + TestSupport.Category.ManifestError, + "EventSource_CreateManifestAndDescriptors", + new ArgumentException("EventSource_CreateManifestAndDescriptors")); +#endif + + try + { + MethodInfo[] methods = eventSourceType.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); + EventAttribute defaultEventAttribute; + int eventId = 1; // The number given to an event that does not have a explicitly given ID. + EventMetadata[] eventData = null; + Dictionary eventsByName = null; + if (source != null || (flags & EventManifestOptions.Strict) != 0) + { + eventData = new EventMetadata[methods.Length + 1]; + eventData[0].Name = ""; // Event 0 is the 'write messages string' event, and has an empty name. + } + + // See if we have localization information. + ResourceManager resources = null; + EventSourceAttribute eventSourceAttrib = (EventSourceAttribute)GetCustomAttributeHelper(eventSourceType, typeof(EventSourceAttribute), flags); + if (eventSourceAttrib != null && eventSourceAttrib.LocalizationResources != null) + resources = new ResourceManager(eventSourceAttrib.LocalizationResources, eventSourceType.Assembly()); + + manifest = new ManifestBuilder(GetName(eventSourceType, flags), GetGuid(eventSourceType), eventSourceDllName, + resources, flags); + + // Add an entry unconditionally for event ID 0 which will be for a string message. + manifest.StartEvent("EventSourceMessage", new EventAttribute(0) { Level = EventLevel.LogAlways, Task = (EventTask)0xFFFE }); + manifest.AddEventParameter(typeof(string), "message"); + manifest.EndEvent(); + + // eventSourceType must be sealed and must derive from this EventSource + if ((flags & EventManifestOptions.Strict) != 0) + { + bool typeMatch = GetEventSourceBaseType(eventSourceType, (flags & EventManifestOptions.AllowEventSourceOverride) != 0, eventSourceType.Assembly().ReflectionOnly()) != null; + + if (!typeMatch) + { + manifest.ManifestError(Resources.GetResourceString("EventSource_TypeMustDeriveFromEventSource")); + } + if (!eventSourceType.IsAbstract() && !eventSourceType.IsSealed()) + { + manifest.ManifestError(Resources.GetResourceString("EventSource_TypeMustBeSealedOrAbstract")); + } + } + + // Collect task, opcode, keyword and channel information +#if FEATURE_MANAGED_ETW_CHANNELS && FEATURE_ADVANCED_MANAGED_ETW_CHANNELS + foreach (var providerEnumKind in new string[] { "Keywords", "Tasks", "Opcodes", "Channels" }) +#else + foreach (var providerEnumKind in new string[] { "Keywords", "Tasks", "Opcodes" }) +#endif + { + Type nestedType = eventSourceType.GetNestedType(providerEnumKind); + if (nestedType != null) + { + if (eventSourceType.IsAbstract()) + { + manifest.ManifestError(Resources.GetResourceString("EventSource_AbstractMustNotDeclareKTOC", nestedType.Name)); + } + else + { + foreach (FieldInfo staticField in nestedType.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)) + { + AddProviderEnumKind(manifest, staticField, providerEnumKind); + } + } + } + } + // ensure we have keywords for the session-filtering reserved bits + { + manifest.AddKeyword("Session3", (long)0x1000 << 32); + manifest.AddKeyword("Session2", (long)0x2000 << 32); + manifest.AddKeyword("Session1", (long)0x4000 << 32); + manifest.AddKeyword("Session0", (long)0x8000 << 32); + } + + if (eventSourceType != typeof(EventSource)) + { + for (int i = 0; i < methods.Length; i++) + { + MethodInfo method = methods[i]; + ParameterInfo[] args = method.GetParameters(); + + // Get the EventDescriptor (from the Custom attributes) + EventAttribute eventAttribute = (EventAttribute)GetCustomAttributeHelper(method, typeof(EventAttribute), flags); + + // Compat: until v4.5.1 we ignored any non-void returning methods as well as virtual methods for + // the only reason of limiting the number of methods considered to be events. This broke a common + // design of having event sources implement specific interfaces. To fix this in a compatible way + // we will now allow both non-void returning and virtual methods to be Event methods, as long + // as they are marked with the [Event] attribute + if (/* method.IsVirtual || */ method.IsStatic) + { + continue; + } + + if (eventSourceType.IsAbstract()) + { + if (eventAttribute != null) + { + manifest.ManifestError(Resources.GetResourceString("EventSource_AbstractMustNotDeclareEventMethods", method.Name, eventAttribute.EventId)); + } + continue; + } + else if (eventAttribute == null) + { + // Methods that don't return void can't be events, if they're NOT marked with [Event]. + // (see Compat comment above) + if (method.ReturnType != typeof(void)) + { + continue; + } + + // Continue to ignore virtual methods if they do NOT have the [Event] attribute + // (see Compat comment above) + if (method.IsVirtual) + { + continue; + } + + // If we explicitly mark the method as not being an event, then honor that. + if (GetCustomAttributeHelper(method, typeof(NonEventAttribute), flags) != null) + continue; + + defaultEventAttribute = new EventAttribute(eventId); + eventAttribute = defaultEventAttribute; + } + else if (eventAttribute.EventId <= 0) + { + manifest.ManifestError(Resources.GetResourceString("EventSource_NeedPositiveId", method.Name), true); + continue; // don't validate anything else for this event + } + if (method.Name.LastIndexOf('.') >= 0) + { + manifest.ManifestError(Resources.GetResourceString("EventSource_EventMustNotBeExplicitImplementation", method.Name, eventAttribute.EventId)); + } + + eventId++; + string eventName = method.Name; + + if (eventAttribute.Opcode == EventOpcode.Info) // We are still using the default opcode. + { + // By default pick a task ID derived from the EventID, starting with the highest task number and working back + bool noTask = (eventAttribute.Task == EventTask.None); + if (noTask) + eventAttribute.Task = (EventTask)(0xFFFE - eventAttribute.EventId); + + // Unless we explicitly set the opcode to Info (to override the auto-generate of Start or Stop opcodes, + // pick a default opcode based on the event name (either Info or start or stop if the name ends with that suffix). + if (!eventAttribute.IsOpcodeSet) + eventAttribute.Opcode = GetOpcodeWithDefault(EventOpcode.Info, eventName); + + // Make the stop opcode have the same task as the start opcode. + if (noTask) + { + if (eventAttribute.Opcode == EventOpcode.Start) + { + string taskName = eventName.Substring(0, eventName.Length - s_ActivityStartSuffix.Length); // Remove the Stop suffix to get the task name + if (string.Compare(eventName, 0, taskName, 0, taskName.Length) == 0 && + string.Compare(eventName, taskName.Length, s_ActivityStartSuffix, 0, Math.Max(eventName.Length - taskName.Length, s_ActivityStartSuffix.Length)) == 0) + { + // Add a task that is just the task name for the start event. This suppress the auto-task generation + // That would otherwise happen (and create 'TaskName'Start as task name rather than just 'TaskName' + manifest.AddTask(taskName, (int)eventAttribute.Task); + } + } + else if (eventAttribute.Opcode == EventOpcode.Stop) + { + // Find the start associated with this stop event. We require start to be immediately before the stop + int startEventId = eventAttribute.EventId - 1; + if (eventData != null && startEventId < eventData.Length) + { + Debug.Assert(0 <= startEventId); // Since we reserve id 0, we know that id-1 is <= 0 + EventMetadata startEventMetadata = eventData[startEventId]; + + // If you remove the Stop and add a Start does that name match the Start Event's Name? + // Ideally we would throw an error + string taskName = eventName.Substring(0, eventName.Length - s_ActivityStopSuffix.Length); // Remove the Stop suffix to get the task name + if (startEventMetadata.Descriptor.Opcode == (byte)EventOpcode.Start && + string.Compare(startEventMetadata.Name, 0, taskName, 0, taskName.Length) == 0 && + string.Compare(startEventMetadata.Name, taskName.Length, s_ActivityStartSuffix, 0, Math.Max(startEventMetadata.Name.Length - taskName.Length, s_ActivityStartSuffix.Length)) == 0) + { + + // Make the stop event match the start event + eventAttribute.Task = (EventTask)startEventMetadata.Descriptor.Task; + noTask = false; + } + } + if (noTask && (flags & EventManifestOptions.Strict) != 0) // Throw an error if we can compatibly. + { + throw new ArgumentException(Resources.GetResourceString("EventSource_StopsFollowStarts")); + } + } + } + } + + bool hasRelatedActivityID = RemoveFirstArgIfRelatedActivityId(ref args); + if (!(source != null && source.SelfDescribingEvents)) + { + manifest.StartEvent(eventName, eventAttribute); + for (int fieldIdx = 0; fieldIdx < args.Length; fieldIdx++) + { + manifest.AddEventParameter(args[fieldIdx].ParameterType, args[fieldIdx].Name); + } + manifest.EndEvent(); + } + + if (source != null || (flags & EventManifestOptions.Strict) != 0) + { + // Do checking for user errors (optional, but not a big deal so we do it). + DebugCheckEvent(ref eventsByName, eventData, method, eventAttribute, manifest, flags); + +#if FEATURE_MANAGED_ETW_CHANNELS + // add the channel keyword for Event Viewer channel based filters. This is added for creating the EventDescriptors only + // and is not required for the manifest + if (eventAttribute.Channel != EventChannel.None) + { + unchecked + { + eventAttribute.Keywords |= (EventKeywords)manifest.GetChannelKeyword(eventAttribute.Channel, (ulong)eventAttribute.Keywords); + } + } +#endif + string eventKey = "event_" + eventName; + string msg = manifest.GetLocalizedMessage(eventKey, CultureInfo.CurrentUICulture, etwFormat: false); + // overwrite inline message with the localized message + if (msg != null) eventAttribute.Message = msg; + + AddEventDescriptor(ref eventData, eventName, eventAttribute, args, hasRelatedActivityID); + } + } + } + + // Tell the TraceLogging stuff where to start allocating its own IDs. + NameInfo.ReserveEventIDsBelow(eventId); + + if (source != null) + { + TrimEventDescriptors(ref eventData); + source.m_eventData = eventData; // officially initialize it. We do this at most once (it is racy otherwise). +#if FEATURE_MANAGED_ETW_CHANNELS + source.m_channelData = manifest.GetChannelData(); +#endif + } + + // if this is an abstract event source we've already performed all the validation we can + if (!eventSourceType.IsAbstract() && (source == null || !source.SelfDescribingEvents)) + { + bNeedsManifest = (flags & EventManifestOptions.OnlyIfNeededForRegistration) == 0 +#if FEATURE_MANAGED_ETW_CHANNELS + || manifest.GetChannelData().Length > 0 +#endif +; + + // if the manifest is not needed and we're not requested to validate the event source return early + if (!bNeedsManifest && (flags & EventManifestOptions.Strict) == 0) + return null; + + res = manifest.CreateManifest(); + } + } + catch (Exception e) + { + // if this is a runtime manifest generation let the exception propagate + if ((flags & EventManifestOptions.Strict) == 0) + throw; + // else store it to include it in the Argument exception we raise below + exception = e; + } + + if ((flags & EventManifestOptions.Strict) != 0 && (manifest.Errors.Count > 0 || exception != null)) + { + string msg = String.Empty; + if (manifest.Errors.Count > 0) + { + bool firstError = true; + foreach (string error in manifest.Errors) + { + if (!firstError) + msg += Environment.NewLine; + firstError = false; + msg += error; + } + } + else + msg = "Unexpected error: " + exception.Message; + + throw new ArgumentException(msg, exception); + } + + return bNeedsManifest ? res : null; + } + + private static bool RemoveFirstArgIfRelatedActivityId(ref ParameterInfo[] args) + { + // If the first parameter is (case insensitive) 'relatedActivityId' then skip it. + if (args.Length > 0 && args[0].ParameterType == typeof(Guid) && + string.Compare(args[0].Name, "relatedActivityId", StringComparison.OrdinalIgnoreCase) == 0) + { + var newargs = new ParameterInfo[args.Length - 1]; + Array.Copy(args, 1, newargs, 0, args.Length - 1); + args = newargs; + + return true; + } + + return false; + } + + // adds a enumeration (keyword, opcode, task or channel) represented by 'staticField' + // to the manifest. + private static void AddProviderEnumKind(ManifestBuilder manifest, FieldInfo staticField, string providerEnumKind) + { + bool reflectionOnly = staticField.Module.Assembly.ReflectionOnly(); + Type staticFieldType = staticField.FieldType; + if (!reflectionOnly && (staticFieldType == typeof(EventOpcode)) || AttributeTypeNamesMatch(staticFieldType, typeof(EventOpcode))) + { + if (providerEnumKind != "Opcodes") goto Error; + int value = (int)staticField.GetRawConstantValue(); + manifest.AddOpcode(staticField.Name, value); + } + else if (!reflectionOnly && (staticFieldType == typeof(EventTask)) || AttributeTypeNamesMatch(staticFieldType, typeof(EventTask))) + { + if (providerEnumKind != "Tasks") goto Error; + int value = (int)staticField.GetRawConstantValue(); + manifest.AddTask(staticField.Name, value); + } + else if (!reflectionOnly && (staticFieldType == typeof(EventKeywords)) || AttributeTypeNamesMatch(staticFieldType, typeof(EventKeywords))) + { + if (providerEnumKind != "Keywords") goto Error; + ulong value = unchecked((ulong)(long)staticField.GetRawConstantValue()); + manifest.AddKeyword(staticField.Name, value); + } +#if FEATURE_MANAGED_ETW_CHANNELS && FEATURE_ADVANCED_MANAGED_ETW_CHANNELS + else if (!reflectionOnly && (staticFieldType == typeof(EventChannel)) || AttributeTypeNamesMatch(staticFieldType, typeof(EventChannel))) + { + if (providerEnumKind != "Channels") goto Error; + var channelAttribute = (EventChannelAttribute)GetCustomAttributeHelper(staticField, typeof(EventChannelAttribute)); + manifest.AddChannel(staticField.Name, (byte)staticField.GetRawConstantValue(), channelAttribute); + } +#endif + return; + Error: + manifest.ManifestError(Resources.GetResourceString("EventSource_EnumKindMismatch", staticField.Name, staticField.FieldType.Name, providerEnumKind)); + } + + // Helper used by code:CreateManifestAndDescriptors to add a code:EventData descriptor for a method + // with the code:EventAttribute 'eventAttribute'. resourceManger may be null in which case we populate it + // it is populated if we need to look up message resources + private static void AddEventDescriptor(ref EventMetadata[] eventData, string eventName, + EventAttribute eventAttribute, ParameterInfo[] eventParameters, + bool hasRelatedActivityID) + { + if (eventData == null || eventData.Length <= eventAttribute.EventId) + { + EventMetadata[] newValues = new EventMetadata[Math.Max(eventData.Length + 16, eventAttribute.EventId + 1)]; + Array.Copy(eventData, 0, newValues, 0, eventData.Length); + eventData = newValues; + } + + eventData[eventAttribute.EventId].Descriptor = new EventDescriptor( + eventAttribute.EventId, + eventAttribute.Version, +#if FEATURE_MANAGED_ETW_CHANNELS + (byte)eventAttribute.Channel, +#else + (byte)0, +#endif + (byte)eventAttribute.Level, + (byte)eventAttribute.Opcode, + (int)eventAttribute.Task, + unchecked((long)((ulong)eventAttribute.Keywords | SessionMask.All.ToEventKeywords()))); + + eventData[eventAttribute.EventId].Tags = eventAttribute.Tags; + eventData[eventAttribute.EventId].Name = eventName; + eventData[eventAttribute.EventId].Parameters = eventParameters; + eventData[eventAttribute.EventId].Message = eventAttribute.Message; + eventData[eventAttribute.EventId].ActivityOptions = eventAttribute.ActivityOptions; + eventData[eventAttribute.EventId].HasRelatedActivityID = hasRelatedActivityID; + } + + // Helper used by code:CreateManifestAndDescriptors that trims the m_eventData array to the correct + // size after all event descriptors have been added. + private static void TrimEventDescriptors(ref EventMetadata[] eventData) + { + int idx = eventData.Length; + while (0 < idx) + { + --idx; + if (eventData[idx].Descriptor.EventId != 0) + break; + } + if (eventData.Length - idx > 2) // allow one wasted slot. + { + EventMetadata[] newValues = new EventMetadata[idx + 1]; + Array.Copy(eventData, 0, newValues, 0, newValues.Length); + eventData = newValues; + } + } + + // Helper used by code:EventListener.AddEventSource and code:EventListener.EventListener + // when a listener gets attached to a eventSource + internal void AddListener(EventListener listener) + { + lock (EventListener.EventListenersLock) + { + bool[] enabledArray = null; + if (m_eventData != null) + enabledArray = new bool[m_eventData.Length]; + m_Dispatchers = new EventDispatcher(m_Dispatchers, enabledArray, listener); + listener.OnEventSourceCreated(this); + } + } + + // Helper used by code:CreateManifestAndDescriptors to find user mistakes like reusing an event + // index for two distinct events etc. Throws exceptions when it finds something wrong. + private static void DebugCheckEvent(ref Dictionary eventsByName, + EventMetadata[] eventData, MethodInfo method, EventAttribute eventAttribute, + ManifestBuilder manifest, EventManifestOptions options) + { + int evtId = eventAttribute.EventId; + string evtName = method.Name; + int eventArg = GetHelperCallFirstArg(method); + if (eventArg >= 0 && evtId != eventArg) + { + manifest.ManifestError(Resources.GetResourceString("EventSource_MismatchIdToWriteEvent", evtName, evtId, eventArg), true); + } + + if (evtId < eventData.Length && eventData[evtId].Descriptor.EventId != 0) + { + manifest.ManifestError(Resources.GetResourceString("EventSource_EventIdReused", evtName, evtId, eventData[evtId].Name), true); + } + + // We give a task to things if they don't have one. + // TODO this is moderately expensive (N*N). We probably should not even bother.... + Debug.Assert(eventAttribute.Task != EventTask.None || eventAttribute.Opcode != EventOpcode.Info); + for (int idx = 0; idx < eventData.Length; ++idx) + { + // skip unused Event IDs. + if (eventData[idx].Name == null) + continue; + + if (eventData[idx].Descriptor.Task == (int)eventAttribute.Task && eventData[idx].Descriptor.Opcode == (int)eventAttribute.Opcode) + { + manifest.ManifestError(Resources.GetResourceString("EventSource_TaskOpcodePairReused", + evtName, evtId, eventData[idx].Name, idx)); + // If we are not strict stop on first error. We have had problems with really large providers taking forever. because of many errors. + if ((options & EventManifestOptions.Strict) == 0) + break; + } + } + + // for non-default event opcodes the user must define a task! + if (eventAttribute.Opcode != EventOpcode.Info) + { + bool failure = false; + if (eventAttribute.Task == EventTask.None) + failure = true; + else + { + // If you have the auto-assigned Task, then you did not explicitly set one. + // This is OK for Start events because we have special logic to assign the task to a prefix derived from the event name + // But all other cases we want to catch the omission. + var autoAssignedTask = (EventTask)(0xFFFE - evtId); + if ((eventAttribute.Opcode != EventOpcode.Start && eventAttribute.Opcode != EventOpcode.Stop) && eventAttribute.Task == autoAssignedTask) + failure = true; + } + if (failure) + { + manifest.ManifestError(Resources.GetResourceString("EventSource_EventMustHaveTaskIfNonDefaultOpcode", evtName, evtId)); + } + } + + // If we ever want to enforce the rule: MethodName = TaskName + OpcodeName here's how: + // (the reason we don't is backwards compat and the need for handling this as a non-fatal error + // by eventRegister.exe) + // taskName & opcodeName could be passed in by the caller which has opTab & taskTab handy + // if (!(((int)eventAttribute.Opcode == 0 && evtName == taskName) || (evtName == taskName+opcodeName))) + // { + // throw new WarningException(Resources.GetResourceString("EventSource_EventNameDoesNotEqualTaskPlusOpcode")); + // } + + if (eventsByName == null) + eventsByName = new Dictionary(); + + if (eventsByName.ContainsKey(evtName)) + { + manifest.ManifestError(Resources.GetResourceString("EventSource_EventNameReused", evtName), true); + } + + eventsByName[evtName] = evtName; + } + + /// + /// This method looks at the IL and tries to pattern match against the standard + /// 'boilerplate' event body + /// + /// { if (Enabled()) WriteEvent(#, ...) } + /// + /// If the pattern matches, it returns the literal number passed as the first parameter to + /// the WriteEvent. This is used to find common user errors (mismatching this + /// number with the EventAttribute ID). It is only used for validation. + /// + /// The method to probe. + /// The literal value or -1 if the value could not be determined. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Switch statement is clearer than alternatives")] + static private int GetHelperCallFirstArg(MethodInfo method) + { +#if (!ES_BUILD_PCL && !ES_BUILD_PN) + // Currently searches for the following pattern + // + // ... // CAN ONLY BE THE INSTRUCTIONS BELOW + // LDARG0 + // LDC.I4 XXX + // ... // CAN ONLY BE THE INSTRUCTIONS BELOW CAN'T BE A BRANCH OR A CALL + // CALL + // NOP // 0 or more times + // RET + // + // If we find this pattern we return the XXX. Otherwise we return -1. +#if !CORECLR + (new ReflectionPermission(ReflectionPermissionFlag.MemberAccess)).Assert(); +#endif + byte[] instrs = method.GetMethodBody().GetILAsByteArray(); + int retVal = -1; + for (int idx = 0; idx < instrs.Length;) + { + switch (instrs[idx]) + { + case 0: // NOP + case 1: // BREAK + case 2: // LDARG_0 + case 3: // LDARG_1 + case 4: // LDARG_2 + case 5: // LDARG_3 + case 6: // LDLOC_0 + case 7: // LDLOC_1 + case 8: // LDLOC_2 + case 9: // LDLOC_3 + case 10: // STLOC_0 + case 11: // STLOC_1 + case 12: // STLOC_2 + case 13: // STLOC_3 + break; + case 14: // LDARG_S + case 16: // STARG_S + idx++; + break; + case 20: // LDNULL + break; + case 21: // LDC_I4_M1 + case 22: // LDC_I4_0 + case 23: // LDC_I4_1 + case 24: // LDC_I4_2 + case 25: // LDC_I4_3 + case 26: // LDC_I4_4 + case 27: // LDC_I4_5 + case 28: // LDC_I4_6 + case 29: // LDC_I4_7 + case 30: // LDC_I4_8 + if (idx > 0 && instrs[idx - 1] == 2) // preceeded by LDARG0 + retVal = instrs[idx] - 22; + break; + case 31: // LDC_I4_S + if (idx > 0 && instrs[idx - 1] == 2) // preceeded by LDARG0 + retVal = instrs[idx + 1]; + idx++; + break; + case 32: // LDC_I4 + idx += 4; + break; + case 37: // DUP + break; + case 40: // CALL + idx += 4; + + if (retVal >= 0) + { + // Is this call just before return? + for (int search = idx + 1; search < instrs.Length; search++) + { + if (instrs[search] == 42) // RET + return retVal; + if (instrs[search] != 0) // NOP + break; + } + } + retVal = -1; + break; + case 44: // BRFALSE_S + case 45: // BRTRUE_S + retVal = -1; + idx++; + break; + case 57: // BRFALSE + case 58: // BRTRUE + retVal = -1; + idx += 4; + break; + case 103: // CONV_I1 + case 104: // CONV_I2 + case 105: // CONV_I4 + case 106: // CONV_I8 + case 109: // CONV_U4 + case 110: // CONV_U8 + break; + case 140: // BOX + case 141: // NEWARR + idx += 4; + break; + case 162: // STELEM_REF + break; + case 254: // PREFIX + idx++; + // Covers the CEQ instructions used in debug code for some reason. + if (idx >= instrs.Length || instrs[idx] >= 6) + goto default; + break; + default: + /* Debug.Assert(false, "Warning: User validation code sub-optimial: Unsuported opcode " + instrs[idx] + + " at " + idx + " in method " + method.Name); */ + return -1; + } + idx++; + } +#endif + return -1; + } + +#if false // This routine is not needed at all, it was used for unit test debugging. + [Conditional("DEBUG")] + private static void OutputDebugString(string msg) + { +#if !ES_BUILD_PCL + msg = msg.TrimEnd('\r', '\n') + + string.Format(CultureInfo.InvariantCulture, ", Thrd({0})" + Environment.NewLine, Thread.CurrentThread.ManagedThreadId); + System.Diagnostics.Debugger.Log(0, null, msg); +#endif + } +#endif + + /// + /// Sends an error message to the debugger (outputDebugString), as well as the EventListeners + /// It will do this even if the EventSource is not enabled. + /// TODO remove flush parameter it is not used. + /// + [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + internal void ReportOutOfBandMessage(string msg, bool flush) + { + try + { +#if (!ES_BUILD_PCL && !ES_BUILD_PN) + // send message to debugger without delay + System.Diagnostics.Debugger.Log(0, null, String.Format("EventSource Error: {0}{1}", msg, Environment.NewLine)); +#endif + + // Send it to all listeners. + if (m_outOfBandMessageCount < 16 - 1) // Note this is only if size byte + m_outOfBandMessageCount++; + else + { + if (m_outOfBandMessageCount == 16) + return; + m_outOfBandMessageCount = 16; // Mark that we hit the limit. Notify them that this is the case. + msg = "Reached message limit. End of EventSource error messages."; + } + + WriteEventString(EventLevel.LogAlways, -1, msg); + WriteStringToAllListeners("EventSourceMessage", msg); + } + catch (Exception) { } // If we fail during last chance logging, well, we have to give up.... + } + + private EventSourceSettings ValidateSettings(EventSourceSettings settings) + { + var evtFormatMask = EventSourceSettings.EtwManifestEventFormat | + EventSourceSettings.EtwSelfDescribingEventFormat; + if ((settings & evtFormatMask) == evtFormatMask) + { + throw new ArgumentException(Resources.GetResourceString("EventSource_InvalidEventFormat"), nameof(settings)); + } + + // If you did not explicitly ask for manifest, you get self-describing. + if ((settings & evtFormatMask) == 0) + settings |= EventSourceSettings.EtwSelfDescribingEventFormat; + return settings; + } + + private bool ThrowOnEventWriteErrors + { + get { return (m_config & EventSourceSettings.ThrowOnEventWriteErrors) != 0; } + set + { + if (value) m_config |= EventSourceSettings.ThrowOnEventWriteErrors; + else m_config &= ~EventSourceSettings.ThrowOnEventWriteErrors; + } + } + + private bool SelfDescribingEvents + { + get + { + Debug.Assert(((m_config & EventSourceSettings.EtwManifestEventFormat) != 0) != + ((m_config & EventSourceSettings.EtwSelfDescribingEventFormat) != 0)); + return (m_config & EventSourceSettings.EtwSelfDescribingEventFormat) != 0; + } + set + { + if (!value) + { + m_config |= EventSourceSettings.EtwManifestEventFormat; + m_config &= ~EventSourceSettings.EtwSelfDescribingEventFormat; + } + else + { + m_config |= EventSourceSettings.EtwSelfDescribingEventFormat; + m_config &= ~EventSourceSettings.EtwManifestEventFormat; + } + } + } + +#if FEATURE_ACTIVITYSAMPLING + private void ReportActivitySamplingInfo(EventListener listener, SessionMask sessions) + { + Debug.Assert(listener == null || (uint)sessions == (uint)SessionMask.FromId(0)); + + for (int perEventSourceSessionId = 0; perEventSourceSessionId < SessionMask.MAX; ++perEventSourceSessionId) + { + if (!sessions[perEventSourceSessionId]) + continue; + + ActivityFilter af; + if (listener == null) + { + EtwSession etwSession = m_etwSessionIdMap[perEventSourceSessionId]; + Debug.Assert(etwSession != null); + af = etwSession.m_activityFilter; + } + else + { + af = listener.m_activityFilter; + } + + if (af == null) + continue; + + SessionMask m = new SessionMask(); + m[perEventSourceSessionId] = true; + + foreach (var t in af.GetFilterAsTuple(m_guid)) + { + WriteStringToListener(listener, string.Format(CultureInfo.InvariantCulture, "Session {0}: {1} = {2}", perEventSourceSessionId, t.Item1, t.Item2), m); + } + + bool participateInSampling = (listener == null) ? + m_activityFilteringForETWEnabled[perEventSourceSessionId] : + GetDispatcher(listener).m_activityFilteringEnabled; + WriteStringToListener(listener, string.Format(CultureInfo.InvariantCulture, "Session {0}: Activity Sampling support: {1}", + perEventSourceSessionId, participateInSampling ? "enabled" : "disabled"), m); + } + } +#endif // FEATURE_ACTIVITYSAMPLING + + // private instance state + private string m_name; // My friendly name (privided in ctor) + internal int m_id; // A small integer that is unique to this instance. + private Guid m_guid; // GUID representing the ETW eventSource to the OS. + internal volatile EventMetadata[] m_eventData; // None per-event data + private volatile byte[] m_rawManifest; // Bytes to send out representing the event schema + + private EventHandler m_eventCommandExecuted; + + private EventSourceSettings m_config; // configuration information + + private bool m_eventSourceDisposed; // has Dispose been called. + + // Enabling bits + private bool m_eventSourceEnabled; // am I enabled (any of my events are enabled for any dispatcher) + internal EventLevel m_level; // highest level enabled by any output dispatcher + internal EventKeywords m_matchAnyKeyword; // the logical OR of all levels enabled by any output dispatcher (zero is a special case) meaning 'all keywords' + + // Dispatching state + internal volatile EventDispatcher m_Dispatchers; // Linked list of code:EventDispatchers we write the data to (we also do ETW specially) +#if FEATURE_MANAGED_ETW + private volatile OverideEventProvider m_provider; // This hooks up ETW commands to our 'OnEventCommand' callback +#endif + private bool m_completelyInited; // The EventSource constructor has returned without exception. + private Exception m_constructionException; // If there was an exception construction, this is it + private byte m_outOfBandMessageCount; // The number of out of band messages sent (we throttle them + private EventCommandEventArgs m_deferredCommands;// If we get commands before we are fully we store them here and run the when we are fully inited. + + private string[] m_traits; // Used to implement GetTraits + + internal static uint s_currentPid; // current process id, used in synthesizing quasi-GUIDs + [ThreadStatic] + private static byte m_EventSourceExceptionRecurenceCount = 0; // current recursion count inside ThrowEventSourceException + + [ThreadStatic] + private static bool m_EventSourceInDecodeObject = false; + +#if FEATURE_MANAGED_ETW_CHANNELS + internal volatile ulong[] m_channelData; +#endif + +#if FEATURE_ACTIVITYSAMPLING + private SessionMask m_curLiveSessions; // the activity-tracing aware sessions' bits + private EtwSession[] m_etwSessionIdMap; // the activity-tracing aware sessions + private List m_legacySessions; // the legacy ETW sessions listening to this source + internal long m_keywordTriggers; // a bit is set if it corresponds to a keyword that's part of an enabled triggering event + internal SessionMask m_activityFilteringForETWEnabled; // does THIS EventSource have activity filtering turned on for each ETW session + static internal Action s_activityDying; // Fires when something calls SetCurrentThreadToActivity() + // Also used to mark that activity tracing is on for some case +#endif // FEATURE_ACTIVITYSAMPLING + + // We use a single instance of ActivityTracker for all EventSources instances to allow correlation between multiple event providers. + // We have m_activityTracker field simply because instance field is more efficient than static field fetch. + ActivityTracker m_activityTracker; + internal const string s_ActivityStartSuffix = "Start"; + internal const string s_ActivityStopSuffix = "Stop"; + + // used for generating GUID from eventsource name + private static readonly byte[] namespaceBytes = new byte[] { + 0x48, 0x2C, 0x2D, 0xB2, 0xC3, 0x90, 0x47, 0xC8, + 0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB, + }; + + #endregion + } + + /// + /// Enables specifying event source configuration options to be used in the EventSource constructor. + /// + [Flags] + public enum EventSourceSettings + { + /// + /// This specifies none of the special configuration options should be enabled. + /// + Default = 0, + /// + /// Normally an EventSource NEVER throws; setting this option will tell it to throw when it encounters errors. + /// + ThrowOnEventWriteErrors = 1, + /// + /// Setting this option is a directive to the ETW listener should use manifest-based format when + /// firing events. This is the default option when defining a type derived from EventSource + /// (using the protected EventSource constructors). + /// Only one of EtwManifestEventFormat or EtwSelfDescribingEventFormat should be specified + /// + EtwManifestEventFormat = 4, + /// + /// Setting this option is a directive to the ETW listener should use self-describing event format + /// when firing events. This is the default option when creating a new instance of the EventSource + /// type (using the public EventSource constructors). + /// Only one of EtwManifestEventFormat or EtwSelfDescribingEventFormat should be specified + /// + EtwSelfDescribingEventFormat = 8, + } + + /// + /// An EventListener represents a target for the events generated by EventSources (that is subclasses + /// of ), in the current appdomain. When a new EventListener is created + /// it is logically attached to all eventSources in that appdomain. When the EventListener is Disposed, then + /// it is disconnected from the event eventSources. Note that there is a internal list of STRONG references + /// to EventListeners, which means that relying on the lack of references to EventListeners to clean up + /// EventListeners will NOT work. You must call EventListener.Dispose explicitly when a dispatcher is no + /// longer needed. + /// + /// Once created, EventListeners can enable or disable on a per-eventSource basis using verbosity levels + /// () and bitfields () to further restrict the set of + /// events to be sent to the dispatcher. The dispatcher can also send arbitrary commands to a particular + /// eventSource using the 'SendCommand' method. The meaning of the commands are eventSource specific. + /// + /// The Null Guid (that is (new Guid()) has special meaning as a wildcard for 'all current eventSources in + /// the appdomain'. Thus it is relatively easy to turn on all events in the appdomain if desired. + /// + /// It is possible for there to be many EventListener's defined in a single appdomain. Each dispatcher is + /// logically independent of the other listeners. Thus when one dispatcher enables or disables events, it + /// affects only that dispatcher (other listeners get the events they asked for). It is possible that + /// commands sent with 'SendCommand' would do a semantic operation that would affect the other listeners + /// (like doing a GC, or flushing data ...), but this is the exception rather than the rule. + /// + /// Thus the model is that each EventSource keeps a list of EventListeners that it is sending events + /// to. Associated with each EventSource-dispatcher pair is a set of filtering criteria that determine for + /// that eventSource what events that dispatcher will receive. + /// + /// Listeners receive the events on their 'OnEventWritten' method. Thus subclasses of EventListener must + /// override this method to do something useful with the data. + /// + /// In addition, when new eventSources are created, the 'OnEventSourceCreate' method is called. The + /// invariant associated with this callback is that every eventSource gets exactly one + /// 'OnEventSourceCreate' call for ever eventSource that can potentially send it log messages. In + /// particular when a EventListener is created, typically a series of OnEventSourceCreate' calls are + /// made to notify the new dispatcher of all the eventSources that existed before the EventListener was + /// created. + /// + /// + public class EventListener : IDisposable + { + private event EventHandler _EventSourceCreated; + + /// + /// This event is raised whenever a new eventSource is 'attached' to the dispatcher. + /// This can happen for all existing EventSources when the EventListener is created + /// as well as for any EventSources that come into existence after the EventListener + /// has been created. + /// + /// These 'catch up' events are called during the construction of the EventListener. + /// Subclasses need to be prepared for that. + /// + /// In a multi-threaded environment, it is possible that 'EventSourceEventWrittenCallback' + /// events for a particular eventSource to occur BEFORE the EventSourceCreatedCallback is issued. + /// + public event EventHandler EventSourceCreated + { + add + { + CallBackForExistingEventSources(false, value); + + this._EventSourceCreated = (EventHandler)Delegate.Combine(_EventSourceCreated, value); + } + remove + { + this._EventSourceCreated = (EventHandler)Delegate.Remove(_EventSourceCreated, value); + } + } + + /// + /// This event is raised whenever an event has been written by a EventSource for which + /// the EventListener has enabled events. + /// + public event EventHandler EventWritten; + + /// + /// Create a new EventListener in which all events start off turned off (use EnableEvents to turn + /// them on). + /// + public EventListener() + { + // This will cause the OnEventSourceCreated callback to fire. + CallBackForExistingEventSources(true, (obj, args) => args.EventSource.AddListener((EventListener)obj)); + } + + /// + /// Dispose should be called when the EventListener no longer desires 'OnEvent*' callbacks. Because + /// there is an internal list of strong references to all EventListeners, calling 'Dispose' directly + /// is the only way to actually make the listen die. Thus it is important that users of EventListener + /// call Dispose when they are done with their logging. + /// +#if ES_BUILD_STANDALONE + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")] +#endif + public virtual void Dispose() + { + lock (EventListenersLock) + { + if (s_Listeners != null) + { + if (this == s_Listeners) + { + EventListener cur = s_Listeners; + s_Listeners = this.m_Next; + RemoveReferencesToListenerInEventSources(cur); + } + else + { + // Find 'this' from the s_Listeners linked list. + EventListener prev = s_Listeners; + for (;;) + { + EventListener cur = prev.m_Next; + if (cur == null) + break; + if (cur == this) + { + // Found our Listener, remove references to to it in the eventSources + prev.m_Next = cur.m_Next; // Remove entry. + RemoveReferencesToListenerInEventSources(cur); + break; + } + prev = cur; + } + } + } + Validate(); + } + } + // We don't expose a Dispose(bool), because the contract is that you don't have any non-syncronous + // 'cleanup' associated with this object + + /// + /// Enable all events from the eventSource identified by 'eventSource' to the current + /// dispatcher that have a verbosity level of 'level' or lower. + /// + /// This call can have the effect of REDUCING the number of events sent to the + /// dispatcher if 'level' indicates a less verbose level than was previously enabled. + /// + /// This call never has an effect on other EventListeners. + /// + /// + public void EnableEvents(EventSource eventSource, EventLevel level) + { + EnableEvents(eventSource, level, EventKeywords.None); + } + /// + /// Enable all events from the eventSource identified by 'eventSource' to the current + /// dispatcher that have a verbosity level of 'level' or lower and have a event keyword + /// matching any of the bits in 'matchAnyKeyword'. + /// + /// This call can have the effect of REDUCING the number of events sent to the + /// dispatcher if 'level' indicates a less verbose level than was previously enabled or + /// if 'matchAnyKeyword' has fewer keywords set than where previously set. + /// + /// This call never has an effect on other EventListeners. + /// + public void EnableEvents(EventSource eventSource, EventLevel level, EventKeywords matchAnyKeyword) + { + EnableEvents(eventSource, level, matchAnyKeyword, null); + } + /// + /// Enable all events from the eventSource identified by 'eventSource' to the current + /// dispatcher that have a verbosity level of 'level' or lower and have a event keyword + /// matching any of the bits in 'matchAnyKeyword' as well as any (eventSource specific) + /// effect passing additional 'key-value' arguments 'arguments' might have. + /// + /// This call can have the effect of REDUCING the number of events sent to the + /// dispatcher if 'level' indicates a less verbose level than was previously enabled or + /// if 'matchAnyKeyword' has fewer keywords set than where previously set. + /// + /// This call never has an effect on other EventListeners. + /// + public void EnableEvents(EventSource eventSource, EventLevel level, EventKeywords matchAnyKeyword, IDictionary arguments) + { + if (eventSource == null) + { + throw new ArgumentNullException(nameof(eventSource)); + } + Contract.EndContractBlock(); + + eventSource.SendCommand(this, 0, 0, EventCommand.Update, true, level, matchAnyKeyword, arguments); + } + /// + /// Disables all events coming from eventSource identified by 'eventSource'. + /// + /// This call never has an effect on other EventListeners. + /// + public void DisableEvents(EventSource eventSource) + { + if (eventSource == null) + { + throw new ArgumentNullException(nameof(eventSource)); + } + Contract.EndContractBlock(); + + eventSource.SendCommand(this, 0, 0, EventCommand.Update, false, EventLevel.LogAlways, EventKeywords.None, null); + } + + /// + /// EventSourceIndex is small non-negative integer (suitable for indexing in an array) + /// identifying EventSource. It is unique per-appdomain. Some EventListeners might find + /// it useful to store additional information about each eventSource connected to it, + /// and EventSourceIndex allows this extra information to be efficiently stored in a + /// (growable) array (eg List(T)). + /// + public static int EventSourceIndex(EventSource eventSource) { return eventSource.m_id; } + + /// + /// This method is called whenever a new eventSource is 'attached' to the dispatcher. + /// This can happen for all existing EventSources when the EventListener is created + /// as well as for any EventSources that come into existence after the EventListener + /// has been created. + /// + /// These 'catch up' events are called during the construction of the EventListener. + /// Subclasses need to be prepared for that. + /// + /// In a multi-threaded environment, it is possible that 'OnEventWritten' callbacks + /// for a particular eventSource to occur BEFORE the OnEventSourceCreated is issued. + /// + /// + internal protected virtual void OnEventSourceCreated(EventSource eventSource) + { + EventHandler callBack = this._EventSourceCreated; + if (callBack != null) + { + EventSourceCreatedEventArgs args = new EventSourceCreatedEventArgs(); + args.EventSource = eventSource; + callBack(this, args); + } + } + + /// + /// This method is called whenever an event has been written by a EventSource for which + /// the EventListener has enabled events. + /// + /// + internal protected virtual void OnEventWritten(EventWrittenEventArgs eventData) + { + EventHandler callBack = this.EventWritten; + if (callBack != null) + { + callBack(this, eventData); + } + } + + + #region private + /// + /// This routine adds newEventSource to the global list of eventSources, it also assigns the + /// ID to the eventSource (which is simply the ordinal in the global list). + /// + /// EventSources currently do not pro-actively remove themselves from this list. Instead + /// when eventSources's are GCed, the weak handle in this list naturally gets nulled, and + /// we will reuse the slot. Today this list never shrinks (but we do reuse entries + /// that are in the list). This seems OK since the expectation is that EventSources + /// tend to live for the lifetime of the appdomain anyway (they tend to be used in + /// global variables). + /// + /// + internal static void AddEventSource(EventSource newEventSource) + { + lock (EventListenersLock) + { + if (s_EventSources == null) + s_EventSources = new List(2); + + if (!s_EventSourceShutdownRegistered) + { + s_EventSourceShutdownRegistered = true; + } + + + // Periodically search the list for existing entries to reuse, this avoids + // unbounded memory use if we keep recycling eventSources (an unlikely thing). + int newIndex = -1; + if (s_EventSources.Count % 64 == 63) // on every block of 64, fill up the block before continuing + { + int i = s_EventSources.Count; // Work from the top down. + while (0 < i) + { + --i; + WeakReference weakRef = s_EventSources[i]; + if (!weakRef.IsAlive) + { + newIndex = i; + weakRef.Target = newEventSource; + break; + } + } + } + if (newIndex < 0) + { + newIndex = s_EventSources.Count; + s_EventSources.Add(new WeakReference(newEventSource)); + } + newEventSource.m_id = newIndex; + + // Add every existing dispatcher to the new EventSource + for (EventListener listener = s_Listeners; listener != null; listener = listener.m_Next) + newEventSource.AddListener(listener); + + Validate(); + } + } + + // Whenver we have async callbacks from native code, there is an ugly issue where + // during .NET shutdown native code could be calling the callback, but the CLR + // has already prohibited callbacks to managed code in the appdomain, causing the CLR + // to throw a COMPLUS_BOOT_EXCEPTION. The guideline we give is that you must unregister + // such callbacks on process shutdown or appdomain so that unmanaged code will never + // do this. This is what this callback is for. + // See bug 724140 for more + private static void DisposeOnShutdown(object sender, EventArgs e) + { + lock (EventListenersLock) + { + foreach (var esRef in s_EventSources) + { + EventSource es = esRef.Target as EventSource; + if (es != null) + es.Dispose(); + } + } + } + + /// + /// Helper used in code:Dispose that removes any references to 'listenerToRemove' in any of the + /// eventSources in the appdomain. + /// + /// The EventListenersLock must be held before calling this routine. + /// + private static void RemoveReferencesToListenerInEventSources(EventListener listenerToRemove) + { +#if !ES_BUILD_STANDALONE + Debug.Assert(Monitor.IsEntered(EventListener.EventListenersLock)); +#endif + // Foreach existing EventSource in the appdomain + foreach (WeakReference eventSourceRef in s_EventSources) + { + EventSource eventSource = eventSourceRef.Target as EventSource; + if (eventSource != null) + { + // Is the first output dispatcher the dispatcher we are removing? + if (eventSource.m_Dispatchers.m_Listener == listenerToRemove) + eventSource.m_Dispatchers = eventSource.m_Dispatchers.m_Next; + else + { + // Remove 'listenerToRemove' from the eventSource.m_Dispatchers linked list. + EventDispatcher prev = eventSource.m_Dispatchers; + for (;;) + { + EventDispatcher cur = prev.m_Next; + if (cur == null) + { + Debug.Assert(false, "EventSource did not have a registered EventListener!"); + break; + } + if (cur.m_Listener == listenerToRemove) + { + prev.m_Next = cur.m_Next; // Remove entry. + break; + } + prev = cur; + } + } + } + } + } + + /// + /// Checks internal consistency of EventSources/Listeners. + /// + [Conditional("DEBUG")] + internal static void Validate() + { + lock (EventListenersLock) + { + // Get all listeners + Dictionary allListeners = new Dictionary(); + EventListener cur = s_Listeners; + while (cur != null) + { + allListeners.Add(cur, true); + cur = cur.m_Next; + } + + // For all eventSources + int id = -1; + foreach (WeakReference eventSourceRef in s_EventSources) + { + id++; + EventSource eventSource = eventSourceRef.Target as EventSource; + if (eventSource == null) + continue; + Debug.Assert(eventSource.m_id == id, "Unexpected event source ID."); + + // None listeners on eventSources exist in the dispatcher list. + EventDispatcher dispatcher = eventSource.m_Dispatchers; + while (dispatcher != null) + { + Debug.Assert(allListeners.ContainsKey(dispatcher.m_Listener), "EventSource has a listener not on the global list."); + dispatcher = dispatcher.m_Next; + } + + // Every dispatcher is on Dispatcher List of every eventSource. + foreach (EventListener listener in allListeners.Keys) + { + dispatcher = eventSource.m_Dispatchers; + for (;;) + { + Debug.Assert(dispatcher != null, "Listener is not on all eventSources."); + if (dispatcher.m_Listener == listener) + break; + dispatcher = dispatcher.m_Next; + } + } + } + } + } + + /// + /// Gets a global lock that is intended to protect the code:s_Listeners linked list and the + /// code:s_EventSources WeakReference list. (We happen to use the s_EventSources list as + /// the lock object) + /// + internal static object EventListenersLock + { + get + { + if (s_EventSources == null) + Interlocked.CompareExchange(ref s_EventSources, new List(2), null); + return s_EventSources; + } + } + + private void CallBackForExistingEventSources(bool addToListenersList, EventHandler callback) + { + lock (EventListenersLock) + { + // Disallow creating EventListener reentrancy. + if (s_CreatingListener) + { + throw new InvalidOperationException(Resources.GetResourceString("EventSource_ListenerCreatedInsideCallback")); + } + + try + { + s_CreatingListener = true; + + if (addToListenersList) + { + // Add to list of listeners in the system, do this BEFORE firing the 'OnEventSourceCreated' so that + // Those added sources see this listener. + this.m_Next = s_Listeners; + s_Listeners = this; + } + + // Find all existing eventSources call OnEventSourceCreated to 'catchup' + // Note that we DO have reentrancy here because 'AddListener' calls out to user code (via OnEventSourceCreated callback) + // We tolerate this by iterating over a copy of the list here. New event sources will take care of adding listeners themselves + // EventSources are not guaranteed to be added at the end of the s_EventSource list -- We re-use slots when a new source + // is created. + WeakReference[] eventSourcesSnapshot = s_EventSources.ToArray(); + + for (int i = 0; i < eventSourcesSnapshot.Length; i++) + { + WeakReference eventSourceRef = eventSourcesSnapshot[i]; + EventSource eventSource = eventSourceRef.Target as EventSource; + if (eventSource != null) + { + EventSourceCreatedEventArgs args = new EventSourceCreatedEventArgs(); + args.EventSource = eventSource; + callback(this, args); + } + } + + Validate(); + } + finally + { + s_CreatingListener = false; + } + } + + } + + // Instance fields + internal volatile EventListener m_Next; // These form a linked list in s_Listeners +#if FEATURE_ACTIVITYSAMPLING + internal ActivityFilter m_activityFilter; // If we are filtering by activity on this Listener, this keeps track of it. +#endif // FEATURE_ACTIVITYSAMPLING + + // static fields + + /// + /// The list of all listeners in the appdomain. Listeners must be explicitly disposed to remove themselves + /// from this list. Note that EventSources point to their listener but NOT the reverse. + /// + internal static EventListener s_Listeners; + /// + /// The list of all active eventSources in the appdomain. Note that eventSources do NOT + /// remove themselves from this list this is a weak list and the GC that removes them may + /// not have happened yet. Thus it can contain event sources that are dead (thus you have + /// to filter those out. + /// + internal static List s_EventSources; + + /// + /// Used to disallow reentrancy. + /// + private static bool s_CreatingListener = false; + + /// + /// Used to register AD/Process shutdown callbacks. + /// + private static bool s_EventSourceShutdownRegistered = false; + #endregion + } + + /// + /// Passed to the code:EventSource.OnEventCommand callback + /// + public class EventCommandEventArgs : EventArgs + { + /// + /// Gets the command for the callback. + /// + public EventCommand Command { get; internal set; } + + /// + /// Gets the arguments for the callback. + /// + public IDictionary Arguments { get; internal set; } + + /// + /// Enables the event that has the specified identifier. + /// + /// Event ID of event to be enabled + /// true if eventId is in range + public bool EnableEvent(int eventId) + { + if (Command != EventCommand.Enable && Command != EventCommand.Disable) + throw new InvalidOperationException(); + return eventSource.EnableEventForDispatcher(dispatcher, eventId, true); + } + + /// + /// Disables the event that have the specified identifier. + /// + /// Event ID of event to be disabled + /// true if eventId is in range + public bool DisableEvent(int eventId) + { + if (Command != EventCommand.Enable && Command != EventCommand.Disable) + throw new InvalidOperationException(); + return eventSource.EnableEventForDispatcher(dispatcher, eventId, false); + } + + #region private + + internal EventCommandEventArgs(EventCommand command, IDictionary arguments, EventSource eventSource, + EventListener listener, int perEventSourceSessionId, int etwSessionId, bool enable, EventLevel level, EventKeywords matchAnyKeyword) + { + this.Command = command; + this.Arguments = arguments; + this.eventSource = eventSource; + this.listener = listener; + this.perEventSourceSessionId = perEventSourceSessionId; + this.etwSessionId = etwSessionId; + this.enable = enable; + this.level = level; + this.matchAnyKeyword = matchAnyKeyword; + } + + internal EventSource eventSource; + internal EventDispatcher dispatcher; + + // These are the arguments of sendCommand and are only used for deferring commands until after we are fully initialized. + internal EventListener listener; + internal int perEventSourceSessionId; + internal int etwSessionId; + internal bool enable; + internal EventLevel level; + internal EventKeywords matchAnyKeyword; + internal EventCommandEventArgs nextCommand; // We form a linked list of these deferred commands. + + #endregion + } + + /// + /// EventSourceCreatedEventArgs is passed to + /// + public class EventSourceCreatedEventArgs : EventArgs + { + /// + /// The EventSource that is attaching to the listener. + /// + public EventSource EventSource + { + get; + internal set; + } + } + + /// + /// EventWrittenEventArgs is passed to the user-provided override for + /// when an event is fired. + /// + public class EventWrittenEventArgs : EventArgs + { + /// + /// The name of the event. + /// + public string EventName + { + get + { + if (m_eventName != null || EventId < 0) // TraceLogging convention EventID == -1 + { + return m_eventName; + } + else + return m_eventSource.m_eventData[EventId].Name; + } + internal set + { + m_eventName = value; + } + } + + /// + /// Gets the event ID for the event that was written. + /// + public int EventId { get; internal set; } + + /// + /// Gets the activity ID for the thread on which the event was written. + /// + public Guid ActivityId + { + get { return EventSource.CurrentThreadActivityId; } + } + + /// + /// Gets the related activity ID if one was specified when the event was written. + /// + public Guid RelatedActivityId + { + get; + internal set; + } + + /// + /// Gets the payload for the event. + /// + public ReadOnlyCollection Payload { get; internal set; } + + /// + /// Gets the payload argument names. + /// + public ReadOnlyCollection PayloadNames + { + get + { + // For contract based events we create the list lazily. + if (m_payloadNames == null) + { + // Self described events are identified by id -1. + Debug.Assert(EventId != -1); + + var names = new List(); + foreach (var parameter in m_eventSource.m_eventData[EventId].Parameters) + { + names.Add(parameter.Name); + } + m_payloadNames = new ReadOnlyCollection(names); + } + + return m_payloadNames; + } + + internal set + { + m_payloadNames = value; + } + } + + /// + /// Gets the event source object. + /// + public EventSource EventSource { get { return m_eventSource; } } + + /// + /// Gets the keywords for the event. + /// + public EventKeywords Keywords + { + get + { + if (EventId < 0) // TraceLogging convention EventID == -1 + return m_keywords; + + return (EventKeywords)m_eventSource.m_eventData[EventId].Descriptor.Keywords; + } + } + + /// + /// Gets the operation code for the event. + /// + public EventOpcode Opcode + { + get + { + if (EventId <= 0) // TraceLogging convention EventID == -1 + return m_opcode; + return (EventOpcode)m_eventSource.m_eventData[EventId].Descriptor.Opcode; + } + } + + /// + /// Gets the task for the event. + /// + public EventTask Task + { + get + { + if (EventId <= 0) // TraceLogging convention EventID == -1 + return EventTask.None; + + return (EventTask)m_eventSource.m_eventData[EventId].Descriptor.Task; + } + } + + /// + /// Any provider/user defined options associated with the event. + /// + public EventTags Tags + { + get + { + if (EventId <= 0) // TraceLogging convention EventID == -1 + return m_tags; + return m_eventSource.m_eventData[EventId].Tags; + } + } + + /// + /// Gets the message for the event. If the message has {N} parameters they are NOT substituted. + /// + public string Message + { + get + { + if (EventId <= 0) // TraceLogging convention EventID == -1 + return m_message; + else + return m_eventSource.m_eventData[EventId].Message; + } + internal set + { + m_message = value; + } + } + + +#if FEATURE_MANAGED_ETW_CHANNELS + /// + /// Gets the channel for the event. + /// + public EventChannel Channel + { + get + { + if (EventId <= 0) // TraceLogging convention EventID == -1 + return EventChannel.None; + return (EventChannel)m_eventSource.m_eventData[EventId].Descriptor.Channel; + } + } +#endif + + /// + /// Gets the version of the event. + /// + public byte Version + { + get + { + if (EventId <= 0) // TraceLogging convention EventID == -1 + return 0; + return m_eventSource.m_eventData[EventId].Descriptor.Version; + } + } + + /// + /// Gets the level for the event. + /// + public EventLevel Level + { + get + { + if (EventId <= 0) // TraceLogging convention EventID == -1 + return m_level; + return (EventLevel)m_eventSource.m_eventData[EventId].Descriptor.Level; + } + } + + #region private + internal EventWrittenEventArgs(EventSource eventSource) + { + m_eventSource = eventSource; + } + private string m_message; + private string m_eventName; + private EventSource m_eventSource; + private ReadOnlyCollection m_payloadNames; + internal EventTags m_tags; + internal EventOpcode m_opcode; + internal EventLevel m_level; + internal EventKeywords m_keywords; + #endregion + } + + /// + /// Allows customizing defaults and specifying localization support for the event source class to which it is applied. + /// + [AttributeUsage(AttributeTargets.Class)] + public sealed class EventSourceAttribute : Attribute + { + /// + /// Overrides the ETW name of the event source (which defaults to the class name) + /// + public string Name { get; set; } + + /// + /// Overrides the default (calculated) Guid of an EventSource type. Explicitly defining a GUID is discouraged, + /// except when upgrading existing ETW providers to using event sources. + /// + public string Guid { get; set; } + + /// + /// + /// EventSources support localization of events. The names used for events, opcodes, tasks, keywords and maps + /// can be localized to several languages if desired. This works by creating a ResX style string table + /// (by simply adding a 'Resource File' to your project). This resource file is given a name e.g. + /// 'DefaultNameSpace.ResourceFileName' which can be passed to the ResourceManager constructor to read the + /// resources. This name is the value of the LocalizationResources property. + /// + /// If LocalizationResources property is non-null, then EventSource will look up the localized strings for events by + /// using the following resource naming scheme + /// + /// * event_EVENTNAME + /// * task_TASKNAME + /// * keyword_KEYWORDNAME + /// * map_MAPNAME + /// + /// where the capitalized name is the name of the event, task, keyword, or map value that should be localized. + /// Note that the localized string for an event corresponds to the Message string, and can have {0} values + /// which represent the payload values. + /// + /// + public string LocalizationResources { get; set; } + } + + /// + /// Any instance methods in a class that subclasses and that return void are + /// assumed by default to be methods that generate an ETW event. Enough information can be deduced from the + /// name of the method and its signature to generate basic schema information for the event. The + /// class allows you to specify additional event schema information for an event if + /// desired. + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class EventAttribute : Attribute + { + /// Construct an EventAttribute with specified eventId + /// ID of the ETW event (an integer between 1 and 65535) + public EventAttribute(int eventId) { this.EventId = eventId; Level = EventLevel.Informational; this.m_opcodeSet = false; } + /// Event's ID + public int EventId { get; private set; } + /// Event's severity level: indicates the severity or verbosity of the event + public EventLevel Level { get; set; } + /// Event's keywords: allows classification of events by "categories" + public EventKeywords Keywords { get; set; } + /// Event's operation code: allows defining operations, generally used with Tasks + public EventOpcode Opcode + { + get + { + return m_opcode; + } + set + { + this.m_opcode = value; + this.m_opcodeSet = true; + } + } + + internal bool IsOpcodeSet + { + get + { + return m_opcodeSet; + } + } + + /// Event's task: allows logical grouping of events + public EventTask Task { get; set; } +#if FEATURE_MANAGED_ETW_CHANNELS + /// Event's channel: defines an event log as an additional destination for the event + public EventChannel Channel { get; set; } +#endif + /// Event's version + public byte Version { get; set; } + + /// + /// This can be specified to enable formatting and localization of the event's payload. You can + /// use standard .NET substitution operators (eg {1}) in the string and they will be replaced + /// with the 'ToString()' of the corresponding part of the event payload. + /// + public string Message { get; set; } + + /// + /// User defined options associated with the event. These do not have meaning to the EventSource but + /// are passed through to listeners which given them semantics. + /// + public EventTags Tags { get; set; } + + /// + /// Allows fine control over the Activity IDs generated by start and stop events + /// + public EventActivityOptions ActivityOptions { get; set; } + + #region private + EventOpcode m_opcode; + private bool m_opcodeSet; + #endregion + } + + /// + /// By default all instance methods in a class that subclasses code:EventSource that and return + /// void are assumed to be methods that generate an event. This default can be overridden by specifying + /// the code:NonEventAttribute + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class NonEventAttribute : Attribute + { + /// + /// Constructs a default NonEventAttribute + /// + public NonEventAttribute() { } + } + + // FUTURE we may want to expose this at some point once we have a partner that can help us validate the design. +#if FEATURE_MANAGED_ETW_CHANNELS + /// + /// EventChannelAttribute allows customizing channels supported by an EventSource. This attribute must be + /// applied to an member of type EventChannel defined in a Channels class nested in the EventSource class: + /// + /// public static class Channels + /// { + /// [Channel(Enabled = true, EventChannelType = EventChannelType.Admin)] + /// public const EventChannel Admin = (EventChannel)16; + /// + /// [Channel(Enabled = false, EventChannelType = EventChannelType.Operational)] + /// public const EventChannel Operational = (EventChannel)17; + /// } + /// + /// + [AttributeUsage(AttributeTargets.Field)] +#if FEATURE_ADVANCED_MANAGED_ETW_CHANNELS + public +#endif + class EventChannelAttribute : Attribute + { + /// + /// Specified whether the channel is enabled by default + /// + public bool Enabled { get; set; } + + /// + /// Legal values are in EventChannelType + /// + public EventChannelType EventChannelType { get; set; } + +#if FEATURE_ADVANCED_MANAGED_ETW_CHANNELS + /// + /// Specifies the isolation for the channel + /// + public EventChannelIsolation Isolation { get; set; } + + /// + /// Specifies an SDDL access descriptor that controls access to the log file that backs the channel. + /// See MSDN ((http://msdn.microsoft.com/en-us/library/windows/desktop/aa382741.aspx) for details. + /// + public string Access { get; set; } + + /// + /// Allows importing channels defined in external manifests + /// + public string ImportChannel { get; set; } +#endif + + // TODO: there is a convention that the name is the Provider/Type Should we provide an override? + // public string Name { get; set; } + } + + /// + /// Allowed channel types + /// +#if FEATURE_ADVANCED_MANAGED_ETW_CHANNELS + public +#endif + enum EventChannelType + { + /// The admin channel + Admin = 1, + /// The operational channel + Operational, + /// The Analytic channel + Analytic, + /// The debug channel + Debug, + } + +#if FEATURE_ADVANCED_MANAGED_ETW_CHANNELS + /// + /// Allowed isolation levels. See MSDN (http://msdn.microsoft.com/en-us/library/windows/desktop/aa382741.aspx) + /// for the default permissions associated with each level. EventChannelIsolation and Access allows control over the + /// access permissions for the channel and backing file. + /// + public + enum EventChannelIsolation + { + /// + /// This is the default isolation level. All channels that specify Application isolation use the same ETW session + /// + Application = 1, + /// + /// All channels that specify System isolation use the same ETW session + /// + System, + /// + /// Use sparingly! When specifying Custom isolation, a separate ETW session is created for the channel. + /// Using Custom isolation lets you control the access permissions for the channel and backing file. + /// Because there are only 64 ETW sessions available, you should limit your use of Custom isolation. + /// + Custom, + } +#endif +#endif + + /// + /// Describes the pre-defined command (EventCommandEventArgs.Command property) that is passed to the OnEventCommand callback. + /// + public enum EventCommand + { + /// + /// Update EventSource state + /// + Update = 0, + /// + /// Request EventSource to generate and send its manifest + /// + SendManifest = -1, + /// + /// Enable event + /// + Enable = -2, + /// + /// Disable event + /// + Disable = -3 + }; + + + #region private classes + +#if FEATURE_ACTIVITYSAMPLING + + /// + /// ActivityFilter is a helper structure that is used to keep track of run-time state + /// associated with activity filtering. It is 1-1 with EventListeners (logically + /// every listener has one of these, however we actually allocate them lazily), as well + /// as 1-to-1 with tracing-aware EtwSessions. + /// + /// This structure also keeps track of the sampling counts associated with 'trigger' + /// events. Because these trigger events are rare, and you typically only have one of + /// them, we store them here as a linked list. + /// + internal sealed class ActivityFilter : IDisposable + { + /// + /// Disable all activity filtering for the listener associated with 'filterList', + /// (in the session associated with it) that is triggered by any event in 'source'. + /// + public static void DisableFilter(ref ActivityFilter filterList, EventSource source) + { +#if !ES_BUILD_STANDALONE + Debug.Assert(Monitor.IsEntered(EventListener.EventListenersLock)); +#endif + + if (filterList == null) + return; + + ActivityFilter cur; + // Remove it from anywhere in the list (except the first element, which has to + // be treated specially) + ActivityFilter prev = filterList; + cur = prev.m_next; + while (cur != null) + { + if (cur.m_providerGuid == source.Guid) + { + // update TriggersActivityTracking bit + if (cur.m_eventId >= 0 && cur.m_eventId < source.m_eventData.Length) + --source.m_eventData[cur.m_eventId].TriggersActivityTracking; + + // Remove it from the linked list. + prev.m_next = cur.m_next; + // dispose of the removed node + cur.Dispose(); + // update cursor + cur = prev.m_next; + } + else + { + // update cursors + prev = cur; + cur = prev.m_next; + } + } + + // Sadly we have to treat the first element specially in linked list removal in C# + if (filterList.m_providerGuid == source.Guid) + { + // update TriggersActivityTracking bit + if (filterList.m_eventId >= 0 && filterList.m_eventId < source.m_eventData.Length) + --source.m_eventData[filterList.m_eventId].TriggersActivityTracking; + + // We are the first element in the list. + var first = filterList; + filterList = first.m_next; + // dispose of the removed node + first.Dispose(); + } + // the above might have removed the one ActivityFilter in the session that contains the + // cleanup delegate; re-create the delegate if needed + if (filterList != null) + { + EnsureActivityCleanupDelegate(filterList); + } + } + + /// + /// Currently this has "override" semantics. We first disable all filters + /// associated with 'source', and next we add new filters for each entry in the + /// string 'startEvents'. participateInSampling specifies whether non-startEvents + /// always trigger or only trigger when current activity is 'active'. + /// + public static void UpdateFilter( + ref ActivityFilter filterList, + EventSource source, + int perEventSourceSessionId, + string startEvents) + { +#if !ES_BUILD_STANDALONE + Debug.Assert(Monitor.IsEntered(EventListener.EventListenersLock)); +#endif + + // first remove all filters associated with 'source' + DisableFilter(ref filterList, source); + + if (!string.IsNullOrEmpty(startEvents)) + { + // ActivitySamplingStartEvents is a space-separated list of Event:Frequency pairs. + // The Event may be specified by name or by ID. Errors in parsing such a pair + // result in the error being reported to the listeners, and the pair being ignored. + // E.g. "CustomActivityStart:1000 12:10" specifies that for event CustomActivityStart + // we should initiate activity tracing once every 1000 events, *and* for event ID 12 + // we should initiate activity tracing once every 10 events. + string[] activityFilterStrings = startEvents.Split(' '); + + for (int i = 0; i < activityFilterStrings.Length; ++i) + { + string activityFilterString = activityFilterStrings[i]; + int sampleFreq = 1; + int eventId = -1; + int colonIdx = activityFilterString.IndexOf(':'); + if (colonIdx < 0) + { + source.ReportOutOfBandMessage("ERROR: Invalid ActivitySamplingStartEvent specification: " + + activityFilterString, false); + // ignore failure... + continue; + } + string sFreq = activityFilterString.Substring(colonIdx + 1); + if (!int.TryParse(sFreq, out sampleFreq)) + { + source.ReportOutOfBandMessage("ERROR: Invalid sampling frequency specification: " + sFreq, false); + continue; + } + activityFilterString = activityFilterString.Substring(0, colonIdx); + if (!int.TryParse(activityFilterString, out eventId)) + { + // reset eventId + eventId = -1; + // see if it's an event name + for (int j = 0; j < source.m_eventData.Length; j++) + { + EventSource.EventMetadata[] ed = source.m_eventData; + if (ed[j].Name != null && ed[j].Name.Length == activityFilterString.Length && + string.Compare(ed[j].Name, activityFilterString, StringComparison.OrdinalIgnoreCase) == 0) + { + eventId = ed[j].Descriptor.EventId; + break; + } + } + } + if (eventId < 0 || eventId >= source.m_eventData.Length) + { + source.ReportOutOfBandMessage("ERROR: Invalid eventId specification: " + activityFilterString, false); + continue; + } + EnableFilter(ref filterList, source, perEventSourceSessionId, eventId, sampleFreq); + } + } + } + + /// + /// Returns the first ActivityFilter from 'filterList' corresponding to 'source'. + /// + public static ActivityFilter GetFilter(ActivityFilter filterList, EventSource source) + { + for (var af = filterList; af != null; af = af.m_next) + { + if (af.m_providerGuid == source.Guid && af.m_samplingFreq != -1) + return af; + } + return null; + } + + /// + /// Returns a session mask representing all sessions in which the activity + /// associated with the current thread is allowed through the activity filter. + /// If 'triggeringEvent' is true the event MAY be a triggering event. Ideally + /// most of the time this is false as you can guarentee this event is NOT a + /// triggering event. If 'triggeringEvent' is true, then it checks the + /// 'EventSource' and 'eventID' of the event being logged to see if it is actually + /// a trigger. If so it activates the current activity. + /// + /// If 'childActivityID' is present, it will be added to the active set if the + /// current activity is active. + /// + unsafe public static bool PassesActivityFilter( + ActivityFilter filterList, + Guid* childActivityID, + bool triggeringEvent, + EventSource source, + int eventId) + { + Debug.Assert(filterList != null && filterList.m_activeActivities != null); + bool shouldBeLogged = false; + if (triggeringEvent) + { + for (ActivityFilter af = filterList; af != null; af = af.m_next) + { + if (eventId == af.m_eventId && source.Guid == af.m_providerGuid) + { + // Update the sampling count with wrap-around + int curSampleCount, newSampleCount; + do + { + curSampleCount = af.m_curSampleCount; + if (curSampleCount <= 1) + newSampleCount = af.m_samplingFreq; // Wrap around, counting down to 1 + else + newSampleCount = curSampleCount - 1; + } + while (Interlocked.CompareExchange(ref af.m_curSampleCount, newSampleCount, curSampleCount) != curSampleCount); + // If we hit zero, then start tracking the activity. + if (curSampleCount <= 1) + { + Guid currentActivityId = EventSource.InternalCurrentThreadActivityId; + Tuple startId; + // only add current activity if it's not already a root activity + if (!af.m_rootActiveActivities.TryGetValue(currentActivityId, out startId)) + { + // EventSource.OutputDebugString(string.Format(" PassesAF - Triggering(session {0}, evt {1})", af.m_perEventSourceSessionId, eventId)); + shouldBeLogged = true; + af.m_activeActivities[currentActivityId] = Environment.TickCount; + af.m_rootActiveActivities[currentActivityId] = Tuple.Create(source.Guid, eventId); + } + } + else + { + // a start event following a triggering start event + Guid currentActivityId = EventSource.InternalCurrentThreadActivityId; + Tuple startId; + // only remove current activity if we added it + if (af.m_rootActiveActivities.TryGetValue(currentActivityId, out startId) && + startId.Item1 == source.Guid && startId.Item2 == eventId) + { + // EventSource.OutputDebugString(string.Format("Activity dying: {0} -> StartEvent({1})", currentActivityId, eventId)); + // remove activity only from current logging scope (af) + int dummy; + af.m_activeActivities.TryRemove(currentActivityId, out dummy); + } + } + break; + } + } + } + + var activeActivities = GetActiveActivities(filterList); + if (activeActivities != null) + { + // if we hadn't already determined this should be logged, test further + if (!shouldBeLogged) + { + shouldBeLogged = !activeActivities.IsEmpty && + activeActivities.ContainsKey(EventSource.InternalCurrentThreadActivityId); + } + if (shouldBeLogged && childActivityID != null && + ((EventOpcode)source.m_eventData[eventId].Descriptor.Opcode == EventOpcode.Send)) + { + FlowActivityIfNeeded(filterList, null, childActivityID); + // EventSource.OutputDebugString(string.Format(" PassesAF - activity {0}", *childActivityID)); + } + } + // EventSource.OutputDebugString(string.Format(" PassesAF - shouldBeLogged(evt {0}) = {1:x}", eventId, shouldBeLogged)); + return shouldBeLogged; + } + + public static bool IsCurrentActivityActive(ActivityFilter filterList) + { + var activeActivities = GetActiveActivities(filterList); + if (activeActivities != null && + activeActivities.ContainsKey(EventSource.InternalCurrentThreadActivityId)) + return true; + + return false; + } + + /// + /// For the EventListener/EtwSession associated with 'filterList', add 'childActivityid' + /// to list of active activities IF 'currentActivityId' is also active. Passing in a null + /// value for 'currentActivityid' is an indication tha caller has already verified + /// that the current activity is active. + /// + unsafe public static void FlowActivityIfNeeded(ActivityFilter filterList, Guid* currentActivityId, Guid* childActivityID) + { + Debug.Assert(childActivityID != null); + + var activeActivities = GetActiveActivities(filterList); + Debug.Assert(activeActivities != null); + + // take currentActivityId == null to mean we *know* the current activity is "active" + if (currentActivityId != null && !activeActivities.ContainsKey(*currentActivityId)) + return; + + if (activeActivities.Count > MaxActivityTrackCount) + { + TrimActiveActivityStore(activeActivities); + // make sure current activity is still in the set: + activeActivities[EventSource.InternalCurrentThreadActivityId] = Environment.TickCount; + } + // add child activity to list of actives + activeActivities[*childActivityID] = Environment.TickCount; + + } + + /// + /// + public static void UpdateKwdTriggers(ActivityFilter activityFilter, Guid sourceGuid, EventSource source, EventKeywords sessKeywords) + { + for (var af = activityFilter; af != null; af = af.m_next) + { + if ((sourceGuid == af.m_providerGuid) && + (source.m_eventData[af.m_eventId].TriggersActivityTracking > 0 || + ((EventOpcode)source.m_eventData[af.m_eventId].Descriptor.Opcode == EventOpcode.Send))) + { + // we could be more precise here, if we tracked 'anykeywords' per session + unchecked + { + source.m_keywordTriggers |= (source.m_eventData[af.m_eventId].Descriptor.Keywords & (long)sessKeywords); + } + } + } + } + + /// + /// For the EventSource specified by 'sourceGuid' and the EventListener/EtwSession + /// associated with 'this' ActivityFilter list, return configured sequence of + /// [eventId, sampleFreq] pairs that defines the sampling policy. + /// + public IEnumerable> GetFilterAsTuple(Guid sourceGuid) + { + for (ActivityFilter af = this; af != null; af = af.m_next) + { + if (af.m_providerGuid == sourceGuid) + yield return Tuple.Create(af.m_eventId, af.m_samplingFreq); + } + } + + /// + /// The cleanup being performed consists of removing the m_myActivityDelegate from + /// the static s_activityDying, therefore allowing the ActivityFilter to be reclaimed. + /// + public void Dispose() + { +#if !ES_BUILD_STANDALONE + Debug.Assert(Monitor.IsEntered(EventListener.EventListenersLock)); +#endif + // m_myActivityDelegate is still alive (held by the static EventSource.s_activityDying). + // Therefore we are ok to take a dependency on m_myActivityDelegate being valid even + // during the finalization of the ActivityFilter + if (m_myActivityDelegate != null) + { + EventSource.s_activityDying = (Action)Delegate.Remove(EventSource.s_activityDying, m_myActivityDelegate); + m_myActivityDelegate = null; + } + } + + #region private + + /// + /// Creates a new ActivityFilter that is triggered by 'eventId' from 'source' ever + /// 'samplingFreq' times the event fires. You can have several of these forming a + /// linked list. + /// + private ActivityFilter(EventSource source, int perEventSourceSessionId, int eventId, int samplingFreq, ActivityFilter existingFilter = null) + { + m_providerGuid = source.Guid; + m_perEventSourceSessionId = perEventSourceSessionId; + m_eventId = eventId; + m_samplingFreq = samplingFreq; + m_next = existingFilter; + + Debug.Assert(existingFilter == null || + (existingFilter.m_activeActivities == null) == (existingFilter.m_rootActiveActivities == null)); + + // if this is the first filter we add for this session, we need to create a new + // table of activities. m_activeActivities is common across EventSources in the same + // session + ConcurrentDictionary activeActivities = null; + if (existingFilter == null || + (activeActivities = GetActiveActivities(existingFilter)) == null) + { + m_activeActivities = new ConcurrentDictionary(); + m_rootActiveActivities = new ConcurrentDictionary>(); + + // Add a delegate to the 'SetCurrentThreadToActivity callback so that I remove 'dead' activities + m_myActivityDelegate = GetActivityDyingDelegate(this); + EventSource.s_activityDying = (Action)Delegate.Combine(EventSource.s_activityDying, m_myActivityDelegate); + } + else + { + m_activeActivities = activeActivities; + m_rootActiveActivities = existingFilter.m_rootActiveActivities; + } + + } + + /// + /// Ensure there's at least one ActivityFilter in the 'filterList' that contains an + /// activity-removing delegate for the listener/session associated with 'filterList'. + /// + private static void EnsureActivityCleanupDelegate(ActivityFilter filterList) + { + if (filterList == null) + return; + + for (ActivityFilter af = filterList; af != null; af = af.m_next) + { + if (af.m_myActivityDelegate != null) + return; + } + + // we didn't find a delegate + filterList.m_myActivityDelegate = GetActivityDyingDelegate(filterList); + EventSource.s_activityDying = (Action)Delegate.Combine(EventSource.s_activityDying, filterList.m_myActivityDelegate); + } + + /// + /// Builds the delegate to be called when an activity is dying. This is responsible + /// for performing whatever cleanup is needed for the ActivityFilter list passed in. + /// This gets "added" to EventSource.s_activityDying and ends up being called from + /// EventSource.SetCurrentThreadActivityId and ActivityFilter.PassesActivityFilter. + /// + /// The delegate to be called when an activity is dying + private static Action GetActivityDyingDelegate(ActivityFilter filterList) + { + return (Guid oldActivity) => + { + int dummy; + filterList.m_activeActivities.TryRemove(oldActivity, out dummy); + Tuple dummyTuple; + filterList.m_rootActiveActivities.TryRemove(oldActivity, out dummyTuple); + }; + } + + /// + /// Enables activity filtering for the listener associated with 'filterList', triggering on + /// the event 'eventID' from 'source' with a sampling frequency of 'samplingFreq' + /// + /// if 'eventID' is out of range (e.g. negative), it means we are not triggering (but we are + /// activitySampling if something else triggered). + /// + /// true if activity sampling is enabled the samplingFreq is non-zero + private static bool EnableFilter(ref ActivityFilter filterList, EventSource source, int perEventSourceSessionId, int eventId, int samplingFreq) + { +#if !ES_BUILD_STANDALONE + Debug.Assert(Monitor.IsEntered(EventListener.EventListenersLock)); +#endif + Debug.Assert(samplingFreq > 0); + Debug.Assert(eventId >= 0); + + filterList = new ActivityFilter(source, perEventSourceSessionId, eventId, samplingFreq, filterList); + + // Mark the 'quick Check' that indicates this is a trigger event. + // If eventId is out of range then this mark is not done which has the effect of ignoring + // the trigger. + if (0 <= eventId && eventId < source.m_eventData.Length) + ++source.m_eventData[eventId].TriggersActivityTracking; + + return true; + } + + /// + /// Normally this code never runs, it is here just to prevent run-away resource usage. + /// + private static void TrimActiveActivityStore(ConcurrentDictionary activities) + { + if (activities.Count > MaxActivityTrackCount) + { + // Remove half of the oldest activity ids. + var keyValues = activities.ToArray(); + var tickNow = Environment.TickCount; + + // Sort by age, taking into account wrap-around. As long as x and y are within + // 23 days of now then (0x7FFFFFFF & (tickNow - x.Value)) is the delta (even if + // TickCount wraps). I then sort by DESCENDING age. (that is oldest value first) + Array.Sort(keyValues, (x, y) => (0x7FFFFFFF & (tickNow - y.Value)) - (0x7FFFFFFF & (tickNow - x.Value))); + for (int i = 0; i < keyValues.Length / 2; i++) + { + int dummy; + activities.TryRemove(keyValues[i].Key, out dummy); + } + } + } + + private static ConcurrentDictionary GetActiveActivities( + ActivityFilter filterList) + { + for (ActivityFilter af = filterList; af != null; af = af.m_next) + { + if (af.m_activeActivities != null) + return af.m_activeActivities; + } + return null; + } + + // m_activeActivities always points to the sample dictionary for EVERY ActivityFilter + // in the m_next list. The 'int' value in the m_activities set is a timestamp + // (Environment.TickCount) of when the entry was put in the system and is used to + // remove 'old' entries that if the set gets too big. + ConcurrentDictionary m_activeActivities; + + // m_rootActiveActivities holds the "root" active activities, i.e. the activities + // that were marked as active because a Start event fired on them. We need to keep + // track of these to enable sampling in the scenario of an app's main thread that + // never explicitly sets distinct activity IDs as it executes. To handle these + // situations we manufacture a Guid from the thread's ID, and: + // (a) we consider the firing of a start event when the sampling counter reaches + // zero to mark the beginning of an interesting activity, and + // (b) we consider the very next firing of the same start event to mark the + // ending of that activity. + // We use a ConcurrentDictionary to avoid taking explicit locks. + // The key (a guid) represents the activity ID of the root active activity + // The value is made up of the Guid of the event provider and the eventId of + // the start event. + ConcurrentDictionary> m_rootActiveActivities; + Guid m_providerGuid; // We use the GUID rather than object identity because we don't want to keep the eventSource alive + int m_eventId; // triggering event + int m_samplingFreq; // Counter reset to this when it hits 0 + int m_curSampleCount; // We count down to 0 and then activate the activity. + int m_perEventSourceSessionId; // session ID bit for ETW, 0 for EventListeners + + const int MaxActivityTrackCount = 100000; // maximum number of tracked activities + + ActivityFilter m_next; // We create a linked list of these + Action m_myActivityDelegate; + #endregion + }; + + + /// + /// An EtwSession instance represents an activity-tracing-aware ETW session. Since these + /// are limited to 8 concurrent sessions per machine (currently) we're going to store + /// the active ones in a singly linked list. + /// + internal class EtwSession + { + public static EtwSession GetEtwSession(int etwSessionId, bool bCreateIfNeeded = false) + { + if (etwSessionId < 0) + return null; + + EtwSession etwSession; + foreach (var wrEtwSession in s_etwSessions) + { +#if ES_BUILD_STANDALONE + if ((etwSession = (EtwSession) wrEtwSession.Target) != null && etwSession.m_etwSessionId == etwSessionId) + return etwSession; +#else + if (wrEtwSession.TryGetTarget(out etwSession) && etwSession.m_etwSessionId == etwSessionId) + return etwSession; +#endif + } + + if (!bCreateIfNeeded) + return null; + +#if ES_BUILD_STANDALONE + if (s_etwSessions == null) + s_etwSessions = new List(); + + etwSession = new EtwSession(etwSessionId); + s_etwSessions.Add(new WeakReference(etwSession)); +#else + if (s_etwSessions == null) + s_etwSessions = new List>(); + + etwSession = new EtwSession(etwSessionId); + s_etwSessions.Add(new WeakReference(etwSession)); +#endif + + if (s_etwSessions.Count > s_thrSessionCount) + TrimGlobalList(); + + return etwSession; + + } + + public static void RemoveEtwSession(EtwSession etwSession) + { + Debug.Assert(etwSession != null); + if (s_etwSessions == null || etwSession == null) + return; + + s_etwSessions.RemoveAll((wrEtwSession) => + { + EtwSession session; +#if ES_BUILD_STANDALONE + return (session = (EtwSession) wrEtwSession.Target) != null && + (session.m_etwSessionId == etwSession.m_etwSessionId); +#else + return wrEtwSession.TryGetTarget(out session) && + (session.m_etwSessionId == etwSession.m_etwSessionId); +#endif + }); + + if (s_etwSessions.Count > s_thrSessionCount) + TrimGlobalList(); + } + + private static void TrimGlobalList() + { + if (s_etwSessions == null) + return; + + s_etwSessions.RemoveAll((wrEtwSession) => + { +#if ES_BUILD_STANDALONE + return wrEtwSession.Target == null; +#else + EtwSession session; + return !wrEtwSession.TryGetTarget(out session); +#endif + }); + } + + private EtwSession(int etwSessionId) + { + m_etwSessionId = etwSessionId; + } + + public readonly int m_etwSessionId; // ETW session ID (as retrieved by EventProvider) + public ActivityFilter m_activityFilter; // all filters enabled for this session + +#if ES_BUILD_STANDALONE + private static List s_etwSessions = new List(); +#else + private static List> s_etwSessions = new List>(); +#endif + private const int s_thrSessionCount = 16; + } + +#endif // FEATURE_ACTIVITYSAMPLING + + // holds a bitfield representing a session mask + /// + /// A SessionMask represents a set of (at most MAX) sessions as a bit mask. The perEventSourceSessionId + /// is the index in the SessionMask of the bit that will be set. These can translate to + /// EventSource's reserved keywords bits using the provided ToEventKeywords() and + /// FromEventKeywords() methods. + /// + internal struct SessionMask + { + public SessionMask(SessionMask m) + { m_mask = m.m_mask; } + + public SessionMask(uint mask = 0) + { m_mask = mask & MASK; } + + public bool IsEqualOrSupersetOf(SessionMask m) + { + return (this.m_mask | m.m_mask) == this.m_mask; + } + + public static SessionMask All + { + get { return new SessionMask(MASK); } + } + + public static SessionMask FromId(int perEventSourceSessionId) + { + Debug.Assert(perEventSourceSessionId < MAX); + return new SessionMask((uint)1 << perEventSourceSessionId); + } + + public ulong ToEventKeywords() + { + return (ulong)m_mask << SHIFT_SESSION_TO_KEYWORD; + } + + public static SessionMask FromEventKeywords(ulong m) + { + return new SessionMask((uint)(m >> SHIFT_SESSION_TO_KEYWORD)); + } + + public bool this[int perEventSourceSessionId] + { + get + { + Debug.Assert(perEventSourceSessionId < MAX); + return (m_mask & (1 << perEventSourceSessionId)) != 0; + } + set + { + Debug.Assert(perEventSourceSessionId < MAX); + if (value) m_mask |= ((uint)1 << perEventSourceSessionId); + else m_mask &= ~((uint)1 << perEventSourceSessionId); + } + } + + public static SessionMask operator |(SessionMask m1, SessionMask m2) + { + return new SessionMask(m1.m_mask | m2.m_mask); + } + + public static SessionMask operator &(SessionMask m1, SessionMask m2) + { + return new SessionMask(m1.m_mask & m2.m_mask); + } + + public static SessionMask operator ^(SessionMask m1, SessionMask m2) + { + return new SessionMask(m1.m_mask ^ m2.m_mask); + } + + public static SessionMask operator ~(SessionMask m) + { + return new SessionMask(MASK & ~(m.m_mask)); + } + + public static explicit operator ulong(SessionMask m) + { return m.m_mask; } + + public static explicit operator uint(SessionMask m) + { return m.m_mask; } + + private uint m_mask; + + internal const int SHIFT_SESSION_TO_KEYWORD = 44; // bits 44-47 inclusive are reserved + internal const uint MASK = 0x0fU; // the mask of 4 reserved bits + internal const uint MAX = 4; // maximum number of simultaneous ETW sessions supported + } + + /// + /// code:EventDispatchers are a simple 'helper' structure that holds the filtering state + /// (m_EventEnabled) for a particular EventSource X EventListener tuple + /// + /// Thus a single EventListener may have many EventDispatchers (one for every EventSource + /// that that EventListener has activate) and a Single EventSource may also have many + /// event Dispatchers (one for every EventListener that has activated it). + /// + /// Logically a particular EventDispatcher belongs to exactly one EventSource and exactly + /// one EventListener (alhtough EventDispatcher does not 'remember' the EventSource it is + /// associated with. + /// + internal class EventDispatcher + { + internal EventDispatcher(EventDispatcher next, bool[] eventEnabled, EventListener listener) + { + m_Next = next; + m_EventEnabled = eventEnabled; + m_Listener = listener; + } + + // Instance fields + readonly internal EventListener m_Listener; // The dispatcher this entry is for + internal bool[] m_EventEnabled; // For every event in a the eventSource, is it enabled? +#if FEATURE_ACTIVITYSAMPLING + internal bool m_activityFilteringEnabled; // does THIS EventSource have activity filtering turned on for this listener? +#endif // FEATURE_ACTIVITYSAMPLING + + // Only guaranteed to exist after a InsureInit() + internal EventDispatcher m_Next; // These form a linked list in code:EventSource.m_Dispatchers + // Of all listeners for that eventSource. + } + + /// + /// Flags that can be used with EventSource.GenerateManifest to control how the ETW manifest for the EventSource is + /// generated. + /// + [Flags] + public enum EventManifestOptions + { + /// + /// Only the resources associated with current UI culture are included in the manifest + /// + None = 0x0, + /// + /// Throw exceptions for any inconsistency encountered + /// + Strict = 0x1, + /// + /// Generate a "resources" node under "localization" for every satellite assembly provided + /// + AllCultures = 0x2, + /// + /// Generate the manifest only if the event source needs to be registered on the machine, + /// otherwise return null (but still perform validation if Strict is specified) + /// + OnlyIfNeededForRegistration = 0x4, + /// + /// When generating the manifest do *not* enforce the rule that the current EventSource class + /// must be the base class for the user-defined type passed in. This allows validation of .net + /// event sources using the new validation code + /// + AllowEventSourceOverride = 0x8, + } + + /// + /// ManifestBuilder is designed to isolate the details of the message of the event from the + /// rest of EventSource. This one happens to create XML. + /// + internal partial class ManifestBuilder + { + /// + /// Build a manifest for 'providerName' with the given GUID, which will be packaged into 'dllName'. + /// 'resources, is a resource manager. If specified all messages are localized using that manager. + /// + public ManifestBuilder(string providerName, Guid providerGuid, string dllName, ResourceManager resources, + EventManifestOptions flags) + { +#if FEATURE_MANAGED_ETW_CHANNELS + this.providerName = providerName; +#endif + this.flags = flags; + + this.resources = resources; + sb = new StringBuilder(); + events = new StringBuilder(); + templates = new StringBuilder(); + opcodeTab = new Dictionary(); + stringTab = new Dictionary(); + errors = new List(); + perEventByteArrayArgIndices = new Dictionary>(); + + sb.AppendLine(""); + sb.AppendLine(" "); + sb.AppendLine(" "); + sb.Append("").AppendLine(); + } + + public void AddOpcode(string name, int value) + { + if ((flags & EventManifestOptions.Strict) != 0) + { + if (value <= 10 || value >= 239) + { + ManifestError(Resources.GetResourceString("EventSource_IllegalOpcodeValue", name, value)); + } + string prevName; + if (opcodeTab.TryGetValue(value, out prevName) && !name.Equals(prevName, StringComparison.Ordinal)) + { + ManifestError(Resources.GetResourceString("EventSource_OpcodeCollision", name, prevName, value)); + } + } + opcodeTab[value] = name; + } + public void AddTask(string name, int value) + { + if ((flags & EventManifestOptions.Strict) != 0) + { + if (value <= 0 || value >= 65535) + { + ManifestError(Resources.GetResourceString("EventSource_IllegalTaskValue", name, value)); + } + string prevName; + if (taskTab != null && taskTab.TryGetValue(value, out prevName) && !name.Equals(prevName, StringComparison.Ordinal)) + { + ManifestError(Resources.GetResourceString("EventSource_TaskCollision", name, prevName, value)); + } + } + if (taskTab == null) + taskTab = new Dictionary(); + taskTab[value] = name; + } + public void AddKeyword(string name, ulong value) + { + if ((value & (value - 1)) != 0) // Is it a power of 2? + { + ManifestError(Resources.GetResourceString("EventSource_KeywordNeedPowerOfTwo", "0x" + value.ToString("x", CultureInfo.CurrentCulture), name), true); + } + if ((flags & EventManifestOptions.Strict) != 0) + { + if (value >= 0x0000100000000000UL && !name.StartsWith("Session", StringComparison.Ordinal)) + { + ManifestError(Resources.GetResourceString("EventSource_IllegalKeywordsValue", name, "0x" + value.ToString("x", CultureInfo.CurrentCulture))); + } + string prevName; + if (keywordTab != null && keywordTab.TryGetValue(value, out prevName) && !name.Equals(prevName, StringComparison.Ordinal)) + { + ManifestError(Resources.GetResourceString("EventSource_KeywordCollision", name, prevName, "0x" + value.ToString("x", CultureInfo.CurrentCulture))); + } + } + if (keywordTab == null) + keywordTab = new Dictionary(); + keywordTab[value] = name; + } + +#if FEATURE_MANAGED_ETW_CHANNELS + /// + /// Add a channel. channelAttribute can be null + /// + public void AddChannel(string name, int value, EventChannelAttribute channelAttribute) + { + EventChannel chValue = (EventChannel)value; + if (value < (int)EventChannel.Admin || value > 255) + ManifestError(Resources.GetResourceString("EventSource_EventChannelOutOfRange", name, value)); + else if (chValue >= EventChannel.Admin && chValue <= EventChannel.Debug && + channelAttribute != null && EventChannelToChannelType(chValue) != channelAttribute.EventChannelType) + { + // we want to ensure developers do not define EventChannels that conflict with the builtin ones, + // but we want to allow them to override the default ones... + ManifestError(Resources.GetResourceString("EventSource_ChannelTypeDoesNotMatchEventChannelValue", + name, ((EventChannel)value).ToString())); + } + + // TODO: validate there are no conflicting manifest exposed names (generally following the format "provider/type") + + ulong kwd = GetChannelKeyword(chValue); + + if (channelTab == null) + channelTab = new Dictionary(4); + channelTab[value] = new ChannelInfo { Name = name, Keywords = kwd, Attribs = channelAttribute }; + } + + private EventChannelType EventChannelToChannelType(EventChannel channel) + { +#if !ES_BUILD_STANDALONE + Debug.Assert(channel >= EventChannel.Admin && channel <= EventChannel.Debug); +#endif + return (EventChannelType)((int)channel - (int)EventChannel.Admin + (int)EventChannelType.Admin); + } + private EventChannelAttribute GetDefaultChannelAttribute(EventChannel channel) + { + EventChannelAttribute attrib = new EventChannelAttribute(); + attrib.EventChannelType = EventChannelToChannelType(channel); + if (attrib.EventChannelType <= EventChannelType.Operational) + attrib.Enabled = true; + return attrib; + } + + public ulong[] GetChannelData() + { + if (this.channelTab == null) + { + return new ulong[0]; + } + + // We create an array indexed by the channel id for fast look up. + // E.g. channelMask[Admin] will give you the bit mask for Admin channel. + int maxkey = -1; + foreach (var item in this.channelTab.Keys) + { + if (item > maxkey) + { + maxkey = item; + } + } + + ulong[] channelMask = new ulong[maxkey + 1]; + foreach (var item in this.channelTab) + { + channelMask[item.Key] = item.Value.Keywords; + } + + return channelMask; + } + +#endif + public void StartEvent(string eventName, EventAttribute eventAttribute) + { + Debug.Assert(numParams == 0); + Debug.Assert(this.eventName == null); + this.eventName = eventName; + numParams = 0; + byteArrArgIndices = null; + + events.Append(" ").AppendLine(); + if (type == typeof(byte[])) + { + // mark this index as "extraneous" (it has no parallel in the managed signature) + // we use these values in TranslateToManifestConvention() + if (byteArrArgIndices == null) + byteArrArgIndices = new List(4); + byteArrArgIndices.Add(numParams); + + // add an extra field to the template representing the length of the binary blob + numParams++; + templates.Append(" ").AppendLine(); + } + numParams++; + templates.Append(" (); + if (!mapsTab.ContainsKey(type.Name)) + mapsTab.Add(type.Name, type); // Remember that we need to dump the type enumeration + } + + templates.Append("/>").AppendLine(); + } + public void EndEvent() + { + if (numParams > 0) + { + templates.Append(" ").AppendLine(); + events.Append(" template=\"").Append(eventName).Append("Args\""); + } + events.Append("/>").AppendLine(); + + if (byteArrArgIndices != null) + perEventByteArrayArgIndices[eventName] = byteArrArgIndices; + + // at this point we have all the information we need to translate the C# Message + // to the manifest string we'll put in the stringTab + string msg; + if (stringTab.TryGetValue("event_" + eventName, out msg)) + { + msg = TranslateToManifestConvention(msg, eventName); + stringTab["event_" + eventName] = msg; + } + + eventName = null; + numParams = 0; + byteArrArgIndices = null; + } + +#if FEATURE_MANAGED_ETW_CHANNELS + // Channel keywords are generated one per channel to allow channel based filtering in event viewer. These keywords are autogenerated + // by mc.exe for compiling a manifest and are based on the order of the channels (fields) in the Channels inner class (when advanced + // channel support is enabled), or based on the order the predefined channels appear in the EventAttribute properties (for simple + // support). The manifest generated *MUST* have the channels specified in the same order (that's how our computed keywords are mapped + // to channels by the OS infrastructure). + // If channelKeyworkds is present, and has keywords bits in the ValidPredefinedChannelKeywords then it is + // assumed that that the keyword for that channel should be that bit. + // otherwise we allocate a channel bit for the channel. + // explicit channel bits are only used by WCF to mimic an existing manifest, + // so we don't dont do error checking. + public ulong GetChannelKeyword(EventChannel channel, ulong channelKeyword = 0) + { + // strip off any non-channel keywords, since we are only interested in channels here. + channelKeyword &= ValidPredefinedChannelKeywords; + if (channelTab == null) + { + channelTab = new Dictionary(4); + } + + if (channelTab.Count == MaxCountChannels) + ManifestError(Resources.GetResourceString("EventSource_MaxChannelExceeded")); + + ChannelInfo info; + if (!channelTab.TryGetValue((int)channel, out info)) + { + // If we were not given an explicit channel, allocate one. + if (channelKeyword != 0) + { + channelKeyword = nextChannelKeywordBit; + nextChannelKeywordBit >>= 1; + } + } + else + { + channelKeyword = info.Keywords; + } + + return channelKeyword; + } +#endif + + public byte[] CreateManifest() + { + string str = CreateManifestString(); + return Encoding.UTF8.GetBytes(str); + } + + public IList Errors { get { return errors; } } + + /// + /// When validating an event source it adds the error to the error collection. + /// When not validating it throws an exception if runtimeCritical is "true". + /// Otherwise the error is ignored. + /// + /// + /// + public void ManifestError(string msg, bool runtimeCritical = false) + { + if ((flags & EventManifestOptions.Strict) != 0) + errors.Add(msg); + else if (runtimeCritical) + throw new ArgumentException(msg); + } + + private string CreateManifestString() + { + +#if FEATURE_MANAGED_ETW_CHANNELS + // Write out the channels + if (channelTab != null) + { + sb.Append(" ").AppendLine(); + var sortedChannels = new List>(); + foreach (KeyValuePair p in channelTab) { sortedChannels.Add(p); } + sortedChannels.Sort((p1, p2) => -Comparer.Default.Compare(p1.Value.Keywords, p2.Value.Keywords)); + foreach (var kvpair in sortedChannels) + { + int channel = kvpair.Key; + ChannelInfo channelInfo = kvpair.Value; + + string channelType = null; + string elementName = "channel"; + bool enabled = false; + string fullName = null; +#if FEATURE_ADVANCED_MANAGED_ETW_CHANNELS + string isolation = null; + string access = null; +#endif + if (channelInfo.Attribs != null) + { + var attribs = channelInfo.Attribs; + if (Enum.IsDefined(typeof(EventChannelType), attribs.EventChannelType)) + channelType = attribs.EventChannelType.ToString(); + enabled = attribs.Enabled; +#if FEATURE_ADVANCED_MANAGED_ETW_CHANNELS + if (attribs.ImportChannel != null) + { + fullName = attribs.ImportChannel; + elementName = "importChannel"; + } + if (Enum.IsDefined(typeof(EventChannelIsolation), attribs.Isolation)) + isolation = attribs.Isolation.ToString(); + access = attribs.Access; +#endif + } + if (fullName == null) + fullName = providerName + "/" + channelInfo.Name; + + sb.Append(" <").Append(elementName); + sb.Append(" chid=\"").Append(channelInfo.Name).Append("\""); + sb.Append(" name=\"").Append(fullName).Append("\""); + if (elementName == "channel") // not applicable to importChannels. + { + WriteMessageAttrib(sb, "channel", channelInfo.Name, null); + sb.Append(" value=\"").Append(channel).Append("\""); + if (channelType != null) + sb.Append(" type=\"").Append(channelType).Append("\""); + sb.Append(" enabled=\"").Append(enabled.ToString().ToLower()).Append("\""); +#if FEATURE_ADVANCED_MANAGED_ETW_CHANNELS + if (access != null) + sb.Append(" access=\"").Append(access).Append("\""); + if (isolation != null) + sb.Append(" isolation=\"").Append(isolation).Append("\""); +#endif + } + sb.Append("/>").AppendLine(); + } + sb.Append(" ").AppendLine(); + } +#endif + + // Write out the tasks + if (taskTab != null) + { + + sb.Append(" ").AppendLine(); + var sortedTasks = new List(taskTab.Keys); + sortedTasks.Sort(); + foreach (int task in sortedTasks) + { + sb.Append(" ").AppendLine(); + } + sb.Append(" ").AppendLine(); + } + + // Write out the maps + if (mapsTab != null) + { + sb.Append(" ").AppendLine(); + foreach (Type enumType in mapsTab.Values) + { + bool isbitmap = EventSource.GetCustomAttributeHelper(enumType, typeof(FlagsAttribute), flags) != null; + string mapKind = isbitmap ? "bitMap" : "valueMap"; + sb.Append(" <").Append(mapKind).Append(" name=\"").Append(enumType.Name).Append("\">").AppendLine(); + + // write out each enum value + FieldInfo[] staticFields = enumType.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static); + foreach (FieldInfo staticField in staticFields) + { + object constantValObj = staticField.GetRawConstantValue(); + if (constantValObj != null) + { + long hexValue; + if (constantValObj is int) + hexValue = ((int)constantValObj); + else if (constantValObj is long) + hexValue = ((long)constantValObj); + else + continue; + + // ETW requires all bitmap values to be powers of 2. Skip the ones that are not. + // TODO: Warn people about the dropping of values. + if (isbitmap && ((hexValue & (hexValue - 1)) != 0 || hexValue == 0)) + continue; + + sb.Append(" ").AppendLine(); + } + } + sb.Append(" ").AppendLine(); + } + sb.Append(" ").AppendLine(); + } + + // Write out the opcodes + sb.Append(" ").AppendLine(); + var sortedOpcodes = new List(opcodeTab.Keys); + sortedOpcodes.Sort(); + foreach (int opcode in sortedOpcodes) + { + sb.Append(" ").AppendLine(); + } + sb.Append(" ").AppendLine(); + + // Write out the keywords + if (keywordTab != null) + { + sb.Append(" ").AppendLine(); + var sortedKeywords = new List(keywordTab.Keys); + sortedKeywords.Sort(); + foreach (ulong keyword in sortedKeywords) + { + sb.Append(" ").AppendLine(); + } + sb.Append(" ").AppendLine(); + } + + sb.Append(" ").AppendLine(); + sb.Append(events); + sb.Append(" ").AppendLine(); + + sb.Append(" ").AppendLine(); + if (templates.Length > 0) + { + sb.Append(templates); + } + else + { + // Work around a cornercase ETW issue where a manifest with no templates causes + // ETW events to not get sent to their associated channel. + sb.Append(" ").AppendLine(); + } + sb.Append(" ").AppendLine(); + + sb.Append("").AppendLine(); + sb.Append("").AppendLine(); + sb.Append("").AppendLine(); + + // Output the localization information. + sb.Append("").AppendLine(); + + List cultures = null; + if (resources != null && (flags & EventManifestOptions.AllCultures) != 0) + { + cultures = GetSupportedCultures(resources); + } + else + { + cultures = new List(); + cultures.Add(CultureInfo.CurrentUICulture); + } +#if ES_BUILD_STANDALONE || ES_BUILD_PN + var sortedStrings = new List(stringTab.Keys); + sortedStrings.Sort(); +#else + // DD 947936 + var sortedStrings = new string[stringTab.Keys.Count]; + stringTab.Keys.CopyTo(sortedStrings, 0); + // Avoid using public Array.Sort as that attempts to access BinaryCompatibility. Unfortunately FrameworkEventSource gets called + // very early in the app domain creation, when _FusionStore is not set up yet, resulting in a failure to run the static constructory + // for BinaryCompatibility. This failure is then cached and a TypeInitializationException is thrown every time some code attampts to + // access BinaryCompatibility. + ArraySortHelper.IntrospectiveSort(sortedStrings, 0, sortedStrings.Length, string.Compare); +#endif + foreach (var ci in cultures) + { + sb.Append(" ").AppendLine(); + sb.Append(" ").AppendLine(); + + foreach (var stringKey in sortedStrings) + { + string val = GetLocalizedMessage(stringKey, ci, etwFormat: true); + sb.Append(" ").AppendLine(); + } + sb.Append(" ").AppendLine(); + sb.Append(" ").AppendLine(); + } + sb.Append("").AppendLine(); + sb.AppendLine(""); + return sb.ToString(); + } + + #region private + private void WriteNameAndMessageAttribs(StringBuilder stringBuilder, string elementName, string name) + { + stringBuilder.Append(" name=\"").Append(name).Append("\""); + WriteMessageAttrib(sb, elementName, name, name); + } + private void WriteMessageAttrib(StringBuilder stringBuilder, string elementName, string name, string value) + { + string key = elementName + "_" + name; + // See if the user wants things localized. + if (resources != null) + { + // resource fallback: strings in the neutral culture will take precedence over inline strings + string localizedString = resources.GetString(key, CultureInfo.InvariantCulture); + if (localizedString != null) + value = localizedString; + } + if (value == null) + return; + + stringBuilder.Append(" message=\"$(string.").Append(key).Append(")\""); + string prevValue; + if (stringTab.TryGetValue(key, out prevValue) && !prevValue.Equals(value)) + { + ManifestError(Resources.GetResourceString("EventSource_DuplicateStringKey", key), true); + return; + } + + stringTab[key] = value; + } + internal string GetLocalizedMessage(string key, CultureInfo ci, bool etwFormat) + { + string value = null; + if (resources != null) + { + string localizedString = resources.GetString(key, ci); + if (localizedString != null) + { + value = localizedString; + if (etwFormat && key.StartsWith("event_", StringComparison.Ordinal)) + { + var evtName = key.Substring("event_".Length); + value = TranslateToManifestConvention(value, evtName); + } + } + } + if (etwFormat && value == null) + stringTab.TryGetValue(key, out value); + + return value; + } + + /// + /// There's no API to enumerate all languages an assembly is localized into, so instead + /// we enumerate through all the "known" cultures and attempt to load a corresponding satellite + /// assembly + /// + /// + /// + private static List GetSupportedCultures(ResourceManager resources) + { + var cultures = new List(); + + if (!cultures.Contains(CultureInfo.CurrentUICulture)) + cultures.Insert(0, CultureInfo.CurrentUICulture); + return cultures; + } + + private static string GetLevelName(EventLevel level) + { + return (((int)level >= 16) ? "" : "win:") + level.ToString(); + } + +#if FEATURE_MANAGED_ETW_CHANNELS + private string GetChannelName(EventChannel channel, string eventName, string eventMessage) + { + ChannelInfo info = null; + if (channelTab == null || !channelTab.TryGetValue((int)channel, out info)) + { + if (channel < EventChannel.Admin) // || channel > EventChannel.Debug) + ManifestError(Resources.GetResourceString("EventSource_UndefinedChannel", channel, eventName)); + + // allow channels to be auto-defined. The well known ones get their well known names, and the + // rest get names Channel. This allows users to modify the Manifest if they want more advanced features. + if (channelTab == null) + channelTab = new Dictionary(4); + + string channelName = channel.ToString(); // For well know channels this is a nice name, otherwise a number + if (EventChannel.Debug < channel) + channelName = "Channel" + channelName; // Add a 'Channel' prefix for numbers. + + AddChannel(channelName, (int)channel, GetDefaultChannelAttribute(channel)); + if (!channelTab.TryGetValue((int)channel, out info)) + ManifestError(Resources.GetResourceString("EventSource_UndefinedChannel", channel, eventName)); + } + // events that specify admin channels *must* have non-null "Message" attributes + if (resources != null && eventMessage == null) + eventMessage = resources.GetString("event_" + eventName, CultureInfo.InvariantCulture); + if (info.Attribs.EventChannelType == EventChannelType.Admin && eventMessage == null) + ManifestError(Resources.GetResourceString("EventSource_EventWithAdminChannelMustHaveMessage", eventName, info.Name)); + return info.Name; + } +#endif + private string GetTaskName(EventTask task, string eventName) + { + if (task == EventTask.None) + return ""; + + string ret; + if (taskTab == null) + taskTab = new Dictionary(); + if (!taskTab.TryGetValue((int)task, out ret)) + ret = taskTab[(int)task] = eventName; + return ret; + } + + private string GetOpcodeName(EventOpcode opcode, string eventName) + { + switch (opcode) + { + case EventOpcode.Info: + return "win:Info"; + case EventOpcode.Start: + return "win:Start"; + case EventOpcode.Stop: + return "win:Stop"; + case EventOpcode.DataCollectionStart: + return "win:DC_Start"; + case EventOpcode.DataCollectionStop: + return "win:DC_Stop"; + case EventOpcode.Extension: + return "win:Extension"; + case EventOpcode.Reply: + return "win:Reply"; + case EventOpcode.Resume: + return "win:Resume"; + case EventOpcode.Suspend: + return "win:Suspend"; + case EventOpcode.Send: + return "win:Send"; + case EventOpcode.Receive: + return "win:Receive"; + } + + string ret; + if (opcodeTab == null || !opcodeTab.TryGetValue((int)opcode, out ret)) + { + ManifestError(Resources.GetResourceString("EventSource_UndefinedOpcode", opcode, eventName), true); + ret = null; + } + return ret; + } + + private string GetKeywords(ulong keywords, string eventName) + { +#if FEATURE_MANAGED_ETW_CHANNELS + // ignore keywords associate with channels + // See ValidPredefinedChannelKeywords def for more. + keywords &= ~ValidPredefinedChannelKeywords; +#endif + + string ret = ""; + for (ulong bit = 1; bit != 0; bit <<= 1) + { + if ((keywords & bit) != 0) + { + string keyword = null; + if ((keywordTab == null || !keywordTab.TryGetValue(bit, out keyword)) && + (bit >= (ulong)0x1000000000000)) + { + // do not report Windows reserved keywords in the manifest (this allows the code + // to be resilient to potential renaming of these keywords) + keyword = string.Empty; + } + if (keyword == null) + { + ManifestError(Resources.GetResourceString("EventSource_UndefinedKeyword", "0x" + bit.ToString("x", CultureInfo.CurrentCulture), eventName), true); + keyword = string.Empty; + } + if (ret.Length != 0 && keyword.Length != 0) + ret = ret + " "; + ret = ret + keyword; + } + } + return ret; + } + + private string GetTypeName(Type type) + { + if (type.IsEnum()) + { + FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); + var typeName = GetTypeName(fields[0].FieldType); + return typeName.Replace("win:Int", "win:UInt"); // ETW requires enums to be unsigned. + } + + return GetTypeNameHelper(type); + } + + private static void UpdateStringBuilder(ref StringBuilder stringBuilder, string eventMessage, int startIndex, int count) + { + if (stringBuilder == null) + stringBuilder = new StringBuilder(); + stringBuilder.Append(eventMessage, startIndex, count); + } + + private static readonly string[] s_escapes = { "&", "<", ">", "'", """, "%r", "%n", "%t" }; + // Manifest messages use %N conventions for their message substitutions. Translate from + // .NET conventions. We can't use RegEx for this (we are in mscorlib), so we do it 'by hand' + private string TranslateToManifestConvention(string eventMessage, string evtName) + { + StringBuilder stringBuilder = null; // We lazily create this + int writtenSoFar = 0; + int chIdx = -1; + for (int i = 0; ;) + { + if (i >= eventMessage.Length) + { + if (stringBuilder == null) + return eventMessage; + UpdateStringBuilder(ref stringBuilder, eventMessage, writtenSoFar, i - writtenSoFar); + return stringBuilder.ToString(); + } + + if (eventMessage[i] == '%') + { + // handle format message escaping character '%' by escaping it + UpdateStringBuilder(ref stringBuilder, eventMessage, writtenSoFar, i - writtenSoFar); + stringBuilder.Append("%%"); + i++; + writtenSoFar = i; + } + else if (i < eventMessage.Length - 1 && + (eventMessage[i] == '{' && eventMessage[i + 1] == '{' || eventMessage[i] == '}' && eventMessage[i + 1] == '}')) + { + // handle C# escaped '{" and '}' + UpdateStringBuilder(ref stringBuilder, eventMessage, writtenSoFar, i - writtenSoFar); + stringBuilder.Append(eventMessage[i]); + i++; i++; + writtenSoFar = i; + } + else if (eventMessage[i] == '{') + { + int leftBracket = i; + i++; + int argNum = 0; + while (i < eventMessage.Length && Char.IsDigit(eventMessage[i])) + { + argNum = argNum * 10 + eventMessage[i] - '0'; + i++; + } + if (i < eventMessage.Length && eventMessage[i] == '}') + { + i++; + UpdateStringBuilder(ref stringBuilder, eventMessage, writtenSoFar, leftBracket - writtenSoFar); + int manIndex = TranslateIndexToManifestConvention(argNum, evtName); + stringBuilder.Append('%').Append(manIndex); + // An '!' after the insert specifier {n} will be interpreted as a literal. + // We'll escape it so that mc.exe does not attempt to consider it the + // beginning of a format string. + if (i < eventMessage.Length && eventMessage[i] == '!') + { + i++; + stringBuilder.Append("%!"); + } + writtenSoFar = i; + } + else + { + ManifestError(Resources.GetResourceString("EventSource_UnsupportedMessageProperty", evtName, eventMessage)); + } + } + else if ((chIdx = "&<>'\"\r\n\t".IndexOf(eventMessage[i])) >= 0) + { + UpdateStringBuilder(ref stringBuilder, eventMessage, writtenSoFar, i - writtenSoFar); + i++; + stringBuilder.Append(s_escapes[chIdx]); + writtenSoFar = i; + } + else + i++; + } + } + + private int TranslateIndexToManifestConvention(int idx, string evtName) + { + List byteArrArgIndices; + if (perEventByteArrayArgIndices.TryGetValue(evtName, out byteArrArgIndices)) + { + foreach (var byArrIdx in byteArrArgIndices) + { + if (idx >= byArrIdx) + ++idx; + else + break; + } + } + return idx + 1; + } + +#if FEATURE_MANAGED_ETW_CHANNELS + class ChannelInfo + { + public string Name; + public ulong Keywords; + public EventChannelAttribute Attribs; + } +#endif + + Dictionary opcodeTab; + Dictionary taskTab; +#if FEATURE_MANAGED_ETW_CHANNELS + Dictionary channelTab; +#endif + Dictionary keywordTab; + Dictionary mapsTab; + + Dictionary stringTab; // Maps unlocalized strings to localized ones + +#if FEATURE_MANAGED_ETW_CHANNELS + // WCF used EventSource to mimic a existing ETW manifest. To support this + // in just their case, we allowed them to specify the keywords associated + // with their channels explicitly. ValidPredefinedChannelKeywords is + // this set of channel keywords that we allow to be explicitly set. You + // can ignore these bits otherwise. + internal const ulong ValidPredefinedChannelKeywords = 0xF000000000000000; + ulong nextChannelKeywordBit = 0x8000000000000000; // available Keyword bit to be used for next channel definition, grows down + const int MaxCountChannels = 8; // a manifest can defined at most 8 ETW channels +#endif + + StringBuilder sb; // Holds the provider information. + StringBuilder events; // Holds the events. + StringBuilder templates; + +#if FEATURE_MANAGED_ETW_CHANNELS + string providerName; +#endif + ResourceManager resources; // Look up localized strings here. + EventManifestOptions flags; + IList errors; // list of currently encountered errors + Dictionary> perEventByteArrayArgIndices; // "event_name" -> List_of_Indices_of_Byte[]_Arg + + // State we track between StartEvent and EndEvent. + string eventName; // Name of the event currently being processed. + int numParams; // keeps track of the number of args the event has. + List byteArrArgIndices; // keeps track of the index of each byte[] argument + #endregion + } + + /// + /// Used to send the m_rawManifest into the event dispatcher as a series of events. + /// + internal struct ManifestEnvelope + { + public const int MaxChunkSize = 0xFF00; + public enum ManifestFormats : byte + { + SimpleXmlFormat = 1, // simply dump the XML manifest as UTF8 + } + +#if FEATURE_MANAGED_ETW + public ManifestFormats Format; + public byte MajorVersion; + public byte MinorVersion; + public byte Magic; + public ushort TotalChunks; + public ushort ChunkNumber; +#endif + }; + + #endregion +} + diff --git a/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventSourceException.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventSourceException.cs new file mode 100644 index 0000000..89d3ee55 --- /dev/null +++ b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/EventSourceException.cs @@ -0,0 +1,53 @@ +// 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.Serialization; + +#if ES_BUILD_STANDALONE +using Environment = Microsoft.Diagnostics.Tracing.Internal.Environment; +#endif + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + /// + /// Exception that is thrown when an error occurs during EventSource operation. + /// +#if (!ES_BUILD_PCL && !ES_BUILD_PN) + [Serializable] +#endif + public class EventSourceException : Exception + { + /// + /// Initializes a new instance of the EventSourceException class. + /// + public EventSourceException() : + base(Resources.GetResourceString("EventSource_ListenerWriteFailure")) { } + + /// + /// Initializes a new instance of the EventSourceException class with a specified error message. + /// + public EventSourceException(string message) : base(message) { } + + /// + /// Initializes a new instance of the EventSourceException class with a specified error message + /// and a reference to the inner exception that is the cause of this exception. + /// + public EventSourceException(string message, Exception innerException) : base(message, innerException) { } + +#if (!ES_BUILD_PCL && !ES_BUILD_PN) + /// + /// Initializes a new instance of the EventSourceException class with serialized data. + /// + protected EventSourceException(SerializationInfo info, StreamingContext context) : base(info, context) { } +#endif + + internal EventSourceException(Exception innerException) : + base(Resources.GetResourceString("EventSource_ListenerWriteFailure"), innerException) { } + } +} diff --git a/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/FrameworkEventSource.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/FrameworkEventSource.cs new file mode 100644 index 0000000..80c524b --- /dev/null +++ b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/FrameworkEventSource.cs @@ -0,0 +1,662 @@ +// 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. + +// +// ResourcesEtwProvider.cs +// +// +// Managed event source for things that can version with MSCORLIB. +// +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.Text; +using System.Runtime.CompilerServices; + +namespace System.Diagnostics.Tracing { + + // To use the framework provider + // + // \\clrmain\tools\Perfmonitor /nokernel /noclr /provider:8E9F5090-2D75-4d03-8A81-E5AFBF85DAF1 start + // Run run your app + // \\clrmain\tools\Perfmonitor stop + // \\clrmain\tools\Perfmonitor print + // + // This will produce an XML file, where each event is pretty-printed with all its arguments nicely parsed. + // + [FriendAccessAllowed] + [EventSource(Guid = "8E9F5090-2D75-4d03-8A81-E5AFBF85DAF1", Name = "System.Diagnostics.Eventing.FrameworkEventSource")] + sealed internal class FrameworkEventSource : EventSource { + // Defines the singleton instance for the Resources ETW provider + public static readonly FrameworkEventSource Log = new FrameworkEventSource(); + + // Keyword definitions. These represent logical groups of events that can be turned on and off independently + // Often each task has a keyword, but where tasks are determined by subsystem, keywords are determined by + // usefulness to end users to filter. Generally users don't mind extra events if they are not high volume + // so grouping low volume events together in a single keywords is OK (users can post-filter by task if desired) + public static class Keywords { + public const EventKeywords Loader = (EventKeywords)0x0001; // This is bit 0 + public const EventKeywords ThreadPool = (EventKeywords)0x0002; + public const EventKeywords NetClient = (EventKeywords)0x0004; + // + // This is a private event we do not want to expose to customers. It is to be used for profiling + // uses of dynamic type loading by ProjectN applications running on the desktop CLR + // + public const EventKeywords DynamicTypeUsage = (EventKeywords)0x0008; + public const EventKeywords ThreadTransfer = (EventKeywords)0x0010; + } + + /// ETW tasks that have start/stop events. + [FriendAccessAllowed] + public static class Tasks // this name is important for EventSource + { + /// Begin / End - GetResponse. + public const EventTask GetResponse = (EventTask)1; + /// Begin / End - GetRequestStream + public const EventTask GetRequestStream = (EventTask)2; + /// Send / Receive - begin transfer/end transfer + public const EventTask ThreadTransfer = (EventTask)3; + } + + [FriendAccessAllowed] + public static class Opcodes + { + public const EventOpcode ReceiveHandled = (EventOpcode)11; + } + + // This predicate is used by consumers of this class to deteremine if the class has actually been initialized, + // and therefore if the public statics are available for use. This is typically not a problem... if the static + // class constructor fails, then attempts to access the statics (or even this property) will result in a + // TypeInitializationException. However, that is not the case while the class loader is actually trying to construct + // the TypeInitializationException instance to represent that failure, and some consumers of this class are on + // that code path, specifically the resource manager. + public static bool IsInitialized + { + get + { + return Log != null; + } + } + + // The FrameworkEventSource GUID is {8E9F5090-2D75-4d03-8A81-E5AFBF85DAF1} + private FrameworkEventSource() : base(new Guid(0x8e9f5090, 0x2d75, 0x4d03, 0x8a, 0x81, 0xe5, 0xaf, 0xbf, 0x85, 0xda, 0xf1), "System.Diagnostics.Eventing.FrameworkEventSource") { } + + // WriteEvent overloads (to avoid the "params" EventSource.WriteEvent + + // optimized for common signatures (used by the ThreadTransferSend/Receive events) + [NonEvent] +#if !CORECLR + [System.Security.SecuritySafeCritical] +#endif // !CORECLR + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + private unsafe void WriteEvent(int eventId, long arg1, int arg2, string arg3, bool arg4) + { + if (IsEnabled()) + { + if (arg3 == null) arg3 = ""; + fixed (char* string3Bytes = arg3) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[4]; + descrs[0].DataPointer = (IntPtr)(&arg1); + descrs[0].Size = 8; + descrs[1].DataPointer = (IntPtr)(&arg2); + descrs[1].Size = 4; + descrs[2].DataPointer = (IntPtr)string3Bytes; + descrs[2].Size = ((arg3.Length + 1) * 2); + descrs[3].DataPointer = (IntPtr)(&arg4); + descrs[3].Size = 4; + WriteEventCore(eventId, 4, descrs); + } + } + } + + // optimized for common signatures (used by the ThreadTransferSend/Receive events) + [NonEvent] +#if !CORECLR + [System.Security.SecuritySafeCritical] +#endif // !CORECLR + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + private unsafe void WriteEvent(int eventId, long arg1, int arg2, string arg3) + { + if (IsEnabled()) + { + if (arg3 == null) arg3 = ""; + fixed (char* string3Bytes = arg3) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[3]; + descrs[0].DataPointer = (IntPtr)(&arg1); + descrs[0].Size = 8; + descrs[1].DataPointer = (IntPtr)(&arg2); + descrs[1].Size = 4; + descrs[2].DataPointer = (IntPtr)string3Bytes; + descrs[2].Size = ((arg3.Length + 1) * 2); + WriteEventCore(eventId, 3, descrs); + } + } + } + + // optimized for common signatures (used by the BeginGetResponse/BeginGetRequestStream events) + [NonEvent] +#if !CORECLR + [System.Security.SecuritySafeCritical] +#endif // !CORECLR + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + private unsafe void WriteEvent(int eventId, long arg1, string arg2, bool arg3, bool arg4) + { + if (IsEnabled()) + { + if (arg2 == null) arg2 = ""; + fixed (char* string2Bytes = arg2) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[4]; + descrs[0].DataPointer = (IntPtr)(&arg1); + descrs[0].Size = 8; + descrs[1].DataPointer = (IntPtr)string2Bytes; + descrs[1].Size = ((arg2.Length + 1) * 2); + descrs[2].DataPointer = (IntPtr)(&arg3); + descrs[2].Size = 4; + descrs[3].DataPointer = (IntPtr)(&arg4); + descrs[3].Size = 4; + WriteEventCore(eventId, 4, descrs); + } + } + } + + // optimized for common signatures (used by the EndGetRequestStream event) + [NonEvent] +#if !CORECLR + [System.Security.SecuritySafeCritical] +#endif // !CORECLR + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + private unsafe void WriteEvent(int eventId, long arg1, bool arg2, bool arg3) + { + if (IsEnabled()) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[3]; + descrs[0].DataPointer = (IntPtr)(&arg1); + descrs[0].Size = 8; + descrs[1].DataPointer = (IntPtr)(&arg2); + descrs[1].Size = 4; + descrs[2].DataPointer = (IntPtr)(&arg3); + descrs[2].Size = 4; + WriteEventCore(eventId, 3, descrs); + } + } + + // optimized for common signatures (used by the EndGetResponse event) + [NonEvent] +#if !CORECLR + [System.Security.SecuritySafeCritical] +#endif // !CORECLR + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "This does not need to be correct when racing with other threads")] + private unsafe void WriteEvent(int eventId, long arg1, bool arg2, bool arg3, int arg4) + { + if (IsEnabled()) + { + EventSource.EventData* descrs = stackalloc EventSource.EventData[4]; + descrs[0].DataPointer = (IntPtr)(&arg1); + descrs[0].Size = 8; + descrs[1].DataPointer = (IntPtr)(&arg2); + descrs[1].Size = 4; + descrs[2].DataPointer = (IntPtr)(&arg3); + descrs[2].Size = 4; + descrs[3].DataPointer = (IntPtr)(&arg4); + descrs[3].Size = 4; + WriteEventCore(eventId, 4, descrs); + } + } + + // ResourceManager Event Definitions + + [Event(1, Level = EventLevel.Informational, Keywords = Keywords.Loader)] + public void ResourceManagerLookupStarted(String baseName, String mainAssemblyName, String cultureName) { + WriteEvent(1, baseName, mainAssemblyName, cultureName); + } + + [Event(2, Level = EventLevel.Informational, Keywords = Keywords.Loader)] + public void ResourceManagerLookingForResourceSet(String baseName, String mainAssemblyName, String cultureName) { + if (IsEnabled()) + WriteEvent(2, baseName, mainAssemblyName, cultureName); + } + + [Event(3, Level = EventLevel.Informational, Keywords = Keywords.Loader)] + public void ResourceManagerFoundResourceSetInCache(String baseName, String mainAssemblyName, String cultureName) { + if (IsEnabled()) + WriteEvent(3, baseName, mainAssemblyName, cultureName); + } + + // After loading a satellite assembly, we already have the ResourceSet for this culture in + // the cache. This can happen if you have an assembly load callback that called into this + // instance of the ResourceManager. + [Event(4, Level = EventLevel.Warning, Keywords = Keywords.Loader)] + public void ResourceManagerFoundResourceSetInCacheUnexpected(String baseName, String mainAssemblyName, String cultureName) { + if (IsEnabled()) + WriteEvent(4, baseName, mainAssemblyName, cultureName); + } + + // manifest resource stream lookup succeeded + [Event(5, Level = EventLevel.Informational, Keywords = Keywords.Loader)] + public void ResourceManagerStreamFound(String baseName, String mainAssemblyName, String cultureName, String loadedAssemblyName, String resourceFileName) { + if (IsEnabled()) + WriteEvent(5, baseName, mainAssemblyName, cultureName, loadedAssemblyName, resourceFileName); + } + + // manifest resource stream lookup failed + [Event(6, Level = EventLevel.Warning, Keywords = Keywords.Loader)] + public void ResourceManagerStreamNotFound(String baseName, String mainAssemblyName, String cultureName, String loadedAssemblyName, String resourceFileName) { + if (IsEnabled()) + WriteEvent(6, baseName, mainAssemblyName, cultureName, loadedAssemblyName, resourceFileName); + } + + [Event(7, Level = EventLevel.Informational, Keywords = Keywords.Loader)] + public void ResourceManagerGetSatelliteAssemblySucceeded(String baseName, String mainAssemblyName, String cultureName, String assemblyName) { + if (IsEnabled()) + WriteEvent(7, baseName, mainAssemblyName, cultureName, assemblyName); + } + + [Event(8, Level = EventLevel.Warning, Keywords = Keywords.Loader)] + public void ResourceManagerGetSatelliteAssemblyFailed(String baseName, String mainAssemblyName, String cultureName, String assemblyName) { + if (IsEnabled()) + WriteEvent(8, baseName, mainAssemblyName, cultureName, assemblyName); + } + + [Event(9, Level = EventLevel.Informational, Keywords = Keywords.Loader)] + public void ResourceManagerCaseInsensitiveResourceStreamLookupSucceeded(String baseName, String mainAssemblyName, String assemblyName, String resourceFileName) { + if (IsEnabled()) + WriteEvent(9, baseName, mainAssemblyName, assemblyName, resourceFileName); + } + + [Event(10, Level = EventLevel.Warning, Keywords = Keywords.Loader)] + public void ResourceManagerCaseInsensitiveResourceStreamLookupFailed(String baseName, String mainAssemblyName, String assemblyName, String resourceFileName) { + if (IsEnabled()) + WriteEvent(10, baseName, mainAssemblyName, assemblyName, resourceFileName); + } + + // Could not access the manifest resource the assembly + [Event(11, Level = EventLevel.Error, Keywords = Keywords.Loader)] + public void ResourceManagerManifestResourceAccessDenied(String baseName, String mainAssemblyName, String assemblyName, String canonicalName) { + if (IsEnabled()) + WriteEvent(11, baseName, mainAssemblyName, assemblyName, canonicalName); + } + + // Neutral resources are sufficient for this culture. Skipping satellites + [Event(12, Level = EventLevel.Informational, Keywords = Keywords.Loader)] + public void ResourceManagerNeutralResourcesSufficient(String baseName, String mainAssemblyName, String cultureName) { + if (IsEnabled()) + WriteEvent(12, baseName, mainAssemblyName, cultureName); + } + + [Event(13, Level = EventLevel.Warning, Keywords = Keywords.Loader)] + public void ResourceManagerNeutralResourceAttributeMissing(String mainAssemblyName) { + if (IsEnabled()) + WriteEvent(13, mainAssemblyName); + } + + [Event(14, Level = EventLevel.Informational, Keywords = Keywords.Loader)] + public void ResourceManagerCreatingResourceSet(String baseName, String mainAssemblyName, String cultureName, String fileName) { + if (IsEnabled()) + WriteEvent(14, baseName, mainAssemblyName, cultureName, fileName); + } + + [Event(15, Level = EventLevel.Informational, Keywords = Keywords.Loader)] + public void ResourceManagerNotCreatingResourceSet(String baseName, String mainAssemblyName, String cultureName) { + if (IsEnabled()) + WriteEvent(15, baseName, mainAssemblyName, cultureName); + } + + [Event(16, Level = EventLevel.Warning, Keywords = Keywords.Loader)] + public void ResourceManagerLookupFailed(String baseName, String mainAssemblyName, String cultureName) { + if (IsEnabled()) + WriteEvent(16, baseName, mainAssemblyName, cultureName); + } + + [Event(17, Level = EventLevel.Informational, Keywords = Keywords.Loader)] + public void ResourceManagerReleasingResources(String baseName, String mainAssemblyName) { + if (IsEnabled()) + WriteEvent(17, baseName, mainAssemblyName); + } + + [Event(18, Level = EventLevel.Warning, Keywords = Keywords.Loader)] + public void ResourceManagerNeutralResourcesNotFound(String baseName, String mainAssemblyName, String resName) { + if (IsEnabled()) + WriteEvent(18, baseName, mainAssemblyName, resName); + } + + [Event(19, Level = EventLevel.Informational, Keywords = Keywords.Loader)] + public void ResourceManagerNeutralResourcesFound(String baseName, String mainAssemblyName, String resName) { + if (IsEnabled()) + WriteEvent(19, baseName, mainAssemblyName, resName); + } + + [Event(20, Level = EventLevel.Informational, Keywords = Keywords.Loader)] + public void ResourceManagerAddingCultureFromConfigFile(String baseName, String mainAssemblyName, String cultureName) { + if (IsEnabled()) + WriteEvent(20, baseName, mainAssemblyName, cultureName); + } + + [Event(21, Level = EventLevel.Informational, Keywords = Keywords.Loader)] + public void ResourceManagerCultureNotFoundInConfigFile(String baseName, String mainAssemblyName, String cultureName) { + if (IsEnabled()) + WriteEvent(21, baseName, mainAssemblyName, cultureName); + } + + [Event(22, Level = EventLevel.Informational, Keywords = Keywords.Loader)] + public void ResourceManagerCultureFoundInConfigFile(String baseName, String mainAssemblyName, String cultureName) { + if (IsEnabled()) + WriteEvent(22, baseName, mainAssemblyName, cultureName); + } + + + // ResourceManager Event Wrappers + + [NonEvent] + public void ResourceManagerLookupStarted(String baseName, Assembly mainAssembly, String cultureName) { + if (IsEnabled()) + ResourceManagerLookupStarted(baseName, GetName(mainAssembly), cultureName); + } + + [NonEvent] + public void ResourceManagerLookingForResourceSet(String baseName, Assembly mainAssembly, String cultureName) { + if (IsEnabled()) + ResourceManagerLookingForResourceSet(baseName, GetName(mainAssembly), cultureName); + } + + [NonEvent] + public void ResourceManagerFoundResourceSetInCache(String baseName, Assembly mainAssembly, String cultureName) { + if (IsEnabled()) + ResourceManagerFoundResourceSetInCache(baseName, GetName(mainAssembly), cultureName); + } + + [NonEvent] + public void ResourceManagerFoundResourceSetInCacheUnexpected(String baseName, Assembly mainAssembly, String cultureName) { + if (IsEnabled()) + ResourceManagerFoundResourceSetInCacheUnexpected(baseName, GetName(mainAssembly), cultureName); + } + + [NonEvent] + public void ResourceManagerStreamFound(String baseName, Assembly mainAssembly, String cultureName, Assembly loadedAssembly, String resourceFileName) { + if (IsEnabled()) + ResourceManagerStreamFound(baseName, GetName(mainAssembly), cultureName, GetName(loadedAssembly), resourceFileName); + } + + [NonEvent] + public void ResourceManagerStreamNotFound(String baseName, Assembly mainAssembly, String cultureName, Assembly loadedAssembly, String resourceFileName) { + if (IsEnabled()) + ResourceManagerStreamNotFound(baseName, GetName(mainAssembly), cultureName, GetName(loadedAssembly), resourceFileName); + } + + [NonEvent] + public void ResourceManagerGetSatelliteAssemblySucceeded(String baseName, Assembly mainAssembly, String cultureName, String assemblyName) { + if (IsEnabled()) + ResourceManagerGetSatelliteAssemblySucceeded(baseName, GetName(mainAssembly), cultureName, assemblyName); + } + + [NonEvent] + public void ResourceManagerGetSatelliteAssemblyFailed(String baseName, Assembly mainAssembly, String cultureName, String assemblyName) { + if (IsEnabled()) + ResourceManagerGetSatelliteAssemblyFailed(baseName, GetName(mainAssembly), cultureName, assemblyName); + } + + [NonEvent] + public void ResourceManagerCaseInsensitiveResourceStreamLookupSucceeded(String baseName, Assembly mainAssembly, String assemblyName, String resourceFileName) { + if (IsEnabled()) + ResourceManagerCaseInsensitiveResourceStreamLookupSucceeded(baseName, GetName(mainAssembly), assemblyName, resourceFileName); + } + + [NonEvent] + public void ResourceManagerCaseInsensitiveResourceStreamLookupFailed(String baseName, Assembly mainAssembly, String assemblyName, String resourceFileName) { + if (IsEnabled()) + ResourceManagerCaseInsensitiveResourceStreamLookupFailed(baseName, GetName(mainAssembly), assemblyName, resourceFileName); + } + + [NonEvent] + public void ResourceManagerManifestResourceAccessDenied(String baseName, Assembly mainAssembly, String assemblyName, String canonicalName) { + if (IsEnabled()) + ResourceManagerManifestResourceAccessDenied(baseName, GetName(mainAssembly), assemblyName, canonicalName); + } + + [NonEvent] + public void ResourceManagerNeutralResourcesSufficient(String baseName, Assembly mainAssembly, String cultureName) { + if (IsEnabled()) + ResourceManagerNeutralResourcesSufficient(baseName, GetName(mainAssembly), cultureName); + } + + [NonEvent] + public void ResourceManagerNeutralResourceAttributeMissing(Assembly mainAssembly) { + if (IsEnabled()) + ResourceManagerNeutralResourceAttributeMissing(GetName(mainAssembly)); + } + + [NonEvent] + public void ResourceManagerCreatingResourceSet(String baseName, Assembly mainAssembly, String cultureName, String fileName) { + if (IsEnabled()) + ResourceManagerCreatingResourceSet(baseName, GetName(mainAssembly), cultureName, fileName); + } + + [NonEvent] + public void ResourceManagerNotCreatingResourceSet(String baseName, Assembly mainAssembly, String cultureName) { + if (IsEnabled()) + ResourceManagerNotCreatingResourceSet(baseName, GetName(mainAssembly), cultureName); + } + + [NonEvent] + public void ResourceManagerLookupFailed(String baseName, Assembly mainAssembly, String cultureName) { + if (IsEnabled()) + ResourceManagerLookupFailed(baseName, GetName(mainAssembly), cultureName); + } + + [NonEvent] + public void ResourceManagerReleasingResources(String baseName, Assembly mainAssembly) { + if (IsEnabled()) + ResourceManagerReleasingResources(baseName, GetName(mainAssembly)); + } + + [NonEvent] + public void ResourceManagerNeutralResourcesNotFound(String baseName, Assembly mainAssembly, String resName) { + if (IsEnabled()) + ResourceManagerNeutralResourcesNotFound(baseName, GetName(mainAssembly), resName); + } + + [NonEvent] + public void ResourceManagerNeutralResourcesFound(String baseName, Assembly mainAssembly, String resName) { + if (IsEnabled()) + ResourceManagerNeutralResourcesFound(baseName, GetName(mainAssembly), resName); + } + + [NonEvent] + public void ResourceManagerAddingCultureFromConfigFile(String baseName, Assembly mainAssembly, String cultureName) { + if (IsEnabled()) + ResourceManagerAddingCultureFromConfigFile(baseName, GetName(mainAssembly), cultureName); + } + + [NonEvent] + public void ResourceManagerCultureNotFoundInConfigFile(String baseName, Assembly mainAssembly, String cultureName) { + if (IsEnabled()) + ResourceManagerCultureNotFoundInConfigFile(baseName, GetName(mainAssembly), cultureName); + } + + [NonEvent] + public void ResourceManagerCultureFoundInConfigFile(String baseName, Assembly mainAssembly, String cultureName) { + if (IsEnabled()) + ResourceManagerCultureFoundInConfigFile(baseName, GetName(mainAssembly), cultureName); + } + + private static string GetName(Assembly assembly) { + if (assembly == null) + return "<>"; + else + return assembly.FullName; + } + + [Event(30, Level = EventLevel.Verbose, Keywords = Keywords.ThreadPool|Keywords.ThreadTransfer)] + public void ThreadPoolEnqueueWork(long workID) { + WriteEvent(30, workID); + } + [NonEvent] +#if !CORECLR + [System.Security.SecuritySafeCritical] +#endif // !CORECLR + public unsafe void ThreadPoolEnqueueWorkObject(object workID) { + // convert the Object Id to a long + ThreadPoolEnqueueWork((long) *((void**) JitHelpers.UnsafeCastToStackPointer(ref workID))); + } + + [Event(31, Level = EventLevel.Verbose, Keywords = Keywords.ThreadPool|Keywords.ThreadTransfer)] + public void ThreadPoolDequeueWork(long workID) { + WriteEvent(31, workID); + } + + [NonEvent] +#if !CORECLR + [System.Security.SecuritySafeCritical] +#endif // !CORECLR + public unsafe void ThreadPoolDequeueWorkObject(object workID) { + // convert the Object Id to a long + ThreadPoolDequeueWork((long) *((void**) JitHelpers.UnsafeCastToStackPointer(ref workID))); + } + + // In the desktop runtime they don't use Tasks for the point at which the response happens, which means that the + // Activity ID created by start using implicit activity IDs does not match. Thus disable implicit activities (until we fix that) + [Event(140, Level = EventLevel.Informational, Keywords = Keywords.NetClient, ActivityOptions=EventActivityOptions.Disable, + Task = Tasks.GetResponse, Opcode = EventOpcode.Start, Version = 1)] + private void GetResponseStart(long id, string uri, bool success, bool synchronous) { + WriteEvent(140, id, uri, success, synchronous); + } + + [Event(141, Level = EventLevel.Informational, Keywords = Keywords.NetClient, ActivityOptions=EventActivityOptions.Disable, + Task = Tasks.GetResponse, Opcode = EventOpcode.Stop, Version = 1)] + private void GetResponseStop(long id, bool success, bool synchronous, int statusCode) { + WriteEvent(141, id, success, synchronous, statusCode); + } + + // In the desktop runtime they don't use Tasks for the point at which the response happens, which means that the + // Activity ID created by start using implicit activity IDs does not match. Thus disable implicit activities (until we fix that) + [Event(142, Level = EventLevel.Informational, Keywords = Keywords.NetClient, ActivityOptions=EventActivityOptions.Disable, + Task = Tasks.GetRequestStream, Opcode = EventOpcode.Start, Version = 1)] + private void GetRequestStreamStart(long id, string uri, bool success, bool synchronous) { + WriteEvent(142, id, uri, success, synchronous); + } + + [Event(143, Level = EventLevel.Informational, Keywords = Keywords.NetClient, ActivityOptions=EventActivityOptions.Disable, + Task = Tasks.GetRequestStream, Opcode = EventOpcode.Stop, Version = 1)] + private void GetRequestStreamStop(long id, bool success, bool synchronous) { + WriteEvent(143, id, success, synchronous); + } + + [NonEvent] +#if !CORECLR + [System.Security.SecuritySafeCritical] +#endif // !CORECLR + public unsafe void BeginGetResponse(object id, string uri, bool success, bool synchronous) { + if (IsEnabled()) + GetResponseStart(IdForObject(id), uri, success, synchronous); + } + + [NonEvent] +#if !CORECLR + [System.Security.SecuritySafeCritical] +#endif // !CORECLR + public unsafe void EndGetResponse(object id, bool success, bool synchronous, int statusCode) { + if (IsEnabled()) + GetResponseStop(IdForObject(id), success, synchronous, statusCode); + } + + [NonEvent] +#if !CORECLR + [System.Security.SecuritySafeCritical] +#endif // !CORECLR + public unsafe void BeginGetRequestStream(object id, string uri, bool success, bool synchronous) { + if (IsEnabled()) + GetRequestStreamStart(IdForObject(id), uri, success, synchronous); + } + + [NonEvent] +#if !CORECLR + [System.Security.SecuritySafeCritical] +#endif // !CORECLR + public unsafe void EndGetRequestStream(object id, bool success, bool synchronous) { + if (IsEnabled()) + GetRequestStreamStop(IdForObject(id), success, synchronous); + } + + // id - represents a correlation ID that allows correlation of two activities, one stamped by + // ThreadTransferSend, the other by ThreadTransferReceive + // kind - identifies the transfer: values below 64 are reserved for the runtime. Currently used values: + // 1 - managed Timers ("roaming" ID) + // 2 - managed async IO operations (FileStream, PipeStream, a.o.) + // 3 - WinRT dispatch operations + // info - any additional information user code might consider interesting + [Event(150, Level = EventLevel.Informational, Keywords = Keywords.ThreadTransfer, Task = Tasks.ThreadTransfer, Opcode = EventOpcode.Send)] + public void ThreadTransferSend(long id, int kind, string info, bool multiDequeues) { + if (IsEnabled()) + WriteEvent(150, id, kind, info, multiDequeues); + } + // id - is a managed object. it gets translated to the object's address. ETW listeners must + // keep track of GC movements in order to correlate the value passed to XyzSend with the + // (possibly changed) value passed to XyzReceive + [NonEvent] +#if !CORECLR + [System.Security.SecuritySafeCritical] +#endif // !CORECLR + public unsafe void ThreadTransferSendObj(object id, int kind, string info, bool multiDequeues) { + ThreadTransferSend((long) *((void**) JitHelpers.UnsafeCastToStackPointer(ref id)), kind, info, multiDequeues); + } + + // id - represents a correlation ID that allows correlation of two activities, one stamped by + // ThreadTransferSend, the other by ThreadTransferReceive + // kind - identifies the transfer: values below 64 are reserved for the runtime. Currently used values: + // 1 - managed Timers ("roaming" ID) + // 2 - managed async IO operations (FileStream, PipeStream, a.o.) + // 3 - WinRT dispatch operations + // info - any additional information user code might consider interesting + [Event(151, Level = EventLevel.Informational, Keywords = Keywords.ThreadTransfer, Task = Tasks.ThreadTransfer, Opcode = EventOpcode.Receive)] + public void ThreadTransferReceive(long id, int kind, string info) { + if (IsEnabled()) + WriteEvent(151, id, kind, info); + } + // id - is a managed object. it gets translated to the object's address. ETW listeners must + // keep track of GC movements in order to correlate the value passed to XyzSend with the + // (possibly changed) value passed to XyzReceive + [NonEvent] +#if !CORECLR + [System.Security.SecuritySafeCritical] +#endif // !CORECLR + public unsafe void ThreadTransferReceiveObj(object id, int kind, string info) { + ThreadTransferReceive((long) *((void**) JitHelpers.UnsafeCastToStackPointer(ref id)), kind, info); + } + + // id - represents a correlation ID that allows correlation of two activities, one stamped by + // ThreadTransferSend, the other by ThreadTransferReceive + // kind - identifies the transfer: values below 64 are reserved for the runtime. Currently used values: + // 1 - managed Timers ("roaming" ID) + // 2 - managed async IO operations (FileStream, PipeStream, a.o.) + // 3 - WinRT dispatch operations + // info - any additional information user code might consider interesting + [Event(152, Level = EventLevel.Informational, Keywords = Keywords.ThreadTransfer, Task = Tasks.ThreadTransfer, Opcode = Opcodes.ReceiveHandled)] + public void ThreadTransferReceiveHandled(long id, int kind, string info) { + if (IsEnabled()) + WriteEvent(152, id, kind, info); + } + // id - is a managed object. it gets translated to the object's address. ETW listeners must + // keep track of GC movements in order to correlate the value passed to XyzSend with the + // (possibly changed) value passed to XyzReceive + [NonEvent] +#if !CORECLR + [System.Security.SecuritySafeCritical] +#endif // !CORECLR + public unsafe void ThreadTransferReceiveHandledObj(object id, int kind, string info) { + ThreadTransferReceive((long) *((void**) JitHelpers.UnsafeCastToStackPointer(ref id)), kind, info); + } + + // return a stable ID for a an object. We use the hash code which is not truely unique but is + // close enough for now at least. we add to it 0x7FFFFFFF00000000 to make it distinguishable + // from the style of ID that simply casts the object reference to a long (since old versions of the + // runtime will emit IDs of that form). + private static long IdForObject(object obj) { + return obj.GetHashCode() + 0x7FFFFFFF00000000; + } + } +} + diff --git a/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/StubEnvironment.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/StubEnvironment.cs new file mode 100644 index 0000000..add2812 --- /dev/null +++ b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/StubEnvironment.cs @@ -0,0 +1,381 @@ +// 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.Internal +#else +namespace System.Diagnostics.Tracing.Internal +#endif +{ +#if ES_BUILD_AGAINST_DOTNET_V35 + using Microsoft.Internal; +#endif + using Microsoft.Reflection; + using System.Reflection; + + internal static class Environment + { + public static readonly string NewLine = System.Environment.NewLine; + + public static int TickCount + { get { return System.Environment.TickCount; } } + + public static string GetResourceString(string key, params object[] args) + { + string fmt = rm.GetString(key); + if (fmt != null) + return string.Format(fmt, args); + + string sargs = String.Empty; + foreach(var arg in args) + { + if (sargs != String.Empty) + sargs += ", "; + sargs += arg.ToString(); + } + + return key + " (" + sargs + ")"; + } + + public static string GetRuntimeResourceString(string key, params object[] args) + { + return GetResourceString(key, args); + } + + private static System.Resources.ResourceManager rm = new System.Resources.ResourceManager("Microsoft.Diagnostics.Tracing.Messages", typeof(Environment).Assembly()); + } +} + +#if ES_BUILD_AGAINST_DOTNET_V35 + +namespace Microsoft.Diagnostics.Contracts.Internal +{ + internal class Contract + { + public static void Assert(bool invariant) + { + Assert(invariant, string.Empty); + } + public static void Assert(bool invariant, string message) + { + if (!invariant) + { + if (System.Diagnostics.Debugger.IsAttached) + System.Diagnostics.Debugger.Break(); + throw new Exception("Assertion failed: " + message); + } + } + public static void EndContractBlock() + { } + } +} + + +namespace Microsoft.Internal +{ + using System.Text; + + internal static class Tuple + { + public static Tuple Create(T1 item1) + { + return new Tuple(item1); + } + + public static Tuple Create(T1 item1, T2 item2) + { + return new Tuple(item1, item2); + } + } + + [Serializable] + internal class Tuple + { + private readonly T1 m_Item1; + + public T1 Item1 { get { return m_Item1; } } + + public Tuple(T1 item1) + { + m_Item1 = item1; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("("); + sb.Append(m_Item1); + sb.Append(")"); + return sb.ToString(); + } + + int Size + { + get + { + return 1; + } + } + } + + [Serializable] + public class Tuple + { + private readonly T1 m_Item1; + private readonly T2 m_Item2; + + public T1 Item1 { get { return m_Item1; } } + public T2 Item2 { get { return m_Item2; } } + + public Tuple(T1 item1, T2 item2) + { + m_Item1 = item1; + m_Item2 = item2; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.Append("("); + sb.Append(m_Item1); + sb.Append(", "); + sb.Append(m_Item2); + sb.Append(")"); + return sb.ToString(); + } + + int Size + { + get + { + return 2; + } + } + } +} + +#endif + +namespace Microsoft.Reflection +{ + using System.Reflection; + +#if ES_BUILD_PCL + [Flags] + public enum BindingFlags + { + DeclaredOnly = 0x02, // Only look at the members declared on the Type + Instance = 0x04, // Include Instance members in search + Static = 0x08, // Include Static members in search + Public = 0x10, // Include Public members in search + NonPublic = 0x20, // Include Non-Public members in search + } + + public enum TypeCode { + Empty = 0, // Null reference + Object = 1, // Instance that isn't a value + DBNull = 2, // Database null value + Boolean = 3, // Boolean + Char = 4, // Unicode character + SByte = 5, // Signed 8-bit integer + Byte = 6, // Unsigned 8-bit integer + Int16 = 7, // Signed 16-bit integer + UInt16 = 8, // Unsigned 16-bit integer + Int32 = 9, // Signed 32-bit integer + UInt32 = 10, // Unsigned 32-bit integer + Int64 = 11, // Signed 64-bit integer + UInt64 = 12, // Unsigned 64-bit integer + Single = 13, // IEEE 32-bit float + Double = 14, // IEEE 64-bit double + Decimal = 15, // Decimal + DateTime = 16, // DateTime + String = 18, // Unicode character string + } +#endif + static class ReflectionExtensions + { +#if (!ES_BUILD_PCL && !ES_BUILD_PN) + + // + // Type extension methods + // + public static bool IsEnum(this Type type) { return type.IsEnum; } + public static bool IsAbstract(this Type type) { return type.IsAbstract; } + public static bool IsSealed(this Type type) { return type.IsSealed; } + public static bool IsValueType(this Type type) { return type.IsValueType; } + public static bool IsGenericType(this Type type) { return type.IsGenericType; } + public static Type BaseType(this Type type) { return type.BaseType; } + public static Assembly Assembly(this Type type) { return type.Assembly; } + public static TypeCode GetTypeCode(this Type type) { return Type.GetTypeCode(type); } + + public static bool ReflectionOnly(this Assembly assm) { return assm.ReflectionOnly; } + +#else // ES_BUILD_PCL + + // + // Type extension methods + // + public static bool IsEnum(this Type type) { return type.GetTypeInfo().IsEnum; } + public static bool IsAbstract(this Type type) { return type.GetTypeInfo().IsAbstract; } + public static bool IsSealed(this Type type) { return type.GetTypeInfo().IsSealed; } + public static bool IsValueType(this Type type) { return type.GetTypeInfo().IsValueType; } + public static bool IsGenericType(this Type type) { return type.IsConstructedGenericType; } + public static Type BaseType(this Type type) { return type.GetTypeInfo().BaseType; } + public static Assembly Assembly(this Type type) { return type.GetTypeInfo().Assembly; } + public static IEnumerable GetProperties(this Type type) + { +#if ES_BUILD_PN + return type.GetProperties(); +#else + return type.GetRuntimeProperties(); +#endif + } + public static MethodInfo GetGetMethod(this PropertyInfo propInfo) { return propInfo.GetMethod; } + public static Type[] GetGenericArguments(this Type type) { return type.GenericTypeArguments; } + + public static MethodInfo[] GetMethods(this Type type, BindingFlags flags) + { + // Minimal implementation to cover only the cases we need + System.Diagnostics.Debug.Assert((flags & BindingFlags.DeclaredOnly) != 0); + System.Diagnostics.Debug.Assert((flags & ~(BindingFlags.DeclaredOnly|BindingFlags.Instance|BindingFlags.Static|BindingFlags.Public|BindingFlags.NonPublic)) == 0); + Func visFilter; + Func instFilter; + switch (flags & (BindingFlags.Public | BindingFlags.NonPublic)) + { + case 0: visFilter = mi => false; break; + case BindingFlags.Public: visFilter = mi => mi.IsPublic; break; + case BindingFlags.NonPublic: visFilter = mi => !mi.IsPublic; break; + default: visFilter = mi => true; break; + } + switch (flags & (BindingFlags.Instance | BindingFlags.Static)) + { + case 0: instFilter = mi => false; break; + case BindingFlags.Instance: instFilter = mi => !mi.IsStatic; break; + case BindingFlags.Static: instFilter = mi => mi.IsStatic; break; + default: instFilter = mi => true; break; + } + List methodInfos = new List(); + foreach (var declaredMethod in type.GetTypeInfo().DeclaredMethods) + { + if (visFilter(declaredMethod) && instFilter(declaredMethod)) + methodInfos.Add(declaredMethod); + } + return methodInfos.ToArray(); + } + public static FieldInfo[] GetFields(this Type type, BindingFlags flags) + { + // Minimal implementation to cover only the cases we need + System.Diagnostics.Debug.Assert((flags & BindingFlags.DeclaredOnly) != 0); + System.Diagnostics.Debug.Assert((flags & ~(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) == 0); + Func visFilter; + Func instFilter; + switch (flags & (BindingFlags.Public | BindingFlags.NonPublic)) + { + case 0: visFilter = fi => false; break; + case BindingFlags.Public: visFilter = fi => fi.IsPublic; break; + case BindingFlags.NonPublic: visFilter = fi => !fi.IsPublic; break; + default: visFilter = fi => true; break; + } + switch (flags & (BindingFlags.Instance | BindingFlags.Static)) + { + case 0: instFilter = fi => false; break; + case BindingFlags.Instance: instFilter = fi => !fi.IsStatic; break; + case BindingFlags.Static: instFilter = fi => fi.IsStatic; break; + default: instFilter = fi => true; break; + } + List fieldInfos = new List(); + foreach (var declaredField in type.GetTypeInfo().DeclaredFields) + { + if (visFilter(declaredField) && instFilter(declaredField)) + fieldInfos.Add(declaredField); + } + return fieldInfos.ToArray(); + } + public static Type GetNestedType(this Type type, string nestedTypeName) + { + TypeInfo ti = null; + foreach(var nt in type.GetTypeInfo().DeclaredNestedTypes) + { + if (nt.Name == nestedTypeName) + { + ti = nt; + break; + } + } + return ti == null ? null : ti.AsType(); + } + public static TypeCode GetTypeCode(this Type type) + { + if (type == typeof(bool)) return TypeCode.Boolean; + else if (type == typeof(byte)) return TypeCode.Byte; + else if (type == typeof(char)) return TypeCode.Char; + else if (type == typeof(ushort)) return TypeCode.UInt16; + else if (type == typeof(uint)) return TypeCode.UInt32; + else if (type == typeof(ulong)) return TypeCode.UInt64; + else if (type == typeof(sbyte)) return TypeCode.SByte; + else if (type == typeof(short)) return TypeCode.Int16; + else if (type == typeof(int)) return TypeCode.Int32; + else if (type == typeof(long)) return TypeCode.Int64; + else if (type == typeof(string)) return TypeCode.String; + else if (type == typeof(float)) return TypeCode.Single; + else if (type == typeof(double)) return TypeCode.Double; + else if (type == typeof(DateTime)) return TypeCode.DateTime; + else if (type == (typeof(Decimal))) return TypeCode.Decimal; + else return TypeCode.Object; + } + + // + // FieldInfo extension methods + // + public static object GetRawConstantValue(this FieldInfo fi) + { return fi.GetValue(null); } + + // + // Assembly extension methods + // + public static bool ReflectionOnly(this Assembly assm) + { + // In PCL we can't load in reflection-only context + return false; + } + +#endif + } +} + +// Defining some no-ops in PCL builds +#if ES_BUILD_PCL +namespace System.Security +{ + class SuppressUnmanagedCodeSecurityAttribute : Attribute { } + + enum SecurityAction { Demand } +} + +namespace System.Security.Permissions +{ + class HostProtectionAttribute : Attribute { public bool MayLeakOnAbort { get; set; } } + class PermissionSetAttribute : Attribute + { + public PermissionSetAttribute(System.Security.SecurityAction action) { } + public bool Unrestricted { get; set; } + } +} +#endif + +#if ES_BUILD_PN +namespace System +{ + public static class AppDomain + { + public static int GetCurrentThreadId() + { + return Internal.Runtime.Augments.RuntimeThread.CurrentThread.ManagedThreadId; + } + } +} +#endif diff --git a/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ArrayTypeInfo.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ArrayTypeInfo.cs new file mode 100644 index 0000000..5771354 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSet.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSet.cs new file mode 100644 index 0000000..76c01c6 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSetItem.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/ConcurrentSetItem.cs new file mode 100644 index 0000000..558dbf6 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/DataCollector.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/DataCollector.cs new file mode 100644 index 0000000..27aae82 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EmptyStruct.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EmptyStruct.cs new file mode 100644 index 0000000..bc7fb8c --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EnumHelper.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EnumHelper.cs new file mode 100644 index 0000000..7a23378 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EnumerableTypeInfo.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EnumerableTypeInfo.cs new file mode 100644 index 0000000..74a3fa2 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventDataAttribute.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventDataAttribute.cs new file mode 100644 index 0000000..cdedf13 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldAttribute.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldAttribute.cs new file mode 100644 index 0000000..1a298c2 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldFormat.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventFieldFormat.cs new file mode 100644 index 0000000..fd77b07 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventIgnoreAttribute.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventIgnoreAttribute.cs new file mode 100644 index 0000000..769345f --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventPayload.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventPayload.cs new file mode 100644 index 0000000..5967ad6 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceActivity.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceActivity.cs new file mode 100644 index 0000000..38c1767 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceOptions.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/EventSourceOptions.cs new file mode 100644 index 0000000..26305a5 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/FieldMetadata.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/FieldMetadata.cs new file mode 100644 index 0000000..309226b --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/InvokeTypeInfo.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/InvokeTypeInfo.cs new file mode 100644 index 0000000..3e5997b --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/NameInfo.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/NameInfo.cs new file mode 100644 index 0000000..668043a --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyAnalysis.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyAnalysis.cs new file mode 100644 index 0000000..1f07539 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyValue.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/PropertyValue.cs new file mode 100644 index 0000000..3ea7812 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleEventTypes.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleEventTypes.cs new file mode 100644 index 0000000..cdced96 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleTypeInfos.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/SimpleTypeInfos.cs new file mode 100644 index 0000000..901a0ed --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/Statics.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/Statics.cs new file mode 100644 index 0000000..9fa7767 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataCollector.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataCollector.cs new file mode 100644 index 0000000..04a047f --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataType.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingDataType.cs new file mode 100644 index 0000000..529948d --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventSource.cs new file mode 100644 index 0000000..8c875fc --- /dev/null +++ b/src/coreclr/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_UNIX +#define FEATURE_MANAGED_ETW + +#if !ES_BUILD_STANDALONE +#define FEATURE_ACTIVITYSAMPLING +#endif +#endif // PLATFORM_UNIX + +#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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTraits.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTraits.cs new file mode 100644 index 0000000..e808a88 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTypes.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingEventTypes.cs new file mode 100644 index 0000000..c223967 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingMetadataCollector.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingMetadataCollector.cs new file mode 100644 index 0000000..41225c8 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingTypeInfo.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TraceLoggingTypeInfo.cs new file mode 100644 index 0000000..d68e106 --- /dev/null +++ b/src/coreclr/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/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TypeAnalysis.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/TraceLogging/TypeAnalysis.cs new file mode 100644 index 0000000..42cdde5 --- /dev/null +++ b/src/coreclr/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; + } + } + } +} diff --git a/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/Winmeta.cs b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/Winmeta.cs new file mode 100644 index 0000000..ac756b6 --- /dev/null +++ b/src/coreclr/src/mscorlib/shared/System/Diagnostics/Tracing/Winmeta.cs @@ -0,0 +1,196 @@ +// 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. + +/*============================================================ +** +** +** Purpose: +** Contains eventing constants defined by the Windows +** environment. +** +============================================================*/ +#if ES_BUILD_STANDALONE +#define FEATURE_MANAGED_ETW_CHANNELS +#endif + +#if ES_BUILD_STANDALONE +namespace Microsoft.Diagnostics.Tracing +#else +namespace System.Diagnostics.Tracing +#endif +{ + using System; + + /// + /// WindowsEventLevel. Custom values must be in the range from 16 through 255 + /// + public enum EventLevel + { + /// + /// Log always + /// + LogAlways = 0, + /// + /// Only critical errors + /// + Critical, + /// + /// All errors, including previous levels + /// + Error, + /// + /// All warnings, including previous levels + /// + Warning, + /// + /// All informational events, including previous levels + /// + Informational, + /// + /// All events, including previous levels + /// + Verbose + } + /// + /// WindowsEventTask. Custom values must be in the range from 1 through 65534 + /// +#if (!ES_BUILD_STANDALONE && !ES_BUILD_PN) + [System.Runtime.CompilerServices.FriendAccessAllowed] +#endif + public enum EventTask + { + /// + /// Undefined task + /// + None = 0 + } + /// + /// EventOpcode. Custom values must be in the range from 11 through 239 + /// +#if (!ES_BUILD_STANDALONE && !ES_BUILD_PN) + [System.Runtime.CompilerServices.FriendAccessAllowed] +#endif + public enum EventOpcode + { + /// + /// An informational event + /// + Info = 0, + /// + /// An activity start event + /// + Start, + /// + /// An activity end event + /// + Stop, + /// + /// A trace collection start event + /// + DataCollectionStart, + /// + /// A trace collection end event + /// + DataCollectionStop, + /// + /// An extensional event + /// + Extension, + /// + /// A reply event + /// + Reply, + /// + /// An event representing the activity resuming from the suspension + /// + Resume, + /// + /// An event representing the activity is suspended, pending another activity's completion + /// + Suspend, + /// + /// An event representing the activity is transferred to another component, and can continue to work + /// + Send, + /// + /// An event representing receiving an activity transfer from another component + /// + Receive = 240 + } + + // Added for CLR V4 + /// + /// EventChannel. Custom values must be in the range from 16 through 255. Currently only predefined values allowed. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1028:EnumStorageShouldBeInt32", Justification = "Backwards compatibility")] +#if (!ES_BUILD_STANDALONE && !ES_BUILD_PN) + [System.Runtime.CompilerServices.FriendAccessAllowed] +#endif + public enum EventChannel : byte + { + /// + /// No channel + /// + None = 0, + // Channels 1 - 15 are reserved... + /// The admin channel + Admin = 16, + /// The operational channel + Operational = 17, + /// The analytic channel + Analytic = 18, + /// The debug channel + Debug = 19, + + }; + + /// + /// EventOpcode + /// + [Flags] + public enum EventKeywords : long + { + /// + /// No events. + /// + None = 0x0, + /// + /// All Events + /// + All = ~0, + /// + /// Telemetry events + /// + MicrosoftTelemetry = 0x02000000000000, + /// + /// WDI context events + /// + WdiContext = 0x02000000000000, + /// + /// WDI diagnostic events + /// + WdiDiagnostic = 0x04000000000000, + /// + /// SQM events + /// + Sqm = 0x08000000000000, + /// + /// Failed security audits + /// + AuditFailure = 0x10000000000000, + /// + /// Successful security audits + /// + AuditSuccess = 0x20000000000000, + /// + /// Transfer events where the related Activity ID is a computed value and not a GUID + /// N.B. The correct value for this field is 0x40000000000000. + /// + CorrelationHint = 0x10000000000000, + /// + /// Events raised using classic eventlog API + /// + EventLogClassic = 0x80000000000000 + } +} diff --git a/src/coreclr/src/mscorlib/shared/System/IO/FileLoadException.cs b/src/coreclr/src/mscorlib/shared/System/IO/FileLoadException.cs new file mode 100644 index 0000000..a4b68a8 --- /dev/null +++ b/src/coreclr/src/mscorlib/shared/System/IO/FileLoadException.cs @@ -0,0 +1,102 @@ +// 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.Runtime.Serialization; + +namespace System.IO +{ + [Serializable] + public partial class FileLoadException : IOException + { + public FileLoadException() + : base(SR.IO_FileLoad) + { + SetErrorCode(__HResults.COR_E_FILELOAD); + } + + public FileLoadException(string message) + : base(message) + { + SetErrorCode(__HResults.COR_E_FILELOAD); + } + + public FileLoadException(string message, Exception inner) + : base(message, inner) + { + SetErrorCode(__HResults.COR_E_FILELOAD); + } + + public FileLoadException(string message, string fileName) : base(message) + { + SetErrorCode(__HResults.COR_E_FILELOAD); + FileName = fileName; + } + + public FileLoadException(string message, string fileName, Exception inner) + : base(message, inner) + { + SetErrorCode(__HResults.COR_E_FILELOAD); + FileName = fileName; + } + + public override string Message + { + get + { + if (_message == null) + { + _message = FormatFileLoadExceptionMessage(FileName, HResult); + } + return _message; + } + } + + public string FileName { get; } + public string FusionLog { get; } + + public override string ToString() + { + string s = GetType().ToString() + ": " + Message; + + if (FileName != null && FileName.Length != 0) + s += Environment.NewLine + SR.Format(SR.IO_FileName_Name, FileName); + + if (InnerException != null) + s = s + " ---> " + InnerException.ToString(); + + if (StackTrace != null) + s += Environment.NewLine + StackTrace; + + if (FusionLog != null) + { + if (s == null) + s = " "; + s += Environment.NewLine; + s += Environment.NewLine; + s += FusionLog; + } + + return s; + } + + protected FileLoadException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + // Base class constructor will check info != null. + + FileName = info.GetString("FileLoad_FileName"); + FusionLog = info.GetString("FileLoad_FusionLog"); + } + + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + // Serialize data for our base classes. base will verify info != null. + base.GetObjectData(info, context); + + // Serialize data for this class + info.AddValue("FileLoad_FileName", FileName, typeof(string)); + info.AddValue("FileLoad_FusionLog", FusionLog, typeof(string)); + } + } +} -- 2.7.4