|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Reflection;
using Microsoft.DotNet.Cli.Utils;
using RuntimeEnvironment = Microsoft.DotNet.Cli.Utils.RuntimeEnvironment;
namespace Microsoft.DotNet.Cli;
[EventSource(Name = "Microsoft-Dotnet-CLI-Performance", Guid = "cbd57d06-3b9f-5374-ed53-cfbcc23cf44f")]
internal sealed class PerformanceLogEventSource : EventSource
{
internal static PerformanceLogEventSource Log = new();
private PerformanceLogEventSource()
{
}
[NonEvent]
internal void LogStartUpInformation(PerformanceLogStartupInformation startupInfo)
{
if (!IsEnabled())
{
return;
}
DotnetVersionFile versionFile = DotnetFiles.VersionFileObject;
string commitSha = versionFile.CommitSha ?? "N/A";
LogMachineConfiguration();
OSInfo(RuntimeEnvironment.OperatingSystem, RuntimeEnvironment.OperatingSystemVersion, RuntimeEnvironment.OperatingSystemPlatform.ToString());
SDKInfo(Product.Version, commitSha, RuntimeInformation.RuntimeIdentifier, versionFile.BuildRid, AppContext.BaseDirectory);
EnvironmentInfo(Environment.CommandLine);
LogMemoryConfiguration();
LogDrives();
// It's possible that IsEnabled returns true if an out-of-process collector such as ETW is enabled.
// If the perf log hasn't been enabled, then startupInfo will be null, so protect against nullref here.
if (startupInfo != null)
{
if (startupInfo.TimedAssembly != null)
{
AssemblyLoad(startupInfo.TimedAssembly.GetName().Name, startupInfo.AssemblyLoadTime.TotalMilliseconds);
}
Process currentProcess = Process.GetCurrentProcess();
TimeSpan latency = startupInfo.MainTimeStamp - currentProcess.StartTime;
HostLatency(latency.TotalMilliseconds);
}
}
[Event(1)]
internal void OSInfo(string osname, string osversion, string osplatform)
{
WriteEvent(1, osname, osversion, osplatform);
}
[Event(2)]
internal void SDKInfo(string version, string commit, string currentRid, string buildRid, string basePath)
{
WriteEvent(2, version, commit, currentRid, buildRid, basePath);
}
[Event(3)]
internal void EnvironmentInfo(string commandLine)
{
WriteEvent(3, commandLine);
}
[Event(4)]
internal void HostLatency(double timeInMs)
{
WriteEvent(4, timeInMs);
}
[Event(5)]
internal void CLIStart()
{
WriteEvent(5);
}
[Event(6)]
internal void CLIStop()
{
WriteEvent(6);
}
[Event(7)]
internal void FirstTimeConfigurationStart()
{
WriteEvent(7);
}
[Event(8)]
internal void FirstTimeConfigurationStop()
{
WriteEvent(8);
}
[Event(9)]
internal void TelemetryRegistrationStart()
{
WriteEvent(9);
}
[Event(10)]
internal void TelemetryRegistrationStop()
{
WriteEvent(10);
}
[Event(11)]
internal void TelemetrySaveIfEnabledStart()
{
WriteEvent(11);
}
[Event(12)]
internal void TelemetrySaveIfEnabledStop()
{
WriteEvent(12);
}
[Event(13)]
internal void BuiltInCommandStart()
{
WriteEvent(13);
}
[Event(14)]
internal void BuiltInCommandStop()
{
WriteEvent(14);
}
[Event(15)]
internal void BuiltInCommandParserStart()
{
WriteEvent(15);
}
[Event(16)]
internal void BuiltInCommandParserStop()
{
WriteEvent(16);
}
[Event(17)]
internal void ExtensibleCommandResolverStart()
{
WriteEvent(17);
}
[Event(18)]
internal void ExtensibleCommandResolverStop()
{
WriteEvent(18);
}
[Event(19)]
internal void ExtensibleCommandStart()
{
WriteEvent(19);
}
[Event(20)]
internal void ExtensibleCommandStop()
{
WriteEvent(20);
}
[Event(21)]
internal void TelemetryClientFlushStart()
{
WriteEvent(21);
}
[Event(22)]
internal void TelemetryClientFlushStop()
{
WriteEvent(22);
}
[NonEvent]
internal void LogMachineConfiguration()
{
if (IsEnabled())
{
MachineConfiguration(Environment.MachineName, Environment.ProcessorCount);
}
}
[Event(23)]
internal void MachineConfiguration(string machineName, int processorCount)
{
WriteEvent(23, machineName, processorCount);
}
[NonEvent]
internal void LogDrives()
{
if (IsEnabled())
{
foreach (DriveInfo driveInfo in DriveInfo.GetDrives())
{
try
{
DriveConfiguration(driveInfo.Name, driveInfo.DriveFormat, driveInfo.DriveType.ToString(),
(double)driveInfo.TotalSize / 1024 / 1024, (double)driveInfo.AvailableFreeSpace / 1024 / 1024);
}
catch
{
// If we fail to log a drive, skip it and continue.
}
}
}
}
[Event(24)]
internal void DriveConfiguration(string name, string format, string type, double totalSizeMB, double availableFreeSpaceMB)
{
WriteEvent(24, name, format, type, totalSizeMB, availableFreeSpaceMB);
}
[Event(25)]
internal void AssemblyLoad(string assemblyName, double timeInMs)
{
WriteEvent(25, assemblyName, timeInMs);
}
[NonEvent]
internal void LogMemoryConfiguration()
{
if (IsEnabled())
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Interop.MEMORYSTATUSEX memoryStatusEx = new();
memoryStatusEx.dwLength = (uint)Marshal.SizeOf(memoryStatusEx);
if (Interop.GlobalMemoryStatusEx(ref memoryStatusEx))
{
MemoryConfiguration((int)memoryStatusEx.dwMemoryLoad, (int)(memoryStatusEx.ullAvailPhys / 1024 / 1024),
(int)(memoryStatusEx.ullTotalPhys / 1024 / 1024));
}
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
ProcMemInfo memInfo = new();
if (memInfo.Valid)
{
MemoryConfiguration(memInfo.MemoryLoad, memInfo.AvailableMemoryMB, memInfo.TotalMemoryMB);
}
}
}
}
[Event(26)]
internal void MemoryConfiguration(int memoryLoad, int availablePhysicalMB, int totalPhysicalMB)
{
WriteEvent(26, memoryLoad, availablePhysicalMB, totalPhysicalMB);
}
[NonEvent]
internal void LogMSBuildStart(string fileName, string arguments)
{
if (IsEnabled())
{
MSBuildStart($"{fileName} {arguments}");
}
}
[Event(27)]
internal void MSBuildStart(string cmdline)
{
WriteEvent(27, cmdline);
}
[Event(28)]
internal void MSBuildStop(int exitCode)
{
WriteEvent(28, exitCode);
}
[Event(29)]
internal void CreateBuildCommandStart()
{
WriteEvent(29);
}
[Event(30)]
internal void CreateBuildCommandStop()
{
WriteEvent(30);
}
}
internal class PerformanceLogStartupInformation
{
public PerformanceLogStartupInformation(DateTime mainTimeStamp)
{
// Save the main timestamp.
MainTimeStamp = mainTimeStamp;
// Attempt to load an assembly.
// Ideally, we've picked one that we'll already need, so we're not adding additional overhead.
MeasureModuleLoad();
}
internal DateTime MainTimeStamp { get; private set; }
internal Assembly TimedAssembly { get; private set; }
internal TimeSpan AssemblyLoadTime { get; private set; }
private void MeasureModuleLoad()
{
// Make sure the assembly hasn't been loaded yet.
string assemblyName = "Microsoft.DotNet.Configurer";
try
{
foreach (Assembly loadedAssembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (loadedAssembly.GetName().Name.Equals(assemblyName))
{
// If the assembly is already loaded, then bail.
return;
}
}
}
catch
{
// If we fail to enumerate, just bail.
return;
}
Stopwatch stopWatch = Stopwatch.StartNew();
Assembly assembly;
try
{
assembly = Assembly.Load(assemblyName);
}
catch
{
return;
}
stopWatch.Stop();
if (assembly != null)
{
// Save the results.
TimedAssembly = assembly;
AssemblyLoadTime = stopWatch.Elapsed;
}
}
}
/// <summary>
/// Global memory statistics on Windows.
/// </summary>
internal static class Interop
{
[StructLayout(LayoutKind.Sequential)]
internal struct MEMORYSTATUSEX
{
// The length field must be set to the size of this data structure.
internal uint dwLength;
internal uint dwMemoryLoad;
internal ulong ullTotalPhys;
internal ulong ullAvailPhys;
internal ulong ullTotalPageFile;
internal ulong ullAvailPageFile;
internal ulong ullTotalVirtual;
internal ulong ullAvailVirtual;
internal ulong ullAvailExtendedVirtual;
}
[DllImport("kernel32.dll")]
internal static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);
}
/// <summary>
/// Global memory statistics on Linux.
/// </summary>
internal sealed class ProcMemInfo
{
private const string MemTotal = "MemTotal:";
private const string MemAvailable = "MemAvailable:";
private short _matchingLineCount = 0;
internal ProcMemInfo()
{
Initialize();
}
/// <summary>
/// The data in this class is valid if we parsed the file, found, and properly parsed the two matching lines.
/// </summary>
internal bool Valid
{
get { return _matchingLineCount == 2; }
}
internal int MemoryLoad
{
get { return (int)((double)(TotalMemoryMB - AvailableMemoryMB) / TotalMemoryMB * 100); }
}
internal int AvailableMemoryMB
{
get;
private set;
}
internal int TotalMemoryMB
{
get;
private set;
}
private void Initialize()
{
try
{
using (StreamReader reader = new(File.OpenRead("/proc/meminfo")))
{
string line;
while (!Valid && ((line = reader.ReadLine()) != null))
{
if (line.StartsWith(MemTotal) || line.StartsWith(MemAvailable))
{
string[] tokens = line.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (tokens.Length == 3)
{
if (MemTotal.Equals(tokens[0]))
{
TotalMemoryMB = (int)Convert.ToUInt64(tokens[1]) / 1024;
_matchingLineCount++;
}
else if (MemAvailable.Equals(tokens[0]))
{
AvailableMemoryMB = (int)Convert.ToUInt64(tokens[1]) / 1024;
_matchingLineCount++;
}
}
}
}
}
}
catch (Exception ex) when (ex is IOException || ex.InnerException is IOException)
{
// in some environments (restricted docker container, shared hosting etc.),
// procfs is not accessible and we get UnauthorizedAccessException while the
// inner exception is set to IOException. Ignore and continue when that happens.
}
}
}
|