diff --git a/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs b/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs
index 36cdea9e1bd9a4..40423ae3d2083b 100644
--- a/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs
+++ b/src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs
@@ -11,6 +11,7 @@ internal static partial class Libraries
internal const string libobjc = "/usr/lib/libobjc.dylib";
internal const string libproc = "/usr/lib/libproc.dylib";
internal const string Odbc32 = "libodbc.2.dylib";
+ internal const string libSystem = "libSystem.dylib";
internal const string OpenLdap = "libldap.dylib";
internal const string SystemConfigurationLibrary = "/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration";
internal const string AppleCryptoNative = "libSystem.Security.Cryptography.Native.Apple";
diff --git a/src/libraries/Common/src/Interop/OSX/Interop.libSystem.cs b/src/libraries/Common/src/Interop/OSX/Interop.libSystem.cs
new file mode 100644
index 00000000000000..e8662da28e709d
--- /dev/null
+++ b/src/libraries/Common/src/Interop/OSX/Interop.libSystem.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class libSystem
+ {
+ [LibraryImport(Interop.Libraries.libSystem)]
+ public static unsafe partial int mach_timebase_info(mach_timebase_info_data_t* info);
+ public struct mach_timebase_info_data_t
+ {
+ public uint numer;
+ public uint denom;
+ }
+ }
+}
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 e82e069ffd3ed5..ddd518ff333e09 100644
--- a/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj
+++ b/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj
@@ -317,6 +317,8 @@
Link="Common\Interop\OSX\Interop.libproc.cs" />
+
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 da44fc1ed3426f..07f55780d82de9 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
@@ -10,6 +10,7 @@ namespace System.Diagnostics
public partial class Process
{
private const int NanosecondsTo100NanosecondsFactor = 100;
+ private static volatile uint s_timeBase_numer, s_timeBase_denom;
private const int MicrosecondsToSecondsFactor = 1_000_000;
@@ -23,7 +24,7 @@ public TimeSpan PrivilegedProcessorTime
{
EnsureState(State.HaveNonExitedId);
Interop.libproc.rusage_info_v3 info = Interop.libproc.proc_pid_rusage(_processId);
- return new TimeSpan(Convert.ToInt64(info.ri_system_time / NanosecondsTo100NanosecondsFactor));
+ return MapTime(info.ri_system_time);
}
}
@@ -65,7 +66,7 @@ public TimeSpan TotalProcessorTime
{
EnsureState(State.HaveNonExitedId);
Interop.libproc.rusage_info_v3 info = Interop.libproc.proc_pid_rusage(_processId);
- return new TimeSpan(Convert.ToInt64((info.ri_system_time + info.ri_user_time) / NanosecondsTo100NanosecondsFactor));
+ return MapTime(info.ri_system_time + info.ri_user_time);
}
}
@@ -82,7 +83,7 @@ public TimeSpan UserProcessorTime
{
EnsureState(State.HaveNonExitedId);
Interop.libproc.rusage_info_v3 info = Interop.libproc.proc_pid_rusage(_processId);
- return new TimeSpan(Convert.ToInt64(info.ri_user_time / NanosecondsTo100NanosecondsFactor));
+ return MapTime(info.ri_user_time);
}
}
@@ -109,5 +110,35 @@ private static Interop.libproc.rusage_info_v3 GetCurrentProcessRUsage()
{
return Interop.libproc.proc_pid_rusage(Environment.ProcessId);
}
+
+ 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.Diagnostics.Process/tests/ProcessTests.Unix.cs b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs
index 2085fddbb5d202..79ad7ec01f0a13 100644
--- a/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs
+++ b/src/libraries/System.Diagnostics.Process/tests/ProcessTests.Unix.cs
@@ -851,6 +851,39 @@ static void ExecuteChildProcess(string filename, string flags)
}
}
+ [Fact]
+ [PlatformSpecific(TestPlatforms.OSX)]
+ public unsafe void TestTotalProcessorTimeMacOs()
+ {
+ var rUsage = Interop.libproc.proc_pid_rusage(Environment.ProcessId);
+ var timeBase = new Interop.libSystem.mach_timebase_info_data_t();
+ Interop.libSystem.mach_timebase_info(&timeBase);
+
+ var nativeUserUs = rUsage.ri_user_time / 1000 * timeBase.numer / timeBase.denom;
+ var nativeSystemUs = rUsage.ri_system_time / 1000 * timeBase.numer / timeBase.denom;
+ var nativeTotalUs = nativeSystemUs + nativeUserUs;
+
+ var nativeUserTime = TimeSpan.FromMicroseconds(nativeUserUs);
+ var nativeSystemTime = TimeSpan.FromMicroseconds(nativeSystemUs);
+ var nativeTotalTime = TimeSpan.FromMicroseconds(nativeTotalUs);
+
+ var process = Process.GetCurrentProcess();
+ var managedUserTime = process.UserProcessorTime;
+ var managedSystemTime = process.PrivilegedProcessorTime;
+ var managedTotalTime = process.TotalProcessorTime;
+
+ AssertTime(managedUserTime, nativeUserTime, "user");
+ AssertTime(managedSystemTime, nativeSystemTime, "system");
+ AssertTime(managedTotalTime, nativeTotalTime, "total");
+
+ void AssertTime(TimeSpan managed, TimeSpan native, string label)
+ {
+ Assert.True(
+ managed >= native,
+ $"Time '{label}' returned by managed API ({managed}) should be greated or equal to the time returned by native API ({native}).");
+ }
+ }
+
[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[InlineData(true)]
[InlineData(false)]
diff --git a/src/libraries/System.Diagnostics.Process/tests/Resources/Strings.resx b/src/libraries/System.Diagnostics.Process/tests/Resources/Strings.resx
index 4fd9192b919e10..d443842f56cbc6 100644
--- a/src/libraries/System.Diagnostics.Process/tests/Resources/Strings.resx
+++ b/src/libraries/System.Diagnostics.Process/tests/Resources/Strings.resx
@@ -120,4 +120,10 @@
The argv[0] argument cannot include a double quote.
+
+ Could not get all running Process IDs.
+
+
+ Failed to set or retrieve rusage information. See the error code for OS-specific error information.
+
\ No newline at end of file
diff --git a/src/libraries/System.Diagnostics.Process/tests/System.Diagnostics.Process.Tests.csproj b/src/libraries/System.Diagnostics.Process/tests/System.Diagnostics.Process.Tests.csproj
index c3f01adc3bc673..889e34344048ac 100644
--- a/src/libraries/System.Diagnostics.Process/tests/System.Diagnostics.Process.Tests.csproj
+++ b/src/libraries/System.Diagnostics.Process/tests/System.Diagnostics.Process.Tests.csproj
@@ -63,6 +63,12 @@
+
+
+