Skip to content

Commit 3a968c4

Browse files
authored
StackExchange.Redis Batch Integration + tests (#159)
* test async methods * add batch implementation * fix field * look for operations * fix integrations, use a dictionary for test
1 parent 90d4d3c commit 3a968c4

File tree

7 files changed

+490
-103
lines changed

7 files changed

+490
-103
lines changed

integrations.json

+18-2
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@
146146
},
147147
"wrapper": {
148148
"assembly": "Datadog.Trace.ClrProfiler.Managed, Version=0.3.2.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb",
149-
"type": "Datadog.Trace.ClrProfiler.Integrations.StackExchangeRedis",
149+
"type": "Datadog.Trace.ClrProfiler.Integrations.StackExchange.Redis.ConnectionMultiplexer",
150150
"method": "ExecuteSyncImpl",
151151
"signature": "10 01 04 1E 00 1C 1C 1C 1C"
152152
}
@@ -162,10 +162,26 @@
162162
},
163163
"wrapper": {
164164
"assembly": "Datadog.Trace.ClrProfiler.Managed, Version=0.3.2.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb",
165-
"type": "Datadog.Trace.ClrProfiler.Integrations.StackExchangeRedis",
165+
"type": "Datadog.Trace.ClrProfiler.Integrations.StackExchange.Redis.ConnectionMultiplexer",
166166
"method": "ExecuteAsyncImpl",
167167
"signature": "10 01 05 1C 1C 1C 1C 1C 1C"
168168
}
169+
},
170+
{
171+
"caller": {
172+
"assembly": "StackExchange.Redis"
173+
},
174+
"target": {
175+
"assembly": "StackExchange.Redis",
176+
"type": "StackExchange.Redis.RedisBase",
177+
"method": "ExecuteAsync"
178+
},
179+
"wrapper": {
180+
"assembly": "Datadog.Trace.ClrProfiler.Managed, Version=0.3.2.0, Culture=neutral, PublicKeyToken=def86d061d0d2eeb",
181+
"type": "Datadog.Trace.ClrProfiler.Integrations.StackExchange.Redis.RedisBatch",
182+
"method": "ExecuteAsync",
183+
"signature": "10 01 04 1C 1C 1C 1C 1C"
184+
}
169185
}
170186
]
171187
},

samples/Samples.RedisCore/Program.cs

+178-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using System.Threading.Tasks;
45
using ServiceStack.Redis;
56
using StackExchange.Redis;
67

@@ -66,9 +67,9 @@ private static void RunStackExchange(string prefix)
6667
prefix += "StackExchange.Redis.";
6768

6869
Console.WriteLine($"Testing StackExchange.Redis {prefix}");
69-
using (var redis = ConnectionMultiplexer.Connect("localhost"))
70+
using (var redis = ConnectionMultiplexer.Connect("localhost,allowAdmin=true"))
7071
{
71-
var db = redis.GetDatabase();
72+
var db = redis.GetDatabase(1);
7273

7374
db.StringSet($"{prefix}INCR", "0");
7475

@@ -83,13 +84,188 @@ private static void RunStackExchange(string prefix)
8384
{ "GET", () => db.StringGet($"{prefix}INCR") },
8485
{ "TIME", () => db.Execute("TIME") },
8586
});
87+
88+
89+
var batch = db.CreateBatch();
90+
var batchPending = RunStackExchangeAsync(prefix + "Batch.", batch);
91+
batch.Execute();
92+
foreach (var item in batchPending)
93+
{
94+
try
95+
{
96+
Console.WriteLine($"{item.Item1}: {TaskResult(item.Item2)}");
97+
}
98+
catch (Exception e)
99+
{
100+
while (e.InnerException != null)
101+
{
102+
e = e.InnerException;
103+
}
104+
Console.WriteLine($"{item.Item1}: {e.Message}");
105+
}
106+
}
107+
108+
try
109+
{
110+
redis.GetServer("localhost:6379").FlushDatabase(1);
111+
}
112+
catch
113+
{
114+
}
115+
}
116+
}
117+
118+
private static TupleList<string, Task> RunStackExchangeAsync(string prefix, IDatabaseAsync db)
119+
{
120+
var tasks = new TupleList<string, Func<Task>>()
121+
{
122+
{ "DebugObjectAsync", () => db.DebugObjectAsync($"{prefix}DebugObjectAsync") },
123+
{ "ExecuteAsync", () => db.ExecuteAsync("DDCUSTOM", "COMMAND") },
124+
{ "GeoAddAsync", () => db.GeoAddAsync($"{prefix}GeoAddAsync", new GeoEntry(1.5, 2.5, "member")) },
125+
{ "GeoDistanceAsync", () => db.GeoDistanceAsync($"{prefix}GeoDistanceAsync", "member1", "member2") },
126+
{ "GeoHashAsync", () => db.GeoHashAsync($"{prefix}GeoHashAsync", "member") },
127+
{ "GeoPositionAsync", () => db.GeoPositionAsync($"{prefix}GeoPositionAsync", "member") },
128+
{ "GeoRadiusAsync", () => db.GeoRadiusAsync($"{prefix}GeoRadiusAsync", "member", 2.3) },
129+
{ "GeoRemoveAsync", () => db.GeoRemoveAsync($"{prefix}GeoRemoveAsync", "member") },
130+
{ "HashDecrementAsync", () => db.HashDecrementAsync($"{prefix}HashDecrementAsync", "hashfield", 4.5) },
131+
{ "HashDeleteAsync", () => db.HashDeleteAsync($"{prefix}HashDeleteAsync", "hashfield") },
132+
{ "HashExistsAsync", () => db.HashExistsAsync($"{prefix}HashExistsAsync", "hashfield") },
133+
{ "HashGetAllAsync", () => db.HashGetAllAsync($"{prefix}HashGetAllAsync") },
134+
{ "HashIncrementAsync", () => db.HashIncrementAsync($"{prefix}HashIncrementAsync", "hashfield", 1.5) },
135+
{ "HashKeysAsync", () => db.HashKeysAsync($"{prefix}HashKeysAsync") },
136+
{ "HashLengthAsync", () => db.HashLengthAsync($"{prefix}HashLengthAsync") },
137+
{ "HashSetAsync", () => db.HashSetAsync($"{prefix}HashSetAsync", new HashEntry[] { new HashEntry("x", "y") }) },
138+
{ "HashValuesAsync", () => db.HashValuesAsync($"{prefix}HashValuesAsync") },
139+
{ "HyperLogLogAddAsync", () => db.HyperLogLogAddAsync($"{prefix}HyperLogLogAddAsync", "value") },
140+
{ "HyperLogLogLengthAsync", () => db.HyperLogLogLengthAsync($"{prefix}HyperLogLogLengthAsync") },
141+
{ "HyperLogLogMergeAsync", () => db.HyperLogLogMergeAsync($"{prefix}HyperLogLogMergeAsync", new RedisKey[] { "key1", "key2" }) },
142+
{ "IdentifyEndpointAsync", () => db.IdentifyEndpointAsync() },
143+
{ "KeyDeleteAsync", () => db.KeyDeleteAsync("key") },
144+
{ "KeyDumpAsync", () => db.KeyDumpAsync("key") },
145+
{ "KeyExistsAsync", () => db.KeyExistsAsync("key") },
146+
{ "KeyExpireAsync", () => db.KeyExpireAsync("key", DateTime.Now) },
147+
// () => db.KeyMigrateAsync("key", ???)
148+
{ "KeyMoveAsync", () => db.KeyMoveAsync("key", 1) },
149+
{ "KeyPersistAsync", () => db.KeyPersistAsync("key") },
150+
{ "KeyRandomAsync", () => db.KeyRandomAsync() },
151+
{ "KeyRenameAsync", () => db.KeyRenameAsync("key1", "key2") },
152+
{ "KeyRestoreAsync", () => db.KeyRestoreAsync("key", new byte[] { 0,1,2,3,4 }) },
153+
{ "KeyTimeToLiveAsync", () => db.KeyTimeToLiveAsync("key") },
154+
{ "KeyTypeAsync", () => db.KeyTypeAsync("key") },
155+
{ "ListGetByIndexAsync", () => db.ListGetByIndexAsync("listkey", 0) },
156+
{ "ListInsertAfterAsync", () => db.ListInsertAfterAsync("listkey", "value1", "value2") },
157+
{ "ListInsertBeforeAsync", () => db.ListInsertBeforeAsync("listkey", "value1", "value2") },
158+
{ "ListLeftPopAsync", () => db.ListLeftPopAsync("listkey") },
159+
{ "ListLeftPushAsync", () => db.ListLeftPushAsync("listkey", new RedisValue[] { "value3", "value4" }) },
160+
{ "ListLengthAsync", () => db.ListLengthAsync("listkey") },
161+
{ "ListRangeAsync", () => db.ListRangeAsync("listkey") },
162+
{ "ListRemoveAsync", () => db.ListRemoveAsync("listkey", "value3") },
163+
{ "ListRightPopAsync", () => db.ListRightPopAsync("listkey") },
164+
{ "ListRightPopLeftPushAsync", () => db.ListRightPopLeftPushAsync("listkey", "listkey2") },
165+
{ "ListRightPushAsync", () => db.ListRightPushAsync("listkey", new RedisValue[] { "value5", "value6" }) },
166+
{ "ListSetByIndexAsync", () => db.ListSetByIndexAsync("listkey", 0, "value7") },
167+
{ "ListTrimAsync", () => db.ListTrimAsync("listkey", 0, 1) },
168+
{ "LockExtendAsync", () => db.LockExtendAsync("listkey", "value7", new TimeSpan(0, 0, 10)) },
169+
{ "LockQueryAsync", () => db.LockQueryAsync("listkey") },
170+
{ "LockReleaseAsync", () => db.LockReleaseAsync("listkey", "value7") },
171+
{ "LockTakeAsync", () => db.LockTakeAsync("listkey", "value8", new TimeSpan(0, 0, 10)) },
172+
{ "PublishAsync", () => db.PublishAsync(new RedisChannel("channel", RedisChannel.PatternMode.Auto), "somemessage") },
173+
// { "ScriptEvaluateAsync", () => db.ScriptEvaluateAsync(}
174+
{ "SetAddAsync", () => db.SetAddAsync("setkey", "value1") },
175+
{ "SetCombineAndStoreAsync", () => db.SetCombineAndStoreAsync(SetOperation.Union, "setkey", new RedisKey[] { "value2" }) },
176+
{ "SetCombineAsync", () => db.SetCombineAsync(SetOperation.Union, new RedisKey[] { "setkey1", "setkey2"}) },
177+
{ "SetContainsAsync", () => db.SetContainsAsync("setkey", "value1") },
178+
{ "SetLengthAsync", () => db.SetLengthAsync("setkey") },
179+
{ "SetMembersAsync", () => db.SetMembersAsync("setkey") },
180+
{ "SetMoveAsync", () => db.SetMoveAsync("setkey1", "setkey2", "value2") },
181+
{ "SetPopAsync", () => db.SetPopAsync("setkey1") },
182+
{ "SetRandomMemberAsync", () => db.SetRandomMemberAsync("setkey") },
183+
{ "SetRandomMembersAsync", () => db.SetRandomMembersAsync("setkey", 2) },
184+
{ "SetRemoveAsync", () => db.SetRemoveAsync("setkey", "value2") },
185+
{ "SortAndStoreAsync", () => db.SortAndStoreAsync("setkey2", "setkey") },
186+
{ "SortAsync", () => db.SortAsync("setkey") },
187+
{ "SortedSetAddAsync", () => db.SortedSetAddAsync("ssetkey", new SortedSetEntry[] { new SortedSetEntry("value1", 1.5), new SortedSetEntry("value2", 2.5) }) },
188+
{ "SortedSetCombineAndStoreAsync", () => db.SortedSetCombineAndStoreAsync(SetOperation.Union, "ssetkey1", "ssetkey2", "ssetkey3") },
189+
{ "SortedSetDecrementAsync", () => db.SortedSetDecrementAsync("ssetkey", "value1", 1) },
190+
{ "SortedSetIncrementAsync", () => db.SortedSetIncrementAsync("ssetkey", "value2", 1) },
191+
{ "SortedSetLengthAsync", () => db.SortedSetLengthAsync("ssetkey") },
192+
{ "SortedSetLengthByValueAsync", () => db.SortedSetLengthByValueAsync("ssetkey", "value1", "value2") },
193+
{ "SortedSetRangeByRankAsync", () => db.SortedSetRangeByRankAsync("ssetkey") },
194+
{ "SortedSetRangeByRankWithScoresAsync", () => db.SortedSetRangeByRankWithScoresAsync("ssetkey") },
195+
{ "SortedSetRangeByScoreAsync", () => db.SortedSetRangeByScoreAsync("ssetkey") },
196+
{ "SortedSetRangeByScoreWithScoresAsync", () => db.SortedSetRangeByScoreWithScoresAsync("ssetkey") },
197+
{ "SortedSetRangeByValueAsync", () => db.SortedSetRangeByValueAsync("ssetkey") },
198+
{ "SortedSetRankAsync", () => db.SortedSetRankAsync("ssetkey", "value1") },
199+
{ "SortedSetRemoveAsync", () => db.SortedSetRemoveAsync("ssetkey", "value1") },
200+
{ "SortedSetRemoveRangeByRankAsync", () => db.SortedSetRemoveRangeByRankAsync("ssetkey", 0, 1) },
201+
{ "SortedSetRemoveRangeByScoreAsync", () => db.SortedSetRemoveRangeByScoreAsync("ssetkey", 0, 1) },
202+
{ "SortedSetRemoveRangeByValueAsync", () => db.SortedSetRemoveRangeByValueAsync("ssetkey", "value1", "value2") },
203+
{ "SortedSetScoreAsync", () => db.SortedSetScoreAsync("ssestkey", "value1") },
204+
{ "StringAppendAsync", () => db.StringAppendAsync("ssetkey", "value1") },
205+
{ "StringBitCountAsync", () => db.StringBitCountAsync("ssetkey") },
206+
{ "StringBitOperationAsync", () => db.StringBitOperationAsync(Bitwise.And, "ssetkey1", new RedisKey[] { "ssetkey2", "ssetkey3" }) },
207+
{ "StringBitPositionAsync", () => db.StringBitPositionAsync("ssetkey1", true) },
208+
{ "StringDecrementAsync", () => db.StringDecrementAsync("key", 1.45) },
209+
{ "StringGetAsync", () => db.StringGetAsync("key") },
210+
{ "StringGetBitAsync", () => db.StringGetBitAsync("key", 3) },
211+
{ "StringGetRangeAsync", () => db.StringGetRangeAsync("key", 0, 1) },
212+
{ "StringGetSetAsync", () => db.StringGetSetAsync("key", "value") },
213+
{ "StringGetWithExpiryAsync", () => db.StringGetWithExpiryAsync("key") },
214+
{ "StringIncrementAsync", () => db.StringIncrementAsync("key", 1) },
215+
{ "StringLengthAsync", () => db.StringLengthAsync("key") },
216+
{ "StringSetAsync", () => db.StringSetAsync(new KeyValuePair<RedisKey, RedisValue>[] { new KeyValuePair<RedisKey, RedisValue>("key", "value")}) },
217+
{ "StringSetBitAsync", () => db.StringSetBitAsync("key", 0, true) },
218+
{ "StringSetRangeAsync", () => db.StringSetRangeAsync("key", 3, "value") },
219+
};
220+
221+
var pending = new TupleList<string, Task>();
222+
foreach (var item in tasks)
223+
{
224+
try
225+
{
226+
pending.Add(item.Item1, item.Item2());
227+
}
228+
catch(Exception e)
229+
{
230+
while (e.InnerException != null)
231+
{
232+
e = e.InnerException;
233+
}
234+
Console.WriteLine($"{e.Message}");
235+
}
236+
}
237+
238+
return pending;
239+
}
240+
241+
private static object TaskResult(Task task)
242+
{
243+
var taskType = task.GetType();
244+
245+
bool isTaskOfT =
246+
taskType.IsGenericType
247+
&& taskType.GetGenericTypeDefinition() == typeof(Task<>);
248+
249+
if (isTaskOfT)
250+
{
251+
return taskType.GetProperty("Result").GetValue(task);
252+
}
253+
else
254+
{
255+
task.Wait();
256+
return null;
86257
}
87258
}
88259

89260
private static void RunCommands(TupleList<string, Func<object>> commands)
90261
{
91262
foreach (var cmd in commands)
92263
{
264+
if (cmd.Item2 == null)
265+
{
266+
continue;
267+
}
268+
93269
object result;
94270
try
95271
{

src/Datadog.Trace.ClrProfiler.Managed/Extensions.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,11 @@ private static Task<T> TraceTask<T>(Span span, Task<T> task, Action<Exception> o
9292
{
9393
if (t.IsFaulted)
9494
{
95-
onComplete(t.Exception);
96-
throw t.Exception;
95+
t.Exception.Handle(e =>
96+
{
97+
onComplete(e);
98+
return false;
99+
});
97100
}
98101

99102
if (t.IsCompleted)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
using System;
2+
using System.Linq;
3+
using System.Reflection;
4+
5+
namespace Datadog.Trace.ClrProfiler.Integrations.StackExchange.Redis
6+
{
7+
/// <summary>
8+
/// Base class for redis integration.
9+
/// </summary>
10+
public class Base
11+
{
12+
private static Func<object, string> _getCommandAndKeyMethod;
13+
14+
private static Func<object, string> _getConfigurationMethod;
15+
16+
/// <summary>
17+
/// Get the configuration for the multiplexer.
18+
/// </summary>
19+
/// <param name="multiplexer">The multiplexer</param>
20+
/// <returns>The configuration</returns>
21+
protected static string GetConfiguration(object multiplexer)
22+
{
23+
try
24+
{
25+
if (_getConfigurationMethod == null)
26+
{
27+
_getConfigurationMethod = DynamicMethodBuilder<Func<object, string>>.CreateMethodCallDelegate(multiplexer.GetType(), "get_Configuration");
28+
}
29+
30+
return _getConfigurationMethod(multiplexer);
31+
}
32+
catch
33+
{
34+
return null;
35+
}
36+
}
37+
38+
/// <summary>
39+
/// Get the host and port from the config
40+
/// </summary>
41+
/// <param name="config">The config</param>
42+
/// <returns>The host and port</returns>
43+
protected static Tuple<string, string> GetHostAndPort(string config)
44+
{
45+
string host = null;
46+
string port = null;
47+
48+
if (config != null)
49+
{
50+
// config can contain several settings separated by commas:
51+
// hostname:port,name=MyName,keepAlive=180,syncTimeout=10000,abortConnect=False
52+
// split in commas, find the one without '=', split that one on ':'
53+
string[] hostAndPort = config.Split(',')
54+
.FirstOrDefault(p => !p.Contains("="))
55+
?.Split(':');
56+
57+
if (hostAndPort != null)
58+
{
59+
host = hostAndPort[0];
60+
}
61+
62+
// check length because port is optional
63+
if (hostAndPort?.Length > 1)
64+
{
65+
port = hostAndPort[1];
66+
}
67+
}
68+
69+
return new Tuple<string, string>(host, port);
70+
}
71+
72+
/// <summary>
73+
/// Get the raw command.
74+
/// </summary>
75+
/// <param name="multiplexer">The multiplexer</param>
76+
/// <param name="message">The message</param>
77+
/// <returns>The raw command</returns>
78+
protected static string GetRawCommand(object multiplexer, object message)
79+
{
80+
string cmdAndKey = null;
81+
try
82+
{
83+
if (_getCommandAndKeyMethod == null)
84+
{
85+
var asm = multiplexer.GetType().Assembly;
86+
var messageType = asm.GetType("StackExchange.Redis.Message");
87+
_getCommandAndKeyMethod = DynamicMethodBuilder<Func<object, string>>.CreateMethodCallDelegate(messageType, "get_CommandAndKey");
88+
}
89+
90+
cmdAndKey = _getCommandAndKeyMethod(message);
91+
}
92+
catch
93+
{
94+
}
95+
96+
return cmdAndKey ?? "COMMAND";
97+
}
98+
99+
/// <summary>
100+
/// GetMultiplexer returns the Multiplexer for an object
101+
/// </summary>
102+
/// <param name="obj">The object</param>
103+
/// <returns>The multiplexer</returns>
104+
protected static object GetMultiplexer(object obj)
105+
{
106+
object multiplexer = null;
107+
try
108+
{
109+
var fi = obj.GetType().GetField("multiplexer", BindingFlags.NonPublic | BindingFlags.Instance);
110+
multiplexer = fi.GetValue(obj);
111+
}
112+
catch
113+
{
114+
}
115+
116+
return multiplexer;
117+
}
118+
}
119+
}

0 commit comments

Comments
 (0)