File: Internal\DataflowEtwProvider.cs
Web Access
Project: src\src\libraries\System.Threading.Tasks.Dataflow\src\System.Threading.Tasks.Dataflow.csproj (System.Threading.Tasks.Dataflow)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
// DataflowEtwProvider.cs
//
//
// EventSource for Dataflow.
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using System.Linq;
using System.Security;
 
namespace System.Threading.Tasks.Dataflow.Internal
{
    /// <summary>Provides an event source for tracing Dataflow information.</summary>
    [EventSource(
        Name = "System.Threading.Tasks.Dataflow.DataflowEventSource",
        Guid = "16F53577-E41D-43D4-B47E-C17025BF4025")]
    internal sealed class DataflowEtwProvider : EventSource
    {
        /// <summary>
        /// Defines the singleton instance for the dataflow ETW provider.
        /// The dataflow provider GUID is {16F53577-E41D-43D4-B47E-C17025BF4025}.
        /// </summary>
        internal static readonly DataflowEtwProvider Log = new DataflowEtwProvider();
        /// <summary>Prevent external instantiation.  All logging should go through the Log instance.</summary>
        private DataflowEtwProvider() { }
 
        /// <summary>Enabled for all keywords.</summary>
        private const EventKeywords ALL_KEYWORDS = (EventKeywords)(-1);
 
        //-----------------------------------------------------------------------------------
        //
        // Dataflow Event IDs (must be unique)
        //
 
        /// <summary>The event ID for when we encounter a new dataflow block object that hasn't had its name traced to the trace file.</summary>
        private const int DATAFLOWBLOCKCREATED_EVENTID = 1;
        /// <summary>The event ID for the task launched event.</summary>
        private const int TASKLAUNCHED_EVENTID = 2;
        /// <summary>The event ID for the block completed event.</summary>
        private const int BLOCKCOMPLETED_EVENTID = 3;
        /// <summary>The event ID for the block linked event.</summary>
        private const int BLOCKLINKED_EVENTID = 4;
        /// <summary>The event ID for the block unlinked event.</summary>
        private const int BLOCKUNLINKED_EVENTID = 5;
 
        //-----------------------------------------------------------------------------------
        //
        // Dataflow Events
        //
 
#region Block Creation
        /// <summary>Trace an event for when a new block is instantiated.</summary>
        /// <param name="block">The dataflow block that was created.</param>
        /// <param name="dataflowBlockOptions">The options with which the block was created.</param>
        [NonEvent]
        internal void DataflowBlockCreated(IDataflowBlock block, DataflowBlockOptions dataflowBlockOptions)
        {
            Debug.Assert(block != null, "Block needed for the ETW event.");
            Debug.Assert(dataflowBlockOptions != null, "Options needed for the ETW event.");
 
            if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS))
            {
                DataflowBlockCreated(
                    Common.GetNameForDebugger(block, dataflowBlockOptions),
                    Common.GetBlockId(block));
            }
        }
 
        [Event(DATAFLOWBLOCKCREATED_EVENTID, Level = EventLevel.Informational)]
        private void DataflowBlockCreated(string blockName, int blockId)
        {
            WriteEvent(DATAFLOWBLOCKCREATED_EVENTID, blockName, blockId);
        }
#endregion
 
#region Task Launching
        /// <summary>Trace an event for a block launching a task to handle messages.</summary>
        /// <param name="block">The owner block launching a task.</param>
        /// <param name="task">The task being launched for processing.</param>
        /// <param name="reason">The reason the task is being launched.</param>
        /// <param name="availableMessages">The number of messages available to be handled by the task.</param>
        [NonEvent]
        internal void TaskLaunchedForMessageHandling(
            IDataflowBlock block, Task task, TaskLaunchedReason reason, int availableMessages)
        {
            Debug.Assert(block != null, "Block needed for the ETW event.");
            Debug.Assert(task != null, "Task needed for the ETW event.");
            Debug.Assert(reason == TaskLaunchedReason.ProcessingInputMessages || reason == TaskLaunchedReason.OfferingOutputMessages,
                "The reason should be a supported value from the TaskLaunchedReason enumeration.");
            if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS))
            {
                TaskLaunchedForMessageHandling(Common.GetBlockId(block), reason, availableMessages, task.Id);
            }
        }
 
        [Event(TASKLAUNCHED_EVENTID, Level = EventLevel.Informational)]
#if !NET8_0_OR_GREATER
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
                                      Justification = "This calls WriteEvent with all primitive arguments which is safe. Primitives are always serialized properly.")]
#endif
        private void TaskLaunchedForMessageHandling(int blockId, TaskLaunchedReason reason, int availableMessages, int taskId)
        {
            WriteEvent(TASKLAUNCHED_EVENTID, blockId, reason, availableMessages, taskId);
        }
 
        /// <summary>Describes the reason a task is being launched.</summary>
        internal enum TaskLaunchedReason
        {
            /// <summary>A task is being launched to process incoming messages.</summary>
            ProcessingInputMessages = 1,
            /// <summary>A task is being launched to offer outgoing messages to linked targets.</summary>
            OfferingOutputMessages = 2,
        }
#endregion
 
#region Block Completion
        /// <summary>Trace an event for a block completing.</summary>
        /// <param name="block">The block that's completing.</param>
        [NonEvent]
        internal void DataflowBlockCompleted(IDataflowBlock block)
        {
            Debug.Assert(block != null, "Block needed for the ETW event.");
            if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS))
            {
                Task? completionTask = Common.GetPotentiallyNotSupportedCompletionTask(block);
                bool blockIsCompleted = completionTask != null && completionTask.IsCompleted;
                Debug.Assert(blockIsCompleted, "Block must be completed for this event to be valid.");
                if (blockIsCompleted)
                {
                    var reason = (BlockCompletionReason)completionTask!.Status;
                    string exceptionData = string.Empty;
 
                    if (completionTask.IsFaulted)
                    {
                        try { exceptionData = string.Join(Environment.NewLine, completionTask.Exception!.InnerExceptions.Select(static e => e.ToString())); }
                        catch { }
                    }
 
                    DataflowBlockCompleted(Common.GetBlockId(block), reason, exceptionData);
                }
            }
        }
 
        /// <summary>Describes the reason a block completed.</summary>
        internal enum BlockCompletionReason
        {
            /// <summary>The block completed successfully.</summary>
            RanToCompletion = (int)TaskStatus.RanToCompletion,
            /// <summary>The block completed due to an error.</summary>
            Faulted = (int)TaskStatus.Faulted,
            /// <summary>The block completed due to cancellation.</summary>
            Canceled = (int)TaskStatus.Canceled
        }
 
        [Event(BLOCKCOMPLETED_EVENTID, Level = EventLevel.Informational)]
#if !NET8_0_OR_GREATER
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
                                      Justification = "This calls WriteEvent with all primitive arguments which is safe. Primitives are always serialized properly.")]
#endif
        private void DataflowBlockCompleted(int blockId, BlockCompletionReason reason, string exceptionData)
        {
            WriteEvent(BLOCKCOMPLETED_EVENTID, blockId, reason, exceptionData);
        }
#endregion
 
#region Linking
        /// <summary>Trace an event for a block linking.</summary>
        /// <param name="source">The source block linking to a target.</param>
        /// <param name="target">The target block being linked from a source.</param>
        [NonEvent]
        internal void DataflowBlockLinking<T>(ISourceBlock<T> source, ITargetBlock<T> target)
        {
            Debug.Assert(source != null, "Source needed for the ETW event.");
            Debug.Assert(target != null, "Target needed for the ETW event.");
            if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS))
            {
                DataflowBlockLinking(Common.GetBlockId(source), Common.GetBlockId(target));
            }
        }
 
        [Event(BLOCKLINKED_EVENTID, Level = EventLevel.Informational)]
        private void DataflowBlockLinking(int sourceId, int targetId)
        {
            WriteEvent(BLOCKLINKED_EVENTID, sourceId, targetId);
        }
#endregion
 
#region Unlinking
        /// <summary>Trace an event for a block unlinking.</summary>
        /// <param name="source">The source block unlinking from a target.</param>
        /// <param name="target">The target block being unlinked from a source.</param>
        [NonEvent]
        internal void DataflowBlockUnlinking<T>(ISourceBlock<T> source, ITargetBlock<T> target)
        {
            Debug.Assert(source != null, "Source needed for the ETW event.");
            Debug.Assert(target != null, "Target needed for the ETW event.");
            if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS))
            {
                // Try catch exists to prevent against faulty blocks or blocks that only partially implement the interface
                DataflowBlockUnlinking(Common.GetBlockId(source), Common.GetBlockId(target));
            }
        }
 
        [Event(BLOCKUNLINKED_EVENTID, Level = EventLevel.Informational)]
        private void DataflowBlockUnlinking(int sourceId, int targetId)
        {
            WriteEvent(BLOCKUNLINKED_EVENTID, sourceId, targetId);
        }
#endregion
    }
}