Expose percentage free space metric for each cache
Expose of the amount of free space in the cache as a percentage.
Feature: Issue 13940
Change-Id: I27c1feb2eb06124ab69fe3d30bdeb72f22a2d383
diff --git a/metrics.md b/metrics.md
new file mode 100644
index 0000000..aeb6679
--- /dev/null
+++ b/metrics.md
@@ -0,0 +1,12 @@
+Metrics
+=============
+
+In addition to the [usual metrics](https://gerrit-review.googlesource.com/Documentation/metrics.html#_caches)
+exposed by caches, chronicle-map emits additional metrics that might be useful
+to monitor the state of the cache:
+
+* cache/chroniclemap/percentagae_free_space_<cache-name>
+ : the amount of free space left in the cache as a percentage.
+
+ See the [official documentation](https://javadoc.io/static/net.openhft/chronicle-map/3.20.83/net/openhft/chronicle/map/ChronicleMap.html#percentageFreeSpace--)
+ for more information.
\ No newline at end of file
diff --git a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheFactory.java b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheFactory.java
index 01bc2cc..174ba03 100644
--- a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheFactory.java
+++ b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/ChronicleMapCacheFactory.java
@@ -20,6 +20,7 @@
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.server.cache.CacheBackend;
import com.google.gerrit.server.cache.PersistentCacheDef;
import com.google.gerrit.server.cache.PersistentCacheFactory;
@@ -41,14 +42,18 @@
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final ChronicleMapCacheConfig.Factory configFactory;
+ private final MetricMaker metricMaker;
private final DynamicMap<Cache<?, ?>> cacheMap;
private final List<ChronicleMapCacheImpl<?, ?>> caches;
private final ScheduledExecutorService cleanup;
@Inject
ChronicleMapCacheFactory(
- ChronicleMapCacheConfig.Factory configFactory, DynamicMap<Cache<?, ?>> cacheMap) {
+ ChronicleMapCacheConfig.Factory configFactory,
+ DynamicMap<Cache<?, ?>> cacheMap,
+ MetricMaker metricMaker) {
this.configFactory = configFactory;
+ this.metricMaker = metricMaker;
this.caches = new LinkedList<>();
this.cacheMap = cacheMap;
this.cleanup =
@@ -74,7 +79,7 @@
in.version());
ChronicleMapCacheImpl<K, V> cache = null;
try {
- cache = new ChronicleMapCacheImpl<>(in, config, null);
+ cache = new ChronicleMapCacheImpl<>(in, config, null, metricMaker);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
@@ -98,7 +103,7 @@
in.version());
ChronicleMapCacheImpl<K, V> cache = null;
try {
- cache = new ChronicleMapCacheImpl<>(in, config, loader);
+ cache = new ChronicleMapCacheImpl<>(in, config, loader, metricMaker);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
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 88c93c8..a7a5589 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
@@ -17,6 +17,8 @@
import com.google.common.cache.CacheLoader;
import com.google.common.cache.CacheStats;
import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.server.cache.PersistentCache;
import com.google.gerrit.server.cache.PersistentCacheDef;
import com.google.gerrit.server.util.time.TimeUtil;
@@ -47,7 +49,10 @@
@SuppressWarnings("unchecked")
ChronicleMapCacheImpl(
- PersistentCacheDef<K, V> def, ChronicleMapCacheConfig config, CacheLoader<K, V> loader)
+ PersistentCacheDef<K, V> def,
+ ChronicleMapCacheConfig config,
+ CacheLoader<K, V> loader,
+ MetricMaker metricMaker)
throws IOException {
this.config = config;
this.loader = loader;
@@ -55,6 +60,8 @@
new InMemoryLRU<>(
(int) Math.max(config.getMaxEntries() * config.getpercentageHotKeys() / 100, 1));
+ ChronicleMapStorageMetrics metrics = new ChronicleMapStorageMetrics(metricMaker);
+
final Class<K> keyClass = (Class<K>) def.keyType().getRawType();
final Class<TimedValue<V>> valueWrapperClass = (Class<TimedValue<V>>) (Class) TimedValue.class;
@@ -101,6 +108,28 @@
config.getMaxBloatFactor(),
store.remainingAutoResizes(),
store.percentageFreeSpace());
+
+ metrics.registerCallBackMetrics(def.name(), store);
+ }
+
+ private static class ChronicleMapStorageMetrics {
+
+ private final MetricMaker metricMaker;
+
+ ChronicleMapStorageMetrics(MetricMaker metricMaker) {
+ this.metricMaker = metricMaker;
+ }
+
+ <K, V> void registerCallBackMetrics(String name, ChronicleMap<K, TimedValue<V>> store) {
+ String PERCENTAGE_FREE_SPACE_METRIC = "cache/chroniclemap/percentage_free_space_" + name;
+
+ metricMaker.newCallbackMetric(
+ PERCENTAGE_FREE_SPACE_METRIC,
+ Long.class,
+ new Description(
+ String.format("The amount of free space in the %s cache as a percentage", name)),
+ () -> (long) store.percentageFreeSpace());
+ }
}
public ChronicleMapCacheConfig getConfig() {
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 124a7f3..e8f8e7d 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
@@ -14,15 +14,26 @@
package com.googlesource.gerrit.modules.cache.chroniclemap;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.MetricRegistry;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.Weigher;
+import com.google.gerrit.acceptance.WaitUtil;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.lifecycle.LifecycleManager;
+import com.google.gerrit.metrics.DisabledMetricMaker;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.metrics.dropwizard.DropWizardMetricMaker;
import com.google.gerrit.server.cache.PersistentCacheDef;
import com.google.gerrit.server.cache.serialize.CacheSerializer;
import com.google.gerrit.server.cache.serialize.StringCacheSerializer;
import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
import com.google.inject.TypeLiteral;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -40,6 +51,8 @@
import org.junit.rules.TemporaryFolder;
public class ChronicleMapCacheTest {
+ @Inject MetricMaker metricMaker;
+ @Inject MetricRegistry metricRegistry;
@Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
private SitePaths sitePaths;
@@ -54,6 +67,18 @@
new FileBasedConfig(
sitePaths.resolve("etc").resolve("gerrit.config").toFile(), FS.DETECTED);
gerritConfig.load();
+
+ setupMetrics();
+ }
+
+ public void setupMetrics() {
+ Injector injector = Guice.createInjector(new DropWizardMetricMaker.ApiModule());
+
+ LifecycleManager mgr = new LifecycleManager();
+ mgr.add(injector);
+ mgr.start();
+
+ injector.injectMembers(this);
}
@Test
@@ -326,6 +351,25 @@
assertThat(cache.runningOutOfFreeSpace()).isFalse();
}
+ @Test
+ public void shouldTriggerPercentageFreeMetric() throws Exception {
+ String cachedValue = UUID.randomUUID().toString();
+ String freeSpaceMetricName = "cache/chroniclemap/percentage_free_space_" + cachedValue;
+ gerritConfig.setInt("cache", cachedValue, "maxEntries", 2);
+ gerritConfig.setInt("cache", cachedValue, "avgKeySize", cachedValue.getBytes().length);
+ gerritConfig.setInt("cache", cachedValue, "avgValueSize", valueSize(cachedValue));
+ gerritConfig.save();
+
+ ChronicleMapCacheImpl<String, String> cache = newCacheWithMetrics(cachedValue);
+
+ assertThat(getMetric(freeSpaceMetricName).getValue()).isEqualTo(100);
+
+ cache.put(cachedValue, cachedValue);
+
+ WaitUtil.waitUntil(
+ () -> (long) getMetric(freeSpaceMetricName).getValue() < 100, Duration.ofSeconds(2));
+ }
+
private int valueSize(String value) {
final TimedValueMarshaller<String> marshaller =
new TimedValueMarshaller<>(StringCacheSerializer.INSTANCE);
@@ -335,6 +379,11 @@
return out.toByteArray().length;
}
+ private ChronicleMapCacheImpl<String, String> newCacheWithMetrics(String cachedValue)
+ throws IOException {
+ return newCache(true, cachedValue, null, null, 1, metricMaker);
+ }
+
private ChronicleMapCacheImpl<String, String> newCache(
Boolean withLoader,
@Nullable String cachedValue,
@@ -342,6 +391,23 @@
@Nullable Duration refreshAfterWrite,
Integer version)
throws IOException {
+ return newCache(
+ withLoader,
+ cachedValue,
+ expireAfterWrite,
+ refreshAfterWrite,
+ version,
+ new DisabledMetricMaker());
+ }
+
+ private ChronicleMapCacheImpl<String, String> newCache(
+ Boolean withLoader,
+ @Nullable String cachedValue,
+ @Nullable Duration expireAfterWrite,
+ @Nullable Duration refreshAfterWrite,
+ Integer version,
+ MetricMaker metricMaker)
+ throws IOException {
TestPersistentCacheDef cacheDef = new TestPersistentCacheDef(cachedValue);
ChronicleMapCacheConfig config =
@@ -355,7 +421,8 @@
refreshAfterWrite != null ? refreshAfterWrite : Duration.ZERO,
version);
- return new ChronicleMapCacheImpl<>(cacheDef, config, withLoader ? cacheDef.loader() : null);
+ return new ChronicleMapCacheImpl<>(
+ cacheDef, config, withLoader ? cacheDef.loader() : null, metricMaker);
}
private ChronicleMapCacheImpl<String, String> newCacheWithLoader(@Nullable String cachedValue)
@@ -375,11 +442,18 @@
return newCache(false, null, null, null, 1);
}
+ private <V> Gauge<V> getMetric(String name) {
+ @SuppressWarnings("unchecked")
+ Gauge<V> gauge = (Gauge<V>) metricRegistry.getMetrics().get(name);
+ assertWithMessage(name).that(gauge).isNotNull();
+ return gauge;
+ }
+
public static class TestPersistentCacheDef implements PersistentCacheDef<String, String> {
private final String loadedValue;
- TestPersistentCacheDef(@Nullable String loadedValue) {
+ TestPersistentCacheDef(String loadedValue) {
this.loadedValue = loadedValue;
}
@@ -406,7 +480,7 @@
@Override
public String name() {
- return "foo";
+ return loadedValue;
}
@Override