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