diff --git a/src/Agent/NewRelic/Agent/Core/AgentShim.cs b/src/Agent/NewRelic/Agent/Core/AgentShim.cs
index e2302b91b0..276bf1adb8 100644
--- a/src/Agent/NewRelic/Agent/Core/AgentShim.cs
+++ b/src/Agent/NewRelic/Agent/Core/AgentShim.cs
@@ -109,11 +109,11 @@ public static object GetFinishTracerDelegateFunc()
}
///
- /// This method is used to work around .net framework ad .net < 6.0 not supporting generic delegates
+ /// This method is used to work around .net framework and .net < 6.0 not supporting generic delegates
/// with 11 parameters.
///
/// An array of boxed parameters for the GetFinishTracerDelegate method.
- ///
+ /// The delegate to invoke when a method completes or results in an exception.
public static Action GetFinishTracerDelegateParameterWrapper(object[] parameters)
{
return GetFinishTracerDelegate(
diff --git a/src/Agent/NewRelic/Agent/Core/ProfilerAgentMethodCallCache.cs b/src/Agent/NewRelic/Agent/Core/ProfilerAgentMethodCallCache.cs
index edd9a7a175..5dcbcb25f3 100644
--- a/src/Agent/NewRelic/Agent/Core/ProfilerAgentMethodCallCache.cs
+++ b/src/Agent/NewRelic/Agent/Core/ProfilerAgentMethodCallCache.cs
@@ -1,9 +1,10 @@
-// Copyright 2020 New Relic, Inc. All rights reserved.
+// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
using System;
using System.Collections.Concurrent;
-using System.Reflection;
+using System.Collections.Generic;
+using System.Linq.Expressions;
namespace NewRelic.Agent.Core
{
@@ -12,50 +13,90 @@ namespace NewRelic.Agent.Core
///
public class ProfilerAgentMethodCallCache
{
- private static readonly ConcurrentDictionary _methodInfoCache = new ConcurrentDictionary();
+ private static readonly ConcurrentDictionary> _invokerCache = new ConcurrentDictionary>();
///
/// This method is invoked once using reflection by the byte-code injected by the profiler. This method is used to get a
- /// reference to the method that is used to cache reflection lookups for Agent methods. These methods are either
- /// Public Agent API methods, or AgentShim methods.
+ /// reference to the method that is used to cache reflection lookups and Expression compilation for Agent methods.
+ /// This is currently used for Agent API methods.
///
///
- /// A reference to the method that can access the method info cache. The result is treated as an object
+ /// A reference to the method that is used to invoked the requrested agent method. The result is treated as an object
/// to simplify the type definition injected by the profiler to store this reference.
///
- public static object GetMethodCacheFunc()
+ public static object GetInvokerFromCache()
{
- return (Func)GetAgentMethodInfoFromCache;
+ return (Func)GetAndInvokeMethodFromCache;
}
- private static MethodInfo GetAgentMethodInfoFromCache(string key, string className, string methodName, Type[] types)
+ ///
+ /// This is the method that is invoked by the injected byte code for the Agent API.
+ /// It uses a cache so that the more expensive operations are only executed once.
+ /// This method will invoke the requested method using the specified parameters.
+ /// The method will always return a value. This value will either be the result of the
+ /// requested method call, or null if the requested method has a void return type.
+ ///
+ /// The cache key generated by the profiler.
+ /// The name of the type containing the static method to execute.
+ /// The name of the static method to execute.
+ /// An array of types that represent the type for each parameter in the method to invoke.
+ /// The type returned by the method to invoke. A null is used to represent a void return type.
+ /// The parameters to invoke the method with.
+ /// The value returned from invoking the requested method, or null if the method has a void return type.
+ public static object GetAndInvokeMethodFromCache(string key, string className, string methodName, Type[] originalTypes, Type returnType, object[] originalParameters)
{
- var methodInfoFactory = new MethodInfoCacheItemFactory(className, methodName, types);
- return _methodInfoCache!.GetOrAdd(key, methodInfoFactory.GetMethodInfo);
+ var types = originalTypes ?? Array.Empty();
+ var parameters = originalParameters ?? Array.Empty();
+
+ if (!_invokerCache.TryGetValue(key, out var invoker))
+ {
+ var type = Type.GetType(className);
+ var mi = type.GetMethod(methodName, types);
+ var delegateType = returnType == null ? Expression.GetActionType(types) : Expression.GetFuncType(GetTypesForFunc(types, returnType));
+ var methodDelegate = mi.CreateDelegate(delegateType);
+
+ /* Create a function similar to the following code
+ * object InovkeMethod(object[] parameters) {
+ * return methodDelegate.Invoke((types[0])parameters[0]);
+ * }
+ */
+ var parametersParam = Expression.Parameter(typeof(object[]), "parameters");
+ var methodToCall = Expression.Constant(methodDelegate, delegateType);
+ IEnumerable parameterExpressions = GetInvokerParameterExpressions(parametersParam, types);
+ var invocationExpression = Expression.Invoke(methodToCall, parameterExpressions);
+
+ var lambdaExpression = returnType != null ?
+ Expression.Lambda>(invocationExpression, parametersParam) :
+ Expression.Lambda>(Expression.Block(invocationExpression, Expression.Constant(null)), parametersParam);
+
+ invoker = lambdaExpression.Compile();
+ _invokerCache.TryAdd(key, invoker);
+ }
+
+ return invoker(parameters);
}
- // Using a struct to try to take advantage of stack allocation to reduce overall allocations.
- private struct MethodInfoCacheItemFactory
+ private static IEnumerable GetInvokerParameterExpressions(Expression parametersExpression, Type[] types)
{
- private string _className;
- private string _methodName;
- private Type[] _types;
-
- public MethodInfoCacheItemFactory(string className, string methodName, Type[] types)
+ for (var i = 0; i < types.Length; i++)
{
- _className = className;
- _methodName = methodName;
- _types = types;
+ var getParameterAtIndex = Expression.ArrayAccess(parametersExpression, Expression.Constant(i));
+ yield return Expression.Convert(getParameterAtIndex, types[i]);
}
+ }
- public MethodInfo GetMethodInfo(string _)
+ private static Type[] GetTypesForFunc(Type[] parameterTypes, Type returnType)
+ {
+ if (parameterTypes.Length == 0)
{
- var type = Type.GetType(_className);
+ return new Type[] { returnType };
+ }
- var method = _types != null ? type.GetMethod(_methodName, _types) : type.GetMethod(_methodName);
+ var types = new Type[parameterTypes.Length + 1];
+ Array.Copy(parameterTypes, types, parameterTypes.Length);
+ types[parameterTypes.Length] = returnType;
- return method;
- }
+ return types;
}
}
}
diff --git a/src/Agent/NewRelic/Profiler/MethodRewriter/ApiFunctionManipulator.h b/src/Agent/NewRelic/Profiler/MethodRewriter/ApiFunctionManipulator.h
index c3d97ce5ea..0b11c9a87a 100644
--- a/src/Agent/NewRelic/Profiler/MethodRewriter/ApiFunctionManipulator.h
+++ b/src/Agent/NewRelic/Profiler/MethodRewriter/ApiFunctionManipulator.h
@@ -45,21 +45,68 @@ namespace NewRelic { namespace Profiler { namespace MethodRewriter
TryCatch(
[&]()
{
- // delegate = System.CannotUnloadAppDomainException.GetMethodFromAppDomainStorageOrReflectionOrThrow("NewRelic_Delegate_API_", "C:\path\to\NewRelic.Agent.Core", "NewRelic.Core.AgentApi", "", new object[] { })
- LoadMethodInfo(_instrumentationSettings->GetCorePath(), _X("NewRelic.Agent.Core.AgentApi"), _function->GetFunctionName(), _function->GetFunctionId(), GetArrayOfTypeParametersLamdba());
-
- _instructions->Append(_X("ldnull"));
- BuildObjectArrayOfParameters();
- _instructions->Append(_X("call instance object [") + _instructions->GetCoreLibAssemblyName() + _X("]System.Reflection.MethodBase::Invoke(object, object[])"));
-
- if (_methodSignature->_returnType->_kind == SignatureParser::ReturnType::Kind::VOID_RETURN_TYPE)
+ if (_agentCallStrategy == AgentCallStyle::Strategy::InAgentCache)
{
- _instructions->Append(_X("pop"));
+ // Ensure that the managed agent is loaded
+ _instructions->AppendString(_instrumentationSettings->GetCorePath());
+ _instructions->Append(_X("call void [") + _instructions->GetCoreLibAssemblyName() + _X("]System.CannotUnloadAppDomainException::EnsureInitialized(string)"));
+
+ // Get the Func holding a reference to the ProfilerAgentMethodCallCache.GetAndInvokeMethodFromCache method
+ _instructions->Append(_X("call object [") + _instructions->GetCoreLibAssemblyName() + _X("]System.CannotUnloadAppDomainException::GetMethodCacheLookupMethod()"));
+ _instructions->Append(CEE_CASTCLASS, _X("class [") + _instructions->GetCoreLibAssemblyName() + _X("]System.Func`7GetCoreLibAssemblyName() + _X("]System.Type[], class [") + _instructions->GetCoreLibAssemblyName() + _X("]System.Type, object[], object>"));
+
+ xstring_t className = _X("NewRelic.Agent.Core.AgentApi");
+ xstring_t methodName = _function->GetFunctionName();
+ xstring_t keyName = className + _X(".") + methodName + _X("_") + to_xstring((unsigned long)_function->GetFunctionId());
+ auto argumentTypesLambda = GetArrayOfTypeParametersLamdba();
+
+ _instructions->AppendString(keyName);
+ _instructions->AppendString(className);
+ _instructions->AppendString(methodName);
+
+ if (argumentTypesLambda == NULL)
+ {
+ _instructions->Append(CEE_LDNULL);
+ }
+ else
+ {
+ argumentTypesLambda();
+ }
+
+ _instructions->AppendTypeOfArgument(_methodSignature->_returnType);
+
+ BuildObjectArrayOfParameters();
+
+ _instructions->Append(CEE_CALLVIRT, _X("instance !6 class [") + _instructions->GetCoreLibAssemblyName() + _X("]System.Func`7GetCoreLibAssemblyName() + _X("]System.Type[], class [") + _instructions->GetCoreLibAssemblyName() + _X("]System.Type, object[], object>::Invoke(!0, !1, !2, !3, !4, !5)"));
+
+ if (_methodSignature->_returnType->_kind == SignatureParser::ReturnType::Kind::VOID_RETURN_TYPE)
+ {
+ _instructions->Append(_X("pop"));
+ }
+ else {
+ // we can't leave an object on the stack and CEE_LEAVE a protected block.
+ // we have to store it in a local and reload it outside of the try..catch.
+ _instructions->AppendStoreLocal(resultLocalIndex);
+ }
}
- else {
- // we can't leave an object on the stack and CEE_LEAVE a protected block.
- // we have to store it in a local and reload it outside of the try..catch.
- _instructions->AppendStoreLocal(resultLocalIndex);
+ else
+ {
+ // delegate = System.CannotUnloadAppDomainException.GetMethodFromAppDomainStorageOrReflectionOrThrow("NewRelic_Delegate_API_", "C:\path\to\NewRelic.Agent.Core", "NewRelic.Core.AgentApi", "", new object[] { })
+ LoadMethodInfo(_instrumentationSettings->GetCorePath(), _X("NewRelic.Agent.Core.AgentApi"), _function->GetFunctionName(), _function->GetFunctionId(), GetArrayOfTypeParametersLamdba());
+
+ _instructions->Append(_X("ldnull"));
+ BuildObjectArrayOfParameters();
+ _instructions->Append(_X("call instance object [") + _instructions->GetCoreLibAssemblyName() + _X("]System.Reflection.MethodBase::Invoke(object, object[])"));
+
+ if (_methodSignature->_returnType->_kind == SignatureParser::ReturnType::Kind::VOID_RETURN_TYPE)
+ {
+ _instructions->Append(_X("pop"));
+ }
+ else {
+ // we can't leave an object on the stack and CEE_LEAVE a protected block.
+ // we have to store it in a local and reload it outside of the try..catch.
+ _instructions->AppendStoreLocal(resultLocalIndex);
+ }
}
},
[&]()
diff --git a/src/Agent/NewRelic/Profiler/MethodRewriter/FunctionManipulator.h b/src/Agent/NewRelic/Profiler/MethodRewriter/FunctionManipulator.h
index a1de849b1e..e838eee036 100644
--- a/src/Agent/NewRelic/Profiler/MethodRewriter/FunctionManipulator.h
+++ b/src/Agent/NewRelic/Profiler/MethodRewriter/FunctionManipulator.h
@@ -320,25 +320,7 @@ namespace NewRelic { namespace Profiler { namespace MethodRewriter
// The function id is used as a tie-breaker for overloaded methods when computing the key name for the cache.
void LoadMethodInfo(xstring_t assemblyPath, xstring_t className, xstring_t methodName, uintptr_t functionId, std::function argumentTypesLambda)
{
- if (_agentCallStrategy == AgentCallStyle::Strategy::InAgentCache)
- {
- auto keyName = className + _X(".") + methodName + _X("_") + to_xstring((unsigned long)functionId);
- _instructions->AppendString(keyName);
- _instructions->AppendString(assemblyPath);
- _instructions->AppendString(className);
- _instructions->AppendString(methodName);
- if (argumentTypesLambda == NULL)
- {
- _instructions->Append(CEE_LDNULL);
- }
- else
- {
- argumentTypesLambda();
- }
-
- _instructions->Append(CEE_CALL, _X("class [") + _instructions->GetCoreLibAssemblyName() + _X("]System.Reflection.MethodInfo [") + _instructions->GetCoreLibAssemblyName() + _X("]System.CannotUnloadAppDomainException::GetMethodInfoFromAgentCache(string,string,string,string,class [") + _instructions->GetCoreLibAssemblyName() + _X("]System.Type[])"));
- }
- else if (_agentCallStrategy == AgentCallStyle::Strategy::AppDomainCache)
+ if (_agentCallStrategy == AgentCallStyle::Strategy::AppDomainCache)
{
auto keyName = className + _X(".") + methodName + _X("_") + to_xstring((unsigned long)functionId);
_instructions->AppendString(keyName);
@@ -356,11 +338,16 @@ namespace NewRelic { namespace Profiler { namespace MethodRewriter
_instructions->Append(CEE_CALL, _X("class [") + _instructions->GetCoreLibAssemblyName() + _X("]System.Reflection.MethodInfo [") + _instructions->GetCoreLibAssemblyName() + _X("]System.CannotUnloadAppDomainException::GetMethodFromAppDomainStorageOrReflectionOrThrow(string,string,string,string,class [") + _instructions->GetCoreLibAssemblyName() + _X("]System.Type[])"));
}
- else
+ else if (_agentCallStrategy == AgentCallStyle::Strategy::Reflection)
{
LoadType(assemblyPath, className);
LoadMethodInfoFromType(methodName, argumentTypesLambda);
}
+ else
+ {
+ LogError(L"Attempting to call LoadMethodInfo when an AgentCallStyle::Strategy that does not support loading method info is configured.");
+ throw FunctionManipulatorException();
+ }
}
// Creates an array of elementLoadLanbdas.size() and loads the elements into the array
diff --git a/src/Agent/NewRelic/Profiler/MethodRewriter/HelperFunctionManipulator.h b/src/Agent/NewRelic/Profiler/MethodRewriter/HelperFunctionManipulator.h
index 1b5fa6a6cf..d200b97573 100644
--- a/src/Agent/NewRelic/Profiler/MethodRewriter/HelperFunctionManipulator.h
+++ b/src/Agent/NewRelic/Profiler/MethodRewriter/HelperFunctionManipulator.h
@@ -211,7 +211,7 @@ namespace NewRelic { namespace Profiler { namespace MethodRewriter
{
_instructions->Append(CEE_LDARG_0);
_instructions->AppendString(_X("NewRelic.Agent.Core.ProfilerAgentMethodCallCache"));
- _instructions->AppendString(_X("GetMethodCacheFunc"));
+ _instructions->AppendString(_X("GetInvokerFromCache"));
_instructions->Append(CEE_LDNULL);
_instructions->Append(CEE_CALL, _X("class System.Reflection.MethodInfo System.CannotUnloadAppDomainException::GetMethodViaReflectionOrThrow(string,string,string,class System.Type[])"));
diff --git a/src/Agent/NewRelic/Profiler/MethodRewriter/InstructionSet.h b/src/Agent/NewRelic/Profiler/MethodRewriter/InstructionSet.h
index 08c9d44505..4da6b7cbf7 100644
--- a/src/Agent/NewRelic/Profiler/MethodRewriter/InstructionSet.h
+++ b/src/Agent/NewRelic/Profiler/MethodRewriter/InstructionSet.h
@@ -346,6 +346,22 @@ namespace NewRelic { namespace Profiler { namespace MethodRewriter
}
}
+ void AppendTypeOfArgument(SignatureParser::ReturnTypePtr returnType)
+ {
+ auto typeToken = GetTypeTokenForReturnType(returnType);
+
+ if (typeToken != 0)
+ {
+ Append(CEE_LDTOKEN, typeToken);
+ Append(_X("call class [") + _coreLibAssemblyName + _X("]System.Type[") + _coreLibAssemblyName + _X("]System.Type::GetTypeFromHandle(valuetype[") + _coreLibAssemblyName + _X("]System.RuntimeTypeHandle)"));
+ }
+ else
+ {
+ // void return type
+ Append(CEE_LDNULL);
+ }
+ }
+
// append a load argument instruction
void AppendLoadArgument(uint16_t argumentIndex)
{
@@ -544,6 +560,32 @@ namespace NewRelic { namespace Profiler { namespace MethodRewriter
}
}
+ uint32_t GetTypeTokenForReturnType(SignatureParser::ReturnTypePtr returnType)
+ {
+ switch (returnType->_kind)
+ {
+ case SignatureParser::ReturnType::Kind::VOID_RETURN_TYPE:
+ {
+ return 0;
+ }
+ case SignatureParser::ReturnType::Kind::TYPED_BY_REF_RETURN_TYPE:
+ {
+ auto token = _tokenizer->GetTypeRefToken(_coreLibAssemblyName, _X("System.TypedReference"));
+ return token;
+ }
+ case SignatureParser::ReturnType::Kind::TYPED_RETURN_TYPE:
+ {
+ auto typedReturnType = std::static_pointer_cast(returnType);
+ return GetTypeTokenForType(typedReturnType->_type);
+ }
+ default:
+ {
+ LogError(L"Unhandled return type encountered in InstructionSet::GetTypeTokenForReturnType. Kind: ", std::hex, std::showbase, returnType->_kind, std::resetiosflags(std::ios_base::basefield | std::ios_base::showbase));
+ throw InstructionSetException();
+ }
+ }
+ }
+
uint32_t GetTypeTokenForType(SignatureParser::TypePtr type)
{
switch (type->_kind)
diff --git a/src/Agent/NewRelic/Profiler/ModuleInjector/ModuleInjector.h b/src/Agent/NewRelic/Profiler/ModuleInjector/ModuleInjector.h
index 7adcdcd5e3..4acb433b2a 100644
--- a/src/Agent/NewRelic/Profiler/ModuleInjector/ModuleInjector.h
+++ b/src/Agent/NewRelic/Profiler/ModuleInjector/ModuleInjector.h
@@ -67,8 +67,8 @@ namespace NewRelic { namespace Profiler { namespace ModuleInjector
ManagedMethodToInject(_X("[mscorlib]System.CannotUnloadAppDomainException"), _X("GetAgentShimFinishTracerDelegateMethod"), _X("object"), _X(""))
};
- // When injecting HELPER METHODS into the System.Private.CoreLib assembly, theses references should be local.
- // They cannot reference [System.Private.CoreLib] since these methods are being rewritten in System.Private.CoreLib.
+ // When injecting HELPER METHODS into the Core Lib assembly, theses references should be local.
+ // They cannot reference an assembly since these methods are being rewritten in the Core Lib so only types available in that lib can be used.
constexpr std::array methodImplsToInject{
ManagedMethodToInject(_X("System.CannotUnloadAppDomainException"), _X("LoadAssemblyOrThrow"), _X("class System.Reflection.Assembly"), _X("string")),
ManagedMethodToInject(_X("System.CannotUnloadAppDomainException"), _X("GetTypeViaReflectionOrThrow"), _X("class System.Type"), _X("string,string")),
@@ -86,7 +86,7 @@ namespace NewRelic { namespace Profiler { namespace ModuleInjector
const auto is_coreLib = module.GetIsThisTheCoreLibAssembly();
- // If instrumenting System.Private.CoreLib, use local (to the assembly) references
+ // If instrumenting Core Lib, use local (to the assembly) references
// otherwise use external references
auto methods = is_coreLib
? methodImplsToInject
diff --git a/src/Agent/NewRelic/Profiler/ModuleInjectorTest/ModuleInjectorTest.cpp b/src/Agent/NewRelic/Profiler/ModuleInjectorTest/ModuleInjectorTest.cpp
index 3134f0e278..b4fe3c360f 100644
--- a/src/Agent/NewRelic/Profiler/ModuleInjectorTest/ModuleInjectorTest.cpp
+++ b/src/Agent/NewRelic/Profiler/ModuleInjectorTest/ModuleInjectorTest.cpp
@@ -130,7 +130,7 @@ namespace NewRelic { namespace Profiler { namespace MethodRewriter { namespace T
void AssertHelperMethodsWereInjected(const bool isCoreClr, const bool throwsException)
{
- std::array expectedMethods = {
+ std::array expectedMethods = {
_X("System.CannotUnloadAppDomainException.LoadAssemblyOrThrow"),
_X("System.CannotUnloadAppDomainException.GetTypeViaReflectionOrThrow"),
_X("System.CannotUnloadAppDomainException.GetMethodViaReflectionOrThrow"),
@@ -139,7 +139,10 @@ namespace NewRelic { namespace Profiler { namespace MethodRewriter { namespace T
_X("System.CannotUnloadAppDomainException.StoreMethodInAppDomainStorageOrThrow"),
_X("System.CannotUnloadAppDomainException.EnsureInitialized"),
_X("System.CannotUnloadAppDomainException.GetMethodInfoFromAgentCache"),
- _X("System.CannotUnloadAppDomainException.GetMethodCacheLookupMethod")
+ _X("System.CannotUnloadAppDomainException.GetMethodCacheLookupMethod"),
+ _X("System.CannotUnloadAppDomainException.GetAgentShimFinishTracerDelegateMethod"),
+ _X("System.CannotUnloadAppDomainException.StoreAgentShimFinishTracerDelegateMethod"),
+ _X("System.CannotUnloadAppDomainException.StoreMethodCacheLookupMethod")
};
auto modulePtr = std::make_shared();
@@ -173,7 +176,7 @@ namespace NewRelic { namespace Profiler { namespace MethodRewriter { namespace T
void AssertHelperMethodReferencesWereInjected(const bool isCoreClr, const bool throwsException)
{
const xstring_t expectedAssembly = isCoreClr ? _X("[System.Private.CoreLib]") : _X("[mscorlib]");
- std::array expectedMethods = {
+ std::array expectedMethods = {
expectedAssembly + _X("System.CannotUnloadAppDomainException.LoadAssemblyOrThrow"),
expectedAssembly + _X("System.CannotUnloadAppDomainException.GetTypeViaReflectionOrThrow"),
expectedAssembly + _X("System.CannotUnloadAppDomainException.GetMethodViaReflectionOrThrow"),
@@ -182,7 +185,10 @@ namespace NewRelic { namespace Profiler { namespace MethodRewriter { namespace T
expectedAssembly + _X("System.CannotUnloadAppDomainException.StoreMethodInAppDomainStorageOrThrow"),
expectedAssembly + _X("System.CannotUnloadAppDomainException.EnsureInitialized"),
expectedAssembly + _X("System.CannotUnloadAppDomainException.GetMethodInfoFromAgentCache"),
- expectedAssembly + _X("System.CannotUnloadAppDomainException.GetMethodCacheLookupMethod")
+ expectedAssembly + _X("System.CannotUnloadAppDomainException.GetMethodCacheLookupMethod"),
+ expectedAssembly + _X("System.CannotUnloadAppDomainException.GetAgentShimFinishTracerDelegateMethod"),
+ expectedAssembly + _X("System.CannotUnloadAppDomainException.StoreAgentShimFinishTracerDelegateMethod"),
+ expectedAssembly + _X("System.CannotUnloadAppDomainException.StoreMethodCacheLookupMethod")
};
auto modulePtr = std::make_shared();
diff --git a/tests/Agent/UnitTests/Core.UnitTest/ProfilerAgentMethodCallCacheTests.cs b/tests/Agent/UnitTests/Core.UnitTest/ProfilerAgentMethodCallCacheTests.cs
index 99082cb384..423afde1a3 100644
--- a/tests/Agent/UnitTests/Core.UnitTest/ProfilerAgentMethodCallCacheTests.cs
+++ b/tests/Agent/UnitTests/Core.UnitTest/ProfilerAgentMethodCallCacheTests.cs
@@ -1,7 +1,6 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-using System.Reflection;
using System;
using NUnit.Framework;
using NewRelic.Testing.Assertions;
@@ -14,64 +13,82 @@ public class ProfilerAgentMethodCallCacheTests
[Test]
public void GetMethodCacheFuncShouldReturnAFuncAsAnObject()
{
- var methodReference = ProfilerAgentMethodCallCache.GetMethodCacheFunc();
- Assert.IsAssignableFrom(typeof(Func), methodReference);
+ var methodReference = ProfilerAgentMethodCallCache.GetInvokerFromCache();
+ Assert.IsAssignableFrom(typeof(Func), methodReference);
}
[Test]
- public void ShouldGetMethodWithNoParameters()
+ public void ShouldInvokeMethodWithNoParameters()
{
- var cacheGetter = (Func)ProfilerAgentMethodCallCache.GetMethodCacheFunc();
+ var invoker = (Func)ProfilerAgentMethodCallCache.GetInvokerFromCache();
var typeName = typeof(TestingClass).AssemblyQualifiedName;
var methodName = nameof(TestingClass.MethodWithNoParams);
var cacheKey = string.Concat(typeName, "|", methodName);
- var methodInfoFromCache = cacheGetter.Invoke(cacheKey, typeName, methodName, null);
+ var result1 = invoker.Invoke(cacheKey, typeName, methodName, null, typeof(string), null);
+ var result2 = invoker.Invoke(cacheKey, typeName, methodName, Array.Empty(), typeof(string), null);
NrAssert.Multiple(
- () => Assert.IsNotNull(methodInfoFromCache),
- () => Assert.AreEqual(methodName, methodInfoFromCache.Invoke(null, null))
+ () => Assert.IsNotNull(result1),
+ () => Assert.AreEqual(methodName, result1),
+ () => Assert.AreEqual(result1, result2)
);
}
[Test]
- public void ShouldGetMethodWithParameters()
+ public void ShouldInvokeMethodWithParameters()
{
- var cacheGetter = (Func)ProfilerAgentMethodCallCache.GetMethodCacheFunc();
+ var invoker = (Func)ProfilerAgentMethodCallCache.GetInvokerFromCache();
var typeName = typeof(TestingClass).AssemblyQualifiedName;
var methodName = nameof(TestingClass.MethodWithParams);
var cacheKey = string.Concat(typeName, "|", methodName);
+ var parameterValue = "param_value";
- var methodInfoFromCache = cacheGetter.Invoke(cacheKey, typeName, methodName, new Type[] { typeof(string) });
+ var result = invoker.Invoke(cacheKey, typeName, methodName, new Type[] { typeof(string) }, typeof(string), new object[] { parameterValue });
- var expectedMethodResult = $"{methodName} + param_value";
+ var expectedMethodResult = $"{methodName} + {parameterValue}";
NrAssert.Multiple(
- () => Assert.IsNotNull(methodInfoFromCache),
- () => Assert.AreEqual(expectedMethodResult, methodInfoFromCache.Invoke(null, new object[] { "param_value" }))
+ () => Assert.IsNotNull(result),
+ () => Assert.AreEqual(expectedMethodResult, result)
);
}
[Test]
- public void ShouldGetWrongMethodIfCacheKeyNotUniqueEnough()
+ public void ShouldInvokeWrongMethodIfCacheKeyNotUniqueEnough()
{
- var cacheGetter = (Func)ProfilerAgentMethodCallCache.GetMethodCacheFunc();
+ var invoker = (Func)ProfilerAgentMethodCallCache.GetInvokerFromCache();
var typeName = typeof(TestingClass).AssemblyQualifiedName;
var methodName = nameof(TestingClass.MethodWithOverload);
var cacheKey = string.Concat(typeName, "|", methodName);
- var overloadWith1Param = cacheGetter.Invoke(cacheKey, typeName, methodName, new Type[] { typeof(string) });
- var overloadWith2Params = cacheGetter.Invoke(cacheKey, typeName, methodName, new Type[] { typeof(string), typeof(string) });
+ var resultWithStringParam = invoker.Invoke(cacheKey, typeName, methodName, new Type[] { typeof(string) }, typeof(string), new object[] { "param" });
NrAssert.Multiple(
- () => Assert.IsNotNull(overloadWith1Param),
- () => Assert.IsNotNull(overloadWith2Params),
- () => Assert.AreEqual(overloadWith2Params, overloadWith1Param),
- // The call to the 2 parameter overload should fail because the 1 parameter overload was returned from the cache
- () => Assert.Throws(typeof(TargetParameterCountException), () => overloadWith2Params.Invoke(null, new object[] { "param_1", "param_2" }))
+ () => Assert.IsNotNull(resultWithStringParam),
+ // The call to the bool parameter overload should fail because the string parameter overload was returned from the cache
+ () => Assert.Throws(typeof(InvalidCastException), () => invoker.Invoke(cacheKey, typeName, methodName, new Type[] { typeof(bool) }, typeof(string), new object[] { true }))
+ );
+ }
+
+ [Test]
+ public void ShouldHandleMethodsWithVoidReturnType()
+ {
+ var invoker = (Func)ProfilerAgentMethodCallCache.GetInvokerFromCache();
+
+ var typeName = typeof(TestingClass).AssemblyQualifiedName;
+
+ var resultNoParam = invoker.Invoke(string.Concat(typeName, "|", nameof(TestingClass.ActionWithNoParams)), typeName, nameof(TestingClass.ActionWithNoParams), null, null, null);
+ var resultWithParam = invoker.Invoke(string.Concat(typeName, "|", nameof(TestingClass.ActionWithParam)), typeName, nameof(TestingClass.ActionWithParam), new Type[] { typeof(int) }, null, new object[] { 5 });
+
+ NrAssert.Multiple(
+ () => Assert.IsNull(resultNoParam),
+ () => Assert.IsNull(resultWithParam),
+ () => Assert.AreEqual(1, TestingClass.ActionWithNoParamsCallCount),
+ () => Assert.AreEqual(5, TestingClass.ActionWithParamLatestValue)
);
}
@@ -92,9 +109,21 @@ public static string MethodWithOverload(string param)
return $"{nameof(MethodWithOverload)} + {param}";
}
- public static string MethodWithOverload(string param1, string param2)
+ public static string MethodWithOverload(bool param1)
+ {
+ return $"{nameof(MethodWithOverload)} + {param1}";
+ }
+
+ public static int ActionWithNoParamsCallCount = 0;
+ public static void ActionWithNoParams()
+ {
+ ActionWithNoParamsCallCount++;
+ }
+
+ public static int ActionWithParamLatestValue = 0;
+ public static void ActionWithParam(int value)
{
- return $"{nameof(MethodWithOverload)} + {param1} + {param2}";
+ ActionWithParamLatestValue = value;
}
}
}