Skip to content

Commit

Permalink
8469: New VirtualMap metric: virtual node cache size, in Mb (#8755)
Browse files Browse the repository at this point in the history
Fixes: #8469
Reviewed-by: Joseph Sinclair <[email protected]>
Signed-off-by: Artem Ananev <[email protected]>
  • Loading branch information
artemananiev authored Sep 21, 2023
1 parent ab80cb2 commit 108dd18
Show file tree
Hide file tree
Showing 5 changed files with 294 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public class VirtualMapStatistics {
/** Virtual map entities - reads / s */
private LongAccumulator readEntities;

/** Estimated virtual node cache size, Mb*/
private LongGauge nodeCacheSizeMb;
/** Number of virtual root copies in the pipeline */
private IntegerGauge pipelineSize;
/** The number of virtual root node copies in virtual pipeline flush backlog */
Expand Down Expand Up @@ -133,6 +135,9 @@ public void registerMetrics(final Metrics metrics) {
"Read virtual map entities, " + label + ", per second");

// Lifecycle
nodeCacheSizeMb = metrics.getOrCreate(
new LongGauge.Config(STAT_CATEGORY, VMAP_PREFIX + LIFECYCLE_PREFIX + "nodeCacheSizeMb_" + label)
.withDescription("Virtual node cache size, " + label + ", Mb"));
pipelineSize = metrics.getOrCreate(
new IntegerGauge.Config(STAT_CATEGORY, VMAP_PREFIX + LIFECYCLE_PREFIX + "pipelineSize_" + label)
.withDescription("Virtual pipeline size, " + label));
Expand Down Expand Up @@ -211,6 +216,17 @@ public void countReadEntities() {
}
}

/**
* Updates {@link #nodeCacheSizeMb} stat to the given value.
*
* @param value the value to set
*/
public void setNodeCacheSize(final long value) {
if (this.nodeCacheSizeMb != null) {
this.nodeCacheSizeMb.set(value);
}
}

/**
* Updates {@link #pipelineSize} stat to the given value.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1052,9 +1052,11 @@ long getFlushThreshold() {
*/
@Override
public boolean shouldBeFlushed() {
// Check if this copy was explicitly marked to flush
if (shouldBeFlushed.get()) {
return true;
}
// Otherwise check its size and compare against flush threshold
final long threshold = flushThreshold.get();
return (threshold > 0) && (estimatedSize() >= threshold);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,6 @@ private long currentTotalSize() {
for (PipelineListNode<VirtualRoot> node = copies.getFirst(); node != null; node = node.getNext()) {
final VirtualRoot copy = node.getValue();
if (!copy.isImmutable()) {
assert node.getNext() == null;
break;
}
final long estimatedSize = copy.estimatedSize();
Expand Down Expand Up @@ -562,8 +561,10 @@ private void hashFlushMerge() {
logger.debug(VIRTUAL_MERKLE_STATS.getMarker(), "Merge {}", copy.getFastCopyVersion());
merge(next);
copies.remove(next);
statistics.setPipelineSize(copies.getSize());
}
statistics.setPipelineSize(copies.getSize());
final long totalSize = currentTotalSize();
statistics.setNodeCacheSize(totalSize);
next = next.getNext();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/*
* Copyright (C) 2023 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.swirlds.virtualmap;

import static com.swirlds.common.metrics.Metric.ValueType.VALUE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.swirlds.common.metrics.Metric;
import com.swirlds.common.metrics.Metrics;
import com.swirlds.common.metrics.config.MetricsConfig;
import com.swirlds.common.metrics.platform.DefaultMetrics;
import com.swirlds.common.metrics.platform.DefaultMetricsFactory;
import com.swirlds.common.metrics.platform.MetricKeyRegistry;
import com.swirlds.config.api.Configuration;
import com.swirlds.test.framework.config.TestConfigBuilder;
import com.swirlds.virtualmap.internal.merkle.VirtualMapStatistics;
import java.util.concurrent.ScheduledExecutorService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class VirtualMapStatisticsTest {

private static final String LABEL = "VMST";

private VirtualMapStatistics statistics;
private Metrics metrics;

private Metric getMetric(final String section, final String suffix) {
return getMetric(metrics, "vmap_" + section + suffix);
}

private Metric getMetric(final Metrics metrics, final String name) {
return metrics.getMetric(VirtualMapStatistics.STAT_CATEGORY, name);
}

private static void assertValueSet(final Metric metric) {
assertNotEquals(0.0, metric.get(VALUE));
}

private static <T> void assertValueEquals(final Metric metric, final T value) {
assertEquals(value, metric.get(VALUE));
}

@BeforeEach
void setupTest() {
final Configuration configuration = new TestConfigBuilder().getOrCreateConfig();
final MetricsConfig metricsConfig = configuration.getConfigData(MetricsConfig.class);

final MetricKeyRegistry registry = mock(MetricKeyRegistry.class);
when(registry.register(any(), any(), any())).thenReturn(true);
metrics = new DefaultMetrics(
null,
registry,
mock(ScheduledExecutorService.class),
new DefaultMetricsFactory(metricsConfig),
metricsConfig);
statistics = new VirtualMapStatistics(LABEL);
statistics.registerMetrics(metrics);
}

@Test
void testSetSize() {
// given
final Metric metric = getMetric(metrics, "vmap_size_" + LABEL);
// when
statistics.setSize(12345678L);
// then
assertValueEquals(metric, 12345678L);
}

@Test
void testCountAddedEntities() {
// given
final Metric metric = getMetric("queries_", "addedEntities_" + LABEL);
// when
statistics.countAddedEntities();
// then
assertValueSet(metric);
}

@Test
void testCountUpdatedEntities() {
// given
final Metric metric = getMetric("queries_", "updatedEntities_" + LABEL);
// when
statistics.countUpdatedEntities();
// then
assertValueSet(metric);
}

@Test
void testCountRemovedEntities() {
// given
final Metric metric = getMetric("queries_", "removedEntities_" + LABEL);
// when
statistics.countRemovedEntities();
// then
assertValueSet(metric);
}

@Test
void testCountReadEntities() {
// given
final Metric metric = getMetric("queries_", "readEntities_" + LABEL);
// when
statistics.countReadEntities();
// then
assertValueSet(metric);
}

@Test
void testNodeCacheSize() {
// given
final Metric metric = getMetric("lifecycle_", "nodeCacheSizeMb_" + LABEL);
// when
statistics.setNodeCacheSize(2345L);
// then
assertValueEquals(metric, 2345L);
}

@Test
void testPipelineSize() {
// given
final Metric metric = getMetric("lifecycle_", "pipelineSize_" + LABEL);
// when
statistics.setPipelineSize(23456);
// then
assertValueEquals(metric, 23456);
}

@Test
void testFlushBacklogSize() {
// given
final Metric metric = getMetric("lifecycle_", "flushBacklogSize_" + LABEL);
// when
statistics.recordFlushBacklogSize(3456);
// then
assertValueEquals(metric, 3456);
}

@Test
void testFlushBackpressureMs() {
// given
final Metric metric = getMetric("lifecycle_", "flushBackpressureMs_" + LABEL);
// when
statistics.recordFlushBackpressureMs(34567);
// then
assertValueSet(metric);
}

@Test
void testFamilySizeBackpressureMs() {
// given
final Metric metric = getMetric("lifecycle_", "familySizeBackpressureMs_" + LABEL);
// when
statistics.recordFamilySizeBackpressureMs(4567);
// then
assertValueSet(metric);
}

@Test
void testMergeDurationMs() {
// given
final Metric metric = getMetric("lifecycle_", "mergeDurationMs_" + LABEL);
// when
statistics.recordMerge(45678L);
// then
assertValueEquals(metric, 45678L);
}

@Test
void testFlushCountAndDurationMs() {
// given
final Metric metricDurationMs = getMetric("lifecycle_", "flushDurationMs_" + LABEL);
final Metric metricCount = getMetric("lifecycle_", "flushCount_" + LABEL);
// when
statistics.recordFlush(5678L);
// then
assertValueEquals(metricDurationMs, 5678L);
assertValueEquals(metricCount, 1L);
}

@Test
void testHashDurationMs() {
// given
final Metric metric = getMetric("lifecycle_", "hashDurationMs_" + LABEL);
// when
statistics.recordHash(56789L);
// then
assertValueEquals(metric, 56789L);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static com.swirlds.common.io.utility.FileUtils.deleteDirectory;
import static com.swirlds.common.merkle.iterators.MerkleIterationOrder.BREADTH_FIRST;
import static com.swirlds.common.test.fixtures.AssertionUtils.assertEventuallyEquals;
import static com.swirlds.common.test.fixtures.AssertionUtils.assertEventuallyTrue;
import static com.swirlds.test.framework.ResourceLoader.loadLog4jContext;
import static com.swirlds.virtualmap.VirtualMapTestUtils.createMap;
import static java.util.concurrent.TimeUnit.SECONDS;
Expand All @@ -45,7 +46,9 @@
import com.swirlds.common.merkle.route.MerkleRoute;
import com.swirlds.common.merkle.route.MerkleRouteFactory;
import com.swirlds.common.metrics.Counter;
import com.swirlds.common.metrics.LongGauge;
import com.swirlds.common.metrics.Metric;
import com.swirlds.common.metrics.Metric.ValueType;
import com.swirlds.common.metrics.Metrics;
import com.swirlds.common.metrics.config.MetricsConfig;
import com.swirlds.common.metrics.platform.DefaultMetrics;
Expand All @@ -64,7 +67,6 @@
import java.io.IOException;
import java.nio.file.Path;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
Expand Down Expand Up @@ -864,6 +866,65 @@ void canFlushDetachedStateForStateSaving() throws InterruptedException {
}
}

@Test
@Tags({@Tag("VirtualMerkle")})
@DisplayName("Tests nodeCacheSizeMb metric")
void testNodeCacheSizeMetric() throws IOException, InterruptedException {
final Configuration configuration = new TestConfigBuilder().getOrCreateConfig();
final MetricsConfig metricsConfig = configuration.getConfigData(MetricsConfig.class);
final MetricKeyRegistry registry = mock(MetricKeyRegistry.class);
when(registry.register(any(), any(), any())).thenReturn(true);
final Metrics metrics = new DefaultMetrics(
null,
registry,
mock(ScheduledExecutorService.class),
new DefaultMetricsFactory(metricsConfig),
metricsConfig);

VirtualMap<TestKey, TestValue> map0 = createMap();
map0.registerMetrics(metrics);

Metric metric = metrics.getMetric(VirtualMapStatistics.STAT_CATEGORY, "vmap_lifecycle_nodeCacheSizeMb_Test");
assertNotNull(metric);
if (!(metric instanceof LongGauge)) {
throw new AssertionError("nodeCacheSizeMb metric is not a gauge");
}

long metricValue = (long) metric.get(ValueType.VALUE);
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 50; j++) {
map0.put(new TestKey((char) (i * 50 + j)), new TestValue(String.valueOf(i * j + 1)));
}

VirtualMap<TestKey, TestValue> map1 = map0.copy();
map0.release();
map0 = map1;

long newValue = (long) metric.get(ValueType.VALUE);
assertTrue(
newValue >= metricValue,
"Node cache size must be increasing" + " old value = " + metricValue + " new value = " + newValue);
metricValue = newValue;
}

final long value = metricValue;

final VirtualRootNode<TestKey, TestValue> lastRoot = map0.getRight();
lastRoot.enableFlush();
VirtualMap<TestKey, TestValue> map1 = map0.copy();
map0.release();
lastRoot.waitUntilFlushed();
map1.release();

assertEventuallyTrue(
() -> {
long lastValue = (long) metric.get(ValueType.VALUE);
return lastValue < value;
},
Duration.ofSeconds(4),
"Node cache size must decrease after flush");
}

@Test
@Tags({@Tag("VirtualMerkle")})
@DisplayName("Tests vMapFlushes metric")
Expand Down Expand Up @@ -919,7 +980,7 @@ void testFlushCount() throws IOException, InterruptedException {
assertEventuallyEquals(
flushCount,
() -> counterMetric.get(),
Duration.of(1, ChronoUnit.SECONDS),
Duration.ofSeconds(4),
"Expected flush count (%s) to match actual value (%s)".formatted(flushCount, counterMetric.get()));
}

Expand Down

0 comments on commit 108dd18

Please sign in to comment.