This is an automated email from the ASF dual-hosted git repository.
jt2594838 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/master by this push:
new e5651cbc928 [Performance] Optimize tablet RPC deserialization (#18248)
e5651cbc928 is described below
commit e5651cbc928f3449319dc588f54cb189d9c4a7e7
Author: Caideyipi <[email protected]>
AuthorDate: Mon Jul 20 17:44:50 2026 +0800
[Performance] Optimize tablet RPC deserialization (#18248)
* [Performance] Optimize tablet RPC deserialization
* [Performance] Add tablet RPC deserialization benchmarks
* Address tablet deserialization review comments
---
.../protocol/thrift/impl/ClientRPCServiceImpl.java | 6 +-
.../plan/parser/StatementGenerator.java | 38 +-
.../org/apache/iotdb/db/utils/TabletDecoder.java | 36 +-
.../apache/iotdb/db/utils/TabletDecoderTest.java | 81 +++
.../TabletRpcDeserializationPerformanceTest.java | 617 +++++++++++++++++++++
.../org/apache/iotdb/commons/utils/PathUtils.java | 67 ++-
.../apache/iotdb/commons/utils/PathUtilsTest.java | 78 +++
7 files changed, 895 insertions(+), 28 deletions(-)
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java
index 65e4a3b8743..ea67ebb8523 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/ClientRPCServiceImpl.java
@@ -2669,8 +2669,7 @@ public class ClientRPCServiceImpl implements
IClientRPCServiceWithHandler {
if (!SESSION_MANAGER.checkLogin(clientSession)) {
return getNotLoggedInStatus();
}
- req.setMeasurementsList(
-
PathUtils.checkIsLegalSingleMeasurementListsAndUpdate(req.getMeasurementsList()));
+
PathUtils.checkIsLegalSingleMeasurementListsAndUpdateInPlace(req.getMeasurementsList());
// Step 1: transfer from TSInsertTabletsReq to Statement
InsertMultiTabletsStatement statement =
StatementGenerator.createStatement(req);
@@ -2736,8 +2735,7 @@ public class ClientRPCServiceImpl implements
IClientRPCServiceWithHandler {
// check whether measurement is legal according to syntax convention
(only for tree model)
if (!req.isWriteToTable()) {
- req.setMeasurements(
-
PathUtils.checkIsLegalSingleMeasurementsAndUpdate(req.getMeasurements()));
+
PathUtils.checkIsLegalSingleMeasurementsAndUpdateInPlace(req.getMeasurements());
}
// Step 1: transfer from TSInsertTabletReq to Statement
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/parser/StatementGenerator.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/parser/StatementGenerator.java
index 6bdc4814a6d..7464e6d99fb 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/parser/StatementGenerator.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/parser/StatementGenerator.java
@@ -339,10 +339,7 @@ public class StatementGenerator {
? new PartialPath(insertTabletReq.getPrefixPath(), false)
:
DEVICE_PATH_CACHE.getPartialPath(insertTabletReq.getPrefixPath()));
insertStatement.setMeasurements(insertTabletReq.getMeasurements().toArray(new
String[0]));
- TSDataType[] dataTypes = new TSDataType[insertTabletReq.types.size()];
- for (int i = 0; i < insertTabletReq.types.size(); i++) {
- dataTypes[i] = TSDataType.deserialize((byte)
insertTabletReq.types.get(i).intValue());
- }
+ TSDataType[] dataTypes = deserializeDataTypes(insertTabletReq.types);
insertStatement.setDataTypes(dataTypes);
TabletDecoder tabletDecoder =
@@ -397,32 +394,29 @@ public class StatementGenerator {
final long startTime = System.nanoTime();
// construct insert statement
InsertMultiTabletsStatement insertStatement = new
InsertMultiTabletsStatement();
- List<InsertTabletStatement> insertTabletStatementList = new ArrayList<>();
- for (int i = 0; i < req.prefixPaths.size(); i++) {
+ int tabletCount = req.prefixPaths.size();
+ List<InsertTabletStatement> insertTabletStatementList = new
ArrayList<>(tabletCount);
+ for (int i = 0; i < tabletCount; i++) {
+ List<String> measurements = req.measurementsList.get(i);
+ TSDataType[] dataTypes = deserializeDataTypes(req.typesList.get(i));
+ int rowCount = req.sizeList.get(i);
InsertTabletStatement insertTabletStatement = new
InsertTabletStatement();
insertTabletStatement.setDevicePath(DEVICE_PATH_CACHE.getPartialPath(req.prefixPaths.get(i)));
-
insertTabletStatement.setMeasurements(req.measurementsList.get(i).toArray(new
String[0]));
+ insertTabletStatement.setMeasurements(measurements.toArray(new
String[0]));
long[] timestamps =
- QueryDataSetUtils.readTimesFromBuffer(req.timestampsList.get(i),
req.sizeList.get(i));
+ QueryDataSetUtils.readTimesFromBuffer(req.timestampsList.get(i),
rowCount);
if (timestamps.length != 0) {
TimestampPrecisionUtils.checkTimestampPrecision(timestamps[timestamps.length -
1]);
}
insertTabletStatement.setTimes(timestamps);
insertTabletStatement.setColumns(
QueryDataSetUtils.readTabletValuesFromBuffer(
- req.valuesList.get(i),
- req.typesList.get(i),
- req.measurementsList.get(i).size(),
- req.sizeList.get(i)));
+ req.valuesList.get(i), dataTypes, measurements.size(),
rowCount));
insertTabletStatement.setBitMaps(
QueryDataSetUtils.readBitMapsFromBuffer(
- req.valuesList.get(i), req.measurementsList.get(i).size(),
req.sizeList.get(i))
+ req.valuesList.get(i), measurements.size(), rowCount)
.orElse(null));
- insertTabletStatement.setRowCount(req.sizeList.get(i));
- TSDataType[] dataTypes = new TSDataType[req.typesList.get(i).size()];
- for (int j = 0; j < dataTypes.length; j++) {
- dataTypes[j] = TSDataType.deserialize((byte)
req.typesList.get(i).get(j).intValue());
- }
+ insertTabletStatement.setRowCount(rowCount);
insertTabletStatement.setDataTypes(dataTypes);
insertTabletStatement.setAligned(req.isAligned);
// skip empty tablet
@@ -436,6 +430,14 @@ public class StatementGenerator {
return insertStatement;
}
+ private static TSDataType[] deserializeDataTypes(List<Integer>
serializedDataTypes) {
+ TSDataType[] dataTypes = new TSDataType[serializedDataTypes.size()];
+ for (int i = 0; i < dataTypes.length; i++) {
+ dataTypes[i] = TSDataType.deserialize((byte)
serializedDataTypes.get(i).intValue());
+ }
+ return dataTypes;
+ }
+
public static InsertRowsStatement createStatement(TSInsertRecordsReq req)
throws IllegalPathException, QueryProcessException {
final long startTime = System.nanoTime();
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/TabletDecoder.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/TabletDecoder.java
index e6e584ed7fe..2f1d73b7231 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/TabletDecoder.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/TabletDecoder.java
@@ -43,6 +43,7 @@ public class TabletDecoder {
private final List<TSEncoding> columnEncodings;
private final IUnCompressor unCompressor;
private final int rowSize;
+ private final boolean allValueColumnsPlain;
/**
* @param compressionType the overall compression
@@ -60,6 +61,7 @@ public class TabletDecoder {
this.columnEncodings = columnEncodings;
this.unCompressor = IUnCompressor.getUnCompressor(compressionType);
this.rowSize = rowSize;
+ this.allValueColumnsPlain = allValueColumnsPlain(dataTypes,
columnEncodings);
}
public long[] decodeTime(ByteBuffer buffer) {
@@ -113,6 +115,10 @@ public class TabletDecoder {
TSDataType dataType = TSDataType.INT64;
TSEncoding encodingType = columnEncodings.get(0);
+ if (encodingType == TSEncoding.PLAIN) {
+ return QueryDataSetUtils.readTimesFromBuffer(buffer, rowCount);
+ }
+
Decoder decoder = Decoder.getDecoderByType(encodingType, dataType);
long[] result = new long[rowCount];
for (int i = 0; i < rowCount; i++) {
@@ -129,9 +135,16 @@ public class TabletDecoder {
RPCServiceThriftHandlerMetrics.getInstance().recordCompressionSizeTimer(compressedSize);
long startDecodeTime = System.nanoTime();
- Object[] columns = new Object[dataTypes.length];
- for (int i = 0; i < dataTypes.length; i++) {
- columns[i] = decodeColumn(uncompressed, i);
+ Object[] columns;
+ if (allValueColumnsPlain) {
+ columns =
+ QueryDataSetUtils.readTabletValuesFromBuffer(
+ uncompressed, dataTypes, dataTypes.length, rowSize);
+ } else {
+ columns = new Object[dataTypes.length];
+ for (int i = 0; i < dataTypes.length; i++) {
+ columns[i] = decodeColumn(uncompressed, i);
+ }
}
RPCServiceThriftHandlerMetrics.getInstance()
@@ -139,6 +152,23 @@ public class TabletDecoder {
return new Pair<>(columns, uncompressed);
}
+ private static boolean allValueColumnsPlain(
+ TSDataType[] dataTypes, List<TSEncoding> columnEncodings) {
+ for (int i = 0; i < dataTypes.length; i++) {
+ if (columnEncodings.get(i + 1) != TSEncoding.PLAIN ||
!supportsPlainFastPath(dataTypes[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean supportsPlainFastPath(TSDataType dataType) {
+ return switch (dataType) {
+ case BOOLEAN, DATE, INT32, TIMESTAMP, INT64, FLOAT, DOUBLE, STRING,
BLOB, TEXT -> true;
+ case VECTOR, UNKNOWN, OBJECT -> false;
+ };
+ }
+
private Object decodeColumn(ByteBuffer uncompressed, int columnIndex) {
TSDataType dataType = dataTypes[columnIndex];
TSEncoding encoding = columnEncodings.get(columnIndex + 1);
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/TabletDecoderTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/TabletDecoderTest.java
new file mode 100644
index 00000000000..0585c7430b2
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/TabletDecoderTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.utils;
+
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.file.metadata.enums.CompressionType;
+import org.apache.tsfile.file.metadata.enums.TSEncoding;
+import org.apache.tsfile.utils.Binary;
+import org.apache.tsfile.utils.Pair;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.Collections;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+public class TabletDecoderTest {
+
+ @Test
+ public void testDecodeUncompressedPlainTablet() {
+ TSDataType[] dataTypes = {
+ TSDataType.BOOLEAN,
+ TSDataType.INT32,
+ TSDataType.INT64,
+ TSDataType.FLOAT,
+ TSDataType.DOUBLE,
+ TSDataType.TEXT
+ };
+ TabletDecoder decoder =
+ new TabletDecoder(
+ CompressionType.UNCOMPRESSED,
+ dataTypes,
+ Collections.nCopies(dataTypes.length + 1, TSEncoding.PLAIN),
+ 2);
+
+ ByteBuffer timeBuffer = ByteBuffer.allocate(2 * Long.BYTES);
+ timeBuffer.putLong(1L).putLong(2L).flip();
+ assertArrayEquals(new long[] {1L, 2L}, decoder.decodeTime(timeBuffer));
+ assertFalse(timeBuffer.hasRemaining());
+
+ ByteBuffer valueBuffer = ByteBuffer.allocate(128);
+ valueBuffer.put((byte) 1).put((byte) 0);
+ valueBuffer.putInt(3).putInt(4);
+ valueBuffer.putLong(5L).putLong(6L);
+ valueBuffer.putFloat(7.0F).putFloat(8.0F);
+ valueBuffer.putDouble(9.0D).putDouble(10.0D);
+ valueBuffer.putInt(1).put((byte) 'a').putInt(1).put((byte) 'b');
+ valueBuffer.flip();
+
+ Pair<Object[], ByteBuffer> result = decoder.decodeValues(valueBuffer);
+
+ assertArrayEquals(new boolean[] {true, false}, (boolean[]) result.left[0]);
+ assertArrayEquals(new int[] {3, 4}, (int[]) result.left[1]);
+ assertArrayEquals(new long[] {5L, 6L}, (long[]) result.left[2]);
+ assertArrayEquals(new float[] {7.0F, 8.0F}, (float[]) result.left[3],
0.0F);
+ assertArrayEquals(new double[] {9.0D, 10.0D}, (double[]) result.left[4],
0.0D);
+ assertArrayEquals(
+ new Binary[] {new Binary(new byte[] {'a'}), new Binary(new byte[]
{'b'})},
+ (Binary[]) result.left[5]);
+ assertEquals(valueBuffer, result.right);
+ assertFalse(result.right.hasRemaining());
+ }
+}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/TabletRpcDeserializationPerformanceTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/TabletRpcDeserializationPerformanceTest.java
new file mode 100644
index 00000000000..9302cb392d2
--- /dev/null
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/utils/TabletRpcDeserializationPerformanceTest.java
@@ -0,0 +1,617 @@
+/*
+ * 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.utils;
+
+import org.apache.iotdb.commons.exception.IllegalPathException;
+import org.apache.iotdb.commons.exception.MetadataException;
+import org.apache.iotdb.commons.queryengine.utils.TimestampPrecisionUtils;
+import org.apache.iotdb.commons.service.metric.PerformanceOverviewMetrics;
+import org.apache.iotdb.commons.utils.PathUtils;
+import
org.apache.iotdb.db.protocol.thrift.handler.RPCServiceThriftHandlerMetrics;
+import
org.apache.iotdb.db.queryengine.plan.analyze.cache.schema.DataNodeDevicePathCache;
+import org.apache.iotdb.db.queryengine.plan.parser.StatementGenerator;
+import
org.apache.iotdb.db.queryengine.plan.statement.crud.InsertMultiTabletsStatement;
+import
org.apache.iotdb.db.queryengine.plan.statement.crud.InsertTabletStatement;
+import org.apache.iotdb.db.utils.ManualPerformanceTestUtils.Measurement;
+import org.apache.iotdb.db.utils.ManualPerformanceTestUtils.Summary;
+import org.apache.iotdb.service.rpc.thrift.TSInsertTabletsReq;
+
+import org.apache.tsfile.compress.IUnCompressor;
+import org.apache.tsfile.encoding.decoder.Decoder;
+import org.apache.tsfile.enums.TSDataType;
+import org.apache.tsfile.file.metadata.enums.CompressionType;
+import org.apache.tsfile.file.metadata.enums.TSEncoding;
+import org.apache.tsfile.utils.Pair;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+public class TabletRpcDeserializationPerformanceTest {
+
+ private static final String ENABLED_PROPERTY =
"iotdb.tablet.rpc.deserialization.perf.enabled";
+ private static final String ROUNDS_PROPERTY =
"iotdb.tablet.rpc.deserialization.perf.rounds";
+
+ private static final String DECODE_COLUMNS_PROPERTY =
+ "iotdb.tablet.rpc.deserialization.perf.decode.columns";
+ private static final String DECODE_ROWS_PROPERTY =
+ "iotdb.tablet.rpc.deserialization.perf.decode.rows";
+ private static final String DECODE_WARMUP_ITERATIONS_PROPERTY =
+ "iotdb.tablet.rpc.deserialization.perf.decode.warmup.iterations";
+ private static final String DECODE_ITERATIONS_PROPERTY =
+ "iotdb.tablet.rpc.deserialization.perf.decode.iterations";
+
+ private static final String BATCH_TABLETS_PROPERTY =
+ "iotdb.tablet.rpc.deserialization.perf.batch.tablets";
+ private static final String BATCH_MEASUREMENTS_PROPERTY =
+ "iotdb.tablet.rpc.deserialization.perf.batch.measurements";
+ private static final String BATCH_ROWS_PROPERTY =
+ "iotdb.tablet.rpc.deserialization.perf.batch.rows";
+ private static final String BATCH_WARMUP_ITERATIONS_PROPERTY =
+ "iotdb.tablet.rpc.deserialization.perf.batch.warmup.iterations";
+ private static final String BATCH_ITERATIONS_PROPERTY =
+ "iotdb.tablet.rpc.deserialization.perf.batch.iterations";
+
+ private static final String REUSE_TABLETS_PROPERTY =
+ "iotdb.tablet.rpc.deserialization.perf.reuse.tablets";
+ private static final String REUSE_MEASUREMENTS_PROPERTY =
+ "iotdb.tablet.rpc.deserialization.perf.reuse.measurements";
+ private static final String REUSE_WARMUP_ITERATIONS_PROPERTY =
+ "iotdb.tablet.rpc.deserialization.perf.reuse.warmup.iterations";
+ private static final String REUSE_ITERATIONS_PROPERTY =
+ "iotdb.tablet.rpc.deserialization.perf.reuse.iterations";
+
+ private static final Runnable NO_OP = () -> {};
+
+ private static volatile Object benchmarkBlackhole;
+ private static volatile long benchmarkLongBlackhole;
+
+ @Test
+ public void plainTabletDecodeBenchmark() {
+ assumeEnabled();
+
+ final int columnCount = Integer.getInteger(DECODE_COLUMNS_PROPERTY, 64);
+ final int rowCount = Integer.getInteger(DECODE_ROWS_PROPERTY, 256);
+ final int warmupIterations =
Integer.getInteger(DECODE_WARMUP_ITERATIONS_PROPERTY, 500);
+ final int iterations = Integer.getInteger(DECODE_ITERATIONS_PROPERTY,
1_000);
+ final int rounds = Integer.getInteger(ROUNDS_PROPERTY, 5);
+ assertPositive(columnCount, rowCount, warmupIterations, iterations,
rounds);
+
+ final DecodeScenario scenario = createDecodeScenario(columnCount,
rowCount);
+ final DecodedTablet legacy = decodeLegacy(scenario);
+ final DecodedTablet optimized = decodeOptimized(scenario);
+ assertDecodedTabletEquals(legacy, optimized);
+
+ compare(
+ String.format(
+ Locale.ROOT,
+ "PLAIN tablet decode benchmark: columns=%d, rows=%d",
+ columnCount,
+ rowCount),
+ warmupIterations,
+ iterations,
+ rounds,
+ NO_OP,
+ () -> consume(decodeLegacy(scenario)),
+ NO_OP,
+ () -> consume(decodeOptimized(scenario)));
+ }
+
+ @Test
+ public void batchStatementParsingBenchmark() throws IllegalPathException {
+ assumeEnabled();
+
+ final int tabletCount = Integer.getInteger(BATCH_TABLETS_PROPERTY, 32);
+ final int measurementCount =
Integer.getInteger(BATCH_MEASUREMENTS_PROPERTY, 32);
+ final int rowCount = Integer.getInteger(BATCH_ROWS_PROPERTY, 16);
+ final int warmupIterations =
Integer.getInteger(BATCH_WARMUP_ITERATIONS_PROPERTY, 500);
+ final int iterations = Integer.getInteger(BATCH_ITERATIONS_PROPERTY,
1_000);
+ final int rounds = Integer.getInteger(ROUNDS_PROPERTY, 5);
+ assertPositive(tabletCount, measurementCount, rowCount, warmupIterations,
iterations, rounds);
+
+ final TSInsertTabletsReq legacyRequest =
+ createInsertTabletsRequest(tabletCount, measurementCount, rowCount);
+ final TSInsertTabletsReq optimizedRequest =
+ createInsertTabletsRequest(tabletCount, measurementCount, rowCount);
+ final InsertMultiTabletsStatement legacy =
createLegacyStatement(legacyRequest);
+ resetRequestBuffers(optimizedRequest);
+ final InsertMultiTabletsStatement optimized =
+ StatementGenerator.createStatement(optimizedRequest);
+ assertStatementsEqual(legacy, optimized);
+
+ compare(
+ String.format(
+ Locale.ROOT,
+ "Batch tablet statement benchmark: tablets=%d,
measurements/tablet=%d, rows/tablet=%d",
+ tabletCount,
+ measurementCount,
+ rowCount),
+ warmupIterations,
+ iterations,
+ rounds,
+ () -> resetRequestBuffers(legacyRequest),
+ () -> runLegacyStatementParsing(legacyRequest),
+ () -> resetRequestBuffers(optimizedRequest),
+ () -> runOptimizedStatementParsing(optimizedRequest));
+ }
+
+ @Test
+ public void measurementCanonicalizationBenchmark() throws MetadataException {
+ assumeEnabled();
+
+ final int tabletCount = Integer.getInteger(REUSE_TABLETS_PROPERTY, 64);
+ final int measurementCount =
Integer.getInteger(REUSE_MEASUREMENTS_PROPERTY, 64);
+ final int warmupIterations =
Integer.getInteger(REUSE_WARMUP_ITERATIONS_PROPERTY, 500);
+ final int iterations = Integer.getInteger(REUSE_ITERATIONS_PROPERTY,
2_000);
+ final int rounds = Integer.getInteger(ROUNDS_PROPERTY, 5);
+ assertPositive(tabletCount, measurementCount, warmupIterations,
iterations, rounds);
+
+ final MeasurementBatch legacyBatch = createMeasurementBatch(tabletCount,
measurementCount);
+ final MeasurementBatch optimizedBatch =
createMeasurementBatch(tabletCount, measurementCount);
+ final List<List<String>> legacy =
+
PathUtils.checkIsLegalSingleMeasurementListsAndUpdate(legacyBatch.measurementLists);
+
PathUtils.checkIsLegalSingleMeasurementListsAndUpdateInPlace(optimizedBatch.measurementLists);
+ Assert.assertEquals(legacy, optimizedBatch.measurementLists);
+ assertMeasurementsCanonicalized(optimizedBatch.measurementLists);
+
+ compare(
+ String.format(
+ Locale.ROOT,
+ "Measurement canonicalization benchmark: tablets=%d,
measurements/tablet=%d",
+ tabletCount,
+ measurementCount),
+ warmupIterations,
+ iterations,
+ rounds,
+ () -> legacyBatch.reset(),
+ () -> runLegacyMeasurementCanonicalization(legacyBatch),
+ () -> optimizedBatch.reset(),
+ () -> runOptimizedMeasurementCanonicalization(optimizedBatch));
+ }
+
+ private static void assumeEnabled() {
+ Assume.assumeTrue(
+ String.format(
+ "Manual performance UT. Enable with -D%s=true; use the
benchmark-specific properties to tune its workload.",
+ ENABLED_PROPERTY),
+ Boolean.getBoolean(ENABLED_PROPERTY));
+ Assume.assumeTrue(
+ "Current-thread CPU time and allocation metrics are required.",
+ ManualPerformanceTestUtils.enableThreadMetrics());
+ }
+
+ private static DecodeScenario createDecodeScenario(int columnCount, int
rowCount) {
+ final TSDataType[] dataTypes = new TSDataType[columnCount];
+ final List<TSEncoding> encodings = new ArrayList<>(columnCount + 1);
+ encodings.add(TSEncoding.PLAIN);
+ final ByteBuffer timeBuffer = ByteBuffer.allocate(rowCount * Long.BYTES);
+ for (int row = 0; row < rowCount; row++) {
+ timeBuffer.putLong(row);
+ }
+ timeBuffer.flip();
+
+ final ByteBuffer valueBuffer = ByteBuffer.allocate(columnCount * rowCount
* Long.BYTES);
+ for (int column = 0; column < columnCount; column++) {
+ dataTypes[column] = TSDataType.INT64;
+ encodings.add(TSEncoding.PLAIN);
+ for (int row = 0; row < rowCount; row++) {
+ valueBuffer.putLong((long) column * rowCount + row);
+ }
+ }
+ valueBuffer.flip();
+ return new DecodeScenario(dataTypes, encodings, rowCount, timeBuffer,
valueBuffer);
+ }
+
+ private static DecodedTablet decodeLegacy(DecodeScenario scenario) {
+ final LegacyPlainTabletDecoder decoder =
+ new LegacyPlainTabletDecoder(scenario.dataTypes, scenario.encodings,
scenario.rowCount);
+ return new DecodedTablet(
+ decoder.decodeTime(scenario.timeBuffer.duplicate()),
+ decoder.decodeValues(scenario.valueBuffer.duplicate()));
+ }
+
+ private static DecodedTablet decodeOptimized(DecodeScenario scenario) {
+ final TabletDecoder decoder =
+ new TabletDecoder(
+ CompressionType.UNCOMPRESSED,
+ scenario.dataTypes,
+ scenario.encodings,
+ scenario.rowCount);
+ return new DecodedTablet(
+ decoder.decodeTime(scenario.timeBuffer.duplicate()),
+ decoder.decodeValues(scenario.valueBuffer.duplicate()));
+ }
+
+ private static TSInsertTabletsReq createInsertTabletsRequest(
+ int tabletCount, int measurementCount, int rowCount) {
+ final List<String> prefixPaths = new ArrayList<>(tabletCount);
+ final List<List<String>> measurementsList = new ArrayList<>(tabletCount);
+ final List<ByteBuffer> valuesList = new ArrayList<>(tabletCount);
+ final List<ByteBuffer> timestampsList = new ArrayList<>(tabletCount);
+ final List<List<Integer>> typesList = new ArrayList<>(tabletCount);
+ final List<Integer> sizeList = new ArrayList<>(tabletCount);
+ for (int tablet = 0; tablet < tabletCount; tablet++) {
+ prefixPaths.add("root.tablet_rpc_performance.d" + tablet);
+ final List<String> measurements = new ArrayList<>(measurementCount);
+ final List<Integer> types = new ArrayList<>(measurementCount);
+ final ByteBuffer values = ByteBuffer.allocate(measurementCount *
rowCount * Long.BYTES);
+ for (int measurement = 0; measurement < measurementCount; measurement++)
{
+ measurements.add("s" + measurement);
+ types.add((int) TSDataType.INT64.serialize());
+ for (int row = 0; row < rowCount; row++) {
+ values.putLong((long) measurement * rowCount + row);
+ }
+ }
+ values.flip();
+
+ final ByteBuffer timestamps = ByteBuffer.allocate(rowCount * Long.BYTES);
+ for (int row = 0; row < rowCount; row++) {
+ timestamps.putLong(row);
+ }
+ timestamps.flip();
+
+ measurementsList.add(measurements);
+ valuesList.add(values);
+ timestampsList.add(timestamps);
+ typesList.add(types);
+ sizeList.add(rowCount);
+ }
+ return new TSInsertTabletsReq(
+ 0L, prefixPaths, measurementsList, valuesList, timestampsList,
typesList, sizeList);
+ }
+
+ // Keep the origin/master implementation for an in-process, same-JVM
comparison.
+ private static InsertMultiTabletsStatement
createLegacyStatement(TSInsertTabletsReq request)
+ throws IllegalPathException {
+ final long startTime = System.nanoTime();
+ final InsertMultiTabletsStatement insertStatement = new
InsertMultiTabletsStatement();
+ final List<InsertTabletStatement> insertTabletStatementList = new
ArrayList<>();
+ for (int i = 0; i < request.prefixPaths.size(); i++) {
+ final InsertTabletStatement insertTabletStatement = new
InsertTabletStatement();
+ insertTabletStatement.setDevicePath(
+
DataNodeDevicePathCache.getInstance().getPartialPath(request.prefixPaths.get(i)));
+
insertTabletStatement.setMeasurements(request.measurementsList.get(i).toArray(new
String[0]));
+ final long[] timestamps =
+ QueryDataSetUtils.readTimesFromBuffer(
+ request.timestampsList.get(i), request.sizeList.get(i));
+ if (timestamps.length != 0) {
+
TimestampPrecisionUtils.checkTimestampPrecision(timestamps[timestamps.length -
1]);
+ }
+ insertTabletStatement.setTimes(timestamps);
+ insertTabletStatement.setColumns(
+ QueryDataSetUtils.readTabletValuesFromBuffer(
+ request.valuesList.get(i),
+ request.typesList.get(i),
+ request.measurementsList.get(i).size(),
+ request.sizeList.get(i)));
+ insertTabletStatement.setBitMaps(
+ QueryDataSetUtils.readBitMapsFromBuffer(
+ request.valuesList.get(i),
+ request.measurementsList.get(i).size(),
+ request.sizeList.get(i))
+ .orElse(null));
+ insertTabletStatement.setRowCount(request.sizeList.get(i));
+ final TSDataType[] dataTypes = new
TSDataType[request.typesList.get(i).size()];
+ for (int j = 0; j < dataTypes.length; j++) {
+ dataTypes[j] = TSDataType.deserialize((byte)
request.typesList.get(i).get(j).intValue());
+ }
+ insertTabletStatement.setDataTypes(dataTypes);
+ insertTabletStatement.setAligned(request.isAligned);
+ if (!insertTabletStatement.isEmpty()) {
+ insertTabletStatementList.add(insertTabletStatement);
+ }
+ }
+ insertStatement.setInsertTabletStatementList(insertTabletStatementList);
+ PerformanceOverviewMetrics.getInstance().recordParseCost(System.nanoTime()
- startTime);
+ return insertStatement;
+ }
+
+ private static MeasurementBatch createMeasurementBatch(int tabletCount, int
measurementCount) {
+ final List<List<String>> measurementLists = new ArrayList<>(tabletCount);
+ final String[][] originals = new String[tabletCount][measurementCount];
+ for (int tablet = 0; tablet < tabletCount; tablet++) {
+ final List<String> measurements = new ArrayList<>(measurementCount);
+ for (int measurement = 0; measurement < measurementCount; measurement++)
{
+ final String value = new String("tablet_rpc_performance_s" +
measurement);
+ originals[tablet][measurement] = value;
+ measurements.add(value);
+ }
+ measurementLists.add(measurements);
+ }
+ return new MeasurementBatch(measurementLists, originals);
+ }
+
+ private static void runLegacyStatementParsing(TSInsertTabletsReq request) {
+ try {
+ benchmarkBlackhole = createLegacyStatement(request);
+ } catch (IllegalPathException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private static void runOptimizedStatementParsing(TSInsertTabletsReq request)
{
+ try {
+ benchmarkBlackhole = StatementGenerator.createStatement(request);
+ } catch (IllegalPathException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private static void runLegacyMeasurementCanonicalization(MeasurementBatch
batch) {
+ try {
+ benchmarkBlackhole =
+
PathUtils.checkIsLegalSingleMeasurementListsAndUpdate(batch.measurementLists);
+ } catch (MetadataException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private static void runOptimizedMeasurementCanonicalization(MeasurementBatch
batch) {
+ try {
+
PathUtils.checkIsLegalSingleMeasurementListsAndUpdateInPlace(batch.measurementLists);
+ benchmarkBlackhole = batch.measurementLists;
+ } catch (MetadataException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private static void resetRequestBuffers(TSInsertTabletsReq request) {
+ request.timestampsList.forEach(ByteBuffer::rewind);
+ request.valuesList.forEach(ByteBuffer::rewind);
+ }
+
+ private static void consume(DecodedTablet decodedTablet) {
+ benchmarkLongBlackhole =
decodedTablet.timestamps[decodedTablet.timestamps.length - 1];
+ benchmarkBlackhole = decodedTablet.values;
+ }
+
+ private static void assertDecodedTabletEquals(DecodedTablet expected,
DecodedTablet actual) {
+ Assert.assertArrayEquals(expected.timestamps, actual.timestamps);
+ Assert.assertEquals(expected.values.left.length,
actual.values.left.length);
+ for (int i = 0; i < expected.values.left.length; i++) {
+ Assert.assertArrayEquals((long[]) expected.values.left[i], (long[])
actual.values.left[i]);
+ }
+ Assert.assertFalse(expected.values.right.hasRemaining());
+ Assert.assertFalse(actual.values.right.hasRemaining());
+ }
+
+ private static void assertStatementsEqual(
+ InsertMultiTabletsStatement expected, InsertMultiTabletsStatement
actual) {
+ final List<InsertTabletStatement> expectedTablets =
expected.getInsertTabletStatementList();
+ final List<InsertTabletStatement> actualTablets =
actual.getInsertTabletStatementList();
+ Assert.assertEquals(expectedTablets.size(), actualTablets.size());
+ for (int i = 0; i < expectedTablets.size(); i++) {
+ Assert.assertArrayEquals(
+ expectedTablets.get(i).getMeasurements(),
actualTablets.get(i).getMeasurements());
+ Assert.assertArrayEquals(
+ expectedTablets.get(i).getDataTypes(),
actualTablets.get(i).getDataTypes());
+ Assert.assertArrayEquals(expectedTablets.get(i).getTimes(),
actualTablets.get(i).getTimes());
+ Assert.assertEquals(expectedTablets.get(i).getRowCount(),
actualTablets.get(i).getRowCount());
+ }
+ }
+
+ private static void assertMeasurementsCanonicalized(List<List<String>>
measurementLists) {
+ final List<String> firstTablet = measurementLists.get(0);
+ for (int tablet = 1; tablet < measurementLists.size(); tablet++) {
+ for (int measurement = 0; measurement < firstTablet.size();
measurement++) {
+ Assert.assertSame(
+ firstTablet.get(measurement),
measurementLists.get(tablet).get(measurement));
+ }
+ }
+ }
+
+ private static void assertPositive(int... values) {
+ for (int value : values) {
+ Assert.assertTrue(value > 0);
+ }
+ }
+
+ private static void compare(
+ String title,
+ int warmupIterations,
+ int iterations,
+ int rounds,
+ Runnable legacyBefore,
+ Runnable legacyOperation,
+ Runnable optimizedBefore,
+ Runnable optimizedOperation) {
+ for (int i = 0; i < warmupIterations; i++) {
+ if ((i & 1) == 0) {
+ legacyBefore.run();
+ legacyOperation.run();
+ optimizedBefore.run();
+ optimizedOperation.run();
+ } else {
+ optimizedBefore.run();
+ optimizedOperation.run();
+ legacyBefore.run();
+ legacyOperation.run();
+ }
+ }
+
+ final Measurement[] legacyMeasurements = new Measurement[rounds];
+ final Measurement[] optimizedMeasurements = new Measurement[rounds];
+ for (int round = 0; round < rounds; round++) {
+ if ((round & 1) == 0) {
+ legacyMeasurements[round] =
+ ManualPerformanceTestUtils.measure(iterations, legacyBefore,
legacyOperation);
+ optimizedMeasurements[round] =
+ ManualPerformanceTestUtils.measure(iterations, optimizedBefore,
optimizedOperation);
+ } else {
+ optimizedMeasurements[round] =
+ ManualPerformanceTestUtils.measure(iterations, optimizedBefore,
optimizedOperation);
+ legacyMeasurements[round] =
+ ManualPerformanceTestUtils.measure(iterations, legacyBefore,
legacyOperation);
+ }
+ }
+
+ final Summary legacy =
ManualPerformanceTestUtils.summarize(legacyMeasurements, iterations);
+ final Summary optimized =
+ ManualPerformanceTestUtils.summarize(optimizedMeasurements,
iterations);
+ System.out.printf(
+ Locale.ROOT,
+ "%s, warmups=%d, iterations/round=%d, rounds=%d%n",
+ title,
+ warmupIterations,
+ iterations,
+ rounds);
+ printSummary("origin/master", legacy);
+ printSummary("optimized", optimized);
+ System.out.printf(
+ Locale.ROOT,
+ " change: CPU speedup=%.2fx, allocation reduction=%.1f%%, peak-heap
reduction=%.1f%%%n",
+ ratio(legacy.getCpuNanosPerOperation(),
optimized.getCpuNanosPerOperation()),
+ reduction(
+ legacy.getAllocatedBytesPerOperation(),
optimized.getAllocatedBytesPerOperation()),
+ reduction(legacy.getPeakHeapDeltaBytes(),
optimized.getPeakHeapDeltaBytes()));
+ }
+
+ private static void printSummary(String label, Summary summary) {
+ System.out.printf(
+ Locale.ROOT,
+ " %-13s CPU=%.3f us/op, allocated=%.1f bytes/op, peak heap delta=%.3f
MiB%n",
+ label,
+ summary.getCpuNanosPerOperation() / 1_000.0,
+ summary.getAllocatedBytesPerOperation(),
+ summary.getPeakHeapDeltaBytes() / 1024.0 / 1024.0);
+ }
+
+ private static double ratio(double baseline, double optimized) {
+ return optimized == 0 ? Double.POSITIVE_INFINITY : baseline / optimized;
+ }
+
+ private static double reduction(double baseline, double optimized) {
+ return baseline == 0 ? 0 : (baseline - optimized) * 100.0 / baseline;
+ }
+
+ private static final class DecodeScenario {
+
+ private final TSDataType[] dataTypes;
+ private final List<TSEncoding> encodings;
+ private final int rowCount;
+ private final ByteBuffer timeBuffer;
+ private final ByteBuffer valueBuffer;
+
+ private DecodeScenario(
+ TSDataType[] dataTypes,
+ List<TSEncoding> encodings,
+ int rowCount,
+ ByteBuffer timeBuffer,
+ ByteBuffer valueBuffer) {
+ this.dataTypes = dataTypes;
+ this.encodings = encodings;
+ this.rowCount = rowCount;
+ this.timeBuffer = timeBuffer;
+ this.valueBuffer = valueBuffer;
+ }
+ }
+
+ private static final class DecodedTablet {
+
+ private final long[] timestamps;
+ private final Pair<Object[], ByteBuffer> values;
+
+ private DecodedTablet(long[] timestamps, Pair<Object[], ByteBuffer>
values) {
+ this.timestamps = timestamps;
+ this.values = values;
+ }
+ }
+
+ private static final class MeasurementBatch {
+
+ private final List<List<String>> measurementLists;
+ private final String[][] originals;
+
+ private MeasurementBatch(List<List<String>> measurementLists, String[][]
originals) {
+ this.measurementLists = measurementLists;
+ this.originals = originals;
+ }
+
+ private void reset() {
+ for (int tablet = 0; tablet < originals.length; tablet++) {
+ final List<String> measurements = measurementLists.get(tablet);
+ for (int measurement = 0; measurement < originals[tablet].length;
measurement++) {
+ measurements.set(measurement, originals[tablet][measurement]);
+ }
+ }
+ }
+ }
+
+ /** The PLAIN INT64 decoding path from origin/master, kept only for
benchmark comparison. */
+ private static final class LegacyPlainTabletDecoder {
+
+ private final TSDataType[] dataTypes;
+ private final List<TSEncoding> columnEncodings;
+
+ @SuppressWarnings("unused")
+ private final IUnCompressor unCompressor;
+
+ private final int rowSize;
+
+ private LegacyPlainTabletDecoder(
+ TSDataType[] dataTypes, List<TSEncoding> columnEncodings, int rowSize)
{
+ this.dataTypes = dataTypes;
+ this.columnEncodings = columnEncodings;
+ this.unCompressor =
IUnCompressor.getUnCompressor(CompressionType.UNCOMPRESSED);
+ this.rowSize = rowSize;
+ }
+
+ private long[] decodeTime(ByteBuffer buffer) {
+ final int compressedSize = buffer.remaining();
+
RPCServiceThriftHandlerMetrics.getInstance().recordUnCompressionSizeTimer(buffer.remaining());
+
RPCServiceThriftHandlerMetrics.getInstance().recordCompressionSizeTimer(compressedSize);
+
RPCServiceThriftHandlerMetrics.getInstance().recordMemoryUsage(buffer.remaining());
+
+ final long startDecodeTime = System.nanoTime();
+ final Decoder decoder = Decoder.getDecoderByType(columnEncodings.get(0),
TSDataType.INT64);
+ final long[] timestamps = new long[rowSize];
+ for (int row = 0; row < rowSize; row++) {
+ timestamps[row] = decoder.readLong(buffer);
+ }
+ RPCServiceThriftHandlerMetrics.getInstance()
+ .recordDecodeLatencyTimer(System.nanoTime() - startDecodeTime);
+ return timestamps;
+ }
+
+ private Pair<Object[], ByteBuffer> decodeValues(ByteBuffer buffer) {
+ final int compressedSize = buffer.remaining();
+
RPCServiceThriftHandlerMetrics.getInstance().recordUnCompressionSizeTimer(buffer.remaining());
+
RPCServiceThriftHandlerMetrics.getInstance().recordCompressionSizeTimer(compressedSize);
+
+ final long startDecodeTime = System.nanoTime();
+ final Object[] columns = new Object[dataTypes.length];
+ for (int column = 0; column < dataTypes.length; column++) {
+ final Decoder decoder =
+ Decoder.getDecoderByType(columnEncodings.get(column + 1),
dataTypes[column]);
+ final long[] values = new long[rowSize];
+ for (int row = 0; row < rowSize; row++) {
+ values[row] = decoder.readLong(buffer);
+ }
+ columns[column] = values;
+ }
+ RPCServiceThriftHandlerMetrics.getInstance()
+ .recordDecodeLatencyTimer(System.nanoTime() - startDecodeTime);
+ return new Pair<>(columns, buffer);
+ }
+ }
+}
diff --git
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/PathUtils.java
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/PathUtils.java
index 1803ebb68ca..36b3ff84ae7 100644
---
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/PathUtils.java
+++
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/utils/PathUtils.java
@@ -79,7 +79,7 @@ public class PathUtils {
}
// skip checking duplicated measurements
Map<String, String> checkedMeasurements = new HashMap<>();
- List<List<String>> res = new ArrayList<>();
+ List<List<String>> res = new ArrayList<>(measurementLists.size());
for (List<String> measurements : measurementLists) {
res.add(checkLegalSingleMeasurementsAndSkipDuplicate(measurements,
checkedMeasurements));
}
@@ -95,7 +95,7 @@ public class PathUtils {
if (measurements == null) {
return null;
}
- List<String> res = new ArrayList<>();
+ List<String> res = new ArrayList<>(measurements.size());
for (String measurement : measurements) {
if (measurement == null) {
res.add(null);
@@ -120,7 +120,7 @@ public class PathUtils {
if (measurements == null) {
return null;
}
- List<String> res = new ArrayList<>();
+ List<String> res = new ArrayList<>(measurements.size());
for (String measurement : measurements) {
if (measurement == null || measurement.isEmpty()) {
res.add(null);
@@ -131,6 +131,67 @@ public class PathUtils {
return res;
}
+ /**
+ * Check and canonicalize single measurements in place. This avoids
allocating another list when
+ * the input is a mutable list created by Thrift.
+ */
+ public static void
checkIsLegalSingleMeasurementsAndUpdateInPlace(List<String> measurements)
+ throws MetadataException {
+ if (measurements == null) {
+ return;
+ }
+ for (int i = 0; i < measurements.size(); i++) {
+ String measurement = measurements.get(i);
+ measurements.set(
+ i,
+ measurement == null || measurement.isEmpty()
+ ? null
+ : checkAndReturnSingleMeasurement(measurement));
+ }
+ }
+
+ /**
+ * Check and canonicalize lists of single measurements in place. Duplicate
measurements in one
+ * request are checked only once.
+ */
+ public static void checkIsLegalSingleMeasurementListsAndUpdateInPlace(
+ List<List<String>> measurementLists) throws MetadataException {
+ if (measurementLists == null || measurementLists.isEmpty()) {
+ return;
+ }
+ if (measurementLists.size() == 1) {
+ List<String> measurements = measurementLists.get(0);
+ if (measurements == null) {
+ return;
+ }
+ for (int i = 0; i < measurements.size(); i++) {
+ String measurement = measurements.get(i);
+ if (measurement != null) {
+ measurements.set(i, checkAndReturnSingleMeasurement(measurement));
+ }
+ }
+ return;
+ }
+ Map<String, String> checkedMeasurements = new HashMap<>();
+ for (List<String> measurements : measurementLists) {
+ if (measurements == null) {
+ continue;
+ }
+ for (int i = 0; i < measurements.size(); i++) {
+ String measurement = measurements.get(i);
+ if (measurement == null) {
+ continue;
+ }
+ String checked = checkedMeasurements.get(measurement);
+ if (checked == null) {
+ checked = checkAndReturnSingleMeasurement(measurement);
+ checkedMeasurements.put(measurement, checked);
+ }
+ measurements.set(i, checked);
+ }
+ }
+ }
+
/**
* check whether measurement is legal according to syntax convention
measurement could be like a.b
* (more than one node name), in template?
diff --git
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/utils/PathUtilsTest.java
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/utils/PathUtilsTest.java
new file mode 100644
index 00000000000..f4cf96af02c
--- /dev/null
+++
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/utils/PathUtilsTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.commons.utils;
+
+import org.apache.iotdb.commons.exception.MetadataException;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+public class PathUtilsTest {
+
+ @Test
+ public void testCheckSingleMeasurementsInPlace() throws MetadataException {
+ List<String> measurements =
+ new ArrayList<>(Arrays.asList("path_utils_test_s1",
"`path_utils_test_s2`", "", null));
+
+ PathUtils.checkIsLegalSingleMeasurementsAndUpdateInPlace(measurements);
+
+ assertEquals("path_utils_test_s1", measurements.get(0));
+ assertEquals("path_utils_test_s2", measurements.get(1));
+ assertNull(measurements.get(2));
+ assertNull(measurements.get(3));
+ }
+
+ @Test
+ public void testCheckSingleMeasurementListsInPlaceReusesStrings() throws
MetadataException {
+ List<List<String>> measurementLists = new ArrayList<>();
+ measurementLists.add(
+ new ArrayList<>(
+ Arrays.asList(new String("path_utils_batch_s1"), new
String("`path_utils_batch_s2`"))));
+ measurementLists.add(
+ new ArrayList<>(
+ Arrays.asList(new String("path_utils_batch_s1"), new
String("`path_utils_batch_s2`"))));
+
+
PathUtils.checkIsLegalSingleMeasurementListsAndUpdateInPlace(measurementLists);
+
+ assertSame(measurementLists.get(0).get(0), measurementLists.get(1).get(0));
+ assertSame(measurementLists.get(0).get(1), measurementLists.get(1).get(1));
+ assertEquals("path_utils_batch_s2", measurementLists.get(0).get(1));
+ }
+
+ @Test
+ public void testCheckSingleMeasurementListInPlace() throws MetadataException
{
+ List<String> measurements =
+ new ArrayList<>(Arrays.asList("path_utils_batch_s1",
"`path_utils_batch_s2`", null));
+
+ PathUtils.checkIsLegalSingleMeasurementListsAndUpdateInPlace(
+ Collections.singletonList(measurements));
+
+ assertEquals("path_utils_batch_s1", measurements.get(0));
+ assertEquals("path_utils_batch_s2", measurements.get(1));
+ assertNull(measurements.get(2));
+ }
+}