File: CodeMarkers\ManagedCodeMarkers.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_detahxbt_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// 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
 
// <auto-generated /> - disable StyleCop compile time checks for this file
// This file would normally be source-linked from the DevDiv source tree,
// but is copied here because Roslyn needs to build outside DevDiv sources for now.
 
using System;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Microsoft.Win32;
 
namespace Microsoft.Internal.Performance;
 
internal sealed class CodeMarkers
{
    // Singleton access
    public static readonly CodeMarkers Instance = new CodeMarkers();
 
    private static class NativeMethods
    {
#if Codemarkers_IncludeAppEnum
        ///// Code markers test function imports
        [DllImport(TestDllName, EntryPoint = "InitPerf")]
        public static extern void TestDllInitPerf(System.Int32 iApp);
 
        [DllImport(TestDllName, EntryPoint = "UnInitPerf")]
        public static extern void TestDllUnInitPerf(System.Int32 iApp);
#endif // Codemarkers_IncludeAppEnum
 
        [DllImport(TestDllName, EntryPoint = "PerfCodeMarker")]
        public static extern void TestDllPerfCodeMarker(System.Int32 nTimerID, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] aUserParams, System.Int32 cbParams);
 
        [DllImport(ProductDllName, EntryPoint = "PerfCodeMarker")]
        public static extern void TestDllPerfCodeMarkerString(System.Int32 nTimerID, [MarshalAs(UnmanagedType.LPStr, SizeParamIndex = 2)] string aUserParams, System.Int32 cbParams);
 
#if Codemarkers_IncludeAppEnum
        ///// Code markers product function imports
        [DllImport(ProductDllName, EntryPoint = "InitPerf")]
        public static extern void ProductDllInitPerf(System.Int32 iApp);
 
        [DllImport(ProductDllName, EntryPoint = "UnInitPerf")]
        public static extern void ProductDllUnInitPerf(System.Int32 iApp);
#endif // Codemarkers_IncludeAppEnum
 
        [DllImport(ProductDllName, EntryPoint = "PerfCodeMarker")]
        public static extern void ProductDllPerfCodeMarker(System.Int32 nTimerID, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] aUserParams, System.Int32 cbParams);
 
        [DllImport(ProductDllName, EntryPoint = "PerfCodeMarker")]
        public static extern void ProductDllPerfCodeMarkerString(System.Int32 nTimerID, [MarshalAs(UnmanagedType.LPStr, SizeParamIndex = 2)] string aUserParams, System.Int32 cbParams);
 
        ///// global native method imports
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
        public static extern System.UInt16 FindAtom([MarshalAs(UnmanagedType.LPWStr)] string lpString);
 
#if Codemarkers_IncludeAppEnum
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
        public static extern System.UInt16 AddAtom([MarshalAs(UnmanagedType.LPWStr)] string lpString);
 
        [DllImport("kernel32.dll")]
        public static extern System.UInt16 DeleteAtom(System.UInt16 atom);
#endif // Codemarkers_IncludeAppEnum
 
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
        public static extern IntPtr GetModuleHandle([MarshalAs(UnmanagedType.LPWStr)] string lpModuleName);
    }
 
    // Atom name. This ATOM will be set by the host application when code markers are enabled
    // in the registry.
    private const string AtomName = "VSCodeMarkersEnabled";
 
    // Internal Test CodeMarkers DLL name
    private const string TestDllName = "Microsoft.Internal.Performance.CodeMarkers.dll";
 
    // External Product CodeMarkers DLL name
    private const string ProductDllName = "Microsoft.VisualStudio.CodeMarkers.dll";
 
    private enum State
    {
        /// <summary>
        /// The atom is present. CodeMarkers are enabled.
        /// </summary>
        Enabled,
 
        /// <summary>
        /// The atom is not present, but InitPerformanceDll has not yet been called.
        /// </summary>
        Disabled,
 
        /// <summary>
        /// Disabled because the CodeMarkers transport DLL could not be found or
        /// an import failed to resolve.
        /// </summary>
        DisabledDueToDllImportException
    }
 
    private State _state;
 
    /// <summary>
    /// Are CodeMarkers enabled? Note that even if IsEnabled returns false, CodeMarkers
    /// may still be enabled later in another component.
    /// </summary>
    public bool IsEnabled
    {
        get
        {
            return _state == State.Enabled;
        }
    }
 
    // should CodeMarker events be fired to the test or product CodeMarker DLL
    private RegistryView _registryView = RegistryView.Default;
    private string _regroot = null;
    private bool? _shouldUseTestDll;
 
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
    public bool ShouldUseTestDll
    {
        get
        {
            if (!_shouldUseTestDll.HasValue)
            {
                try
                {
                    // this code can either be used in an InitPerf (loads CodeMarker DLL) or AttachPerf context (CodeMarker DLL already loaded)
                    // in the InitPerf context we have a regroot and should check for the test DLL registration
                    // in the AttachPerf context we should see which module is already loaded 
                    if (_regroot == null)
                    {
                        _shouldUseTestDll = NativeMethods.GetModuleHandle(ProductDllName) == IntPtr.Zero;
                    }
                    else
                    {
                        // if CodeMarkers are explicitly enabled in the registry then try to
                        // use the test DLL, otherwise fall back to trying to use the product DLL
                        _shouldUseTestDll = UsePrivateCodeMarkers(_regroot, _registryView);
                    }
                }
                catch (Exception)
                {
                    _shouldUseTestDll = true;
                }
            }
 
            return _shouldUseTestDll.Value;
        }
    }
 
    // Constructor. Do not call directly. Use CodeMarkers.Instance to access the singleton
    // Checks to see if code markers are enabled by looking for a named ATOM
    private CodeMarkers()
    {
        // This ATOM will be set by the native Code Markers host
        _state = (NativeMethods.FindAtom(AtomName) != 0) ? State.Enabled : State.Disabled;
    }
 
    /// <summary>
    /// Sends a code marker event
    /// </summary>
    /// <param name="nTimerID">The code marker event ID</param>
    /// <returns>true if the code marker was successfully sent, false if code markers are
    /// not enabled or an error occurred.</returns>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    public bool CodeMarker(int nTimerID)
    {
        if (!IsEnabled)
            return false;
 
        try
        {
            if (this.ShouldUseTestDll)
            {
                NativeMethods.TestDllPerfCodeMarker(nTimerID, null, 0);
            }
            else
            {
                NativeMethods.ProductDllPerfCodeMarker(nTimerID, null, 0);
            }
        }
        catch (DllNotFoundException)
        {
            // If the DLL doesn't load or the entry point doesn't exist, then
            // abandon all further attempts to send codemarkers.
            _state = State.DisabledDueToDllImportException;
            return false;
        }
 
        return true;
    }
 
    /// <summary>
    /// Sends a code marker event with additional user data
    /// </summary>
    /// <param name="nTimerID">The code marker event ID</param>
    /// <param name="aBuff">User data buffer. May not be null.</param>
    /// <returns>true if the code marker was successfully sent, false if code markers are
    /// not enabled or an error occurred.</returns>
    /// <exception cref="ArgumentNullException">aBuff was null</exception>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    public bool CodeMarkerEx(int nTimerID, byte[] aBuff)
    {
        if (!IsEnabled)
            return false;
 
        // Check the arguments only after checking whether code markers are enabled
        // This allows the calling code to pass null value and avoid calculation of data if nothing is to be logged
        if (aBuff == null)
            throw new ArgumentNullException(nameof(aBuff));
 
        try
        {
            if (this.ShouldUseTestDll)
            {
                NativeMethods.TestDllPerfCodeMarker(nTimerID, aBuff, aBuff.Length);
            }
            else
            {
                NativeMethods.ProductDllPerfCodeMarker(nTimerID, aBuff, aBuff.Length);
            }
        }
        catch (DllNotFoundException)
        {
            // If the DLL doesn't load or the entry point doesn't exist, then
            // abandon all further attempts to send codemarkers.
            _state = State.DisabledDueToDllImportException;
            return false;
        }
 
        return true;
    }
 
    /// <summary>
    /// Used by ManagedPerfTrack.cs to report errors accessing the DLL.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    public void SetStateDLLException()
    {
        _state = State.DisabledDueToDllImportException;
    }
 
 
    /// <summary>
    /// Sends a code marker event with additional Guid user data
    /// </summary>
    /// <param name="nTimerID">The code marker event ID</param>
    /// <param name="guidData">The additional Guid to include with the event</param>
    /// <returns>true if the code marker was successfully sent, false if code markers are
    /// not enabled or an error occurred.</returns>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    public bool CodeMarkerEx(int nTimerID, Guid guidData)
    {
        return CodeMarkerEx(nTimerID, guidData.ToByteArray());
    }
 
    /// <summary>
    /// Sends a code marker event with additional String user data
    /// </summary>
    /// <param name="nTimerID">The code marker event ID</param>
    /// <param name="stringData">The additional String to include with the event</param>
    /// <returns>true if the code marker was successfully sent, false if code markers are
    /// not enabled or an error occurred.</returns>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    public bool CodeMarkerEx(int nTimerID, string stringData)
    {
        //return CodeMarkerEx(nTimerID, StringToBytesZeroTerminated(stringData));
 
        if (!IsEnabled)
            return false;
 
        // Check the arguments only after checking whether code markers are enabled
        // This allows the calling code to pass null value and avoid calculation of data if nothing is to be logged
        if (stringData == null)
            throw new ArgumentNullException(nameof(stringData));
 
        try
        {
            int byteCount = stringData == null ? 0 : stringData.Length + 1;
            if (this.ShouldUseTestDll)
            {
                NativeMethods.TestDllPerfCodeMarkerString(nTimerID, stringData, byteCount);
            }
            else
            {
                NativeMethods.ProductDllPerfCodeMarkerString(nTimerID, stringData, byteCount);
            }
        }
        catch (DllNotFoundException)
        {
            // If the DLL doesn't load or the entry point doesn't exist, then
            // abandon all further attempts to send codemarkers.
            _state = State.DisabledDueToDllImportException;
            return false;
        }
 
        return true;
    }
 
    /// <summary>
    /// Converts a string into a byte buffer including a zero terminator (needed for proper ETW message formatting)
    /// </summary>
    /// <param name="stringData">String to be converted to bytes</param>
    /// <returns></returns>
    internal static byte[] StringToBytesZeroTerminated(string stringData)
    {
        var encoding = System.Text.Encoding.Unicode;
        int stringByteLength = encoding.GetByteCount(stringData);
        byte[] data = new byte[stringByteLength + sizeof(char)]; /* string + null termination */
        encoding.GetBytes(stringData, 0, stringData.Length, data, 0); // null terminator is already there, just write string over it
        return data;
    }
 
 
    /// <summary>
    /// Sends a code marker event with additional DWORD user data
    /// </summary>
    /// <param name="nTimerID">The code marker event ID</param>
    /// <param name="uintData">The additional DWORD to include with the event</param>
    /// <returns>true if the code marker was successfully sent, false if code markers are
    /// not enabled or an error occurred.</returns>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    public bool CodeMarkerEx(int nTimerID, uint uintData)
    {
        return CodeMarkerEx(nTimerID, BitConverter.GetBytes(uintData));
    }
 
    /// <summary>
    /// Sends a code marker event with additional QWORD user data
    /// </summary>
    /// <param name="nTimerID">The code marker event ID</param>
    /// <param name="ulongData">The additional QWORD to include with the event</param>
    /// <returns>true if the code marker was successfully sent, false if code markers are
    /// not enabled or an error occurred.</returns>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    public bool CodeMarkerEx(int nTimerID, ulong ulongData)
    {
        return CodeMarkerEx(nTimerID, BitConverter.GetBytes(ulongData));
    }
 
    /// <summary>
    /// Checks the registry to see if code markers are enabled
    /// </summary>
    /// <param name="regRoot">The registry root</param>
    /// <param name="registryView"></param>
    /// <returns>Whether CodeMarkers are enabled in the registry</returns>
    private static bool UsePrivateCodeMarkers(string regRoot, RegistryView registryView)
    {
        if (regRoot == null)
        {
            throw new ArgumentNullException(nameof(regRoot));
        }
 
        // Reads the Performance subkey from the given registry key
        using (RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, registryView))
        using (RegistryKey key = baseKey.OpenSubKey(regRoot + "\\Performance"))
        {
            if (key != null)
            {
                // Read the default value
                // It doesn't matter what the value is, if it's present and not empty, code markers are enabled
                string defaultValue = key.GetValue(string.Empty).ToString();
                return !string.IsNullOrEmpty(defaultValue);
            }
        }
 
        return false;
    }
 
#if Codemarkers_IncludeAppEnum
    /// <summary>
    /// Check the registry and, if appropriate, loads and initializes the code markers dll.
    /// InitPerformanceDll may be called more than once, but only the first successful call will do anything.
    /// Subsequent calls will be ignored.
    /// For 32-bit processes on a 64-bit machine, the 32-bit (Wow6432Node) registry will be used.
    /// For 64-bit processes, the 64-bit registry will be used. If you need to use the Wow6432Node in this case
    /// then use the overload of InitPerformanceDll that takes a RegistryView parameter.
    /// </summary>
    /// <param name="iApp">The application ID value that distinguishes these code marker events from other applications.</param>
    /// <param name="strRegRoot">The registry root of the application. The default value of the "Performance" subkey under this
    /// root will be checked to determine if CodeMarkers should be enabled.</param>
    /// <returns>true if CodeMarkers were initialized successfully, or if InitPerformanceDll has already been called
    /// successfully once.
    /// false indicates that either CodeMarkers are not enabled in the registry, or that the CodeMarkers transport
    /// DLL failed to load.</returns>
    public bool InitPerformanceDll(int iApp, string strRegRoot)
    {            
        return InitPerformanceDll(iApp, strRegRoot, RegistryView.Default);
    }
 
    /// <summary>
    /// Check the registry and, if appropriate, loads and initializes the code markers dll.
    /// InitPerformanceDll may be called more than once, but only the first successful call will do anything.
    /// Subsequent calls will be ignored.
    /// </summary>
    /// <param name="iApp">The application ID value that distinguishes these code marker events from other applications.</param>
    /// <param name="strRegRoot">The registry root of the application. The default value of the "Performance" subkey under this
    /// root will be checked to determine if CodeMarkers should be enabled.</param>
    /// <param name="registryView">Specify RegistryView.Registry32 to use the 32-bit registry even if the calling application
    /// is 64-bit</param>
    /// <returns>true if CodeMarkers were initialized successfully, or if InitPerformanceDll has already been called
    /// successfully once.
    /// false indicates that either CodeMarkers are not enabled in the registry, or that the CodeMarkers transport
    /// DLL failed to load.</returns>
    public bool InitPerformanceDll(int iApp, string strRegRoot, RegistryView registryView)
    {           
        // Prevent multiple initializations.
        if (IsEnabled)
        {
            return true;
        }
 
        if (strRegRoot == null)
        {
            throw new ArgumentNullException(nameof(strRegRoot));
        }
        
        this.regroot = strRegRoot;
        this.registryView = registryView;
 
        try
        {
            if (this.ShouldUseTestDll)
            {
                NativeMethods.TestDllInitPerf(iApp);
            }
            else
            {
                NativeMethods.ProductDllInitPerf(iApp);
            }
            
            this.state = State.Enabled;
            
            // Add an ATOM so that other CodeMarker enabled code in this process
            // knows that CodeMarkers are enabled 
            NativeMethods.AddAtom(AtomName);
        }
        // catch BadImageFormatException to handle 64-bit process loading 32-bit CodeMarker DLL (e.g., msbuild.exe)
        catch (BadImageFormatException)
        {
            this.state = State.DisabledDueToDllImportException;
        }
        catch (DllNotFoundException)
        {
            this.state = State.DisabledDueToDllImportException;
            return false;
        }
 
        return true;
    }
 
    
    // Opposite of InitPerformanceDLL. Call it when your app does not need the code markers dll.
    public void UninitializePerformanceDLL(int iApp)
    {
        bool? usingTestDL = this.shouldUseTestDll; // remember this or we can end up uninitializing the wrong dll.
        this.shouldUseTestDll = null; // reset which DLL we should use (needed for unit testing)
        this.regroot = null;
 
        if (!IsEnabled)
        {
            return;
        }
 
        this.state = State.Disabled;
 
        // Delete the atom created during the initialization if it exists
        System.UInt16 atom = NativeMethods.FindAtom(AtomName);
        if (atom != 0)
        {
            NativeMethods.DeleteAtom(atom);
        }
 
        try
        {
            if (usingTestDL.HasValue)  // If we don't have a value, then we never initialized the DLL.
            {
                if (usingTestDL.Value)
                {
                    NativeMethods.TestDllUnInitPerf(iApp);
                }
                else
                {
                    NativeMethods.ProductDllUnInitPerf(iApp);
                }
            }
        }
        catch (DllNotFoundException)
        {
            // Swallow exception
        }
    }        
#endif //Codemarkers_IncludeAppEnum
}
 
#if !Codemarkers_NoCodeMarkerStartEnd
/// <summary>
/// Use CodeMarkerStartEnd in a using clause when you need to bracket an
/// operation with a start/end CodeMarker event pair.
/// </summary>
internal struct CodeMarkerStartEnd : IDisposable
{
    private int _end;
 
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    internal CodeMarkerStartEnd(int begin, int end)
    {
        Debug.Assert(end != default(int));
        CodeMarkers.Instance.CodeMarker(begin);
        _end = end;
    }
 
    public void Dispose()
    {
        if (_end != default(int)) // Protect against multiple Dispose calls
        {
            CodeMarkers.Instance.CodeMarker(_end);
            _end = default(int);
        }
    }
}
 
/// <summary>
/// Use CodeMarkerExStartEnd in a using clause when you need to bracket an
/// operation with a start/end CodeMarker event pair.
/// </summary>
internal struct CodeMarkerExStartEnd : IDisposable
{
    private int _end;
    private byte[] _aBuff;
 
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    internal CodeMarkerExStartEnd(int begin, int end, byte[] aBuff)
    {
        Debug.Assert(end != default(int));
        CodeMarkers.Instance.CodeMarkerEx(begin, aBuff);
        _end = end;
        _aBuff = aBuff;
    }
 
    // Specialization to use Guids for the code marker data
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    internal CodeMarkerExStartEnd(int begin, int end, Guid guidData)
        : this(begin, end, guidData.ToByteArray())
    {
    }
 
    // Specialization for string
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    internal CodeMarkerExStartEnd(int begin, int end, string stringData)
        : this(begin, end, CodeMarkers.StringToBytesZeroTerminated(stringData))
    {
    }
 
    // Specialization for uint
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    internal CodeMarkerExStartEnd(int begin, int end, uint uintData)
        : this(begin, end, BitConverter.GetBytes(uintData))
    {
    }
 
    // Specialization for ulong
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
    internal CodeMarkerExStartEnd(int begin, int end, ulong ulongData)
        : this(begin, end, BitConverter.GetBytes(ulongData))
    {
    }
 
    public void Dispose()
    {
        if (_end != default(int)) // Protect against multiple Dispose calls
        {
            CodeMarkers.Instance.CodeMarkerEx(_end, _aBuff);
            _end = default(int);
            _aBuff = null;
        }
    }
}
 
#endif