// 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 && !PROJECTN #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; using System.Security.Permissions; 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_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("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"), "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("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("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"), "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 { [System.Security.SecurityCritical] get { Guid retval = CurrentThreadActivityId; if (retval == Guid.Empty) { retval = FallbackActivityId; } return retval; } } internal static Guid FallbackActivityId { [System.Security.SecurityCritical] 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) [SecuritySafeCritical] [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) [SecuritySafeCritical] [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); } } [SecuritySafeCritical] [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); } } [SecuritySafeCritical] [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) [SecuritySafeCritical] [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); } } [SecuritySafeCritical] [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); } } [SecuritySafeCritical] [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) [SecuritySafeCritical] [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); } } } [SecuritySafeCritical] [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); } } } [SecuritySafeCritical] [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) [SecuritySafeCritical] [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); } } } [SecuritySafeCritical] [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) [SecuritySafeCritical] [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) [SecuritySafeCritical] [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) [SecuritySafeCritical] [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); } } } [SecuritySafeCritical] [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); } } } } [SecuritySafeCritical] [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 [SecurityCritical] 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); /// } /// } /// } /// /// [SecurityCritical] [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); /// } /// } /// } /// /// [SecurityCritical] [CLSCompliant(false)] protected unsafe void WriteEventWithRelatedActivityIdCore(int eventId, Guid* relatedActivityId, int eventDataCount, EventSource.EventData* data) { if (m_eventSourceEnabled) { try { Contract.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); #if FEATURE_MANAGED_ETW if (m_eventData[eventId].EnabledForETW) { 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_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. /// [SecuritySafeCritical] [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. /// [SecuritySafeCritical] 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) { Contract.Assert(listener == null || (uint)m == (uint)SessionMask.FromId(0)); if (m_eventSourceEnabled) { if (listener == null) { WriteEventString(0, unchecked((long)m.ToEventKeywords()), msg); } else { List arg = new List(); arg.Add(msg); EventWrittenEventArgs eventCallbackArgs = new EventWrittenEventArgs(this); eventCallbackArgs.EventId = 0; eventCallbackArgs.Payload = new ReadOnlyCollection(arg); listener.OnEventWritten(eventCallbackArgs); } } } #endif [SecurityCritical] 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")] [SecuritySafeCritical] 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"), "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 && !PROJECTN) // 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 Contract.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("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); } [SecurityCritical] 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); 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); } 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; } [SecurityCritical] private unsafe void WriteEventVarargs(int eventId, Guid* childActivityID, object[] args) { if (m_eventSourceEnabled) { try { Contract.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); #if FEATURE_MANAGED_ETW if (m_eventData[eventId].EnabledForETW) { 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_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)(ulong)etwSessions | 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)(ulong)etwSessions | 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 && !PROJECTN) // 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); } } } [SecurityCritical] 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 && !PROJECTN) // 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 } [SecurityCritical] unsafe private void WriteToAllListeners(int eventId, Guid* childActivityID, int eventDataCount, EventSource.EventData* data) { int paramCount = GetParameterCount(m_eventData[eventId]); if (eventDataCount != paramCount) { 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. [SecurityCritical] 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); } [SecurityCritical] private unsafe void DispatchToAllListeners(int eventId, Guid* childActivityID, EventWrittenEventArgs eventCallbackArgs) { Exception lastThrownException = null; for (EventDispatcher dispatcher = m_Dispatchers; dispatcher != null; dispatcher = dispatcher.m_Next) { Contract.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); } } [SecuritySafeCritical] [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 [SecurityCritical] 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 PROJECTN 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. Contract.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(); Contract.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 Contract.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) { Contract.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 // Contract.Assert(enable == true); // Contract.Assert(level == EventLevel.LogAlways); // Contract.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; } } [SecuritySafeCritical] private void EnsureDescriptorsInitialized() { #if !ES_BUILD_STANDALONE Contract.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. Contract.Assert(m_rawManifest == null); m_rawManifest = CreateManifestAndDescriptors(this.GetType(), Name, this); Contract.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 // 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. [SecuritySafeCritical] private unsafe bool SendManifest(byte[] rawManifest) { bool success = true; if (rawManifest == null) return false; Contract.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++; } } #endif // FEATURE_MANAGED_ETW return success; } #if (ES_BUILD_PCL || PROJECTN) 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 && !PROJECTN) // 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; Contract.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 && PROJECTN 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") && (reflectedAttributeType.Namespace.EndsWith("Diagnostics.Tracing") #if EVENT_SOURCE_LEGACY_NAMESPACE_SUPPORT || reflectedAttributeType.Namespace.EndsWith("Diagnostics.Eventing") #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) { Contract.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); } } #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, newValues, 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, newValues, 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.... Contract.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. [SecuritySafeCritical] [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 && !PROJECTN) // 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. (new ReflectionPermission(ReflectionPermissionFlag.MemberAccess)).Assert(); 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: /* Contract.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 && !PROJECTN) // 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"), "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 { Contract.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) { Contract.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]; Contract.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(this) ); } /// /// 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) { Contract.Assert(s_Listeners != null); 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("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("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; #if (!ES_BUILD_PCL && !FEATURE_CORECLR && !PROJECTN) AppDomain.CurrentDomain.ProcessExit += DisposeOnShutdown; AppDomain.CurrentDomain.DomainUnload += DisposeOnShutdown; #endif } // 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 Contract.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) { Contract.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; Contract.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) { Contract.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 (; ; ) { Contract.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 { [System.Security.SecurityCritical] get { return EventSource.CurrentThreadActivityId; } } /// /// Gets the related activity ID if one was specified when the event was written. /// public Guid RelatedActivityId { [System.Security.SecurityCritical] 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. Contract.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. /// 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 Contract.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 Contract.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. /// [SecurityCritical] unsafe public static bool PassesActivityFilter( ActivityFilter filterList, Guid* childActivityID, bool triggeringEvent, EventSource source, int eventId) { Contract.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; } [System.Security.SecuritySafeCritical] 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. /// [SecurityCritical] unsafe public static void FlowActivityIfNeeded(ActivityFilter filterList, Guid* currentActivityId, Guid* childActivityID) { Contract.Assert(childActivityID != null); var activeActivities = GetActiveActivities(filterList); Contract.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 Contract.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; Contract.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 Contract.Assert(Monitor.IsEntered(EventListener.EventListenersLock)); #endif Contract.Assert(samplingFreq > 0); Contract.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) { Contract.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) { Contract.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 { Contract.Assert(perEventSourceSessionId < MAX); return (m_mask & (1 << perEventSourceSessionId)) != 0; } set { Contract.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 guarenteed 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 Contract.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) { Contract.Assert(numParams == 0); Contract.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). public ulong GetChannelKeyword(EventChannel channel) { if (channelTab == null) { channelTab = new Dictionary(4); } if (channelTab.Count == MaxCountChannels) ManifestError(Resources.GetResourceString("EventSource_MaxChannelExceeded")); ulong channelKeyword; ChannelInfo info; if (!channelTab.TryGetValue((int)channel, out info)) { 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 || PROJECTN 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, Comparer.Default); #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_")) { 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 !ES_BUILD_PCL && !FEATURE_CORECLR && !PROJECTN foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.SpecificCultures /*| CultureTypes.NeutralCultures*/)) { if (resources.GetResourceSet(ci, true, false) != null) cultures.Add(ci); } #endif // !ES_BUILD_PCL && !FEATURE_CORECLR 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) { 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); } // 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) { string[] escapes = { "&", "<", ">", "'", """, "%r", "%n", "%t" }; var update = new Action( (ch, escape) => { UpdateStringBuilder(ref stringBuilder, eventMessage, writtenSoFar, i - writtenSoFar); i++; stringBuilder.Append(escape); writtenSoFar = i; }); update(eventMessage[i], escapes[chIdx]); } 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 ulong nextChannelKeywordBit = 0x8000000000000000; // available Keyword bit to be used for next channel definition 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 } public ManifestFormats Format; public byte MajorVersion; public byte MinorVersion; public byte Magic; public ushort TotalChunks; public ushort ChunkNumber; }; #endregion }