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