Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/libraries/Common/src/Interop/OSX/Interop.Libraries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ internal static partial class Libraries
internal const string CFNetworkLibrary = "/System/Library/Frameworks/CFNetwork.framework/CFNetwork";
internal const string libobjc = "/usr/lib/libobjc.dylib";
internal const string libproc = "/usr/lib/libproc.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";
Expand Down
22 changes: 22 additions & 0 deletions src/libraries/Common/src/Interop/OSX/Interop.libSystem.cs
Original file line number Diff line number Diff line change
@@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkoritzinsky @AaronRobinsonMSFT In terms of marshaller best practices, do we need to explicitly specify sequential layout for such structs?

Suggested change
public struct mach_timebase_info_data_t
[StructLayout(LayoutKind.Sequential)]
public struct mach_timebase_info_data_t

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, no. Value types in .NET default to sequential layout. However, and this is annoying, there are some Roslyn warnings that are suppressed if one does explicitly mark the type with sequential layout. The reasoning here is historical, but the gist is if Roslyn complains about unreferenced fields, which can happen for types used in interop, then placing StructLayout(LayoutKind.Sequential) on the type will automatically suppress the warning.

The interop team's general guidance here has been to accept the defaults except where there is annoying friction with C# or where the tooling requires explicit details. This falls into the C# friction bucket, but only if a warning is emitted.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AaronRobinsonMSFT thank you for a very detailed answer!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't see any related warnings in the compilation (and also we seem to have warnings as errors enabled in this part?).

Does this mean this attribute is unnecessary? I am totally okay with adding that if required. Though, yeah, we all know that sequential is the default struct layout 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean this attribute is unnecessary?

Yes.

{
public uint numer;
public uint denom;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@
Link="Common\Interop\OSX\Interop.libproc.cs" />
<Compile Include="$(CommonPath)Interop\OSX\Interop.libproc.GetProcessInfoById.cs"
Link="Common\Interop\OSX\Interop.libproc.GetProcessInfoById.cs" />
<Compile Include="$(CommonPath)Interop\OSX\Interop.libSystem.cs"
Link="Common\Interop\OSX\Interop.Interop.libSystem.cs" />
<Compile Include="$(CommonPath)Interop\OSX\Interop.Libraries.cs"
Link="Common\Interop\OSX\Interop.Libraries.cs" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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);
}
}

Expand All @@ -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_denom = denom = timeBase.denom;
s_timeBase_numer = timeBase.numer;
}
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,10 @@
<data name="Argv_IncludeDoubleQuote" xml:space="preserve">
<value>The argv[0] argument cannot include a double quote.</value>
</data>
<data name="CantGetAllPids" xml:space="preserve">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix I've suggested in #92185 (comment) should just work, but I can take care of that in a separate PR to get the fix merged right now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<value>Could not get all running Process IDs.</value>
</data>
<data name="RUsageFailure" xml:space="preserve">
<value>Failed to set or retrieve rusage information. See the error code for OS-specific error information.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@
<Compile Include="ProcessThreadTests.Unix.cs" />
<Compile Include="$(CoreLibSharedDir)System\PasteArguments.Unix.cs"
Link="System\PasteArguments.Unix.cs" />
<Compile Include="$(CommonPath)Interop\OSX\Interop.Libraries.cs"
Link="Common\Interop\OSX\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)Interop\OSX\Interop.libproc.cs"
Link="Common\Interop\OSX\Interop.libproc.cs" />
<Compile Include="$(CommonPath)Interop\OSX\Interop.libSystem.cs"
Link="Common\Interop\OSX\Interop.libSystem.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Win32.Registry\src\Microsoft.Win32.Registry.csproj" />
Expand Down