From 7ef3464f126072830a8806477f3381d14b8a6b08 Mon Sep 17 00:00:00 2001 From: Caleb Doxsey Date: Fri, 14 Sep 2018 10:58:26 -0400 Subject: [PATCH 1/6] wip --- Datadog.Trace.sln | 15 +++++ devenv.bat | 2 +- integrations.json | 23 ++++++- samples/Samples.RedisCore/Program.cs | 16 +++++ .../Properties/launchSettings.json | 7 ++ .../Samples.RedisCore.csproj | 18 +++++ .../Integrations/StackExchangeRedis.cs | 66 +++++++++++++++++++ .../clr_helpers.cpp | 13 +++- .../clr_helpers.h | 14 ++-- .../cor_profiler.cpp | 12 +++- .../integration_loader.cpp | 31 ++++++++- .../metadata_builder.h | 4 +- .../module_metadata.h | 4 +- .../clr_helper_test.cpp | 6 +- .../metadata_builder_test.cpp | 4 +- 15 files changed, 209 insertions(+), 26 deletions(-) create mode 100644 samples/Samples.RedisCore/Program.cs create mode 100644 samples/Samples.RedisCore/Properties/launchSettings.json create mode 100644 samples/Samples.RedisCore/Samples.RedisCore.csproj create mode 100644 src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchangeRedis.cs diff --git a/Datadog.Trace.sln b/Datadog.Trace.sln index 0211f734b895..ef590997e219 100644 --- a/Datadog.Trace.sln +++ b/Datadog.Trace.sln @@ -114,6 +114,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.SqlServer", "sample {C0C8D381-D6B9-4C76-9428-F40F2FA93A9A} = {C0C8D381-D6B9-4C76-9428-F40F2FA93A9A} EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.RedisCore", "samples\Samples.RedisCore\Samples.RedisCore.csproj", "{F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -364,6 +366,18 @@ Global {086FF8A0-9CEE-470A-9751-78B0F1340649}.Release|x64.Build.0 = Release|x64 {086FF8A0-9CEE-470A-9751-78B0F1340649}.Release|x86.ActiveCfg = Release|x86 {086FF8A0-9CEE-470A-9751-78B0F1340649}.Release|x86.Build.0 = Release|x86 + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Debug|x64.ActiveCfg = Debug|Any CPU + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Debug|x64.Build.0 = Debug|Any CPU + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Debug|x86.ActiveCfg = Debug|Any CPU + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Debug|x86.Build.0 = Debug|Any CPU + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Release|Any CPU.Build.0 = Release|Any CPU + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Release|x64.ActiveCfg = Release|Any CPU + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Release|x64.Build.0 = Release|Any CPU + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Release|x86.ActiveCfg = Release|Any CPU + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -390,6 +404,7 @@ Global {BC7B3216-9C37-4570-8A4A-5D716AB98E9C} = {AA6F5582-3B71-49AC-AA39-8F7815AC46BE} {0D546118-B70A-44D0-B675-39EDB99FCEEE} = {8CEC2042-F11C-49F5-A674-2355793B600A} {086FF8A0-9CEE-470A-9751-78B0F1340649} = {AA6F5582-3B71-49AC-AA39-8F7815AC46BE} + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A} = {AA6F5582-3B71-49AC-AA39-8F7815AC46BE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {160A1D00-1F5B-40F8-A155-621B4459D78F} diff --git a/devenv.bat b/devenv.bat index dd659b42b5e8..9003e330c9a3 100644 --- a/devenv.bat +++ b/devenv.bat @@ -50,7 +50,7 @@ SET CORECLR_PROFILER={846F5F1C-F9AE-4B07-969E-05C26BC060D8} SET CORECLR_PROFILER_PATH=%~dp0\src\Datadog.Trace.ClrProfiler.Native\bin\%profiler_configuration%\%profiler_platform%\Datadog.Trace.ClrProfiler.Native.dll rem Limit profiling to these processes only -SET DD_PROFILER_PROCESSES=w3wp.exe;iisexpress.exe;Samples.AspNetCoreMvc2.exe;dotnet.exe;Samples.ConsoleFramework.exe;Samples.ConsoleCore.exe;Samples.SqlServer.exe +SET DD_PROFILER_PROCESSES=w3wp.exe;iisexpress.exe;Samples.AspNetCoreMvc2.exe;dotnet.exe;Samples.ConsoleFramework.exe;Samples.ConsoleCore.exe;Samples.SqlServer.exe;Samples.RedisCore.exe rem Set location of integration definitions SET DD_INTEGRATIONS=%~dp0\integrations.json;%~dp0\test-integrations.json diff --git a/integrations.json b/integrations.json index 6d2a0bfc9475..36daad8b8b28 100644 --- a/integrations.json +++ b/integrations.json @@ -97,7 +97,7 @@ { "name": "SqlServer", "method_replacements": [ - { + { "caller": { "assembly": "System.Data", "type": "System.Data.SqlClient.SqlCommand" @@ -131,5 +131,26 @@ } } ] + }, + { + "name": "StackExchangeRedis", + "method_replacements": [ + { + "caller": { + "assembly": "StackExchange.Redis" + }, + "target": { + "assembly": "StackExchange.Redis", + "type": "StackExchange.Redis.ConnectionMultiplexer", + "method": "ExecuteSyncImpl" + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=0.2.2.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.StackExchangeRedis", + "method": "ExecuteSyncImpl", + "signature": "00 04 1C 1C 1C 1C 1C" + } + } + ] } ] diff --git a/samples/Samples.RedisCore/Program.cs b/samples/Samples.RedisCore/Program.cs new file mode 100644 index 000000000000..df0dacefc701 --- /dev/null +++ b/samples/Samples.RedisCore/Program.cs @@ -0,0 +1,16 @@ +using System; +using StackExchange.Redis; + +namespace Samples.RedisCore +{ + class Program + { + static void Main(string[] args) + { + var redis = ConnectionMultiplexer.Connect("localhost"); + redis.GetDatabase().StringSet("KEY", "VALUE"); + var value = redis.GetDatabase().StringGetAsync("KEY").Result; + Console.WriteLine(value.ToString()); + } + } +} diff --git a/samples/Samples.RedisCore/Properties/launchSettings.json b/samples/Samples.RedisCore/Properties/launchSettings.json new file mode 100644 index 000000000000..5d84b08be7cf --- /dev/null +++ b/samples/Samples.RedisCore/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "Samples.RedisCore": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/samples/Samples.RedisCore/Samples.RedisCore.csproj b/samples/Samples.RedisCore/Samples.RedisCore.csproj new file mode 100644 index 000000000000..ba8d39f87baa --- /dev/null +++ b/samples/Samples.RedisCore/Samples.RedisCore.csproj @@ -0,0 +1,18 @@ + + + + Exe + net461;net47;netcoreapp2.0 + AnyCPU;x64;x86 + win-x64;win-x86;linux-x64 + + + + + + + + + + + diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchangeRedis.cs b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchangeRedis.cs new file mode 100644 index 000000000000..806a3cc6049a --- /dev/null +++ b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchangeRedis.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Datadog.Trace.ClrProfiler.Integrations +{ + /// + /// Wraps calls to the Stack Exchange redis library. + /// + public static class StackExchangeRedis + { + private static ConcurrentDictionary _executeSyncImplBoundMethods = new ConcurrentDictionary(); + + /// + /// Execute a synchronous redis operation. + /// + /// The multiplexer running the command + /// The message to send to redis + /// The processor to handle the reuslt + /// The server to call + /// The result + public static object ExecuteSyncImpl(object multiplexer, object message, object processor, object server) + { + var resultType = GetResultTypeFromProcessor(processor); + dynamic originalMethod; + if (!_executeSyncImplBoundMethods.TryGetValue(resultType, out originalMethod)) + { + var method = multiplexer.GetType().GetMethod("ExecuteSyncImpl", BindingFlags.Instance | BindingFlags.NonPublic); + var boundMethod = method.MakeGenericMethod(resultType); + var methodParams = boundMethod.GetParameters(); + var funcType = typeof(Func<,,,,>).MakeGenericType(boundMethod.ReturnType, boundMethod.DeclaringType, methodParams[0].ParameterType, methodParams[1].ParameterType, methodParams[2].ParameterType); + + originalMethod = boundMethod.CreateDelegate(funcType); + + _executeSyncImplBoundMethods[resultType] = originalMethod; + } + + File.WriteAllLines( + @"C:\Temp\stack-exchange-redis.trace", + new string[] { $"multiplexer: {multiplexer}", $"message: {message}", $"processor: {processor}", $"server: {server}" }); + return originalMethod(multiplexer, message, processor, server); + } + + private static Type GetResultTypeFromProcessor(object processor) + { + var type = processor.GetType(); + while (type != null) + { + if (type.GenericTypeArguments.Length > 0) + { + return type.GenericTypeArguments[0]; + } + + type = type.BaseType; + } + + return typeof(object); + } + } +} diff --git a/src/Datadog.Trace.ClrProfiler.Native/clr_helpers.cpp b/src/Datadog.Trace.ClrProfiler.Native/clr_helpers.cpp index 9b2423d5dfa7..c7de0b8f8e59 100644 --- a/src/Datadog.Trace.ClrProfiler.Native/clr_helpers.cpp +++ b/src/Datadog.Trace.ClrProfiler.Native/clr_helpers.cpp @@ -52,7 +52,7 @@ std::wstring GetAssemblyName( return name.substr(0, name_len - 1); } -FunctionInfo GetFunctionInfo(const ComPtr& metadata_import, +FunctionInfo GetFunctionInfo(const ComPtr& metadata_import, const mdToken& token) { mdToken parent_token = mdTokenNil; std::wstring function_name(kNameMaxSize, 0); @@ -76,6 +76,15 @@ FunctionInfo GetFunctionInfo(const ComPtr& metadata_import, &raw_signature, &raw_signature_len, nullptr, nullptr, nullptr, nullptr, nullptr); break; + case mdtMethodSpec: + hr = metadata_import->GetMethodSpecProps(token, &parent_token, nullptr, + nullptr); + if (!FAILED(hr)) { + return GetFunctionInfo(metadata_import, parent_token); + } + default: + LOG_APPEND(L"[trace::GetFunctionInfo] unknown token type:" + << HEX(TypeFromToken(token))); } if (FAILED(hr) || function_name_len == 0) { return {}; @@ -109,7 +118,7 @@ ModuleInfo GetModuleInfo(ICorProfilerInfo3* info, const ModuleID& module_id) { module_flags}; } -TypeInfo GetTypeInfo(const ComPtr& metadata_import, +TypeInfo GetTypeInfo(const ComPtr& metadata_import, const mdToken& token) { mdToken parent_token = mdTokenNil; std::wstring type_name(kNameMaxSize, 0); diff --git a/src/Datadog.Trace.ClrProfiler.Native/clr_helpers.h b/src/Datadog.Trace.ClrProfiler.Native/clr_helpers.h index b6f4ded9ffec..d112b615799d 100644 --- a/src/Datadog.Trace.ClrProfiler.Native/clr_helpers.h +++ b/src/Datadog.Trace.ClrProfiler.Native/clr_helpers.h @@ -98,7 +98,7 @@ class EnumeratorIterator { }; static Enumerator EnumTypeDefs( - const ComPtr& metadata_import) { + const ComPtr& metadata_import) { return Enumerator( [metadata_import](HCORENUM* ptr, mdTypeDef arr[], ULONG max, ULONG* cnt) -> HRESULT { @@ -110,7 +110,7 @@ static Enumerator EnumTypeDefs( } static Enumerator EnumTypeRefs( - const ComPtr& metadata_import) { + const ComPtr& metadata_import) { return Enumerator( [metadata_import](HCORENUM* ptr, mdTypeRef arr[], ULONG max, ULONG* cnt) -> HRESULT { @@ -122,7 +122,7 @@ static Enumerator EnumTypeRefs( } static Enumerator EnumMethods( - const ComPtr& metadata_import, + const ComPtr& metadata_import, const mdToken& parent_token) { return Enumerator( [metadata_import, parent_token](HCORENUM* ptr, mdMethodDef arr[], @@ -135,7 +135,7 @@ static Enumerator EnumMethods( } static Enumerator EnumMemberRefs( - const ComPtr& metadata_import, + const ComPtr& metadata_import, const mdToken& parent_token) { return Enumerator( [metadata_import, parent_token](HCORENUM* ptr, mdMemberRef arr[], @@ -149,7 +149,7 @@ static Enumerator EnumMemberRefs( } static Enumerator EnumModuleRefs( - const ComPtr& metadata_import) { + const ComPtr& metadata_import) { return Enumerator( [metadata_import](HCORENUM* ptr, mdModuleRef arr[], ULONG max, ULONG* cnt) -> HRESULT { @@ -240,12 +240,12 @@ std::wstring GetAssemblyName( const ComPtr& assembly_import, const mdAssemblyRef& assembly_ref); -FunctionInfo GetFunctionInfo(const ComPtr& metadata_import, +FunctionInfo GetFunctionInfo(const ComPtr& metadata_import, const mdToken& token); ModuleInfo GetModuleInfo(ICorProfilerInfo3* info, const ModuleID& module_id); -TypeInfo GetTypeInfo(const ComPtr& metadata_import, +TypeInfo GetTypeInfo(const ComPtr& metadata_import, const mdToken& token); mdAssemblyRef FindAssemblyRef( diff --git a/src/Datadog.Trace.ClrProfiler.Native/cor_profiler.cpp b/src/Datadog.Trace.ClrProfiler.Native/cor_profiler.cpp index f05ea3fa7068..97b2bd512349 100644 --- a/src/Datadog.Trace.ClrProfiler.Native/cor_profiler.cpp +++ b/src/Datadog.Trace.ClrProfiler.Native/cor_profiler.cpp @@ -35,7 +35,8 @@ CorProfiler::Initialize(IUnknown* cor_profiler_info_unknown) { return E_FAIL; } - const auto allowed_process_names = GetEnvironmentValues(kProcessesEnvironmentName); + const auto allowed_process_names = + GetEnvironmentValues(kProcessesEnvironmentName); if (allowed_process_names.empty()) { LOG_APPEND( @@ -105,13 +106,13 @@ HRESULT STDMETHODCALLTYPE CorProfiler::ModuleLoadFinished(ModuleID module_id, ComPtr metadata_interfaces; auto hr = this->info_->GetModuleMetaData(module_id, ofRead | ofWrite, - IID_IMetaDataImport, + IID_IMetaDataImport2, metadata_interfaces.GetAddressOf()); LOG_IFFAILEDRET(hr, L"Failed to get metadata interface."); const auto metadata_import = - metadata_interfaces.As(IID_IMetaDataImport); + metadata_interfaces.As(IID_IMetaDataImport); const auto metadata_emit = metadata_interfaces.As(IID_IMetaDataEmit); const auto assembly_import = metadata_interfaces.As( @@ -246,6 +247,11 @@ HRESULT STDMETHODCALLTYPE CorProfiler::JITCompilationStarted( continue; } + if (target.name.find(L"Execute") != std::wstring::npos) { + LOG_APPEND(L">>> method invocation: " << target.type.name << L"," + << target.name); + } + // make sure the type and method names match if (method_replacement.target_method.type_name != target.type.name || method_replacement.target_method.method_name != target.name) { diff --git a/src/Datadog.Trace.ClrProfiler.Native/integration_loader.cpp b/src/Datadog.Trace.ClrProfiler.Native/integration_loader.cpp index e77dbd1f8659..bf013c813089 100644 --- a/src/Datadog.Trace.ClrProfiler.Native/integration_loader.cpp +++ b/src/Datadog.Trace.ClrProfiler.Native/integration_loader.cpp @@ -110,14 +110,39 @@ MethodReference MethodReferenceFromJson(const json::value_type& src) { std::wstring assembly = converter.from_bytes(src.value("assembly", "")); std::wstring type = converter.from_bytes(src.value("type", "")); std::wstring method = converter.from_bytes(src.value("method", "")); - auto arr = src.value("signature", json::array()); + auto raw_signature = src.value("signature", json::array()); std::vector signature; - if (arr.is_array()) { - for (auto& el : arr) { + if (raw_signature.is_array()) { + for (auto& el : raw_signature) { if (el.is_number_unsigned()) { signature.push_back(BYTE(el.get())); } } + } else if (raw_signature.is_string()) { + std::string str = raw_signature; + bool flip = false; + char prev = 0; + for (auto& c : str) { + BYTE b = 0; + if ('0' <= c && c <= '9') { + b = c - '0'; + } else if ('a' <= c && c <= 'f') { + b = c - 'a' + 10; + } else if ('A' <= c && c <= 'F') { + b = c - 'A' + 10; + } else { + // skip any non-hex character + continue; + } + if (flip) { + signature.push_back((prev << 4) + b); + } + flip = !flip; + prev = b; + } + for (auto& el : signature) { + LOG_APPEND(L"SIGNATURE " << method << L" " << HEX(el)); + } } return MethodReference(assembly, type, method, signature); } diff --git a/src/Datadog.Trace.ClrProfiler.Native/metadata_builder.h b/src/Datadog.Trace.ClrProfiler.Native/metadata_builder.h index b9165eb9eca3..b29e92d1f488 100644 --- a/src/Datadog.Trace.ClrProfiler.Native/metadata_builder.h +++ b/src/Datadog.Trace.ClrProfiler.Native/metadata_builder.h @@ -11,7 +11,7 @@ class MetadataBuilder { private: ModuleMetadata& metadata_; const mdModule module_ = mdModuleNil; - const ComPtr metadata_import_{}; + const ComPtr metadata_import_{}; const ComPtr metadata_emit_{}; const ComPtr assembly_import_{}; const ComPtr assembly_emit_{}; @@ -21,7 +21,7 @@ class MetadataBuilder { public: MetadataBuilder(ModuleMetadata& metadata, const mdModule module, - ComPtr metadata_import, + ComPtr metadata_import, ComPtr metadata_emit, ComPtr assembly_import, ComPtr assembly_emit) diff --git a/src/Datadog.Trace.ClrProfiler.Native/module_metadata.h b/src/Datadog.Trace.ClrProfiler.Native/module_metadata.h index 0afe96fc0a96..b0a98ea5a9fa 100644 --- a/src/Datadog.Trace.ClrProfiler.Native/module_metadata.h +++ b/src/Datadog.Trace.ClrProfiler.Native/module_metadata.h @@ -16,11 +16,11 @@ class ModuleMetadata { std::unordered_map wrapper_parent_type{}; public: - const ComPtr metadata_import{}; + const ComPtr metadata_import{}; std::wstring assemblyName = L""; std::vector integrations = {}; - ModuleMetadata(ComPtr metadata_import, + ModuleMetadata(ComPtr metadata_import, std::wstring assembly_name, std::vector integrations) : metadata_import(std::move(metadata_import)), diff --git a/test/Datadog.Trace.ClrProfiler.Native.Tests/clr_helper_test.cpp b/test/Datadog.Trace.ClrProfiler.Native.Tests/clr_helper_test.cpp index 375d55fbdacf..d6ac31ee4340 100644 --- a/test/Datadog.Trace.ClrProfiler.Native.Tests/clr_helper_test.cpp +++ b/test/Datadog.Trace.ClrProfiler.Native.Tests/clr_helper_test.cpp @@ -7,7 +7,7 @@ using namespace trace; class DISABLED_CLRHelperTest : public ::testing::Test { protected: IMetaDataDispenser* metadata_dispenser_; - ComPtr metadata_import_; + ComPtr metadata_import_; ComPtr assembly_import_; void SetUp() override { @@ -36,12 +36,12 @@ class DISABLED_CLRHelperTest : public ::testing::Test { ComPtr metadataInterfaces; hr = metadata_dispenser_->OpenScope(L"Samples.ExampleLibrary.dll", - ofReadWriteMask, IID_IMetaDataImport, + ofReadWriteMask, IID_IMetaDataImport2, metadataInterfaces.GetAddressOf()); ASSERT_TRUE(SUCCEEDED(hr)); metadata_import_ = - metadataInterfaces.As(IID_IMetaDataImport); + metadataInterfaces.As(IID_IMetaDataImport2); assembly_import_ = metadataInterfaces.As( IID_IMetaDataAssemblyImport); } diff --git a/test/Datadog.Trace.ClrProfiler.Native.Tests/metadata_builder_test.cpp b/test/Datadog.Trace.ClrProfiler.Native.Tests/metadata_builder_test.cpp index 8e61ef2180b3..30ac44b3cfe1 100644 --- a/test/Datadog.Trace.ClrProfiler.Native.Tests/metadata_builder_test.cpp +++ b/test/Datadog.Trace.ClrProfiler.Native.Tests/metadata_builder_test.cpp @@ -42,12 +42,12 @@ class MetadataBuilderTest : public ::testing::Test { ComPtr metadataInterfaces; hr = metadata_dispenser_->OpenScope(L"Samples.ExampleLibrary.dll", - ofReadWriteMask, IID_IMetaDataImport, + ofReadWriteMask, IID_IMetaDataImport2, metadataInterfaces.GetAddressOf()); ASSERT_TRUE(SUCCEEDED(hr)); const auto metadataImport = - metadataInterfaces.As(IID_IMetaDataImport); + metadataInterfaces.As(IID_IMetaDataImport2); const auto metadataEmit = metadataInterfaces.As(IID_IMetaDataEmit); const auto assemblyImport = metadataInterfaces.As( From 188376e4c710a6ba213473722925695abfa7522f Mon Sep 17 00:00:00 2001 From: Caleb Doxsey Date: Fri, 14 Sep 2018 13:09:34 -0400 Subject: [PATCH 2/6] use explicity type parameters --- .../DynamicMethodBuilder.cs | 46 +++++++++++++++---- .../Integrations/AspNetCoreMvc2Integration.cs | 6 +-- .../Integrations/SqlServer.cs | 4 +- .../Integrations/StackExchangeRedis.cs | 24 +++++----- 4 files changed, 54 insertions(+), 26 deletions(-) diff --git a/src/Datadog.Trace.ClrProfiler.Managed/DynamicMethodBuilder.cs b/src/Datadog.Trace.ClrProfiler.Managed/DynamicMethodBuilder.cs index ceca9cd9f504..98a9e661f6db 100644 --- a/src/Datadog.Trace.ClrProfiler.Managed/DynamicMethodBuilder.cs +++ b/src/Datadog.Trace.ClrProfiler.Managed/DynamicMethodBuilder.cs @@ -17,12 +17,14 @@ public static class DynamicMethodBuilder /// A type with the signature of the method to call. /// The that contains the method. /// The name of the method. - /// true if the method is static, false otherwise. + /// optional types for the method parameters + /// optional generic type arguments for a generic method /// A that can be used to execute the dynamic method. public static TDelegate CreateMethodCallDelegate( Type type, string methodName, - bool isStatic) + Type[] methodParameterTypes = null, + Type[] methodGenericArguments = null) where TDelegate : Delegate { Type delegateType = typeof(TDelegate); @@ -48,13 +50,36 @@ public static class DynamicMethodBuilder } // find any method that matches by name and parameter types - MethodInfo methodInfo = type.GetMethod( - methodName, - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static, - null, - isStatic ? parameterTypes : parameterTypes.Skip(1).ToArray(), - null); + var methods = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + methods = methods.Where(m => m.Name == methodName).ToArray(); + if (methodParameterTypes != null) + { + methods = methods.Where(m => + { + 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]; + // 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(); + } + + MethodInfo methodInfo = methods.FirstOrDefault(); if (methodInfo == null) { // method not found @@ -62,6 +87,11 @@ public static class DynamicMethodBuilder return null; } + if (methodGenericArguments != null) + { + methodInfo = methodInfo.MakeGenericMethod(methodGenericArguments); + } + var dynamicMethod = new DynamicMethod(methodName, returnType, parameterTypes, type); ILGenerator ilGenerator = dynamicMethod.GetILGenerator(); diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AspNetCoreMvc2Integration.cs b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AspNetCoreMvc2Integration.cs index 98f5b22a6f32..e7dbd6b20e79 100644 --- a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AspNetCoreMvc2Integration.cs +++ b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/AspNetCoreMvc2Integration.cs @@ -86,8 +86,7 @@ public AspNetCoreMvc2Integration(object actionDescriptorObj, object httpContextO _beforeAction = DynamicMethodBuilder.CreateMethodCallDelegate>( type, - "BeforeAction", - isStatic: true); + "BeforeAction"); } } catch @@ -141,8 +140,7 @@ public AspNetCoreMvc2Integration(object actionDescriptorObj, object httpContextO _afterAction = DynamicMethodBuilder.CreateMethodCallDelegate>( type, - "AfterAction", - isStatic: true); + "AfterAction"); } } catch diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/SqlServer.cs b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/SqlServer.cs index 0b31b67e46ab..c672f4aa8cda 100644 --- a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/SqlServer.cs +++ b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/SqlServer.cs @@ -29,7 +29,7 @@ public static object ExecuteReaderWithMethod(dynamic @this, int behavior, string _executeReaderWithMethod = DynamicMethodBuilder.CreateMethodCallDelegate>( command.GetType(), "ExecuteReader", - isStatic: false); + new Type[] { typeof(CommandBehavior), typeof(string) }); } using (var scope = CreateScope(command)) @@ -61,7 +61,7 @@ public static object ExecuteReader(dynamic @this, int behavior) _executeReader = DynamicMethodBuilder.CreateMethodCallDelegate>( command.GetType(), "ExecuteReader", - isStatic: false); + new Type[] { typeof(CommandBehavior) }); } using (var scope = CreateScope(command)) diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchangeRedis.cs b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchangeRedis.cs index 806a3cc6049a..24ff51db615d 100644 --- a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchangeRedis.cs +++ b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchangeRedis.cs @@ -15,7 +15,7 @@ namespace Datadog.Trace.ClrProfiler.Integrations /// public static class StackExchangeRedis { - private static ConcurrentDictionary _executeSyncImplBoundMethods = new ConcurrentDictionary(); + private static ConcurrentDictionary> _executeSyncImplBoundMethods = new ConcurrentDictionary>(); /// /// Execute a synchronous redis operation. @@ -28,22 +28,22 @@ public static class StackExchangeRedis public static object ExecuteSyncImpl(object multiplexer, object message, object processor, object server) { var resultType = GetResultTypeFromProcessor(processor); - dynamic originalMethod; - if (!_executeSyncImplBoundMethods.TryGetValue(resultType, out originalMethod)) + if (!_executeSyncImplBoundMethods.TryGetValue(resultType, out var originalMethod)) { - var method = multiplexer.GetType().GetMethod("ExecuteSyncImpl", BindingFlags.Instance | BindingFlags.NonPublic); - var boundMethod = method.MakeGenericMethod(resultType); - var methodParams = boundMethod.GetParameters(); - var funcType = typeof(Func<,,,,>).MakeGenericType(boundMethod.ReturnType, boundMethod.DeclaringType, methodParams[0].ParameterType, methodParams[1].ParameterType, methodParams[2].ParameterType); - - originalMethod = boundMethod.CreateDelegate(funcType); + 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 serverType = asm.GetType("StackExchange.Redis.ServerEndPoint"); + originalMethod = DynamicMethodBuilder.CreateMethodCallDelegate>( + multiplexerType, + "ExecuteSyncImpl", + new Type[] { messageType, processorType, serverType }, + new Type[] { resultType }); _executeSyncImplBoundMethods[resultType] = originalMethod; } - File.WriteAllLines( - @"C:\Temp\stack-exchange-redis.trace", - new string[] { $"multiplexer: {multiplexer}", $"message: {message}", $"processor: {processor}", $"server: {server}" }); return originalMethod(multiplexer, message, processor, server); } From 67cbd0aa3ca476575e87fc10d6cdf3f99eb79b17 Mon Sep 17 00:00:00 2001 From: Caleb Doxsey Date: Fri, 14 Sep 2018 13:15:45 -0400 Subject: [PATCH 3/6] remove log line --- src/Datadog.Trace.ClrProfiler.Native/integration_loader.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Datadog.Trace.ClrProfiler.Native/integration_loader.cpp b/src/Datadog.Trace.ClrProfiler.Native/integration_loader.cpp index bf013c813089..880a8301a1b7 100644 --- a/src/Datadog.Trace.ClrProfiler.Native/integration_loader.cpp +++ b/src/Datadog.Trace.ClrProfiler.Native/integration_loader.cpp @@ -119,6 +119,7 @@ MethodReference MethodReferenceFromJson(const json::value_type& src) { } } } else if (raw_signature.is_string()) { + // load as a hex string std::string str = raw_signature; bool flip = false; char prev = 0; @@ -140,9 +141,6 @@ MethodReference MethodReferenceFromJson(const json::value_type& src) { flip = !flip; prev = b; } - for (auto& el : signature) { - LOG_APPEND(L"SIGNATURE " << method << L" " << HEX(el)); - } } return MethodReference(assembly, type, method, signature); } From 0e27f653ea44a07d1e5ae73fa331e6ef143fc75a Mon Sep 17 00:00:00 2001 From: Caleb Doxsey Date: Fri, 14 Sep 2018 15:21:16 -0400 Subject: [PATCH 4/6] add async, span tagging --- integrations.json | 16 ++ samples/Samples.RedisCore/Program.cs | 9 +- .../Integrations/StackExchangeRedis.cs | 170 +++++++++++++++++- src/Datadog.Trace/Agent/AgentWriter.cs | 4 +- src/Datadog.Trace/SpanTypes.cs | 5 + src/Datadog.Trace/Tags.cs | 15 ++ 6 files changed, 208 insertions(+), 11 deletions(-) diff --git a/integrations.json b/integrations.json index 36daad8b8b28..05d659a12254 100644 --- a/integrations.json +++ b/integrations.json @@ -150,6 +150,22 @@ "method": "ExecuteSyncImpl", "signature": "00 04 1C 1C 1C 1C 1C" } + }, + { + "caller": { + "assembly": "StackExchange.Redis" + }, + "target": { + "assembly": "StackExchange.Redis", + "type": "StackExchange.Redis.ConnectionMultiplexer", + "method": "ExecuteAsyncImpl" + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=0.2.2.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.StackExchangeRedis", + "method": "ExecuteAsyncImpl", + "signature": "00 05 1C 1C 1C 1C 1C 1C" + } } ] } diff --git a/samples/Samples.RedisCore/Program.cs b/samples/Samples.RedisCore/Program.cs index df0dacefc701..aae0f01433fa 100644 --- a/samples/Samples.RedisCore/Program.cs +++ b/samples/Samples.RedisCore/Program.cs @@ -8,9 +8,12 @@ class Program static void Main(string[] args) { var redis = ConnectionMultiplexer.Connect("localhost"); - redis.GetDatabase().StringSet("KEY", "VALUE"); - var value = redis.GetDatabase().StringGetAsync("KEY").Result; - Console.WriteLine(value.ToString()); + for (var i = 0; i < 100; i++) + { + redis.GetDatabase().StringSet($"KEY-{i}", $"VALUE {i}"); + var value = redis.GetDatabase().StringGetAsync($"KEY-{i}").Result; + Console.WriteLine(value.ToString()); + } } } } diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchangeRedis.cs b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchangeRedis.cs index 24ff51db615d..c75247bc548c 100644 --- a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchangeRedis.cs +++ b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchangeRedis.cs @@ -15,15 +15,26 @@ namespace Datadog.Trace.ClrProfiler.Integrations /// public static class StackExchangeRedis { - private static ConcurrentDictionary> _executeSyncImplBoundMethods = new ConcurrentDictionary>(); + private const string OperationName = "redis.command"; + private const string ServiceName = "redis"; + + private static ConcurrentDictionary> _executeSyncImplBoundMethods = + new ConcurrentDictionary>(); + + private static ConcurrentDictionary> _executeAsyncImplBoundMethods = + new ConcurrentDictionary>(); + + private static Func _getCommandAndKeyMethod; + + private static Func _getConfigurationMethod; /// /// Execute a synchronous redis operation. /// - /// The multiplexer running the command - /// The message to send to redis - /// The processor to handle the reuslt - /// The server to call + /// The connection multiplexer running the command. + /// The message to send to redis. + /// The processor to handle the result. + /// The server to call. /// The result public static object ExecuteSyncImpl(object multiplexer, object message, object processor, object server) { @@ -44,9 +55,156 @@ public static object ExecuteSyncImpl(object multiplexer, object message, object _executeSyncImplBoundMethods[resultType] = originalMethod; } - return originalMethod(multiplexer, message, processor, server); + using (var scope = CreateScope(multiplexer, message, server)) + { + return originalMethod(multiplexer, message, processor, server); + } + } + + /// + /// Execute an asynchronous redis operation. + /// + /// The connection multiplexer running the command. + /// The message to send to redis. + /// The processor to handle the result. + /// The state to use for the task. + /// The server to call. + /// An asynchronous task. + public static object ExecuteAsyncImpl(object multiplexer, object message, object processor, object state, object server) + { + var resultType = GetResultTypeFromProcessor(processor); + if (!_executeAsyncImplBoundMethods.TryGetValue(resultType, out var originalMethod)) + { + 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 stateType = typeof(object); + var serverType = asm.GetType("StackExchange.Redis.ServerEndPoint"); + + originalMethod = DynamicMethodBuilder.CreateMethodCallDelegate>( + multiplexerType, + "ExecuteAsyncImpl", + new Type[] { messageType, processorType, stateType, serverType }, + new Type[] { resultType }); + _executeAsyncImplBoundMethods[resultType] = originalMethod; + } + + using (var scope = CreateScope(multiplexer, message, server, finishOnClose: false)) + { + try + { + var result = originalMethod(multiplexer, message, processor, state, server); + if (result is Task task) + { + task.ContinueWith(t => + { + if (t.IsFaulted) + { + scope.Span.SetException(t.Exception); + scope.Span.Finish(); + } + else if (t.IsCanceled) + { + // abandon the span + } + else + { + scope.Span.Finish(); + } + }); + } + else + { + scope.Span.Finish(); + } + + return result; + } + catch (Exception ex) + { + scope.Span.SetException(ex); + scope.Span.Finish(); + throw; + } + } + } + + private static Scope CreateScope(object multiplexer, object message, object server, bool finishOnClose = true) + { + var scope = Tracer.Instance.StartActive(OperationName, serviceName: ServiceName, finishOnClose: finishOnClose); + + var rawCommand = GetCommandAndKey(multiplexer, message); + var command = rawCommand; + if (command.Contains(" ")) + { + command = command.Substring(0, command.IndexOf(' ')); + } + + scope.Span.Type = SpanTypes.Redis; + scope.Span.ResourceName = command; + scope.Span.SetTag(Tags.RedisRawCommand, rawCommand); + + var config = GetConfiguration(multiplexer); + var host = config; + var port = "6379"; + if (host.Contains(":")) + { + port = host.Substring(host.IndexOf(':') + 1); + host = host.Substring(0, host.IndexOf(':')); + } + + scope.Span.SetTag(Tags.RedisHost, host); + scope.Span.SetTag(Tags.RedisPort, port); + + return scope; } + private static string GetCommandAndKey(object multiplexer, object message) + { + string cmdAndKey = null; + try + { + if (_getCommandAndKeyMethod == null) + { + var asm = multiplexer.GetType().Assembly; + var messageType = asm.GetType("StackExchange.Redis.Message"); + _getCommandAndKeyMethod = DynamicMethodBuilder.CreateMethodCallDelegate>(messageType, "get_CommandAndKey"); + } + + cmdAndKey = _getCommandAndKeyMethod(message); + } + catch + { + } + + return cmdAndKey ?? "COMMAND"; + } + + private static string GetConfiguration(object multiplexer) + { + string config = null; + try + { + if (_getConfigurationMethod == null) + { + _getConfigurationMethod = DynamicMethodBuilder.CreateMethodCallDelegate>(multiplexer.GetType(), "get_Configuration"); + } + + config = _getConfigurationMethod(multiplexer); + } + catch + { + } + + return config ?? "localhost:6379"; + } + + /// + /// Processor is a ResultProcessor<T>. This method returns the type of T. + /// + /// The result processor + /// The generic type private static Type GetResultTypeFromProcessor(object processor) { var type = processor.GetType(); diff --git a/src/Datadog.Trace/Agent/AgentWriter.cs b/src/Datadog.Trace/Agent/AgentWriter.cs index c7fd361240b5..a12a3423e8a0 100644 --- a/src/Datadog.Trace/Agent/AgentWriter.cs +++ b/src/Datadog.Trace/Agent/AgentWriter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -33,7 +33,7 @@ public void WriteTrace(List trace) public async Task FlushAndCloseAsync() { _processExit.SetResult(true); - await Task.WhenAny(_flushTask, Task.Delay(TimeSpan.FromSeconds(2))); + await Task.WhenAny(_flushTask, Task.Delay(TimeSpan.FromSeconds(20))); if (!_flushTask.IsCompleted) { _log.Warn("Could not flush all traces before process exit"); diff --git a/src/Datadog.Trace/SpanTypes.cs b/src/Datadog.Trace/SpanTypes.cs index ceef7c488aaa..8d343374ff36 100644 --- a/src/Datadog.Trace/SpanTypes.cs +++ b/src/Datadog.Trace/SpanTypes.cs @@ -5,6 +5,11 @@ namespace Datadog.Trace /// public static class SpanTypes { + /// + /// The span type for a redis database. + /// + public const string Redis = "redis"; + /// /// The span type for a sql database. /// diff --git a/src/Datadog.Trace/Tags.cs b/src/Datadog.Trace/Tags.cs index af12427caa1c..8cf8330969fa 100644 --- a/src/Datadog.Trace/Tags.cs +++ b/src/Datadog.Trace/Tags.cs @@ -69,5 +69,20 @@ public static class Tags /// The MVC or Web API action name. /// public const string AspNetAction = "aspnet.action"; + + /// + /// The Redis server hostname. + /// + public const string RedisHost = "out.host"; + + /// + /// The Redis server port. + /// + public const string RedisPort = "out.port"; + + /// + /// The raw command sent to redis. + /// + public const string RedisRawCommand = "redis.raw_command"; } } From 948865444951d34ee1d3d8b3d54165f67180c0e1 Mon Sep 17 00:00:00 2001 From: Caleb Doxsey Date: Fri, 14 Sep 2018 16:14:53 -0400 Subject: [PATCH 5/6] add integration test for redis --- Datadog.Trace.sln | 19 ++++---- .../Samples.RedisCore.csproj | 8 ++++ .../StackExchangeRedisTests.cs | 44 +++++++++++++++++++ 3 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs diff --git a/Datadog.Trace.sln b/Datadog.Trace.sln index ef590997e219..26a2a5a6ec36 100644 --- a/Datadog.Trace.sln +++ b/Datadog.Trace.sln @@ -107,6 +107,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Datadog.Trace.ClrProfiler.I {BC7B3216-9C37-4570-8A4A-5D716AB98E9C} = {BC7B3216-9C37-4570-8A4A-5D716AB98E9C} {C0C8D381-D6B9-4C76-9428-F40F2FA93A9A} = {C0C8D381-D6B9-4C76-9428-F40F2FA93A9A} {086FF8A0-9CEE-470A-9751-78B0F1340649} = {086FF8A0-9CEE-470A-9751-78B0F1340649} + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A} = {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A} EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.SqlServer", "samples\Samples.SqlServer\Samples.SqlServer.csproj", "{086FF8A0-9CEE-470A-9751-78B0F1340649}" @@ -114,7 +115,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.SqlServer", "sample {C0C8D381-D6B9-4C76-9428-F40F2FA93A9A} = {C0C8D381-D6B9-4C76-9428-F40F2FA93A9A} EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.RedisCore", "samples\Samples.RedisCore\Samples.RedisCore.csproj", "{F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.RedisCore", "samples\Samples.RedisCore\Samples.RedisCore.csproj", "{F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -368,16 +369,16 @@ Global {086FF8A0-9CEE-470A-9751-78B0F1340649}.Release|x86.Build.0 = Release|x86 {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Debug|x64.ActiveCfg = Debug|Any CPU - {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Debug|x64.Build.0 = Debug|Any CPU - {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Debug|x86.ActiveCfg = Debug|Any CPU - {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Debug|x86.Build.0 = Debug|Any CPU + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Debug|x64.ActiveCfg = Debug|x64 + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Debug|x64.Build.0 = Debug|x64 + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Debug|x86.ActiveCfg = Debug|x86 + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Debug|x86.Build.0 = Debug|x86 {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Release|Any CPU.ActiveCfg = Release|Any CPU {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Release|Any CPU.Build.0 = Release|Any CPU - {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Release|x64.ActiveCfg = Release|Any CPU - {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Release|x64.Build.0 = Release|Any CPU - {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Release|x86.ActiveCfg = Release|Any CPU - {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Release|x86.Build.0 = Release|Any CPU + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Release|x64.ActiveCfg = Release|x64 + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Release|x64.Build.0 = Release|x64 + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Release|x86.ActiveCfg = Release|x86 + {F5B27CC4-1DF6-4ECD-A4FD-8200152F9A5A}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/samples/Samples.RedisCore/Samples.RedisCore.csproj b/samples/Samples.RedisCore/Samples.RedisCore.csproj index ba8d39f87baa..234948927e93 100644 --- a/samples/Samples.RedisCore/Samples.RedisCore.csproj +++ b/samples/Samples.RedisCore/Samples.RedisCore.csproj @@ -7,6 +7,14 @@ win-x64;win-x86;linux-x64 + + netcoreapp2.0 + + + + win-$(Platform) + + diff --git a/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs b/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs new file mode 100644 index 000000000000..b9013099ed34 --- /dev/null +++ b/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs @@ -0,0 +1,44 @@ +using Datadog.Trace.TestHelpers; +using Xunit; +using Xunit.Abstractions; + +#if !NET452 + +namespace Datadog.Trace.ClrProfiler.IntegrationTests +{ + public class StackExchangeRedisTests : TestHelper + { + private const int AgentPort = 9003; + + public StackExchangeRedisTests(ITestOutputHelper output) + : base("RedisCore", output) + { + } + + [Fact] + [Trait("Category", "EndToEnd")] + public void SubmitsTraces() + { + using (var agent = new MockTracerAgent(AgentPort)) + using (ProcessResult processResult = RunSampleAndWaitForExit(AgentPort)) + { + Assert.True(processResult.ExitCode >= 0, $"Process exited with code {processResult.ExitCode}"); + + var spans = agent.GetSpans(); + Assert.True(spans.Count > 0, "expected at least one span"); + foreach (var span in spans) + { + Assert.Equal("redis.command", span.Name); + Assert.Equal("redis", span.Service); + Assert.Equal("redis", span.Type); + Assert.Equal("localhost", span.Tags.Get("out.host")); + Assert.Equal("6379", span.Tags.Get("out.port")); + Assert.True(span.Resource == "GET" || span.Resource == "SET", "resource should be set to command name"); + Assert.NotEmpty(span.Tags.Get("redis.raw_command")); + } + } + } + } +} + +#endif From f8a6206f7eca612d336e0ad524bb2edcd43610c1 Mon Sep 17 00:00:00 2001 From: Caleb Doxsey Date: Mon, 17 Sep 2018 06:18:19 -0700 Subject: [PATCH 6/6] add redis install script --- ci/install-redis.ps1 | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 ci/install-redis.ps1 diff --git a/ci/install-redis.ps1 b/ci/install-redis.ps1 new file mode 100644 index 000000000000..9bb9d9a18fe8 --- /dev/null +++ b/ci/install-redis.ps1 @@ -0,0 +1,20 @@ +[Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls" + +$tmp = [System.IO.Path]::GetTempPath() +$installerUrl = "https://github.com/MicrosoftArchive/redis/releases/download/win-3.2.100/Redis-x64-3.2.100.msi" +$installerPath = Join-Path $tmp "install-redis.msi" +$installerLog = Join-Path $tmp 'install-redis.log' + +if (Test-Path "C:\Program Files\Redis\redis-server.exe") { + Write-Output "redis is already installed" + Exit 0; +} + +if (-not (Test-Path $installerPath)) { + Write-Output "downloading redis to $installerPath" + (New-Object Net.WebClient).DownloadFile($installerUrl, $installerPath) +} + +Write-Output "installing redis" +Start-Process "msiexec.exe" -ArgumentList "/i",$installerPath,"/qn","/norestart","/L",$installerLog -Wait -NoNewWindow +Get-Content $installerLog