diff --git a/README.md b/README.md
index 4d078152..e337f894 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,9 @@ This project comprises JDK language compatible modules for the [Oxia][oxia] serv
the following capabilities:
- [Client](client/) for the Oxia service
-- [Testcontainer](testcontainers/) for local integration testing with an Oxia service.
+- [OpenTelemetry Metrics](client-metrics-opentelemetry/) integration with the client
+- [Testcontainer](testcontainers/) for integration testing with a local Oxia service
+- [Performance Test Tool](perf/) for performance testing with an Oxia service.
## Build
diff --git a/client-metrics-opentelemetry/pom.xml b/client-metrics-opentelemetry/pom.xml
index 7a640349..016b77d2 100644
--- a/client-metrics-opentelemetry/pom.xml
+++ b/client-metrics-opentelemetry/pom.xml
@@ -44,21 +44,6 @@
oxia-client-metrics-api
${project.version}
-
- org.projectlombok
- lombok
- provided
-
-
- org.junit.jupiter
- junit-jupiter
- test
-
-
- org.mockito
- mockito-junit-jupiter
- test
-
diff --git a/client/pom.xml b/client/pom.xml
index 3259dc78..7257d079 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -30,7 +30,6 @@
Oxia Client
- 3.0.0
3.1.4
1.51.0
1.2.2
@@ -96,15 +95,6 @@
zero-allocation-hashing
${zah.version}
-
- org.slf4j
- slf4j-api
-
-
- org.projectlombok
- lombok
- provided
-
io.projectreactor
reactor-test
@@ -117,42 +107,16 @@
${project.version}
test
-
- org.assertj
- assertj-core
- test
-
-
- org.awaitility
- awaitility
- ${awaitility.version}
- test
-
-
- org.awaitility
- awaitility-proxy
- ${awaitility.version}
- test
-
-
- org.junit.jupiter
- junit-jupiter
- test
-
-
- org.mockito
- mockito-junit-jupiter
- test
-
-
- org.slf4j
- slf4j-simple
- test
-
org.testcontainers
junit-jupiter
test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+
+
diff --git a/perf/README.md b/perf/README.md
new file mode 100644
index 00000000..2e3b7a95
--- /dev/null
+++ b/perf/README.md
@@ -0,0 +1,35 @@
+# Performance Test Tool
+
+## Usage
+
+```commandline
+Usage: oxia-java perf [options]
+ Options:
+ --batch-linger-ms
+ Batch linger time
+ Default: 5
+ -h, --help
+ Help message
+ -k, --keys-cardinality
+ Number of unique keys
+ Default: 1000
+ --max-requests-per-batch
+ Maximum requests per batch
+ Default: 1000
+ -r, --rate
+ Request rate, ops/s
+ Default: 100.0
+ -p, --read-write-percent
+ Percentage of read requests, compared to total requests
+ Default: 80.0
+ --request-timeout-ms
+ Requests timeout
+ Default: 30000
+ -a, --service-addr
+ Oxia Service Address
+ Default: localhost:6648
+ -s, --value-size
+ Size of the values to write
+ Default: 128
+```
+
diff --git a/perf/pom.xml b/perf/pom.xml
new file mode 100644
index 00000000..c827b32a
--- /dev/null
+++ b/perf/pom.xml
@@ -0,0 +1,93 @@
+
+
+
+ 4.0.0
+
+
+ io.streamnative.oxia
+ oxia-java
+ 0.0.6-SNAPSHOT
+
+
+ oxia-perf
+ Oxia Perf Client
+
+
+ 2.1.9
+ 1.82
+ 3.4.1
+
+
+
+
+ com.beust
+ jcommander
+ ${jcommander.version}
+ compile
+
+
+ io.streamnative.oxia
+ oxia-client
+ ${project.version}
+
+
+ org.hdrhistogram
+ HdrHistogram
+ ${hdr-histogram.version}
+
+
+ org.slf4j
+ slf4j-simple
+ ${slf4j.version}
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ ${maven.shade.plugin.version}
+
+
+
+ shade
+
+ package
+
+
+
+ *:*
+
+ module-info.class
+ META-INF/MANIFEST.MF
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/perf/src/main/java/io/streamnative/oxia/client/perf/PerfArguments.java b/perf/src/main/java/io/streamnative/oxia/client/perf/PerfArguments.java
new file mode 100644
index 00000000..85ea4c2a
--- /dev/null
+++ b/perf/src/main/java/io/streamnative/oxia/client/perf/PerfArguments.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright © 2022-2023 StreamNative Inc.
+ *
+ * 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 io.streamnative.oxia.client.perf;
+
+
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import io.streamnative.oxia.client.OxiaClientBuilder;
+
+@Parameters(commandDescription = "Test Oxia Java client performance.")
+public class PerfArguments {
+
+ @Parameter(
+ names = {"-h", "--help"},
+ description = "Help message",
+ help = true)
+ boolean help;
+
+ @Parameter(
+ names = {"-a", "--service-addr"},
+ description = "Oxia Service Address")
+ String serviceAddr = "localhost:6648";
+
+ @Parameter(
+ names = {"-r", "--rate"},
+ description = "Request rate, ops/s")
+ double requestsRate = 100.0;
+
+ @Parameter(
+ names = {"-p", "--read-write-percent"},
+ description = "Percentage of read requests, compared to total requests")
+ double readPercentage = 80.0;
+
+ @Parameter(
+ names = {"-k", "--keys-cardinality"},
+ description = "Number of unique keys")
+ int keysCardinality = 1_000;
+
+ @Parameter(
+ names = {"-s", "--value-size"},
+ description = "Size of the values to write")
+ int valueSize = 128;
+
+ @Parameter(
+ names = {"--batch-linger-ms"},
+ description = "Batch linger time")
+ long batchLingerMs = OxiaClientBuilder.DefaultBatchLinger.toMillis();
+
+ @Parameter(
+ names = {"--max-requests-per-batch"},
+ description = "Maximum requests per batch")
+ int maxRequestsPerBatch = OxiaClientBuilder.DefaultMaxRequestsPerBatch;
+
+ @Parameter(
+ names = {"--request-timeout-ms"},
+ description = "Requests timeout")
+ long requestTimeoutMs = OxiaClientBuilder.DefaultRequestTimeout.toMillis();
+}
diff --git a/perf/src/main/java/io/streamnative/oxia/client/perf/PerfClient.java b/perf/src/main/java/io/streamnative/oxia/client/perf/PerfClient.java
new file mode 100644
index 00000000..a86916e1
--- /dev/null
+++ b/perf/src/main/java/io/streamnative/oxia/client/perf/PerfClient.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright © 2022-2023 StreamNative Inc.
+ *
+ * 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 io.streamnative.oxia.client.perf;
+
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.ParameterException;
+import com.google.common.util.concurrent.RateLimiter;
+import io.streamnative.oxia.client.OxiaClientBuilder;
+import io.streamnative.oxia.client.api.AsyncOxiaClient;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.LongAdder;
+import java.util.function.Function;
+import lombok.extern.slf4j.Slf4j;
+import org.HdrHistogram.Histogram;
+import org.HdrHistogram.Recorder;
+
+@Slf4j
+public class PerfClient {
+
+ private static final List keys = new ArrayList<>();
+ private static final LongAdder writeOps = new LongAdder();
+ private static final LongAdder readOps = new LongAdder();
+ private static final LongAdder writeFailed = new LongAdder();
+ private static final LongAdder readFailed = new LongAdder();
+ private static final Recorder writeLatency = new Recorder(TimeUnit.SECONDS.toMicros(120_000), 5);
+ private static final Recorder readLatency = new Recorder(TimeUnit.SECONDS.toMicros(120_000), 5);
+ private static final PerfArguments arguments = new PerfArguments();
+
+ public static void main(String[] args) throws Exception {
+ JCommander jc = new JCommander(arguments);
+ jc.setProgramName("oxia-java perf");
+
+ try {
+ jc.parse(args);
+ } catch (ParameterException e) {
+ System.out.println(e.getMessage());
+ jc.usage();
+ System.exit(1);
+ }
+
+ if (arguments.help) {
+ jc.usage();
+ System.exit(1);
+ }
+
+ AsyncOxiaClient client = new OxiaClientBuilder(arguments.serviceAddr)
+ .batchLinger(Duration.ofMillis(arguments.batchLingerMs))
+ .maxRequestsPerBatch(arguments.maxRequestsPerBatch)
+ .requestTimeout(Duration.ofMillis(arguments.requestTimeoutMs))
+ .asyncClient()
+ .get();
+
+ for (int i = 0; i < arguments.keysCardinality; i++) {
+ keys.add("key-" + i);
+ }
+
+ ExecutorService executor = Executors.newCachedThreadPool();
+
+ if (arguments.readPercentage != 100) {
+ executor.execute(() -> generateWriteTraffic(client));
+ }
+
+ if (arguments.readPercentage != 0) {
+ executor.execute(() -> generateReadTraffic(client));
+ }
+
+ Histogram writeReportHistogram = null;
+ Histogram readReportHistogram = null;
+
+ long oldTime = System.nanoTime();
+
+ while (true) {
+ try {
+ Thread.sleep(10000);
+ } catch (InterruptedException e) {
+ break;
+ }
+
+ long now = System.nanoTime();
+ double elapsed = (now - oldTime) / 1e9;
+
+ double writeRate = writeOps.sumThenReset() / elapsed;
+ double readRate = readOps.sumThenReset() / elapsed;
+ double failedWriteRate = writeFailed.sumThenReset() / elapsed;
+ double failedReadRate = readFailed.sumThenReset() / elapsed;
+
+ writeReportHistogram = writeLatency.getIntervalHistogram(writeReportHistogram);
+ readReportHistogram = readLatency.getIntervalHistogram(readReportHistogram);
+
+ log.info("""
+ Stats - Total ops: {} ops/s - Failed ops: {} ops/s
+ Write ops {} w/s Latency ms: 50% {} - 95% {} - 99% {} - 99.9% {} - max {}
+ Read ops {} r/s Latency ms: 50% {} - 95% {} - 99% {} - 99.9% {} - max {}""",
+ INT_FORMAT.apply(writeRate + readRate), INT_FORMAT.apply(failedWriteRate + failedReadRate),
+
+ INT_FORMAT.apply(writeRate),
+ DEC_FORMAT.apply(writeReportHistogram.getValueAtPercentile(50) / 1000.0),
+ DEC_FORMAT.apply(writeReportHistogram.getValueAtPercentile(95) / 1000.0),
+ DEC_FORMAT.apply(writeReportHistogram.getValueAtPercentile(99) / 1000.0),
+ DEC_FORMAT.apply(writeReportHistogram.getValueAtPercentile(99.9) / 1000.0),
+ DEC_FORMAT.apply(writeReportHistogram.getMaxValue() / 1000.0),
+
+ INT_FORMAT.apply(readRate),
+ DEC_FORMAT.apply(readReportHistogram.getValueAtPercentile(50) / 1000.0),
+ DEC_FORMAT.apply(readReportHistogram.getValueAtPercentile(95) / 1000.0),
+ DEC_FORMAT.apply(readReportHistogram.getValueAtPercentile(99) / 1000.0),
+ DEC_FORMAT.apply(readReportHistogram.getValueAtPercentile(99.9) / 1000.0),
+ DEC_FORMAT.apply(readReportHistogram.getMaxValue() / 1000.0)
+
+ );
+
+ writeReportHistogram.reset();
+ readReportHistogram.reset();
+
+ oldTime = now;
+ }
+ }
+
+ private static void generateWriteTraffic(AsyncOxiaClient client) {
+ double writeRate = arguments.requestsRate * (100.0 - arguments.readPercentage) / 100;
+ RateLimiter limiter = RateLimiter.create(writeRate);
+
+ byte[] value = new byte[arguments.valueSize];
+ Random rand = new Random();
+
+ while (true) {
+ limiter.acquire();
+
+ String key = keys.get(rand.nextInt(keys.size()));
+
+ long start = System.nanoTime();
+ client.put(key, value).thenRun(() -> {
+ writeOps.increment();
+ long latencyMicros = NANOSECONDS.toMicros(System.nanoTime() - start);
+ writeLatency.recordValue(latencyMicros);
+ }).exceptionally(ex -> {
+ log.warn("Write operation failed {}", ex.getMessage());
+ writeFailed.increment();
+ return null;
+ });
+ }
+ }
+
+ private static void generateReadTraffic(AsyncOxiaClient client) {
+ double readRate = arguments.requestsRate * arguments.readPercentage / 100;
+ RateLimiter limiter = RateLimiter.create(readRate);
+
+ Random rand = new Random();
+
+ while (true) {
+ limiter.acquire();
+
+ String key = keys.get(rand.nextInt(keys.size()));
+
+ long start = System.nanoTime();
+ client.get(key).thenRun(() -> {
+ readOps.increment();
+ long latencyMicros = NANOSECONDS.toMicros(System.nanoTime() - start);
+ readLatency.recordValue(latencyMicros);
+ }).exceptionally(ex -> {
+ log.warn("Read operation failed {}", ex.getMessage());
+ readFailed.increment();
+ return null;
+ });
+ }
+ }
+
+ static final Function DEC_FORMAT = d -> String.format("%7.3f", d);
+ static final Function INT_FORMAT = d -> String.format("%7.0f", d);
+}
diff --git a/pom.xml b/pom.xml
index eee95f10..ce7f524d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -46,6 +46,7 @@
client-metrics-opentelemetry
testcontainers
pulsar-metadatastore-oxia
+ perf
@@ -63,6 +64,7 @@
UTF-8
3.24.1
+ 3.0.0
10.3.3
1.18.24
2.0.5
@@ -89,50 +91,65 @@
pom
import
-
- org.slf4j
- slf4j-api
- ${slf4j.version}
-
-
- org.projectlombok
- lombok
- ${lombok.version}
- provided
-
-
- org.assertj
- assertj-core
- ${assertj.version}
- test
-
-
- org.junit.jupiter
- junit-jupiter
- ${junit.jupiter.version}
- test
-
-
- org.junit.jupiter
- junit-jupiter-params
- ${junit.jupiter.version}
- test
-
-
- org.mockito
- mockito-junit-jupiter
- ${mockito.junit.jupiter.version}
- test
-
-
- org.slf4j
- slf4j-simple
- ${slf4j.version}
- test
-
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+ provided
+
+
+ org.assertj
+ assertj-core
+ ${assertj.version}
+ test
+
+
+ org.awaitility
+ awaitility
+ ${awaitility.version}
+ test
+
+
+ org.awaitility
+ awaitility-proxy
+ ${awaitility.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${junit.jupiter.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ ${junit.jupiter.version}
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ ${mockito.junit.jupiter.version}
+ test
+
+
+ org.slf4j
+ slf4j-simple
+ ${slf4j.version}
+ test
+
+
+
diff --git a/pulsar-metadatastore-oxia/pom.xml b/pulsar-metadatastore-oxia/pom.xml
index 034d65aa..1fb469bb 100644
--- a/pulsar-metadatastore-oxia/pom.xml
+++ b/pulsar-metadatastore-oxia/pom.xml
@@ -39,12 +39,8 @@
2.12.0-SNAPSHOT
10.3.3
- 1.18.24
- 2.0.5
7.7.0
- 3.18.1
4.1.12.1
- 4.2.0
1.1.8.4
3.2.0
@@ -76,18 +72,6 @@
-
- org.projectlombok
- lombok
- ${lombok.version}
- provided
-
-
- org.slf4j
- slf4j-api
- ${slf4j.version}
- provided
-
io.dropwizard.metrics
metrics-core
@@ -119,24 +103,6 @@
-
- org.assertj
- assertj-core
- ${assertj-core.version}
- test
-
-
- org.awaitility
- awaitility
- ${awaitility.version}
- test
-
-
- org.slf4j
- slf4j-simple
- ${slf4j.version}
- test
-
org.testng
testng
diff --git a/testcontainers/pom.xml b/testcontainers/pom.xml
index 4c4ba826..6e25ed44 100644
--- a/testcontainers/pom.xml
+++ b/testcontainers/pom.xml
@@ -33,26 +33,6 @@
org.testcontainers
testcontainers
-
- org.projectlombok
- lombok
- provided
-
-
- org.assertj
- assertj-core
- test
-
-
- org.junit.jupiter
- junit-jupiter
- test
-
-
- org.slf4j
- slf4j-simple
- test
-
org.testcontainers
junit-jupiter