File: Log\RoslynEventSource.cs
Web Access
Project: src\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();
    }
}