public static string Path(ActivityInfo?activityInfo)
 {
     if (activityInfo == null)
     {
         return("");
     }
     return(Path(activityInfo.m_creator) + "/" + activityInfo.m_uniqueId.ToString());
 }
 public static string LiveActivities(ActivityInfo?list)
 {
     if (list == null)
     {
         return("");
     }
     return(list.ToString() + ";" + LiveActivities(list.m_creator));
 }
Exemple #3
0
 public static string Path(ActivityInfo?activityInfo)
 {
     if (activityInfo == null)
     {
         return("");
     }
     return($"{Path(activityInfo.m_creator)}/{activityInfo.m_uniqueId}");
 }
            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);
            }
        /// <summary>
        /// Searched for a active (nonstopped) activity with the given name.  Returns null if not found.
        /// </summary>
        private static ActivityInfo?FindActiveActivity(string name, ActivityInfo?startLocation)
        {
            ActivityInfo?activity = startLocation;

            while (activity != null)
            {
                if (name == activity.m_name && activity.m_stopped == 0)
                {
                    return(activity);
                }
                activity = activity.m_creator;
            }
            return(null);
        }
            /// <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 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>
            private unsafe void CreateOverflowGuid(Guid *outPtr)
            {
                // Search backwards for an ancestor that has sufficient space to put the ID.
                for (ActivityInfo?ancestor = m_creator; ancestor != null; ancestor = ancestor.m_creator)
                {
                    if (ancestor.m_activityPathGuidOffset <= 10)                                       // we need at least 2 bytes.
                    {
                        uint id = unchecked ((uint)Interlocked.Increment(ref ancestor.m_lastChildID)); // Get a unique ID
                        // Try to put the ID into the GUID
                        *   outPtr = ancestor.m_guid;
                        int endId  = AddIdToGuid(outPtr, ancestor.m_activityPathGuidOffset, id, true);

                        // Does it fit?
                        if (endId <= 12)
                        {
                            break;
                        }
                    }
                }
            }
Exemple #7
0
        // This callback is used to initialize the m_current AsyncLocal Variable.
        // Its job is to keep the ETW Activity ID (part of thread local storage) in sync
        // with m_current.ActivityID
        //
        // WARNING: When mixing manual usage of EventSource.SetCurrentThreadActivityID
        // and Start/Stop EventSource events I can't identify a clear design how this
        // synchronization is intended to work. For example code that changes
        // SetCurrentThreadActivityID after a FooStart() event will not flow the
        // explicit ID with the async work, but if FooStart() event is called after
        // SetCurrentThreadActivityID then the explicit ID change does flow.
        // For now I've adopted the approach:
        // Priority 1: Make the API predictable/sensible when only Start/Stop events
        // are in use.
        // Priority 2: If users aren't complaining and it doesn't interfere with
        // goal #1, try to preserve the arbitrary/buggy? existing behavior
        // for mixed usage of SetActivityID + events.
        //
        // For scenarios that only use start/stop events this is what we expect:
        // calling start -> push new ID on stack and update thread-local to match new ID
        // calling stop -> pop ID from stack and update thread-local to match new topmost
        //                 still active ID. If there is none, set ID to zero
        // thread swap -> update thread-local to match topmost active ID.
        //                 If there is none, set ID to zero.
        private void ActivityChanging(AsyncLocalValueChangedArgs <ActivityInfo?> args)
        {
            ActivityInfo?cur  = args.CurrentValue;
            ActivityInfo?prev = args.PreviousValue;

            // Special case only relevant for mixed SetActivityID usage:
            //
            // Are we MAYBE popping off a value?   (we have a prev, and it creator is cur)
            // We can't be certain this is a pop because a thread swapping between two
            // ExecutionContexts can also appear the same way.
            // 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;
                }
            }

            // Set the activity to current ActivityInfo.  However that activity
            // might be dead, in which case we should skip it, so we never set
            // the ID to dead things.
            while (cur != null)
            {
                // We found a live activity (typically the first time), set it to that.
                if (cur.m_stopped == 0)
                {
                    EventSource.SetCurrentThreadActivityId(cur.ActivityId);
                    return;
                }
                cur = cur.m_creator;
            }

            // we can get here if there is no information on our activity stack (everything is dead)
            // Set ActivityID to zero
            EventSource.SetCurrentThreadActivityId(Guid.Empty);
        }
        // This callback is used to initialize the m_current AsyncLocal Variable.
        // Its job is to keep the ETW Activity ID (part of thread local storage) in sync
        // with m_current.ActivityID
        private void ActivityChanging(AsyncLocalValueChangedArgs <ActivityInfo?> args)
        {
            ActivityInfo?cur  = args.CurrentValue;
            ActivityInfo?prev = args.PreviousValue;

            // Are we popping off a value?   (we have a prev, and it creator is cur)
            // Then check if we should use the GUID at the time of the start event
            if (prev != null && prev.m_creator == cur)
            {
                // If the saved activity ID is not the same as the creator activity
                // that takes precedence (it means someone explicitly did a SetActivityID)
                // Set it to that and get out
                if (cur == null || prev.m_activityIdToRestore != cur.ActivityId)
                {
                    EventSource.SetCurrentThreadActivityId(prev.m_activityIdToRestore);
                    return;
                }
            }

            // OK we did not have an explicit SetActivityID set.   Then we should be
            // setting the activity to current ActivityInfo.  However that activity
            // might be dead, in which case we should skip it, so we never set
            // the ID to dead things.
            while (cur != null)
            {
                // We found a live activity (typically the first time), set it to that.
                if (cur.m_stopped == 0)
                {
                    EventSource.SetCurrentThreadActivityId(cur.ActivityId);
                    return;
                }
                cur = cur.m_creator;
            }
            // we can get here if there is no information on our activity stack (everything is dead)
            // currently we do nothing, as that seems better than setting to Guid.Emtpy.
        }
        /// <summary>
        /// Called on work item begins.  The activity name = providerName + activityName without 'Start' suffix.
        /// It updates CurrentActivityId to track.
        ///
        /// It returns true if the Start should be logged, otherwise (if it is illegal recursion) it return false.
        ///
        /// The start event should use as its activity ID the CurrentActivityId AFTER calling this routine and its
        /// RelatedActivityID the CurrentActivityId BEFORE calling this routine (the creator).
        ///
        /// If activity tracing is not on, then activityId and relatedActivityId are not set
        /// </summary>
        public void OnStart(string providerName, string activityName, int task, ref Guid activityId, ref Guid relatedActivityId, EventActivityOptions options, bool useTplSource = true)
        {
            if (m_current == null)        // We are not enabled
            {
                // We  used to rely on the TPL provider turning us on, but that has the disadvantage that you don't get Start-Stop tracking
                // until you use Tasks for the first time (which you may never do).   Thus we change it to pull rather tan push for whether
                // we are enabled.
                if (m_checkedForEnable)
                {
                    return;
                }
                m_checkedForEnable = true;
                if (useTplSource && TplEventSource.Log.IsEnabled(EventLevel.Informational, TplEventSource.Keywords.TasksFlowActivityIds))
                {
                    Enable();
                }
                if (m_current == null)
                {
                    return;
                }
            }

            Debug.Assert((options & EventActivityOptions.Disable) == 0);

            ActivityInfo?currentActivity  = m_current.Value;
            string       fullActivityName = NormalizeActivityName(providerName, activityName, task);

            TplEventSource?log      = useTplSource ? TplEventSource.Log : null;
            bool           tplDebug = log != null && log.Debug;

            if (tplDebug)
            {
                log !.DebugFacilityMessage("OnStartEnter", fullActivityName);
                log !.DebugFacilityMessage("OnStartEnterActivityState", ActivityInfo.LiveActivities(currentActivity));
            }

            if (currentActivity != null)
            {
                // Stop activity tracking if we reached the maximum allowed depth
                if (currentActivity.m_level >= MAX_ACTIVITY_DEPTH)
                {
                    activityId        = Guid.Empty;
                    relatedActivityId = Guid.Empty;
                    if (tplDebug)
                    {
                        log !.DebugFacilityMessage("OnStartRET", "Fail");
                    }
                    return;
                }
                // Check for recursion, and force-stop any activities if the activity already started.
                if ((options & EventActivityOptions.Recursive) == 0)
                {
                    ActivityInfo?existingActivity = FindActiveActivity(fullActivityName, currentActivity);
                    if (existingActivity != null)
                    {
                        OnStop(providerName, activityName, task, ref activityId);
                        currentActivity = m_current.Value;
                    }
                }
            }

            // Get a unique ID for this activity.
            long id;

            if (currentActivity == null)
            {
                id = Interlocked.Increment(ref m_nextId);
            }
            else
            {
                id = Interlocked.Increment(ref currentActivity.m_lastChildID);
            }

            // The previous ID is my 'causer' and becomes my related activity ID
            relatedActivityId = EventSource.CurrentThreadActivityId;

            // Add to the list of started but not stopped activities.
            ActivityInfo newActivity = new ActivityInfo(fullActivityName, id, currentActivity, relatedActivityId, options);

            m_current.Value = newActivity;

            // Remember the current ID so we can log it
            activityId = newActivity.ActivityId;

            if (tplDebug)
            {
                log !.DebugFacilityMessage("OnStartRetActivityState", ActivityInfo.LiveActivities(newActivity));
                log !.DebugFacilityMessage1("OnStartRet", activityId.ToString(), relatedActivityId.ToString());
            }
        }
        /// <summary>
        /// Called when a work item stops.  The activity name = providerName + activityName without 'Stop' suffix.
        /// It updates m_current variable to track this fact.   The Stop event associated with stop should log the ActivityID associated with the event.
        ///
        /// If activity tracing is not on, then activityId and relatedActivityId are not set
        /// </summary>
        public void OnStop(string providerName, string activityName, int task, ref Guid activityId, bool useTplSource = true)
        {
            if (m_current == null)        // We are not enabled
            {
                return;
            }

            string fullActivityName = NormalizeActivityName(providerName, activityName, task);

            TplEventSource?log      = useTplSource ? TplEventSource.Log : null;
            bool           tplDebug = log != null && log.Debug;

            if (tplDebug)
            {
                log !.DebugFacilityMessage("OnStopEnter", fullActivityName);
                log !.DebugFacilityMessage("OnStopEnterActivityState", ActivityInfo.LiveActivities(m_current.Value));
            }

            while (true) // This is a retry loop.
            {
                ActivityInfo?currentActivity    = m_current.Value;
                ActivityInfo?newCurrentActivity = null;                // if we have seen any live activities (orphans), at he first one we have seen.

                // Search to find the activity to stop in one pass.   This insures that we don't let one mistake
                // (stopping something that was not started) cause all active starts to be stopped
                // By first finding the target start to stop we are more robust.
                ActivityInfo?activityToStop = FindActiveActivity(fullActivityName, currentActivity);

                // ignore stops where we can't find a start because we may have popped them previously.
                if (activityToStop == null)
                {
                    activityId = Guid.Empty;
                    // TODO add some logging about this. Basically could not find matching start.
                    if (tplDebug)
                    {
                        log !.DebugFacilityMessage("OnStopRET", "Fail");
                    }
                    return;
                }

                activityId = activityToStop.ActivityId;

                // See if there are any orphans that need to be stopped.
                ActivityInfo?orphan = currentActivity;
                while (orphan != activityToStop && orphan != null)
                {
                    if (orphan.m_stopped != 0)      // Skip dead activities.
                    {
                        orphan = orphan.m_creator;
                        continue;
                    }
                    if (orphan.CanBeOrphan())
                    {
                        // We can't pop anything after we see a valid orphan, remember this for later when we update m_current.
                        newCurrentActivity ??= orphan;
                    }
                    else
                    {
                        orphan.m_stopped = 1;
                        Debug.Assert(orphan.m_stopped != 0);
                    }
                    orphan = orphan.m_creator;
                }

                // try to Stop the activity atomically.  Other threads may be trying to do this as well.
                if (Interlocked.CompareExchange(ref activityToStop.m_stopped, 1, 0) == 0)
                {
                    // I succeeded stopping this activity. Now we update our m_current pointer

                    // If I haven't yet determined the new current activity, it is my creator.
                    newCurrentActivity ??= activityToStop.m_creator;

                    m_current.Value = newCurrentActivity;

                    if (tplDebug)
                    {
                        log !.DebugFacilityMessage("OnStopRetActivityState", ActivityInfo.LiveActivities(newCurrentActivity));
                        log !.DebugFacilityMessage("OnStopRet", activityId.ToString());
                    }
                    return;
                }
                // We failed to stop it.  We must have hit a race to stop it.  Just start over and try again.
            }
        }