diff --git a/BitFaster.Caching.UnitTests/Lru/ClassicLruTests.cs b/BitFaster.Caching.UnitTests/Lru/ClassicLruTests.cs
index 2c1036c6..5fc74f18 100644
--- a/BitFaster.Caching.UnitTests/Lru/ClassicLruTests.cs
+++ b/BitFaster.Caching.UnitTests/Lru/ClassicLruTests.cs
@@ -261,6 +261,23 @@ public void WhenMoreKeysRequestedThanCapacityOldestItemIsEvicted()
valueFactory.timesCalled.Should().Be(capacity + 2);
}
+ [Fact]
+ public void WhenMoreKeysRequestedThanCapacityEvictedMetricRecordsNumberEvicted()
+ {
+ // request 3 items, LRU is now full
+ for (int i = 0; i < capacity; i++)
+ {
+ lru.GetOrAdd(i, valueFactory.Create);
+ }
+
+ lru.Metrics.Evicted.Should().Be(0);
+
+ // request 0, now item 1 is to be evicted
+ lru.GetOrAdd(4, valueFactory.Create);
+
+ lru.Metrics.Evicted.Should().Be(1);
+ }
+
[Fact]
public void WhenValueExpiresItIsDisposed()
{
diff --git a/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs b/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs
index c4218365..054e197c 100644
--- a/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs
+++ b/BitFaster.Caching.UnitTests/Lru/ConcurrentLruTests.cs
@@ -494,6 +494,16 @@ public void WhenValueEvictedItemRemovedEventIsFired()
removedItems[1].Reason.Should().Be(ItemRemovedReason.Evicted);
}
+ [Fact]
+ public void WhenValuesAreEvictedEvictionMetricCountsEvicted()
+ {
+ this.Warmup();
+
+ this.lru.GetOrAdd(1, valueFactory.Create);
+
+ this.lru.Metrics.Evicted.Should().Be(1);
+ }
+
[Fact]
public void WhenItemRemovedEventIsUnregisteredEventIsNotFired()
{
diff --git a/BitFaster.Caching.UnitTests/Lru/NoTelemetryPolicyTests.cs b/BitFaster.Caching.UnitTests/Lru/NoTelemetryPolicyTests.cs
index f937926e..0a77c4af 100644
--- a/BitFaster.Caching.UnitTests/Lru/NoTelemetryPolicyTests.cs
+++ b/BitFaster.Caching.UnitTests/Lru/NoTelemetryPolicyTests.cs
@@ -35,6 +35,12 @@ public void MissesIsZero()
counter.Misses.Should().Be(0);
}
+ [Fact]
+ public void EvictedIsZero()
+ {
+ counter.Evicted.Should().Be(0);
+ }
+
[Fact]
public void IsEnabledIsFalse()
{
diff --git a/BitFaster.Caching.UnitTests/Lru/TelemetryPolicyTests.cs b/BitFaster.Caching.UnitTests/Lru/TelemetryPolicyTests.cs
index dbdeb7c7..5bbb6c86 100644
--- a/BitFaster.Caching.UnitTests/Lru/TelemetryPolicyTests.cs
+++ b/BitFaster.Caching.UnitTests/Lru/TelemetryPolicyTests.cs
@@ -64,6 +64,22 @@ public void WhenTotalCountIsZeroRatioReturnsZero()
telemetryPolicy.HitRatio.Should().Be(0.0);
}
+ [Fact]
+ public void WhenItemRemovedIsEvictedIncrementEvictedCount()
+ {
+ telemetryPolicy.OnItemRemoved(1, 2, ItemRemovedReason.Evicted);
+
+ telemetryPolicy.Evicted.Should().Be(1);
+ }
+
+ [Fact]
+ public void WhenItemRemovedIsRemovedDontIncrementEvictedCount()
+ {
+ telemetryPolicy.OnItemRemoved(1, 2, ItemRemovedReason.Removed);
+
+ telemetryPolicy.Evicted.Should().Be(0);
+ }
+
[Fact]
public void WhenOnItemRemovedInvokedEventIsFired()
{
diff --git a/BitFaster.Caching/ICacheMetrics.cs b/BitFaster.Caching/ICacheMetrics.cs
index e5539a06..fab33d76 100644
--- a/BitFaster.Caching/ICacheMetrics.cs
+++ b/BitFaster.Caching/ICacheMetrics.cs
@@ -33,6 +33,11 @@ public interface ICacheMetrics
///
long Misses { get; }
+ ///
+ /// Gets the total number of evicted items.
+ ///
+ long Evicted { get; }
+
///
/// Gets a value indicating whether metrics are enabled.
///
diff --git a/BitFaster.Caching/Lru/ClassicLru.cs b/BitFaster.Caching/Lru/ClassicLru.cs
index 199b3cf3..200ece03 100644
--- a/BitFaster.Caching/Lru/ClassicLru.cs
+++ b/BitFaster.Caching/Lru/ClassicLru.cs
@@ -135,6 +135,7 @@ public V GetOrAdd(K key, Func valueFactory)
{
dictionary.TryRemove(first.Value.Key, out var removed);
+ Interlocked.Increment(ref this.metrics.evictedCount);
Disposer.Dispose(removed.Value.Value);
}
@@ -179,6 +180,7 @@ public async Task GetOrAddAsync(K key, Func> valueFactory)
{
dictionary.TryRemove(first.Value.Key, out var removed);
+ Interlocked.Increment(ref this.metrics.evictedCount);
Disposer.Dispose(removed.Value.Value);
}
@@ -267,6 +269,7 @@ public void AddOrUpdate(K key, V value)
{
dictionary.TryRemove(first.Value.Key, out var removed);
+ Interlocked.Increment(ref this.metrics.evictedCount);
Disposer.Dispose(removed.Value.Value);
}
@@ -374,6 +377,7 @@ private class CacheMetrics : ICacheMetrics
{
public long requestHitCount;
public long requestTotalCount;
+ public long evictedCount;
public double HitRatio => (double)requestHitCount / (double)requestTotalCount;
@@ -383,6 +387,8 @@ private class CacheMetrics : ICacheMetrics
public long Misses => requestTotalCount - requestHitCount;
+ public long Evicted => evictedCount;
+
public bool IsEnabled => true;
}
}
diff --git a/BitFaster.Caching/Lru/NoTelemetryPolicy.cs b/BitFaster.Caching/Lru/NoTelemetryPolicy.cs
index e78910d8..0ab35749 100644
--- a/BitFaster.Caching/Lru/NoTelemetryPolicy.cs
+++ b/BitFaster.Caching/Lru/NoTelemetryPolicy.cs
@@ -17,6 +17,8 @@ public struct NoTelemetryPolicy : ITelemetryPolicy
public long Misses => 0;
+ public long Evicted => 0;
+
public bool IsEnabled => false;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/BitFaster.Caching/Lru/TelemetryPolicy.cs b/BitFaster.Caching/Lru/TelemetryPolicy.cs
index 1ef28e29..55c74b19 100644
--- a/BitFaster.Caching/Lru/TelemetryPolicy.cs
+++ b/BitFaster.Caching/Lru/TelemetryPolicy.cs
@@ -12,6 +12,7 @@ public struct TelemetryPolicy : ITelemetryPolicy
{
private long hitCount;
private long missCount;
+ private long evictedCount;
private object eventSource;
public double HitRatio => Total == 0 ? 0 : (double)hitCount / (double)Total;
@@ -22,6 +23,8 @@ public struct TelemetryPolicy : ITelemetryPolicy
public long Misses => this.missCount;
+ public long Evicted => this.evictedCount;
+
public bool IsEnabled => true;
public EventHandler> ItemRemoved;
@@ -38,6 +41,11 @@ public void IncrementHit()
public void OnItemRemoved(K key, V value, ItemRemovedReason reason)
{
+ if (reason == ItemRemovedReason.Evicted)
+ {
+ Interlocked.Increment(ref this.evictedCount);
+ }
+
// passing 'this' as source boxes the struct, and is anyway the wrong object
this.ItemRemoved?.Invoke(this.eventSource, new ItemRemovedEventArgs(key, value, reason));
}