From e829a5027b1db551f4012cffb195e4bb4339a403 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Fri, 19 Jul 2024 09:08:13 -0700 Subject: [PATCH 01/21] Introducing Environment.CpuUsage --- .../SafeHandles/SafeProcessHandle.Windows.cs | 7 +- .../Win32/SafeHandles/SafeProcessHandle.cs | 7 +- .../Diagnostics/Metrics/RuntimeMetrics.cs | 32 ++++--- .../tests/RuntimeMetricsTests.cs | 83 +++++++++---------- .../src/System.Diagnostics.Process.csproj | 6 +- .../System.Private.CoreLib.Shared.projitems | 15 ++++ .../src/System/Environment.Browser.cs | 13 +++ .../src/System/Environment.FreeBSD.cs | 17 ++++ .../src/System/Environment.Linux.cs | 24 ++++++ .../src/System/Environment.OSX.cs | 48 +++++++++++ .../src/System/Environment.SunOS.cs | 24 ++++++ .../src/System/Environment.Unix.cs | 24 ++++++ .../src/System/Environment.Windows.cs | 40 +++++++++ .../src/System/Environment.cs | 23 +++++ .../src/System/Environment.iOS.cs | 13 +++ .../System.Runtime/ref/System.Runtime.cs | 11 +++ .../System/EnvironmentTests.cs | 38 +++++++++ 17 files changed, 362 insertions(+), 63 deletions(-) rename src/libraries/{System.Diagnostics.Process => Common}/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs (77%) rename src/libraries/{System.Diagnostics.Process => Common}/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs (88%) diff --git a/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs similarity index 77% rename from src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs rename to src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs index 1fc7a409713278..6a7fbb39277c27 100644 --- a/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs @@ -16,7 +16,12 @@ namespace Microsoft.Win32.SafeHandles { - public sealed partial class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid +#if SYSTEM_PRIVATE_CORELIB + internal +#else + public +#endif // SYSTEM_PRIVATE_CORELIB + sealed partial class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid { protected override bool ReleaseHandle() { diff --git a/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs similarity index 88% rename from src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs rename to src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs index c7d52e23e0b0ce..21588930e608ba 100644 --- a/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs @@ -15,7 +15,12 @@ namespace Microsoft.Win32.SafeHandles { - public sealed partial class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid +#if SYSTEM_PRIVATE_CORELIB + internal +#else + public +#endif // SYSTEM_PRIVATE_CORELIB + sealed partial class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid { internal static readonly SafeProcessHandle InvalidHandle = new SafeProcessHandle(); diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/RuntimeMetrics.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/RuntimeMetrics.cs index 1ac12630a201f1..7a1ea40bc4297e 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/RuntimeMetrics.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/RuntimeMetrics.cs @@ -146,12 +146,11 @@ static RuntimeMetrics() unit: "{cpu}", description: "The number of processors available to the process."); - // TODO - Uncomment once an implementation for https://github.com/dotnet/runtime/issues/104844 is available. - //private static readonly ObservableCounter s_processCpuTime = s_meter.CreateObservableCounter( - // "dotnet.process.cpu.time", - // GetCpuTime, - // unit: "s", - // description: "CPU time used by the process as reported by the CLR."); + private static readonly ObservableCounter s_processCpuTime = s_meter.CreateObservableCounter( + "dotnet.process.cpu.time", + GetCpuTime, + unit: "s", + description: "CPU time used by the process as reported by the CLR."); public static bool IsEnabled() { @@ -172,8 +171,8 @@ public static bool IsEnabled() || s_threadPoolQueueLength.Enabled || s_assembliesCount.Enabled || s_exceptions.Enabled - || s_processCpuCount.Enabled; - //|| s_processCpuTime.Enabled; + || s_processCpuCount.Enabled + || s_processCpuTime.Enabled; } private static IEnumerable> GetGarbageCollectionCounts() @@ -188,17 +187,16 @@ private static IEnumerable> GetGarbageCollectionCounts() } } - // TODO - Uncomment once an implementation for https://github.com/dotnet/runtime/issues/104844 is available. - //private static IEnumerable> GetCpuTime() - //{ - // if (OperatingSystem.IsBrowser() || OperatingSystem.IsTvOS() || OperatingSystem.IsIOS()) - // yield break; + private static IEnumerable> GetCpuTime() + { + if (OperatingSystem.IsBrowser() || OperatingSystem.IsTvOS() || OperatingSystem.IsIOS()) + yield break; - // ProcessCpuUsage processCpuUsage = Environment.CpuUsage; + Environment.ProcessCpuUsage processCpuUsage = Environment.CpuUsage; - // yield return new(processCpuUsage.UserTime.TotalSeconds, [new KeyValuePair("cpu.mode", "user")]); - // yield return new(processCpuUsage.PrivilegedTime.TotalSeconds, [new KeyValuePair("cpu.mode", "system")]); - //} + yield return new(processCpuUsage.UserTime.TotalSeconds, [new KeyValuePair("cpu.mode", "user")]); + yield return new(processCpuUsage.PrivilegedTime.TotalSeconds, [new KeyValuePair("cpu.mode", "system")]); + } private static IEnumerable> GetHeapSizes() { diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/RuntimeMetricsTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/RuntimeMetricsTests.cs index f5554720f4551e..66d9617b57c09d 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/RuntimeMetricsTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/RuntimeMetricsTests.cs @@ -112,48 +112,47 @@ public void GcCollectionsCount() } } - // TODO - Uncomment once an implementation for https://github.com/dotnet/runtime/issues/104844 is available. - //[Fact] - //public void CpuTime() - //{ - // using InstrumentRecorder instrumentRecorder = new("dotnet.process.cpu.time"); - - // instrumentRecorder.RecordObservableInstruments(); - - // bool[] foundCpuModes = [false, false]; - - // foreach (Measurement measurement in instrumentRecorder.GetMeasurements().Where(m => m.Value >= 0)) - // { - // var tags = measurement.Tags.ToArray(); - // var tag = tags.SingleOrDefault(k => k.Key == "cpu.mode"); - - // if (tag.Key is not null) - // { - // Assert.True(tag.Value is string, "Expected CPU mode tag to be a string."); - - // string tagValue = (string)tag.Value; - - // switch (tagValue) - // { - // case "user": - // foundCpuModes[0] = true; - // break; - // case "system": - // foundCpuModes[1] = true; - // break; - // default: - // Assert.Fail($"Unexpected CPU mode tag value '{tagValue}'."); - // break; - // } - // } - // } - - // for (int i = 0; i < foundCpuModes.Length; i++) - // { - // var mode = i == 0 ? "user" : "system"; - // Assert.True(foundCpuModes[i], $"Expected to find a measurement for '{mode}' CPU mode."); - // } - //} + [Fact] + public void CpuTime() + { + using InstrumentRecorder instrumentRecorder = new("dotnet.process.cpu.time"); + + instrumentRecorder.RecordObservableInstruments(); + + bool[] foundCpuModes = [false, false]; + + foreach (Measurement measurement in instrumentRecorder.GetMeasurements().Where(m => m.Value >= 0)) + { + var tags = measurement.Tags.ToArray(); + var tag = tags.SingleOrDefault(k => k.Key == "cpu.mode"); + + if (tag.Key is not null) + { + Assert.True(tag.Value is string, "Expected CPU mode tag to be a string."); + + string tagValue = (string)tag.Value; + + switch (tagValue) + { + case "user": + foundCpuModes[0] = true; + break; + case "system": + foundCpuModes[1] = true; + break; + default: + Assert.Fail($"Unexpected CPU mode tag value '{tagValue}'."); + break; + } + } + } + + for (int i = 0; i < foundCpuModes.Length; i++) + { + var mode = i == 0 ? "user" : "system"; + Assert.True(foundCpuModes[i], $"Expected to find a measurement for '{mode}' CPU mode."); + } + } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] public void ExceptionsCount() diff --git a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj index 97e289045e324a..26f0836c2e4fa1 100644 --- a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj +++ b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj @@ -15,7 +15,6 @@ - @@ -35,6 +34,8 @@ + - + diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 533ff2cb96b213..fda8ba20eb04b8 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2174,6 +2174,18 @@ Common\System\Threading\AsyncOverSyncWithIoCancellation.cs + + Common\Interop\Windows\Kernel32\Interop.OpenProcess.cs" + + + Common\Interop\Windows\Advapi32\Interop.ProcessOptions.cs" + + + Common\Microsoft\Win32\SafeHandles\SafeProcessHandle.cs + + + Common\Microsoft\Win32\SafeHandles\SafeProcessHandle.Windows.cs + @@ -2562,6 +2574,9 @@ + + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs index 6d3aabb565fba6..2418044b5cd88d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.IO; +using System.Runtime.Versioning; namespace System { @@ -34,5 +35,17 @@ private static OperatingSystem GetOSVersion() /// /// Path of the executable that started the currently executing process private static string? GetProcessPath() => null; + + /// + /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, + /// and the total time spent running both the application and operating system code. + /// + [SupportedOSPlatform("maccatalyst")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + public static ProcessCpuUsage CpuUsage + { + get { throw new PlatformNotSupportedException(); } + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.FreeBSD.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.FreeBSD.cs index aa568ef7822a9c..b7af5904f30462 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.FreeBSD.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.FreeBSD.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.InteropServices; +using System.Runtime.Versioning; namespace System { @@ -22,5 +23,21 @@ public static unsafe long WorkingSet } } } + + /// + /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, + /// and the total time spent running both the application and operating system code. + /// + [SupportedOSPlatform("maccatalyst")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + public static ProcessCpuUsage CpuUsage + { + get + { + Interop.Process.proc_stats stat = Interop.Process.GetThreadInfo(ProcessId, 0); + return new ProcessCpuUsage { UserTime = TicksToTimeSpan(stat.userTime), PrivilegedTime = TicksToTimeSpan(stat.systemTime) }; + } + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Linux.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Linux.cs index 765ef5ab64e22c..17a1aa3f9dfa48 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Linux.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Linux.cs @@ -2,11 +2,35 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.InteropServices; +using System.Runtime.Versioning; namespace System { public static partial class Environment { public static long WorkingSet => (long)(Interop.procfs.TryReadStatusFile(Interop.procfs.ProcPid.Self, out Interop.procfs.ParsedStatus status) ? status.VmRSS : 0); + + /// + /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, + /// and the total time spent running both the application and operating system code. + /// + [SupportedOSPlatform("maccatalyst")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + public static ProcessCpuUsage CpuUsage + { + get + { + Interop.procfs.ParsedStat stat = GetStat(); + return new ProcessCpuUsage { UserTime = TicksToTimeSpan(GetStat().Utime), PrivilegedTime = TicksToTimeSpan(GetStat().Stime) }; + } + } + + private static Interop.procfs.ParsedStat GetStat() + { + Interop.procfs.ParsedStat stat; + Interop.procfs.TryReadStatFile(Interop.procfs.ProcPid.Self, out stat); + return stat; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs index 8206f95c69061c..82b6345ff40e1e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs @@ -2,11 +2,59 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.InteropServices; +using System.Runtime.Versioning; namespace System { public static partial class Environment { public static long WorkingSet => (long)(Interop.libproc.GetProcessInfoById(ProcessId)?.ptinfo.pti_resident_size ?? 0); + + /// + /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, + /// and the total time spent running both the application and operating system code. + /// + [SupportedOSPlatform("maccatalyst")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + public static ProcessCpuUsage CpuUsage + { + get + { + Interop.libproc.rusage_info_v3 info = Interop.libproc.proc_pid_rusage(ProcessId); + return new ProcessCpuUsage { UserTime = MapTime(info.ri_user_time), PrivilegedTime = MapTime(info.ri_system_time) }; + } + } + + private static volatile uint s_timeBase_numer, s_timeBase_denom; + private static TimeSpan MapTime(ulong sysTime) + { + uint denom = s_timeBase_denom; + if (denom == default) + { + Interop.libSystem.mach_timebase_info_data_t timeBase = GetTimeBase(); + s_timeBase_numer = timeBase.numer; + s_timeBase_denom = denom = timeBase.denom; + } + uint numer = s_timeBase_numer; + + // By dividing by NanosecondsTo100NanosecondsFactor first, we lose some precision, but increase the range + // where no overflow will happen. + return new TimeSpan(Convert.ToInt64(sysTime / NanosecondsTo100NanosecondsFactor * numer / denom)); + } + + private static unsafe Interop.libSystem.mach_timebase_info_data_t GetTimeBase() + { + Interop.libSystem.mach_timebase_info_data_t timeBase = default; + var returnCode = Interop.libSystem.mach_timebase_info(&timeBase); + Debug.Assert(returnCode == 0, $"Non-zero exit code from mach_timebase_info: {returnCode}"); + if (returnCode != 0) + { + // Fallback: let's assume that the time values are in nanoseconds, + // i.e. the time base is 1/1. + timeBase.numer = timeBase.denom = 1; + } + return timeBase; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs index 84199a3d306d28..2897752649a331 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.InteropServices; +using System.Runtime.Versioning; namespace System { @@ -9,5 +10,28 @@ public static partial class Environment { public static long WorkingSet => (long)(Interop.procfs.TryReadProcessStatusInfo(Interop.procfs.ProcPid.Self, out Interop.procfs.ProcessStatusInfo status) ? status.ResidentSetSize : 0); + + /// + /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, + /// and the total time spent running both the application and operating system code. + /// + [SupportedOSPlatform("maccatalyst")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + public static ProcessCpuUsage CpuUsage + { + get + { + Interop.procfs.ParsedStat stat = GetStat(); + return new ProcessCpuUsage { UserTime = TicksToTimeSpan(GetStat().Utime), PrivilegedTime = TicksToTimeSpan(GetStat().Stime) }; + } + } + + private static Interop.procfs.ParsedStat GetStat() + { + Interop.procfs.ParsedStat stat; + Interop.procfs.TryReadStatFile(Interop.procfs.ProcPid.Self, out stat); + return stat; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs index 5a80fe41990b8f..3a89b9fc2b1595 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs @@ -41,5 +41,29 @@ private static string[] GetCommandLineArgsNative() // Consider to use /proc/self/cmdline to get command line return Array.Empty(); } + + private static long s_ticksPerSecond; + + /// Convert a number of "jiffies", or ticks, to a TimeSpan. + /// The number of ticks. + /// The equivalent TimeSpan. + internal static TimeSpan TicksToTimeSpan(double ticks) + { + long ticksPerSecond = Volatile.Read(ref s_ticksPerSecond); + if (ticksPerSecond == 0) + { + // Look up the number of ticks per second in the system's configuration, + // then use that to convert to a TimeSpan + ticksPerSecond = Interop.Sys.SysConf(Interop.Sys.SysConfName._SC_CLK_TCK); + if (ticksPerSecond <= 0) + { + throw new Win32Exception(); + } + + Volatile.Write(ref s_ticksPerSecond, ticksPerSecond); + } + + return TimeSpan.FromSeconds(ticks / (double)ticksPerSecond); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs index 82f7271c780fc8..8f7ebc9bcdaf24 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs @@ -2,10 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using System.Text; using Microsoft.Win32.SafeHandles; @@ -358,5 +360,43 @@ private static unsafe string[] SegmentCommandLine(char* cmdLine) return arrayBuilder.ToArray(); } + + /// + /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, + /// and the total time spent running both the application and operating system code. + /// + [SupportedOSPlatform("maccatalyst")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + public static ProcessCpuUsage CpuUsage + { + get + { + using (SafeProcessHandle handle = GetProcessHandle()) + { + Debug.Assert(!handle.IsInvalid); + + if (!Interop.Kernel32.GetProcessTimes(handle.DangerousGetHandle(), out long create, out long exit, out long kernel, out long user)) + { + throw new Win32Exception(); + } + + return new ProcessCpuUsage { UserTime = new TimeSpan(user), PrivilegedTime = new TimeSpan(kernel) }; + } + } + } + + private static SafeProcessHandle GetProcessHandle() + { + SafeProcessHandle processHandle = Interop.Kernel32.OpenProcess(Interop.Advapi32.ProcessOptions.PROCESS_QUERY_LIMITED_INFORMATION, false, GetProcessId()); + int result = Marshal.GetLastWin32Error(); + if (processHandle.IsInvalid) + { + processHandle.Dispose(); + throw new Win32Exception(result); + } + + return processHandle; + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.cs index aaefefd0d22d4a..db7844eaed2403 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.cs @@ -4,12 +4,35 @@ using System.Collections; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.Versioning; using System.Threading; namespace System { public static partial class Environment { + /// + /// Represents the CPU usage statistics of a process. It includes information about the time spent by the process in both the application code (user mode) + /// and the operating system code (kernel mode). In addition to the total time spent by the process in both user mode and kernel mode. + /// + public readonly struct ProcessCpuUsage + { + /// + /// the amount of time the associated process has spent running code inside the application portion of the process (not the operating system core). + /// + public TimeSpan UserTime { get; internal init; } + + /// + /// The amount of time the process has spent running code inside the operating system code. + /// + public TimeSpan PrivilegedTime { get; internal init; } + + /// + /// The amount of time the process has spent utilizing the CPU including the process time spent in the application code and the process time spent in the operating system code. + /// + public TimeSpan TotalTime => UserTime + PrivilegedTime; + } + public static int ProcessorCount { get; } = GetProcessorCount(); /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.iOS.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.iOS.cs index 973790e0785a01..58a85227101bc3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.iOS.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.iOS.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using System.Threading; using NSSearchPathDirectory = Interop.Sys.NSSearchPathDirectory; @@ -14,6 +15,18 @@ public static partial class Environment #if !TARGET_MACCATALYST // iOS/tvOS aren't allowed to call libproc APIs so return 0 here, this also matches what we returned in earlier releases public static long WorkingSet => 0; + + /// + /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, + /// and the total time spent running both the application and operating system code. + /// + [SupportedOSPlatform("maccatalyst")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + public static ProcessCpuUsage CpuUsage + { + get { throw new PlatformNotSupportedException(); } + } #endif private static Dictionary? s_specialFolders; diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index b88630ba4c592d..a23e2b0cf1c4b9 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -2666,6 +2666,13 @@ protected Enum() { } } public static partial class Environment { + public readonly struct ProcessCpuUsage + { + public System.TimeSpan UserTime { get { throw null; } } + public System.TimeSpan PrivilegedTime { get { throw null; } } + public System.TimeSpan TotalTime { get { throw null; } } + } + public static string CommandLine { get { throw null; } } public static string CurrentDirectory { get { throw null; } set { } } public static int CurrentManagedThreadId { get { throw null; } } @@ -2679,6 +2686,10 @@ public static partial class Environment public static System.OperatingSystem OSVersion { get { throw null; } } public static int ProcessId { get { throw null; } } public static int ProcessorCount { get { throw null; } } + [System.Runtime.Versioning.UnsupportedOSPlatform("ios")] + [System.Runtime.Versioning.UnsupportedOSPlatform("tvos")] + [System.Runtime.Versioning.SupportedOSPlatform("maccatalyst")] + public static ProcessCpuUsage CpuUsage { get { throw null; } } public static string? ProcessPath { get { throw null; } } public static string StackTrace { get { throw null; } } public static string SystemDirectory { get { throw null; } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs index fd47fb81eb7424..83187a66cd06c8 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs @@ -574,6 +574,44 @@ public void GetLogicalDrives_Windows_MatchesExpectedLetters() } } + [Fact] + public void TestCpuUsage() + { + if (PlatformDetection.IsBrowser || PlatformDetection.IsiOS && PlatformDetection.IstvOS) + { + Assert.Throws(() => Environment.CpuUsage); + } + else + { + TimeSpan delta = TimeSpan.FromMinutes(1); + + for (int i = 0; i < 10; i++) + { + Process currentProcess = Process.GetCurrentProcess(); + + TimeSpan userTime = currentProcess.UserProcessorTime; + TimeSpan privilegedTime = currentProcess.PrivilegedProcessorTime; + TimeSpan totalTime = currentProcess.TotalProcessorTime; + + Environment.ProcessCpuUsage usage = Environment.CpuUsage; + Assert.True(usage.UserTime.TotalMilliseconds >= 0); + Assert.True(usage.PrivilegedTime.TotalMilliseconds >= 0); + Assert.True(usage.TotalTime.TotalMilliseconds >= 0); + Assert.Equal(usage.TotalTime, usage.UserTime + usage.PrivilegedTime); + + Assert.True(usage.UserTime >= userTime); + Assert.True(usage.PrivilegedTime >= privilegedTime); + Assert.True(usage.TotalTime >= totalTime); + + Assert.True(usage.UserTime - userTime < delta); + Assert.True(usage.PrivilegedTime - privilegedTime < delta); + Assert.True(usage.TotalTime - totalTime < delta); + + Thread.Sleep(100); + } + } + } + [DllImport("kernel32.dll", SetLastError = true)] internal static extern int GetLogicalDrives(); From 97ff30201b74fd2b2145729120a3b41ef2266f77 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Fri, 19 Jul 2024 10:36:59 -0700 Subject: [PATCH 02/21] Check Unix builds --- .../System.Private.CoreLib.Shared.projitems | 2 - .../src/System/Environment.Browser.cs | 12 ------ .../src/System/Environment.FreeBSD.cs | 16 -------- .../src/System/Environment.Linux.cs | 23 ----------- .../src/System/Environment.SunOS.cs | 23 ----------- .../src/System/Environment.Unix.cs | 38 +++++++++++-------- 6 files changed, 23 insertions(+), 91 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index fda8ba20eb04b8..0a68c992d7c2c1 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2574,8 +2574,6 @@ - - diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs index 2418044b5cd88d..97658880343b9e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs @@ -35,17 +35,5 @@ private static OperatingSystem GetOSVersion() /// /// Path of the executable that started the currently executing process private static string? GetProcessPath() => null; - - /// - /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, - /// and the total time spent running both the application and operating system code. - /// - [SupportedOSPlatform("maccatalyst")] - [UnsupportedOSPlatform("ios")] - [UnsupportedOSPlatform("tvos")] - public static ProcessCpuUsage CpuUsage - { - get { throw new PlatformNotSupportedException(); } - } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.FreeBSD.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.FreeBSD.cs index b7af5904f30462..493ebd0dfde9ae 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.FreeBSD.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.FreeBSD.cs @@ -23,21 +23,5 @@ public static unsafe long WorkingSet } } } - - /// - /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, - /// and the total time spent running both the application and operating system code. - /// - [SupportedOSPlatform("maccatalyst")] - [UnsupportedOSPlatform("ios")] - [UnsupportedOSPlatform("tvos")] - public static ProcessCpuUsage CpuUsage - { - get - { - Interop.Process.proc_stats stat = Interop.Process.GetThreadInfo(ProcessId, 0); - return new ProcessCpuUsage { UserTime = TicksToTimeSpan(stat.userTime), PrivilegedTime = TicksToTimeSpan(stat.systemTime) }; - } - } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Linux.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Linux.cs index 17a1aa3f9dfa48..265f762bb06650 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Linux.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Linux.cs @@ -9,28 +9,5 @@ namespace System public static partial class Environment { public static long WorkingSet => (long)(Interop.procfs.TryReadStatusFile(Interop.procfs.ProcPid.Self, out Interop.procfs.ParsedStatus status) ? status.VmRSS : 0); - - /// - /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, - /// and the total time spent running both the application and operating system code. - /// - [SupportedOSPlatform("maccatalyst")] - [UnsupportedOSPlatform("ios")] - [UnsupportedOSPlatform("tvos")] - public static ProcessCpuUsage CpuUsage - { - get - { - Interop.procfs.ParsedStat stat = GetStat(); - return new ProcessCpuUsage { UserTime = TicksToTimeSpan(GetStat().Utime), PrivilegedTime = TicksToTimeSpan(GetStat().Stime) }; - } - } - - private static Interop.procfs.ParsedStat GetStat() - { - Interop.procfs.ParsedStat stat; - Interop.procfs.TryReadStatFile(Interop.procfs.ProcPid.Self, out stat); - return stat; - } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs index 2897752649a331..4d9c8b4322486e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs @@ -10,28 +10,5 @@ public static partial class Environment { public static long WorkingSet => (long)(Interop.procfs.TryReadProcessStatusInfo(Interop.procfs.ProcPid.Self, out Interop.procfs.ProcessStatusInfo status) ? status.ResidentSetSize : 0); - - /// - /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, - /// and the total time spent running both the application and operating system code. - /// - [SupportedOSPlatform("maccatalyst")] - [UnsupportedOSPlatform("ios")] - [UnsupportedOSPlatform("tvos")] - public static ProcessCpuUsage CpuUsage - { - get - { - Interop.procfs.ParsedStat stat = GetStat(); - return new ProcessCpuUsage { UserTime = TicksToTimeSpan(GetStat().Utime), PrivilegedTime = TicksToTimeSpan(GetStat().Stime) }; - } - } - - private static Interop.procfs.ParsedStat GetStat() - { - Interop.procfs.ParsedStat stat; - Interop.procfs.TryReadStatFile(Interop.procfs.ProcPid.Self, out stat); - return stat; - } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs index 3a89b9fc2b1595..6af8d122cf62c5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; @@ -42,28 +43,35 @@ private static string[] GetCommandLineArgsNative() return Array.Empty(); } - private static long s_ticksPerSecond; - /// Convert a number of "jiffies", or ticks, to a TimeSpan. - /// The number of ticks. - /// The equivalent TimeSpan. - internal static TimeSpan TicksToTimeSpan(double ticks) + /// + /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, + /// and the total time spent running both the application and operating system code. + /// + [SupportedOSPlatform("maccatalyst")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + public static ProcessCpuUsage CpuUsage { - long ticksPerSecond = Volatile.Read(ref s_ticksPerSecond); - if (ticksPerSecond == 0) + get { - // Look up the number of ticks per second in the system's configuration, - // then use that to convert to a TimeSpan - ticksPerSecond = Interop.Sys.SysConf(Interop.Sys.SysConfName._SC_CLK_TCK); - if (ticksPerSecond <= 0) + Interop.Sys.ProcessCpuInformation cpuInfo = default; + Interop.Sys.GetCpuUtilization(ref cpuInfo); + + ulong userTime100Nanoseconds = cpuInfo.lastRecordedUserTime / 100; // nanoseconds to 100-nanoseconds + if (userTime100Nanoseconds > long.MaxValue) { - throw new Win32Exception(); + userTime100Nanoseconds = long.MaxValue; } - Volatile.Write(ref s_ticksPerSecond, ticksPerSecond); - } + ulong kernelTime100Nanoseconds = cpuInfo.lastRecordedKernelTime / 100; // nanoseconds to 100-nanoseconds + if (kernelTime100Nanoseconds > long.MaxValue) + { + kernelTime100Nanoseconds = long.MaxValue; + } - return TimeSpan.FromSeconds(ticks / (double)ticksPerSecond); + return new ProcessCpuUsage { UserTime = new TimeSpan((long)userTime100Nanoseconds), PrivilegedTime = new TimeSpan((long)kernelTime100Nanoseconds) }; + } } } } From dc237c0f2cf6feab07fcbb60d51ca2375b908846 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Fri, 19 Jul 2024 10:53:52 -0700 Subject: [PATCH 03/21] increment --- .../Win32/SafeHandles/SafeProcessHandle.Windows.cs | 8 ++++---- .../src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs | 8 ++++---- .../src/System.Diagnostics.Process.csproj | 2 +- .../src/System/Environment.Browser.cs | 1 - .../src/System/Environment.FreeBSD.cs | 1 - .../src/System/Environment.Linux.cs | 1 - .../src/System/Environment.SunOS.cs | 1 - .../System.Private.CoreLib/src/System/Environment.Unix.cs | 1 + .../src/System/Environment.Windows.cs | 4 ++-- 9 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs index 6a7fbb39277c27..70f8e128f5e310 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs @@ -16,11 +16,11 @@ namespace Microsoft.Win32.SafeHandles { -#if SYSTEM_PRIVATE_CORELIB - internal -#else +#if SYSTEM_DIAGNOSTICS_PROCESS public -#endif // SYSTEM_PRIVATE_CORELIB +#else + internal +#endif // SYSTEM_DIAGNOSTICS_PROCESS sealed partial class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid { protected override bool ReleaseHandle() diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs index 21588930e608ba..ba41594b82f075 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs @@ -15,11 +15,11 @@ namespace Microsoft.Win32.SafeHandles { -#if SYSTEM_PRIVATE_CORELIB - internal -#else +#if SYSTEM_DIAGNOSTICS_PROCESS public -#endif // SYSTEM_PRIVATE_CORELIB +#else + internal +#endif // SYSTEM_DIAGNOSTICS_PROCESS sealed partial class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid { internal static readonly SafeProcessHandle InvalidHandle = new SafeProcessHandle(); diff --git a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj index 26f0836c2e4fa1..f9d60017598cfe 100644 --- a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj +++ b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj @@ -2,7 +2,7 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-freebsd;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent) - $(DefineConstants);FEATURE_REGISTRY + $(DefineConstants);FEATURE_REGISTRY;SYSTEM_DIAGNOSTICS_PROCESS true false diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs index 97658880343b9e..6d3aabb565fba6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.IO; -using System.Runtime.Versioning; namespace System { diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.FreeBSD.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.FreeBSD.cs index 493ebd0dfde9ae..aa568ef7822a9c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.FreeBSD.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.FreeBSD.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.InteropServices; -using System.Runtime.Versioning; namespace System { diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Linux.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Linux.cs index 265f762bb06650..765ef5ab64e22c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Linux.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Linux.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.InteropServices; -using System.Runtime.Versioning; namespace System { diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs index 4d9c8b4322486e..84199a3d306d28 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.SunOS.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.InteropServices; -using System.Runtime.Versioning; namespace System { diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs index 6af8d122cf62c5..f3a807eb455af9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs @@ -6,6 +6,7 @@ using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using System.Text; using System.Threading; diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs index 8f7ebc9bcdaf24..d213f7a757d103 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs @@ -388,10 +388,10 @@ public static ProcessCpuUsage CpuUsage private static SafeProcessHandle GetProcessHandle() { - SafeProcessHandle processHandle = Interop.Kernel32.OpenProcess(Interop.Advapi32.ProcessOptions.PROCESS_QUERY_LIMITED_INFORMATION, false, GetProcessId()); - int result = Marshal.GetLastWin32Error(); + SafeProcessHandle processHandle = Interop.Kernel32.OpenProcess(Interop.Advapi32.ProcessOptions.PROCESS_QUERY_LIMITED_INFORMATION, false, ProcessId); if (processHandle.IsInvalid) { + int result = Marshal.GetLastWin32Error(); processHandle.Dispose(); throw new Win32Exception(result); } From 9aa3ddd7d238318054d9dcb8ac38f131f7b68316 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Fri, 19 Jul 2024 11:02:50 -0700 Subject: [PATCH 04/21] update 1 --- .../src/System.Private.CoreLib.Shared.projitems | 1 + .../System.Private.CoreLib/src/System/Environment.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 0a68c992d7c2c1..9b2d9b4e10aab9 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2575,6 +2575,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.cs index db7844eaed2403..e912eb2538bdb5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.cs @@ -20,12 +20,12 @@ public readonly struct ProcessCpuUsage /// /// the amount of time the associated process has spent running code inside the application portion of the process (not the operating system core). /// - public TimeSpan UserTime { get; internal init; } + public TimeSpan UserTime { get; internal set; } /// /// The amount of time the process has spent running code inside the operating system code. /// - public TimeSpan PrivilegedTime { get; internal init; } + public TimeSpan PrivilegedTime { get; internal set; } /// /// The amount of time the process has spent utilizing the CPU including the process time spent in the application code and the process time spent in the operating system code. From aee85bb46337ce5532bec54ebf92effb6c59a6c6 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Fri, 19 Jul 2024 11:06:21 -0700 Subject: [PATCH 05/21] enable browser test --- .../System.Runtime.Extensions.Tests/System/EnvironmentTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs index 83187a66cd06c8..b2de3eefedfcd1 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs @@ -577,7 +577,7 @@ public void GetLogicalDrives_Windows_MatchesExpectedLetters() [Fact] public void TestCpuUsage() { - if (PlatformDetection.IsBrowser || PlatformDetection.IsiOS && PlatformDetection.IstvOS) + if (PlatformDetection.IsiOS && PlatformDetection.IstvOS) { Assert.Throws(() => Environment.CpuUsage); } From 76b5e43c4fa5c1b1b5037284052450cc3f506744 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Fri, 19 Jul 2024 11:21:31 -0700 Subject: [PATCH 06/21] revert to use init. --- .../System.Private.CoreLib/src/System/Environment.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.cs index e912eb2538bdb5..db7844eaed2403 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.cs @@ -20,12 +20,12 @@ public readonly struct ProcessCpuUsage /// /// the amount of time the associated process has spent running code inside the application portion of the process (not the operating system core). /// - public TimeSpan UserTime { get; internal set; } + public TimeSpan UserTime { get; internal init; } /// /// The amount of time the process has spent running code inside the operating system code. /// - public TimeSpan PrivilegedTime { get; internal set; } + public TimeSpan PrivilegedTime { get; internal init; } /// /// The amount of time the process has spent utilizing the CPU including the process time spent in the application code and the process time spent in the operating system code. From 80c446bbd241c96870ced790dfab4eced4ee37d5 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Fri, 19 Jul 2024 15:09:52 -0700 Subject: [PATCH 07/21] Updates --- .../src/System/Environment.Browser.cs | 30 +++++++++++++++++++ .../src/System/Environment.Unix.cs | 3 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs index 6d3aabb565fba6..75a2633e67e34e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs @@ -34,5 +34,35 @@ private static OperatingSystem GetOSVersion() /// /// Path of the executable that started the currently executing process private static string? GetProcessPath() => null; + + /// + /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, + /// and the total time spent running both the application and operating system code. + /// + [SupportedOSPlatform("maccatalyst")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + public static ProcessCpuUsage CpuUsage + { + get + { + Interop.Sys.ProcessCpuInformation cpuInfo = default; + Interop.Sys.GetCpuUtilization(ref cpuInfo); + + ulong userTime100Nanoseconds = cpuInfo.lastRecordedUserTime / 100; // nanoseconds to 100-nanoseconds + if (userTime100Nanoseconds > long.MaxValue) + { + userTime100Nanoseconds = long.MaxValue; + } + + ulong kernelTime100Nanoseconds = cpuInfo.lastRecordedKernelTime / 100; // nanoseconds to 100-nanoseconds + if (kernelTime100Nanoseconds > long.MaxValue) + { + kernelTime100Nanoseconds = long.MaxValue; + } + + return new ProcessCpuUsage { UserTime = new TimeSpan((long)userTime100Nanoseconds), PrivilegedTime = new TimeSpan((long)kernelTime100Nanoseconds) }; + } + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs index f3a807eb455af9..f82c32969852cc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs @@ -44,7 +44,7 @@ private static string[] GetCommandLineArgsNative() return Array.Empty(); } - +#if !TARGET_OSX && !TARGET_IOS && !TARGET_TVOS /// /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, /// and the total time spent running both the application and operating system code. @@ -74,5 +74,6 @@ public static ProcessCpuUsage CpuUsage return new ProcessCpuUsage { UserTime = new TimeSpan((long)userTime100Nanoseconds), PrivilegedTime = new TimeSpan((long)kernelTime100Nanoseconds) }; } } +#endif // !TARGET_OSX && !TARGET_IOS && !TARGET_TVOS } } From 14404e68e7a48b4e4a3f122e6d723cacef1774b6 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Fri, 19 Jul 2024 15:49:38 -0700 Subject: [PATCH 08/21] Fix OSX build failures --- .../src/Resources/Strings.resx | 60 ++++++++++--------- .../src/System/Environment.Browser.cs | 1 + .../src/System/Environment.OSX.cs | 3 + .../src/System/Environment.iOS.cs | 2 +- 4 files changed, 38 insertions(+), 28 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 7fbe6aa0963827..397893e8bf03ab 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -1,17 +1,17 @@  - @@ -4337,4 +4337,10 @@ Only array or span of primitive or enum types can be initialized from static data. + + Could not get all running Process IDs. + + + Failed to set or retrieve rusage information. See the error code for OS-specific error information. + diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs index 75a2633e67e34e..4815ef73615b0f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.IO; +using System.Runtime.Versioning; namespace System { diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs index 82b6345ff40e1e..c13a975b198d8d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.Versioning; @@ -26,6 +27,8 @@ public static ProcessCpuUsage CpuUsage } } + private const int NanosecondsTo100NanosecondsFactor = 100; + private static volatile uint s_timeBase_numer, s_timeBase_denom; private static TimeSpan MapTime(ulong sysTime) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.iOS.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.iOS.cs index 58a85227101bc3..b6bd6a16bdb7df 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.iOS.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.iOS.cs @@ -25,7 +25,7 @@ public static partial class Environment [UnsupportedOSPlatform("tvos")] public static ProcessCpuUsage CpuUsage { - get { throw new PlatformNotSupportedException(); } + get => new ProcessCpuUsage { UserTime = TimeSpan.Zero, PrivilegedTime = TimeSpan.Zero }; } #endif From 50bcf87c8a48a55d744094798b8cf89a423ecf80 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Fri, 19 Jul 2024 16:34:19 -0700 Subject: [PATCH 09/21] Fix MACCATALYST builds --- .../System.Private.CoreLib/src/System/Environment.Unix.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs index f82c32969852cc..d5c1ce17c0bb88 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs @@ -44,7 +44,7 @@ private static string[] GetCommandLineArgsNative() return Array.Empty(); } -#if !TARGET_OSX && !TARGET_IOS && !TARGET_TVOS +#if !TARGET_OSX && !TARGET_IOS && !TARGET_TVOS && !TARGET_MACCATALYST /// /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, /// and the total time spent running both the application and operating system code. @@ -74,6 +74,6 @@ public static ProcessCpuUsage CpuUsage return new ProcessCpuUsage { UserTime = new TimeSpan((long)userTime100Nanoseconds), PrivilegedTime = new TimeSpan((long)kernelTime100Nanoseconds) }; } } -#endif // !TARGET_OSX && !TARGET_IOS && !TARGET_TVOS +#endif // !TARGET_OSX && !TARGET_IOS && !TARGET_TVOS && !TARGET_MACCATALYST } } From fe589da41c1a16fad7fcb59119504b999fd8643e Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Fri, 19 Jul 2024 19:15:16 -0700 Subject: [PATCH 10/21] Address different feedback --- .../SafeHandles/SafeProcessHandle.Windows.cs | 7 +-- .../Win32/SafeHandles/SafeProcessHandle.cs | 7 +-- .../src/System.Diagnostics.Process.csproj | 8 ++- .../src/System/Diagnostics/Process.FreeBSD.cs | 15 ++++++ .../src/System/Diagnostics/Process.Linux.cs | 15 +++--- .../src/System/Diagnostics/Process.OSX.cs | 15 ++++++ .../src/System/Diagnostics/Process.Windows.cs | 6 +-- .../src/System/Diagnostics/Process.cs | 2 + .../src/Resources/Strings.resx | 6 --- .../System.Private.CoreLib.Shared.projitems | 14 ------ .../src/System/Environment.OSX.cs | 49 ------------------- .../src/System/Environment.Unix.cs | 2 +- .../src/System/Environment.Windows.cs | 30 ++---------- 13 files changed, 51 insertions(+), 125 deletions(-) rename src/libraries/{Common => System.Diagnostics.Process}/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs (77%) rename src/libraries/{Common => System.Diagnostics.Process}/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs (88%) diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs b/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs similarity index 77% rename from src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs rename to src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs index 70f8e128f5e310..1fc7a409713278 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs +++ b/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs @@ -16,12 +16,7 @@ namespace Microsoft.Win32.SafeHandles { -#if SYSTEM_DIAGNOSTICS_PROCESS - public -#else - internal -#endif // SYSTEM_DIAGNOSTICS_PROCESS - sealed partial class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid + public sealed partial class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid { protected override bool ReleaseHandle() { diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs b/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs similarity index 88% rename from src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs rename to src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs index ba41594b82f075..c7d52e23e0b0ce 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs +++ b/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs @@ -15,12 +15,7 @@ namespace Microsoft.Win32.SafeHandles { -#if SYSTEM_DIAGNOSTICS_PROCESS - public -#else - internal -#endif // SYSTEM_DIAGNOSTICS_PROCESS - sealed partial class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid + public sealed partial class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvalid { internal static readonly SafeProcessHandle InvalidHandle = new SafeProcessHandle(); diff --git a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj index f9d60017598cfe..97e289045e324a 100644 --- a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj +++ b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj @@ -2,7 +2,7 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-freebsd;$(NetCoreAppCurrent)-linux;$(NetCoreAppCurrent)-osx;$(NetCoreAppCurrent)-maccatalyst;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos;$(NetCoreAppCurrent) - $(DefineConstants);FEATURE_REGISTRY;SYSTEM_DIAGNOSTICS_PROCESS + $(DefineConstants);FEATURE_REGISTRY true false @@ -15,6 +15,7 @@ + @@ -34,8 +35,6 @@ - - + diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.FreeBSD.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.FreeBSD.cs index ab2c652ed2cc97..a597bb2f6565ea 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.FreeBSD.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.FreeBSD.cs @@ -34,6 +34,11 @@ public TimeSpan TotalProcessorTime { get { + if (IsCurrentProcess) + { + return Environment.CpuUsage.TotalTime; + } + EnsureState(State.HaveNonExitedId); Interop.Process.proc_stats stat = Interop.Process.GetThreadInfo(_processId, 0); return Process.TicksToTimeSpan(stat.userTime + stat.systemTime); @@ -51,6 +56,11 @@ public TimeSpan UserProcessorTime { get { + if (IsCurrentProcess) + { + return Environment.CpuUsage.UserTime; + } + EnsureState(State.HaveNonExitedId); Interop.Process.proc_stats stat = Interop.Process.GetThreadInfo(_processId, 0); @@ -66,6 +76,11 @@ public TimeSpan PrivilegedProcessorTime { get { + if (IsCurrentProcess) + { + return Environment.CpuUsage.PrivilegedTime; + } + EnsureState(State.HaveNonExitedId); Interop.Process.proc_stats stat = Interop.Process.GetThreadInfo(_processId, 0); diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs index dd1b40fd9a570c..4541a450c67ba9 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs @@ -53,10 +53,7 @@ public static Process[] GetProcessesByName(string? processName, string machineNa [SupportedOSPlatform("maccatalyst")] public TimeSpan PrivilegedProcessorTime { - get - { - return TicksToTimeSpan(GetStat().stime); - } + get => IsCurrentProcess ? Environment.CpuUsage.PrivilegedTime : TicksToTimeSpan(GetStat().stime); } /// Gets the time the associated process was started. @@ -132,6 +129,11 @@ public TimeSpan TotalProcessorTime { get { + if (IsCurrentProcess) + { + Environment.CpuUsage.TotalTime; + } + Interop.procfs.ParsedStat stat = GetStat(); return TicksToTimeSpan(stat.utime + stat.stime); } @@ -146,10 +148,7 @@ public TimeSpan TotalProcessorTime [SupportedOSPlatform("maccatalyst")] public TimeSpan UserProcessorTime { - get - { - return TicksToTimeSpan(GetStat().utime); - } + get => IsCurrentProcess ? Environment.CpuUsage.UserTime : TicksToTimeSpan(GetStat().utime); } partial void EnsureHandleCountPopulated() diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.OSX.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.OSX.cs index 07f55780d82de9..b84938c65e8eea 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.OSX.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.OSX.cs @@ -22,6 +22,11 @@ public TimeSpan PrivilegedProcessorTime { get { + if (IsCurrentProcess) + { + return Environment.CpuUsage.PrivilegedTime; + } + EnsureState(State.HaveNonExitedId); Interop.libproc.rusage_info_v3 info = Interop.libproc.proc_pid_rusage(_processId); return MapTime(info.ri_system_time); @@ -64,6 +69,11 @@ public TimeSpan TotalProcessorTime { get { + if (IsCurrentProcess) + { + return Environment.CpuUsage.TotalTime; + } + EnsureState(State.HaveNonExitedId); Interop.libproc.rusage_info_v3 info = Interop.libproc.proc_pid_rusage(_processId); return MapTime(info.ri_system_time + info.ri_user_time); @@ -81,6 +91,11 @@ public TimeSpan UserProcessorTime { get { + if (IsCurrentProcess) + { + return Environment.CpuUsage.UserTime; + } + EnsureState(State.HaveNonExitedId); Interop.libproc.rusage_info_v3 info = Interop.libproc.proc_pid_rusage(_processId); return MapTime(info.ri_user_time); diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs index 4407fc81e821e6..1ffa8882094759 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs @@ -232,7 +232,7 @@ private DateTime ExitTimeCore [SupportedOSPlatform("maccatalyst")] public TimeSpan PrivilegedProcessorTime { - get { return GetProcessTimes().PrivilegedProcessorTime; } + get => IsCurrentProcess ? Environment.CpuUsage.PrivilegedTime : GetProcessTimes().TotalProcessorTime; } /// Gets the time the associated process was started. @@ -251,7 +251,7 @@ internal DateTime StartTimeCore [SupportedOSPlatform("maccatalyst")] public TimeSpan TotalProcessorTime { - get { return GetProcessTimes().TotalProcessorTime; } + get => IsCurrentProcess ? Environment.CpuUsage.TotalTime : GetProcessTimes().TotalProcessorTime; } /// @@ -263,7 +263,7 @@ public TimeSpan TotalProcessorTime [SupportedOSPlatform("maccatalyst")] public TimeSpan UserProcessorTime { - get { return GetProcessTimes().UserProcessorTime; } + get => IsCurrentProcess ? Environment.CpuUsage.UserTime : GetProcessTimes().UserProcessorTime; } /// diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs index cc8a0ae13811d3..4218596c129644 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs @@ -1106,6 +1106,8 @@ public static Process[] GetProcesses(string machineName) return processes; } + private bool IsCurrentProcess => _processId == Environment.ProcessId; + /// /// /// Returns a new diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 397893e8bf03ab..5551edb6b821a3 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -4337,10 +4337,4 @@ Only array or span of primitive or enum types can be initialized from static data. - - Could not get all running Process IDs. - - - Failed to set or retrieve rusage information. See the error code for OS-specific error information. - diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 9b2d9b4e10aab9..533ff2cb96b213 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2174,18 +2174,6 @@ Common\System\Threading\AsyncOverSyncWithIoCancellation.cs - - Common\Interop\Windows\Kernel32\Interop.OpenProcess.cs" - - - Common\Interop\Windows\Advapi32\Interop.ProcessOptions.cs" - - - Common\Microsoft\Win32\SafeHandles\SafeProcessHandle.cs - - - Common\Microsoft\Win32\SafeHandles\SafeProcessHandle.Windows.cs - @@ -2574,8 +2562,6 @@ - - diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs index c13a975b198d8d..1d124fff28035d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs @@ -10,54 +10,5 @@ namespace System public static partial class Environment { public static long WorkingSet => (long)(Interop.libproc.GetProcessInfoById(ProcessId)?.ptinfo.pti_resident_size ?? 0); - - /// - /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, - /// and the total time spent running both the application and operating system code. - /// - [SupportedOSPlatform("maccatalyst")] - [UnsupportedOSPlatform("ios")] - [UnsupportedOSPlatform("tvos")] - public static ProcessCpuUsage CpuUsage - { - get - { - Interop.libproc.rusage_info_v3 info = Interop.libproc.proc_pid_rusage(ProcessId); - return new ProcessCpuUsage { UserTime = MapTime(info.ri_user_time), PrivilegedTime = MapTime(info.ri_system_time) }; - } - } - - private const int NanosecondsTo100NanosecondsFactor = 100; - - private static volatile uint s_timeBase_numer, s_timeBase_denom; - private static TimeSpan MapTime(ulong sysTime) - { - uint denom = s_timeBase_denom; - if (denom == default) - { - Interop.libSystem.mach_timebase_info_data_t timeBase = GetTimeBase(); - s_timeBase_numer = timeBase.numer; - s_timeBase_denom = denom = timeBase.denom; - } - uint numer = s_timeBase_numer; - - // By dividing by NanosecondsTo100NanosecondsFactor first, we lose some precision, but increase the range - // where no overflow will happen. - return new TimeSpan(Convert.ToInt64(sysTime / NanosecondsTo100NanosecondsFactor * numer / denom)); - } - - private static unsafe Interop.libSystem.mach_timebase_info_data_t GetTimeBase() - { - Interop.libSystem.mach_timebase_info_data_t timeBase = default; - var returnCode = Interop.libSystem.mach_timebase_info(&timeBase); - Debug.Assert(returnCode == 0, $"Non-zero exit code from mach_timebase_info: {returnCode}"); - if (returnCode != 0) - { - // Fallback: let's assume that the time values are in nanoseconds, - // i.e. the time base is 1/1. - timeBase.numer = timeBase.denom = 1; - } - return timeBase; - } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs index d5c1ce17c0bb88..042970db485fbc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs @@ -44,7 +44,7 @@ private static string[] GetCommandLineArgsNative() return Array.Empty(); } -#if !TARGET_OSX && !TARGET_IOS && !TARGET_TVOS && !TARGET_MACCATALYST +#if !TARGET_IOS && !TARGET_TVOS /// /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, /// and the total time spent running both the application and operating system code. diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs index d213f7a757d103..8639474e5d18ad 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs @@ -370,33 +370,9 @@ private static unsafe string[] SegmentCommandLine(char* cmdLine) [UnsupportedOSPlatform("tvos")] public static ProcessCpuUsage CpuUsage { - get - { - using (SafeProcessHandle handle = GetProcessHandle()) - { - Debug.Assert(!handle.IsInvalid); - - if (!Interop.Kernel32.GetProcessTimes(handle.DangerousGetHandle(), out long create, out long exit, out long kernel, out long user)) - { - throw new Win32Exception(); - } - - return new ProcessCpuUsage { UserTime = new TimeSpan(user), PrivilegedTime = new TimeSpan(kernel) }; - } - } - } - - private static SafeProcessHandle GetProcessHandle() - { - SafeProcessHandle processHandle = Interop.Kernel32.OpenProcess(Interop.Advapi32.ProcessOptions.PROCESS_QUERY_LIMITED_INFORMATION, false, ProcessId); - if (processHandle.IsInvalid) - { - int result = Marshal.GetLastWin32Error(); - processHandle.Dispose(); - throw new Win32Exception(result); - } - - return processHandle; + get => Interop.Kernel32.GetProcessTimes(Interop.Kernel32.GetCurrentProcess(), out _, out _, out long procKernelTime, out long procUserTime) ? + new ProcessCpuUsage { UserTime = new TimeSpan(procUserTime), PrivilegedTime = new TimeSpan(procKernelTime) } : + new ProcessCpuUsage { UserTime = TimeSpan.Zero, PrivilegedTime = TimeSpan.Zero }; } } } From 15d11ed968d08f1c92230691358e5210c7af31d8 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Sat, 20 Jul 2024 12:05:04 -0700 Subject: [PATCH 11/21] Addressing more feedback --- .../tests/RuntimeMetricsTests.cs | 2 +- .../src/System/Diagnostics/Process.Linux.cs | 2 +- .../src/System/Diagnostics/Process.Windows.cs | 2 +- .../src/System/Environment.Browser.cs | 30 ----------- .../src/System/Environment.Unix.cs | 32 ------------ .../src/System/Environment.UnixOrBrowser.cs | 30 +++++++++++ .../src/System/Environment.iOS.cs | 19 ------- .../System/EnvironmentTests.cs | 52 +++++++++++-------- 8 files changed, 62 insertions(+), 107 deletions(-) diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/RuntimeMetricsTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/RuntimeMetricsTests.cs index 66d9617b57c09d..c4d38f82303122 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/RuntimeMetricsTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/RuntimeMetricsTests.cs @@ -112,7 +112,7 @@ public void GcCollectionsCount() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] public void CpuTime() { using InstrumentRecorder instrumentRecorder = new("dotnet.process.cpu.time"); diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs index 4541a450c67ba9..cf5ceb58e831cc 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Linux.cs @@ -131,7 +131,7 @@ public TimeSpan TotalProcessorTime { if (IsCurrentProcess) { - Environment.CpuUsage.TotalTime; + return Environment.CpuUsage.TotalTime; } Interop.procfs.ParsedStat stat = GetStat(); diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs index 1ffa8882094759..06a2bd51d6d402 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs @@ -232,7 +232,7 @@ private DateTime ExitTimeCore [SupportedOSPlatform("maccatalyst")] public TimeSpan PrivilegedProcessorTime { - get => IsCurrentProcess ? Environment.CpuUsage.PrivilegedTime : GetProcessTimes().TotalProcessorTime; + get => IsCurrentProcess ? Environment.CpuUsage.PrivilegedTime : GetProcessTimes().PrivilegedProcessorTime; } /// Gets the time the associated process was started. diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs index 4815ef73615b0f..97658880343b9e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs @@ -35,35 +35,5 @@ private static OperatingSystem GetOSVersion() /// /// Path of the executable that started the currently executing process private static string? GetProcessPath() => null; - - /// - /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, - /// and the total time spent running both the application and operating system code. - /// - [SupportedOSPlatform("maccatalyst")] - [UnsupportedOSPlatform("ios")] - [UnsupportedOSPlatform("tvos")] - public static ProcessCpuUsage CpuUsage - { - get - { - Interop.Sys.ProcessCpuInformation cpuInfo = default; - Interop.Sys.GetCpuUtilization(ref cpuInfo); - - ulong userTime100Nanoseconds = cpuInfo.lastRecordedUserTime / 100; // nanoseconds to 100-nanoseconds - if (userTime100Nanoseconds > long.MaxValue) - { - userTime100Nanoseconds = long.MaxValue; - } - - ulong kernelTime100Nanoseconds = cpuInfo.lastRecordedKernelTime / 100; // nanoseconds to 100-nanoseconds - if (kernelTime100Nanoseconds > long.MaxValue) - { - kernelTime100Nanoseconds = long.MaxValue; - } - - return new ProcessCpuUsage { UserTime = new TimeSpan((long)userTime100Nanoseconds), PrivilegedTime = new TimeSpan((long)kernelTime100Nanoseconds) }; - } - } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs index 042970db485fbc..c5110352003507 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs @@ -43,37 +43,5 @@ private static string[] GetCommandLineArgsNative() // Consider to use /proc/self/cmdline to get command line return Array.Empty(); } - -#if !TARGET_IOS && !TARGET_TVOS - /// - /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, - /// and the total time spent running both the application and operating system code. - /// - [SupportedOSPlatform("maccatalyst")] - [UnsupportedOSPlatform("ios")] - [UnsupportedOSPlatform("tvos")] - public static ProcessCpuUsage CpuUsage - { - get - { - Interop.Sys.ProcessCpuInformation cpuInfo = default; - Interop.Sys.GetCpuUtilization(ref cpuInfo); - - ulong userTime100Nanoseconds = cpuInfo.lastRecordedUserTime / 100; // nanoseconds to 100-nanoseconds - if (userTime100Nanoseconds > long.MaxValue) - { - userTime100Nanoseconds = long.MaxValue; - } - - ulong kernelTime100Nanoseconds = cpuInfo.lastRecordedKernelTime / 100; // nanoseconds to 100-nanoseconds - if (kernelTime100Nanoseconds > long.MaxValue) - { - kernelTime100Nanoseconds = long.MaxValue; - } - - return new ProcessCpuUsage { UserTime = new TimeSpan((long)userTime100Nanoseconds), PrivilegedTime = new TimeSpan((long)kernelTime100Nanoseconds) }; - } - } -#endif // !TARGET_OSX && !TARGET_IOS && !TARGET_TVOS && !TARGET_MACCATALYST } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.UnixOrBrowser.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.UnixOrBrowser.cs index 3f04333e6b3932..68ceab23b26f04 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.UnixOrBrowser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.UnixOrBrowser.cs @@ -69,5 +69,35 @@ private static int CheckedSysConf(Interop.Sys.SysConfName name) } return (int)result; } + + /// + /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, + /// and the total time spent running both the application and operating system code. + /// + [SupportedOSPlatform("maccatalyst")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + public static ProcessCpuUsage CpuUsage + { + get + { + Interop.Sys.ProcessCpuInformation cpuInfo = default; + Interop.Sys.GetCpuUtilization(ref cpuInfo); + + ulong userTime100Nanoseconds = cpuInfo.lastRecordedUserTime / 100; // nanoseconds to 100-nanoseconds + if (userTime100Nanoseconds > long.MaxValue) + { + userTime100Nanoseconds = long.MaxValue; + } + + ulong kernelTime100Nanoseconds = cpuInfo.lastRecordedKernelTime / 100; // nanoseconds to 100-nanoseconds + if (kernelTime100Nanoseconds > long.MaxValue) + { + kernelTime100Nanoseconds = long.MaxValue; + } + + return new ProcessCpuUsage { UserTime = new TimeSpan((long)userTime100Nanoseconds), PrivilegedTime = new TimeSpan((long)kernelTime100Nanoseconds) }; + } + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.iOS.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.iOS.cs index b6bd6a16bdb7df..681aef453c7d7c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.iOS.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.iOS.cs @@ -10,25 +10,6 @@ namespace System { - public static partial class Environment - { -#if !TARGET_MACCATALYST - // iOS/tvOS aren't allowed to call libproc APIs so return 0 here, this also matches what we returned in earlier releases - public static long WorkingSet => 0; - - /// - /// Get the CPU usage, including the process time spent running the application code, the process time spent running the operating system code, - /// and the total time spent running both the application and operating system code. - /// - [SupportedOSPlatform("maccatalyst")] - [UnsupportedOSPlatform("ios")] - [UnsupportedOSPlatform("tvos")] - public static ProcessCpuUsage CpuUsage - { - get => new ProcessCpuUsage { UserTime = TimeSpan.Zero, PrivilegedTime = TimeSpan.Zero }; - } -#endif - private static Dictionary? s_specialFolders; private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption _ /*option*/) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs index b2de3eefedfcd1..8fe09dd5d0017c 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs @@ -577,38 +577,44 @@ public void GetLogicalDrives_Windows_MatchesExpectedLetters() [Fact] public void TestCpuUsage() { - if (PlatformDetection.IsiOS && PlatformDetection.IstvOS) + Process currentProcess = Process.GetCurrentProcess(); + + if (PlatformDetection.IsiOS || PlatformDetection.IstvOS || PlatformDetection.IsBrowser) { - Assert.Throws(() => Environment.CpuUsage); + TimeSpan temp; + Assert.Throws(() => temp = currentProcess.UserProcessorTime); + Assert.Throws(() => temp = currentProcess.PrivilegedProcessorTime); + Assert.Throws(() => temp = currentProcess.TotalProcessorTime); + + // Environment should return 0 for all values + Environment.ProcessCpuUsage usage = Environment.CpuUsage; + Assert.Equal(TimeSpan.Zero, usage.UserTime); + Assert.Equal(TimeSpan.Zero, usage.PrivilegedTime); + Assert.Equal(TimeSpan.Zero, usage.TotalTime); } else { - TimeSpan delta = TimeSpan.FromMinutes(1); - - for (int i = 0; i < 10; i++) - { - Process currentProcess = Process.GetCurrentProcess(); + TimeSpan userTime = currentProcess.UserProcessorTime; + TimeSpan privilegedTime = currentProcess.PrivilegedProcessorTime; + TimeSpan totalTime = currentProcess.TotalProcessorTime; - TimeSpan userTime = currentProcess.UserProcessorTime; - TimeSpan privilegedTime = currentProcess.PrivilegedProcessorTime; - TimeSpan totalTime = currentProcess.TotalProcessorTime; + Environment.ProcessCpuUsage usage = Environment.CpuUsage; + Assert.True(usage.UserTime.TotalMilliseconds >= 0); + Assert.True(usage.PrivilegedTime.TotalMilliseconds >= 0); + Assert.True(usage.TotalTime.TotalMilliseconds >= 0); + Assert.Equal(usage.TotalTime, usage.UserTime + usage.PrivilegedTime); - Environment.ProcessCpuUsage usage = Environment.CpuUsage; - Assert.True(usage.UserTime.TotalMilliseconds >= 0); - Assert.True(usage.PrivilegedTime.TotalMilliseconds >= 0); - Assert.True(usage.TotalTime.TotalMilliseconds >= 0); - Assert.Equal(usage.TotalTime, usage.UserTime + usage.PrivilegedTime); + Assert.True(usage.UserTime >= userTime); + Assert.True(usage.PrivilegedTime >= privilegedTime); + Assert.True(usage.TotalTime >= totalTime); - Assert.True(usage.UserTime >= userTime); - Assert.True(usage.PrivilegedTime >= privilegedTime); - Assert.True(usage.TotalTime >= totalTime); + TimeSpan delta = TimeSpan.FromMinutes(1); - Assert.True(usage.UserTime - userTime < delta); - Assert.True(usage.PrivilegedTime - privilegedTime < delta); - Assert.True(usage.TotalTime - totalTime < delta); + Assert.True(usage.UserTime - userTime < delta); + Assert.True(usage.PrivilegedTime - privilegedTime < delta); + Assert.True(usage.TotalTime - totalTime < delta); - Thread.Sleep(100); - } + Thread.Sleep(100); } } From be1482c104f2c103587dfddbe7f8fd19aecb9015 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Sat, 20 Jul 2024 12:35:18 -0700 Subject: [PATCH 12/21] cleanup --- .../src/System/Diagnostics/Process.Windows.cs | 1 - .../src/System/Environment.Browser.cs | 1 - .../System.Private.CoreLib/src/System/Environment.OSX.cs | 2 -- .../System.Private.CoreLib/src/System/Environment.Unix.cs | 2 -- .../src/System/Environment.UnixOrBrowser.cs | 1 + .../System.Private.CoreLib/src/System/Environment.cs | 1 - .../System.Private.CoreLib/src/System/Environment.iOS.cs | 8 +++++++- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs index 06a2bd51d6d402..9ca200be31b3fb 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.Specialized; -using System.ComponentModel; using System.IO; using System.Runtime.InteropServices; using System.Runtime.Versioning; diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs index 97658880343b9e..6d3aabb565fba6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Browser.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.IO; -using System.Runtime.Versioning; namespace System { diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs index 1d124fff28035d..8206f95c69061c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.OSX.cs @@ -1,9 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics; using System.Runtime.InteropServices; -using System.Runtime.Versioning; namespace System { diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs index c5110352003507..5a80fe41990b8f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Unix.cs @@ -1,12 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Runtime.Versioning; using System.Text; using System.Threading; diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.UnixOrBrowser.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.UnixOrBrowser.cs index 68ceab23b26f04..35018e98e70611 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.UnixOrBrowser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.UnixOrBrowser.cs @@ -77,6 +77,7 @@ private static int CheckedSysConf(Interop.Sys.SysConfName name) [SupportedOSPlatform("maccatalyst")] [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] + [UnsupportedOSPlatform("browser")] public static ProcessCpuUsage CpuUsage { get diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.cs index db7844eaed2403..47eebb9b8a231f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.cs @@ -4,7 +4,6 @@ using System.Collections; using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Runtime.Versioning; using System.Threading; namespace System diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.iOS.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.iOS.cs index 681aef453c7d7c..973790e0785a01 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.iOS.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.iOS.cs @@ -4,12 +4,18 @@ using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; -using System.Runtime.Versioning; using System.Threading; using NSSearchPathDirectory = Interop.Sys.NSSearchPathDirectory; namespace System { + public static partial class Environment + { +#if !TARGET_MACCATALYST + // iOS/tvOS aren't allowed to call libproc APIs so return 0 here, this also matches what we returned in earlier releases + public static long WorkingSet => 0; +#endif + private static Dictionary? s_specialFolders; private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption _ /*option*/) From 9a4a025797ae1902c50a47f91a20de5f1acce078 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Sat, 20 Jul 2024 13:02:23 -0700 Subject: [PATCH 13/21] revert the resources file change --- .../src/Resources/Strings.resx | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 5551edb6b821a3..7fbe6aa0963827 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -1,17 +1,17 @@  - From 2b6df97fa6d6d4e35b9f35eda843099435cd0ded Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Sat, 20 Jul 2024 13:49:32 -0700 Subject: [PATCH 14/21] addressing the feedback --- .../Diagnostics/Metrics/RuntimeMetrics.cs | 27 +++++++++++-------- .../src/System/Environment.cs | 13 +++++---- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/RuntimeMetrics.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/RuntimeMetrics.cs index 7a1ea40bc4297e..25dc80e191cfe0 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/RuntimeMetrics.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/RuntimeMetrics.cs @@ -146,11 +146,14 @@ static RuntimeMetrics() unit: "{cpu}", description: "The number of processors available to the process."); - private static readonly ObservableCounter s_processCpuTime = s_meter.CreateObservableCounter( - "dotnet.process.cpu.time", - GetCpuTime, - unit: "s", - description: "CPU time used by the process as reported by the CLR."); + private static readonly ObservableCounter? s_processCpuTime = + OperatingSystem.IsBrowser() || OperatingSystem.IsTvOS() || (OperatingSystem.IsIOS() && !OperatingSystem.IsMacCatalyst()) ? + null : + s_meter.CreateObservableCounter( + "dotnet.process.cpu.time", + GetCpuTime, + unit: "s", + description: "CPU time used by the process."); public static bool IsEnabled() { @@ -172,7 +175,7 @@ public static bool IsEnabled() || s_assembliesCount.Enabled || s_exceptions.Enabled || s_processCpuCount.Enabled - || s_processCpuTime.Enabled; + || (s_processCpuTime is not null && s_processCpuTime.Enabled); } private static IEnumerable> GetGarbageCollectionCounts() @@ -189,13 +192,15 @@ private static IEnumerable> GetGarbageCollectionCounts() private static IEnumerable> GetCpuTime() { - if (OperatingSystem.IsBrowser() || OperatingSystem.IsTvOS() || OperatingSystem.IsIOS()) - yield break; + Debug.Assert(s_processCpuTime is not null); + Debug.Assert(!OperatingSystem.IsBrowser() && !OperatingSystem.IsTvOS() && !(OperatingSystem.IsIOS() && !OperatingSystem.IsMacCatalyst())); - Environment.ProcessCpuUsage processCpuUsage = Environment.CpuUsage; + #pragma warning disable CA1416 // This call site is reachable on all platforms. 'Environment.CpuUsage' is unsupported on: 'ios', 'tvos' + Environment.ProcessCpuUsage processCpuUsage = Environment.CpuUsage; + #pragma warning restore CA1416 - yield return new(processCpuUsage.UserTime.TotalSeconds, [new KeyValuePair("cpu.mode", "user")]); - yield return new(processCpuUsage.PrivilegedTime.TotalSeconds, [new KeyValuePair("cpu.mode", "system")]); + yield return new(processCpuUsage.UserTime.TotalSeconds, [new KeyValuePair("cpu.mode", "user")]); + yield return new(processCpuUsage.PrivilegedTime.TotalSeconds, [new KeyValuePair("cpu.mode", "system")]); } private static IEnumerable> GetHeapSizes() diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.cs index 47eebb9b8a231f..8cb8e05d5358d3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.cs @@ -11,23 +11,26 @@ namespace System public static partial class Environment { /// - /// Represents the CPU usage statistics of a process. It includes information about the time spent by the process in both the application code (user mode) - /// and the operating system code (kernel mode). In addition to the total time spent by the process in both user mode and kernel mode. + /// Represents the CPU usage statistics of a process. /// + /// + /// The CPU usage statistics include information about the time spent by the process in the application code (user mode) and the operating system code (kernel mode), + /// as well as the total time spent by the process in both user mode and kernel mode. + /// public readonly struct ProcessCpuUsage { /// - /// the amount of time the associated process has spent running code inside the application portion of the process (not the operating system core). + /// Gets the amount of time the associated process has spent running code inside the application portion of the process (not the operating system core). /// public TimeSpan UserTime { get; internal init; } /// - /// The amount of time the process has spent running code inside the operating system code. + /// Gets the amount of time the process has spent running code inside the operating system code. /// public TimeSpan PrivilegedTime { get; internal init; } /// - /// The amount of time the process has spent utilizing the CPU including the process time spent in the application code and the process time spent in the operating system code. + /// Gets the amount of time the process has spent utilizing the CPU including the process time spent in the application code and the process time spent in the operating system code. /// public TimeSpan TotalTime => UserTime + PrivilegedTime; } From 409c4278fb885637711fa86a8ad0f6b3edf683f0 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Sat, 20 Jul 2024 14:05:53 -0700 Subject: [PATCH 15/21] Update the test --- .../System.Runtime.Extensions.Tests/System/EnvironmentTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs index 8fe09dd5d0017c..e066d3014c3df7 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs @@ -579,7 +579,7 @@ public void TestCpuUsage() { Process currentProcess = Process.GetCurrentProcess(); - if (PlatformDetection.IsiOS || PlatformDetection.IstvOS || PlatformDetection.IsBrowser) + if ((OperatingSystem.IsIOS() && !OperatingSystem.IsMacCatalyst()) || PlatformDetection.IstvOS || PlatformDetection.IsBrowser) { TimeSpan temp; Assert.Throws(() => temp = currentProcess.UserProcessorTime); From 02b5e5248d7cbede114f96f3bc1b67e07cb32bf0 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Sat, 20 Jul 2024 14:09:13 -0700 Subject: [PATCH 16/21] kittle tweaks --- .../src/System/Environment.UnixOrBrowser.cs | 1 + .../System.Private.CoreLib/src/System/Environment.Windows.cs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.UnixOrBrowser.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.UnixOrBrowser.cs index 35018e98e70611..2f44f755cfdb11 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.UnixOrBrowser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.UnixOrBrowser.cs @@ -5,6 +5,7 @@ using System.IO; using System.Reflection; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using System.Text; using System.Threading; diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs index 8639474e5d18ad..a2a7ffdc43e204 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; From e86fc3181e8e44fea9e060137536384ec444f648 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Sat, 20 Jul 2024 14:26:45 -0700 Subject: [PATCH 17/21] ref fix --- .../System.Private.CoreLib/src/System/Environment.Windows.cs | 1 + src/libraries/System.Runtime/ref/System.Runtime.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs index a2a7ffdc43e204..2d4e82dca88748 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.Windows.cs @@ -367,6 +367,7 @@ private static unsafe string[] SegmentCommandLine(char* cmdLine) [SupportedOSPlatform("maccatalyst")] [UnsupportedOSPlatform("ios")] [UnsupportedOSPlatform("tvos")] + [UnsupportedOSPlatform("browser")] public static ProcessCpuUsage CpuUsage { get => Interop.Kernel32.GetProcessTimes(Interop.Kernel32.GetCurrentProcess(), out _, out _, out long procKernelTime, out long procUserTime) ? diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index a23e2b0cf1c4b9..db49bbfb30de25 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -2689,6 +2689,7 @@ public readonly struct ProcessCpuUsage [System.Runtime.Versioning.UnsupportedOSPlatform("ios")] [System.Runtime.Versioning.UnsupportedOSPlatform("tvos")] [System.Runtime.Versioning.SupportedOSPlatform("maccatalyst")] + [System.Runtime.Versioning.UnsupportedOSPlatform("browser")] public static ProcessCpuUsage CpuUsage { get { throw null; } } public static string? ProcessPath { get { throw null; } } public static string StackTrace { get { throw null; } } From 55dfbcba6e4c9859c2d1a7be650215a69d00f0b4 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Sat, 20 Jul 2024 14:35:58 -0700 Subject: [PATCH 18/21] minore comment fix. --- src/libraries/System.Private.CoreLib/src/System/Environment.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.cs index 8cb8e05d5358d3..654c1d1aaf8ba5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.cs @@ -20,7 +20,7 @@ public static partial class Environment public readonly struct ProcessCpuUsage { /// - /// Gets the amount of time the associated process has spent running code inside the application portion of the process (not the operating system core). + /// Gets the amount of time the associated process has spent running code inside the application portion of the process (not the operating system code). /// public TimeSpan UserTime { get; internal init; } From 26223f4ced69d85136b7c64790ede6d045f3521c Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Sat, 20 Jul 2024 14:56:19 -0700 Subject: [PATCH 19/21] revert un-intended change --- .../src/System/Diagnostics/Process.Windows.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs index 9ca200be31b3fb..06a2bd51d6d402 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Specialized; +using System.ComponentModel; using System.IO; using System.Runtime.InteropServices; using System.Runtime.Versioning; From 6ba8858d663a8637510a6210c223e03e9aaa1d64 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Sat, 20 Jul 2024 16:59:47 -0700 Subject: [PATCH 20/21] Fix the test with with the browser --- .../System/EnvironmentTests.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs index e066d3014c3df7..e396b0843e6e38 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs @@ -577,15 +577,8 @@ public void GetLogicalDrives_Windows_MatchesExpectedLetters() [Fact] public void TestCpuUsage() { - Process currentProcess = Process.GetCurrentProcess(); - if ((OperatingSystem.IsIOS() && !OperatingSystem.IsMacCatalyst()) || PlatformDetection.IstvOS || PlatformDetection.IsBrowser) { - TimeSpan temp; - Assert.Throws(() => temp = currentProcess.UserProcessorTime); - Assert.Throws(() => temp = currentProcess.PrivilegedProcessorTime); - Assert.Throws(() => temp = currentProcess.TotalProcessorTime); - // Environment should return 0 for all values Environment.ProcessCpuUsage usage = Environment.CpuUsage; Assert.Equal(TimeSpan.Zero, usage.UserTime); @@ -594,6 +587,8 @@ public void TestCpuUsage() } else { + Process currentProcess = Process.GetCurrentProcess(); + TimeSpan userTime = currentProcess.UserProcessorTime; TimeSpan privilegedTime = currentProcess.PrivilegedProcessorTime; TimeSpan totalTime = currentProcess.TotalProcessorTime; From 6cf6582884cde025efa5162d0bfb7043a7731107 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Sun, 21 Jul 2024 12:26:11 -0700 Subject: [PATCH 21/21] simplify some code --- .../System/Diagnostics/Metrics/RuntimeMetrics.cs | 9 ++++++--- .../src/System/Environment.UnixOrBrowser.cs | 14 +++----------- .../System/EnvironmentTests.cs | 2 -- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/RuntimeMetrics.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/RuntimeMetrics.cs index 25dc80e191cfe0..2857c9538ed04f 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/RuntimeMetrics.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Metrics/RuntimeMetrics.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Runtime.Versioning; using System.Threading; namespace System.Diagnostics.Metrics @@ -175,7 +176,7 @@ public static bool IsEnabled() || s_assembliesCount.Enabled || s_exceptions.Enabled || s_processCpuCount.Enabled - || (s_processCpuTime is not null && s_processCpuTime.Enabled); + || s_processCpuTime?.Enabled is true; } private static IEnumerable> GetGarbageCollectionCounts() @@ -190,14 +191,16 @@ private static IEnumerable> GetGarbageCollectionCounts() } } + [SupportedOSPlatform("maccatalyst")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [UnsupportedOSPlatform("browser")] private static IEnumerable> GetCpuTime() { Debug.Assert(s_processCpuTime is not null); Debug.Assert(!OperatingSystem.IsBrowser() && !OperatingSystem.IsTvOS() && !(OperatingSystem.IsIOS() && !OperatingSystem.IsMacCatalyst())); - #pragma warning disable CA1416 // This call site is reachable on all platforms. 'Environment.CpuUsage' is unsupported on: 'ios', 'tvos' Environment.ProcessCpuUsage processCpuUsage = Environment.CpuUsage; - #pragma warning restore CA1416 yield return new(processCpuUsage.UserTime.TotalSeconds, [new KeyValuePair("cpu.mode", "user")]); yield return new(processCpuUsage.PrivilegedTime.TotalSeconds, [new KeyValuePair("cpu.mode", "system")]); diff --git a/src/libraries/System.Private.CoreLib/src/System/Environment.UnixOrBrowser.cs b/src/libraries/System.Private.CoreLib/src/System/Environment.UnixOrBrowser.cs index 2f44f755cfdb11..eefd1120cb5710 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Environment.UnixOrBrowser.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Environment.UnixOrBrowser.cs @@ -86,17 +86,9 @@ public static ProcessCpuUsage CpuUsage Interop.Sys.ProcessCpuInformation cpuInfo = default; Interop.Sys.GetCpuUtilization(ref cpuInfo); - ulong userTime100Nanoseconds = cpuInfo.lastRecordedUserTime / 100; // nanoseconds to 100-nanoseconds - if (userTime100Nanoseconds > long.MaxValue) - { - userTime100Nanoseconds = long.MaxValue; - } - - ulong kernelTime100Nanoseconds = cpuInfo.lastRecordedKernelTime / 100; // nanoseconds to 100-nanoseconds - if (kernelTime100Nanoseconds > long.MaxValue) - { - kernelTime100Nanoseconds = long.MaxValue; - } + // Division by 100 is to convert the nanoseconds to 100-nanoseconds to match .NET time units (100-nanoseconds). + ulong userTime100Nanoseconds = Math.Min(cpuInfo.lastRecordedUserTime / 100, (ulong)long.MaxValue); + ulong kernelTime100Nanoseconds = Math.Min(cpuInfo.lastRecordedKernelTime / 100, (ulong)long.MaxValue); return new ProcessCpuUsage { UserTime = new TimeSpan((long)userTime100Nanoseconds), PrivilegedTime = new TimeSpan((long)kernelTime100Nanoseconds) }; } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs index e396b0843e6e38..b746973b6189ee 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Extensions.Tests/System/EnvironmentTests.cs @@ -608,8 +608,6 @@ public void TestCpuUsage() Assert.True(usage.UserTime - userTime < delta); Assert.True(usage.PrivilegedTime - privilegedTime < delta); Assert.True(usage.TotalTime - totalTime < delta); - - Thread.Sleep(100); } }