Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow built-in Web API tracing, fix StackExchange.Redis, add IL validation #178

Merged
merged 9 commits into from
Oct 26, 2018
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@
Source="$(var.ManagedDllPath)\MsgPack.dll"
KeyPath="yes" Checksum="yes" Assembly=".net"/>
</Component>
<Component Win64="$(var.Win64)">
<File Id="net45_Sigil.dll"
Source="$(var.ManagedDllPath)\Sigil.dll"
KeyPath="yes" Checksum="yes" Assembly=".net"/>
</Component>
<Component Win64="$(var.Win64)">
<File Id="net45_System.Runtime.InteropServices.RuntimeInformation.dll"
Source="$(var.ManagedDllPath)\System.Runtime.InteropServices.RuntimeInformation.dll"
Expand Down
21 changes: 14 additions & 7 deletions samples/Samples.AspNetMvc5/App_Start/WebApiConfig.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System;
using System.Configuration;
using System.Web.Http;

namespace Samples.AspNetMvc5
Expand All @@ -9,13 +8,21 @@ public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
string enableSystemDiagnosticsTracing = ConfigurationManager.AppSettings["EnableSystemDiagnosticsTracing"] ??
Environment.GetEnvironmentVariable("EnableSystemDiagnosticsTracing");

if (string.Equals(enableSystemDiagnosticsTracing, "true", StringComparison.InvariantCultureIgnoreCase))
{
config.EnableSystemDiagnosticsTracing();
}

config.MapHttpAttributeRoutes();

config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
13 changes: 11 additions & 2 deletions samples/Samples.AspNetMvc5/Controllers/ApiController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;

namespace Samples.AspNetMvc5.Controllers
Expand All @@ -11,14 +12,22 @@ public class ApiController : System.Web.Http.ApiController
public IHttpActionResult Delay(int seconds)
{
Thread.Sleep(TimeSpan.FromSeconds(seconds));
return Ok(seconds);
return Json(seconds);
}

[HttpGet]
[Route("api/delay-async/{seconds}")]
public async Task<IHttpActionResult> DelayAsync(int seconds)
{
await Task.Delay(TimeSpan.FromSeconds(seconds)).ConfigureAwait(false);
return Json(seconds);
}

[HttpGet]
[Route("api/environment")]
public IHttpActionResult Environment()
{
return Ok(System.Environment.GetEnvironmentVariables());
return Json(System.Environment.GetEnvironmentVariables());
}
}
}
3 changes: 3 additions & 0 deletions samples/Samples.AspNetMvc5/Samples.AspNetMvc5.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
<Reference Include="System.Web.Http, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll</HintPath>
</Reference>
<Reference Include="System.Web.Http.Tracing, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.Tracing.5.2.3\lib\net45\System.Web.Http.Tracing.dll</HintPath>
</Reference>
<Reference Include="System.Web.Http.WebHost, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll</HintPath>
</Reference>
Expand Down
1 change: 1 addition & 0 deletions samples/Samples.AspNetMvc5/packages.config
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<package id="Microsoft.AspNet.WebApi" version="5.2.3" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.Client" version="5.2.3" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.Core" version="5.2.3" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.Tracing" version="5.2.3" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.3" targetFramework="net45" />
<package id="Microsoft.AspNet.WebPages" version="3.2.4" targetFramework="net45" />
<package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="1.0.8" targetFramework="net45" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
<ProjectReference Include="..\Datadog.Trace\Datadog.Trace.csproj" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.0'">
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.3.0" />
<ItemGroup>
<PackageReference Include="Sigil" Version="4.7.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net45'">
Expand Down
138 changes: 78 additions & 60 deletions src/Datadog.Trace.ClrProfiler.Managed/DynamicMethodBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Sigil;

namespace Datadog.Trace.ClrProfiler
{
Expand All @@ -12,9 +13,9 @@ namespace Datadog.Trace.ClrProfiler
/// </summary>
/// <typeparam name="TDelegate">The type of delegate</typeparam>
public static class DynamicMethodBuilder<TDelegate>
where TDelegate : Delegate
where TDelegate : Delegate
{
private static ConcurrentDictionary<Key, TDelegate> _cached = new ConcurrentDictionary<Key, TDelegate>(new KeyComparer());
private static readonly ConcurrentDictionary<Key, TDelegate> _cached = new ConcurrentDictionary<Key, TDelegate>(new KeyComparer());

/// <summary>
/// Memoizes CreateMethodCallDelegate
Expand All @@ -31,21 +32,22 @@ public static TDelegate GetOrCreateMethodCallDelegate(
Type[] methodGenericArguments = null)
{
return _cached.GetOrAdd(
new Key(type, methodName, methodParameterTypes, methodGenericArguments),
key =>
{
return CreateMethodCallDelegate(key.Type, key.MethodName, key.MethodParameterTypes, key.MethodGenericArguments);
});
new Key(type, methodName, methodParameterTypes, methodGenericArguments),
key => CreateMethodCallDelegate(
key.Type,
key.MethodName,
key.MethodParameterTypes,
key.MethodGenericArguments));
}

/// <summary>
/// Creates a simple <see cref="DynamicMethod"/> using <see cref="System.Reflection.Emit"/> that
/// calls a method with the specified name and and parameter types.
/// calls a method with the specified name and parameter types.
/// </summary>
/// <param name="type">The <see cref="Type"/> that contains the method.</param>
/// <param name="methodName">The name of the method.</param>
/// <param name="methodParameterTypes">optional types for the method parameters</param>
/// <param name="methodGenericArguments">optional generic type arguments for a generic method</param>
/// <param name="type">The <see cref="Type"/> that contains the method to call when the returned delegate is executed..</param>
/// <param name="methodName">The name of the method to call when the returned delegate is executed.</param>
/// <param name="methodParameterTypes">If not null, use method overload that matches the specified parameters.</param>
/// <param name="methodGenericArguments">If not null, use method overload that has the same number of generic arguments.</param>
/// <returns>A <see cref="Delegate"/> that can be used to execute the dynamic method.</returns>
public static TDelegate CreateMethodCallDelegate(
Type type,
Expand Down Expand Up @@ -76,38 +78,42 @@ public static TDelegate CreateMethodCallDelegate(
}

// find any method that matches by name and parameter types
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
methods = methods.Where(m => m.Name == methodName).ToArray();
IEnumerable<MethodInfo> methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)
.Where(m => m.Name == methodName);

// if methodParameterTypes was specified, check for a method that matches
if (methodParameterTypes != null)
{
methods = methods.Where(m =>
{
var ps = m.GetParameters();
if (ps.Length != methodParameterTypes.Length)
{
return false;
}
{
var ps = m.GetParameters();
if (ps.Length != methodParameterTypes.Length)
{
return false;
}

for (var i = 0; i < ps.Length; i++)
{
var t1 = ps[i].ParameterType;
var t2 = methodParameterTypes[i];
for (var i = 0; i < ps.Length; i++)
{
var t1 = ps[i].ParameterType;
var t2 = methodParameterTypes[i];

// generics can be tricky to compare for type equality
// so we will just check the namespace and name
if (t1.Namespace != t2.Namespace || t1.Name != t2.Name)
{
return false;
}
}
// generics can be tricky to compare for type equality
// so we will just check the namespace and name
if (t1.Namespace != t2.Namespace || t1.Name != t2.Name)
{
return false;
}
}

return true;
}).ToArray();
return true;
});
}

if (methodGenericArguments != null)
{
methods = methods.Where(m => m.IsGenericMethodDefinition).ToArray();
methods = methods.Where(m => m.IsGenericMethodDefinition &&
m.GetGenericArguments().Length == methodGenericArguments.Length)
.ToArray();
}

MethodInfo methodInfo = methods.FirstOrDefault();
Expand All @@ -123,41 +129,53 @@ public static TDelegate CreateMethodCallDelegate(
methodInfo = methodInfo.MakeGenericMethod(methodGenericArguments);
}

var dynamicMethod = new DynamicMethod(methodName, returnType, parameterTypes, type);
ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
Type[] effectiveParameterTypes;

for (int argumentIndex = 0; argumentIndex < parameterTypes.Length; argumentIndex++)
IEnumerable<Type> reflectedParameterTypes = methodInfo.GetParameters()
.Select(p => p.ParameterType);
if (methodInfo.IsStatic)
{
if (argumentIndex == 0)
{
ilGenerator.Emit(OpCodes.Ldarg_0);
}
else if (argumentIndex == 1)
{
ilGenerator.Emit(OpCodes.Ldarg_1);
}
else if (argumentIndex == 2)
{
ilGenerator.Emit(OpCodes.Ldarg_2);
}
else if (argumentIndex == 3)
{
ilGenerator.Emit(OpCodes.Ldarg_3);
}
else if (argumentIndex < 256)
effectiveParameterTypes = reflectedParameterTypes.ToArray();
}
else
{
// for instance methods, insert object's type as first element in array
effectiveParameterTypes = new[] { type }
.Concat(reflectedParameterTypes)
.ToArray();
}

Emit<TDelegate> dynamicMethod = Emit<TDelegate>.NewDynamicMethod(methodInfo.Name);

// load each argument and cast or unbox as necessary
for (ushort argumentIndex = 0; argumentIndex < parameterTypes.Length; argumentIndex++)
{
Type delegateParameterType = parameterTypes[argumentIndex];
Type underlyingParameterType = effectiveParameterTypes[argumentIndex];

dynamicMethod.LoadArgument(argumentIndex);

if (underlyingParameterType.IsValueType && delegateParameterType == typeof(object))
{
ilGenerator.Emit(OpCodes.Ldarg_S, (byte)argumentIndex);
dynamicMethod.UnboxAny(underlyingParameterType);
}
else
else if (underlyingParameterType != delegateParameterType)
{
ilGenerator.Emit(OpCodes.Ldarg, argumentIndex);
dynamicMethod.CastClass(underlyingParameterType);
}
}

ilGenerator.Emit(methodInfo.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, methodInfo);
ilGenerator.Emit(OpCodes.Ret);
if (methodInfo.IsVirtual)
{
dynamicMethod.CallVirtual(methodInfo);
}
else
{
dynamicMethod.Call(methodInfo);
}

return (TDelegate)dynamicMethod.CreateDelegate(delegateType);
dynamicMethod.Return();
return dynamicMethod.CreateDelegate();
}

private struct Key
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#if NET45

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Datadog.Trace.ExtensionMethods;

namespace Datadog.Trace.ClrProfiler.Integrations
Expand All @@ -11,30 +16,47 @@ public static class AspNetWebApi2Integration
{
internal const string OperationName = "aspnet-web-api.request";

private static readonly Type HttpControllerContextType = Type.GetType("System.Web.Http.Controllers.HttpControllerContext, System.Web.Http", throwOnError: false);

/// <summary>
/// ExecuteAsync calls the underlying ExecuteAsync and traces the request.
/// </summary>
/// <param name="this">The Api Controller</param>
/// <param name="controllerContext">The controller context for the call</param>
/// <param name="cancellationTokenSource">The cancellation token source</param>
/// <returns>A task with the result</returns>
public static dynamic ExecuteAsync(dynamic @this, dynamic controllerContext, dynamic cancellationTokenSource)
public static object ExecuteAsync(object @this, object controllerContext, object cancellationTokenSource)
{
using (Scope scope = CreateScope(controllerContext))
{
return scope.Span.Trace(
() => @this.ExecuteAsync(controllerContext, ((CancellationTokenSource)cancellationTokenSource).Token),
onComplete: e =>
{
if (e != null)
{
scope.Span.SetException(e);
}
() =>
{
Type controllerType = @this.GetType();
Type[] parameterTypes = null; // { HttpControllerContextType, typeof(CancellationToken) };

// in some cases, ExecuteAsync() is an explicit interface implementation,
// which is not public and has a different name, so try both
var executeAsyncFunc =
DynamicMethodBuilder<Func<object, object, CancellationToken, object>>
.GetOrCreateMethodCallDelegate(controllerType, "ExecuteAsync", parameterTypes) ??
DynamicMethodBuilder<Func<object, object, CancellationToken, object>>
.GetOrCreateMethodCallDelegate(controllerType, "System.Web.Http.Controllers.IHttpController.ExecuteAsync", parameterTypes);

// some fields aren't set till after execution, so repopulate anything missing
UpdateSpan(controllerContext, scope.Span);
scope.Span.Finish();
});
CancellationToken cancellationToken = ((CancellationTokenSource)cancellationTokenSource).Token;
return executeAsyncFunc(@this, controllerContext, cancellationToken);
},
onComplete: e =>
{
if (e != null)
{
scope.Span.SetException(e);
}

// some fields aren't set till after execution, so repopulate anything missing
UpdateSpan(controllerContext, scope.Span);
scope.Span.Finish();
});
}
}

Expand Down Expand Up @@ -92,3 +114,5 @@ private static void UpdateSpan(dynamic controllerContext, Span span)
}
}
}

#endif
Loading