Add cache keys index metrics
The following metrics were added foe each cache:
* cache/chroniclemap/keys_index_size_<cache-name> for showing the
current size of the index (that one is one to one with disk entries)
* cache/chroniclemap/keys_index_add_latency_<cache-name> to indicate how
much time it takes to add key to the index over time
* cache/chroniclemap/keys_index_remove_and_consume_older_than_latency_<cache-name>
to indicate how much time it takes to remove and consume all keys
older than expiration time
* cache/chroniclemap/keys_index_remove_lru_key_latency_<cache-name> to
indicate how much time it takes to remove and consumer LRU key
Bug: Issue 15121
Change-Id: I8141c0f00dfe6e7dc3b06517c3383d19fe883492
diff --git a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/CacheKeysIndex.java b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/CacheKeysIndex.java
index 34e3eac..a6f608d 100644
--- a/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/CacheKeysIndex.java
+++ b/src/main/java/com/googlesource/gerrit/modules/cache/chroniclemap/CacheKeysIndex.java
@@ -18,6 +18,10 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.errorprone.annotations.CompatibleWith;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.Description.Units;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.metrics.Timer0;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Objects;
@@ -63,10 +67,62 @@
}
}
- private final Set<TimedKey> keys;
+ private class Metrics {
+ private final Timer0 addLatency;
+ private final Timer0 removeAndConsumeOlderThanLatency;
+ private final Timer0 removeAndConsumeLruKeyLatency;
- CacheKeysIndex() {
+ private Metrics(MetricMaker metricMaker, String name) {
+ String sanitizedName = metricMaker.sanitizeMetricName(name);
+
+ metricMaker.newCallbackMetric(
+ "cache/chroniclemap/keys_index_size_" + sanitizedName,
+ Integer.class,
+ new Description(
+ String.format(
+ "The number of cache index keys for %s cache that are currently in memory",
+ name)),
+ keys::size);
+
+ addLatency =
+ metricMaker.newTimer(
+ "cache/chroniclemap/keys_index_add_latency_" + sanitizedName,
+ new Description(
+ String.format("The latency of adding key to the index for %s cache", name))
+ .setCumulative()
+ .setUnit(Units.NANOSECONDS));
+
+ removeAndConsumeOlderThanLatency =
+ metricMaker.newTimer(
+ "cache/chroniclemap/keys_index_remove_and_consume_older_than_latency_"
+ + sanitizedName,
+ new Description(
+ String.format(
+ "The latency of removing and consuming all keys older than expiration"
+ + " time for the index for %s cache",
+ name))
+ .setCumulative()
+ .setUnit(Units.NANOSECONDS));
+
+ removeAndConsumeLruKeyLatency =
+ metricMaker.newTimer(
+ "cache/chroniclemap/keys_index_remove_lru_key_latency_" + sanitizedName,
+ new Description(
+ String.format(
+ "The latency of removing and consuming LRU key from the index for %s"
+ + " cache",
+ name))
+ .setCumulative()
+ .setUnit(Units.NANOSECONDS));
+ }
+ }
+
+ private final Set<TimedKey> keys;
+ private final Metrics metrics;
+
+ CacheKeysIndex(MetricMaker metricMaker, String name) {
this.keys = Collections.synchronizedSet(new LinkedHashSet<>());
+ this.metrics = new Metrics(metricMaker, name);
}
@SuppressWarnings("unchecked")
@@ -75,8 +131,10 @@
TimedKey timedKey = new TimedKey((T) key, created);
// bubble up MRU key by re-adding it to a set
- keys.remove(timedKey);
- keys.add(timedKey);
+ try (Timer0.Context timer = metrics.addLatency.start()) {
+ keys.remove(timedKey);
+ keys.add(timedKey);
+ }
}
void refresh(T key) {
@@ -84,32 +142,36 @@
}
void removeAndConsumeKeysOlderThan(long time, Consumer<T> consumer) {
- Set<TimedKey> toRemoveAndConsume;
- synchronized (keys) {
- toRemoveAndConsume =
- keys.stream().filter(key -> key.created < time).collect(toUnmodifiableSet());
+ try (Timer0.Context timer = metrics.removeAndConsumeOlderThanLatency.start()) {
+ Set<TimedKey> toRemoveAndConsume;
+ synchronized (keys) {
+ toRemoveAndConsume =
+ keys.stream().filter(key -> key.created < time).collect(toUnmodifiableSet());
+ }
+ toRemoveAndConsume.forEach(
+ key -> {
+ keys.remove(key);
+ consumer.accept(key.getKey());
+ });
}
- toRemoveAndConsume.forEach(
- key -> {
- keys.remove(key);
- consumer.accept(key.getKey());
- });
}
boolean removeAndConsumeLruKey(Consumer<T> consumer) {
- Optional<TimedKey> lruKey;
- synchronized (keys) {
- lruKey = keys.stream().findFirst();
- }
+ try (Timer0.Context timer = metrics.removeAndConsumeLruKeyLatency.start()) {
+ Optional<TimedKey> lruKey;
+ synchronized (keys) {
+ lruKey = keys.stream().findFirst();
+ }
- return lruKey
- .map(
- key -> {
- keys.remove(key);
- consumer.accept(key.getKey());
- return true;
- })
- .orElse(false);
+ return lruKey
+ .map(
+ key -> {
+ keys.remove(key);
+ consumer.accept(key.getKey());
+ return true;
+ })
+ .orElse(false);
+ }
}
void invalidate(@CompatibleWith("T") Object key) {
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 98d7f42..1161699 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
@@ -117,7 +117,11 @@
cache =
new ChronicleMapCacheImpl<>(
- in, config, memLoader, new InMemoryCacheLoadingFromStoreImpl<>(mem, false));
+ in,
+ config,
+ metricMaker,
+ memLoader,
+ new InMemoryCacheLoadingFromStoreImpl<>(mem, false));
} catch (IOException e) {
throw new UncheckedIOException(e);
@@ -165,7 +169,11 @@
cache =
new ChronicleMapCacheImpl<>(
- in, config, memLoader, new InMemoryCacheLoadingFromStoreImpl<>(mem, true));
+ in,
+ config,
+ metricMaker,
+ memLoader,
+ new InMemoryCacheLoadingFromStoreImpl<>(mem, true));
} 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 12a463b..3740959 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
@@ -13,7 +13,6 @@
// limitations under the License.
package com.googlesource.gerrit.modules.cache.chroniclemap;
-import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.AbstractLoadingCache;
import com.google.common.cache.CacheStats;
import com.google.common.flogger.FluentLogger;
@@ -27,7 +26,6 @@
import java.sql.Timestamp;
import java.time.Duration;
import java.time.Instant;
-import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.LongAdder;
@@ -57,7 +55,7 @@
this.cacheDefinition = def;
this.config = config;
- this.keysIndex = new CacheKeysIndex<>();
+ this.keysIndex = new CacheKeysIndex<>(metricMaker, def.name());
this.store = createOrRecoverStore(def, config, metricMaker);
this.memLoader =
new ChronicleMapCacheLoader<>(
@@ -68,12 +66,13 @@
ChronicleMapCacheImpl(
PersistentCacheDef<K, V> def,
ChronicleMapCacheConfig config,
+ MetricMaker metricMaker,
ChronicleMapCacheLoader<K, V> memLoader,
InMemoryCache<K, V> mem) {
this.cacheDefinition = def;
this.config = config;
- this.keysIndex = new CacheKeysIndex<>();
+ this.keysIndex = new CacheKeysIndex<>(metricMaker, def.name());
this.memLoader = memLoader;
this.mem = mem;
this.store = memLoader.getStore();
@@ -342,9 +341,4 @@
public String name() {
return store.name();
}
-
- @VisibleForTesting
- Set<CacheKeysIndex<K>.TimedKey> keys() {
- return keysIndex.keys();
- }
}
diff --git a/src/main/resources/Documentation/metrics.md b/src/main/resources/Documentation/metrics.md
index 565fe62..b0bc902 100644
--- a/src/main/resources/Documentation/metrics.md
+++ b/src/main/resources/Documentation/metrics.md
@@ -20,5 +20,17 @@
* cache/chroniclemap/max_autoresizes_<cache-name>
: The maximum number of times the cache can automatically expand its capacity.
+* cache/chroniclemap/keys_index_size_<cache-name>
+ : The number of index keys for the cache that are currently in memory.
+
+* cache/chroniclemap/keys_index_add_latency_<cache-name>
+ : The latency of adding cache key to an index.
+
+* cache/chroniclemap/keys_index_remove_and_consume_older_than_latency_<cache-name>
+ : The latency of removing and consuming all keys older than expiration time for an index.
+
+* cache/chroniclemap/keys_index_remove_lru_key_latency_<cache-name>
+ : The latency of removing and consuming LRU key from an index.
+
* "cache/chroniclemap/store_put_failures_<cache-name>
: The number of errors caught when inserting entries in chronicle-map store
\ No newline at end of file
diff --git a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/CacheKeysIndexTest.java b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/CacheKeysIndexTest.java
index 405e5a5..27c9e76 100644
--- a/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/CacheKeysIndexTest.java
+++ b/src/test/java/com/googlesource/gerrit/modules/cache/chroniclemap/CacheKeysIndexTest.java
@@ -20,6 +20,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
+import com.google.gerrit.metrics.DisabledMetricMaker;
import java.util.List;
import java.util.function.Consumer;
import org.junit.Before;
@@ -30,7 +31,7 @@
@Before
public void setup() {
- index = new CacheKeysIndex<>();
+ index = new CacheKeysIndex<>(new DisabledMetricMaker(), "test-cache");
}
@Test
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 ecc457a..9c839f0 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
@@ -20,6 +20,7 @@
import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.UseLocalDisk;
import com.google.gerrit.acceptance.WaitUtil;
@@ -455,22 +456,69 @@
}
@Test
- public void shouldResetKeysIndexWhenInvalidateAll() throws Exception {
+ public void shouldTriggerKeysIndexSizeCacheMetric() throws Exception {
String cachedValue = UUID.randomUUID().toString();
int maxEntries = 10;
- int maxHotKeyCapacity = 3;
+ int expectedKeysSize = 3;
+ final Duration METRIC_TRIGGER_TIMEOUT = Duration.ofSeconds(2);
+ String keysIndexSizeMetricName = "cache/chroniclemap/keys_index_size_" + testCacheName;
gerritConfig.setInt("cache", testCacheName, "maxEntries", maxEntries);
gerritConfig.save();
ChronicleMapCacheImpl<String, String> cache = newCacheWithMetrics(testCacheName, cachedValue);
- for (int i = 0; i < maxHotKeyCapacity; i++) {
+ assertThat(getMetric(keysIndexSizeMetricName).getValue()).isEqualTo(0);
+
+ for (int i = 0; i < expectedKeysSize; i++) {
cache.put(cachedValue + i, cachedValue);
}
- assertThat(cache.keys()).isNotEmpty();
+ WaitUtil.waitUntil(
+ () -> (int) getMetric(keysIndexSizeMetricName).getValue() == expectedKeysSize,
+ METRIC_TRIGGER_TIMEOUT);
+ }
+
+ @Test
+ public void shouldTriggerKeysIndexAddLatencyCacheMetric() throws Exception {
+ int maxEntries = 10;
+ final Duration METRIC_TRIGGER_TIMEOUT = Duration.ofSeconds(2);
+ String keysIndexAddLatencyMetricName =
+ "cache/chroniclemap/keys_index_add_latency_" + testCacheName;
+ gerritConfig.setInt("cache", testCacheName, "maxEntries", maxEntries);
+ gerritConfig.save();
+
+ ChronicleMapCacheImpl<String, String> cache = newCacheWithMetrics(testCacheName, null);
+ assertThat(getTimer(keysIndexAddLatencyMetricName).getCount()).isEqualTo(0L);
+
+ String cachedValue = UUID.randomUUID().toString();
+ cache.put(cachedValue, cachedValue);
+
+ WaitUtil.waitUntil(
+ () -> getTimer(keysIndexAddLatencyMetricName).getCount() == 1L, METRIC_TRIGGER_TIMEOUT);
+ }
+
+ @Test
+ public void shouldResetKeysIndexWhenInvalidateAll() throws Exception {
+ String cachedValue = UUID.randomUUID().toString();
+ int maxEntries = 10;
+ int expectedKeysSize = 3;
+ final Duration METRIC_TRIGGER_TIMEOUT = Duration.ofSeconds(2);
+ String keysIndexSizeMetricName = "cache/chroniclemap/keys_index_size_" + testCacheName;
+ gerritConfig.setInt("cache", testCacheName, "maxEntries", maxEntries);
+ gerritConfig.save();
+
+ ChronicleMapCacheImpl<String, String> cache = newCacheWithMetrics(testCacheName, cachedValue);
+
+ for (int i = 0; i < expectedKeysSize; i++) {
+ cache.put(cachedValue + i, cachedValue);
+ }
+
+ WaitUtil.waitUntil(
+ () -> (int) getMetric(keysIndexSizeMetricName).getValue() == expectedKeysSize,
+ METRIC_TRIGGER_TIMEOUT);
cache.invalidateAll();
- assertThat(cache.keys()).isEmpty();
+ WaitUtil.waitUntil(
+ () -> (int) getMetric(keysIndexSizeMetricName).getValue() == 0, METRIC_TRIGGER_TIMEOUT);
}
@Test
@@ -480,12 +528,22 @@
String percentageFreeMetricName = "cache/chroniclemap/percentage_free_space_" + sanitized;
String autoResizeMetricName = "cache/chroniclemap/remaining_autoresizes_" + sanitized;
String maxAutoResizeMetricName = "cache/chroniclemap/max_autoresizes_" + sanitized;
+ String keysIndexSizeMetricName = "cache/chroniclemap/keys_index_size_" + sanitized;
+ String keysIndexAddLatencyMetricName = "cache/chroniclemap/keys_index_add_latency_" + sanitized;
+ String keysIndexRemoveOlderThanLatencyMetricName =
+ "cache/chroniclemap/keys_index_remove_and_consume_older_than_latency_" + sanitized;
+ String keysIndexRemoveLruLatencyMetricName =
+ "cache/chroniclemap/keys_index_remove_lru_key_latency_" + sanitized;
newCacheWithMetrics(cacheName, null);
getMetric(percentageFreeMetricName);
getMetric(autoResizeMetricName);
getMetric(maxAutoResizeMetricName);
+ getMetric(keysIndexSizeMetricName);
+ getTimer(keysIndexAddLatencyMetricName);
+ getTimer(keysIndexRemoveOlderThanLatencyMetricName);
+ getTimer(keysIndexRemoveLruLatencyMetricName);
}
private int valueSize(String value) {
@@ -588,4 +646,10 @@
assertWithMessage(name).that(counter).isNotNull();
return counter;
}
+
+ private Timer getTimer(String name) {
+ Timer timer = (Timer) metricRegistry.getMetrics().get(name);
+ assertWithMessage(name).that(timer).isNotNull();
+ return timer;
+ }
}