Expose max auto resizes metric

Currently, only the number of auto resizes is exposed as a metric.
When this value reaches 0 it is no longer possible to
expand the cache.

However, it is not possible to know the maximum number of auto-resizes
that the cache could go through.

Expose the maximum number of auto-resizes as a metric, so that can be
used together with the remaining auto-resizes to evaluate the overall
utilization of the cache.

Bug: Issue 15458
Change-Id: Ie81684950a28f3e2420bddfcaa10b7db218d00fb
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 04ef4d7..76ae8a4 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
@@ -66,7 +66,7 @@
     this.mem = memLoader.asInMemoryCacheBypass();
 
     ChronicleMapStorageMetrics metrics = new ChronicleMapStorageMetrics(new DisabledMetricMaker());
-    metrics.registerCallBackMetrics(def.name(), store, hotEntries);
+    metrics.registerCallBackMetrics(def.name(), this);
   }
 
   ChronicleMapCacheImpl(
@@ -87,7 +87,7 @@
     this.store = store;
 
     ChronicleMapStorageMetrics metrics = new ChronicleMapStorageMetrics(metricMaker);
-    metrics.registerCallBackMetrics(def.name(), store, hotEntries);
+    metrics.registerCallBackMetrics(def.name(), this);
   }
 
   @SuppressWarnings({"unchecked", "cast", "rawtypes"})
@@ -153,13 +153,13 @@
       this.metricMaker = metricMaker;
     }
 
-    <K, V> void registerCallBackMetrics(
-        String name, ChronicleMap<KeyWrapper<K>, TimedValue<V>> store, InMemoryLRU<K> hotEntries) {
+    <K, V> void registerCallBackMetrics(String name, ChronicleMapCacheImpl<K, V> cache) {
       String sanitizedName = metricMaker.sanitizeMetricName(name);
       String PERCENTAGE_FREE_SPACE_METRIC =
           "cache/chroniclemap/percentage_free_space_" + sanitizedName;
       String REMAINING_AUTORESIZES_METRIC =
           "cache/chroniclemap/remaining_autoresizes_" + sanitizedName;
+      String MAX_AUTORESIZES_METRIC = "cache/chroniclemap/max_autoresizes_" + sanitizedName;
       String HOT_KEYS_CAPACITY_METRIC = "cache/chroniclemap/hot_keys_capacity_" + sanitizedName;
       String HOT_KEYS_SIZE_METRIC = "cache/chroniclemap/hot_keys_size_" + sanitizedName;
 
@@ -168,7 +168,7 @@
           Long.class,
           new Description(
               String.format("The amount of free space in the %s cache as a percentage", name)),
-          () -> (long) store.percentageFreeSpace());
+          () -> (long) cache.store.percentageFreeSpace());
 
       metricMaker.newCallbackMetric(
           REMAINING_AUTORESIZES_METRIC,
@@ -176,11 +176,11 @@
           new Description(
               String.format(
                   "The number of times the %s cache can automatically expand its capacity", name)),
-          store::remainingAutoResizes);
+          cache.store::remainingAutoResizes);
 
       metricMaker.newConstantMetric(
           HOT_KEYS_CAPACITY_METRIC,
-          hotEntries.getCapacity(),
+          cache.hotEntries.getCapacity(),
           new Description(
               String.format(
                   "The number of hot cache keys for %s cache that can be kept in memory", name)));
@@ -191,7 +191,15 @@
           new Description(
               String.format(
                   "The number of hot cache keys for %s cache that are currently in memory", name)),
-          hotEntries::size);
+          cache.hotEntries::size);
+
+      metricMaker.newConstantMetric(
+          MAX_AUTORESIZES_METRIC,
+          cache.maxAutoResizes(),
+          new Description(
+              String.format(
+                  "The maximum number of times the %s cache can automatically expand its capacity",
+                  name)));
     }
   }
 
@@ -415,9 +423,13 @@
      * (`getExtraTiersInUse`) shows the overall percentage.
      */
     VanillaChronicleMap vanillaStore = (VanillaChronicleMap) store;
-    double maxResizes = config.getMaxBloatFactor() * vanillaStore.actualSegments;
     long usedResizes = vanillaStore.globalMutableState().getExtraTiersInUse();
-    return usedResizes * 100 / maxResizes;
+    return usedResizes * 100 / maxAutoResizes();
+  }
+
+  @SuppressWarnings("rawtypes")
+  public double maxAutoResizes() {
+    return config.getMaxBloatFactor() * ((VanillaChronicleMap) store).actualSegments;
   }
 
   public String name() {
diff --git a/src/main/resources/Documentation/metrics.md b/src/main/resources/Documentation/metrics.md
index 8e176ef..013a6c8 100644
--- a/src/main/resources/Documentation/metrics.md
+++ b/src/main/resources/Documentation/metrics.md
@@ -17,6 +17,9 @@
   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.
 
+* cache/chroniclemap/max_autoresizes_<cache-name>
+  : The maximum number of times the cache can automatically expand its capacity.
+
 * cache/chroniclemap/hot_keys_capacity_<cache-name>
   : Constant number of hot keys for the cache that can be kept in memory.
 
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 91ddbe3..6cc4c05 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
@@ -403,6 +403,21 @@
   }
 
   @Test
+  public void shouldTriggerMaxAutoResizeMetric() throws Exception {
+    String cachedValue = UUID.randomUUID().toString();
+    String maxAutoResizeMetricName = "cache/chroniclemap/max_autoresizes_" + testCacheName;
+    gerritConfig.setInt("cache", testCacheName, "maxEntries", 2);
+    gerritConfig.setInt("cache", testCacheName, "avgKeySize", cachedValue.getBytes().length);
+    gerritConfig.setInt("cache", testCacheName, "avgValueSize", valueSize(cachedValue));
+    gerritConfig.setInt("cache", testCacheName, "maxBloatFactor", 3);
+    gerritConfig.save();
+
+    newCacheWithMetrics(testCacheName, cachedValue);
+
+    assertThat(getMetric(maxAutoResizeMetricName).getValue()).isEqualTo(3);
+  }
+
+  @Test
   public void shouldTriggerHotKeysCapacityCacheMetric() throws Exception {
     String cachedValue = UUID.randomUUID().toString();
     int percentageHotKeys = 60;
@@ -487,6 +502,7 @@
     String hotKeySizeMetricName = "cache/chroniclemap/hot_keys_size_" + sanitized;
     String percentageFreeMetricName = "cache/chroniclemap/percentage_free_space_" + sanitized;
     String autoResizeMetricName = "cache/chroniclemap/remaining_autoresizes_" + sanitized;
+    String maxAutoResizeMetricName = "cache/chroniclemap/max_autoresizes_" + sanitized;
     String hotKeyCapacityMetricName = "cache/chroniclemap/hot_keys_capacity_" + sanitized;
 
     newCacheWithMetrics(cacheName, null);
@@ -494,6 +510,7 @@
     getMetric(hotKeySizeMetricName);
     getMetric(percentageFreeMetricName);
     getMetric(autoResizeMetricName);
+    getMetric(maxAutoResizeMetricName);
     getMetric(hotKeyCapacityMetricName);
   }