Skip to content

Commit

Permalink
Convert AdoNet to use mdToken (DataDog#438)
Browse files Browse the repository at this point in the history
* Convert AdoNet to use mdToken

* Use DbCommand as concrete type
  • Loading branch information
colin-higgins authored Aug 8, 2019
1 parent e8dd723 commit dbd1945
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 44 deletions.
20 changes: 10 additions & 10 deletions src/Datadog.Trace.ClrProfiler.Managed/Emit/MethodBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal class MethodBuilder<TDelegate>
private static readonly ConcurrentDictionary<Key, TDelegate> Cache = new ConcurrentDictionary<Key, TDelegate>(new KeyComparer());
private static readonly ILog Log = LogProvider.GetLogger(typeof(MethodBuilder<TDelegate>));

private readonly Assembly _callingAssembly;
private readonly Assembly _resolutionAssembly;
private readonly int _mdToken;
private readonly int _originalOpCodeValue;
private readonly OpCodeValue _opCode;
Expand All @@ -36,19 +36,19 @@ internal class MethodBuilder<TDelegate>
private Type[] _methodGenerics;
private bool _forceMethodDefResolve;

private MethodBuilder(Assembly callingAssembly, int mdToken, int opCode, string methodName)
private MethodBuilder(Assembly resolutionAssembly, int mdToken, int opCode, string methodName)
{
_callingAssembly = callingAssembly;
_resolutionAssembly = resolutionAssembly;
_mdToken = mdToken;
_opCode = (OpCodeValue)opCode;
_originalOpCodeValue = opCode;
_methodName = methodName;
_forceMethodDefResolve = false;
}

public static MethodBuilder<TDelegate> Start(Assembly callingAssembly, int mdToken, int opCode, string methodName)
public static MethodBuilder<TDelegate> Start(Assembly resolutionAssembly, int mdToken, int opCode, string methodName)
{
return new MethodBuilder<TDelegate>(callingAssembly, mdToken, opCode, methodName);
return new MethodBuilder<TDelegate>(resolutionAssembly, mdToken, opCode, methodName);
}

public MethodBuilder<TDelegate> WithConcreteType(Type type)
Expand All @@ -60,7 +60,7 @@ public MethodBuilder<TDelegate> WithConcreteType(Type type)

public MethodBuilder<TDelegate> WithConcreteTypeName(string typeName)
{
var concreteType = _callingAssembly.GetType(typeName);
var concreteType = _resolutionAssembly.GetType(typeName);
return this.WithConcreteType(concreteType);
}

Expand Down Expand Up @@ -108,7 +108,7 @@ public MethodBuilder<TDelegate> WithReturnType(Type returnType)
public TDelegate Build()
{
var cacheKey = new Key(
callingModule: _callingAssembly.ManifestModule,
callingModule: _resolutionAssembly.ManifestModule,
mdToken: _mdToken,
callOpCode: _opCode,
concreteType: _concreteType,
Expand Down Expand Up @@ -136,12 +136,12 @@ private TDelegate EmitDelegate()
if (_forceMethodDefResolve || (_declaringTypeGenerics == null && _methodGenerics == null))
{
_methodBase =
_callingAssembly.ManifestModule.ResolveMethod(metadataToken: _mdToken);
_resolutionAssembly.ManifestModule.ResolveMethod(metadataToken: _mdToken);
}
else
{
_methodBase =
_callingAssembly.ManifestModule.ResolveMethod(
_resolutionAssembly.ManifestModule.ResolveMethod(
metadataToken: _mdToken,
genericTypeArguments: _declaringTypeGenerics,
genericMethodArguments: _methodGenerics);
Expand Down Expand Up @@ -281,7 +281,7 @@ private MethodInfo MakeGenericMethod(MethodInfo methodInfo)
private MethodInfo VerifyMethodFromToken(MethodInfo methodInfo)
{
// Verify baselines to ensure this isn't the wrong method somehow
var detailMessage = $"Unexpected method: {_concreteTypeName}.{_methodName} received for mdToken: {_mdToken} in assembly: {_callingAssembly.FullName}";
var detailMessage = $"Unexpected method: {_concreteTypeName}.{_methodName} received for mdToken: {_mdToken} in assembly: {_resolutionAssembly.FullName}";

if (!string.Equals(_methodName, methodInfo.Name))
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;
using System.Data;
using System.Data.Common;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Datadog.Trace.ClrProfiler.Emit;
using Datadog.Trace.ClrProfiler.Helpers;
using Datadog.Trace.ExtensionMethods;
using Datadog.Trace.Logging;

Expand All @@ -16,6 +18,11 @@ public static class AdoNetIntegration
{
private const string IntegrationName = "AdoNet";
private const string Major4 = "4";
private const string FrameworkAssembly = "System.Data";
private const string CoreAssembly = "System.Data.Common";
private const string DbCommand = "System.Data.Common.DbCommand";
private const string DbDataReader = "System.Data.Common.DbDataReader";
private const string CommandBehavior = "System.Data.CommandBehavior";

private static readonly ILog Log = LogProvider.GetLogger(typeof(AdoNetIntegration));

Expand All @@ -28,37 +35,49 @@ public static class AdoNetIntegration
/// <param name="mdToken">The mdToken of the original method call.</param>
/// <returns>The value returned by the instrumented method.</returns>
[InterceptMethod(
TargetAssembly = "System.Data", // .NET Framework
TargetType = "System.Data.Common.DbCommand",
TargetSignatureTypes = new[] { "System.Data.Common.DbDataReader", "System.Data.CommandBehavior" },
TargetAssembly = FrameworkAssembly,
TargetType = DbCommand,
TargetSignatureTypes = new[] { DbDataReader, CommandBehavior },
TargetMinimumVersion = Major4,
TargetMaximumVersion = Major4)]
[InterceptMethod(
TargetAssembly = "System.Data.Common", // .NET Core
TargetType = "System.Data.Common.DbCommand",
TargetSignatureTypes = new[] { "System.Data.Common.DbDataReader", "System.Data.CommandBehavior" },
TargetAssembly = CoreAssembly,
TargetType = DbCommand,
TargetSignatureTypes = new[] { DbDataReader, CommandBehavior },
TargetMinimumVersion = Major4,
TargetMaximumVersion = Major4)]
public static object ExecuteDbDataReader(object @this, int behavior, int opCode, int mdToken)
{
var command = (DbCommand)@this;
var commandBehavior = (CommandBehavior)behavior;
var instanceType = command.GetType();

var executeReader = Emit.DynamicMethodBuilder<Func<object, CommandBehavior, object>>
.GetOrCreateMethodCallDelegate(
typeof(DbCommand),
"ExecuteDbDataReader",
(OpCodeValue)opCode);
Func<object, CommandBehavior, object> instrumentedMethod = null;

try
{
instrumentedMethod =
MethodBuilder<Func<object, CommandBehavior, object>>
.Start(instanceType.Assembly, mdToken, opCode, nameof(ExecuteDbDataReader))
.WithConcreteType(typeof(DbCommand))
.WithParameters(commandBehavior)
.Build();
}
catch (Exception ex)
{
Log.ErrorException($"Error resolving {DbCommand}.{nameof(ExecuteDbDataReader)}(...)", ex);
throw;
}

using (var scope = CreateScope(command))
{
try
{
return executeReader(command, commandBehavior);
return instrumentedMethod(command, commandBehavior);
}
catch (Exception ex) when (scope?.Span.SetExceptionForFilter(ex) ?? false)
catch (Exception ex)
{
// unreachable code
scope?.Span.SetException(ex);
throw;
}
}
Expand All @@ -74,50 +93,77 @@ public static object ExecuteDbDataReader(object @this, int behavior, int opCode,
/// <param name="mdToken">The mdToken of the original method call.</param>
/// <returns>The value returned by the instrumented method.</returns>
[InterceptMethod(
TargetAssembly = "System.Data", // .NET Framework
TargetType = "System.Data.Common.DbCommand",
TargetSignatureTypes = new[] { "System.Threading.Tasks.Task`1<System.Data.Common.DbDataReader>", "System.Data.CommandBehavior", "System.Threading.CancellationToken" },
TargetAssembly = FrameworkAssembly,
TargetType = DbCommand,
TargetSignatureTypes = new[] { "System.Threading.Tasks.Task`1<System.Data.Common.DbDataReader>", CommandBehavior, ClrNames.CancellationToken },
TargetMinimumVersion = Major4,
TargetMaximumVersion = Major4)]
[InterceptMethod(
TargetAssembly = "System.Data.Common", // .NET Core
TargetType = "System.Data.Common.DbCommand",
TargetSignatureTypes = new[] { "System.Threading.Tasks.Task`1<System.Data.Common.DbDataReader>", "System.Data.CommandBehavior", "System.Threading.CancellationToken" },
TargetAssembly = CoreAssembly,
TargetType = DbCommand,
TargetSignatureTypes = new[] { "System.Threading.Tasks.Task`1<System.Data.Common.DbDataReader>", CommandBehavior, ClrNames.CancellationToken },
TargetMinimumVersion = Major4,
TargetMaximumVersion = Major4)]
public static object ExecuteDbDataReaderAsync(object @this, int behavior, object cancellationTokenSource, int opCode, int mdToken)
public static object ExecuteDbDataReaderAsync(
object @this,
int behavior,
object cancellationTokenSource,
int opCode,
int mdToken)
{
var tokenSource = cancellationTokenSource as CancellationTokenSource;
var cancellationToken = tokenSource?.Token ?? CancellationToken.None;

var command = (DbCommand)@this;
var instanceType = command.GetType();
var commandBehavior = (CommandBehavior)behavior;
var callOpCode = (OpCodeValue)opCode;
var callingAssembly = Assembly.GetCallingAssembly();
var dataReaderType = callingAssembly.GetType(DbDataReader);

Func<object, object, object, object> instrumentedMethod = null;

return ExecuteDbDataReaderAsyncInternal(command, commandBehavior, cancellationToken, callOpCode);
try
{
instrumentedMethod =
MethodBuilder<Func<object, object, object, object>>
.Start(Assembly.GetCallingAssembly(), mdToken, opCode, nameof(ExecuteDbDataReaderAsync))
.WithConcreteType(typeof(DbCommand))
.WithParameters(commandBehavior, cancellationToken)
.Build();
}
catch (Exception ex)
{
Log.ErrorException($"Error resolving {DbCommand}.{nameof(ExecuteDbDataReaderAsync)}(...)", ex);
throw;
}

return AsyncHelper.InvokeGenericTaskDelegate(
instanceType,
dataReaderType,
nameof(ExecuteDbDataReaderAsyncInternal),
typeof(AdoNetIntegration),
command,
commandBehavior,
cancellationToken,
instrumentedMethod);
}

private static async Task<object> ExecuteDbDataReaderAsyncInternal(
private static async Task<T> ExecuteDbDataReaderAsyncInternal<T>(
DbCommand command,
CommandBehavior behavior,
CancellationToken cancellationToken,
OpCodeValue callOpCode)
Func<object, object, object, object> originalMethod)
{
var executeReader = Emit.DynamicMethodBuilder<Func<object, CommandBehavior, CancellationToken, Task<object>>>
.GetOrCreateMethodCallDelegate(
typeof(DbCommand),
"ExecuteDbDataReaderAsync",
callOpCode);

using (var scope = CreateScope(command))
{
try
{
return await executeReader(command, behavior, cancellationToken).ConfigureAwait(false);
var awaitable = (Task<T>)originalMethod(command, behavior, cancellationToken);
return await awaitable.ConfigureAwait(false);
}
catch (Exception ex) when (scope?.Span.SetExceptionForFilter(ex) ?? false)
catch (Exception ex)
{
// unreachable code
scope?.Span.SetException(ex);
throw;
}
}
Expand Down

0 comments on commit dbd1945

Please sign in to comment.