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