From d404f4387d0575496cee9f44d7136dcc18716ddf Mon Sep 17 00:00:00 2001 From: Caleb Doxsey Date: Thu, 27 Sep 2018 07:09:06 -0700 Subject: [PATCH 1/6] test async methods --- samples/Samples.RedisCore/Program.cs | 175 ++++++++++++++++++++++++++- 1 file changed, 173 insertions(+), 2 deletions(-) diff --git a/samples/Samples.RedisCore/Program.cs b/samples/Samples.RedisCore/Program.cs index 08001648b86e..0d4719e925d7 100644 --- a/samples/Samples.RedisCore/Program.cs +++ b/samples/Samples.RedisCore/Program.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using ServiceStack.Redis; using StackExchange.Redis; @@ -66,9 +67,9 @@ private static void RunStackExchange(string prefix) prefix += "StackExchange.Redis."; Console.WriteLine($"Testing StackExchange.Redis {prefix}"); - using (var redis = ConnectionMultiplexer.Connect("localhost")) + using (var redis = ConnectionMultiplexer.Connect("localhost,allowAdmin=true")) { - var db = redis.GetDatabase(); + var db = redis.GetDatabase(1); db.StringSet($"{prefix}INCR", "0"); @@ -83,6 +84,171 @@ private static void RunStackExchange(string prefix) { "GET", () => db.StringGet($"{prefix}INCR") }, { "TIME", () => db.Execute("TIME") }, }); + + + var batch = db.CreateBatch(); + var batchPending = RunStackExchangeAsync(prefix + "Batch.", batch); + batch.Execute(); + foreach (var item in batchPending) + { + try + { + Console.WriteLine($"{item.Item1}: {TaskResult(item.Item2)}"); + } + catch (Exception e) + { + while (e.InnerException != null) + { + e = e.InnerException; + } + Console.WriteLine($"{item.Item1}: {e.Message}"); + } + } + + try + { + redis.GetServer("localhost:6379").FlushDatabase(1); + } + catch + { + } + } + } + + private static TupleList RunStackExchangeAsync(string prefix, IDatabaseAsync db) + { + var tasks = new TupleList>() + { + { "DebugObjectAsync", () => db.DebugObjectAsync($"{prefix}DebugObjectAsync") }, + { "ExecuteAsync", () => db.ExecuteAsync("DDCUSTOM", "COMMAND") }, + { "GeoAddAsync", () => db.GeoAddAsync($"{prefix}GeoAddAsync", new GeoEntry(1.5, 2.5, "member")) }, + { "GeoDistanceAsync", () => db.GeoDistanceAsync($"{prefix}GeoDistanceAsync", "member1", "member2") }, + { "GeoHashAsync", () => db.GeoHashAsync($"{prefix}GeoHashAsync", "member") }, + { "GeoPositionAsync", () => db.GeoPositionAsync($"{prefix}GeoPositionAsync", "member") }, + { "GeoRadiusAsync", () => db.GeoRadiusAsync($"{prefix}GeoRadiusAsync", "member", 2.3) }, + { "GeoRemoveAsync", () => db.GeoRemoveAsync($"{prefix}GeoRemoveAsync", "member") }, + { "HashDecrementAsync", () => db.HashDecrementAsync($"{prefix}HashDecrementAsync", "hashfield", 4.5) }, + { "HashDeleteAsync", () => db.HashDeleteAsync($"{prefix}HashDeleteAsync", "hashfield") }, + { "HashExistsAsync", () => db.HashExistsAsync($"{prefix}HashExistsAsync", "hashfield") }, + { "HashGetAllAsync", () => db.HashGetAllAsync($"{prefix}HashGetAllAsync") }, + { "HashIncrementAsync", () => db.HashIncrementAsync($"{prefix}HashIncrementAsync", "hashfield", 1.5) }, + { "HashKeysAsync", () => db.HashKeysAsync($"{prefix}HashKeysAsync") }, + { "HashLengthAsync", () => db.HashLengthAsync($"{prefix}HashLengthAsync") }, + { "HashSetAsync", () => db.HashSetAsync($"{prefix}HashSetAsync", new HashEntry[] { new HashEntry("x", "y") }) }, + { "HashValuesAsync", () => db.HashValuesAsync($"{prefix}HashValuesAsync") }, + { "HyperLogLogAddAsync", () => db.HyperLogLogAddAsync($"{prefix}HyperLogLogAddAsync", "value") }, + { "HyperLogLogLengthAsync", () => db.HyperLogLogLengthAsync($"{prefix}HyperLogLogLengthAsync") }, + { "HyperLogLogMergeAsync", () => db.HyperLogLogMergeAsync($"{prefix}HyperLogLogMergeAsync", new RedisKey[] { "key1", "key2" }) }, + { "IdentifyEndpointAsync", () => db.IdentifyEndpointAsync() }, + { "KeyDeleteAsync", () => db.KeyDeleteAsync("key") }, + { "KeyDumpAsync", () => db.KeyDumpAsync("key") }, + { "KeyExistsAsync", () => db.KeyExistsAsync("key") }, + { "KeyExpireAsync", () => db.KeyExpireAsync("key", DateTime.Now) }, + // () => db.KeyMigrateAsync("key", ???) + { "KeyMoveAsync", () => db.KeyMoveAsync("key", 1) }, + { "KeyPersistAsync", () => db.KeyPersistAsync("key") }, + { "KeyRandomAsync", () => db.KeyRandomAsync() }, + { "KeyRenameAsync", () => db.KeyRenameAsync("key1", "key2") }, + { "KeyRestoreAsync", () => db.KeyRestoreAsync("key", new byte[] { 0,1,2,3,4 }) }, + { "KeyTimeToLiveAsync", () => db.KeyTimeToLiveAsync("key") }, + { "KeyTypeAsync", () => db.KeyTypeAsync("key") }, + { "ListGetByIndexAsync", () => db.ListGetByIndexAsync("listkey", 0) }, + { "ListInsertAfterAsync", () => db.ListInsertAfterAsync("listkey", "value1", "value2") }, + { "ListInsertBeforeAsync", () => db.ListInsertBeforeAsync("listkey", "value1", "value2") }, + { "ListLeftPopAsync", () => db.ListLeftPopAsync("listkey") }, + { "ListLeftPushAsync", () => db.ListLeftPushAsync("listkey", new RedisValue[] { "value3", "value4" }) }, + { "ListLengthAsync", () => db.ListLengthAsync("listkey") }, + { "ListRangeAsync", () => db.ListRangeAsync("listkey") }, + { "ListRemoveAsync", () => db.ListRemoveAsync("listkey", "value3") }, + { "ListRightPopAsync", () => db.ListRightPopAsync("listkey") }, + { "ListRightPopLeftPushAsync", () => db.ListRightPopLeftPushAsync("listkey", "listkey2") }, + { "ListRightPushAsync", () => db.ListRightPushAsync("listkey", new RedisValue[] { "value5", "value6" }) }, + { "ListSetByIndexAsync", () => db.ListSetByIndexAsync("listkey", 0, "value7") }, + { "ListTrimAsync", () => db.ListTrimAsync("listkey", 0, 1) }, + { "LockExtendAsync", () => db.LockExtendAsync("listkey", "value7", new TimeSpan(0, 0, 10)) }, + { "LockQueryAsync", () => db.LockQueryAsync("listkey") }, + { "LockReleaseAsync", () => db.LockReleaseAsync("listkey", "value7") }, + { "LockTakeAsync", () => db.LockTakeAsync("listkey", "value8", new TimeSpan(0, 0, 10)) }, + { "PublishAsync", () => db.PublishAsync(new RedisChannel("channel", RedisChannel.PatternMode.Auto), "somemessage") }, + // { "ScriptEvaluateAsync", () => db.ScriptEvaluateAsync(} + { "SetAddAsync", () => db.SetAddAsync("setkey", "value1") }, + { "SetCombineAndStoreAsync", () => db.SetCombineAndStoreAsync(SetOperation.Union, "setkey", new RedisKey[] { "value2" }) }, + { "SetCombineAsync", () => db.SetCombineAsync(SetOperation.Union, new RedisKey[] { "setkey1", "setkey2"}) }, + { "SetContainsAsync", () => db.SetContainsAsync("setkey", "value1") }, + { "SetLengthAsync", () => db.SetLengthAsync("setkey") }, + { "SetMembersAsync", () => db.SetMembersAsync("setkey") }, + { "SetMoveAsync", () => db.SetMoveAsync("setkey1", "setkey2", "value2") }, + { "SetPopAsync", () => db.SetPopAsync("setkey1") }, + { "SetRandomMemberAsync", () => db.SetRandomMemberAsync("setkey") }, + { "SetRandomMembersAsync", () => db.SetRandomMembersAsync("setkey", 2) }, + { "SetRemoveAsync", () => db.SetRemoveAsync("setkey", "value2") }, + { "SortAndStoreAsync", () => db.SortAndStoreAsync("setkey2", "setkey") }, + { "SortAsync", () => db.SortAsync("setkey") }, + { "SortedSetAddAsync", () => db.SortedSetAddAsync("ssetkey", new SortedSetEntry[] { new SortedSetEntry("value1", 1.5), new SortedSetEntry("value2", 2.5) }) }, + { "SortedSetCombineAndStoreAsync", () => db.SortedSetCombineAndStoreAsync(SetOperation.Union, "ssetkey1", "ssetkey2", "ssetkey3") }, + { "SortedSetDecrementAsync", () => db.SortedSetDecrementAsync("ssetkey", "value1", 1) }, + { "SortedSetIncrementAsync", () => db.SortedSetIncrementAsync("ssetkey", "value2", 1) }, + { "SortedSetLengthAsync", () => db.SortedSetLengthAsync("ssetkey") }, + { "SortedSetLengthByValueAsync", () => db.SortedSetLengthByValueAsync("ssetkey", "value1", "value2") }, + { "SortedSetRangeByRankAsync", () => db.SortedSetRangeByRankAsync("ssetkey") }, + { "SortedSetRangeByRankWithScoresAsync", () => db.SortedSetRangeByRankWithScoresAsync("ssetkey") }, + { "SortedSetRangeByScoreAsync", () => db.SortedSetRangeByScoreAsync("ssetkey") }, + { "SortedSetRangeByScoreWithScoresAsync", () => db.SortedSetRangeByScoreWithScoresAsync("ssetkey") }, + { "SortedSetRangeByValueAsync", () => db.SortedSetRangeByValueAsync("ssetkey") }, + { "SortedSetRankAsync", () => db.SortedSetRankAsync("ssetkey", "value1") }, + { "SortedSetRemoveAsync", () => db.SortedSetRemoveAsync("ssetkey", "value1") }, + { "SortedSetRemoveRangeByRankAsync", () => db.SortedSetRemoveRangeByRankAsync("ssetkey", 0, 1) }, + { "SortedSetRemoveRangeByScoreAsync", () => db.SortedSetRemoveRangeByScoreAsync("ssetkey", 0, 1) }, + { "SortedSetRemoveRangeByValueAsync", () => db.SortedSetRemoveRangeByValueAsync("ssetkey", "value1", "value2") }, + { "SortedSetScoreAsync", () => db.SortedSetScoreAsync("ssestkey", "value1") }, + { "StringAppendAsync", () => db.StringAppendAsync("ssetkey", "value1") }, + { "StringBitCountAsync", () => db.StringBitCountAsync("ssetkey") }, + { "StringBitOperationAsync", () => db.StringBitOperationAsync(Bitwise.And, "ssetkey1", new RedisKey[] { "ssetkey2", "ssetkey3" }) }, + { "StringBitPositionAsync", () => db.StringBitPositionAsync("ssetkey1", true) }, + { "StringDecrementAsync", () => db.StringDecrementAsync("key", 1.45) }, + { "StringGetAsync", () => db.StringGetAsync("key") }, + { "StringGetBitAsync", () => db.StringGetBitAsync("key", 3) }, + { "StringGetRangeAsync", () => db.StringGetRangeAsync("key", 0, 1) }, + { "StringGetSetAsync", () => db.StringGetSetAsync("key", "value") }, + { "StringGetWithExpiryAsync", () => db.StringGetWithExpiryAsync("key") }, + { "StringIncrementAsync", () => db.StringIncrementAsync("key", 1) }, + { "StringLengthAsync", () => db.StringLengthAsync("key") }, + { "StringSetAsync", () => db.StringSetAsync(new KeyValuePair[] { new KeyValuePair("key", "value")}) }, + { "StringSetBitAsync", () => db.StringSetBitAsync("key", 0, true) }, + { "StringSetRangeAsync", () => db.StringSetRangeAsync("key", 3, "value") }, + }; + + var pending = new TupleList(); + foreach (var item in tasks) + { + try + { + pending.Add(item.Item1, item.Item2()); + } + catch + { + } + } + + return pending; + } + + private static object TaskResult(Task task) + { + var taskType = task.GetType(); + + bool isTaskOfT = + taskType.IsGenericType + && taskType.GetGenericTypeDefinition() == typeof(Task<>); + + if (isTaskOfT) + { + return taskType.GetProperty("Result").GetValue(task); + } + else + { + task.Wait(); + return null; } } @@ -90,6 +256,11 @@ private static void RunCommands(TupleList> commands) { foreach (var cmd in commands) { + if (cmd.Item2 == null) + { + continue; + } + object result; try { From cb07d33ee290233f92890e3c5fad8edd5efc2bdd Mon Sep 17 00:00:00 2001 From: Caleb Doxsey Date: Thu, 27 Sep 2018 09:57:59 -0700 Subject: [PATCH 2/6] add batch implementation --- integrations.json | 20 ++- samples/Samples.RedisCore/Program.cs | 7 +- .../Extensions.cs | 7 +- .../Integrations/StackExchange.Redis/Base.cs | 124 ++++++++++++++++++ .../ConnectionMultiplexer.cs} | 95 +------------- .../StackExchange.Redis/RedisBatch.cs | 61 +++++++++ 6 files changed, 219 insertions(+), 95 deletions(-) create mode 100644 src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/Base.cs rename src/Datadog.Trace.ClrProfiler.Managed/Integrations/{StackExchangeRedis.cs => StackExchange.Redis/ConnectionMultiplexer.cs} (54%) create mode 100644 src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/RedisBatch.cs diff --git a/integrations.json b/integrations.json index a9c6a86673b9..49f14c50923c 100644 --- a/integrations.json +++ b/integrations.json @@ -146,7 +146,7 @@ }, "wrapper": { "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=0.3.1.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.StackExchangeRedis", + "type": "Datadog.Trace.ClrProfiler.Integrations.StackExchange.Redis.ConnectionMultiplexer", "method": "ExecuteSyncImpl", "signature": "10 01 04 1E 00 1C 1C 1C 1C" } @@ -162,10 +162,26 @@ }, "wrapper": { "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=0.3.1.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.StackExchangeRedis", + "type": "Datadog.Trace.ClrProfiler.Integrations.StackExchange.Redis.ConnectionMultiplexer", "method": "ExecuteAsyncImpl", "signature": "10 01 05 1C 1C 1C 1C 1C 1C" } + }, + { + "caller": { + "assembly": "StackExchange.Redis" + }, + "target": { + "assembly": "StackExchange.Redis", + "type": "StackExchange.Redis.RedisBase", + "method": "ExecuteAsync" + }, + "wrapper": { + "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=0.3.1.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", + "type": "Datadog.Trace.ClrProfiler.Integrations.StackExchange.Redis.RedisBatch", + "method": "ExecuteAsync", + "signature": "10 01 04 1C 1C 1C 1C 1C" + } } ] }, diff --git a/samples/Samples.RedisCore/Program.cs b/samples/Samples.RedisCore/Program.cs index 0d4719e925d7..c8a091bbcd1d 100644 --- a/samples/Samples.RedisCore/Program.cs +++ b/samples/Samples.RedisCore/Program.cs @@ -225,8 +225,13 @@ private static TupleList RunStackExchangeAsync(string prefix, IDat { pending.Add(item.Item1, item.Item2()); } - catch + catch(Exception e) { + while (e.InnerException != null) + { + e = e.InnerException; + } + Console.WriteLine($"{e.Message}"); } } diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Extensions.cs b/src/Datadog.Trace.ClrProfiler.Managed/Extensions.cs index 9949dd53ece3..8de450e84c40 100644 --- a/src/Datadog.Trace.ClrProfiler.Managed/Extensions.cs +++ b/src/Datadog.Trace.ClrProfiler.Managed/Extensions.cs @@ -92,8 +92,11 @@ private static Task TraceTask(Span span, Task task, Action o { if (t.IsFaulted) { - onComplete(t.Exception); - throw t.Exception; + t.Exception.Handle(e => + { + onComplete(e); + return false; + }); } if (t.IsCompleted) diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/Base.cs b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/Base.cs new file mode 100644 index 000000000000..49b0f1e71b3f --- /dev/null +++ b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/Base.cs @@ -0,0 +1,124 @@ +using System; +using System.Linq; + +namespace Datadog.Trace.ClrProfiler.Integrations.StackExchange.Redis +{ + /// + /// Base class for redis integration. + /// + public class Base + { + private static Func _getCommandAndKeyMethod; + + private static Func _getConfigurationMethod; + + private static Func _getMultiplexer; + + /// + /// Get the configuration for the multiplexer. + /// + /// The multiplexer + /// The configuration + protected static string GetConfiguration(object multiplexer) + { + try + { + if (_getConfigurationMethod == null) + { + _getConfigurationMethod = DynamicMethodBuilder>.CreateMethodCallDelegate(multiplexer.GetType(), "get_Configuration"); + } + + return _getConfigurationMethod(multiplexer); + } + catch + { + return null; + } + } + + /// + /// Get the host and port from the config + /// + /// The config + /// The host and port + protected static Tuple GetHostAndPort(string config) + { + string host = null; + string port = null; + + if (config != null) + { + // config can contain several settings separated by commas: + // hostname:port,name=MyName,keepAlive=180,syncTimeout=10000,abortConnect=False + // split in commas, find the one without '=', split that one on ':' + string[] hostAndPort = config.Split(',') + .FirstOrDefault(p => !p.Contains("=")) + ?.Split(':'); + + if (hostAndPort != null) + { + host = hostAndPort[0]; + } + + // check length because port is optional + if (hostAndPort?.Length > 1) + { + port = hostAndPort[1]; + } + } + + return new Tuple(host, port); + } + + /// + /// Get the raw command. + /// + /// The multiplexer + /// The message + /// The raw command + protected static string GetRawCommand(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"; + } + + /// + /// GetMultiplexer returns the Multiplexer for an object + /// + /// The object + /// The multiplexer + protected static object GetMultiplexer(object obj) + { + object multiplexer = null; + try + { + if (_getMultiplexer == null) + { + _getMultiplexer = DynamicMethodBuilder>.CreateMethodCallDelegate(obj.GetType(), "get_Multiplexer"); + } + + multiplexer = _getMultiplexer(obj); + } + catch + { + } + + return multiplexer; + } + } +} diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchangeRedis.cs b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/ConnectionMultiplexer.cs similarity index 54% rename from src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchangeRedis.cs rename to src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/ConnectionMultiplexer.cs index dc5e5f548914..698cdfb76d4d 100644 --- a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchangeRedis.cs +++ b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/ConnectionMultiplexer.cs @@ -3,17 +3,13 @@ using System.Linq; using System.Threading.Tasks; -namespace Datadog.Trace.ClrProfiler.Integrations +namespace Datadog.Trace.ClrProfiler.Integrations.StackExchange.Redis { /// /// Wraps calls to the Stack Exchange redis library. /// - public static class StackExchangeRedis + public class ConnectionMultiplexer : Base { - private static Func _getCommandAndKeyMethod; - - private static Func _getConfigurationMethod; - /// /// Execute a synchronous redis operation. /// @@ -78,92 +74,11 @@ public static object ExecuteAsyncImpl(object multiplexer, object message, obj private static Scope CreateScope(object multiplexer, object message, object server, bool finishOnClose = true) { - string config = GetConfiguration(multiplexer); - string host = null; - string port = null; - - if (config != null) - { - // config can contain several settings separated by commas: - // hostname:port,name=MyName,keepAlive=180,syncTimeout=10000,abortConnect=False - // split in commas, find the one without '=', split that one on ':' - string[] hostAndPort = config.Split(',') - .FirstOrDefault(p => !p.Contains("=")) - ?.Split(':'); - - if (hostAndPort != null) - { - host = hostAndPort[0]; - } - - // check length because port is optional - if (hostAndPort?.Length > 1) - { - port = hostAndPort[1]; - } - } + var config = GetConfiguration(multiplexer); + var hostAndPort = GetHostAndPort(config); var rawCommand = GetRawCommand(multiplexer, message); - return Redis.CreateScope(host, port, rawCommand, finishOnClose); - } - - private static string GetRawCommand(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) - { - try - { - if (_getConfigurationMethod == null) - { - _getConfigurationMethod = DynamicMethodBuilder>.CreateMethodCallDelegate(multiplexer.GetType(), "get_Configuration"); - } - - return _getConfigurationMethod(multiplexer); - } - catch - { - return null; - } - } - - /// - /// 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(); - while (type != null) - { - if (type.GenericTypeArguments.Length > 0) - { - return type.GenericTypeArguments[0]; - } - - type = type.BaseType; - } - - return typeof(object); + return Datadog.Trace.ClrProfiler.Integrations.Redis.CreateScope(hostAndPort.Item1, hostAndPort.Item2, rawCommand, finishOnClose); } } } diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/RedisBatch.cs b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/RedisBatch.cs new file mode 100644 index 000000000000..5ae2bbf75f34 --- /dev/null +++ b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/RedisBatch.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Datadog.Trace.ClrProfiler.Integrations.StackExchange.Redis +{ + /// + /// Traces StackExchange.Redis.RedisBatch + /// + public class RedisBatch : Base + { + /// + /// Execute an asynchronous redis operation. + /// + /// The result type + /// The this object + /// The message + /// The result processor + /// The server + /// An asynchronous task. + public static object ExecuteAsync(object obj, object message, object processor, object server) + { + var resultType = typeof(Task); + 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 serverType = asm.GetType("StackExchange.Redis.ServerEndPoint"); + + var originalMethod = DynamicMethodBuilder>>.CreateMethodCallDelegate( + obj.GetType(), + "ExecuteAsync", + new Type[] { messageType, processorType, serverType }, + new Type[] { resultType }); + + // we only trace RedisBatch methods here + if (obj.GetType() == batchType) + { + using (var scope = CreateScope(obj, message, server)) + { + return scope.Span.Trace(() => originalMethod(obj, message, processor, server)); + } + } + else + { + return originalMethod(obj, message, processor, server); + } + } + + private static Scope CreateScope(object batch, object message, object server) + { + var multiplexer = GetMultiplexer(batch); + var config = GetConfiguration(multiplexer); + var hostAndPort = GetHostAndPort(config); + var cmd = GetRawCommand(batch, message); + return Datadog.Trace.ClrProfiler.Integrations.Redis.CreateScope(hostAndPort.Item1, hostAndPort.Item2, cmd, finishOnClose: false); + } + } +} From 776c6b4b2456afe9e82aab70ffb250cb05858011 Mon Sep 17 00:00:00 2001 From: Caleb Doxsey Date: Thu, 27 Sep 2018 10:33:30 -0700 Subject: [PATCH 3/6] fix field --- .../Integrations/StackExchange.Redis/Base.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/Base.cs b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/Base.cs index 49b0f1e71b3f..20f4b698e939 100644 --- a/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/Base.cs +++ b/src/Datadog.Trace.ClrProfiler.Managed/Integrations/StackExchange.Redis/Base.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Reflection; namespace Datadog.Trace.ClrProfiler.Integrations.StackExchange.Redis { @@ -12,8 +13,6 @@ public class Base private static Func _getConfigurationMethod; - private static Func _getMultiplexer; - /// /// Get the configuration for the multiplexer. /// @@ -107,12 +106,8 @@ protected static object GetMultiplexer(object obj) object multiplexer = null; try { - if (_getMultiplexer == null) - { - _getMultiplexer = DynamicMethodBuilder>.CreateMethodCallDelegate(obj.GetType(), "get_Multiplexer"); - } - - multiplexer = _getMultiplexer(obj); + var fi = obj.GetType().GetField("multiplexer", BindingFlags.NonPublic | BindingFlags.Instance); + multiplexer = fi.GetValue(obj); } catch { From cb37c4fc735d8b41fa17273d44d87e150ac156a2 Mon Sep 17 00:00:00 2001 From: Caleb Doxsey Date: Thu, 27 Sep 2018 12:40:52 -0700 Subject: [PATCH 4/6] look for operations --- .../StackExchangeRedisTests.cs | 100 +++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs b/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs index c033f7987d09..e0383baa6283 100644 --- a/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs +++ b/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs @@ -49,6 +49,102 @@ public void SubmitsTraces() { "TIME", "TIME" }, }; + prefix = $"{prefix}StackExchange.Redis.Batch."; + expected.AddRange(new TupleList + { + { "DEBUG", $"DEBUG {prefix}DebugObjectAsync" }, + { "DDCUSTOM", $"DDCUSTOM" }, + { "GEOADD", $"GEOADD {prefix}GeoAddAsync" }, + { "GEODIST", $"GEODIST {prefix}GeoDistanceAsync" }, + { "GEOHASH", $"GEOHASH {prefix}GeoHashAsync" }, + { "GEOPOS", $"GEOPOS {prefix}GeoPositionAsync" }, + { "GEORADIUSBYMEMBER", $"GEORADIUSBYMEMBER {prefix}GeoRadiusAsync" }, + { "ZREM", $"ZREM {prefix}GeoRemoveAsync" }, + { "HINCRBYFLOAT", $"HINCRBYFLOAT {prefix}HashDecrementAsync" }, + { "HDEL", $"HDEL {prefix}HashDeleteAsync" }, + { "HEXISTS", $"HEXISTS {prefix}HashExistsAsync" }, + { "HGETALL", $"HGETALL {prefix}HashGetAllAsync" }, + { "HINCRBYFLOAT", $"HINCRBYFLOAT {prefix}HashIncrementAsync" }, + { "HKEYS", $"HKEYS {prefix}HashKeysAsync" }, + { "HLEN", $"HLEN {prefix}HashLengthAsync" }, + { "HMSET", $"HMSET {prefix}HashSetAsync" }, + { "HVALS", $"HVALS {prefix}HashValuesAsync" }, + { "PFADD", $"PFADD {prefix}HyperLogLogAddAsync" }, + { "PFCOUNT", $"PFCOUNT {prefix}HyperLogLogLengthAsync" }, + { "PFMERGE", $"PFMERGE {prefix}HyperLogLogMergeAsync" }, + { "PING", $"PING" }, + { "DEL", $"DEL key" }, + { "DUMP", $"DUMP key" }, + { "EXISTS", $"EXISTS key" }, + { "PEXPIREAT", $"PEXPIREAT key" }, + { "MOVE", $"MOVE key" }, + { "PERSIST", $"PERSIST key" }, + { "RANDOMKEY", $"RANDOMKEY" }, + { "RENAME", "RENAME key1" }, + { "RESTORE", "RESTORE key" }, + { "TYPE", "TYPE key" }, + { "LINDEX", "LINDEX listkey" }, + { "LINSERT", "LINSERT listkey" }, + { "LINSERT", "LINSERT listkey" }, + { "LPOP", "LPOP listkey" }, + { "LPUSH", "LPUSH listkey" }, + { "LLEN", "LLEN listkey" }, + { "LRANGE", "LRANGE listkey" }, + { "LREM", "LREM listkey" }, + { "RPOP", "RPOP listkey" }, + { "RPOPLPUSH", "RPOPLPUSH listkey" }, + { "RPUSH", "RPUSH listkey" }, + { "LSET", "LSET listkey" }, + { "LTRIM", "LTRIM listkey" }, + { "GET", "GET listkey" }, + { "SET", "SET listkey" }, + { "PUBLISH", "PUBLISH channel" }, + { "SADD", "SADD setkey" }, + { "SUNIONSTORE", "SUNIONSTORE setkey" }, + { "SUNION", "SUNION setkey1" }, + { "SISMEMBER", "SISMEMBER setkey" }, + { "SCARD", "SCARD setkey" }, + { "SMEMBERS", "SMEMBERS setkey" }, + { "SMOVE", "SMOVE setkey1" }, + { "SPOP", "SPOP setkey1" }, + { "SRANDMEMBER", "SRANDMEMBER setkey" }, + { "SRANDMEMBER", "SRANDMEMBER setkey" }, + { "SREM", "SREM setkey" }, + { "SORT", "SORT setkey" }, + { "SORT", "SORT setkey" }, + { "ZADD", "ZADD ssetkey" }, + { "ZUNIONSTORE", "ZUNIONSTORE ssetkey1" }, + { "ZINCRBY", "ZINCRBY ssetkey" }, + { "ZINCRBY", "ZINCRBY ssetkey" }, + { "ZCARD", "ZCARD ssetkey" }, + { "ZLEXCOUNT", "ZLEXCOUNT ssetkey" }, + { "ZRANGE", "ZRANGE ssetkey" }, + { "ZRANGE", "ZRANGE ssetkey" }, + { "ZRANGEBYSCORE", "ZRANGEBYSCORE ssetkey" }, + { "ZRANGEBYSCORE", "ZRANGEBYSCORE ssetkey" }, + { "ZRANGEBYLEX", "ZRANGEBYLEX ssetkey" }, + { "ZRANK", "ZRANK ssetkey" }, + { "ZREM", "ZREM ssetkey" }, + { "ZREMRANGEBYRANK", "ZREMRANGEBYRANK ssetkey" }, + { "ZREMRANGEBYSCORE", "ZREMRANGEBYSCORE ssetkey" }, + { "ZREMRANGEBYLEX", "ZREMRANGEBYLEX ssetkey" }, + { "ZSCORE", "ZSCORE ssestkey" }, + { "APPEND", "APPEND ssetkey" }, + { "BITCOUNT", "BITCOUNT ssetkey" }, + { "BITOP", "BITOP" }, + { "BITPOS", "BITPOS ssetkey1" }, + { "INCRBYFLOAT", "INCRBYFLOAT key" }, + { "GET", "GET key" }, + { "GETBIT", "GETBIT key" }, + { "GETRANGE", "GETRANGE key" }, + { "GETSET", "GETSET key" }, + { "INCR", "INCR key" }, + { "STRLEN", "STRLEN key" }, + { "SET", "SET key" }, + { "SETBIT", "SETBIT key" }, + { "SETRANGE", "SETRANGE key" }, + }); + for (int i = 0; i < expected.Count; i++) { var e1 = expected[i].Item1; @@ -57,8 +153,8 @@ public void SubmitsTraces() var a1 = i < spans.Count ? spans[i].Resource : string.Empty; var a2 = i < spans.Count ? spans[i].Tags.Get("redis.raw_command") : string.Empty; - Assert.True(e1 == a1, $"invalid resource name for span {i}, {e1} != {a1}"); - Assert.True(e2 == a2, $"invalid raw command for span {i}, {e2} != {a2}"); + Assert.True(e1 == a1, $"invalid resource name for span {i}, expected `{e1}`, got `{a1}`"); + Assert.True(e2 == a2, $"invalid raw command for span {i}, expected `{e2}`, got `{a2}`"); } } } From 3b1449c2f190e2244cf8597d3d6818bdcfebe1a2 Mon Sep 17 00:00:00 2001 From: Caleb Doxsey Date: Fri, 28 Sep 2018 06:30:31 -0700 Subject: [PATCH 5/6] fix integrations, use a dictionary for test --- integrations.json | 4 ++-- .../StackExchangeRedisTests.cs | 17 +++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/integrations.json b/integrations.json index 859b5a19bb7a..b151eb1c9bc6 100644 --- a/integrations.json +++ b/integrations.json @@ -146,7 +146,7 @@ }, "wrapper": { "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=0.3.2.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.StackExchangeRedis", + "type": "Datadog.Trace.ClrProfiler.Integrations.StackExchange.Redis.ConnectionMultiplexer", "method": "ExecuteSyncImpl", "signature": "10 01 04 1E 00 1C 1C 1C 1C" } @@ -162,7 +162,7 @@ }, "wrapper": { "assembly": "Datadog.Trace.ClrProfiler.Managed, Version=0.3.2.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb", - "type": "Datadog.Trace.ClrProfiler.Integrations.StackExchangeRedis", + "type": "Datadog.Trace.ClrProfiler.Integrations.StackExchange.Redis.ConnectionMultiplexer", "method": "ExecuteAsyncImpl", "signature": "10 01 05 1C 1C 1C 1C 1C 1C" } diff --git a/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs b/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs index e0383baa6283..362a3b257b7f 100644 --- a/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs +++ b/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using System.Linq; using Datadog.Trace.ClrProfiler.Integrations; using Datadog.Trace.TestHelpers; @@ -145,16 +147,15 @@ public void SubmitsTraces() { "SETRANGE", "SETRANGE key" }, }); - for (int i = 0; i < expected.Count; i++) + var spanLookup = new Dictionary, MockTracerAgent.Span>(); + foreach (var span in spans) { - var e1 = expected[i].Item1; - var e2 = expected[i].Item2; - - var a1 = i < spans.Count ? spans[i].Resource : string.Empty; - var a2 = i < spans.Count ? spans[i].Tags.Get("redis.raw_command") : string.Empty; + spanLookup[new Tuple(span.Resource, span.Tags.Get("redis.raw_command"))] = span; + } - Assert.True(e1 == a1, $"invalid resource name for span {i}, expected `{e1}`, got `{a1}`"); - Assert.True(e2 == a2, $"invalid raw command for span {i}, expected `{e2}`, got `{a2}`"); + foreach (var e in expected) + { + Assert.True(spanLookup.ContainsKey(e), $"no span found for `{e.Item1}`, `{e.Item2}`"); } } } From da135531e0e4bac095aa9068a1c09157acdea13d Mon Sep 17 00:00:00 2001 From: Caleb Doxsey Date: Fri, 28 Sep 2018 11:01:02 -0700 Subject: [PATCH 6/6] add sync methods and test --- samples/Samples.RedisCore/Program.cs | 141 ++++++++++++- .../StackExchangeRedisTests.cs | 195 +++++++++++++++--- 2 files changed, 297 insertions(+), 39 deletions(-) diff --git a/samples/Samples.RedisCore/Program.cs b/samples/Samples.RedisCore/Program.cs index c8a091bbcd1d..03923a3404cf 100644 --- a/samples/Samples.RedisCore/Program.cs +++ b/samples/Samples.RedisCore/Program.cs @@ -85,6 +85,7 @@ private static void RunStackExchange(string prefix) { "TIME", () => db.Execute("TIME") }, }); + RunCommands(StackExchangeSyncCommands(prefix + "Database.", db)); var batch = db.CreateBatch(); var batchPending = RunStackExchangeAsync(prefix + "Batch.", batch); @@ -115,6 +116,125 @@ private static void RunStackExchange(string prefix) } } + private static TupleList> StackExchangeSyncCommands(string prefix, IDatabase db) + { + return new TupleList>() + { + { "DebugObject", () => db.DebugObject($"{prefix}DebugObject") }, + { "Execute", () => db.Execute("DDCUSTOM", "COMMAND") }, + + { "GeoAdd", () => db.GeoAdd($"{prefix}Geo", new GeoEntry(1.5, 2.5, "member")) }, + { "GeoDistance", () => db.GeoDistance($"{prefix}Geo", "member1", "member2") }, + { "GeoHash", () => db.GeoHash($"{prefix}Geo", "member") }, + { "GeoPosition", () => db.GeoPosition($"{prefix}Geo", "member") }, + { "GeoRadius", () => db.GeoRadius($"{prefix}Geo", "member", 2.3) }, + { "GeoRemove", () => db.GeoRemove($"{prefix}Geo", "member") }, + + { "HashDecrement", () => db.HashDecrement($"{prefix}Hash", "hashfield", 4.5) }, + { "HashDelete", () => db.HashDelete($"{prefix}Hash", "hashfield") }, + { "HashExists", () => db.HashExists($"{prefix}Hash", "hashfield") }, + { "HashGet", () => db.HashGet($"{prefix}Hash", "hashfield") }, + { "HashGetAll", () => db.HashGetAll($"{prefix}Hash") }, + { "HashIncrement", () => db.HashIncrement($"{prefix}Hash", "hashfield") }, + { "HashKeys", () => db.HashKeys($"{prefix}Hash") }, + { "HashLength", () => db.HashLength($"{prefix}Hash") }, + { "HashScan", () => db.HashScan($"{prefix}Hash", "*", 5, CommandFlags.None) }, + { "HashSet", () => { db.HashSet($"{prefix}Hash", new HashEntry[] { new HashEntry("hashfield", "hashvalue") }); return null; } }, + { "HashValues", () => db.HashValues($"{prefix}Hash") }, + + { "HyperLogLogAdd", () => db.HyperLogLogAdd($"{prefix}HyperLogLog", "value") }, + { "HyperLogLogLength", () => db.HyperLogLogLength($"{prefix}HyperLogLog") }, + { "HyperLogLogMerge", () => { db.HyperLogLogMerge($"{prefix}HyperLogLog2", new RedisKey[] { $"{prefix}HyperLogLog" }); return null; } }, + + { "KeyDelete", () => db.KeyDelete($"{prefix}Key") }, + { "KeyDump", () => db.KeyDump($"{prefix}Key") }, + { "KeyExists", () => db.KeyExists($"{prefix}Key") }, + { "KeyExpire", () => db.KeyExpire($"{prefix}Key", DateTime.Now) }, + { "KeyMigrate", () => { db.KeyMigrate($"{prefix}Key", db.IdentifyEndpoint()); return null; } }, + { "KeyMove", () => db.KeyMove($"{prefix}Key", 1) }, + { "KeyPersist", () => db.KeyPersist($"{prefix}Key") }, + { "KeyRandom", () => db.KeyRandom() }, + { "KeyRename", () => db.KeyRename($"{prefix}Key", $"{prefix}Key2") }, + { "KeyRestore", () => { db.KeyRestore($"{prefix}Key", new byte[] { 1,2,3,4 }); return null; } }, + { "KeyTimeToLive", () => db.KeyTimeToLive($"{prefix}Key") }, + { "KeyType", () => db.KeyType($"{prefix}Key") }, + + { "ListGetByIndex", () => db.ListGetByIndex($"{prefix}List", 0) }, + { "ListInsertAfter", () => db.ListInsertAfter($"{prefix}List", "value1", "value2") }, + { "ListInsertBefore", () => db.ListInsertBefore($"{prefix}List", "value1", "value2") }, + { "ListLeftPop", () => db.ListLeftPop($"{prefix}List") }, + { "ListLeftPush", () => db.ListLeftPush($"{prefix}List", "value3") }, + { "ListLength", () => db.ListLength($"{prefix}List") }, + { "ListRange", () => db.ListRange($"{prefix}List") }, + { "ListRemove", () => db.ListRemove($"{prefix}List", "value1") }, + { "ListRightPop", () => db.ListRightPop($"{prefix}List") }, + { "ListRightPopLeftPush", () => db.ListRightPopLeftPush($"{prefix}List", $"{prefix}List2") }, + { "ListRightPush", () => db.ListRightPush($"{prefix}List", new RedisValue[] { "value1", "value2" }) }, + { "ListSetByIndex", () => { db.ListSetByIndex($"{prefix}List", 0, "value3"); return null; } }, + { "ListTrim", () => { db.ListTrim($"{prefix}List", 0, 1); return null; } }, + + { "LockExtend", () => db.LockExtend($"{prefix}Lock", "value1", new TimeSpan(0, 0, 10)) }, + { "LockQuery", () => db.LockQuery($"{prefix}Lock") }, + { "LockRelease", () => db.LockRelease($"{prefix}Lock", "value1") }, + { "LockTake", () => db.LockTake($"{prefix}Lock", "value1", new TimeSpan(0, 0, 10)) }, + + { "Ping", () => db.Ping() }, + { "Publish", () => db.Publish(new RedisChannel("value", RedisChannel.PatternMode.Auto), "message") }, + // { "ScriptEvaluate", () => db.ScriptEvaluate() } + + { "SetAdd", () => db.SetAdd($"{prefix}Set", "value1") }, + { "SetCombine", () => db.SetCombine(SetOperation.Union, new RedisKey[] { $"{prefix}Set" }) }, + { "SetCombineAndStore", () => db.SetCombineAndStore(SetOperation.Union, $"{prefix}Set", new RedisKey[] { $"{prefix}Set" }) }, + { "SetContains", () => db.SetContains($"{prefix}Set", "value1") }, + { "SetLength", () => db.SetLength($"{prefix}Set") }, + { "SetMembers", () => db.SetMembers($"{prefix}Set") }, + { "SetMove", () => db.SetMove($"{prefix}Set", $"{prefix}Set2", "value1") }, + { "SetPop", () => db.SetPop($"{prefix}Set") }, + { "SetRandomMember", () => db.SetRandomMember($"{prefix}Set") }, + { "SetRandomMembers", () => db.SetRandomMembers($"{prefix}Set", 1) }, + { "SetRemove", () => db.SetRemove($"{prefix}Set", "value1") }, + { "SetScan", () => db.SetScan($"{prefix}Set", "*", 5) }, + + { "Sort", () => db.Sort($"{prefix}Key") }, + { "SortAndStore", () => db.SortAndStore($"{prefix}Key2", $"{prefix}Key") }, + + { "SortedSetAdd", () => db.SortedSetAdd($"{prefix}SortedSet", new SortedSetEntry[] { new SortedSetEntry("element", 1) }) }, + { "SortedSetCombineAndStore", () => db.SortedSetCombineAndStore(SetOperation.Union, $"{prefix}SortedSet2", $"{prefix}SortedSet", $"{prefix}SortedSet2") }, + { "SortedSetDecrement", () => db.SortedSetDecrement($"{prefix}SortedSet", "element", 0.5) }, + { "SortedSetIncrement", () => db.SortedSetIncrement($"{prefix}SortedSet", "element", 0.5) }, + { "SortedSetLength", () => db.SortedSetLength($"{prefix}SortedSet") }, + { "SortedSetLengthByValue", () => db.SortedSetLengthByValue($"{prefix}SortedSet", "value1", "value2") }, + { "SortedSetRangeByRank", () => db.SortedSetRangeByRank($"{prefix}SortedSet") }, + { "SortedSetRangeByRankWithScores", () => db.SortedSetRangeByRankWithScores($"{prefix}SortedSet") }, + { "SortedSetRangeByScore", () => db.SortedSetRangeByScore($"{prefix}SortedSet") }, + { "SortedSetRangeByScoreWithScores", () => db.SortedSetRangeByScoreWithScores($"{prefix}SortedSet") }, + { "SortedSetRangeByValue", () => db.SortedSetRangeByValue($"{prefix}SortedSet") }, + { "SortedSetRank", () => db.SortedSetRank($"{prefix}SortedSet", "element") }, + { "SortedSetRemove", () => db.SortedSetRemove($"{prefix}SortedSet", "element") }, + { "SortedSetRemoveRangeByRank", () => db.SortedSetRemoveRangeByRank($"{prefix}SortedSet", 0, 1) }, + { "SortedSetRemoveRangeByScore", () => db.SortedSetRemoveRangeByScore($"{prefix}SortedSet", 1, 2) }, + { "SortedSetRemoveRangeByValue", () => db.SortedSetRemoveRangeByValue($"{prefix}SortedSet", 1, 2) }, + { "SortedSetScan", () => db.SortedSetScan($"{prefix}SortedSet", "*", 5) }, + { "SortedSetScore", () => db.SortedSetScore($"{prefix}SortedSet", "element") }, + + { "StringAppend", () => db.StringAppend($"{prefix}Key", "value") }, + { "StringBitCount", () => db.StringBitCount($"{prefix}Key") }, + { "StringBitOperation", () => db.StringBitOperation(Bitwise.And, $"{prefix}Key", new RedisKey[] {$"{prefix}Key2" }) }, + { "StringBitPosition", () => db.StringBitPosition($"{prefix}Key", true) }, + { "StringDecrement", () => db.StringDecrement($"{prefix}Key", 5.5) }, + { "StringGet", () => db.StringGet($"{prefix}Key") }, + { "StringGetBit", () => db.StringGetBit($"{prefix}Key", 5) }, + { "StringGetRange", () => db.StringGetRange($"{prefix}Key", 0, 3) }, + { "StringGetSet", () => db.StringGetSet($"{prefix}Key", "value") }, + { "StringGetWithExpiry", () => db.StringGetWithExpiry($"{prefix}Key") }, + { "StringIncrement", () => db.StringIncrement($"{prefix}Key", 7.2) }, + { "StringLength", () => db.StringLength($"{prefix}Key") }, + { "StringSet", () => db.StringSet(new KeyValuePair[] { new KeyValuePair($"{prefix}Key", "value") }) }, + { "StringSetBit", () => db.StringSetBit($"{prefix}Key", 0, true) }, + { "StringSetRange", () => db.StringSetRange($"{prefix}Key", 17, "value") }, + }; + } + private static TupleList RunStackExchangeAsync(string prefix, IDatabaseAsync db) { var tasks = new TupleList>() @@ -225,7 +345,7 @@ private static TupleList RunStackExchangeAsync(string prefix, IDat { pending.Add(item.Item1, item.Item2()); } - catch(Exception e) + catch (Exception e) { while (e.InnerException != null) { @@ -261,7 +381,8 @@ private static void RunCommands(TupleList> commands) { foreach (var cmd in commands) { - if (cmd.Item2 == null) + var f = cmd.Item2; + if (f == null) { continue; } @@ -269,12 +390,22 @@ private static void RunCommands(TupleList> commands) object result; try { - result = cmd.Item2(); + result = f(); } - catch (Exception ex) + catch (Exception e) { - result = ex.Message; + while (e.InnerException != null) + { + e = e.InnerException; + } + result = e.Message; } + + if (result is Task task) + { + result = TaskResult(task); + } + Console.WriteLine($"{cmd.Item1}: {result}"); } } diff --git a/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs b/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs index 362a3b257b7f..6c2e8f9b0599 100644 --- a/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs +++ b/test/Datadog.Trace.ClrProfiler.IntegrationTests/StackExchangeRedisTests.cs @@ -27,17 +27,6 @@ public void SubmitsTraces() { Assert.True(processResult.ExitCode >= 0, $"Process exited with code {processResult.ExitCode}"); - var spans = agent.WaitForSpans(8).Where(s => s.Type == "redis").OrderBy(s => s.Start).ToList(); - - foreach (var span in spans) - { - Assert.Equal(Redis.OperationName, span.Name); - Assert.Equal($"Samples.RedisCore-{Redis.ServiceName}", span.Service); - Assert.Equal(SpanTypes.Redis, span.Type); - Assert.Equal("localhost", span.Tags.Get("out.host")); - Assert.Equal("6379", span.Tags.Get("out.port")); - } - var expected = new TupleList { { "SET", $"SET {prefix}StackExchange.Redis.INCR" }, @@ -51,29 +40,29 @@ public void SubmitsTraces() { "TIME", "TIME" }, }; - prefix = $"{prefix}StackExchange.Redis.Batch."; + var batchPrefix = $"{prefix}StackExchange.Redis.Batch."; expected.AddRange(new TupleList { - { "DEBUG", $"DEBUG {prefix}DebugObjectAsync" }, + { "DEBUG", $"DEBUG {batchPrefix}DebugObjectAsync" }, { "DDCUSTOM", $"DDCUSTOM" }, - { "GEOADD", $"GEOADD {prefix}GeoAddAsync" }, - { "GEODIST", $"GEODIST {prefix}GeoDistanceAsync" }, - { "GEOHASH", $"GEOHASH {prefix}GeoHashAsync" }, - { "GEOPOS", $"GEOPOS {prefix}GeoPositionAsync" }, - { "GEORADIUSBYMEMBER", $"GEORADIUSBYMEMBER {prefix}GeoRadiusAsync" }, - { "ZREM", $"ZREM {prefix}GeoRemoveAsync" }, - { "HINCRBYFLOAT", $"HINCRBYFLOAT {prefix}HashDecrementAsync" }, - { "HDEL", $"HDEL {prefix}HashDeleteAsync" }, - { "HEXISTS", $"HEXISTS {prefix}HashExistsAsync" }, - { "HGETALL", $"HGETALL {prefix}HashGetAllAsync" }, - { "HINCRBYFLOAT", $"HINCRBYFLOAT {prefix}HashIncrementAsync" }, - { "HKEYS", $"HKEYS {prefix}HashKeysAsync" }, - { "HLEN", $"HLEN {prefix}HashLengthAsync" }, - { "HMSET", $"HMSET {prefix}HashSetAsync" }, - { "HVALS", $"HVALS {prefix}HashValuesAsync" }, - { "PFADD", $"PFADD {prefix}HyperLogLogAddAsync" }, - { "PFCOUNT", $"PFCOUNT {prefix}HyperLogLogLengthAsync" }, - { "PFMERGE", $"PFMERGE {prefix}HyperLogLogMergeAsync" }, + { "GEOADD", $"GEOADD {batchPrefix}GeoAddAsync" }, + { "GEODIST", $"GEODIST {batchPrefix}GeoDistanceAsync" }, + { "GEOHASH", $"GEOHASH {batchPrefix}GeoHashAsync" }, + { "GEOPOS", $"GEOPOS {batchPrefix}GeoPositionAsync" }, + { "GEORADIUSBYMEMBER", $"GEORADIUSBYMEMBER {batchPrefix}GeoRadiusAsync" }, + { "ZREM", $"ZREM {batchPrefix}GeoRemoveAsync" }, + { "HINCRBYFLOAT", $"HINCRBYFLOAT {batchPrefix}HashDecrementAsync" }, + { "HDEL", $"HDEL {batchPrefix}HashDeleteAsync" }, + { "HEXISTS", $"HEXISTS {batchPrefix}HashExistsAsync" }, + { "HGETALL", $"HGETALL {batchPrefix}HashGetAllAsync" }, + { "HINCRBYFLOAT", $"HINCRBYFLOAT {batchPrefix}HashIncrementAsync" }, + { "HKEYS", $"HKEYS {batchPrefix}HashKeysAsync" }, + { "HLEN", $"HLEN {batchPrefix}HashLengthAsync" }, + { "HMSET", $"HMSET {batchPrefix}HashSetAsync" }, + { "HVALS", $"HVALS {batchPrefix}HashValuesAsync" }, + { "PFADD", $"PFADD {batchPrefix}HyperLogLogAddAsync" }, + { "PFCOUNT", $"PFCOUNT {batchPrefix}HyperLogLogLengthAsync" }, + { "PFMERGE", $"PFMERGE {batchPrefix}HyperLogLogMergeAsync" }, { "PING", $"PING" }, { "DEL", $"DEL key" }, { "DUMP", $"DUMP key" }, @@ -147,15 +136,153 @@ public void SubmitsTraces() { "SETRANGE", "SETRANGE key" }, }); - var spanLookup = new Dictionary, MockTracerAgent.Span>(); + var dbPrefix = $"{prefix}StackExchange.Redis.Database."; + expected.AddRange(new TupleList + { + { "DEBUG", $"DEBUG {dbPrefix}DebugObject" }, + { "DDCUSTOM", $"DDCUSTOM" }, + { "GEOADD", $"GEOADD {dbPrefix}Geo" }, + { "GEODIST", $"GEODIST {dbPrefix}Geo" }, + { "GEOHASH", $"GEOHASH {dbPrefix}Geo" }, + { "GEOPOS", $"GEOPOS {dbPrefix}Geo" }, + { "GEORADIUSBYMEMBER", $"GEORADIUSBYMEMBER {dbPrefix}Geo" }, + { "ZREM", $"ZREM {dbPrefix}Geo" }, + { "HINCRBYFLOAT", $"HINCRBYFLOAT {dbPrefix}Hash" }, + { "HDEL", $"HDEL {dbPrefix}Hash" }, + { "HEXISTS", $"HEXISTS {dbPrefix}Hash" }, + { "HGET", $"HGET {dbPrefix}Hash" }, + { "HGETALL", $"HGETALL {dbPrefix}Hash" }, + { "HINCRBY", $"HINCRBY {dbPrefix}Hash" }, + { "HKEYS", $"HKEYS {dbPrefix}Hash" }, + { "HLEN", $"HLEN {dbPrefix}Hash" }, + // { "HSCAN", $"HSCAN {dbPrefix}Hash" }, + { "HMSET", $"HMSET {dbPrefix}Hash" }, + { "HVALS", $"HVALS {dbPrefix}Hash" }, + { "PFADD", $"PFADD {dbPrefix}HyperLogLog" }, + { "PFCOUNT", $"PFCOUNT {dbPrefix}HyperLogLog" }, + { "PFMERGE", $"PFMERGE {dbPrefix}HyperLogLog2" }, + { "DEL", $"DEL {dbPrefix}Key" }, + { "DUMP", $"DUMP {dbPrefix}Key" }, + { "EXISTS", $"EXISTS {dbPrefix}Key" }, + { "PEXPIREAT", $"PEXPIREAT {dbPrefix}Key" }, + { "MIGRATE", $"MIGRATE {dbPrefix}Key" }, + { "MOVE", $"MOVE {dbPrefix}Key" }, + { "PERSIST", $"PERSIST {dbPrefix}Key" }, + { "RANDOMKEY", $"RANDOMKEY" }, + { "RENAME", $"RENAME {dbPrefix}Key" }, + { "RESTORE", $"RESTORE {dbPrefix}Key" }, + { "PTTL", $"PTTL {dbPrefix}Key" }, + { "TYPE", $"TYPE {dbPrefix}Key" }, + { "LINDEX", $"LINDEX {dbPrefix}List" }, + { "LINSERT", $"LINSERT {dbPrefix}List" }, + { "LINSERT", $"LINSERT {dbPrefix}List" }, + { "LPOP", $"LPOP {dbPrefix}List" }, + { "LPUSH", $"LPUSH {dbPrefix}List" }, + { "LLEN", $"LLEN {dbPrefix}List" }, + { "LRANGE", $"LRANGE {dbPrefix}List" }, + { "LREM", $"LREM {dbPrefix}List" }, + { "RPOP", $"RPOP {dbPrefix}List" }, + { "RPOPLPUSH", $"RPOPLPUSH {dbPrefix}List" }, + { "RPUSH", $"RPUSH {dbPrefix}List" }, + { "LSET", $"LSET {dbPrefix}List" }, + { "LTRIM", $"LTRIM {dbPrefix}List" }, + { "GET", $"GET {dbPrefix}Lock" }, + { "SET", $"SET {dbPrefix}Lock" }, + { "PING", $"PING" }, + { "PUBLISH", $"PUBLISH value" }, + { "SADD", $"SADD {dbPrefix}Set" }, + { "SUNION", $"SUNION {dbPrefix}Set" }, + { "SUNIONSTORE", $"SUNIONSTORE {dbPrefix}Set" }, + { "SISMEMBER", $"SISMEMBER {dbPrefix}Set" }, + { "SCARD", $"SCARD {dbPrefix}Set" }, + { "SMEMBERS", $"SMEMBERS {dbPrefix}Set" }, + { "SMOVE", $"SMOVE {dbPrefix}Set" }, + { "SPOP", $"SPOP {dbPrefix}Set" }, + { "SRANDMEMBER", $"SRANDMEMBER {dbPrefix}Set" }, + { "SRANDMEMBER", $"SRANDMEMBER {dbPrefix}Set" }, + { "SREM", $"SREM {dbPrefix}Set" }, + { "EXEC", $"EXEC" }, + { "SORT", $"SORT {dbPrefix}Key" }, + { "SORT", $"SORT {dbPrefix}Key" }, + { "ZADD", $"ZADD {dbPrefix}SortedSet" }, + { "ZUNIONSTORE", $"ZUNIONSTORE {dbPrefix}SortedSet2" }, + { "ZINCRBY", $"ZINCRBY {dbPrefix}SortedSet" }, + { "ZINCRBY", $"ZINCRBY {dbPrefix}SortedSet" }, + { "ZCARD", $"ZCARD {dbPrefix}SortedSet" }, + { "ZLEXCOUNT", $"ZLEXCOUNT {dbPrefix}SortedSet" }, + { "ZRANGE", $"ZRANGE {dbPrefix}SortedSet" }, + { "ZRANGE", $"ZRANGE {dbPrefix}SortedSet" }, + { "ZRANGEBYSCORE", $"ZRANGEBYSCORE {dbPrefix}SortedSet" }, + { "ZRANGEBYSCORE", $"ZRANGEBYSCORE {dbPrefix}SortedSet" }, + { "ZRANGEBYLEX", $"ZRANGEBYLEX {dbPrefix}SortedSet" }, + { "ZRANK", $"ZRANK {dbPrefix}SortedSet" }, + { "ZREM", $"ZREM {dbPrefix}SortedSet" }, + { "ZREMRANGEBYRANK", $"ZREMRANGEBYRANK {dbPrefix}SortedSet" }, + { "ZREMRANGEBYSCORE", $"ZREMRANGEBYSCORE {dbPrefix}SortedSet" }, + { "ZREMRANGEBYLEX", $"ZREMRANGEBYLEX {dbPrefix}SortedSet" }, + { "ZSCORE", $"ZSCORE {dbPrefix}SortedSet" }, + { "APPEND", $"APPEND {dbPrefix}Key" }, + { "BITCOUNT", $"BITCOUNT {dbPrefix}Key" }, + { "BITOP", $"BITOP" }, + { "BITPOS", $"BITPOS {dbPrefix}Key" }, + { "INCRBYFLOAT", $"INCRBYFLOAT {dbPrefix}Key" }, + { "GET", $"GET {dbPrefix}Key" }, + { "GETBIT", $"GETBIT {dbPrefix}Key" }, + { "GETRANGE", $"GETRANGE {dbPrefix}Key" }, + { "GETSET", $"GETSET {dbPrefix}Key" }, + { "PTTL+GET", $"PTTL+GET {dbPrefix}Key" }, + { "STRLEN", $"STRLEN {dbPrefix}Key" }, + { "SET", $"SET {dbPrefix}Key" }, + { "SETBIT", $"SETBIT {dbPrefix}Key" }, + { "SETRANGE", $"SETRANGE {dbPrefix}Key" }, + }); + + var spans = agent.WaitForSpans(expected.Count).Where(s => s.Type == "redis").OrderBy(s => s.Start).ToList(); + foreach (var span in spans) { - spanLookup[new Tuple(span.Resource, span.Tags.Get("redis.raw_command"))] = span; + Assert.Equal(Redis.OperationName, span.Name); + Assert.Equal($"Samples.RedisCore-{Redis.ServiceName}", span.Service); + Assert.Equal(SpanTypes.Redis, span.Type); + Assert.Equal("localhost", span.Tags.Get("out.host")); + Assert.Equal("6379", span.Tags.Get("out.port")); } + var spanLookup = new Dictionary, int>(); + foreach (var span in spans) + { + var key = new Tuple(span.Resource, span.Tags.Get("redis.raw_command")); + if (spanLookup.ContainsKey(key)) + { + spanLookup[key]++; + } + else + { + spanLookup[key] = 1; + } + } + + var missing = new List>(); + foreach (var e in expected) { - Assert.True(spanLookup.ContainsKey(e), $"no span found for `{e.Item1}`, `{e.Item2}`"); + var found = spanLookup.ContainsKey(e); + if (found) + { + if (--spanLookup[e] <= 0) + { + spanLookup.Remove(e); + } + } + else + { + missing.Add(e); + } + } + + foreach (var e in missing) + { + Assert.True(false, $"no span found for `{e.Item1}`, `{e.Item2}`, remaining spans: `{string.Join(", ", spanLookup.Select(kvp => $"{kvp.Key.Item1}, {kvp.Key.Item2}").ToArray())}`"); } } }