diff --git a/deploy/Datadog.Trace.ClrProfiler.WindowsInstaller/Product.wxs b/deploy/Datadog.Trace.ClrProfiler.WindowsInstaller/Product.wxs index a1289a807d9d..b65722c95873 100644 --- a/deploy/Datadog.Trace.ClrProfiler.WindowsInstaller/Product.wxs +++ b/deploy/Datadog.Trace.ClrProfiler.WindowsInstaller/Product.wxs @@ -76,6 +76,11 @@ Source="$(var.ManagedDllPath)\MsgPack.dll" KeyPath="yes" Checksum="yes" Assembly=".net"/> + + + 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()); } } } diff --git a/samples/Samples.AspNetMvc5/Samples.AspNetMvc5.csproj b/samples/Samples.AspNetMvc5/Samples.AspNetMvc5.csproj index ea212a92d85b..52c87085f020 100644 --- a/samples/Samples.AspNetMvc5/Samples.AspNetMvc5.csproj +++ b/samples/Samples.AspNetMvc5/Samples.AspNetMvc5.csproj @@ -52,6 +52,9 @@ ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll + + ..\..\packages\Microsoft.AspNet.WebApi.Tracing.5.2.3\lib\net45\System.Web.Http.Tracing.dll + ..\..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll diff --git a/samples/Samples.AspNetMvc5/packages.config b/samples/Samples.AspNetMvc5/packages.config index 311e58d94b80..64f0b26cf614 100644 --- a/samples/Samples.AspNetMvc5/packages.config +++ b/samples/Samples.AspNetMvc5/packages.config @@ -6,6 +6,7 @@ + diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Datadog.Trace.ClrProfiler.Managed.csproj b/src/Datadog.Trace.ClrProfiler.Managed/Datadog.Trace.ClrProfiler.Managed.csproj index 8a7b93ea4d9b..c3c274b299f9 100644 --- a/src/Datadog.Trace.ClrProfiler.Managed/Datadog.Trace.ClrProfiler.Managed.csproj +++ b/src/Datadog.Trace.ClrProfiler.Managed/Datadog.Trace.ClrProfiler.Managed.csproj @@ -19,8 +19,8 @@ - - + + diff --git a/src/Datadog.Trace.ClrProfiler.Managed/DynamicMethodBuilder.cs b/src/Datadog.Trace.ClrProfiler.Managed/DynamicMethodBuilder.cs index eb1bb8dd805e..93908f2cba2e 100644 --- a/src/Datadog.Trace.ClrProfiler.Managed/DynamicMethodBuilder.cs +++ b/src/Datadog.Trace.ClrProfiler.Managed/DynamicMethodBuilder.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using System.Reflection.Emit; +using Sigil; namespace Datadog.Trace.ClrProfiler { @@ -12,9 +13,9 @@ namespace Datadog.Trace.ClrProfiler /// /// The type of delegate public static class DynamicMethodBuilder - where TDelegate : Delegate + where TDelegate : Delegate { - private static ConcurrentDictionary _cached = new ConcurrentDictionary(new KeyComparer()); + private static readonly ConcurrentDictionary _cached = new ConcurrentDictionary(new KeyComparer()); /// /// Memoizes CreateMethodCallDelegate @@ -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)); } /// /// Creates a simple using that - /// calls a method with the specified name and and parameter types. + /// calls a method with the specified name and parameter types. /// - /// The that contains the method. - /// The name of the method. - /// optional types for the method parameters - /// optional generic type arguments for a generic method + /// The that contains the method to call when the returned delegate is executed.. + /// The name of the method to call when the returned delegate is executed. + /// If not null, use method overload that matches the specified parameters. + /// If not null, use method overload that has the same number of generic arguments. /// A that can be used to execute the dynamic method. public static TDelegate CreateMethodCallDelegate( Type type, @@ -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 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(); @@ -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 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 dynamicMethod = Emit.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 diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AspNetWebApi2Integration.cs b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AspNetWebApi2Integration.cs index 62b324a656f8..a6b240711e5b 100644 --- a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AspNetWebApi2Integration.cs +++ b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AspNetWebApi2Integration.cs @@ -1,3 +1,6 @@ +#if NET45 + +using System; using System.Collections.Generic; using System.Threading; using Datadog.Trace.ExtensionMethods; @@ -11,6 +14,8 @@ 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); + /// /// ExecuteAsync calls the underlying ExecuteAsync and traces the request. /// @@ -18,23 +23,37 @@ public static class AspNetWebApi2Integration /// The controller context for the call /// The cancellation token source /// A task with the result - public static dynamic ExecuteAsync(dynamic @this, dynamic controllerContext, dynamic cancellationTokenSource) + public static object ExecuteAsync(object @this, object controllerContext, object cancellationTokenSource) { + Type controllerType = @this.GetType(); + + // in some cases, ExecuteAsync() is an explicit interface implementation, + // which is not public and has a different name, so try both + var executeAsyncFunc = + DynamicMethodBuilder> + .GetOrCreateMethodCallDelegate(controllerType, "ExecuteAsync") ?? + DynamicMethodBuilder> + .GetOrCreateMethodCallDelegate(controllerType, "System.Web.Http.Controllers.IHttpController.ExecuteAsync"); + using (Scope scope = CreateScope(controllerContext)) { return scope.Span.Trace( - () => @this.ExecuteAsync(controllerContext, ((CancellationTokenSource)cancellationTokenSource).Token), - onComplete: e => - { - if (e != null) - { - scope.Span.SetException(e); - } + () => + { + 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(); - }); + // some fields aren't set till after execution, so repopulate anything missing + UpdateSpan(controllerContext, scope.Span); + scope.Span.Finish(); + }); } } @@ -92,3 +111,5 @@ private static void UpdateSpan(dynamic controllerContext, Span span) } } } + +#endif diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/ConnectionMultiplexer.cs b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/ConnectionMultiplexer.cs index 698cdfb76d4d..b34944ae4537 100644 --- a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/ConnectionMultiplexer.cs +++ b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/ConnectionMultiplexer.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Concurrent; -using System.Linq; using System.Threading.Tasks; namespace Datadog.Trace.ClrProfiler.Integrations.StackExchange.Redis @@ -52,11 +50,11 @@ public static T ExecuteSyncImpl(object multiplexer, object message, object pr /// An asynchronous task. public static object ExecuteAsyncImpl(object multiplexer, object message, object processor, object state, object server) { - var resultType = typeof(Task); + var genericType = typeof(T); var asm = multiplexer.GetType().Assembly; var multiplexerType = asm.GetType("StackExchange.Redis.ConnectionMultiplexer"); var messageType = asm.GetType("StackExchange.Redis.Message"); - var processorType = asm.GetType("StackExchange.Redis.ResultProcessor`1").MakeGenericType(resultType); + var processorType = asm.GetType("StackExchange.Redis.ResultProcessor`1").MakeGenericType(genericType); var stateType = typeof(object); var serverType = asm.GetType("StackExchange.Redis.ServerEndPoint"); @@ -64,7 +62,7 @@ public static object ExecuteAsyncImpl(object multiplexer, object message, obj multiplexerType, "ExecuteAsyncImpl", new Type[] { messageType, processorType, stateType, serverType }, - new Type[] { resultType }); + new Type[] { genericType }); using (var scope = CreateScope(multiplexer, message, server, finishOnClose: false)) { diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/RedisBatch.cs b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/RedisBatch.cs index 5ae2bbf75f34..6cd75bd849a6 100644 --- a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/RedisBatch.cs +++ b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/RedisBatch.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace Datadog.Trace.ClrProfiler.Integrations.StackExchange.Redis @@ -22,18 +19,18 @@ public class RedisBatch : Base /// An asynchronous task. public static object ExecuteAsync(object obj, object message, object processor, object server) { - var resultType = typeof(Task); + var genericType = typeof(T); var asm = obj.GetType().Assembly; var batchType = asm.GetType("StackExchange.Redis.RedisBatch"); var messageType = asm.GetType("StackExchange.Redis.Message"); - var processorType = asm.GetType("StackExchange.Redis.ResultProcessor`1").MakeGenericType(typeof(T)); + var processorType = asm.GetType("StackExchange.Redis.ResultProcessor`1").MakeGenericType(genericType); var serverType = asm.GetType("StackExchange.Redis.ServerEndPoint"); var originalMethod = DynamicMethodBuilder>>.CreateMethodCallDelegate( obj.GetType(), "ExecuteAsync", new Type[] { messageType, processorType, serverType }, - new Type[] { resultType }); + new Type[] { genericType }); // we only trace RedisBatch methods here if (obj.GetType() == batchType) diff --git a/src/Datadog.Trace.ClrProfiler.Native/util.cpp b/src/Datadog.Trace.ClrProfiler.Native/util.cpp index f6d325a40e98..6aa5dd07510d 100644 --- a/src/Datadog.Trace.ClrProfiler.Native/util.cpp +++ b/src/Datadog.Trace.ClrProfiler.Native/util.cpp @@ -36,13 +36,13 @@ WSTRING Trim(const WSTRING &str) { WSTRING trimmed = str; auto lpos = trimmed.find_first_not_of(" \t"_W); - if (lpos != WSTRING::npos) { + if (lpos != WSTRING::npos && lpos > 0) { trimmed = trimmed.substr(lpos); } - auto rpos = trimmed.find_last_of(" \t"_W); + auto rpos = trimmed.find_last_not_of(" \t"_W); if (rpos != WSTRING::npos) { - trimmed = trimmed.substr(0, rpos); + trimmed = trimmed.substr(0, rpos + 1); } return trimmed;