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