diff --git a/BitFaster.Caching.UnitTests/Buffers/StripedMpscBufferTests.cs b/BitFaster.Caching.UnitTests/Buffers/StripedMpscBufferTests.cs index 4d2f613e..0c621f0d 100644 --- a/BitFaster.Caching.UnitTests/Buffers/StripedMpscBufferTests.cs +++ b/BitFaster.Caching.UnitTests/Buffers/StripedMpscBufferTests.cs @@ -21,6 +21,22 @@ public void CapacityReturnsCapacity() buffer.Capacity.Should().Be(32); } + [Fact] + public void CountReturnsCount() + { + buffer.Count.Should().Be(0); + + for (var i = 0; i < stripeCount; i++) + { + for (var j = 0; j < bufferSize; j++) + { + buffer.TryAdd(1.ToString()).Should().Be(BufferStatus.Success); + } + } + + buffer.Count.Should().Be(buffer.Capacity); + } + [Fact] public void WhenBufferIsFullTryAddReturnsFull() { diff --git a/BitFaster.Caching/Atomic/AtomicFactoryAsyncCache.cs b/BitFaster.Caching/Atomic/AtomicFactoryAsyncCache.cs index 8071989b..abf4d24f 100644 --- a/BitFaster.Caching/Atomic/AtomicFactoryAsyncCache.cs +++ b/BitFaster.Caching/Atomic/AtomicFactoryAsyncCache.cs @@ -1,12 +1,12 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Diagnostics; using System.Threading.Tasks; namespace BitFaster.Caching.Atomic { + [DebuggerDisplay("Count = {Count}")] public sealed class AtomicFactoryAsyncCache : IAsyncCache { private readonly ICache> cache; diff --git a/BitFaster.Caching/Atomic/AtomicFactoryCache.cs b/BitFaster.Caching/Atomic/AtomicFactoryCache.cs index 3290876b..255bdb3f 100644 --- a/BitFaster.Caching/Atomic/AtomicFactoryCache.cs +++ b/BitFaster.Caching/Atomic/AtomicFactoryCache.cs @@ -1,12 +1,11 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Diagnostics; namespace BitFaster.Caching.Atomic { + [DebuggerDisplay("Count = {Count}")] public sealed class AtomicFactoryCache : ICache { private readonly ICache> cache; diff --git a/BitFaster.Caching/Atomic/AtomicFactoryScopedAsyncCache.cs b/BitFaster.Caching/Atomic/AtomicFactoryScopedAsyncCache.cs index c54778b9..168a9816 100644 --- a/BitFaster.Caching/Atomic/AtomicFactoryScopedAsyncCache.cs +++ b/BitFaster.Caching/Atomic/AtomicFactoryScopedAsyncCache.cs @@ -1,13 +1,13 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; namespace BitFaster.Caching.Atomic { + [DebuggerDisplay("Count = {Count}")] public sealed class AtomicFactoryScopedAsyncCache : IScopedAsyncCache where V : IDisposable { private readonly ICache> cache; diff --git a/BitFaster.Caching/Atomic/AtomicFactoryScopedCache.cs b/BitFaster.Caching/Atomic/AtomicFactoryScopedCache.cs index 5761a404..c61b4f02 100644 --- a/BitFaster.Caching/Atomic/AtomicFactoryScopedCache.cs +++ b/BitFaster.Caching/Atomic/AtomicFactoryScopedCache.cs @@ -1,13 +1,12 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Diagnostics; using System.Threading; -using System.Threading.Tasks; namespace BitFaster.Caching.Atomic { + [DebuggerDisplay("Count = {Count}")] public sealed class AtomicFactoryScopedCache : IScopedCache where V : IDisposable { private readonly ICache> cache; diff --git a/BitFaster.Caching/Atomic/ScopedAsyncAtomicFactory.cs b/BitFaster.Caching/Atomic/ScopedAsyncAtomicFactory.cs index 41a7647a..86809900 100644 --- a/BitFaster.Caching/Atomic/ScopedAsyncAtomicFactory.cs +++ b/BitFaster.Caching/Atomic/ScopedAsyncAtomicFactory.cs @@ -1,12 +1,11 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; namespace BitFaster.Caching.Atomic { + [DebuggerDisplay("IsValueCreated={initializer == null}, Value={ScopeIfCreated}")] public sealed class ScopedAsyncAtomicFactory : IScoped, IDisposable where V : IDisposable { private Scoped scope; diff --git a/BitFaster.Caching/Atomic/ScopedAtomicFactory.cs b/BitFaster.Caching/Atomic/ScopedAtomicFactory.cs index 916ad92f..32a6f46c 100644 --- a/BitFaster.Caching/Atomic/ScopedAtomicFactory.cs +++ b/BitFaster.Caching/Atomic/ScopedAtomicFactory.cs @@ -1,10 +1,6 @@ using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Text; using System.Threading; -using System.Threading.Tasks; namespace BitFaster.Caching.Atomic { @@ -12,6 +8,7 @@ namespace BitFaster.Caching.Atomic // 1. Exactly once disposal. // 2. Exactly once invocation of value factory (synchronized create). // 3. Resolve race between create dispose init, if disposed is called before value is created, scoped value is disposed for life. + [DebuggerDisplay("IsValueCreated={initializer == null}, Value={ScopeIfCreated}")] public sealed class ScopedAtomicFactory : IScoped, IDisposable where V : IDisposable { private Scoped scope; diff --git a/BitFaster.Caching/Buffers/MpscBoundedBuffer.cs b/BitFaster.Caching/Buffers/MpscBoundedBuffer.cs index 6176b413..f97d58f5 100644 --- a/BitFaster.Caching/Buffers/MpscBoundedBuffer.cs +++ b/BitFaster.Caching/Buffers/MpscBoundedBuffer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -14,6 +15,7 @@ namespace BitFaster.Caching.Buffers /// Based on BoundedBuffer by Ben Manes. /// https://github.com/ben-manes/caffeine/blob/master/caffeine/src/main/java/com/github/benmanes/caffeine/cache/BoundedBuffer.java /// + [DebuggerDisplay("Count = {Count}/{Capacity}")] public class MpscBoundedBuffer where T : class { private T[] buffer; diff --git a/BitFaster.Caching/Buffers/StripedMpscBuffer.cs b/BitFaster.Caching/Buffers/StripedMpscBuffer.cs index 24fc1d39..51a11820 100644 --- a/BitFaster.Caching/Buffers/StripedMpscBuffer.cs +++ b/BitFaster.Caching/Buffers/StripedMpscBuffer.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using System.Text; using System.Threading; @@ -15,6 +17,7 @@ namespace BitFaster.Caching.Buffers /// rehashed to select a different buffer to retry up to 3 times. Using this approach /// writes scale linearly with number of concurrent threads. /// + [DebuggerDisplay("Count = {Count}/{Capacity}")] public class StripedMpscBuffer where T : class { const int MaxAttempts = 3; @@ -32,6 +35,8 @@ public StripedMpscBuffer(int stripeCount, int bufferSize) } } + public int Count => buffers.Sum(b => b.Count); + public int Capacity => buffers.Length * buffers[0].Capacity; public int DrainTo(T[] outputBuffer) diff --git a/BitFaster.Caching/Lfu/ConcurrentLfu.cs b/BitFaster.Caching/Lfu/ConcurrentLfu.cs index 80577ef5..7b253792 100644 --- a/BitFaster.Caching/Lfu/ConcurrentLfu.cs +++ b/BitFaster.Caching/Lfu/ConcurrentLfu.cs @@ -31,6 +31,8 @@ namespace BitFaster.Caching.Lfu /// Based on Caffeine written by Ben Manes. /// https://www.apache.org/licenses/LICENSE-2.0 /// + [DebuggerTypeProxy(typeof(ConcurrentLfu<,>.LfuDebugView))] + [DebuggerDisplay("Count = {Count}/{Capacity}")] public sealed class ConcurrentLfu : ICache, IAsyncCache, IBoundedPolicy { private const int MaxWriteBufferRetries = 100; @@ -644,7 +646,7 @@ private void ReFitProtected() } } - [DebuggerDisplay("{Format()}")] + [DebuggerDisplay("{Format(),nq}")] private class DrainStatus { public const int Idle = 0; @@ -687,7 +689,7 @@ public int Status() } [ExcludeFromCodeCoverage] - private string Format() + internal string Format() { switch (this.drainStatus.Value) { @@ -705,7 +707,8 @@ private string Format() } } - private class CacheMetrics : ICacheMetrics + [DebuggerDisplay("Hit = {Hits}, Miss = {Misses}, Upd = {Updated}, Evict = {Evicted}")] + internal class CacheMetrics : ICacheMetrics { public long requestHitCount; public long requestMissCount; @@ -741,6 +744,40 @@ public string FormatLruString() return sb.ToString(); } #endif + + [ExcludeFromCodeCoverage] + internal class LfuDebugView + { + private ConcurrentLfu lfu; + + public LfuDebugView(ConcurrentLfu lfu) + { + this.lfu = lfu; + } + + public string Maintenance => lfu.drainStatus.Format(); + + public ICacheMetrics Metrics => lfu.metrics; + + public StripedMpscBuffer> ReadBuffer => this.lfu.readBuffer; + + public StripedMpscBuffer> WriteBuffer => this.lfu.writeBuffer; + + public KeyValuePair[] Items + { + get + { + var items = new KeyValuePair[lfu.Count]; + + int index = 0; + foreach (var kvp in lfu) + { + items[index++] = kvp; + } + return items; + } + } + } } // Explicit layout cannot be a generic class member diff --git a/BitFaster.Caching/Lfu/LfuCapacityPartition.cs b/BitFaster.Caching/Lfu/LfuCapacityPartition.cs index c0248a51..7eb8a2af 100644 --- a/BitFaster.Caching/Lfu/LfuCapacityPartition.cs +++ b/BitFaster.Caching/Lfu/LfuCapacityPartition.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Text; namespace BitFaster.Caching.Lfu { + [DebuggerDisplay("{Capacity} ({Window}/{Protected}/{Probation})")] public class LfuCapacityPartition { private readonly int max; diff --git a/BitFaster.Caching/Lru/ConcurrentLru.cs b/BitFaster.Caching/Lru/ConcurrentLru.cs index 612f796c..36886f65 100644 --- a/BitFaster.Caching/Lru/ConcurrentLru.cs +++ b/BitFaster.Caching/Lru/ConcurrentLru.cs @@ -1,12 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Diagnostics; namespace BitFaster.Caching.Lru { /// + [DebuggerTypeProxy(typeof(LruDebugView<,>))] + [DebuggerDisplay("Count = {Count}/{Capacity}")] public sealed class ConcurrentLru : ConcurrentLruCore, LruPolicy, TelemetryPolicy> { /// diff --git a/BitFaster.Caching/Lru/ConcurrentLruCore.cs b/BitFaster.Caching/Lru/ConcurrentLruCore.cs index 1d70400e..343609e2 100644 --- a/BitFaster.Caching/Lru/ConcurrentLruCore.cs +++ b/BitFaster.Caching/Lru/ConcurrentLruCore.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; @@ -660,6 +661,7 @@ private static CachePolicy CreatePolicy(ConcurrentLruCore lru) // it becomes immutable. However, this object is then somewhere else on the // heap, which slows down the policies with hit counter logic in benchmarks. Likely // this approach keeps the structs data members in the same CPU cache line as the LRU. + [DebuggerDisplay("Hit = {Hits}, Miss = {Misses}, Upd = {Updated}, Evict = {Evicted}")] private class Proxy : ICacheMetrics, ICacheEvents, IBoundedPolicy, ITimePolicy { private readonly ConcurrentLruCore lru; diff --git a/BitFaster.Caching/Lru/ConcurrentTLru.cs b/BitFaster.Caching/Lru/ConcurrentTLru.cs index 3b047126..58f94d37 100644 --- a/BitFaster.Caching/Lru/ConcurrentTLru.cs +++ b/BitFaster.Caching/Lru/ConcurrentTLru.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Diagnostics; namespace BitFaster.Caching.Lru { /// + [DebuggerTypeProxy(typeof(LruDebugView<,>))] + [DebuggerDisplay("Count = {Count}/{Capacity}")] public sealed class ConcurrentTLru : ConcurrentLruCore, TLruLongTicksPolicy, TelemetryPolicy> { /// diff --git a/BitFaster.Caching/Lru/EqualCapacityPartition.cs b/BitFaster.Caching/Lru/EqualCapacityPartition.cs index 98b3dfcf..9417f879 100644 --- a/BitFaster.Caching/Lru/EqualCapacityPartition.cs +++ b/BitFaster.Caching/Lru/EqualCapacityPartition.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -9,6 +10,7 @@ namespace BitFaster.Caching.Lru /// /// A simple partitioning scheme to put an approximately equal number of items in each queue. /// + [DebuggerDisplay("{Hot}/{Warm}/{Cold}")] public class EqualCapacityPartition : ICapacityPartition { private readonly int hotCapacity; diff --git a/BitFaster.Caching/Lru/FastConcurrentLru.cs b/BitFaster.Caching/Lru/FastConcurrentLru.cs index 7b70a0d8..86c0b0f2 100644 --- a/BitFaster.Caching/Lru/FastConcurrentLru.cs +++ b/BitFaster.Caching/Lru/FastConcurrentLru.cs @@ -1,10 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Collections.Generic; +using System.Diagnostics; namespace BitFaster.Caching.Lru { /// + [DebuggerTypeProxy(typeof(LruDebugView<,>))] + [DebuggerDisplay("Count = {Count}/{Capacity}")] public sealed class FastConcurrentLru : ConcurrentLruCore, LruPolicy, NoTelemetryPolicy> { /// diff --git a/BitFaster.Caching/Lru/FastConcurrentTLru.cs b/BitFaster.Caching/Lru/FastConcurrentTLru.cs index 2b60d62a..d437d665 100644 --- a/BitFaster.Caching/Lru/FastConcurrentTLru.cs +++ b/BitFaster.Caching/Lru/FastConcurrentTLru.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; -using System.Text; +using System.Diagnostics; namespace BitFaster.Caching.Lru { /// + [DebuggerTypeProxy(typeof(LruDebugView<,>))] + [DebuggerDisplay("Count = {Count}/{Capacity}")] public sealed class FastConcurrentTLru : ConcurrentLruCore, TLruLongTicksPolicy, NoTelemetryPolicy> { /// diff --git a/BitFaster.Caching/Lru/FavorWarmPartition.cs b/BitFaster.Caching/Lru/FavorWarmPartition.cs index a03fa2d4..2ecc649f 100644 --- a/BitFaster.Caching/Lru/FavorWarmPartition.cs +++ b/BitFaster.Caching/Lru/FavorWarmPartition.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Diagnostics; namespace BitFaster.Caching.Lru { @@ -10,6 +7,7 @@ namespace BitFaster.Caching.Lru /// A capacity partitioning scheme that favors frequently accessed items by allocating 80% /// capacity to the warm queue. /// + [DebuggerDisplay("{Hot}/{Warm}/{Cold}")] public class FavorWarmPartition : ICapacityPartition { private readonly int hotCapacity; diff --git a/BitFaster.Caching/Lru/LruDebugView.cs b/BitFaster.Caching/Lru/LruDebugView.cs new file mode 100644 index 00000000..d0ce00b9 --- /dev/null +++ b/BitFaster.Caching/Lru/LruDebugView.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace BitFaster.Caching.Lru +{ + [ExcludeFromCodeCoverage] + internal class LruDebugView + { + private readonly ICache cache; + + public LruDebugView(ICache cache) + { + if (cache is null) + { + throw new ArgumentNullException(nameof(cache)); + } + + this.cache = cache; + } + + public KeyValuePair[] Items + { + get + { + var items = new KeyValuePair[cache.Count]; + + var index = 0; + foreach (var kvp in cache) + { + items[index++] = kvp; + } + return items; + } + } + + public ICacheMetrics Metrics => cache.Metrics.Value; + } +} diff --git a/BitFaster.Caching/Lru/TelemetryPolicy.cs b/BitFaster.Caching/Lru/TelemetryPolicy.cs index 9e778292..e5a3566c 100644 --- a/BitFaster.Caching/Lru/TelemetryPolicy.cs +++ b/BitFaster.Caching/Lru/TelemetryPolicy.cs @@ -1,9 +1,11 @@ using System; +using System.Diagnostics; using System.Threading; using BitFaster.Caching.Concurrent; namespace BitFaster.Caching.Lru { + [DebuggerDisplay("Hit = {Hits}, Miss = {Misses}, Upd = {Updated}, Evict = {Evicted}")] public struct TelemetryPolicy : ITelemetryPolicy { private LongAdder hitCount; diff --git a/BitFaster.Caching/Lru/TlruLongTicksPolicy.cs b/BitFaster.Caching/Lru/TlruLongTicksPolicy.cs index 41258b10..599fb99b 100644 --- a/BitFaster.Caching/Lru/TlruLongTicksPolicy.cs +++ b/BitFaster.Caching/Lru/TlruLongTicksPolicy.cs @@ -15,6 +15,7 @@ namespace BitFaster.Caching.Lru /// /// This class measures time using stopwatch. /// + [DebuggerDisplay("TTL = {TimeToLive,nq})")] public readonly struct TLruLongTicksPolicy : IItemPolicy> { // On some platforms (e.g. MacOS), stopwatch and timespan have different resolution diff --git a/BitFaster.Caching/Optional.cs b/BitFaster.Caching/Optional.cs index 7587aba2..34c92307 100644 --- a/BitFaster.Caching/Optional.cs +++ b/BitFaster.Caching/Optional.cs @@ -1,15 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime; -using System.Text; -using System.Threading.Tasks; +using System.Diagnostics; namespace BitFaster.Caching { /// /// Represents an optional value. /// + [DebuggerDisplay("{Value}")] public class Optional { private readonly T value; diff --git a/BitFaster.Caching/Scoped.cs b/BitFaster.Caching/Scoped.cs index 307f80f2..31705583 100644 --- a/BitFaster.Caching/Scoped.cs +++ b/BitFaster.Caching/Scoped.cs @@ -1,8 +1,7 @@ using System; -using System.Collections.Generic; -using System.Text; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Threading; -using BitFaster.Caching.Lru; namespace BitFaster.Caching { @@ -12,6 +11,8 @@ namespace BitFaster.Caching /// the wrapped object from being diposed until the calling code completes. /// /// The type of scoped value. + [DebuggerTypeProxy(typeof(Scoped<>.ScopedDebugView))] + [DebuggerDisplay("{FormatDebug(),nq}")] public sealed class Scoped : IScoped, IDisposable where T : IDisposable { private ReferenceCount refCount; @@ -102,5 +103,36 @@ public void Dispose() this.isDisposed = true; } } + + [ExcludeFromCodeCoverage] + internal string FormatDebug() + { + if (IsDisposed) + { + return "[Disposed Scope]"; + } + + return this.refCount.Value?.ToString(); + } + + [ExcludeFromCodeCoverage] + internal class ScopedDebugView + { + private readonly Scoped scoped; + + public ScopedDebugView(Scoped scoped) + { + if (scoped is null) + { + throw new ArgumentNullException(nameof(scoped)); + } + + this.scoped = scoped; + } + + public bool IsDisposed => this.scoped.IsDisposed; + + public T Value => this.scoped.refCount.Value; + } } } diff --git a/BitFaster.Caching/ScopedAsyncCache.cs b/BitFaster.Caching/ScopedAsyncCache.cs index 70befa59..c2362dcf 100644 --- a/BitFaster.Caching/ScopedAsyncCache.cs +++ b/BitFaster.Caching/ScopedAsyncCache.cs @@ -1,8 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; @@ -15,6 +14,7 @@ namespace BitFaster.Caching /// /// The type of keys in the cache. /// The type of values in the cache. + [DebuggerDisplay("Count = {Count}")] public sealed class ScopedAsyncCache : IScopedAsyncCache where V : IDisposable { private readonly IAsyncCache> cache; diff --git a/BitFaster.Caching/ScopedCache.cs b/BitFaster.Caching/ScopedCache.cs index 5692ad1a..b3ac8fa0 100644 --- a/BitFaster.Caching/ScopedCache.cs +++ b/BitFaster.Caching/ScopedCache.cs @@ -1,10 +1,8 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Diagnostics; using System.Threading; -using System.Threading.Tasks; namespace BitFaster.Caching { @@ -15,6 +13,7 @@ namespace BitFaster.Caching /// /// The type of keys in the cache. /// The type of values in the cache. + [DebuggerDisplay("Count = {Count}")] public sealed class ScopedCache : IScopedCache where V : IDisposable { private readonly ICache> cache;