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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
publicstruct mach_timebase_info_data_t
[StructLayout(LayoutKind.Sequential)]
publicstruct mach_timebase_info_data_t

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
publicstruct mach_timebase_info_data_t
[StructLayout(LayoutKind.Sequential)]
publicstruct mach_timebase_info_data_t

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
publicstruct mach_timebase_info_data_t
[StructLayout(LayoutKind.Sequential)]
publicstruct mach_timebase_info_data_t

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
publicstruct mach_timebase_info_data_t
[StructLayout(LayoutKind.Sequential)]
publicstruct mach_timebase_info_data_t

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
publicstruct mach_timebase_info_data_t
[StructLayout(LayoutKind.Sequential)]
publicstruct mach_timebase_info_data_t

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
publicstruct mach_timebase_info_data_t
[StructLayout(LayoutKind.Sequential)]
publicstruct mach_timebase_info_data_t

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
publicstruct mach_timebase_info_data_t
[StructLayout(LayoutKind.Sequential)]
publicstruct mach_timebase_info_data_t

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
publicstruct mach_timebase_info_data_t
[StructLayout(LayoutKind.Sequential)]
publicstruct mach_timebase_info_data_t

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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
Copy Markdown
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 numberDiff line numberDiff 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 numberDiff line numberDiff 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 DownExpand 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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
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 numberDiff line numberDiff 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