add perf test
diff --git a/integration-test/src/test/java/org/apache/iotdb/db/it/performance/IoTDBAlignedTVListPrimitiveArrayPerformanceIT.java b/integration-test/src/test/java/org/apache/iotdb/db/it/performance/IoTDBAlignedTVListPrimitiveArrayPerformanceIT.java new file mode 100644 index 0000000..c0bbf49 --- /dev/null +++ b/integration-test/src/test/java/org/apache/iotdb/db/it/performance/IoTDBAlignedTVListPrimitiveArrayPerformanceIT.java
@@ -0,0 +1,584 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.iotdb.db.it.performance; + +import org.apache.iotdb.isession.ISession; +import org.apache.iotdb.isession.SessionDataSet; +import org.apache.iotdb.it.env.EnvFactory; +import org.apache.iotdb.it.framework.IoTDBTestRunner; +import org.apache.iotdb.itbase.category.LocalStandaloneIT; + +import org.apache.tsfile.enums.TSDataType; +import org.apache.tsfile.write.record.Tablet; +import org.apache.tsfile.write.schema.IMeasurementSchema; +import org.apache.tsfile.write.schema.MeasurementSchema; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Assume; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Properties; + +@RunWith(IoTDBTestRunner.class) +@Category({LocalStandaloneIT.class}) +public class IoTDBAlignedTVListPrimitiveArrayPerformanceIT { + + private static final Logger LOGGER = + LoggerFactory.getLogger(IoTDBAlignedTVListPrimitiveArrayPerformanceIT.class); + + private static final String PROPERTY_PREFIX = "iotdb.aligned.lazy-allocation.perf."; + private static final String ENABLED_PROPERTY = PROPERTY_PREFIX + "enabled"; + private static final String IMPLEMENTATION_PROPERTY = PROPERTY_PREFIX + "implementation"; + private static final String REVISION_PROPERTY = PROPERTY_PREFIX + "revision"; + private static final String HISTORICAL_ROW_COUNT_PROPERTY = + PROPERTY_PREFIX + "historical.row.count"; + private static final String NEW_COLUMN_COUNT_PROPERTY = PROPERTY_PREFIX + "new.column.count"; + private static final String NO_NULL_BATCH_COUNT_PROPERTY = + PROPERTY_PREFIX + "no-null.batch.count"; + private static final String WARMUP_ROUND_COUNT_PROPERTY = PROPERTY_PREFIX + "warmup.round.count"; + private static final String ROUND_COUNT_PROPERTY = PROPERTY_PREFIX + "round.count"; + private static final String RESULT_PATH_PROPERTY = PROPERTY_PREFIX + "result.path"; + private static final String BASELINE_RESULT_PATH_PROPERTY = + PROPERTY_PREFIX + "baseline.result.path"; + private static final String REPORT_PATH_PROPERTY = PROPERTY_PREFIX + "report.path"; + + private static final int DATANODE_MAX_HEAP_SIZE_IN_MB = 1536; + private static final int PRIMITIVE_ARRAY_SIZE = 64; + private static final int HISTORICAL_ROW_COUNT = + Integer.getInteger(HISTORICAL_ROW_COUNT_PROPERTY, 10_000); + private static final int NEW_COLUMN_COUNT = Integer.getInteger(NEW_COLUMN_COUNT_PROPERTY, 512); + private static final int NO_NULL_ROWS_PER_BATCH = PRIMITIVE_ARRAY_SIZE; + private static final int NO_NULL_BATCH_COUNT = + Integer.getInteger(NO_NULL_BATCH_COUNT_PROPERTY, 50); + private static final int WARMUP_ROUND_COUNT = Integer.getInteger(WARMUP_ROUND_COUNT_PROPERTY, 1); + private static final int ROUND_COUNT = Integer.getInteger(ROUND_COUNT_PROPERTY, 5); + private static final String IMPLEMENTATION = + System.getProperty(IMPLEMENTATION_PROPERTY, "candidate"); + private static final String REVISION = System.getProperty(REVISION_PROPERTY, "unknown"); + private static final String DEVICE_PREFIX = "root.aligned_lazy_allocation_performance.d"; + private static final String NO_NULL_DEVICE = "root.aligned_lazy_allocation_performance.no_null"; + + @BeforeClass + public static void setUp() throws Exception { + Assume.assumeTrue(Boolean.getBoolean(ENABLED_PROPERTY)); + EnvFactory.getEnv() + .getConfig() + .getDataNodeJVMConfig() + .setMaxHeapSize(DATANODE_MAX_HEAP_SIZE_IN_MB); + EnvFactory.getEnv() + .getConfig() + .getCommonConfig() + .setAutoCreateSchemaEnabled(false) + .setEnableMemControl(true) + .setPrimitiveArraySize(PRIMITIVE_ARRAY_SIZE) + .setMemtableSizeThreshold(768L * 1024 * 1024) + .setDatanodeMemoryProportion("6:1:1:1:1:1") + .setWriteMemoryProportion("100:1"); + EnvFactory.getEnv().initClusterEnvironment(); + } + + @AfterClass + public static void tearDown() throws Exception { + EnvFactory.getEnv().cleanClusterEnvironment(); + } + + @Test + public void testAlignedWritePerformance() throws Exception { + Assert.assertTrue(HISTORICAL_ROW_COUNT > 0); + Assert.assertTrue(NEW_COLUMN_COUNT > 0); + Assert.assertTrue(NO_NULL_BATCH_COUNT > 0); + Assert.assertTrue(WARMUP_ROUND_COUNT >= 0); + Assert.assertTrue(ROUND_COUNT > 0); + + List<String> newMeasurements = new ArrayList<>(NEW_COLUMN_COUNT); + List<TSDataType> newDataTypes = new ArrayList<>(NEW_COLUMN_COUNT); + List<Object> newValues = new ArrayList<>(NEW_COLUMN_COUNT); + for (int i = 1; i <= NEW_COLUMN_COUNT; i++) { + newMeasurements.add("s" + i); + newDataTypes.add(TSDataType.INT64); + newValues.add((long) i); + } + + long[] lazyAllocationElapsedNanos; + long[] noNullElapsedNanos; + try (ISession session = EnvFactory.getEnv().getSessionConnection()) { + lazyAllocationElapsedNanos = + runLazyAllocationWorkload(session, newMeasurements, newDataTypes, newValues); + noNullElapsedNanos = runNoNullWorkload(session); + } + + BenchmarkResult result = + new BenchmarkResult( + IMPLEMENTATION, + REVISION, + HISTORICAL_ROW_COUNT, + NEW_COLUMN_COUNT, + WARMUP_ROUND_COUNT, + NO_NULL_BATCH_COUNT, + lazyAllocationElapsedNanos, + noNullElapsedNanos); + Path resultPath = getResultPath(); + writeResult(resultPath, result); + LOGGER.info("AlignedTVList lazy-allocation performance result: {}", result.toSummary()); + LOGGER.info("Performance result written to {}", resultPath.toAbsolutePath()); + + String baselineResultPath = System.getProperty(BASELINE_RESULT_PATH_PROPERTY); + if (baselineResultPath != null && !baselineResultPath.isEmpty()) { + BenchmarkResult baseline = readResult(Paths.get(baselineResultPath)); + assertComparable(baseline, result); + Path reportPath = + Paths.get( + System.getProperty( + REPORT_PATH_PROPERTY, + "target/aligned-tvlist-lazy-allocation-performance-report.md")); + writeFile(reportPath, buildReport(baseline, result)); + LOGGER.info("Comparison report written to {}", reportPath.toAbsolutePath()); + } + } + + private static long[] runLazyAllocationWorkload( + ISession session, + List<String> newMeasurements, + List<TSDataType> newDataTypes, + List<Object> newValues) + throws Exception { + long[] elapsedNanos = new long[ROUND_COUNT]; + int totalRoundCount = WARMUP_ROUND_COUNT + ROUND_COUNT; + for (int round = 0; round < totalRoundCount; round++) { + String device = DEVICE_PREFIX + round; + createAlignedTimeseries(session, device, NEW_COLUMN_COUNT + 1); + insertHistoricalRows(session, device); + + long startNanos = System.nanoTime(); + session.insertAlignedRecord( + device, HISTORICAL_ROW_COUNT, newMeasurements, newDataTypes, newValues); + long roundElapsedNanos = System.nanoTime() - startNanos; + if (round >= WARMUP_ROUND_COUNT) { + elapsedNanos[round - WARMUP_ROUND_COUNT] = roundElapsedNanos; + } + + assertLazyAllocationDataWritten(session, device); + } + return elapsedNanos; + } + + private static long[] runNoNullWorkload(ISession session) throws Exception { + createAlignedTimeseries(session, NO_NULL_DEVICE, NEW_COLUMN_COUNT); + List<IMeasurementSchema> schemas = new ArrayList<>(NEW_COLUMN_COUNT); + for (int column = 0; column < NEW_COLUMN_COUNT; column++) { + schemas.add(new MeasurementSchema("s" + column, TSDataType.INT64)); + } + Tablet tablet = new Tablet(NO_NULL_DEVICE, schemas, NO_NULL_ROWS_PER_BATCH); + for (int column = 0; column < NEW_COLUMN_COUNT; column++) { + long[] columnValues = (long[]) tablet.getValues()[column]; + for (int row = 0; row < NO_NULL_ROWS_PER_BATCH; row++) { + columnValues[row] = column + row; + } + } + tablet.setRowSize(NO_NULL_ROWS_PER_BATCH); + + long nextTimestamp = 0; + long[] elapsedNanos = new long[ROUND_COUNT]; + int totalRoundCount = WARMUP_ROUND_COUNT + ROUND_COUNT; + for (int round = 0; round < totalRoundCount; round++) { + long startNanos = System.nanoTime(); + for (int batch = 0; batch < NO_NULL_BATCH_COUNT; batch++) { + for (int row = 0; row < NO_NULL_ROWS_PER_BATCH; row++) { + tablet.getTimestamps()[row] = nextTimestamp++; + } + session.insertAlignedTablet(tablet); + } + long roundElapsedNanos = System.nanoTime() - startNanos; + if (round >= WARMUP_ROUND_COUNT) { + elapsedNanos[round - WARMUP_ROUND_COUNT] = roundElapsedNanos; + } + } + + assertNoNullDataWritten(session, nextTimestamp); + return elapsedNanos; + } + + private static void createAlignedTimeseries(ISession session, String device, int measurementCount) + throws Exception { + StringBuilder statement = new StringBuilder("CREATE ALIGNED TIMESERIES "); + statement.append(device).append("(s0 INT64"); + for (int i = 1; i < measurementCount; i++) { + statement.append(",s").append(i).append(" INT64"); + } + statement.append(')'); + session.executeNonQueryStatement(statement.toString()); + } + + private static void insertHistoricalRows(ISession session, String device) throws Exception { + List<IMeasurementSchema> schemas = + Collections.singletonList(new MeasurementSchema("s0", TSDataType.INT64)); + Tablet tablet = new Tablet(device, schemas, Math.min(HISTORICAL_ROW_COUNT, 1024)); + for (int row = 0; row < HISTORICAL_ROW_COUNT; row++) { + int rowIndex = tablet.getRowSize(); + if (rowIndex == tablet.getMaxRowNumber()) { + session.insertAlignedTablet(tablet); + tablet.reset(); + rowIndex = 0; + } + tablet.addTimestamp(rowIndex, row); + tablet.addValue("s0", rowIndex, (long) row); + } + if (tablet.getRowSize() > 0) { + session.insertAlignedTablet(tablet); + } + } + + private static void assertLazyAllocationDataWritten(ISession session, String device) + throws Exception { + try (SessionDataSet dataSet = + session.executeQueryStatement( + "SELECT COUNT(s0), COUNT(s" + NEW_COLUMN_COUNT + ") FROM " + device)) { + Assert.assertTrue(dataSet.hasNext()); + List<org.apache.tsfile.read.common.Field> fields = dataSet.next().getFields(); + Assert.assertEquals(HISTORICAL_ROW_COUNT, fields.get(0).getLongV()); + Assert.assertEquals(1, fields.get(1).getLongV()); + Assert.assertFalse(dataSet.hasNext()); + } + } + + private static void assertNoNullDataWritten(ISession session, long expectedRowCount) + throws Exception { + try (SessionDataSet dataSet = + session.executeQueryStatement( + "SELECT COUNT(s0), COUNT(s" + (NEW_COLUMN_COUNT - 1) + ") FROM " + NO_NULL_DEVICE)) { + Assert.assertTrue(dataSet.hasNext()); + List<org.apache.tsfile.read.common.Field> fields = dataSet.next().getFields(); + Assert.assertEquals(expectedRowCount, fields.get(0).getLongV()); + Assert.assertEquals(expectedRowCount, fields.get(1).getLongV()); + Assert.assertFalse(dataSet.hasNext()); + } + } + + private static Path getResultPath() { + String defaultFileName = + "target/aligned-tvlist-lazy-allocation-" + + IMPLEMENTATION.replaceAll("[^A-Za-z0-9_.-]", "_") + + ".properties"; + return Paths.get(System.getProperty(RESULT_PATH_PROPERTY, defaultFileName)); + } + + private static void writeResult(Path path, BenchmarkResult result) throws IOException { + Properties properties = new Properties(); + properties.setProperty("format.version", "2"); + properties.setProperty("implementation", result.implementation); + properties.setProperty("revision", result.revision); + properties.setProperty("historical.row.count", Integer.toString(result.historicalRowCount)); + properties.setProperty("new.column.count", Integer.toString(result.newColumnCount)); + properties.setProperty("warmup.round.count", Integer.toString(result.warmupRoundCount)); + properties.setProperty( + "round.count", Integer.toString(result.lazyAllocationElapsedNanos.length)); + properties.setProperty("no-null.batch.count", Integer.toString(result.noNullBatchCount)); + properties.setProperty("no-null.rows.per.batch", Integer.toString(NO_NULL_ROWS_PER_BATCH)); + properties.setProperty( + "lazy-allocation.latencies.nanos", join(result.lazyAllocationElapsedNanos)); + properties.setProperty("no-null.latencies.nanos", join(result.noNullElapsedNanos)); + createParentDirectories(path); + try (OutputStream outputStream = Files.newOutputStream(path)) { + properties.store(outputStream, "AlignedTVList lazy-allocation integration benchmark"); + } + } + + private static BenchmarkResult readResult(Path path) throws IOException { + Properties properties = new Properties(); + try (InputStream inputStream = Files.newInputStream(path)) { + properties.load(inputStream); + } + return new BenchmarkResult( + properties.getProperty("implementation"), + properties.getProperty("revision"), + Integer.parseInt(properties.getProperty("historical.row.count")), + Integer.parseInt(properties.getProperty("new.column.count")), + Integer.parseInt(properties.getProperty("warmup.round.count")), + Integer.parseInt(properties.getProperty("no-null.batch.count")), + readLongArray(properties, "lazy-allocation.latencies.nanos"), + readLongArray(properties, "no-null.latencies.nanos")); + } + + private static long[] readLongArray(Properties properties, String key) { + String[] values = properties.getProperty(key).split(","); + long[] result = new long[values.length]; + for (int i = 0; i < values.length; i++) { + result[i] = Long.parseLong(values[i]); + } + return result; + } + + private static void assertComparable(BenchmarkResult baseline, BenchmarkResult candidate) { + Assert.assertEquals(baseline.historicalRowCount, candidate.historicalRowCount); + Assert.assertEquals(baseline.newColumnCount, candidate.newColumnCount); + Assert.assertEquals(baseline.warmupRoundCount, candidate.warmupRoundCount); + Assert.assertEquals(baseline.noNullBatchCount, candidate.noNullBatchCount); + Assert.assertEquals( + baseline.lazyAllocationElapsedNanos.length, candidate.lazyAllocationElapsedNanos.length); + Assert.assertEquals(baseline.noNullElapsedNanos.length, candidate.noNullElapsedNanos.length); + } + + private static String buildReport(BenchmarkResult baseline, BenchmarkResult candidate) { + StringBuilder report = new StringBuilder(); + report.append("# AlignedTVList integration performance report\n\n"); + report.append( + "Both revisions were built in separate Git worktrees and ran this same " + + "`LocalStandaloneIT` against a real 1C1D IoTDB environment.\n\n"); + report.append( + String.format( + Locale.ROOT, + "- Lazy-allocation workload: write %,d historical rows only to `s0`, then write " + + "one row to %,d newly used columns.%n" + + "- No-null workload: write %d aligned tablets per round, with %,d columns and " + + "%d rows per tablet; every value is non-null.%n" + + "- Primitive array size: %d%n" + + "- DataNode maximum heap: %,d MiB%n" + + "- Warm-up rounds: %d%n" + + "- Measured rounds: %d%n%n", + candidate.historicalRowCount, + candidate.newColumnCount, + candidate.noNullBatchCount, + candidate.newColumnCount, + NO_NULL_ROWS_PER_BATCH, + PRIMITIVE_ARRAY_SIZE, + DATANODE_MAX_HEAP_SIZE_IN_MB, + candidate.warmupRoundCount, + candidate.lazyAllocationElapsedNanos.length)); + report.append("| Workload | Revision | Commit | Median ms | P95 ms | Values/s |\n"); + report.append("| --- | --- | --- | ---: | ---: | ---: |\n"); + appendReportRow(report, "Lazy allocation", baseline, false); + appendReportRow(report, "Lazy allocation", candidate, false); + appendReportRow(report, "No nulls", baseline, true); + appendReportRow(report, "No nulls", candidate, true); + report.append('\n'); + report.append( + String.format( + Locale.ROOT, + "- Lazy-allocation median latency improvement: %.2f%%%n" + + "- Lazy-allocation throughput improvement: %.2f%%%n" + + "- No-null median latency improvement: %.2f%%%n" + + "- No-null throughput improvement: %.2f%%%n%n", + latencyImprovement(baseline.lazyMedianNanos(), candidate.lazyMedianNanos()), + throughputImprovement(baseline.lazyValuesPerSecond(), candidate.lazyValuesPerSecond()), + latencyImprovement(baseline.noNullMedianNanos(), candidate.noNullMedianNanos()), + throughputImprovement( + baseline.noNullValuesPerSecond(), candidate.noNullValuesPerSecond()))); + report.append( + String.format( + Locale.ROOT, + "- %s lazy-allocation latencies (ms): `%s`%n" + + "- %s lazy-allocation latencies (ms): `%s`%n" + + "- %s no-null latencies (ms): `%s`%n" + + "- %s no-null latencies (ms): `%s`%n%n", + baseline.implementation, + latenciesMillis(baseline.lazyAllocationElapsedNanos), + candidate.implementation, + latenciesMillis(candidate.lazyAllocationElapsedNanos), + baseline.implementation, + latenciesMillis(baseline.noNullElapsedNanos), + candidate.implementation, + latenciesMillis(candidate.noNullElapsedNanos))); + report.append( + "The timed regions contain the client calls and complete server write path; schema " + + "creation, workload preparation, and result verification are outside them. Raw " + + "per-round latencies are retained in the generated result property files.\n\n"); + report.append("## Reproduction\n\n"); + report.append( + "1. Create the baseline worktree: `git worktree add --detach <master-worktree> master`.\n" + + "2. Copy this test source to the identical path in the baseline worktree.\n" + + "3. In each worktree, build with " + + "`mvn -Pwith-integration-tests -pl integration-test -am package -DskipTests`.\n" + + String.format( + Locale.ROOT, + "4. Run the baseline with `-D%s=true`, `-D%s=master`, " + + "`-D%s=<master-commit>`, and `-D%s=<baseline-result>`.%n" + + "5. Run the candidate with the corresponding candidate values plus " + + "`-D%s=<baseline-result>` and `-D%s=<report-path>`. For both runs, select " + + "this class with `-Dit.test=IoTDBAlignedTVListPrimitiveArrayPerformanceIT` " + + "and execute `failsafe:integration-test@integration-test " + + "failsafe:verify@verify`.%n", + ENABLED_PROPERTY, + IMPLEMENTATION_PROPERTY, + REVISION_PROPERTY, + RESULT_PATH_PROPERTY, + BASELINE_RESULT_PATH_PROPERTY, + REPORT_PATH_PROPERTY)); + return report.toString(); + } + + private static void appendReportRow( + StringBuilder report, String workload, BenchmarkResult result, boolean noNull) { + long medianNanos = noNull ? result.noNullMedianNanos() : result.lazyMedianNanos(); + long p95Nanos = noNull ? result.noNullP95Nanos() : result.lazyP95Nanos(); + double valuesPerSecond = noNull ? result.noNullValuesPerSecond() : result.lazyValuesPerSecond(); + report.append( + String.format( + Locale.ROOT, + "| %s | %s | `%s` | %.3f | %.3f | %.0f |%n", + workload, + result.implementation, + result.revision, + medianNanos / 1_000_000.0, + p95Nanos / 1_000_000.0, + valuesPerSecond)); + } + + private static double latencyImprovement(double baseline, double candidate) { + return (baseline - candidate) * 100.0 / baseline; + } + + private static double throughputImprovement(double baseline, double candidate) { + return (candidate - baseline) * 100.0 / baseline; + } + + private static String latenciesMillis(long[] elapsedNanos) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < elapsedNanos.length; i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(String.format(Locale.ROOT, "%.3f", elapsedNanos[i] / 1_000_000.0)); + } + return builder.toString(); + } + + private static void writeFile(Path path, String content) throws IOException { + createParentDirectories(path); + Files.writeString(path, content, StandardCharsets.UTF_8); + } + + private static void createParentDirectories(Path path) throws IOException { + Path parent = path.toAbsolutePath().getParent(); + if (parent != null) { + Files.createDirectories(parent); + } + } + + private static String join(long[] values) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < values.length; i++) { + if (i > 0) { + builder.append(','); + } + builder.append(values[i]); + } + return builder.toString(); + } + + private static class BenchmarkResult { + private final String implementation; + private final String revision; + private final int historicalRowCount; + private final int newColumnCount; + private final int warmupRoundCount; + private final int noNullBatchCount; + private final long[] lazyAllocationElapsedNanos; + private final long[] noNullElapsedNanos; + + private BenchmarkResult( + String implementation, + String revision, + int historicalRowCount, + int newColumnCount, + int warmupRoundCount, + int noNullBatchCount, + long[] lazyAllocationElapsedNanos, + long[] noNullElapsedNanos) { + this.implementation = implementation; + this.revision = revision; + this.historicalRowCount = historicalRowCount; + this.newColumnCount = newColumnCount; + this.warmupRoundCount = warmupRoundCount; + this.noNullBatchCount = noNullBatchCount; + this.lazyAllocationElapsedNanos = lazyAllocationElapsedNanos; + this.noNullElapsedNanos = noNullElapsedNanos; + } + + private static long medianNanos(long[] elapsedNanos) { + long[] sorted = Arrays.copyOf(elapsedNanos, elapsedNanos.length); + Arrays.sort(sorted); + return sorted[sorted.length / 2]; + } + + private static long p95Nanos(long[] elapsedNanos) { + long[] sorted = Arrays.copyOf(elapsedNanos, elapsedNanos.length); + Arrays.sort(sorted); + return sorted[(int) Math.ceil(sorted.length * 0.95) - 1]; + } + + private long lazyMedianNanos() { + return medianNanos(lazyAllocationElapsedNanos); + } + + private long lazyP95Nanos() { + return p95Nanos(lazyAllocationElapsedNanos); + } + + private long noNullMedianNanos() { + return medianNanos(noNullElapsedNanos); + } + + private long noNullP95Nanos() { + return p95Nanos(noNullElapsedNanos); + } + + private double lazyValuesPerSecond() { + return newColumnCount * 1_000_000_000.0 / lazyMedianNanos(); + } + + private double noNullValuesPerSecond() { + return (double) newColumnCount + * NO_NULL_ROWS_PER_BATCH + * noNullBatchCount + * 1_000_000_000.0 + / noNullMedianNanos(); + } + + private String toSummary() { + return String.format( + Locale.ROOT, + "implementation=%s, revision=%s, lazy median=%.3f ms, lazy values/s=%.0f, " + + "no-null median=%.3f ms, no-null values/s=%.0f", + implementation, + revision, + lazyMedianNanos() / 1_000_000.0, + lazyValuesPerSecond(), + noNullMedianNanos() / 1_000_000.0, + noNullValuesPerSecond()); + } + } +}