1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System;
6 using System.Diagnostics;
7 using System.Threading;
8 #if !ES_BUILD_AGAINST_DOTNET_V35
9 using Contract = System.Diagnostics.Contracts.Contract;
10 #else
11 using Contract = Microsoft.Diagnostics.Contracts.Internal.Contract;
12 #endif
13 
14 #if ES_BUILD_STANDALONE
15 namespace Microsoft.Diagnostics.Tracing
16 #else
17 using System.Threading.Tasks;
18 namespace System.Diagnostics.Tracing
19 #endif
20 {
21     /// <summary>
22     /// Tracks activities.  This is meant to be a singleton (accessed by the ActivityTracer.Instance static property)
23     ///
24     /// Logically this is simply holds the m_current variable that holds the async local that holds the current ActivityInfo
25     /// An ActivityInfo is represents a activity (which knows its creator and thus knows its path).
26     ///
27     /// Most of the magic is in the async local (it gets copied to new tasks)
28     ///
29     /// On every start event call OnStart
30     ///
31     ///     Guid activityID;
32     ///     Guid relatedActivityID;
33     ///     if (OnStart(activityName, out activityID, out relatedActivityID, ForceStop, options))
34     ///         // Log Start event with activityID and relatedActivityID
35     ///
36     /// On every stop event call OnStop
37     ///
38     ///     Guid activityID;
39     ///     if (OnStop(activityName, ref activityID  ForceStop))
40     ///         // Stop event with activityID
41     ///
42     /// On any normal event log the event with activityTracker.CurrentActivityId
43     /// </summary>
44     internal class ActivityTracker
45     {
46 
47         /// <summary>
48         /// Called on work item begins.  The activity name = providerName + activityName without 'Start' suffix.
49         /// It updates CurrentActivityId to track.
50         ///
51         /// It returns true if the Start should be logged, otherwise (if it is illegal recursion) it return false.
52         ///
53         /// The start event should use as its activity ID the CurrentActivityId AFTER calling this routine and its
54         /// RelatedActivityID the CurrentActivityId BEFORE calling this routine (the creator).
55         ///
56         /// If activity tracing is not on, then activityId and relatedActivityId are not set
57         /// </summary>
OnStart(string providerName, string activityName, int task, ref Guid activityId, ref Guid relatedActivityId, EventActivityOptions options)58         public void OnStart(string providerName, string activityName, int task, ref Guid activityId, ref Guid relatedActivityId, EventActivityOptions options)
59         {
60             if (m_current == null)        // We are not enabled
61             {
62                 // We  used to rely on the TPL provider turning us on, but that has the disadvantage that you don't get Start-Stop tracking
63                 // until you use Tasks for the first time (which you may never do).   Thus we change it to pull rather tan push for whether
64                 // we are enabled.
65                 if (m_checkedForEnable)
66                     return;
67                 m_checkedForEnable = true;
68                 if (TplEtwProvider.Log.IsEnabled(EventLevel.Informational, TplEtwProvider.Keywords.TasksFlowActivityIds))
69                     Enable();
70                 if (m_current == null)
71                     return;
72             }
73 
74 
75             Debug.Assert((options & EventActivityOptions.Disable) == 0);
76 
77             var currentActivity = m_current.Value;
78             var fullActivityName = NormalizeActivityName(providerName, activityName, task);
79 
80             var etwLog = TplEtwProvider.Log;
81             if (etwLog.Debug)
82             {
83                 etwLog.DebugFacilityMessage("OnStartEnter", fullActivityName);
84                 etwLog.DebugFacilityMessage("OnStartEnterActivityState", ActivityInfo.LiveActivities(currentActivity));
85             }
86 
87             if (currentActivity != null)
88             {
89                 // Stop activity tracking if we reached the maximum allowed depth
90                 if (currentActivity.m_level >= MAX_ACTIVITY_DEPTH)
91                 {
92                     activityId = Guid.Empty;
93                     relatedActivityId = Guid.Empty;
94                     if (etwLog.Debug)
95                         etwLog.DebugFacilityMessage("OnStartRET", "Fail");
96                     return;
97                 }
98                 // Check for recursion, and force-stop any activities if the activity already started.
99                 if ((options & EventActivityOptions.Recursive) == 0)
100                 {
101                     ActivityInfo existingActivity = FindActiveActivity(fullActivityName, currentActivity);
102                     if (existingActivity != null)
103                     {
104                         OnStop(providerName, activityName, task, ref activityId);
105                         currentActivity = m_current.Value;
106                     }
107                 }
108             }
109 
110             // Get a unique ID for this activity.
111             long id;
112             if (currentActivity == null)
113                 id = Interlocked.Increment(ref m_nextId);
114             else
115                 id = Interlocked.Increment(ref currentActivity.m_lastChildID);
116 
117             // The previous ID is my 'causer' and becomes my related activity ID
118             relatedActivityId = EventSource.CurrentThreadActivityId;
119 
120             // Add to the list of started but not stopped activities.
121             ActivityInfo newActivity = new ActivityInfo(fullActivityName, id, currentActivity, relatedActivityId, options);
122             m_current.Value = newActivity;
123 
124             // Remember the current ID so we can log it
125             activityId = newActivity.ActivityId;
126 
127             if (etwLog.Debug)
128             {
129                 etwLog.DebugFacilityMessage("OnStartRetActivityState", ActivityInfo.LiveActivities(newActivity));
130                 etwLog.DebugFacilityMessage1("OnStartRet", activityId.ToString(), relatedActivityId.ToString());
131             }
132         }
133 
134         /// <summary>
135         /// Called when a work item stops.  The activity name = providerName + activityName without 'Stop' suffix.
136         /// It updates m_current variable to track this fact.   The Stop event associated with stop should log the ActivityID associated with the event.
137         ///
138         /// If activity tracing is not on, then activityId and relatedActivityId are not set
139         /// </summary>
OnStop(string providerName, string activityName, int task, ref Guid activityId)140         public void OnStop(string providerName, string activityName, int task, ref Guid activityId)
141         {
142             if (m_current == null)        // We are not enabled
143                 return;
144 
145             var fullActivityName = NormalizeActivityName(providerName, activityName, task);
146 
147             var etwLog = TplEtwProvider.Log;
148             if (etwLog.Debug)
149             {
150                 etwLog.DebugFacilityMessage("OnStopEnter", fullActivityName);
151                 etwLog.DebugFacilityMessage("OnStopEnterActivityState", ActivityInfo.LiveActivities(m_current.Value));
152             }
153 
154             for (; ; ) // This is a retry loop.
155             {
156                 ActivityInfo currentActivity = m_current.Value;
157                 ActivityInfo newCurrentActivity = null;               // if we have seen any live activities (orphans), at he first one we have seen.
158 
159                 // Search to find the activity to stop in one pass.   This insures that we don't let one mistake
160                 // (stopping something that was not started) cause all active starts to be stopped
161                 // By first finding the target start to stop we are more robust.
162                 ActivityInfo activityToStop = FindActiveActivity(fullActivityName, currentActivity);
163 
164                 // ignore stops where we can't find a start because we may have popped them previously.
165                 if (activityToStop == null)
166                 {
167                     activityId = Guid.Empty;
168                     // TODO add some logging about this. Basically could not find matching start.
169                     if (etwLog.Debug)
170                         etwLog.DebugFacilityMessage("OnStopRET", "Fail");
171                     return;
172                 }
173 
174                 activityId = activityToStop.ActivityId;
175 
176                 // See if there are any orphans that need to be stopped.
177                 ActivityInfo orphan = currentActivity;
178                 while (orphan != activityToStop && orphan != null)
179                 {
180                     if (orphan.m_stopped != 0)      // Skip dead activities.
181                     {
182                         orphan = orphan.m_creator;
183                         continue;
184                     }
185                     if (orphan.CanBeOrphan())
186                     {
187                         // We can't pop anything after we see a valid orphan, remember this for later when we update m_current.
188                         if (newCurrentActivity == null)
189                             newCurrentActivity = orphan;
190                     }
191                     else
192                     {
193                         orphan.m_stopped = 1;
194                         Debug.Assert(orphan.m_stopped != 0);
195                     }
196                     orphan = orphan.m_creator;
197                 }
198 
199                 // try to Stop the activity atomically.  Other threads may be trying to do this as well.
200                 if (Interlocked.CompareExchange(ref activityToStop.m_stopped, 1, 0) == 0)
201                 {
202                     // I succeeded stopping this activity. Now we update our m_current pointer
203 
204                     // If I haven't yet determined the new current activity, it is my creator.
205                     if (newCurrentActivity == null)
206                         newCurrentActivity = activityToStop.m_creator;
207 
208                     m_current.Value = newCurrentActivity;
209 
210                     if (etwLog.Debug)
211                     {
212                         etwLog.DebugFacilityMessage("OnStopRetActivityState", ActivityInfo.LiveActivities(newCurrentActivity));
213                         etwLog.DebugFacilityMessage("OnStopRet", activityId.ToString());
214                     }
215                     return;
216                 }
217                 // We failed to stop it.  We must have hit a race to stop it.  Just start over and try again.
218             }
219         }
220 
221         /// <summary>
222         /// Turns on activity tracking.    It is sticky, once on it stays on (race issues otherwise)
223         /// </summary>
Enable()224         public void Enable()
225         {
226             if (m_current == null)
227             {
228                 // Catch the not Implemented
229                 try
230                 {
231                     m_current = new AsyncLocal<ActivityInfo>(ActivityChanging);
232                 }
233                 catch (NotImplementedException) {
234 #if (!ES_BUILD_PCL && ! ES_BUILD_PN)
235                     // send message to debugger without delay
236                     System.Diagnostics.Debugger.Log(0, null, "Activity Enabled() called but AsyncLocals Not Supported (pre V4.6).  Ignoring Enable");
237 #endif
238                 }
239             }
240         }
241 
242         /// <summary>
243         /// An activity tracker is a singleton, this is how you get the one and only instance.
244         /// </summary>
245         public static ActivityTracker Instance { get { return s_activityTrackerInstance; } }
246 
247 
248         #region private
249 
250         /// <summary>
251         /// The current activity ID.  Use this to log normal events.
252         /// </summary>
253         private Guid CurrentActivityId { get { return m_current.Value.ActivityId; } }
254 
255         /// <summary>
256         /// Searched for a active (nonstopped) activity with the given name.  Returns null if not found.
257         /// </summary>
FindActiveActivity(string name, ActivityInfo startLocation)258         private ActivityInfo FindActiveActivity(string name, ActivityInfo startLocation)
259         {
260             var activity = startLocation;
261             while (activity != null)
262             {
263                 if (name == activity.m_name && activity.m_stopped == 0)
264                     return activity;
265                 activity = activity.m_creator;
266             }
267             return null;
268         }
269 
270         /// <summary>
271         /// Strip out "Start" or "End" suffix from activity name and add providerName prefix.
272         /// If 'task'  it does not end in Start or Stop and Task is non-zero use that as the name of the activity
273         /// </summary>
NormalizeActivityName(string providerName, string activityName, int task)274         private string NormalizeActivityName(string providerName, string activityName, int task)
275         {
276             if (activityName.EndsWith(EventSource.s_ActivityStartSuffix, StringComparison.Ordinal))
277                 activityName = activityName.Substring(0, activityName.Length - EventSource.s_ActivityStartSuffix.Length);
278             else if (activityName.EndsWith(EventSource.s_ActivityStopSuffix, StringComparison.Ordinal))
279                 activityName = activityName.Substring(0, activityName.Length - EventSource.s_ActivityStopSuffix.Length);
280             else if (task != 0)
281                 activityName = "task" + task.ToString();
282 
283             // We use provider name to distinguish between activities from different providers.
284             return providerName + activityName;
285         }
286 
287         // *******************************************************************************
288         /// <summary>
289         /// An ActivityInfo represents a particular activity.   It is almost read-only.   The only
290         /// fields that change after creation are
291         ///    m_lastChildID - used to generate unique IDs for the children activities and for the most part can be ignored.
292         ///    m_stopped - indicates that this activity is dead
293         /// This read-only-ness is important because an activity's  m_creator chain forms the
294         /// 'Path of creation' for the activity (which is also its unique ID) but is also used as
295         /// the 'list of live parents' which indicate of those ancestors, which are alive (if they
296         /// are not marked dead they are alive).
297         /// </summary>
298         private class ActivityInfo
299         {
ActivityInfo(string name, long uniqueId, ActivityInfo creator, Guid activityIDToRestore, EventActivityOptions options)300             public ActivityInfo(string name, long uniqueId, ActivityInfo creator, Guid activityIDToRestore, EventActivityOptions options)
301             {
302                 m_name = name;
303                 m_eventOptions = options;
304                 m_creator = creator;
305                 m_uniqueId = uniqueId;
306                 m_level = creator != null ? creator.m_level + 1 : 0;
307                 m_activityIdToRestore = activityIDToRestore;
308 
309                 // Create a nice GUID that encodes the chain of activities that started this one.
310                 CreateActivityPathGuid(out m_guid, out m_activityPathGuidOffset);
311             }
312 
313             public Guid ActivityId
314             {
315                 get
316                 {
317                     return m_guid;
318                 }
319             }
320 
Path(ActivityInfo activityInfo)321             public static string Path(ActivityInfo activityInfo)
322             {
323                 if (activityInfo == null)
324                     return ("");
325                 return Path(activityInfo.m_creator) + "/" + activityInfo.m_uniqueId.ToString();
326             }
327 
ToString()328             public override string ToString()
329             {
330                 return m_name + "(" + Path(this) + (m_stopped != 0 ? ",DEAD)" : ")");
331             }
332 
LiveActivities(ActivityInfo list)333             public static string LiveActivities(ActivityInfo list)
334             {
335                 if (list == null)
336                     return "";
337                 return list.ToString() + ";" + LiveActivities(list.m_creator);
338             }
339 
CanBeOrphan()340             public bool CanBeOrphan()
341             {
342                 if ((m_eventOptions & EventActivityOptions.Detachable) != 0)
343                     return true;
344                 return false;
345             }
346 
347             #region private
348 
349             #region CreateActivityPathGuid
350             /// <summary>
351             /// Logically every activity Path (see Path()) that describes the activities that caused this
352             /// (rooted in an activity that predates activity tracking.
353             ///
354             /// We wish to encode this path in the Guid to the extent that we can.  Many of the paths have
355             /// many small numbers in them and we take advantage of this in the encoding to output as long
356             /// a path in the GUID as possible.
357             ///
358             /// Because of the possibility of GUID collision, we only use 96 of the 128 bits of the GUID
359             /// for encoding the path.  The last 32 bits are a simple checksum (and random number) that
360             /// identifies this as using the convention defined here.
361             ///
362             /// It returns both the GUID which has the path as well as the offset that points just beyond
363             /// the end of the activity (so it can be appended to).  Note that if the end is in a nibble
364             /// (it uses nibbles instead of bytes as the unit of encoding, then it will point at the unfinished
365             /// byte (since the top nibble can't be zero you can determine if this is true by seeing if
366             /// this byte is nonZero.   This offset is needed to efficiently create the ID for child activities.
367             /// </summary>
CreateActivityPathGuid(out Guid idRet, out int activityPathGuidOffset)368             private unsafe void CreateActivityPathGuid(out Guid idRet, out int activityPathGuidOffset)
369             {
370                 fixed (Guid* outPtr = &idRet)
371                 {
372                     int activityPathGuidOffsetStart = 0;
373                     if (m_creator != null)
374                     {
375                         activityPathGuidOffsetStart = m_creator.m_activityPathGuidOffset;
376                         idRet = m_creator.m_guid;
377                     }
378                     else
379                     {
380                         // TODO FIXME - differentiate between AD inside PCL
381                         int appDomainID = 0;
382 #if (!ES_BUILD_STANDALONE && !ES_BUILD_PN)
383                         appDomainID = System.Threading.Thread.GetDomainID();
384 #endif
385                         // We start with the appdomain number to make this unique among appdomains.
386                         activityPathGuidOffsetStart = AddIdToGuid(outPtr, activityPathGuidOffsetStart, (uint)appDomainID);
387                     }
388 
389                     activityPathGuidOffset = AddIdToGuid(outPtr, activityPathGuidOffsetStart, (uint)m_uniqueId);
390 
391 
392                     // If the path does not fit, Make a GUID by incrementing rather than as a path, keeping as much of the path as possible
393                     if (12 < activityPathGuidOffset)
394                         CreateOverflowGuid(outPtr);
395                 }
396             }
397 
398             /// <summary>
399             /// If we can't fit the activity Path into the GUID we come here.   What we do is simply
400             /// generate a 4 byte number (s_nextOverflowId).  Then look for an ancestor that has
401             /// sufficient space for this ID.   By doing this, we preserve the fact that this activity
402             /// is a child (of unknown depth) from that ancestor.
403             /// </summary>
CreateOverflowGuid(Guid* outPtr)404             private unsafe void CreateOverflowGuid(Guid* outPtr)
405             {
406                 // Search backwards for an ancestor that has sufficient space to put the ID.
407                 for (ActivityInfo ancestor = m_creator; ancestor != null; ancestor = ancestor.m_creator)
408                 {
409                     if (ancestor.m_activityPathGuidOffset <= 10)  // we need at least 2 bytes.
410                     {
411                         uint id = unchecked((uint)Interlocked.Increment(ref ancestor.m_lastChildID));        // Get a unique ID
412                         // Try to put the ID into the GUID
413                         *outPtr = ancestor.m_guid;
414                         int endId = AddIdToGuid(outPtr, ancestor.m_activityPathGuidOffset, id, true);
415 
416                         // Does it fit?
417                         if (endId <= 12)
418                             break;
419                     }
420                 }
421             }
422 
423             /// <summary>
424             /// The encoding for a list of numbers used to make Activity  GUIDs.   Basically
425             /// we operate on nibbles (which are nice because they show up as hex digits).  The
426             /// list is ended with a end nibble (0) and depending on the nibble value (Below)
427             /// the value is either encoded into nibble itself or it can spill over into the
428             /// bytes that follow.
429             /// </summary>
430             enum NumberListCodes : byte
431             {
432                 End = 0x0,             // ends the list.   No valid value has this prefix.
433                 LastImmediateValue = 0xA,
434 
435                 PrefixCode = 0xB,      // all the 'long' encodings go here.  If the next nibble is MultiByte1-4
436                                        // than this is a 'overflow' id.   Unlike the hierarchical IDs these are
437                                        // allocated densely but don't tell you anything about nesting. we use
438                                        // these when we run out of space in the GUID to store the path.
439 
440                 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.
441                 // commented out because the code does not explicitly reference the names (but they are logically defined).
442                 // MultiByte2 = 0xD,   // 2 bytes follow (we don't bother with the nibble optimization)
443                 // MultiByte3 = 0xE,   // 3 bytes follow (we don't bother with the nibble optimization)
444                 // MultiByte4 = 0xF,   // 4 bytes follow (we don't bother with the nibble optimization)
445             }
446 
447             /// Add the activity id 'id' to the output Guid 'outPtr' starting at the offset 'whereToAddId'
448             /// Thus if this number is 6 that is where 'id' will be added.    This will return 13 (12
449             /// is the maximum number of bytes that fit in a GUID) if the path did not fit.
450             /// If 'overflow' is true, then the number is encoded as an 'overflow number (which has a
451             /// special (longer prefix) that indicates that this ID is allocated differently
AddIdToGuid(Guid* outPtr, int whereToAddId, uint id, bool overflow = false)452             private static unsafe int AddIdToGuid(Guid* outPtr, int whereToAddId, uint id, bool overflow = false)
453             {
454                 byte* ptr = (byte*)outPtr;
455                 byte* endPtr = ptr + 12;
456                 ptr += whereToAddId;
457                 if (endPtr <= ptr)
458                     return 13;                // 12 means we might exactly fit, 13 means we definately did not fit
459 
460                 if (0 < id && id <= (uint)NumberListCodes.LastImmediateValue && !overflow)
461                     WriteNibble(ref ptr, endPtr, id);
462                 else
463                 {
464                     uint len = 4;
465                     if (id <= 0xFF)
466                         len = 1;
467                     else if (id <= 0xFFFF)
468                         len = 2;
469                     else if (id <= 0xFFFFFF)
470                         len = 3;
471 
472                     if (overflow)
473                     {
474                         if (endPtr <= ptr + 2)        // I need at least 2 bytes
475                             return 13;
476 
477                         // Write out the prefix code nibble and the length nibble
478                         WriteNibble(ref ptr, endPtr, (uint)NumberListCodes.PrefixCode);
479                     }
480                     // The rest is the same for overflow and non-overflow case
481                     WriteNibble(ref ptr, endPtr, (uint)NumberListCodes.MultiByte1 + (len - 1));
482 
483                     // Do we have an odd nibble?   If so flush it or use it for the 12 byte case.
484                     if (ptr < endPtr && *ptr != 0)
485                     {
486                         // If the value < 4096 we can use the nibble we are otherwise just outputting as padding.
487                         if (id < 4096)
488                         {
489                             // Indicate this is a 1 byte multicode with 4 high order bits in the lower nibble.
490                             *ptr = (byte)(((uint)NumberListCodes.MultiByte1 << 4) + (id >> 8));
491                             id &= 0xFF;     // Now we only want the low order bits.
492                         }
493                         ptr++;
494                     }
495 
496                     // Write out the bytes.
497                     while (0 < len)
498                     {
499                         if (endPtr <= ptr)
500                         {
501                             ptr++;        // Indicate that we have overflowed
502                             break;
503                         }
504                         *ptr++ = (byte)id;
505                         id = (id >> 8);
506                         --len;
507                     }
508                 }
509 
510                 // Compute the checksum
511                 uint* sumPtr = (uint*)outPtr;
512                 // We set the last DWORD the sum of the first 3 DWORDS in the GUID.   This
513                 // This last number is a random number (it identifies us as us)  the process ID to make it unique per process.
514                 sumPtr[3] = (sumPtr[0] + sumPtr[1] + sumPtr[2] + 0x599D99AD) ^ EventSource.s_currentPid;
515 
516                 return (int)(ptr - ((byte*)outPtr));
517             }
518 
519             /// <summary>
520             /// Write a single Nible 'value' (must be 0-15) to the byte buffer represented by *ptr.
521             /// Will not go past 'endPtr'.  Also it assumes that we never write 0 so we can detect
522             /// whether a nibble has already been written to ptr  because it will be nonzero.
523             /// Thus if it is non-zero it adds to the current byte, otherwise it advances and writes
524             /// the new byte (in the high bits) of the next byte.
525             /// </summary>
WriteNibble(ref byte* ptr, byte* endPtr, uint value)526             private static unsafe void WriteNibble(ref byte* ptr, byte* endPtr, uint value)
527             {
528                 Debug.Assert(value < 16);
529                 Debug.Assert(ptr < endPtr);
530 
531                 if (*ptr != 0)
532                     *ptr++ |= (byte)value;
533                 else
534                     *ptr = (byte)(value << 4);
535             }
536 
537             #endregion // CreateGuidForActivityPath
538 
539             readonly internal string m_name;                        // The name used in the 'start' and 'stop' APIs to help match up
540             readonly long m_uniqueId;                               // a small number that makes this activity unique among its siblings
541             internal readonly Guid m_guid;                          // Activity Guid, it is basically an encoding of the Path() (see CreateActivityPathGuid)
542             internal readonly int m_activityPathGuidOffset;         // Keeps track of where in m_guid the causality path stops (used to generated child GUIDs)
543             internal readonly int m_level;                          // current depth of the Path() of the activity (used to keep recursion under control)
544             readonly internal EventActivityOptions m_eventOptions;  // Options passed to start.
545             internal long m_lastChildID;                            // used to create a unique ID for my children activities
546             internal int m_stopped;                                 // This work item has stopped
547             readonly internal ActivityInfo m_creator;               // My parent (creator).  Forms the Path() for the activity.
548             readonly internal Guid m_activityIdToRestore;           // The Guid to restore after a stop.
549             #endregion
550         }
551 
552         // This callback is used to initialize the m_current AsyncLocal Variable.
553         // Its job is to keep the ETW Activity ID (part of thread local storage) in sync
554         // with m_current.ActivityID
ActivityChanging(AsyncLocalValueChangedArgs<ActivityInfo> args)555         void ActivityChanging(AsyncLocalValueChangedArgs<ActivityInfo> args)
556         {
557             ActivityInfo cur = args.CurrentValue;
558             ActivityInfo prev = args.PreviousValue;
559 
560             // Are we popping off a value?   (we have a prev, and it creator is cur)
561             // Then check if we should use the GUID at the time of the start event
562             if (prev != null && prev.m_creator == cur)
563             {
564                 // If the saved activity ID is not the same as the creator activity
565                 // that takes precedence (it means someone explicitly did a SetActivityID)
566                 // Set it to that and get out
567                 if (cur == null || prev.m_activityIdToRestore != cur.ActivityId)
568                 {
569                     EventSource.SetCurrentThreadActivityId(prev.m_activityIdToRestore);
570                     return;
571                 }
572             }
573 
574             // OK we did not have an explicit SetActivityID set.   Then we should be
575             // setting the activity to current ActivityInfo.  However that activity
576             // might be dead, in which case we should skip it, so we never set
577             // the ID to dead things.
578             while (cur != null)
579             {
580                 // We found a live activity (typically the first time), set it to that.
581                 if (cur.m_stopped == 0)
582                 {
583                     EventSource.SetCurrentThreadActivityId(cur.ActivityId);
584                     return;
585                 }
586                 cur = cur.m_creator;
587             }
588             // we can get here if there is no information on our activity stack (everything is dead)
589             // currently we do nothing, as that seems better than setting to Guid.Emtpy.
590         }
591 
592         /// <summary>
593         /// Async local variables have the property that the are automatically copied whenever a task is created and used
594         /// while that task is running.   Thus m_current 'flows' to any task that is caused by the current thread that
595         /// last set it.
596         ///
597         /// This variable points a a linked list that represents all Activities that have started but have not stopped.
598         /// </summary>
599         AsyncLocal<ActivityInfo> m_current;
600         bool m_checkedForEnable;
601 
602         // Singleton
603         private static ActivityTracker s_activityTrackerInstance = new ActivityTracker();
604 
605         // Used to create unique IDs at the top level.  Not used for nested Ids (each activity has its own id generator)
606         static long m_nextId = 0;
607         private const ushort MAX_ACTIVITY_DEPTH = 100;            // Limit maximum depth of activities to be tracked at 100.
608                                                                   // This will avoid leaking memory in case of activities that are never stopped.
609 
610         #endregion
611     }
612 
613 #if ES_BUILD_STANDALONE || ES_BUILD_PN
614     /******************************** SUPPORT *****************************/
615     /// <summary>
616     /// This is supplied by the framework.   It is has the semantics that the value is copied to any new Tasks that is created
617     /// by the current task.   Thus all causally related code gets this value.    Note that reads and writes to this VARIABLE
618     /// (not what it points it) to this does not need to be protected by locks because it is inherently thread local (you always
619     /// only get your thread local copy which means that you never have races.
620     /// </summary>
621     ///
622 #if ES_BUILD_STANDALONE
623     [EventSource(Name = "Microsoft.Tasks.Nuget")]
624 #else
625     [EventSource(Name = "System.Diagnostics.Tracing.TplEtwProvider")]
626 #endif
627     internal class TplEtwProvider : EventSource
628     {
629         public class Keywords
630         {
631             public const EventKeywords TasksFlowActivityIds = (EventKeywords)0x80;
632             public const EventKeywords Debug = (EventKeywords)0x20000;
633         }
634 
635         public static TplEtwProvider Log = new TplEtwProvider();
636         public bool Debug { get { return IsEnabled(EventLevel.Verbose, Keywords.Debug); } }
637 
DebugFacilityMessage(string Facility, string Message)638         public void DebugFacilityMessage(string Facility, string Message) { WriteEvent(1, Facility, Message); }
DebugFacilityMessage1(string Facility, string Message, string Arg)639         public void DebugFacilityMessage1(string Facility, string Message, string Arg) { WriteEvent(2, Facility, Message, Arg); }
SetActivityId(Guid Id)640         public void SetActivityId(Guid Id) { WriteEvent(3, Id); }
641     }
642 #endif
643 
644 #if ES_BUILD_AGAINST_DOTNET_V35 || ES_BUILD_PCL || NO_ASYNC_LOCAL
645     // In these cases we don't have any Async local support.   Do nothing.
646     internal sealed class AsyncLocalValueChangedArgs<T>
647     {
648         public T PreviousValue { get { return default(T); } }
649         public T CurrentValue { get { return default(T); } }
650 
651     }
652 
653     internal sealed class AsyncLocal<T>
654     {
AsyncLocal(Action<AsyncLocalValueChangedArgs<T>> valueChangedHandler)655         public AsyncLocal(Action<AsyncLocalValueChangedArgs<T>> valueChangedHandler) {
656             throw new NotImplementedException("AsyncLocal only available on V4.6 and above");
657         }
658         public T Value
659         {
660             get { return default(T); }
661             set { }
662         }
663     }
664 #endif
665 
666 }
667