Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit 6f10ef9

Browse files
committed
Merge pull request #198 from tvjames/master
Diagnostic tool to track IRedisClient instances handed out by IRedisClientsManager
2 parents 36951d5 + 3b86cfe commit 6f10ef9

File tree

5 files changed

+250
-0
lines changed

5 files changed

+250
-0
lines changed

src/ServiceStack.Redis/ServiceStack.Redis.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,10 @@
167167
<Compile Include="ShardedConnectionPool.cs" />
168168
<Compile Include="ShardedRedisClientManager.cs" />
169169
<Compile Include="Support\ConsistentHash.cs" />
170+
<Compile Include="Support\Diagnostic\InvokeEventArgs.cs" />
171+
<Compile Include="Support\Diagnostic\TrackingFrame.cs" />
172+
<Compile Include="Support\Diagnostic\TrackingRedisClientProxy.cs" />
173+
<Compile Include="Support\Diagnostic\TrackingRedisClientsManager.cs" />
170174
<Compile Include="Support\Locking\IDistributedLock.cs" />
171175
<Compile Include="Support\OptimizedObjectSerializer.cs" />
172176
<Compile Include="Support\Queue\ISequentialData.cs" />
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System;
2+
using System.Reflection;
3+
4+
namespace ServiceStack.Redis.Support.Diagnostic
5+
{
6+
/// <summary>
7+
/// Provides access to the method reflection data as part of the before/after event
8+
/// </summary>
9+
public class InvokeEventArgs : EventArgs
10+
{
11+
public MethodInfo MethodInfo { get; private set; }
12+
13+
public InvokeEventArgs(MethodInfo methodInfo)
14+
{
15+
MethodInfo = methodInfo;
16+
}
17+
}
18+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System;
2+
3+
namespace ServiceStack.Redis.Support.Diagnostic
4+
{
5+
/// <summary>
6+
/// Stores details about the context in which an IRedisClient is allocated.
7+
/// </summary>
8+
public class TrackingFrame : IEquatable<TrackingFrame>
9+
{
10+
public Guid Id { get; set; }
11+
12+
public Type ProvidedToInstanceOfType { get; set; }
13+
14+
public DateTime Initialised { get; set; }
15+
16+
public bool Equals(TrackingFrame other)
17+
{
18+
if (ReferenceEquals(null, other)) return false;
19+
if (ReferenceEquals(this, other)) return true;
20+
return Id.Equals(other.Id);
21+
}
22+
23+
public override bool Equals(object obj)
24+
{
25+
if (ReferenceEquals(null, obj)) return false;
26+
if (ReferenceEquals(this, obj)) return true;
27+
if (obj.GetType() != this.GetType()) return false;
28+
return Equals((TrackingFrame)obj);
29+
}
30+
31+
public override int GetHashCode()
32+
{
33+
return Id.GetHashCode();
34+
}
35+
}
36+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System;
2+
using System.Reflection;
3+
using System.Runtime.Remoting.Messaging;
4+
using ServiceStack.Logging;
5+
6+
namespace ServiceStack.Redis.Support.Diagnostic
7+
{
8+
/// <summary>
9+
/// Dynamically proxies access to the IRedisClient providing events for before & after each method invocation
10+
/// </summary>
11+
public class TrackingRedisClientProxy : System.Runtime.Remoting.Proxies.RealProxy
12+
{
13+
private static readonly ILog Logger = LogManager.GetLogger(typeof(TrackingRedisClientProxy));
14+
15+
private readonly IRedisClient redisClientInstance;
16+
private readonly Guid id;
17+
18+
public TrackingRedisClientProxy(IRedisClient instance, Guid id)
19+
: base(typeof(IRedisClient))
20+
{
21+
this.redisClientInstance = instance;
22+
this.id = id;
23+
}
24+
25+
public event EventHandler<InvokeEventArgs> BeforeInvoke;
26+
public event EventHandler<InvokeEventArgs> AfterInvoke;
27+
28+
public override IMessage Invoke(IMessage msg)
29+
{
30+
// Thanks: http://stackoverflow.com/a/15734124/211978
31+
32+
var methodCall = (IMethodCallMessage)msg;
33+
var method = (MethodInfo)methodCall.MethodBase;
34+
35+
try
36+
{
37+
if (this.BeforeInvoke != null)
38+
{
39+
this.BeforeInvoke(this, new InvokeEventArgs(method));
40+
}
41+
var result = method.Invoke(this.redisClientInstance, methodCall.InArgs);
42+
if (this.AfterInvoke != null)
43+
{
44+
this.AfterInvoke(this, new InvokeEventArgs(method));
45+
}
46+
47+
return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
48+
}
49+
catch (TargetInvocationException e)
50+
{
51+
Logger.Error("Reflection exception when invoking target method", e);
52+
return new ReturnMessage(e.InnerException, msg as IMethodCallMessage);
53+
}
54+
}
55+
}
56+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Runtime.CompilerServices;
7+
using System.Threading;
8+
using ServiceStack.Caching;
9+
using ServiceStack.Logging;
10+
11+
namespace ServiceStack.Redis.Support.Diagnostic
12+
{
13+
/// <summary>
14+
/// Tracks each IRedisClient instance allocated from the IRedisClientsManager logging when they are allocated and disposed.
15+
/// Periodically writes the allocated instances to the log for diagnostic purposes.
16+
/// </summary>
17+
public class TrackingRedisClientsManager : IRedisClientsManager
18+
{
19+
private static readonly ILog Logger = LogManager.GetLogger(typeof(TrackingRedisClientsManager));
20+
21+
private readonly HashSet<TrackingFrame> trackingFrames = new HashSet<TrackingFrame>();
22+
private readonly IRedisClientsManager redisClientsManager;
23+
24+
public TrackingRedisClientsManager(IRedisClientsManager redisClientsManager)
25+
{
26+
if (redisClientsManager == null)
27+
{
28+
throw new ArgumentNullException("redisClientsManager");
29+
}
30+
31+
this.redisClientsManager = redisClientsManager;
32+
Logger.DebugFormat("Constructed");
33+
34+
var timer = new Timer(state => this.DumpState());
35+
timer.Change(TimeSpan.FromSeconds(30), TimeSpan.FromMinutes(1));
36+
}
37+
38+
public void Dispose()
39+
{
40+
Logger.DebugFormat("Disposed");
41+
this.redisClientsManager.Dispose();
42+
}
43+
44+
[MethodImpl(MethodImplOptions.NoInlining)]
45+
public IRedisClient GetClient()
46+
{
47+
// get calling instance
48+
var callingStackFrame = new StackFrame(1, true);
49+
var callingMethodType = callingStackFrame.GetMethod();
50+
51+
return TrackInstance(callingMethodType, "GetClient", this.redisClientsManager.GetClient());
52+
}
53+
54+
[MethodImpl(MethodImplOptions.NoInlining)]
55+
public IRedisClient GetReadOnlyClient()
56+
{
57+
// get calling instance
58+
var callingMethodType = new StackFrame(1, true).GetMethod();
59+
60+
return TrackInstance(callingMethodType, "GetReadOnlyClient", this.redisClientsManager.GetReadOnlyClient());
61+
}
62+
63+
public ICacheClient GetCacheClient()
64+
{
65+
Logger.DebugFormat("GetCacheClient");
66+
return this.redisClientsManager.GetCacheClient();
67+
}
68+
69+
public ICacheClient GetReadOnlyCacheClient()
70+
{
71+
Logger.DebugFormat("GetReadOnlyCacheClient");
72+
return this.redisClientsManager.GetReadOnlyCacheClient();
73+
}
74+
75+
private IRedisClient TrackInstance(MethodBase callingMethodType, string method, IRedisClient instance)
76+
{
77+
// track
78+
var frame = new TrackingFrame()
79+
{
80+
Id = Guid.NewGuid(),
81+
Initialised = DateTime.Now,
82+
ProvidedToInstanceOfType = callingMethodType.DeclaringType,
83+
};
84+
lock (this.trackingFrames)
85+
{
86+
this.trackingFrames.Add(frame);
87+
}
88+
89+
// proxy
90+
var proxy = new TrackingRedisClientProxy(instance, frame.Id);
91+
proxy.BeforeInvoke += (sender, args) =>
92+
{
93+
if (string.Compare("Dispose", args.MethodInfo.Name, StringComparison.InvariantCultureIgnoreCase) != 0)
94+
{
95+
return;
96+
}
97+
lock (this.trackingFrames)
98+
{
99+
this.trackingFrames.Remove(frame);
100+
}
101+
var duration = DateTime.Now - frame.Initialised;
102+
103+
Logger.DebugFormat("{0,18} Disposed {1} released from instance of type {2} checked out for {3}", method, frame.Id, frame.ProvidedToInstanceOfType.FullName, duration);
104+
};
105+
106+
Logger.DebugFormat("{0,18} Tracking {1} allocated to instance of type {2}", method, frame.Id, frame.ProvidedToInstanceOfType.FullName);
107+
return proxy.GetTransparentProxy() as IRedisClient;
108+
}
109+
110+
private void DumpState()
111+
{
112+
Logger.InfoFormat("Dumping currently checked out IRedisClient instances");
113+
var inUseInstances = new Func<TrackingFrame[]>(() =>
114+
{
115+
lock (this.trackingFrames)
116+
{
117+
return Enumerable.ToArray(this.trackingFrames);
118+
}
119+
}).Invoke();
120+
121+
var summaryByType = inUseInstances.GroupBy(x => x.ProvidedToInstanceOfType.FullName);
122+
foreach (var grouped in summaryByType)
123+
{
124+
Logger.InfoFormat("{0,60}: {1,-9} oldest {2}", grouped.Key, grouped.Count(),
125+
grouped.Min(x => x.Initialised));
126+
}
127+
128+
foreach (var trackingFrame in inUseInstances)
129+
{
130+
Logger.DebugFormat("Instance {0} allocated to {1} at {2} ({3})", trackingFrame.Id,
131+
trackingFrame.ProvidedToInstanceOfType.FullName, trackingFrame.Initialised,
132+
trackingFrame.ProvidedToInstanceOfType.FullName);
133+
}
134+
}
135+
}
136+
}

0 commit comments

Comments
 (0)