diff --git a/community/kernel/src/main/java/org/neo4j/helpers/MathUtil.java b/community/common/src/main/java/org/neo4j/helpers/MathUtil.java similarity index 85% rename from community/kernel/src/main/java/org/neo4j/helpers/MathUtil.java rename to community/common/src/main/java/org/neo4j/helpers/MathUtil.java index f71d0ed14708c..f1c8e477d0ebd 100644 --- a/community/kernel/src/main/java/org/neo4j/helpers/MathUtil.java +++ b/community/common/src/main/java/org/neo4j/helpers/MathUtil.java @@ -20,11 +20,36 @@ package org.neo4j.helpers; import java.math.BigDecimal; +import java.util.Arrays; -public abstract class MathUtil +public class MathUtil { private static final long NON_DOUBLE_LONG = 0xFFE0_0000_0000_0000L; // doubles are exact integers up to 53 bits + private MathUtil() + { + throw new AssertionError(); + } + + /** + * Calculates the portion of the first value to all values passed + * + * @param n The values in the set + * @return the ratio of n[0] to the sum all n, 0 if result is {@link Double#NaN} + */ + public static double portion( double... n ) + { + assert n.length > 0; + + double first = n[0]; + if ( numbersEqual( first, 0 ) ) + { + return 0d; + } + double total = Arrays.stream( n ).sum(); + return first / total; + } + public static boolean numbersEqual( double fpn, long in ) { if ( in < 0 ) diff --git a/community/io/src/main/java/org/neo4j/io/pagecache/monitoring/PageCacheCounters.java b/community/io/src/main/java/org/neo4j/io/pagecache/monitoring/PageCacheCounters.java index 355b010b6e79e..b55e2ffaa7dac 100644 --- a/community/io/src/main/java/org/neo4j/io/pagecache/monitoring/PageCacheCounters.java +++ b/community/io/src/main/java/org/neo4j/io/pagecache/monitoring/PageCacheCounters.java @@ -19,6 +19,8 @@ */ package org.neo4j.io.pagecache.monitoring; +import org.neo4j.helpers.MathUtil; + /** * The PageCacheCounters exposes internal counters from the page cache. * The data for these counters is sourced through the PageCacheTracer API. @@ -50,6 +52,14 @@ public interface PageCacheCounters */ long hits(); + /** + * @return The cache hit ratio observed thus far. + */ + default double hitRatio() + { + return MathUtil.portion( hits(), faults() ); + } + /** * @return The number of page flushes observed thus far. */ diff --git a/enterprise/metrics/src/main/java/org/neo4j/metrics/output/CsvOutput.java b/enterprise/metrics/src/main/java/org/neo4j/metrics/output/CsvOutput.java index f97607ea4fdd2..8d53cc88a68b4 100644 --- a/enterprise/metrics/src/main/java/org/neo4j/metrics/output/CsvOutput.java +++ b/enterprise/metrics/src/main/java/org/neo4j/metrics/output/CsvOutput.java @@ -31,6 +31,7 @@ import java.io.File; import java.io.IOException; +import java.util.Locale; import java.util.SortedMap; import java.util.concurrent.TimeUnit; @@ -75,6 +76,7 @@ public void init() .convertRatesTo( TimeUnit.SECONDS ) .convertDurationsTo( TimeUnit.MILLISECONDS ) .filter( MetricFilter.ALL ) + .formatFor( Locale.US ) .build( ensureDirectoryExists( outputPath ) ); } diff --git a/enterprise/metrics/src/main/java/org/neo4j/metrics/source/db/PageCacheMetrics.java b/enterprise/metrics/src/main/java/org/neo4j/metrics/source/db/PageCacheMetrics.java index 5756013443b88..1dba57369f2d2 100644 --- a/enterprise/metrics/src/main/java/org/neo4j/metrics/source/db/PageCacheMetrics.java +++ b/enterprise/metrics/src/main/java/org/neo4j/metrics/source/db/PageCacheMetrics.java @@ -47,6 +47,8 @@ public class PageCacheMetrics extends LifecycleAdapter public static final String PC_PAGE_FAULTS = name( PAGE_CACHE_PREFIX, "page_faults" ); @Documented( "The total number of page hits happened in the page cache" ) public static final String PC_HITS = name( PAGE_CACHE_PREFIX, "hits" ); + @Documented( "The ratio of hits to the total number of lookups in the page cache" ) + public static final String PC_HIT_RATIO = name( PAGE_CACHE_PREFIX, "hit_ratio" ); private final MetricRegistry registry; private final PageCacheCounters pageCacheCounters; @@ -67,6 +69,7 @@ public void start() registry.register( PC_HITS, (Gauge) pageCacheCounters::hits ); registry.register( PC_FLUSHES, (Gauge) pageCacheCounters::flushes ); registry.register( PC_EVICTION_EXCEPTIONS, (Gauge) pageCacheCounters::evictionExceptions ); + registry.register( PC_HIT_RATIO, (Gauge) pageCacheCounters::hitRatio ); } @Override @@ -79,5 +82,6 @@ public void stop() registry.remove( PC_HITS ); registry.remove( PC_FLUSHES ); registry.remove( PC_EVICTION_EXCEPTIONS ); + registry.remove( PC_HIT_RATIO ); } } diff --git a/enterprise/metrics/src/test/java/org/neo4j/metrics/MetricsTestHelper.java b/enterprise/metrics/src/test/java/org/neo4j/metrics/MetricsTestHelper.java index 57a37773b0aed..72566628b6f2f 100644 --- a/enterprise/metrics/src/test/java/org/neo4j/metrics/MetricsTestHelper.java +++ b/enterprise/metrics/src/test/java/org/neo4j/metrics/MetricsTestHelper.java @@ -24,6 +24,7 @@ import java.io.FileReader; import java.io.IOException; import java.util.function.BiPredicate; +import java.util.function.Function; import static java.util.concurrent.TimeUnit.SECONDS; import static org.hamcrest.CoreMatchers.is; @@ -33,34 +34,51 @@ public class MetricsTestHelper { - public static final int TIME_STAMP = 0; - public static final int METRICS_VALUE = 1; + private static final int TIME_STAMP = 0; + private static final int METRICS_VALUE = 1; public static long readLongValue( File metricFile ) throws IOException, InterruptedException { - return readLongValueAndAssert( metricFile, (one, two) -> true ); + return readLongValueAndAssert( metricFile, ( one, two ) -> true ); } - public static long readLongValueAndAssert( File metricFile, BiPredicate assumption ) + static long readLongValueAndAssert( File metricFile, BiPredicate assumption ) throws IOException, InterruptedException + { + return readValueAndAssert( metricFile, 0L, Long::parseLong, assumption ); + } + + static double readDoubleValue( File metricFile ) throws IOException, InterruptedException + { + return readValueAndAssert( metricFile, 0.0, Double::parseDouble, + ( one, two ) -> true ); + } + + private static T readValueAndAssert( File metricFile, T startValue, Function parser, + BiPredicate assumption ) throws IOException, InterruptedException { // let's wait until the file is in place (since the reporting is async that might take a while) assertEventually( "Metrics file should exist", metricFile::exists, is( true ), 40, SECONDS ); try ( BufferedReader reader = new BufferedReader( new FileReader( metricFile ) ) ) { - String[] headers = reader.readLine().split( "," ); + String s; + do + { + s = reader.readLine(); + } + while ( s == null ); + String[] headers = s.split( "," ); assertThat( headers.length, is( 2 ) ); assertThat( headers[TIME_STAMP], is( "t" ) ); assertThat( headers[METRICS_VALUE], is( "value" ) ); - // Now we can verify that the number of committed transactions should never decrease. - int currentValue = 0; + T currentValue = startValue; String line; while ( (line = reader.readLine()) != null ) { String[] fields = line.split( "," ); - int newValue = Integer.parseInt( fields[1] ); + T newValue = parser.apply( fields[1] ); assertTrue( "assertion failed on " + newValue + " " + currentValue, assumption.test( newValue, currentValue ) ); currentValue = newValue; diff --git a/enterprise/metrics/src/test/java/org/neo4j/metrics/PageCacheMetricsIT.java b/enterprise/metrics/src/test/java/org/neo4j/metrics/PageCacheMetricsIT.java index 4ba9a8d541b95..c487a63981a0d 100644 --- a/enterprise/metrics/src/test/java/org/neo4j/metrics/PageCacheMetricsIT.java +++ b/enterprise/metrics/src/test/java/org/neo4j/metrics/PageCacheMetricsIT.java @@ -43,7 +43,9 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.neo4j.metrics.MetricsTestHelper.metricsCsv; +import static org.neo4j.metrics.MetricsTestHelper.readDoubleValue; import static org.neo4j.metrics.MetricsTestHelper.readLongValue; import static org.neo4j.test.assertion.Assert.assertEventually; @@ -94,6 +96,12 @@ public void pageCacheMetrics() throws Exception assertMetrics( "Page cache hits should be included in metrics report.", PageCacheMetrics.PC_HITS, greaterThan( 0L ) ); assertMetrics( "Page cache flushes should be included in metrics report.", PageCacheMetrics.PC_FLUSHES, greaterThanOrEqualTo( 0L ) ); assertMetrics( "Page cache exceptions should be included in metrics report.", PageCacheMetrics.PC_EVICTION_EXCEPTIONS, equalTo( 0L ) ); + + assertEventually( + "Page cache hit ratio should be included in metrics report.", + () -> readDoubleValue( metricsCsv( metricsDirectory, PageCacheMetrics.PC_HIT_RATIO ) ), + lessThanOrEqualTo( 1.0 ), + 5, SECONDS ); } private void assertMetrics( String message, String metricName, Matcher matcher ) throws Exception