From 69928565a98a26d489611a966d99e90ce52f1952 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Fri, 9 Dec 2022 14:28:19 +0100 Subject: [PATCH] Add support for MonoVM to MemoryDiagnoser (#2227) * simplify GetRuntimeVersion logic, introduce IsOldMono and IsNewMono helper methods * add a failing test * fix MemoryDiagnoser for MonoVM on .NET 6+ keep it a nop for WASM (I have no ability to test it quickly) --- src/BenchmarkDotNet/Engines/Engine.cs | 2 +- src/BenchmarkDotNet/Engines/GcStats.cs | 6 ++- .../Portability/RuntimeInformation.cs | 45 +++++++++++-------- .../MemoryDiagnoserTests.cs | 17 ++++++- 4 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/BenchmarkDotNet/Engines/Engine.cs b/src/BenchmarkDotNet/Engines/Engine.cs index cdf8adcebc..271c3c44e1 100644 --- a/src/BenchmarkDotNet/Engines/Engine.cs +++ b/src/BenchmarkDotNet/Engines/Engine.cs @@ -267,7 +267,7 @@ private static void ForceGcCollect() private static void EnableMonitoring() { - if (RuntimeInformation.IsMono) // Monitoring is not available in Mono, see http://stackoverflow.com/questions/40234948/how-to-get-the-number-of-allocated-bytes-in-mono + if (RuntimeInformation.IsOldMono) // Monitoring is not available in Mono, see http://stackoverflow.com/questions/40234948/how-to-get-the-number-of-allocated-bytes-in-mono return; if (RuntimeInformation.IsFullFramework) diff --git a/src/BenchmarkDotNet/Engines/GcStats.cs b/src/BenchmarkDotNet/Engines/GcStats.cs index afcf73f515..573e2c4828 100644 --- a/src/BenchmarkDotNet/Engines/GcStats.cs +++ b/src/BenchmarkDotNet/Engines/GcStats.cs @@ -132,7 +132,11 @@ public static GcStats FromForced(int forcedFullGarbageCollections) private static long GetAllocatedBytes() { - if (RuntimeInformation.IsMono) // Monitoring is not available in Mono, see http://stackoverflow.com/questions/40234948/how-to-get-the-number-of-allocated-bytes- + if (RuntimeInformation.IsOldMono) // Monitoring is not available in Mono, see http://stackoverflow.com/questions/40234948/how-to-get-the-number-of-allocated-bytes- + return 0; + + // we have no tests for WASM and don't want to risk introducing a new bug (https://github.com/dotnet/BenchmarkDotNet/issues/2226) + if (RuntimeInformation.IsWasm) return 0; // "This instance Int64 property returns the number of bytes that have been allocated by a specific diff --git a/src/BenchmarkDotNet/Portability/RuntimeInformation.cs b/src/BenchmarkDotNet/Portability/RuntimeInformation.cs index 009e34049c..de8cdc9c1b 100644 --- a/src/BenchmarkDotNet/Portability/RuntimeInformation.cs +++ b/src/BenchmarkDotNet/Portability/RuntimeInformation.cs @@ -25,8 +25,15 @@ internal static class RuntimeInformation internal const string ReleaseConfigurationName = "RELEASE"; internal const string Unknown = "?"; + /// + /// returns true for both the old (implementation of .NET Framework) and new Mono (.NET 6+ flavour) + /// public static bool IsMono { get; } = Type.GetType("Mono.RuntimeStructs") != null; // it allocates a lot of memory, we need to check it once in order to keep Engine non-allocating! + public static bool IsOldMono { get; } = Type.GetType("Mono.Runtime") != null; + + public static bool IsNewMono { get; } = IsMono && !IsOldMono; + public static bool IsFullFramework => #if NET6_0_OR_GREATER false; @@ -200,7 +207,24 @@ internal static CpuInfo GetCpuInfo() internal static string GetRuntimeVersion() { - if (IsMono && !IsWasm) + if (IsWasm) + { + // code copied from https://github.com/dotnet/runtime/blob/2c573b59aaaf3fd17e2ecab95ad3769f195d2dbc/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System/Runtime/InteropServices/RuntimeInformation/RuntimeInformation.cs#L20-L30 + string versionString = typeof(object).Assembly.GetCustomAttribute()?.InformationalVersion; + + // Strip the git hash if there is one + if (versionString != null) + { + int plusIndex = versionString.IndexOf('+'); + if (plusIndex != -1) + { + versionString = versionString.Substring(0, plusIndex); + } + } + + return $".NET Core (Mono) {versionString}"; + } + else if (IsMono) { var monoRuntimeType = Type.GetType("Mono.Runtime"); var monoDisplayName = monoRuntimeType?.GetMethod("GetDisplayName", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); @@ -221,7 +245,7 @@ internal static string GetRuntimeVersion() return "Mono " + version; } - else + else if (IsNewMono) { return $"{GetNetCoreVersion()} using MonoVM"; } @@ -230,23 +254,6 @@ internal static string GetRuntimeVersion() { return FrameworkVersionHelper.GetFrameworkDescription(); } - else if (IsWasm) - { - // code copied from https://github.com/dotnet/runtime/blob/2c573b59aaaf3fd17e2ecab95ad3769f195d2dbc/src/libraries/System.Runtime.InteropServices.RuntimeInformation/src/System/Runtime/InteropServices/RuntimeInformation/RuntimeInformation.cs#L20-L30 - string versionString = typeof(object).Assembly.GetCustomAttribute()?.InformationalVersion; - - // Strip the git hash if there is one - if (versionString != null) - { - int plusIndex = versionString.IndexOf('+'); - if (plusIndex != -1) - { - versionString = versionString.Substring(0, plusIndex); - } - } - - return $".NET Core (Mono) {versionString}"; - } else if (IsNetCore) { return GetNetCoreVersion(); diff --git a/tests/BenchmarkDotNet.IntegrationTests/MemoryDiagnoserTests.cs b/tests/BenchmarkDotNet.IntegrationTests/MemoryDiagnoserTests.cs index 2ffe1b3387..da5dbf57e5 100755 --- a/tests/BenchmarkDotNet.IntegrationTests/MemoryDiagnoserTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/MemoryDiagnoserTests.cs @@ -23,6 +23,7 @@ using BenchmarkDotNet.Toolchains.InProcess.Emit; using Xunit; using Xunit.Abstractions; +using BenchmarkDotNet.Toolchains.Mono; namespace BenchmarkDotNet.IntegrationTests { @@ -34,7 +35,7 @@ public class MemoryDiagnoserTests public static IEnumerable GetToolchains() { - if (RuntimeInformation.IsMono) // https://github.com/mono/mono/issues/8397 + if (RuntimeInformation.IsOldMono) // https://github.com/mono/mono/issues/8397 yield break; yield return new object[] { Job.Default.GetToolchain() }; @@ -56,6 +57,11 @@ public void MemoryDiagnoserIsAccurate(IToolchain toolchain) long objectAllocationOverhead = IntPtr.Size * 2; // pointer to method table + object header word long arraySizeOverhead = IntPtr.Size; // array length + if (toolchain is MonoToolchain) + { + objectAllocationOverhead += IntPtr.Size; + } + AssertAllocations(toolchain, typeof(AccurateAllocations), new Dictionary { { nameof(AccurateAllocations.EightBytesArray), 8 + objectAllocationOverhead + arraySizeOverhead }, @@ -78,6 +84,15 @@ public void MemoryDiagnoserSupportsNativeAOT() .ToToolchain()); } + [FactDotNetCoreOnly("We don't want to test MonoVM twice (for .NET Framework 4.6.2 and .NET 7.0)")] + public void MemoryDiagnoserSupportsModernMono() + { + if (ContinuousIntegration.IsAppVeyorOnWindows()) + return; // timeouts + + MemoryDiagnoserIsAccurate(MonoToolchain.Mono70); + } + public class AllocatingGlobalSetupAndCleanup { private List list;