From 7292960446f1f20dc32b78d8db45cd2519c5b646 Mon Sep 17 00:00:00 2001 From: Andrew Carbonetto Date: Thu, 15 Feb 2024 08:53:46 -0800 Subject: [PATCH] Java: Add ExamplesApp and Update benchmarkingApp to both have Java/Glide-for-redis client (#896) * Java: Add ExamplesApp and Update benchmarkingApp to both have Java/Glide-for-redis client (#896) Signed-off-by: Andrew Carbonetto --------- Signed-off-by: Andrew Carbonetto Signed-off-by: Yury-Fridlyand Co-authored-by: Yury-Fridlyand --- .github/workflows/java-benchmark.yml | 9 +- benchmarks/install_and_test.sh | 2 +- java/README.md | 26 +++--- java/benchmarks/build.gradle | 6 +- .../glide/benchmarks/BenchmarkingApp.java | 6 +- .../clients/glide/GlideAsyncClient.java | 83 +++++++++++++++++++ .../clients/lettuce/LettuceAsyncClient.java | 2 +- java/examples/build.gradle | 25 ++++++ .../main/java/glide/examples/ExamplesApp.java | 41 +++++++++ java/settings.gradle | 1 + 10 files changed, 177 insertions(+), 24 deletions(-) create mode 100644 java/benchmarks/src/main/java/glide/benchmarks/clients/glide/GlideAsyncClient.java create mode 100644 java/examples/build.gradle create mode 100644 java/examples/src/main/java/glide/examples/ExamplesApp.java diff --git a/.github/workflows/java-benchmark.yml b/.github/workflows/java-benchmark.yml index eceaf6cc0f..21d7d7fea1 100644 --- a/.github/workflows/java-benchmark.yml +++ b/.github/workflows/java-benchmark.yml @@ -30,7 +30,7 @@ jobs: submodules: recursive - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: "temurin" java-version: ${{ matrix.java }} @@ -42,16 +42,15 @@ jobs: - name: benchmark uses: ./.github/workflows/test-benchmark - # TODO - enable once benchmark works - if: ${{ false }} with: language-flag: -java - name: Upload test reports if: always() continue-on-error: true - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: test-reports-${{ matrix.java }} + name: test-reports-java-${{ matrix.java }}-redis-${{ matrix.redis }} path: | java/benchmarks/build/reports/** + benchmarks/results/** diff --git a/benchmarks/install_and_test.sh b/benchmarks/install_and_test.sh index b45f8ba23f..72b56fb2cb 100755 --- a/benchmarks/install_and_test.sh +++ b/benchmarks/install_and_test.sh @@ -74,7 +74,7 @@ function runCSharpBenchmark(){ function runJavaBenchmark(){ cd ${BENCH_FOLDER}/../java - ./gradlew run --args="-resultsFile \"${BENCH_FOLDER}/$1\" -dataSize \"$2\" -concurrentTasks \"$concurrentTasks\" -clients \"$chosenClients\" -host $host $javaPortFlag -clientCount \"$clientCount\" $javaTlsFlag $javaClusterFlag" + ./gradlew :benchmarks:run --args="-resultsFile \"${BENCH_FOLDER}/$1\" -dataSize \"$2\" -concurrentTasks \"$concurrentTasks\" -clients \"$chosenClients\" -host $host $javaPortFlag -clientCount \"$clientCount\" $javaTlsFlag $javaClusterFlag" } function runRustBenchmark(){ diff --git a/java/README.md b/java/README.md index a9379f32a1..fbedc1b499 100644 --- a/java/README.md +++ b/java/README.md @@ -9,8 +9,10 @@ to develop this Java wrapper. The Java client contains the following parts: -1. A Java client (lib folder): wrapper to rust client. -2. A benchmark app: A dedicated benchmarking tool designed to evaluate and compare the performance of GLIDE for Redis and other Java clients. +1. `client`: A Java-wrapper around the rust-core client. +2. `examples`: An examples app to test the client against a Redis localhost +3. `benchmark`: A dedicated benchmarking tool designed to evaluate and compare the performance of GLIDE for Redis and other Java clients. +4. `integTest`: An integration test sub-project for API and E2E testing ## Installation and Setup @@ -82,8 +84,10 @@ $ ./gradlew :client:test Other useful gradle developer commands: * `./gradlew :client:test` to run client unit tests +* `./gradlew :integTest:test` to run client examples * `./gradlew spotlessCheck` to check for codestyle issues * `./gradlew spotlessApply` to apply codestyle recommendations +* `./gradlew :examples:run` to run client examples * `./gradlew :benchmarks:run` to run performance benchmarks ## Basic Examples @@ -91,19 +95,15 @@ Other useful gradle developer commands: ### Standalone Redis: ```java -import glide.Client; -import glide.Client.SingleResponse; +import glide.api.RedisClient; -Client client = new Client(); +RedisClient client = RedisClient.CreateClient().get(); -SingleResponse connect = client.asyncConnectToRedis("localhost", 6379); -connect.await().isSuccess(); +CompletableFuture setResponse = client.set("key", "foobar"); +assert setResponse.get() == "OK" : "Failed on client.set("key", "foobar") request"; -SingleResponse set = client.asyncSet("key", "foobar"); -set.await().isSuccess(); - -SingleResponse get = client.asyncGet("key"); -get.await().getValue() == "foobar"; +CompletableFuture getResponse = client.get("key"); +assert getResponse.get() == "foobar" : "Failed on client.get("key") request"; ``` ### Benchmarks @@ -115,7 +115,7 @@ You can run benchmarks using `./gradlew run`. You can set arguments using the ar ./gradlew run --args="-resultsFile=output -dataSize \"100 1000\" -concurrentTasks \"10 100\" -clients all -host localhost -port 6279 -clientCount \"1 5\" -tls" ``` -The following arguments are accepted: +The following arguments are accepted: * `resultsFile`: the results output file * `concurrentTasks`: Number of concurrent tasks * `clients`: one of: all|jedis|lettuce|glide diff --git a/java/benchmarks/build.gradle b/java/benchmarks/build.gradle index 18ca4e4f13..2aaad7c27e 100644 --- a/java/benchmarks/build.gradle +++ b/java/benchmarks/build.gradle @@ -9,6 +9,8 @@ repositories { } dependencies { + implementation project(':client') + // This dependency is used internally, and not exposed to consumers on their own compile classpath. implementation 'com.google.guava:guava:32.1.1-jre' implementation 'redis.clients:jedis:4.4.3' @@ -17,10 +19,12 @@ dependencies { implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.13.0' implementation group: 'org.apache.commons', name: 'commons-math3', version: '3.5' implementation group: 'com.google.code.gson', name: 'gson', version: '2.10.1' - } +run.dependsOn ':client:buildRustRelease' + application { // Define the main class for the application. mainClass = 'glide.benchmarks.BenchmarkingApp' + applicationDefaultJvmArgs = ['-Djava.library.path=../target/release'] } diff --git a/java/benchmarks/src/main/java/glide/benchmarks/BenchmarkingApp.java b/java/benchmarks/src/main/java/glide/benchmarks/BenchmarkingApp.java index e246ec7345..9c5d196699 100644 --- a/java/benchmarks/src/main/java/glide/benchmarks/BenchmarkingApp.java +++ b/java/benchmarks/src/main/java/glide/benchmarks/BenchmarkingApp.java @@ -3,6 +3,7 @@ import static glide.benchmarks.utils.Benchmarking.testClientSetGet; +import glide.benchmarks.clients.glide.GlideAsyncClient; import glide.benchmarks.clients.jedis.JedisClient; import glide.benchmarks.clients.lettuce.LettuceAsyncClient; import java.util.Arrays; @@ -46,17 +47,16 @@ public static void main(String[] args) { for (ClientName client : runConfiguration.clients) { switch (client) { case JEDIS: - // run testClientSetGet on JEDIS sync client System.out.println("Run JEDIS sync client"); testClientSetGet(JedisClient::new, runConfiguration, false); break; case LETTUCE: - // run testClientSetGet on LETTUCE async client System.out.println("Run LETTUCE async client"); testClientSetGet(LettuceAsyncClient::new, runConfiguration, true); break; case GLIDE: - System.out.println("GLIDE for Redis async not yet configured"); + System.out.println("GLIDE for Redis async client"); + testClientSetGet(GlideAsyncClient::new, runConfiguration, true); break; } } diff --git a/java/benchmarks/src/main/java/glide/benchmarks/clients/glide/GlideAsyncClient.java b/java/benchmarks/src/main/java/glide/benchmarks/clients/glide/GlideAsyncClient.java new file mode 100644 index 0000000000..456ba7a50f --- /dev/null +++ b/java/benchmarks/src/main/java/glide/benchmarks/clients/glide/GlideAsyncClient.java @@ -0,0 +1,83 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.benchmarks.clients.glide; + +import static java.util.concurrent.TimeUnit.SECONDS; + +import glide.api.BaseClient; +import glide.api.RedisClient; +import glide.api.RedisClusterClient; +import glide.api.models.configuration.NodeAddress; +import glide.api.models.configuration.RedisClientConfiguration; +import glide.api.models.configuration.RedisClusterClientConfiguration; +import glide.benchmarks.clients.AsyncClient; +import glide.benchmarks.utils.ConnectionSettings; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +/** A Glide client with async capabilities */ +public class GlideAsyncClient implements AsyncClient { + private BaseClient redisClient; + + @Override + public void connectToRedis(ConnectionSettings connectionSettings) { + + if (connectionSettings.clusterMode) { + RedisClusterClientConfiguration config = + RedisClusterClientConfiguration.builder() + .address( + NodeAddress.builder() + .host(connectionSettings.host) + .port(connectionSettings.port) + .build()) + .useTLS(connectionSettings.useSsl) + .build(); + try { + redisClient = RedisClusterClient.CreateClient(config).get(10, SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new RuntimeException(e); + } + + } else { + RedisClientConfiguration config = + RedisClientConfiguration.builder() + .address( + NodeAddress.builder() + .host(connectionSettings.host) + .port(connectionSettings.port) + .build()) + .useTLS(connectionSettings.useSsl) + .build(); + + try { + redisClient = RedisClient.CreateClient(config).get(10, SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public CompletableFuture asyncSet(String key, String value) { + return redisClient.set(key, value); + } + + @Override + public CompletableFuture asyncGet(String key) { + return redisClient.get(key); + } + + @Override + public void closeConnection() { + try { + redisClient.close(); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } + + @Override + public String getName() { + return "Glide Async"; + } +} diff --git a/java/benchmarks/src/main/java/glide/benchmarks/clients/lettuce/LettuceAsyncClient.java b/java/benchmarks/src/main/java/glide/benchmarks/clients/lettuce/LettuceAsyncClient.java index 3c24578ec9..d880b9b71a 100644 --- a/java/benchmarks/src/main/java/glide/benchmarks/clients/lettuce/LettuceAsyncClient.java +++ b/java/benchmarks/src/main/java/glide/benchmarks/clients/lettuce/LettuceAsyncClient.java @@ -30,7 +30,7 @@ public void connectToRedis(ConnectionSettings connectionSettings) { .withPort(connectionSettings.port) .withSsl(connectionSettings.useSsl) .build(); - if (connectionSettings.clusterMode) { + if (!connectionSettings.clusterMode) { client = RedisClient.create(uri); connection = ((RedisClient) client).connect(); asyncCommands = ((StatefulRedisConnection) connection).async(); diff --git a/java/examples/build.gradle b/java/examples/build.gradle new file mode 100644 index 0000000000..0e526d95e4 --- /dev/null +++ b/java/examples/build.gradle @@ -0,0 +1,25 @@ +plugins { + // Apply the application plugin to add support for building a CLI application in Java. + id 'application' +} + +repositories { + // Use Maven Central for resolving dependencies. + mavenCentral() +} + +dependencies { + implementation project(':client') + + implementation 'redis.clients:jedis:4.4.3' + implementation 'io.lettuce:lettuce-core:6.2.6.RELEASE' + implementation 'commons-cli:commons-cli:1.5.0' +} + +run.dependsOn ':client:buildRustRelease' + +application { + // Define the main class for the application. + mainClass = 'glide.examples.ExamplesApp' + applicationDefaultJvmArgs = ['-Djava.library.path=../target/release'] +} diff --git a/java/examples/src/main/java/glide/examples/ExamplesApp.java b/java/examples/src/main/java/glide/examples/ExamplesApp.java new file mode 100644 index 0000000000..ea816f9632 --- /dev/null +++ b/java/examples/src/main/java/glide/examples/ExamplesApp.java @@ -0,0 +1,41 @@ +/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.examples; + +import glide.api.RedisClient; +import glide.api.models.configuration.NodeAddress; +import glide.api.models.configuration.RedisClientConfiguration; +import java.util.concurrent.ExecutionException; + +public class ExamplesApp { + + // main application entrypoint + public static void main(String[] args) { + runGlideExamples(); + } + + private static void runGlideExamples() { + String host = "localhost"; + Integer port = 6379; + boolean useSsl = false; + + RedisClientConfiguration config = + RedisClientConfiguration.builder() + .address(NodeAddress.builder().host(host).port(port).build()) + .useTLS(useSsl) + .build(); + + try { + RedisClient client = RedisClient.CreateClient(config).get(); + + System.out.println("PING: " + client.ping().get()); + System.out.println("PING(found you): " + client.ping("found you").get()); + + System.out.println("SET(apples, oranges): " + client.set("apples", "oranges").get()); + System.out.println("GET(apples): " + client.get("apples").get()); + + } catch (ExecutionException | InterruptedException e) { + System.out.println("Glide example failed with an exception: "); + e.printStackTrace(); + } + } +} diff --git a/java/settings.gradle b/java/settings.gradle index 6d5e31d8a0..d93b818e15 100644 --- a/java/settings.gradle +++ b/java/settings.gradle @@ -2,4 +2,5 @@ rootProject.name = 'glide' include 'client' include 'integTest' +include 'examples' include 'benchmarks'