From 108dd1832939145336570435b339be68495a37b8 Mon Sep 17 00:00:00 2001 From: artemananiev <33361937+artemananiev@users.noreply.github.com> Date: Thu, 21 Sep 2023 10:28:17 -0700 Subject: [PATCH] 8469: New VirtualMap metric: virtual node cache size, in Mb (#8755) Fixes: https://github.com/hashgraph/hedera-services/issues/8469 Reviewed-by: Joseph Sinclair Signed-off-by: Artem Ananev --- .../internal/merkle/VirtualMapStatistics.java | 16 ++ .../internal/merkle/VirtualRootNode.java | 2 + .../internal/pipeline/VirtualPipeline.java | 5 +- .../virtualmap/VirtualMapStatisticsTest.java | 210 ++++++++++++++++++ .../swirlds/virtualmap/VirtualMapTests.java | 65 +++++- 5 files changed, 294 insertions(+), 4 deletions(-) create mode 100644 platform-sdk/swirlds-virtualmap/src/test/java/com/swirlds/virtualmap/VirtualMapStatisticsTest.java diff --git a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualMapStatistics.java b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualMapStatistics.java index e711b0c53d22..7168c2ccdd42 100644 --- a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualMapStatistics.java +++ b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualMapStatistics.java @@ -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 */ @@ -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)); @@ -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. * diff --git a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualRootNode.java b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualRootNode.java index 9dcdbd106f0d..a88eb8a2bc5b 100644 --- a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualRootNode.java +++ b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/merkle/VirtualRootNode.java @@ -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); } diff --git a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/pipeline/VirtualPipeline.java b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/pipeline/VirtualPipeline.java index 777e57b60653..8aa0ff41e121 100644 --- a/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/pipeline/VirtualPipeline.java +++ b/platform-sdk/swirlds-virtualmap/src/main/java/com/swirlds/virtualmap/internal/pipeline/VirtualPipeline.java @@ -473,7 +473,6 @@ private long currentTotalSize() { for (PipelineListNode 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(); @@ -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(); } } diff --git a/platform-sdk/swirlds-virtualmap/src/test/java/com/swirlds/virtualmap/VirtualMapStatisticsTest.java b/platform-sdk/swirlds-virtualmap/src/test/java/com/swirlds/virtualmap/VirtualMapStatisticsTest.java new file mode 100644 index 000000000000..9f0f6509d6fe --- /dev/null +++ b/platform-sdk/swirlds-virtualmap/src/test/java/com/swirlds/virtualmap/VirtualMapStatisticsTest.java @@ -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 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); + } +} diff --git a/platform-sdk/swirlds-virtualmap/src/test/java/com/swirlds/virtualmap/VirtualMapTests.java b/platform-sdk/swirlds-virtualmap/src/test/java/com/swirlds/virtualmap/VirtualMapTests.java index a7e844a49a3f..ec5b590c735f 100644 --- a/platform-sdk/swirlds-virtualmap/src/test/java/com/swirlds/virtualmap/VirtualMapTests.java +++ b/platform-sdk/swirlds-virtualmap/src/test/java/com/swirlds/virtualmap/VirtualMapTests.java @@ -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; @@ -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; @@ -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; @@ -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 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 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 lastRoot = map0.getRight(); + lastRoot.enableFlush(); + VirtualMap 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") @@ -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())); }