-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add support for the EnyimMemcachedCore client. (#2781)
- Loading branch information
Showing
21 changed files
with
893 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Memcached/EnyimMemcachedCoreWrapper.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
// Copyright 2020 New Relic, Inc. All rights reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
using System; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using NewRelic.Agent.Api; | ||
using NewRelic.Agent.Extensions.Parsing; | ||
using NewRelic.Agent.Extensions.Providers.Wrapper; | ||
|
||
namespace NewRelic.Providers.Wrapper.Memcached | ||
{ | ||
public class EnyimMemcachedCoreWrapper : IWrapper | ||
{ | ||
private const string ModelName = "cache"; | ||
private const string WrapperName = "EnyimMemcachedCoreWrapper"; | ||
|
||
public bool IsTransactionRequired => true; | ||
|
||
public CanWrapResponse CanWrap(InstrumentedMethodInfo methodInfo) | ||
{ | ||
return new CanWrapResponse(WrapperName.Equals(methodInfo.RequestedWrapperName)); | ||
} | ||
|
||
public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction) | ||
{ | ||
if (instrumentedMethodCall.IsAsync) | ||
{ | ||
transaction.AttachToAsync(); | ||
} | ||
|
||
// Internally, the key is used to determine what server to read from in a multi-server environment. | ||
// Without a key, we can't determine the server, so we can't determine the connection info. | ||
|
||
ParsedSqlStatement parsedStatement; | ||
string key; | ||
|
||
// Operation is the first argument in all cases, Key is the second argument | ||
if (instrumentedMethodCall.MethodCall.Method.MethodName.Equals("PerformStore") | ||
|| instrumentedMethodCall.MethodCall.Method.MethodName.Equals("PerformStoreAsync") | ||
|| instrumentedMethodCall.MethodCall.Method.MethodName.Equals("PerformMutate") | ||
|| instrumentedMethodCall.MethodCall.Method.MethodName.Equals("PerformMutateAsync") | ||
|| instrumentedMethodCall.MethodCall.Method.MethodName.Equals("PerformConcatenate")) | ||
{ | ||
key = instrumentedMethodCall.MethodCall.MethodArguments[1].ToString(); | ||
parsedStatement = new ParsedSqlStatement(DatastoreVendor.Memcached, | ||
ModelName, | ||
instrumentedMethodCall.MethodCall.MethodArguments[0].ToString()); | ||
} | ||
// Operation is always Get, Key is the first argument | ||
else if (instrumentedMethodCall.MethodCall.Method.MethodName.Equals("PerformTryGet") | ||
|| instrumentedMethodCall.MethodCall.Method.MethodName.Equals("PerformGet") | ||
|| instrumentedMethodCall.MethodCall.Method.MethodName.Equals("GetAsync")) | ||
{ | ||
key = instrumentedMethodCall.MethodCall.MethodArguments[0].ToString(); | ||
parsedStatement = new ParsedSqlStatement(DatastoreVendor.Memcached, | ||
ModelName, | ||
"Get"); | ||
} | ||
// Operation is always Remove, Key is the first argument | ||
else if (instrumentedMethodCall.MethodCall.Method.MethodName.Equals("Remove") | ||
|| instrumentedMethodCall.MethodCall.Method.MethodName.Equals("RemoveAsync")) | ||
{ | ||
key = instrumentedMethodCall.MethodCall.MethodArguments[0].ToString(); | ||
parsedStatement = new ParsedSqlStatement(DatastoreVendor.Memcached, | ||
ModelName, | ||
"Remove"); | ||
} | ||
// Should not happen | ||
else | ||
{ | ||
return Delegates.NoOp; | ||
} | ||
|
||
var connectionInfo = MemcachedHelpers.GetConnectionInfo( | ||
key, | ||
instrumentedMethodCall.MethodCall.InvocationTarget, | ||
agent); | ||
|
||
var segment = transaction.StartDatastoreSegment(instrumentedMethodCall.MethodCall, parsedStatement, connectionInfo, isLeaf: true); | ||
|
||
if (instrumentedMethodCall.IsAsync) | ||
{ | ||
return Delegates.GetAsyncDelegateFor<Task>( | ||
agent, | ||
segment); | ||
} | ||
|
||
return Delegates.GetDelegateFor( | ||
onFailure: (ex) => segment.End(ex), | ||
onComplete: () => segment.End() | ||
); | ||
} | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Memcached/Instrumentation.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<!-- | ||
Copyright 2020 New Relic Corporation. All rights reserved. | ||
SPDX-License-Identifier: Apache-2.0 | ||
--> | ||
<extension xmlns="urn:newrelic-extension"> | ||
<instrumentation> | ||
|
||
<!--Supports all MemcachedClient and "DistributedCache" methods, except MultiX methods.--> | ||
<tracerFactory name="EnyimMemcachedCoreWrapper"> | ||
|
||
<!--MemcachedClient<T> calls into MemcachedClient--> | ||
<!--DistributedCache is just MemcachedClient, not a separate class--> | ||
<match assemblyName="EnyimMemcachedCore" className="Enyim.Caching.MemcachedClient"> | ||
<!--Add/Async, Set/Async, Replace/Async, Store/Async, Cas--> | ||
<exactMethodMatcher methodName="PerformStore" parameters="Enyim.Caching.Memcached.StoreMode,System.String,System.Object,System.UInt32,System.UInt64&,System.Int32&" /> | ||
<exactMethodMatcher methodName="PerformStoreAsync" /> | ||
|
||
<!--Get > TryGet > PerformTryGet--> | ||
<!--GetWithCas > TryGetWithCas > PerformTryGet--> | ||
<!--GetWithCas<T> > TryGetWithCas > PerformTryGet--> | ||
<exactMethodMatcher methodName="PerformTryGet" /> | ||
|
||
<!--Get<T>--> | ||
<exactMethodMatcher methodName="PerformGet" /> | ||
|
||
<!--GetValueAsync--> | ||
<exactMethodMatcher methodName="GetAsync" parameters="System.String" /> | ||
|
||
<!--Increment, Decrement, CasMutate--> | ||
<exactMethodMatcher methodName="PerformMutate" parameters="Enyim.Caching.Memcached.MutationMode,System.String,System.UInt64,System.UInt64,System.UInt32,System.UInt64&" /> | ||
|
||
<!--TouchAsync--> | ||
<exactMethodMatcher methodName="PerformMutateAsync" /> | ||
|
||
<!--Append, Prepend--> | ||
<exactMethodMatcher methodName="PerformConcatenate" /> | ||
|
||
<exactMethodMatcher methodName="Remove" /> | ||
<exactMethodMatcher methodName="RemoveAsync" /> | ||
</match> | ||
</tracerFactory> | ||
|
||
</instrumentation> | ||
</extension> |
18 changes: 18 additions & 0 deletions
18
src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Memcached/Memcached.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0"> | ||
<PropertyGroup> | ||
<TargetFrameworks>net462;netstandard2.0</TargetFrameworks> | ||
<RootNamespace>NewRelic.Providers.Wrapper.Memcached</RootNamespace> | ||
<AssemblyName>NewRelic.Providers.Wrapper.Memcached</AssemblyName> | ||
<Description>Memcached Wrapper Provider for New Relic .NET Agent</Description> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\..\..\NewRelic.Agent.Extensions\NewRelic.Agent.Extensions.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<Content Include="Instrumentation.xml"> | ||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||
</Content> | ||
</ItemGroup> | ||
</Project> |
80 changes: 80 additions & 0 deletions
80
src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Memcached/MemcachedHelpers.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// Copyright 2020 New Relic, Inc. All rights reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
using System; | ||
using NewRelic.Agent.Api; | ||
using NewRelic.Agent.Extensions.Parsing; | ||
using NewRelic.Agent.Extensions.Providers.Wrapper; | ||
using NewRelic.Reflection; | ||
|
||
namespace NewRelic.Providers.Wrapper.Memcached | ||
{ | ||
public class MemcachedHelpers | ||
{ | ||
private static bool _hasGetServerFailed = false; | ||
private const string AssemblyName = "EnyimMemcachedCore"; | ||
private static Func<object, object> _transformerGetter; | ||
private static Func<object, string, string> _transformMethod; | ||
private static Func<object, object> _poolGetter; | ||
private static Func<object, string, object> _locateMethod; | ||
private static Func<object, object> _endpointGetter; | ||
private static Func<object, object> _addressGetter; | ||
private static Func<object, int> _portGetter; | ||
|
||
// To get the ConnectionInfo we need to call the same Transform method that the library calls to get the node. | ||
// This is deterministic based reviewing the code at different versions. | ||
public static ConnectionInfo GetConnectionInfo(string key, object target, IAgent agent) | ||
{ | ||
if (_hasGetServerFailed) | ||
{ | ||
return new ConnectionInfo(DatastoreVendor.Memcached.ToKnownName(), null, -1, null); | ||
} | ||
|
||
try | ||
{ | ||
var targetType = target.GetType(); | ||
_transformerGetter ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<object>(targetType, "KeyTransformer"); | ||
var transformer = _transformerGetter(target); | ||
|
||
_transformMethod ??= VisibilityBypasser.Instance.GenerateOneParameterMethodCaller<string, string>(AssemblyName, transformer.GetType().FullName, "Transform"); | ||
var hashedKey = _transformMethod(transformer, key); | ||
|
||
_poolGetter ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<object>(targetType, "Pool"); | ||
var pool = _poolGetter(target); | ||
|
||
_locateMethod ??= VisibilityBypasser.Instance.GenerateOneParameterMethodCaller<string, object>(AssemblyName, pool.GetType().FullName, "Enyim.Caching.Memcached.IServerPool.Locate"); | ||
var node = _locateMethod(pool, hashedKey); | ||
|
||
_endpointGetter ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<object>(node.GetType(), "EndPoint"); | ||
var endpoint = _endpointGetter(node); | ||
|
||
var endpointType = endpoint.GetType(); | ||
if (endpointType.Name == "DnsEndPoint") // v2.X | ||
{ | ||
_addressGetter ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<object>(endpointType, "Host"); | ||
} | ||
else if (endpointType.Name == "IPEndPoint") // v3.X | ||
{ | ||
_addressGetter ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<object>(endpointType, "Address"); | ||
} | ||
else | ||
{ | ||
throw new Exception("EndPoint type, "+ endpointType.Name + ", did not match supported types."); | ||
} | ||
|
||
var address = _addressGetter(endpoint).ToString(); | ||
|
||
_portGetter ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<int>(endpointType, "Port"); | ||
int? port = _portGetter(endpoint); | ||
|
||
return new ConnectionInfo(DatastoreVendor.Memcached.ToKnownName(), address, port.HasValue ? port.Value : -1, null); | ||
} | ||
catch (Exception exception) | ||
{ | ||
agent.Logger.Warn(exception, "Unable to get Memcached server address/port, likely to due to type differences. Server address/port will not be available."); | ||
_hasGetServerFailed = true; | ||
return new ConnectionInfo(DatastoreVendor.Memcached.ToKnownName(), null, -1, null); | ||
} | ||
} | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
tests/Agent/IntegrationTests/ContainerApplications/MemcachedTestApp/BlogPost.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright 2020 New Relic, Inc. All rights reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
namespace MemcachedTestApp | ||
{ | ||
public class BlogPost | ||
{ | ||
public string Title { get; set; } | ||
public string Body { get; set; } | ||
} | ||
} |
Oops, something went wrong.