Skip to content

Commit

Permalink
Refactor for unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
tippmar-nr committed Oct 18, 2024
1 parent 9686049 commit ef1293a
Show file tree
Hide file tree
Showing 7 changed files with 371 additions and 134 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public static void RegisterServices(IContainer container, bool serverlessModeEna
}
else
{
container.Register<IGCSamplerModernReflectionHelper, GCSamplerModernReflectionHelper>();
container.Register<IGCSampleTransformerModern, GCSampleTransformerModern>();
container.Register<GCSamplerModern, GCSamplerModern>();
}
Expand Down
132 changes: 8 additions & 124 deletions src/Agent/NewRelic/Agent/Core/Samplers/GCSamplerModern.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@
// SPDX-License-Identifier: Apache-2.0

using System;
using System.Linq.Expressions;
using System.Reflection;
using NewRelic.Agent.Core.Time;
using NewRelic.Agent.Core.Transformers;
using NewRelic.Agent.Extensions.Logging;
using NewRelic.Reflection;

namespace NewRelic.Agent.Core.Samplers
{
Expand All @@ -17,56 +14,34 @@ public class GCSamplerModern : AbstractSampler
private DateTime _lastSampleTime;
private bool _hasGCOccurred;

private static Func<object, object> _getGenerationInfo;
private static bool _reflectionFailed;

private static Func<object, object> GCGetMemoryInfo_Invoker;
private static Func<object, object> GCGetTotalAllocatedBytes_Invoker;
private IGCSamplerModernReflectionHelper _gCSamplerModernReflectionHelper;

private const int GCSamplerModernIntervalSeconds = 60;

static GCSamplerModern()
{
if (!VisibilityBypasser.Instance.TryGenerateOneParameterStaticMethodCaller("System.Runtime", "System.GC", "GetGCMemoryInfo", "System.GCKind", "System.GCMemoryInfo", out GCGetMemoryInfo_Invoker))
{
_reflectionFailed = true;
}

if (!_reflectionFailed)
{
if (!VisibilityBypasser.Instance.TryGenerateOneParameterStaticMethodCaller("System.Runtime", "System.GC", "GetTotalAllocatedBytes", "System.Boolean", "System.Int64", out GCGetTotalAllocatedBytes_Invoker))
{
_reflectionFailed = true;
}
}

if (!_reflectionFailed)
_getGenerationInfo = GCMemoryInfoHelper.GenerateGetMemoryInfoMethod();
}

public GCSamplerModern(IScheduler scheduler, IGCSampleTransformerModern transformer)
public GCSamplerModern(IScheduler scheduler, IGCSampleTransformerModern transformer, IGCSamplerModernReflectionHelper gCSamplerModernReflectionHelper)
: base(scheduler, TimeSpan.FromSeconds(GCSamplerModernIntervalSeconds))
{
_transformer = transformer;
_gCSamplerModernReflectionHelper = gCSamplerModernReflectionHelper;
_lastSampleTime = DateTime.UtcNow;
_hasGCOccurred = false;
}

public override void Sample()
{
if (_reflectionFailed)
if (_gCSamplerModernReflectionHelper.ReflectionFailed)
{
Stop();
Log.Error($"Unable to get GC sample due to reflection error. No GC metrics will be reported.");
}

_hasGCOccurred |= GC.CollectionCount(0) > 0;
_hasGCOccurred |= _gCSamplerModernReflectionHelper.HasGCOccurred;

if (_hasGCOccurred) // don't do anything until at least one GC has completed
{

dynamic gcMemoryInfo = GCGetMemoryInfo_Invoker(0); // GCKind.Any
dynamic generationInfo = _getGenerationInfo(gcMemoryInfo);
dynamic gcMemoryInfo = _gCSamplerModernReflectionHelper.GCGetMemoryInfo_Invoker(0); // GCKind.Any
dynamic generationInfo = _gCSamplerModernReflectionHelper.GetGenerationInfo(gcMemoryInfo);

var genInfoLength = generationInfo.Length;
var heapSizesBytes = new long[genInfoLength];
Expand All @@ -84,7 +59,7 @@ public override void Sample()
}

var totalMemoryBytes = GC.GetTotalMemory(false);
var totalAllocatedBytes = (long)GCGetTotalAllocatedBytes_Invoker(false);
var totalAllocatedBytes = (long)_gCSamplerModernReflectionHelper.GCGetTotalAllocatedBytes_Invoker(false);
var totalCommittedBytes = gcMemoryInfo.TotalCommittedBytes;

var currentSampleTime = DateTime.UtcNow;
Expand All @@ -95,95 +70,4 @@ public override void Sample()
}
}
}

public class ImmutableGCSample
{
public readonly DateTime LastSampleTime;
public readonly DateTime CurrentSampleTime;

public readonly long TotalMemoryBytes; // In-use memory on the GC heap as of current GC
public readonly long TotalAllocatedBytes; // total memory allocated on GC heap since process start
public readonly long TotalCommittedBytes;// committed virtual memory as of current GC

public readonly long[] GCHeapSizesBytes; // heap sizes as of current GC
public readonly int[] GCCollectionCounts; // number of collections since last sample
public readonly long[] GCFragmentationSizesBytes; // heap fragmentation as of current GC

public ImmutableGCSample()
{
LastSampleTime = CurrentSampleTime = DateTime.MinValue;
GCHeapSizesBytes = new long[5];
GCCollectionCounts = new int[5];
GCFragmentationSizesBytes = new long[5];
}

public ImmutableGCSample(DateTime lastSampleTime, DateTime currentSampleTime, long totalMemoryBytes, long totalAllocatedBytes, long totalCommittedBytes, long[] heapSizesBytes, int[] rawCollectionCounts, long[] fragmentationSizesBytes)
{
LastSampleTime = lastSampleTime;
CurrentSampleTime = currentSampleTime;

TotalMemoryBytes = totalMemoryBytes;

TotalAllocatedBytes = totalAllocatedBytes;
TotalCommittedBytes = totalCommittedBytes;

GCHeapSizesBytes = heapSizesBytes;
GCFragmentationSizesBytes = fragmentationSizesBytes;

// TODO: verify length is 5 as expected
GCCollectionCounts = new int[rawCollectionCounts.Length];
// Gen 1
GCCollectionCounts[0] = rawCollectionCounts[0] - rawCollectionCounts[1];
// Gen 2
GCCollectionCounts[1] = rawCollectionCounts[1] - rawCollectionCounts[2];
// Gen 3
GCCollectionCounts[2] = rawCollectionCounts[2];

// LOH
GCCollectionCounts[3] = rawCollectionCounts[3]; // or does this need to be [3] - [4]??
// POH
GCCollectionCounts[4] = rawCollectionCounts[4]; //??
}
}

internal static class GCMemoryInfoHelper
{
/// <summary>
/// Generate a function that takes a GCMemoryInfo instance as an input parameter and
/// returns an array of GCGenerationInfo instances.
/// </summary>
public static Func<object, object> GenerateGetMemoryInfoMethod()
{
var assembly = Assembly.Load("System.Runtime");
var gcMemoryInfoType = assembly.GetType("System.GCMemoryInfo");

// Define a parameter expression for the input object
var inputParameter = Expression.Parameter(typeof(object), "input");

// Cast the input parameter to GCMemoryInfo
var gcMemoryInfoParameter = Expression.Convert(inputParameter, gcMemoryInfoType);

// Get the GenerationInfo property
var generationInfoProperty = gcMemoryInfoType.GetProperty("GenerationInfo");

// Access the GenerationInfo property
var accessGenerationInfo = Expression.Property(gcMemoryInfoParameter, generationInfoProperty);

// Get the ReadOnlySpan<GCGenerationInfo> type using the full type name
var readOnlySpanType = assembly.GetType("System.ReadOnlySpan`1[[System.GCGenerationInfo, System.Private.CoreLib]]");

// Get the ToArray method of ReadOnlySpan<GCGenerationInfo>
var toArrayMethod = readOnlySpanType.GetMethod("ToArray", BindingFlags.Public | BindingFlags.Instance);

// Call ToArray() on GenerationInfo
var callToArray = Expression.Call(accessGenerationInfo, toArrayMethod);

// Create a lambda expression
var lambda = Expression.Lambda<Func<object, object>>(Expression.Convert(callToArray, typeof(object)), inputParameter);

// Compile the lambda expression into a delegate
return lambda.Compile();
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using System;
using System.Linq.Expressions;
using System.Reflection;
using NewRelic.Reflection;

namespace NewRelic.Agent.Core.Samplers
{
// to allow for unit testing
public interface IGCSamplerModernReflectionHelper
{
Func<object, object> GetGenerationInfo { get; }
bool ReflectionFailed { get; }
Func<object, object> GCGetMemoryInfo_Invoker { get; }
Func<object, object> GCGetTotalAllocatedBytes_Invoker { get; }

bool HasGCOccurred {get;}
}

public class GCSamplerModernReflectionHelper : IGCSamplerModernReflectionHelper
{
public Func<object, object> GetGenerationInfo { get; private set; }
public bool ReflectionFailed { get; private set; }
public Func<object, object> GCGetMemoryInfo_Invoker { get; private set; }
public Func<object, object> GCGetTotalAllocatedBytes_Invoker { get; private set; }

public GCSamplerModernReflectionHelper()
{
var assembly = Assembly.Load("System.Runtime");
var gcType = assembly.GetType("System.GC");
var paramType = assembly.GetType("System.GCKind");
var returnType = assembly.GetType("System.GCMemoryInfo");

if (!VisibilityBypasser.Instance.TryGenerateOneParameterStaticMethodCaller(gcType, "GetGCMemoryInfo", paramType, returnType, out var accessor))
{
ReflectionFailed = true;
}
else
GCGetMemoryInfo_Invoker = accessor;

if (!ReflectionFailed)
{
paramType = assembly.GetType("System.Boolean");
returnType = assembly.GetType("System.Int64");
if (!VisibilityBypasser.Instance.TryGenerateOneParameterStaticMethodCaller(gcType, "GetTotalAllocatedBytes", paramType, returnType, out var accessor1))
{
ReflectionFailed = true;
}
else
GCGetTotalAllocatedBytes_Invoker = accessor1;
}

if (!ReflectionFailed)
GetGenerationInfo = GCMemoryInfoHelper.GenerateGetMemoryInfoMethod();
}

public bool HasGCOccurred => GC.CollectionCount(0) > 0;
}

internal static class GCMemoryInfoHelper
{
/// <summary>
/// Generate a function that takes a GCMemoryInfo instance as an input parameter and
/// returns an array of GCGenerationInfo instances.
/// </summary>
public static Func<object, object> GenerateGetMemoryInfoMethod()
{
var assembly = Assembly.Load("System.Runtime");
var gcMemoryInfoType = assembly.GetType("System.GCMemoryInfo");

// Define a parameter expression for the input object
var inputParameter = Expression.Parameter(typeof(object), "input");

// Cast the input parameter to GCMemoryInfo
var gcMemoryInfoParameter = Expression.Convert(inputParameter, gcMemoryInfoType);

// Get the GenerationInfo property
var generationInfoProperty = gcMemoryInfoType.GetProperty("GenerationInfo");

// Access the GenerationInfo property
var accessGenerationInfo = Expression.Property(gcMemoryInfoParameter, generationInfoProperty);

// Get the ReadOnlySpan<GCGenerationInfo> type using the full type name
var readOnlySpanType = assembly.GetType("System.ReadOnlySpan`1[[System.GCGenerationInfo, System.Private.CoreLib]]");

// Get the ToArray method of ReadOnlySpan<GCGenerationInfo>
var toArrayMethod = readOnlySpanType.GetMethod("ToArray", BindingFlags.Public | BindingFlags.Instance);

// Call ToArray() on GenerationInfo
var callToArray = Expression.Call(accessGenerationInfo, toArrayMethod);

// Create a lambda expression
var lambda = Expression.Lambda<Func<object, object>>(Expression.Convert(callToArray, typeof(object)), inputParameter);

// Compile the lambda expression into a delegate
return lambda.Compile();
}
}
}
57 changes: 57 additions & 0 deletions src/Agent/NewRelic/Agent/Core/Samplers/ImmutableGCSample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using System;

namespace NewRelic.Agent.Core.Samplers
{
public class ImmutableGCSample
{
public readonly DateTime LastSampleTime;
public readonly DateTime CurrentSampleTime;

public readonly long TotalMemoryBytes; // In-use memory on the GC heap as of current GC
public readonly long TotalAllocatedBytes; // total memory allocated on GC heap since process start
public readonly long TotalCommittedBytes;// committed virtual memory as of current GC

public readonly long[] GCHeapSizesBytes; // heap sizes as of current GC
public readonly int[] GCCollectionCounts; // number of collections since last sample
public readonly long[] GCFragmentationSizesBytes; // heap fragmentation as of current GC

public ImmutableGCSample()
{
LastSampleTime = CurrentSampleTime = DateTime.MinValue;
GCHeapSizesBytes = new long[5];
GCCollectionCounts = new int[5];
GCFragmentationSizesBytes = new long[5];
}

public ImmutableGCSample(DateTime lastSampleTime, DateTime currentSampleTime, long totalMemoryBytes, long totalAllocatedBytes, long totalCommittedBytes, long[] heapSizesBytes, int[] rawCollectionCounts, long[] fragmentationSizesBytes)
{
LastSampleTime = lastSampleTime;
CurrentSampleTime = currentSampleTime;

TotalMemoryBytes = totalMemoryBytes;

TotalAllocatedBytes = totalAllocatedBytes;
TotalCommittedBytes = totalCommittedBytes;

GCHeapSizesBytes = heapSizesBytes;
GCFragmentationSizesBytes = fragmentationSizesBytes;

// TODO: verify length is 5 as expected
GCCollectionCounts = new int[rawCollectionCounts.Length];
// Gen 1
GCCollectionCounts[0] = rawCollectionCounts[0] - rawCollectionCounts[1];
// Gen 2
GCCollectionCounts[1] = rawCollectionCounts[1] - rawCollectionCounts[2];
// Gen 3
GCCollectionCounts[2] = rawCollectionCounts[2];

// LOH
GCCollectionCounts[3] = rawCollectionCounts[3]; // or does this need to be [3] - [4]??
// POH
GCCollectionCounts[4] = rawCollectionCounts[4]; //??
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -297,14 +297,10 @@ private static Func<object, object> GenerateMethodCallerInternal(Type ownerType,
return GenerateMethodCallerInternal(resultType, methodInfo);
}

public bool TryGenerateOneParameterStaticMethodCaller(string assemblyName, string typeName, string methodName, string parameterTypeName, string returnTypeName, out Func<object, object> accessor)
public bool TryGenerateOneParameterStaticMethodCaller(Type ownerType, string methodName, Type paramType, Type returnType, out Func<object, object> accessor)
{
try
{
var ownerType = GetType(assemblyName, typeName);
var paramType = GetType(assemblyName, parameterTypeName);
var returnType = GetType(assemblyName, returnTypeName);

var methodInfo = ownerType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static, null, new Type[] { paramType }, null);
if (methodInfo == null)
{
Expand Down Expand Up @@ -682,10 +678,8 @@ public Func<TResult> GenerateParameterlessStaticMethodCaller<TResult>(string ass
return (Func<TResult>)methodInfo.CreateDelegate(typeof(Func<TResult>));
}

public bool TryGenerateParameterlessStaticMethodCaller<TResult>(string assemblyName, string typeName, string methodName, out Func<TResult> accessor)
public bool TryGenerateParameterlessStaticMethodCaller<TResult>(Type ownerType, string methodName, out Func<TResult> accessor)
{
var ownerType = GetType(assemblyName, typeName);

var methodInfo = ownerType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
if (methodInfo == null)
{
Expand Down
Loading

0 comments on commit ef1293a

Please sign in to comment.