Skip to content

Commit

Permalink
Add support for MonoVM to MemoryDiagnoser (#2227)
Browse files Browse the repository at this point in the history
* 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)
  • Loading branch information
adamsitnik committed Dec 9, 2022
1 parent 530d001 commit 6992856
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 22 deletions.
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/Engines/Engine.cs
Expand Up @@ -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)
Expand Down
6 changes: 5 additions & 1 deletion src/BenchmarkDotNet/Engines/GcStats.cs
Expand Up @@ -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
Expand Down
45 changes: 26 additions & 19 deletions src/BenchmarkDotNet/Portability/RuntimeInformation.cs
Expand Up @@ -25,8 +25,15 @@ internal static class RuntimeInformation
internal const string ReleaseConfigurationName = "RELEASE";
internal const string Unknown = "?";

/// <summary>
/// returns true for both the old (implementation of .NET Framework) and new Mono (.NET 6+ flavour)
/// </summary>
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;
Expand Down Expand Up @@ -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<AssemblyInformationalVersionAttribute>()?.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);
Expand All @@ -221,7 +245,7 @@ internal static string GetRuntimeVersion()

return "Mono " + version;
}
else
else if (IsNewMono)
{
return $"{GetNetCoreVersion()} using MonoVM";
}
Expand All @@ -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<AssemblyInformationalVersionAttribute>()?.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();
Expand Down
17 changes: 16 additions & 1 deletion tests/BenchmarkDotNet.IntegrationTests/MemoryDiagnoserTests.cs
Expand Up @@ -23,6 +23,7 @@
using BenchmarkDotNet.Toolchains.InProcess.Emit;
using Xunit;
using Xunit.Abstractions;
using BenchmarkDotNet.Toolchains.Mono;

namespace BenchmarkDotNet.IntegrationTests
{
Expand All @@ -34,7 +35,7 @@ public class MemoryDiagnoserTests

public static IEnumerable<object[]> 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() };
Expand All @@ -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<string, long>
{
{ nameof(AccurateAllocations.EightBytesArray), 8 + objectAllocationOverhead + arraySizeOverhead },
Expand All @@ -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<int> list;
Expand Down

0 comments on commit 6992856

Please sign in to comment.