File: Log\RoslynEventSource.cs
Web Access
Project: ..\..\..\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace Microsoft.CodeAnalysis.Internal.Log
{
    /// <summary>
    /// This EventSource exposes our events to ETW.
    /// RoslynEventSource GUID is {bf965e67-c7fb-5c5b-d98f-cdf68f8154c2}.
    /// 
    /// When updating this class, use the following to also update Main\Source\Test\Performance\Log\RoslynEventSourceParser.cs:
    /// Main\Tools\Source\TraceParserGen\bin\Debug\TraceParserGen.exe Microsoft.CodeAnalysis.Workspaces.dll -eventsource:RoslynEventSource
    /// 
    /// Use this command to register the ETW manifest on any machine where you need to decode events in xperf/etlstackbrowse:
    /// "\\clrmain\tools\managed\etw\eventRegister\bin\Debug\eventRegister.exe" Microsoft.CodeAnalysis.Workspaces.dll
    /// </summary>
    [EventSource(Name = "RoslynEventSource")]
    internal sealed partial class RoslynEventSource : EventSource
    {
        // might not "enabled" but we always have this singleton alive
        public static readonly RoslynEventSource Instance = new();
 
        private readonly bool _initialized;
        private RoslynEventSource()
            => _initialized = true;
 
        // Do not change the parameter order for this method: it must match the parameter order
        // for WriteEvent() that's being invoked inside it. This is necessary because the ETW schema
        // generation process will reflect over WriteRoslynEvent() to determine how to pack event
        // data. If the orders are different, the generated schema will be wrong, and any perf
        // tools will deserialize event data fields in the wrong order.
        //
        // The WriteEvent overloads are optimized for either:
        //     Up to 3 integer parameters
        //     1 string and up to 2 integer parameters
        // There's also a params object[] overload that is much slower and should be avoided
        [Event(1)]
        public void Log(string message, FunctionId functionId)
            => WriteEvent(1, message ?? string.Empty, (int)functionId);
 
        [Event(2)]
        public void BlockStart(string message, FunctionId functionId, int blockId)
            => WriteEvent(2, message ?? string.Empty, (int)functionId, blockId);
 
        [Event(3)]
        public void BlockStop(FunctionId functionId, int tick, int blockId)
            => WriteEvent(3, (int)functionId, tick, blockId);
 
        [Event(4)]
        public void SendFunctionDefinitions(string definitions)
            => WriteEvent(4, definitions);
 
        [Event(5)]
        public void BlockCanceled(FunctionId functionId, int tick, int blockId)
            => WriteEvent(5, (int)functionId, tick, blockId);
 
        [NonEvent]
        protected override void OnEventCommand(EventCommandEventArgs command)
        {
            base.OnEventCommand(command);
 
            if (command.Command == EventCommand.SendManifest ||
                command.Command != EventCommand.Disable ||
                FunctionDefinitionRequested(command))
            {
                if (!_initialized)
                {
                    // We're still in the constructor, need to defer sending until we've finished initializing
                    Task.Yield().GetAwaiter().OnCompleted(FireAndForgetSendFunctionDefinitions);
                    return;
                }
 
                SendFunctionDefinitions();
            }
 
            // Cannot inline this local function as a lambda because we need NonEventAttribute applied.
            [NonEvent]
            void FireAndForgetSendFunctionDefinitions()
            {
                _ = Task.Run(SendFunctionDefinitions);
            }
        }
 
        [NonEvent]
        private static bool FunctionDefinitionRequested(EventCommandEventArgs command)
        {
            return command.Arguments != null &&
                   command.Arguments.Keys.FirstOrDefault() == "SendFunctionDefinitions";
        }
 
        [NonEvent]
        private void SendFunctionDefinitions()
            => SendFunctionDefinitions(GenerateFunctionDefinitions());
 
        [NonEvent]
        public static string GenerateFunctionDefinitions()
        {
            var output = new StringBuilder();
 
            var functions = from f in typeof(FunctionId).GetFields()
                            where !f.IsSpecialName
                            select f;
 
            var assembly = typeof(RoslynEventSource).Assembly;
            var fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
            output.AppendLine(fvi.ProductVersion);
 
            foreach (var function in functions)
            {
                var value = (int)function.GetRawConstantValue();
#if DEBUG
                var name = "Debug_" + function.Name;
#else
                var name = function.Name;
#endif
                var goal = (from attr in function.GetCustomAttributes(false)
                            where attr is PerfGoalAttribute
                            select ((PerfGoalAttribute)attr).InteractionClass).DefaultIfEmpty(InteractionClass.Undefined).First();
 
                output.Append(value);
                output.Append(" ");
                output.Append(name);
                output.Append(" ");
                output.AppendLine(goal.ToString());
            }
 
            // Note that changing the format of this output string will break any ETW listeners
            // that don't have a direct reference to Microsoft.CodeAnalysis.Workspaces.dll
            return output.ToString();
        }
    }
}