|
| 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