From 696cb74740298865b3fb41b72bd76a8776f1f343 Mon Sep 17 00:00:00 2001 From: Walter Oggioni Date: Mon, 13 Jan 2025 09:51:14 +0800 Subject: [PATCH] added JMH benchmark --- benchmark/build.gradle | 26 +++ .../net/woggioni/gbcs/benchmark/Main.java | 170 ++++++++++++++++++ .../src/jmh/resources/benchmark.properties | 1 + conf/logback.xml | 21 +++ settings.gradle | 2 +- .../woggioni/gbcs/GradleBuildCacheServer.kt | 2 +- 6 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 benchmark/build.gradle create mode 100644 benchmark/src/jmh/java/net/woggioni/gbcs/benchmark/Main.java create mode 100644 benchmark/src/jmh/resources/benchmark.properties create mode 100644 conf/logback.xml diff --git a/benchmark/build.gradle b/benchmark/build.gradle new file mode 100644 index 0000000..bcde4ed --- /dev/null +++ b/benchmark/build.gradle @@ -0,0 +1,26 @@ +plugins { + alias catalog.plugins.gradle.jmh + alias catalog.plugins.lombok +} + +import me.champeau.jmh.JMHTask + +dependencies { + implementation rootProject + + implementation catalog.jwo + implementation catalog.xz + implementation catalog.jackson.databind + + jmhAnnotationProcessor catalog.lombok +} + +jmh { + threads = 4 + iterations = 2 + fork = 1 + warmupIterations = 1 + warmupForks = 0 + resultFormat = 'JSON' +} + diff --git a/benchmark/src/jmh/java/net/woggioni/gbcs/benchmark/Main.java b/benchmark/src/jmh/java/net/woggioni/gbcs/benchmark/Main.java new file mode 100644 index 0000000..b283ee6 --- /dev/null +++ b/benchmark/src/jmh/java/net/woggioni/gbcs/benchmark/Main.java @@ -0,0 +1,170 @@ +package net.woggioni.gbcs.benchmark; + +import lombok.Getter; +import lombok.SneakyThrows; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; + +public class Main { + + @SneakyThrows + private static Properties loadProperties() { + Properties properties = new Properties(); + try (final var is = Main.class.getResourceAsStream("/benchmark.properties")) { + properties.load(is); + } + return properties; + } + + private static final Properties properties = loadProperties(); + + @State(Scope.Thread) + public static class ExecutionPlan { + private final Random random = new Random(101325); + + @Getter + private final HttpClient client = HttpClient.newHttpClient(); + + private final Map entries = new HashMap<>(); + + public final Map getEntries() { + return Collections.unmodifiableMap(entries); + } + + public Map.Entry newEntry() { + final var keyBuffer = new byte[0x20]; + random.nextBytes(keyBuffer); + final var key = Base64.getUrlEncoder().encodeToString(keyBuffer); + final var value = new byte[0x1000]; + random.nextBytes(value); + return Map.entry(key, value); + } + + @SneakyThrows + public HttpRequest.Builder newRequestBuilder(String key) { + final var requestBuilder = HttpRequest.newBuilder() + .uri(getServerURI().resolve(key)); + String user = getUser(); + if (user != null) { + requestBuilder.header("Authorization", buildAuthorizationHeader(user, getPassword())); + } + return requestBuilder; + } + + @SneakyThrows + public URI getServerURI() { + return new URI(properties.getProperty("gbcs.server.url")); + } + + @SneakyThrows + public String getUser() { + return Optional.ofNullable(properties.getProperty("gbcs.server.username")) + .filter(Predicate.not(String::isEmpty)) + .orElse(null); + + } + + @SneakyThrows + public String getPassword() { + return Optional.ofNullable(properties.getProperty("gbcs.server.password")) + .filter(Predicate.not(String::isEmpty)) + .orElse(null); + } + + private String buildAuthorizationHeader(String user, String password) { + final var b64 = Base64.getEncoder().encode(String.format("%s:%s", user, password).getBytes(StandardCharsets.UTF_8)); + return "Basic " + new String(b64); + } + + + @SneakyThrows + @Setup(Level.Trial) + public void setUp() { + try (final var client = HttpClient.newHttpClient()) { + for (int i = 0; i < 10000; i++) { + final var pair = newEntry(); + final var requestBuilder = newRequestBuilder(pair.getKey()) + .header("Content-Type", "application/octet-stream") + .PUT(HttpRequest.BodyPublishers.ofByteArray(pair.getValue())); + final var response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString()); + if (201 != response.statusCode()) { + throw new IllegalStateException(Integer.toString(response.statusCode())); + } else { + entries.put(pair.getKey(), pair.getValue()); + } + } + } + } + + private Iterator> it = null; + + private Map.Entry nextEntry() { + if (it == null || !it.hasNext()) { + it = getEntries().entrySet().iterator(); + } + return it.next(); + } + } + + @SneakyThrows + @Benchmark + @BenchmarkMode(Mode.Throughput) + @OutputTimeUnit(TimeUnit.SECONDS) + public void get(ExecutionPlan plan) { + final var client = plan.getClient(); + final var entry = plan.nextEntry(); + final var requestBuilder = plan.newRequestBuilder(entry.getKey()) + .header("Accept", "application/octet-stream") + .GET(); + final var response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray()); + if (200 != response.statusCode()) { + throw new IllegalStateException(Integer.toString(response.statusCode())); + } else { + if (!Arrays.equals(entry.getValue(), response.body())) { + throw new IllegalStateException("Retrieved unexpected value"); + } + } + } + + + @SneakyThrows + @Benchmark + @BenchmarkMode(Mode.Throughput) + @OutputTimeUnit(TimeUnit.SECONDS) + public void put(Main.ExecutionPlan plan) { + final var client = plan.getClient(); + final var entry = plan.nextEntry(); + + final var requestBuilder = plan.newRequestBuilder(entry.getKey()) + .header("Content-Type", "application/octet-stream") + .PUT(HttpRequest.BodyPublishers.ofByteArray(entry.getValue())); + + final var response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray()); + if (201 != response.statusCode()) { + throw new IllegalStateException(Integer.toString(response.statusCode())); + } + } +} diff --git a/benchmark/src/jmh/resources/benchmark.properties b/benchmark/src/jmh/resources/benchmark.properties new file mode 100644 index 0000000..51f0799 --- /dev/null +++ b/benchmark/src/jmh/resources/benchmark.properties @@ -0,0 +1 @@ +gbcs.server.url= http://localhost:8080 \ No newline at end of file diff --git a/conf/logback.xml b/conf/logback.xml new file mode 100644 index 0000000..c6f9111 --- /dev/null +++ b/conf/logback.xml @@ -0,0 +1,21 @@ + + + + + + + + + System.err + + %d [%highlight(%-5level)] \(%thread\) %logger{36} -%kvp- %msg %n + + + + + + + + + + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 2742140..ce15f6e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -31,5 +31,5 @@ include 'gbcs-base' include 'gbcs-memcached' include 'gbcs-cli' include 'docker' - +include 'benchmark' diff --git a/src/main/kotlin/net/woggioni/gbcs/GradleBuildCacheServer.kt b/src/main/kotlin/net/woggioni/gbcs/GradleBuildCacheServer.kt index 2d0cef5..1911fe6 100644 --- a/src/main/kotlin/net/woggioni/gbcs/GradleBuildCacheServer.kt +++ b/src/main/kotlin/net/woggioni/gbcs/GradleBuildCacheServer.kt @@ -190,7 +190,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) { } else { val javaKeyStore = loadKeystore(keyStore.file, keyStore.password) val serverKey = javaKeyStore.getKey( - keyStore.keyAlias, keyStore.keyPassword?.let(String::toCharArray) + keyStore.keyAlias, (keyStore.keyPassword ?: "").let(String::toCharArray) ) as PrivateKey val serverCert: Array = Arrays.stream(javaKeyStore.getCertificateChain(keyStore.keyAlias))