using System;
using System.Diagnostics;
using System.Threading;
-using System.Threading.Tasks;
-
#if !ES_BUILD_AGAINST_DOTNET_V35
using Contract = System.Diagnostics.Contracts.Contract;
#else
#if ES_BUILD_STANDALONE
namespace Microsoft.Diagnostics.Tracing
-#else
+#else
+using System.Threading.Tasks;
namespace System.Diagnostics.Tracing
#endif
{
/// <summary>
- /// Tracks activities. This is meant to be a singledon (accessed by the ActivityTracer.Instance static property)
+ /// Tracks activities. This is meant to be a singleton (accessed by the ActivityTracer.Instance static property)
///
/// Logically this is simply holds the m_current variable that holds the async local that holds the current ActivityInfo
- /// An ActivityInfo is represents a actvity (which knows its creator and thus knows its path).
+ /// An ActivityInfo is represents a activity (which knows its creator and thus knows its path).
///
/// Most of the magic is in the async local (it gets copied to new tasks)
///
public void OnStart(string providerName, string activityName, int task, ref Guid activityId, ref Guid relatedActivityId, EventActivityOptions options)
{
if (m_current == null) // We are not enabled
- return;
+ {
+ // We used to rely on the TPL provider turning us on, but that has the disadvantage that you don't get Start-Stop tracking
+ // until you use Tasks for the first time (which you may never do). Thus we change it to pull rather tan push for whether
+ // we are enabled.
+ if (m_checkedForEnable)
+ return;
+ m_checkedForEnable = true;
+#if ES_BUILD_STANDALONE
+ Enable(); // Enable it unconditionally.
+#else
+ if (System.Threading.Tasks.TplEtwProvider.Log.IsEnabled(EventLevel.Informational, System.Threading.Tasks.TplEtwProvider.Keywords.TasksFlowActivityIds))
+ Enable();
+#endif
+ }
+
Contract.Assert((options & EventActivityOptions.Disable) == 0);
else
id = Interlocked.Increment(ref currentActivity.m_lastChildID);
- // Remember the previous ID so we can log it
- relatedActivityId = currentActivity != null ? currentActivity.ActivityId : Guid.Empty;
+ // The previous ID is my 'causer' and becomes my related activity ID
+ relatedActivityId = EventSource.CurrentThreadActivityId;
// Add to the list of started but not stopped activities.
- ActivityInfo newActivity = new ActivityInfo(fullActivityName, id, currentActivity, options);
+ ActivityInfo newActivity = new ActivityInfo(fullActivityName, id, currentActivity, relatedActivityId, options);
m_current.Value = newActivity;
// Remember the current ID so we can log it
/// <summary>
/// Called when a work item stops. The activity name = providerName + activityName without 'Stop' suffix.
- /// It updates CurrentActivityId to track this fact. The Stop event associated with stop should log the ActivityID associated with the event.
+ /// It updates m_current variable to track this fact. The Stop event associated with stop should log the ActivityID associated with the event.
///
/// If activity tracing is not on, then activityId and relatedActivityId are not set
/// </summary>
if (activityToStop == null)
{
activityId = Guid.Empty;
- // Basically could not find matching start.
+ // TODO add some logging about this. Basically could not find matching start.
if (etwLog.Debug)
etwLog.DebugFacilityMessage("OnStopRET", "Fail");
return;
m_current.Value = newCurrentActivity;
- if (etwLog.Debug)
+ if (etwLog.Debug)
{
etwLog.DebugFacilityMessage("OnStopRetActivityState", ActivityInfo.LiveActivities(newCurrentActivity));
etwLog.DebugFacilityMessage("OnStopRet", activityId.ToString());
// *******************************************************************************
/// <summary>
- /// An ActivityInfo repesents a particular activity. It is almost read-only the only
+ /// An ActivityInfo represents a particular activity. It is almost read-only. The only
/// fields that change after creation are
/// m_lastChildID - used to generate unique IDs for the children activities and for the most part can be ignored.
/// m_stopped - indicates that this activity is dead
- /// This read-only ness is important because an activity's m_creator chain forms the
+ /// This read-only-ness is important because an activity's m_creator chain forms the
/// 'Path of creation' for the activity (which is also its unique ID) but is also used as
/// the 'list of live parents' which indicate of those ancestors, which are alive (if they
/// are not marked dead they are alive).
/// </summary>
private class ActivityInfo
{
- public ActivityInfo(string name, long uniqueId, ActivityInfo creator, EventActivityOptions options)
+ public ActivityInfo(string name, long uniqueId, ActivityInfo creator, Guid activityIDToRestore, EventActivityOptions options)
{
m_name = name;
m_eventOptions = options;
m_creator = creator;
m_uniqueId = uniqueId;
m_level = creator != null ? creator.m_level + 1 : 0;
+ m_activityIdToRestore = activityIDToRestore;
// Create a nice GUID that encodes the chain of activities that started this one.
CreateActivityPathGuid(out m_guid, out m_activityPathGuidOffset);
public static string Path(ActivityInfo activityInfo)
{
if (activityInfo == null)
- return("");
+ return ("");
return Path(activityInfo.m_creator) + "/" + activityInfo.m_uniqueId;
}
/// (rooted in an activity that predates activity tracking.
///
/// We wish to encode this path in the Guid to the extent that we can. Many of the paths have
- /// many small numbers in them and we take advatage of this in the encoding to output as long
+ /// many small numbers in them and we take advantage of this in the encoding to output as long
/// a path in the GUID as possible.
///
- /// Because of the possiblility of GUID collistion, we only use 96 of the 128 bits of the GUID
+ /// Because of the possibility of GUID collision, we only use 96 of the 128 bits of the GUID
/// for encoding the path. The last 32 bits are a simple checksum (and random number) that
/// identifies this as using the convention defined here.
///
}
else
{
+ // TODO FIXME - differentiate between AD inside PCL
+ int appDomainID = 0;
+#if !ES_BUILD_STANDALONE
+ appDomainID = System.Threading.Thread.GetDomainID();
+#endif
// We start with the appdomain number to make this unique among appdomains.
- activityPathGuidOffsetStart = AddIdToGuid(outPtr, activityPathGuidOffsetStart, (uint) System.Threading.Thread.GetDomainID());
+ activityPathGuidOffsetStart = AddIdToGuid(outPtr, activityPathGuidOffsetStart, (uint)appDomainID);
}
- activityPathGuidOffset = AddIdToGuid(outPtr, activityPathGuidOffsetStart, (uint) m_uniqueId);
+ activityPathGuidOffset = AddIdToGuid(outPtr, activityPathGuidOffsetStart, (uint)m_uniqueId);
// If the path does not fit, Make a GUID by incrementing rather than as a path, keeping as much of the path as possible
/// <summary>
/// If we can't fit the activity Path into the GUID we come here. What we do is simply
- /// generate a 4 byte number (s_nextOverflowId). Then look for an anscesor that has
+ /// generate a 4 byte number (s_nextOverflowId). Then look for an ancestor that has
/// sufficient space for this ID. By doing this, we preserve the fact that this activity
/// is a child (of unknown depth) from that ancestor.
/// </summary>
[System.Security.SecurityCritical]
private unsafe void CreateOverflowGuid(Guid* outPtr)
{
- // Seach backwards for an ancestor that has sufficient space to put the ID.
- for(ActivityInfo ancestor = m_creator; ancestor != null; ancestor = ancestor.m_creator)
+ // Search backwards for an ancestor that has sufficient space to put the ID.
+ for (ActivityInfo ancestor = m_creator; ancestor != null; ancestor = ancestor.m_creator)
{
if (ancestor.m_activityPathGuidOffset <= 10) // we need at least 2 bytes.
{
- uint id = (uint) Interlocked.Increment(ref ancestor.m_lastChildID); // Get a unique ID
+ uint id = unchecked((uint)Interlocked.Increment(ref ancestor.m_lastChildID)); // Get a unique ID
// Try to put the ID into the GUID
*outPtr = ancestor.m_guid;
int endId = AddIdToGuid(outPtr, ancestor.m_activityPathGuidOffset, id, true);
}
/// <summary>
- /// The encoding for a list of numbers used to make Activity Guids. Basically
- /// we operate on nibbles (which are nice becase they show up as hex digits). The
+ /// The encoding for a list of numbers used to make Activity GUIDs. Basically
+ /// we operate on nibbles (which are nice because they show up as hex digits). The
/// list is ended with a end nibble (0) and depending on the nibble value (Below)
/// the value is either encoded into nibble itself or it can spill over into the
/// bytes that follow.
LastImmediateValue = 0xA,
PrefixCode = 0xB, // all the 'long' encodings go here. If the next nibble is MultiByte1-4
- // than this is a 'overflow' id. Unlike the hierarchitcal IDs these are
- // allocated densly but don't tell you anything about nesting. we use
+ // than this is a 'overflow' id. Unlike the hierarchical IDs these are
+ // allocated densely but don't tell you anything about nesting. we use
// these when we run out of space in the GUID to store the path.
MultiByte1 = 0xC, // 1 byte follows. If this Nibble is in the high bits, it the high bits of the number are stored in the low nibble.
// commented out because the code does not explicitly reference the names (but they are logically defined).
- // MultiByte2 = 0xD, // 2 bytes follow (we don't bother with the nibble optimzation)
- // MultiByte3 = 0xE, // 3 bytes follow (we don't bother with the nibble optimzation)
- // MultiByte4 = 0xF, // 4 bytes follow (we don't bother with the nibble optimzation)
+ // MultiByte2 = 0xD, // 2 bytes follow (we don't bother with the nibble optimization)
+ // MultiByte3 = 0xE, // 3 bytes follow (we don't bother with the nibble optimization)
+ // MultiByte4 = 0xF, // 4 bytes follow (we don't bother with the nibble optimization)
}
- /// Add the acivity id 'id' to the output Guid 'outPtr' starting at the offset 'whereToAddId'
+ /// Add the activity id 'id' to the output Guid 'outPtr' starting at the offset 'whereToAddId'
/// Thus if this number is 6 that is where 'id' will be added. This will return 13 (12
/// is the maximum number of bytes that fit in a GUID) if the path did not fit.
/// If 'overflow' is true, then the number is encoded as an 'overflow number (which has a
// Do we have an odd nibble? If so flush it or use it for the 12 byte case.
if (ptr < endPtr && *ptr != 0)
{
- // If the value < 4096 we can use the nibble we are otherwise just outputing as padding.
+ // If the value < 4096 we can use the nibble we are otherwise just outputting as padding.
if (id < 4096)
{
// Indicate this is a 1 byte multicode with 4 high order bits in the lower nibble.
}
// Write out the bytes.
- while(0 < len)
+ while (0 < len)
{
if (endPtr <= ptr)
{
readonly internal string m_name; // The name used in the 'start' and 'stop' APIs to help match up
readonly long m_uniqueId; // a small number that makes this activity unique among its siblings
- internal readonly Guid m_guid; // Activity Guid, it is bascially an encoding of the Path() (see CreateActivityPathGuid)
+ internal readonly Guid m_guid; // Activity Guid, it is basically an encoding of the Path() (see CreateActivityPathGuid)
internal readonly int m_activityPathGuidOffset; // Keeps track of where in m_guid the causality path stops (used to generated child GUIDs)
internal readonly int m_level; // current depth of the Path() of the activity (used to keep recursion under control)
readonly internal EventActivityOptions m_eventOptions; // Options passed to start.
internal long m_lastChildID; // used to create a unique ID for my children activities
internal int m_stopped; // This work item has stopped
readonly internal ActivityInfo m_creator; // My parent (creator). Forms the Path() for the activity.
+ readonly internal Guid m_activityIdToRestore; // The Guid to restore after a stop.
#endregion
}
// with m_current.ActivityID
void ActivityChanging(AsyncLocalValueChangedArgs<ActivityInfo> args)
{
- if (args.PreviousValue == args.CurrentValue)
+ ActivityInfo cur = args.CurrentValue;
+ ActivityInfo prev = args.PreviousValue;
+
+ // Are we popping off a value? (we have a prev, and it creator is cur)
+ // Then check if we should use the GUID at the time of the start event
+ if (prev != null && prev.m_creator == cur)
+ {
+ // If the saved activity ID is not the same as the creator activity
+ // that takes precedence (it means someone explicitly did a SetActivityID)
+ // Set it to that and get out
+ if (cur == null || prev.m_activityIdToRestore != cur.ActivityId)
+ {
+ EventSource.SetCurrentThreadActivityId(prev.m_activityIdToRestore);
return;
+ }
+ }
- if (args.CurrentValue != null)
+ // OK we did not have an explicit SetActivityID set. Then we should be
+ // setting the activity to current ActivityInfo. However that activity
+ // might be dead, in which case we should skip it, so we never set
+ // the ID to dead things.
+ while (cur != null)
{
- // Allow subsequent activities inside this thread to automatically get the current activity ID.
- EventSource.SetCurrentThreadActivityId(args.CurrentValue.ActivityId);
+ // We found a live activity (typically the first time), set it to that.
+ if (cur.m_stopped == 0)
+ {
+ EventSource.SetCurrentThreadActivityId(cur.ActivityId);
+ return;
+ }
+ cur = cur.m_creator;
}
- else
- EventSource.SetCurrentThreadActivityId(Guid.Empty);
+ // we can get here if there is no information on our activity stack (everything is dead)
+ // currently we do nothing, as that seems better than setting to Guid.Emtpy.
}
/// <summary>
- /// Async local variables have the propery that the are automatically copied whenever a task is created and used
+ /// Async local variables have the property that the are automatically copied whenever a task is created and used
/// while that task is running. Thus m_current 'flows' to any task that is caused by the current thread that
/// last set it.
///
/// This variable points a a linked list that represents all Activities that have started but have not stopped.
/// </summary>
AsyncLocal<ActivityInfo> m_current;
+ bool m_checkedForEnable;
// Singleton
private static ActivityTracker s_activityTrackerInstance = new ActivityTracker();
#endregion
}
+
+#if ES_BUILD_STANDALONE
+ /******************************** SUPPORT *****************************/
+ /// <summary>
+ /// This is supplied by the framework. It is has the semantics that the value is copied to any new Tasks that is created
+ /// by the current task. Thus all causally related code gets this value. Note that reads and writes to this VARIABLE
+ /// (not what it points it) to this does not need to be protected by locks because it is inherently thread local (you always
+ /// only get your thread local copy which means that you never have races.
+ /// </summary>
+ ///
+ [EventSource(Name="Microsoft.Tasks.Nuget")]
+ internal class TplEtwProvider : EventSource
+ {
+ public class Keywords
+ {
+ public const EventKeywords Debug = (EventKeywords) 1;
+ }
+
+ public static TplEtwProvider Log = new TplEtwProvider();
+ public bool Debug { get { return IsEnabled(EventLevel.Verbose, Keywords.Debug); } }
+
+ public void DebugFacilityMessage(string Facility, string Message) { WriteEvent(1, Facility, Message); }
+ public void DebugFacilityMessage1(string Facility, string Message, string Arg) { WriteEvent(2, Facility, Message, Arg); }
+ public void SetActivityId(Guid Id) { WriteEvent(3, Id); }
+ }
+#endif
+
+#if ES_BUILD_AGAINST_DOTNET_V35 || ES_BUILD_PCL || NO_ASYNC_LOCAL
+
+ internal sealed class AsyncLocalValueChangedArgs<T>
+ {
+ public AsyncLocalValueChangedArgs()
+ {
+ }
+
+ public T PreviousValue { get { return default(T); } }
+ public T CurrentValue { get { return default(T); } }
+
+ }
+
+ internal sealed class AsyncLocal<T>
+ {
+ public AsyncLocal()
+ {
+ }
+
+ public AsyncLocal(Action<AsyncLocalValueChangedArgs<T>> valueChangedHandler)
+ {
+
+ }
+
+ public T Value
+ {
+ get
+ {
+ object obj = null; // TODO FIX
+ return (obj == null) ? default(T) : (T)obj;
+ }
+ set
+ {
+ // TODO FIX
+ }
+ }
+ }
+#endif
+
}
Other = 5,
};
- // <SecurityKernel Critical="True" Ring="1">
- // <ReferencesCritical Name="Method: Register():Void" Ring="1" />
- // </SecurityKernel>
- /// <summary>
- /// Constructs a new EventProvider. This causes the class to be registered with the OS and
- /// if an ETW controller turns on the logging then logging will start.
- /// </summary>
- /// <param name="providerGuid">The GUID that identifies this provider to the system.</param>
- [System.Security.SecurityCritical]
-#pragma warning disable 618
- [PermissionSet(SecurityAction.Demand, Unrestricted = true)]
-#pragma warning restore 618
- [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames", MessageId = "guid")]
- protected EventProvider(Guid providerGuid)
- {
- m_providerId = providerGuid;
+ // Because callbacks happen on registration, and we need the callbacks for those setup
+ // we can't call Register in the constructor.
//
- // Register the ProviderId with ETW
- //
- Register(providerGuid);
- }
-
+ // Note that EventProvider should ONLY be used by EventSource. In particular because
+ // it registers a callback from native code you MUST dispose it BEFORE shutdown, otherwise
+ // you may get native callbacks during shutdown when we have destroyed the delegate.
+ // EventSource has special logic to do this, no one else should be calling EventProvider.
internal EventProvider()
{
}
// <SecurityKernel Critical="True" Ring="0">
// <CallsSuppressUnmanagedCode Name="UnsafeNativeMethods.ManifestEtw.EventUnregister(System.Int64):System.Int32" />
// </SecurityKernel>
+ // TODO Check return code from UnsafeNativeMethods.ManifestEtw.EventUnregister
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "Microsoft.Win32.UnsafeNativeMethods.ManifestEtw.EventUnregister(System.Int64)"), System.Security.SecurityCritical]
private unsafe void Deregister()
{
{
// This is an optional callback API. We will therefore ignore any failures that happen as a
// result of turning on this provider as to not crash the app.
- // EventSource has code to validate whther initialization it expected to occur actually occurred
+ // EventSource has code to validate whether initialization it expected to occur actually occurred
try
{
ControllerCommand command = ControllerCommand.Update;
IDictionary<string, string> args = null;
- byte[] data;
- int keyIndex;
bool skipFinalOnControllerCommand = false;
if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_ENABLE_PROVIDER)
{
m_anyKeywordMask = anyKeyword;
m_allKeywordMask = allKeyword;
+ // ES_SESSION_INFO is a marker for additional places we #ifdeffed out to remove
+ // references to EnumerateTraceGuidsEx. This symbol is actually not used because
+ // today we use FEATURE_ACTIVITYSAMPLING to determine if this code is there or not.
+ // However we put it in the #if so that we don't lose the fact that this feature
+ // switch is at least partially independent of FEATURE_ACTIVITYSAMPLING
+
List<Tuple<SessionInfo, bool>> sessionsChanged = GetSessions();
foreach (var session in sessionsChanged)
{
filterData = null;
// read filter data only when a session is being *added*
+ byte[] data;
+ int keyIndex;
if (bEnabling &&
GetDataFromController(etwSessionId, filterData, out command, out data, out keyIndex))
{
command = ControllerCommand.SendManifest;
}
else
- return; // per spec you ignore commands you don't recognise.
+ return; // per spec you ignore commands you don't recognize.
if (!skipFinalOnControllerCommand)
OnControllerCommand(command, args, 0, 0);
// (present in the m_liveSessions but not in the new liveSessionList)
if (m_liveSessions != null)
{
- foreach(SessionInfo s in m_liveSessions)
+ foreach (SessionInfo s in m_liveSessions)
{
int idx;
if ((idx = IndexOfSessionInList(liveSessionList, s.etwSessionId)) < 0 ||
if (bitcount(sessionIdBitMask) == 1)
{
// activity-tracing-aware etw session
- sessionList.Add(new SessionInfo(bitindex(sessionIdBitMask)+1, etwSessionId));
+ sessionList.Add(new SessionInfo(bitindex(sessionIdBitMask) + 1, etwSessionId));
}
else
{
// legacy etw session
- sessionList.Add(new SessionInfo(bitcount((uint)SessionMask.All)+1, etwSessionId));
+ sessionList.Add(new SessionInfo(bitcount((uint)SessionMask.All) + 1, etwSessionId));
}
}
[System.Security.SecurityCritical]
private unsafe void GetSessionInfo(Action<int, long> action)
{
+ // We wish the EventSource package to be legal for Windows Store applications.
+ // Currently EnumerateTraceGuidsEx is not an allowed API, so we avoid its use here
+ // and use the information in the registry instead. This means that ETW controllers
+ // that do not publish their intent to the registry (basically all controllers EXCEPT
+ // TraceEventSesion) will not work properly
+
+ // However the framework version of EventSource DOES have ES_SESSION_INFO defined and thus
+ // does not have this issue.
+#if ES_SESSION_INFO
int buffSize = 256; // An initial guess that probably works most of the time.
byte* buffer;
for (; ; )
var structBase = (byte*)providerInstance;
providerInstance = (UnsafeNativeMethods.ManifestEtw.TRACE_PROVIDER_INSTANCE_INFO*)&structBase[providerInstance->NextOffset];
}
+#else
+#if !ES_BUILD_PCL // TODO command arguments don't work on PCL builds...
+ // Determine our session from what is in the registry.
+ string regKey = @"\Microsoft\Windows\CurrentVersion\Winevt\Publishers\{" + m_providerId + "}";
+ if (System.Runtime.InteropServices.Marshal.SizeOf(typeof(IntPtr)) == 8)
+ regKey = @"Software" + @"\Wow6432Node" + regKey;
+ else
+ regKey = @"Software" + regKey;
+
+ var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(regKey);
+ if (key != null)
+ {
+ foreach (string valueName in key.GetValueNames())
+ {
+ if (valueName.StartsWith("ControllerData_Session_"))
+ {
+ string strId = valueName.Substring(23); // strip of the ControllerData_Session_
+ int etwSessionId;
+ if (int.TryParse(strId, out etwSessionId))
+ {
+ // we need to assert this permission for partial trust scenarios
+ (new RegistryPermission(RegistryPermissionAccess.Read, regKey)).Assert();
+ var data = key.GetValue(valueName) as byte[];
+ if (data != null)
+ {
+ var dataAsString = System.Text.Encoding.UTF8.GetString(data);
+ int keywordIdx = dataAsString.IndexOf("EtwSessionKeyword");
+ if (0 <= keywordIdx)
+ {
+ int startIdx = keywordIdx + 18;
+ int endIdx = dataAsString.IndexOf('\0', startIdx);
+ string keywordBitString = dataAsString.Substring(startIdx, endIdx-startIdx);
+ int keywordBit;
+ if (0 < endIdx && int.TryParse(keywordBitString, out keywordBit))
+ action(etwSessionId, 1L << keywordBit);
+ }
+ }
+ }
+ }
+ }
+ }
+#endif
+#endif
}
/// <summary>
data = new byte[filterData->Size];
Marshal.Copy((IntPtr)filterData->Ptr, data, 0, data.Length);
}
- command = (ControllerCommand) filterData->Type;
+ command = (ControllerCommand)filterData->Type;
return true;
}
return false;
}
- // There's a small window of time in EventSource code where m_provider is non-null but the
- // m_regHandle has not been set yet. This method allows EventSource to check if this is the case...
- internal bool IsValid()
- { return m_regHandle != 0; }
-
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
public static WriteEventErrorCode GetLastWriteEventError()
{
/// <param name="eventDescriptor">
/// Event Descriptor for this event.
/// </param>
+ /// <param name="activityID">
+ /// A pointer to the activity ID GUID to log
+ /// </param>
/// <param name="childActivityID">
/// childActivityID is marked as 'related' to the current activity ID.
/// </param>
}
}
- // update argCount based on ctual number of arguments written to 'userData'
+ // update argCount based on actual number of arguments written to 'userData'
argCount = (int)(userDataPtr - userData);
if (totalEventSize > s_traceEventMaximumSize)
/// <param name="eventDescriptor">
/// Event Descriptor for this event.
/// </param>
+ /// <param name="activityID">
+ /// A pointer to the activity ID to log
+ /// </param>
/// <param name="childActivityID">
/// If this event is generating a child activity (WriteEventTransfer related activity) this is child activity
/// This can be null for events that do not generate a child activity.
return status;
}
- static int[] nibblebits = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
+ static int[] nibblebits = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 };
private static int bitcount(uint n)
{
int count = 0;
- for(; n != 0; n = n >> 4)
+ for (; n != 0; n = n >> 4)
count += nibblebits[n & 0x0f];
return count;
}
{
if (m_eventSourceEnabled)
{
- EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
- if (arg1 == null || arg1.Length == 0)
+ if (arg1 == null) arg1 = new byte[0];
+ int blobSize = arg1.Length;
+ fixed (byte* blob = &arg1[0])
{
- int blobSize = 0;
+ EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
descrs[0].DataPointer = (IntPtr)(&blobSize);
descrs[0].Size = 4;
- descrs[1].DataPointer = (IntPtr)(&blobSize); // valid address instead of empty content
- descrs[1].Size = 0;
+ descrs[1].DataPointer = (IntPtr)blob;
+ descrs[1].Size = blobSize;
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);
- }
- }
}
}
{
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)
+ if (arg2 == null) arg2 = new byte[0];
+ int blobSize = arg2.Length;
+ fixed (byte* blob = &arg2[0])
{
- int blobSize = 0;
+ EventSource.EventData* descrs = stackalloc EventSource.EventData[3];
+ descrs[0].DataPointer = (IntPtr)(&arg1);
+ descrs[0].Size = 8;
descrs[1].DataPointer = (IntPtr)(&blobSize);
descrs[1].Size = 4;
- descrs[2].DataPointer = (IntPtr)(&blobSize); // valid address instead of empty contents
- descrs[2].Size = 0;
+ descrs[2].DataPointer = (IntPtr)blob;
+ descrs[2].Size = blobSize;
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);
- }
- }
}
}
}
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),
[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;
}
else if (dataType == typeof(byte*))
{
+ // TODO: how do we want to handle this? For now we ignore it...
return null;
}
else
goto Again;
}
+ // TODO FIX NOW Assuming that it is a string at this point is really likely to be fragile
+ // We should do something better.
+
// Everything else is marshaled as a string.
// ETW strings are NULL-terminated, so marshal everything up to the first
// null in the string.
{
Contract.Assert(m_eventData != null); // You must have initialized this if you enabled the source.
if (childActivityID != null)
+ {
ValidateEventOpcodeForTransfer(ref m_eventData[eventId]);
+ // 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(Environment.GetResourceString("EventSource_NoRelatedActivityId"));
+ }
+ }
+
+ LogEventArgsMismatches(m_eventData[eventId].Parameters, args);
#if FEATURE_MANAGED_ETW
if (m_eventData[eventId].EnabledForETW)
{
}
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),
if (eventTypes == null)
{
eventTypes = new TraceLoggingEventTypes(m_eventData[eventId].Name,
- EventTags.None,
- m_eventData[eventId].Parameters);
+ EventTags.None,
+ m_eventData[eventId].Parameters);
Interlocked.CompareExchange(ref m_eventData[eventId].TraceLoggingEventTypes, eventTypes, null);
}
var eventData = new object[eventTypes.typeInfos.Length];
return eventData;
}
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="infos"></param>
+ /// <param name="args"></param>
+ private void LogEventArgsMismatches(ParameterInfo[] infos, object[] args)
+ {
+#if !ES_BUILD_PCL
+ // 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, Environment.GetResourceString("EventSource_VarArgsParameterMismatch") + "\r\n");
+ }
+#endif //!ES_BUILD_PCL
+ }
+
[SecurityCritical]
unsafe private void WriteToAllListeners(int eventId, Guid* childActivityID, int eventDataCount, EventSource.EventData* data)
{
{
m_EventSourceExceptionRecurenceCount++;
+ // TODO Create variations of EventSourceException that indicate more information using the error code.
switch (EventProvider.GetLastWriteEventError())
{
case EventProvider.WriteEventErrorCode.EventTooBig:
private void ValidateEventOpcodeForTransfer(ref EventMetadata eventData)
{
if ((EventOpcode)eventData.Descriptor.Opcode != EventOpcode.Send &&
- (EventOpcode)eventData.Descriptor.Opcode != EventOpcode.Receive)
+ (EventOpcode)eventData.Descriptor.Opcode != EventOpcode.Receive &&
+ (EventOpcode)eventData.Descriptor.Opcode != EventOpcode.Start)
{
ThrowEventSourceException();
}
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
#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;
+ public ParameterInfo[] Parameters; // TODO can we remove?
public TraceLoggingEventTypes TraceLoggingEventTypes;
public EventActivityOptions ActivityOptions;
{
if (commandArgs.Command == EventCommand.SendManifest)
{
+ // TODO: should we generate the manifest here if we hadn't already?
if (m_rawManifest != null)
SendManifest(m_rawManifest);
}
m_rawManifest = CreateManifestAndDescriptors(this.GetType(), Name, this);
Contract.Assert(m_eventData != null);
+ // TODO Enforce singleton pattern
foreach (WeakReference eventSourceRef in EventListener.s_EventSources)
{
EventSource eventSource = eventSourceRef.Target as EventSource;
manifest.AddKeyword("Session0", (long)0x8000 << 32);
}
- if (eventSourceType.Name != "EventSource")
+ if (eventSourceType != typeof(EventSource))
{
for (int i = 0; i < methods.Length; i++)
{
}
}
- RemoveFirstArgIfRelatedActivityId(ref args);
+ bool hasRelatedActivityID = RemoveFirstArgIfRelatedActivityId(ref args);
if (!(source != null && source.SelfDescribingEvents))
{
manifest.StartEvent(eventName, eventAttribute);
// overwrite inline message with the localized message
if (msg != null) eventAttribute.Message = msg;
- AddEventDescriptor(ref eventData, eventName, eventAttribute, args);
+ AddEventDescriptor(ref eventData, eventName, eventAttribute, args, hasRelatedActivityID);
}
}
}
return bNeedsManifest ? res : null;
}
- private static void RemoveFirstArgIfRelatedActivityId(ref ParameterInfo[] args)
+ 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) &&
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'
// 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)
+ EventAttribute eventAttribute, ParameterInfo[] eventParameters,
+ bool hasRelatedActivityID)
{
if (eventData == null || eventData.Length <= eventAttribute.EventId)
{
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
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)
}
// 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)
{
eventsByName = new Dictionary<string, string>();
if (eventsByName.ContainsKey(evtName))
- manifest.ManifestError(Environment.GetResourceString("EventSource_EventNameReused", evtName));
+ manifest.ManifestError(Environment.GetResourceString("EventSource_EventNameReused", evtName), true);
eventsByName[evtName] = evtName;
}
/// </summary>
public abstract class EventListener : IDisposable
{
+ private event EventHandler<EventSourceCreatedEventArgs> _EventSourceCreated;
+
/// <summary>
- /// Create a new EventListener in which all events start off turned off (use EnableEvents to turn
- /// them on).
+ /// 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.
/// </summary>
- protected EventListener()
- {
- lock (EventListenersLock)
+ public event EventHandler<EventSourceCreatedEventArgs> EventSourceCreated
+ {
+ add
{
- // Disallow creating EventListener reentrancy.
- if (s_CreatingListener)
- throw new InvalidOperationException(Environment.GetResourceString("EventSource_ListenerCreatedInsideCallback"));
-
- try
- {
- s_CreatingListener = true;
+ CallBackForExistingEventSources(false, value);
- // 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();
+ this._EventSourceCreated = (EventHandler<EventSourceCreatedEventArgs>)Delegate.Combine(_EventSourceCreated, value);
+ }
+ remove
+ {
+ this._EventSourceCreated = (EventHandler<EventSourceCreatedEventArgs>)Delegate.Remove(_EventSourceCreated, value);
+ }
+ }
- for (int i = 0; i < eventSourcesSnapshot.Length; i++)
- {
- WeakReference eventSourceRef = eventSourcesSnapshot[i];
- EventSource eventSource = eventSourceRef.Target as EventSource;
- if (eventSource != null)
- eventSource.AddListener(this); // This will cause the OnEventSourceCreated callback to fire.
- }
+ /// <summary>
+ /// This event is raised whenever an event has been written by a EventSource for which
+ /// the EventListener has enabled events.
+ /// </summary>
+ public event EventHandler<EventWrittenEventArgs> EventWritten;
- Validate();
- }
- finally
- {
- s_CreatingListener = false;
- }
- }
+ /// <summary>
+ /// Create a new EventListener in which all events start off turned off (use EnableEvents to turn
+ /// them on).
+ /// </summary>
+ protected EventListener()
+ {
+ // This will cause the OnEventSourceCreated callback to fire.
+ CallBackForExistingEventSources(true, (obj, args) => args.EventSource.AddListener(this) );
}
+
/// <summary>
/// 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
/// for a particular eventSource to occur BEFORE the OnEventSourceCreated is issued.
/// </summary>
/// <param name="eventSource"></param>
- internal protected virtual void OnEventSourceCreated(EventSource eventSource) { }
+ internal protected virtual void OnEventSourceCreated(EventSource eventSource)
+ {
+ EventHandler<EventSourceCreatedEventArgs> callBack = this._EventSourceCreated;
+ if(callBack != null)
+ {
+ EventSourceCreatedEventArgs args = new EventSourceCreatedEventArgs();
+ args.EventSource = eventSource;
+ callBack(this, args);
+ }
+ }
+
/// <summary>
/// This method is called whenever an event has been written by a EventSource for which
/// the EventListener has enabled events.
/// </summary>
- internal protected abstract void OnEventWritten(EventWrittenEventArgs eventData);
+ /// <param name="eventData"></param>
+ internal protected virtual void OnEventWritten(EventWrittenEventArgs eventData)
+ {
+ EventHandler<EventWrittenEventArgs> callBack = this.EventWritten;
+ if (callBack != null)
+ {
+ callBack(this, eventData);
+ }
+ }
+
#region private
/// <summary>
// See bug 724140 for more
private static void DisposeOnShutdown(object sender, EventArgs e)
{
- foreach (var esRef in s_EventSources)
+ lock(EventListenersLock)
{
- EventSource es = esRef.Target as EventSource;
- if (es != null)
- es.Dispose();
+ foreach (var esRef in s_EventSources)
+ {
+ EventSource es = esRef.Target as EventSource;
+ if (es != null)
+ es.Dispose();
+ }
}
}
}
}
+ private void CallBackForExistingEventSources(bool addToListenersList, EventHandler<EventSourceCreatedEventArgs> callback)
+ {
+ lock (EventListenersLock)
+ {
+ // Disallow creating EventListener reentrancy.
+ if (s_CreatingListener)
+ throw new InvalidOperationException(Environment.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
}
/// <summary>
+ /// EventSourceCreatedEventArgs is passed to <see cref="EventListener.EventSourceCreated"/>
+ /// </summary>
+ public class EventSourceCreatedEventArgs : EventArgs
+ {
+ /// <summary>
+ /// The EventSource that is attaching to the listener.
+ /// </summary>
+ public EventSource EventSource
+ {
+ get;
+ internal set;
+ }
+ }
+
+ /// <summary>
/// EventWrittenEventArgs is passed to the user-provided override for
/// <see cref="EventListener.OnEventWritten"/> when an event is fired.
/// </summary>
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
/// <summary>
/// EventChannelAttribute allows customizing channels supported by an EventSource. This attribute must be
/// </summary>
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; }
}
/// <summary>
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)
{
if (this.channelTab == null)
{
- return Array.Empty<ulong>();
+ return new ulong[0];
}
// We create an array indexed by the channel id for fast look up.
}
numParams++;
templates.Append(" <data name=\"").Append(name).Append("\" inType=\"").Append(GetTypeName(type)).Append("\"");
-
+ // TODO: for 'byte*' types it assumes the user provided length is named using the same naming convention
+ // as for 'byte[]' args (blob_arg_name + "Size")
if ((type.IsArray || type.IsPointer) && type.GetElementType() == typeof(byte))
{
// add "length" attribute to the "blob" field in the template (referencing the field added above)
stringBuilder.Append(" message=\"$(string.").Append(key).Append(")\"");
string prevValue;
- if (stringTab.TryGetValue(key, out prevValue))
+ if (stringTab.TryGetValue(key, out prevValue) && !prevValue.Equals(value))
+ {
ManifestError(Environment.GetResourceString("EventSource_DuplicateStringKey", key), true);
- else
- stringTab.Add(key, value);
+ return;
+ }
+
+ stringTab[key] = value;
}
internal string GetLocalizedMessage(string key, CultureInfo ci, bool etwFormat)
{
if (this.bufferNesting == 0)
{
+ /*
+ TODO (perf): consider coalescing adjacent buffered regions into a
+ single buffer, similar to what we're already doing for adjacent
+ scalars. In addition, if a type contains a buffered region adjacent
+ to a blittable array, and the blittable array is small, it would be
+ more efficient to buffer the array instead of pinning it.
+ */
+
this.EnsureBuffer();
this.PinArray(this.buffer, this.bufferPos);
this.buffer = null;
/// Gets or sets the name to use for the field. This defaults to null.
/// If null, the name of the corresponding property will be used
/// as the event field's name.
+ /// TODO REMOVE
/// </summary>
internal string Name
{
{
collector.AddBinary(value);
}
+
+ public override object GetData(object value)
+ {
+ object val = base.GetData(value);
+ if (null == val)
+ val = "";
+
+ return val;
+ }
}
/// <summary>