Skip to content

Commit

Permalink
Add cache size metrics to CacheStats (#1919)
Browse files Browse the repository at this point in the history
  • Loading branch information
pkoenig10 authored Mar 27, 2024
1 parent d0ceae5 commit 617ebe6
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 51 deletions.
5 changes: 5 additions & 0 deletions changelog/@unreleased/pr-1919.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type: feature
feature:
description: Add cache size metrics to `CacheStats`.
links:
- https://github.com/palantir/tritium/pull/1919
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import com.codahale.metrics.Counting;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Timer;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Policy;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.stats.StatsCounter;
import com.google.common.collect.ImmutableMap;
Expand All @@ -33,24 +35,28 @@
import org.checkerframework.checker.index.qual.NonNegative;

public final class CacheStats implements StatsCounter, Supplier<StatsCounter> {

private final CacheMetrics metrics;
private final String name;
private final Meter hitMeter;
private final Meter missMeter;
private final Timer loadSuccessTimer;
private final Timer loadFailureTimer;
private final Meter evictionsTotalMeter;
private final ImmutableMap<RemovalCause, Meter> evictionMeters;
private final LongAdder totalLoadNanos = new LongAdder();
private final ImmutableMap<RemovalCause, Meter> evictionWeightMeters;
private final LongAdder totalLoadTime = new LongAdder();

/**
* Creates a {@link CacheStats} instance that registers metrics for Caffeine cache statistics.
* <p>
* Example usage for a {@link com.github.benmanes.caffeine.cache.Cache} or
* {@link com.github.benmanes.caffeine.cache.LoadingCache}:
* <pre>
* CacheStats cacheStats = CacheStats.of(taggedMetricRegistry, "your-cache-name")
* LoadingCache&lt;Integer, String&gt; cache = Caffeine.newBuilder()
* .recordStats(CacheStats.of(taggedMetricRegistry, "your-cache-name"))
* .recordStats(cacheStats)
* .build(key -&gt; computeSomethingExpensive(key));
* cacheStats.register(cache);
* </pre>
* @param taggedMetricRegistry tagged metric registry to add cache metrics
* @param name cache name
Expand All @@ -61,17 +67,41 @@ public static CacheStats of(TaggedMetricRegistry taggedMetricRegistry, @Safe Str
return new CacheStats(CacheMetrics.of(taggedMetricRegistry), name);
}

/**
* Registers additional metrics for a Caffeine cache.
*
* @param cache cache for which to register metrics
* @return the given cache instance
*/
public <K, V, C extends Cache<K, V>> C register(C cache) {
metrics.estimatedSize().cache(name).build(cache::estimatedSize);
metrics.weightedSize().cache(name).build(() -> cache.policy()
.eviction()
.flatMap(e -> e.weightedSize().stream().boxed().findFirst())
.orElse(null));
metrics.maximumSize().cache(name).build(() -> cache.policy()
.eviction()
.map(Policy.Eviction::getMaximum)
.orElse(null));
return cache;
}

private CacheStats(CacheMetrics metrics, @Safe String name) {
this.metrics = metrics;
this.name = name;
this.hitMeter = metrics.hit(name);
this.missMeter = metrics.miss(name);
this.loadSuccessTimer =
metrics.load().cache(name).result(Load_Result.SUCCESS).build();
this.loadFailureTimer =
metrics.load().cache(name).result(Load_Result.FAILURE).build();
this.evictionsTotalMeter = metrics.eviction(name);
this.evictionMeters = Arrays.stream(RemovalCause.values())
.collect(Maps.toImmutableEnumMap(cause -> cause, cause -> metrics.evictions()
.collect(Maps.toImmutableEnumMap(cause -> cause, cause -> metrics.eviction()
.cache(name)
.cause(cause.toString())
.build()));
this.evictionWeightMeters = Arrays.stream(RemovalCause.values())
.collect(Maps.toImmutableEnumMap(cause -> cause, cause -> metrics.evictionWeight()
.cache(name)
.cause(cause.toString())
.build()));
Expand All @@ -95,22 +125,25 @@ public void recordMisses(@NonNegative int count) {
@Override
public void recordLoadSuccess(@NonNegative long loadTime) {
loadSuccessTimer.update(loadTime, TimeUnit.NANOSECONDS);
totalLoadNanos.add(loadTime);
totalLoadTime.add(loadTime);
}

@Override
public void recordLoadFailure(@NonNegative long loadTime) {
loadFailureTimer.update(loadTime, TimeUnit.NANOSECONDS);
totalLoadNanos.add(loadTime);
totalLoadTime.add(loadTime);
}

@Override
public void recordEviction(@NonNegative int weight, RemovalCause cause) {
Meter counter = evictionMeters.get(cause);
if (counter != null) {
counter.mark(weight);
Meter evictionMeter = evictionMeters.get(cause);
if (evictionMeter != null) {
evictionMeter.mark();
}
Meter evictionWeightMeter = evictionWeightMeters.get(cause);
if (evictionWeightMeter != null) {
evictionWeightMeter.mark(weight);
}
evictionsTotalMeter.mark(weight);
}

@Override
Expand All @@ -120,9 +153,11 @@ public com.github.benmanes.caffeine.cache.stats.CacheStats snapshot() {
missMeter.getCount(),
loadSuccessTimer.getCount(),
loadFailureTimer.getCount(),
totalLoadNanos.sum(),
evictionsTotalMeter.getCount(),
evictionMeters.values().stream().mapToLong(Counting::getCount).sum());
totalLoadTime.sum(),
evictionMeters.values().stream().mapToLong(Counting::getCount).sum(),
evictionWeightMeters.values().stream()
.mapToLong(Counting::getCount)
.sum());
}

@Override
Expand Down
22 changes: 17 additions & 5 deletions tritium-caffeine/src/main/metrics/cache-metrics.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,30 @@ namespaces:
- name: result
values: [success, failure]
docs: Count of successful cache loads
evictions:
eviction:
type: meter
tags: [cache, cause]
docs: Count of evicted entries by cause
eviction:
docs: Count of evicted entries
eviction.weight:
type: meter
tags: [cache]
docs: Total count of evicted entries
tags: [cache, cause]
docs: Count of evicted weights
stats.disabled:
type: meter
tags: [cache]
docs: |
Registered cache does not have stats recording enabled, stats will always be zero.
To enable cache metrics, stats recording must be enabled when constructing the cache:
Caffeine.newBuilder().recordStats()
estimated.size:
type: gauge
tags: [cache]
docs: Approximate number of entries in this cache
weighted.size:
type: gauge
tags: [cache]
docs: Approximate accumulated weight of entries in this cache
maximum.size:
type: gauge
tags: [cache]
docs: Maximum number of cache entries cache can hold if limited, otherwise -1
Original file line number Diff line number Diff line change
Expand Up @@ -217,28 +217,34 @@ void registerCacheWithoutRecordingStatsTagged() {

@Test
void registerTaggedMetrics() {
Cache<Integer, String> cache = Caffeine.newBuilder()
.recordStats(CacheStats.of(taggedMetricRegistry, "test"))
.maximumSize(2)
.build();
CacheStats cacheStats = CacheStats.of(taggedMetricRegistry, "test");
Cache<Integer, String> cache =
Caffeine.newBuilder().recordStats(cacheStats).maximumSize(2).build();
cacheStats.register(cache);

assertThat(taggedMetricRegistry.getMetrics().keySet())
.extracting(MetricName::safeName)
.containsExactlyInAnyOrder(
"cache.hit",
"cache.miss",
"cache.eviction",
"cache.evictions", // RemovalCause.EXPLICIT
"cache.evictions", // RemovalCause.REPLACED
"cache.evictions", // RemovalCause.COLLECTED
"cache.evictions", // RemovalCause.EXPIRED
"cache.evictions", // RemovalCause.SIZE
"cache.eviction", // RemovalCause.EXPLICIT
"cache.eviction", // RemovalCause.REPLACED
"cache.eviction", // RemovalCause.COLLECTED
"cache.eviction", // RemovalCause.EXPIRED
"cache.eviction", // RemovalCause.SIZE
"cache.eviction.weight", // RemovalCause.EXPLICIT
"cache.eviction.weight", // RemovalCause.REPLACED
"cache.eviction.weight", // RemovalCause.COLLECTED
"cache.eviction.weight", // RemovalCause.EXPIRED
"cache.eviction.weight", // RemovalCause.SIZE
"cache.load", // success
"cache.load" // failure
);
"cache.load", // failure
"cache.estimated.size",
"cache.weighted.size",
"cache.maximum.size");

CacheMetrics cacheMetrics = CacheMetrics.of(taggedMetricRegistry);
assertThat(cacheMetrics.evictions().cache("test").cause("SIZE").build().getCount())
.asInstanceOf(InstanceOfAssertFactories.LONG)
assertThat(cacheMetrics.eviction().cache("test").cause("SIZE").build().getCount())
.isZero();
assertMeter(taggedMetricRegistry, "cache.hit")
.isEqualTo(cacheMetrics.hit("test").getCount())
Expand All @@ -260,8 +266,7 @@ void registerTaggedMetrics() {
.isEqualTo(3);

cache.cleanUp(); // force eviction processing
assertThat(cacheMetrics.evictions().cache("test").cause("SIZE").build().getCount())
.asInstanceOf(InstanceOfAssertFactories.LONG)
assertThat(cacheMetrics.eviction().cache("test").cause("SIZE").build().getCount())
.isOne();

assertThat(taggedMetricRegistry.getMetrics())
Expand All @@ -274,28 +279,33 @@ void registerTaggedMetrics() {

@Test
void registerLoadingTaggedMetrics() {
LoadingCache<Integer, String> cache = Caffeine.newBuilder()
.recordStats(CacheStats.of(taggedMetricRegistry, "test"))
.maximumSize(2)
.build(mapping::apply);
CacheStats cacheStats = CacheStats.of(taggedMetricRegistry, "test");
LoadingCache<Integer, String> cache =
Caffeine.newBuilder().recordStats(cacheStats).maximumSize(2).build(mapping::apply);
cacheStats.register(cache);
assertThat(taggedMetricRegistry.getMetrics().keySet())
.extracting(MetricName::safeName)
.containsExactlyInAnyOrder(
"cache.hit",
"cache.miss",
"cache.eviction",
"cache.evictions", // RemovalCause.EXPLICIT
"cache.evictions", // RemovalCause.REPLACED
"cache.evictions", // RemovalCause.COLLECTED
"cache.evictions", // RemovalCause.EXPIRED
"cache.evictions", // RemovalCause.SIZE
"cache.eviction", // RemovalCause.EXPLICIT
"cache.eviction", // RemovalCause.REPLACED
"cache.eviction", // RemovalCause.COLLECTED
"cache.eviction", // RemovalCause.EXPIRED
"cache.eviction", // RemovalCause.SIZE
"cache.eviction.weight", // RemovalCause.EXPLICIT
"cache.eviction.weight", // RemovalCause.REPLACED
"cache.eviction.weight", // RemovalCause.COLLECTED
"cache.eviction.weight", // RemovalCause.EXPIRED
"cache.eviction.weight", // RemovalCause.SIZE
"cache.load", // success
"cache.load" // failure
);
"cache.load", // failure
"cache.estimated.size",
"cache.weighted.size",
"cache.maximum.size");

CacheMetrics cacheMetrics = CacheMetrics.of(taggedMetricRegistry);
assertThat(cacheMetrics.evictions().cache("test").cause("SIZE").build().getCount())
.asInstanceOf(InstanceOfAssertFactories.LONG)
assertThat(cacheMetrics.eviction().cache("test").cause("SIZE").build().getCount())
.isZero();
assertMeter(taggedMetricRegistry, "cache.hit")
.isEqualTo(cacheMetrics.hit("test").getCount())
Expand All @@ -317,8 +327,7 @@ void registerLoadingTaggedMetrics() {
.isEqualTo(3);

cache.cleanUp(); // force eviction processing
assertThat(cacheMetrics.evictions().cache("test").cause("SIZE").build().getCount())
.asInstanceOf(InstanceOfAssertFactories.LONG)
assertThat(cacheMetrics.eviction().cache("test").cause("SIZE").build().getCount())
.isOne();

assertThat(taggedMetricRegistry.getMetrics())
Expand Down

0 comments on commit 617ebe6

Please sign in to comment.