Expose hot keys metrics Expose metrics related to the hot keys in memory cache: - Capacity: number of hot keys that can be kept in memory. - Size: number of hot keys that are currently in memory. Feature: Issue 13940 Change-Id: I8813eb5f404289aa57a2ad008d5cfa5a5bf62e13
diff --git a/metrics.md b/metrics.md index 85e2745..8e176ef 100644 --- a/metrics.md +++ b/metrics.md
@@ -15,4 +15,10 @@ : the number of times the cache can automatically expand its capacity. See the [official documentation](https://javadoc.io/static/net.openhft/chronicle-map/3.20.83/net/openhft/chronicle/map/ChronicleMap.html#remainingAutoResizes--) - for more information. \ No newline at end of file + for more information. + +* cache/chroniclemap/hot_keys_capacity_<cache-name> + : Constant number of hot keys for the cache that can be kept in memory. + +* cache/chroniclemap/hot_keys_size_<cache-name> + : The number of hot keys for the cache that are currently in memory. \ No newline at end of file
diff --git a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheImpl.java b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheImpl.java index 6b95702..f3f6483 100644 --- a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheImpl.java +++ b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheImpl.java
@@ -109,7 +109,7 @@ store.remainingAutoResizes(), store.percentageFreeSpace()); - metrics.registerCallBackMetrics(def.name(), store); + metrics.registerCallBackMetrics(def.name(), store, hotEntries); } private static class ChronicleMapStorageMetrics { @@ -120,9 +120,12 @@ this.metricMaker = metricMaker; } - <K, V> void registerCallBackMetrics(String name, ChronicleMap<K, TimedValue<V>> store) { + <K, V> void registerCallBackMetrics( + String name, ChronicleMap<K, TimedValue<V>> store, InMemoryLRU<K> hotEntries) { String PERCENTAGE_FREE_SPACE_METRIC = "cache/chroniclemap/percentage_free_space_" + name; String REMAINING_AUTORESIZES_METRIC = "cache/chroniclemap/remaining_autoresizes_" + name; + String HOT_KEYS_CAPACITY_METRIC = "cache/chroniclemap/hot_keys_capacity_" + name; + String HOT_KEYS_SIZE_METRIC = "cache/chroniclemap/hot_keys_size_" + name; metricMaker.newCallbackMetric( PERCENTAGE_FREE_SPACE_METRIC, @@ -138,6 +141,21 @@ String.format( "The number of times the %s cache can automatically expand its capacity", name)), store::remainingAutoResizes); + + metricMaker.newConstantMetric( + HOT_KEYS_CAPACITY_METRIC, + hotEntries.getCapacity(), + new Description( + String.format( + "The number of hot cache keys for %s cache that can be kept in memory", name))); + + metricMaker.newCallbackMetric( + HOT_KEYS_SIZE_METRIC, + Integer.class, + new Description( + String.format( + "The number of hot cache keys for %s cache that are currently in memory", name)), + hotEntries::size); } }
diff --git a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/InMemoryLRU.java b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/InMemoryLRU.java index 150ab18..ac5183e 100644 --- a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/InMemoryLRU.java +++ b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/InMemoryLRU.java
@@ -23,8 +23,10 @@ private final Map<K, Boolean> LRUMap; private static final Boolean dummyValue = Boolean.TRUE; + private final int capacity; public InMemoryLRU(int capacity) { + this.capacity = capacity; LRUMap = Collections.synchronizedMap( @@ -52,8 +54,16 @@ LRUMap.clear(); } + public int size() { + return LRUMap.size(); + } + @VisibleForTesting protected Object[] toArray() { return LRUMap.keySet().toArray(); } + + public int getCapacity() { + return capacity; + } }
diff --git a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheTest.java b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheTest.java index 459e2f2..e8e6236 100644 --- a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheTest.java +++ b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheTest.java
@@ -391,6 +391,84 @@ () -> (int) getMetric(autoResizeMetricName).getValue() == 0, Duration.ofSeconds(2)); } + @Test + public void shouldTriggerHotKeysCapacityCacheMetric() throws Exception { + String cachedValue = UUID.randomUUID().toString(); + int percentageHotKeys = 60; + int maxEntries = 10; + int expectedCapacity = 6; + String hotKeysCapacityMetricName = "cache/chroniclemap/hot_keys_capacity_" + cachedValue; + gerritConfig.setInt("cache", cachedValue, "maxEntries", maxEntries); + gerritConfig.setInt("cache", cachedValue, "percentageHotKeys", percentageHotKeys); + gerritConfig.save(); + + ChronicleMapCacheImpl<String, String> cache = newCacheWithMetrics(cachedValue); + + assertThat(getMetric(hotKeysCapacityMetricName).getValue()).isEqualTo(expectedCapacity); + } + + @Test + public void shouldTriggerHotKeysSizeCacheMetric() throws Exception { + String cachedValue = UUID.randomUUID().toString(); + int percentageHotKeys = 30; + int maxEntries = 10; + int maxHotKeyCapacity = 3; + final Duration METRIC_TRIGGER_TIMEOUT = Duration.ofSeconds(2); + String hotKeysSizeMetricName = "cache/chroniclemap/hot_keys_size_" + cachedValue; + gerritConfig.setInt("cache", cachedValue, "maxEntries", maxEntries); + gerritConfig.setInt("cache", cachedValue, "percentageHotKeys", percentageHotKeys); + gerritConfig.save(); + + ChronicleMapCacheImpl<String, String> cache = newCacheWithMetrics(cachedValue); + + assertThat(getMetric(hotKeysSizeMetricName).getValue()).isEqualTo(0); + + for (int i = 0; i < maxHotKeyCapacity; i++) { + cache.put(cachedValue + i, cachedValue); + } + + WaitUtil.waitUntil( + () -> (int) getMetric(hotKeysSizeMetricName).getValue() == maxHotKeyCapacity, + METRIC_TRIGGER_TIMEOUT); + + cache.put(cachedValue + maxHotKeyCapacity + 1, cachedValue); + + assertThrows( + InterruptedException.class, + () -> + WaitUtil.waitUntil( + () -> (int) getMetric(hotKeysSizeMetricName).getValue() > maxHotKeyCapacity, + METRIC_TRIGGER_TIMEOUT)); + } + + @Test + public void shouldResetHotKeysWhenInvalidateAll() throws Exception { + String cachedValue = UUID.randomUUID().toString(); + int percentageHotKeys = 30; + int maxEntries = 10; + int maxHotKeyCapacity = 3; + final Duration METRIC_TRIGGER_TIMEOUT = Duration.ofSeconds(2); + String hotKeysSizeMetricName = "cache/chroniclemap/hot_keys_size_" + cachedValue; + gerritConfig.setInt("cache", cachedValue, "maxEntries", maxEntries); + gerritConfig.setInt("cache", cachedValue, "percentageHotKeys", percentageHotKeys); + gerritConfig.save(); + + ChronicleMapCacheImpl<String, String> cache = newCacheWithMetrics(cachedValue); + + for (int i = 0; i < maxHotKeyCapacity; i++) { + cache.put(cachedValue + i, cachedValue); + } + + WaitUtil.waitUntil( + () -> (int) getMetric(hotKeysSizeMetricName).getValue() == maxHotKeyCapacity, + METRIC_TRIGGER_TIMEOUT); + + cache.invalidateAll(); + + WaitUtil.waitUntil( + () -> (int) getMetric(hotKeysSizeMetricName).getValue() == 0, METRIC_TRIGGER_TIMEOUT); + } + private int valueSize(String value) { final TimedValueMarshaller<String> marshaller = new TimedValueMarshaller<>(StringCacheSerializer.INSTANCE);