diff --git a/test/http-client-benchmarks/pom.xml b/test/http-client-benchmarks/pom.xml index 884a7b6490a4..eae31142484a 100644 --- a/test/http-client-benchmarks/pom.xml +++ b/test/http-client-benchmarks/pom.xml @@ -74,6 +74,24 @@ ${awsjavasdk.version}-PREVIEW + + software.amazon.awssdk + url-connection-client + ${awsjavasdk.version} + + + + software.amazon.awssdk + aws-crt-client + ${awsjavasdk.version} + + + + software.amazon.awssdk + netty-nio-client + ${awsjavasdk.version} + + org.apache.logging.log4j log4j-api diff --git a/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/AsyncVirtualThreadBenchmark.java b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/AsyncVirtualThreadBenchmark.java new file mode 100644 index 000000000000..827aa547144a --- /dev/null +++ b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/AsyncVirtualThreadBenchmark.java @@ -0,0 +1,209 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.benchmark; + +import static software.amazon.awssdk.benchmark.apache5.utility.BenchmarkUtilities.isJava21OrHigher; + +import java.io.IOException; +import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.infra.Blackhole; +import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; +import software.amazon.awssdk.benchmark.core.ObjectSize; +import software.amazon.awssdk.benchmark.core.S3BenchmarkHelper; +import software.amazon.awssdk.core.ResponseInputStream; +import software.amazon.awssdk.core.async.AsyncResponseTransformer; +import software.amazon.awssdk.http.SdkHttpConfigurationOption; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.http.crt.AwsCrtAsyncHttpClient; +import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.awssdk.services.s3.model.PutObjectResponse; +import software.amazon.awssdk.utils.AttributeMap; +import software.amazon.awssdk.utils.IoUtils; +import software.amazon.awssdk.utils.JavaSystemSetting; + +/** + * Async http client benchmark using virtual threads. This class requires Java 21+. + */ +@Fork(jvmArgsAppend = "-Djdk.tracePinnedThreads=full") +@State(Scope.Benchmark) +public class AsyncVirtualThreadBenchmark { + // We redirect standard out to a file for the -Djdk.tracePinnedThreads=full option. When virtual threads become pinned, + // the JDK will print out the stacktrace through standard out. However, because JMH runs benchmarks in a forked JVM + // (unless you specify -f 0, which is not recommended by JMH), that output is lost. Redirect standard out to a file so + // that any time a thread is pinned, the stack trace is written to the file instead,which can be inspected after the + // benchmark run. + static { + try { + Path tmp = Paths.get(AsyncVirtualThreadBenchmark.class.getSimpleName() + "-stdout-" + UUID.randomUUID() + ".log"); + PrintStream fileOut = new PrintStream( + Files.newOutputStream(tmp, StandardOpenOption.APPEND, StandardOpenOption.CREATE)); + System.setOut(fileOut); + } catch (IOException e) { + throw new RuntimeException("Unable to create STDOUT file", e); + } + } + + public enum Client { + Netty, + Crt + } + + @Param("50") + private int maxConnections; + + @Param("SMALL") + private ObjectSize objectSize; + + @Param({"Netty", "Crt"}) + private Client client; + + private S3AsyncClient s3AsyncClient; + private S3BenchmarkHelper benchmark; + private ExecutorService virtualThreadExecutor; + private String putKeyPrefix; + + @Setup(Level.Trial) + public void setup() { + if (!isJava21OrHigher()) { + throw new UnsupportedOperationException( + "Virtual threads require Java 21 or higher. Current version: " + JavaSystemSetting.JAVA_VERSION); + } + + SdkAsyncHttpClient.Builder httpClientBuilder = httpClientBuilder(); + + s3AsyncClient = S3AsyncClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(DefaultCredentialsProvider.create()) + .httpClient(configure(httpClientBuilder)) + .build(); + + String benchmarkName = AsyncVirtualThreadBenchmark.class.getSimpleName(); + + benchmark = new S3BenchmarkHelper(benchmarkName, s3AsyncClient); + benchmark.setup(); + + virtualThreadExecutor = createVirtualThreadExecutor(); + + putKeyPrefix = benchmarkName + "-"; + } + + private SdkAsyncHttpClient configure(SdkAsyncHttpClient.Builder builder) { + AttributeMap config = AttributeMap.builder() + .put(SdkHttpConfigurationOption.MAX_CONNECTIONS, maxConnections) + .build(); + + return builder.buildWithDefaults(config); + } + + private ExecutorService createVirtualThreadExecutor() { + try { + // Use reflection to call Executors.newVirtualThreadPerTaskExecutor() + Method method = Executors.class.getMethod("newVirtualThreadPerTaskExecutor"); + return (ExecutorService) method.invoke(null); + } catch (NoSuchMethodException e) { + throw new UnsupportedOperationException( + "Virtual threads are not available in this Java version. " + + "This benchmark requires Java 21 or higher.", e); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Failed to create virtual thread executor", e); + } + } + + @Benchmark + public void getObject(Blackhole blackhole) { + safeExecute(() -> { + ResponseInputStream object = s3AsyncClient.getObject( + r -> r.bucket(benchmark.bucketName()).key(benchmark.objKey(objectSize)), + AsyncResponseTransformer.toBlockingInputStream()).join(); + blackhole.consume(object.response()); + IoUtils.drainInputStream(object); + }); + } + + @Benchmark + public void putObject(Blackhole blackhole) { + String jmhThreadName = Thread.currentThread().getName(); + safeExecute(() -> { + PutObjectResponse response = s3AsyncClient.putObject( + r -> r.bucket(benchmark.bucketName()).key(putKeyPrefix + jmhThreadName), + benchmark.asyncRequestBody(objectSize)).join(); + blackhole.consume(response); + }); + } + + @TearDown(Level.Trial) + public void tearDown() { + if (virtualThreadExecutor != null) { + virtualThreadExecutor.shutdown(); + try { + if (!virtualThreadExecutor.awaitTermination(30, TimeUnit.SECONDS)) { + virtualThreadExecutor.shutdownNow(); + } + } catch (InterruptedException e) { + virtualThreadExecutor.shutdownNow(); + } + } + + if (benchmark != null) { + benchmark.cleanup(); + } + + if (s3AsyncClient != null) { + s3AsyncClient.close(); + } + } + + private void safeExecute(Runnable runnable) { + try { + virtualThreadExecutor.submit(runnable).get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException("Error during execution", e); + } + } + + private SdkAsyncHttpClient.Builder httpClientBuilder() { + switch (client) { + case Netty: + return NettyNioAsyncHttpClient.builder(); + case Crt: + return AwsCrtAsyncHttpClient.builder(); + default: + throw new IllegalArgumentException("Unknown HTTP client: " + client); + } + } +} diff --git a/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/UnifiedBenchmarkRunner.java b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/UnifiedBenchmarkRunner.java index 8ee83735e204..f0f14a976db6 100644 --- a/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/UnifiedBenchmarkRunner.java +++ b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/UnifiedBenchmarkRunner.java @@ -31,7 +31,6 @@ import org.openjdk.jmh.runner.options.OptionsBuilder; import software.amazon.awssdk.benchmark.apache4.Apache4Benchmark; import software.amazon.awssdk.benchmark.apache5.Apache5Benchmark; -import software.amazon.awssdk.benchmark.apache5.Apache5VirtualBenchmark; import software.amazon.awssdk.benchmark.core.BenchmarkResult; import software.amazon.awssdk.benchmark.metrics.CloudWatchMetricsPublisher; import software.amazon.awssdk.regions.Region; @@ -76,7 +75,7 @@ public static void main(String[] args) throws Exception { // Only run virtual threads benchmark if Java 21+ if (isJava21OrHigher()) { logger.info(() -> "Running Apache5 with virtual threads..."); - allResults.addAll(runBenchmark("Apache5-Virtual", Apache5VirtualBenchmark.class)); + allResults.addAll(runBenchmark("Apache5-Virtual", VirtualThreadBenchmark.class)); } else { logger.info(() -> "Skipping virtual threads benchmark - requires Java 21 or higher (current: " + JavaSystemSetting.JAVA_VERSION.getStringValueOrThrow() + ")"); diff --git a/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/VirtualThreadBenchmark.java b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/VirtualThreadBenchmark.java new file mode 100644 index 000000000000..e1720f4223e4 --- /dev/null +++ b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/VirtualThreadBenchmark.java @@ -0,0 +1,219 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.benchmark; + +import static software.amazon.awssdk.benchmark.apache5.utility.BenchmarkUtilities.isJava21OrHigher; + +import java.io.IOException; +import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.infra.Blackhole; +import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; +import software.amazon.awssdk.benchmark.core.ObjectSize; +import software.amazon.awssdk.benchmark.core.S3BenchmarkHelper; +import software.amazon.awssdk.core.ResponseInputStream; +import software.amazon.awssdk.core.sync.ResponseTransformer; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.SdkHttpConfigurationOption; +import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.http.apache5.Apache5HttpClient; +import software.amazon.awssdk.http.crt.AwsCrtHttpClient; +import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.awssdk.services.s3.model.PutObjectResponse; +import software.amazon.awssdk.utils.AttributeMap; +import software.amazon.awssdk.utils.IoUtils; +import software.amazon.awssdk.utils.JavaSystemSetting; + +/** + * Http client benchmark using virtual threads. This class requires Java 21+. + */ +@Fork(jvmArgsAppend = "-Djdk.tracePinnedThreads=full") +@State(Scope.Benchmark) +public class VirtualThreadBenchmark { + static { + // We redirect standard out to a file for the -Djdk.tracePinnedThreads=full option. When virtual threads become pinned, + // the JDK will print out the stacktrace through standard out. However, because JMH runs benchmarks in a forked JVM + // (unless you specify -f 0, which is not recommended by JMH), that output is lost. Redirect standard out to a file so + // that any time a thread is pinned, the stack trace is written to the file instead,which can be inspected after the + // benchmark run. + try { + Path tmp = Paths.get(VirtualThreadBenchmark.class.getSimpleName() + "-stdout-" + UUID.randomUUID() + ".log"); + PrintStream fileOut = new PrintStream( + Files.newOutputStream(tmp, StandardOpenOption.APPEND, StandardOpenOption.CREATE)); + System.setOut(fileOut); + } catch (IOException e) { + throw new RuntimeException("Unable to create STDOUT file", e); + } + } + + public enum Client { + Apache5, + Apache4, + UrlConnection, + Crt + } + + @Param("50") + private int maxConnections; + + @Param("SMALL") + private ObjectSize objectSize; + + @Param({"Apache5", "UrlConnection", "Crt"}) + // Note: We know pinning happens with Apache4, so don't bother testing it by + // default + private Client client; + + private S3Client s3Client; + private S3BenchmarkHelper benchmark; + private ExecutorService virtualThreadExecutor; + private String putKeyPrefix; + + @Setup(Level.Trial) + public void setup() { + if (!isJava21OrHigher()) { + throw new UnsupportedOperationException( + "Virtual threads require Java 21 or higher. Current version: " + JavaSystemSetting.JAVA_VERSION); + } + + SdkHttpClient.Builder httpClientBuilder = httpClientBuilder(); + + s3Client = S3Client.builder() + .region(Region.US_WEST_2) + .credentialsProvider(DefaultCredentialsProvider.create()) + .httpClient(configure(httpClientBuilder)) + .build(); + + String benchmarkName = VirtualThreadBenchmark.class.getSimpleName(); + + benchmark = new S3BenchmarkHelper(benchmarkName, s3Client); + benchmark.setup(); + + virtualThreadExecutor = createVirtualThreadExecutor(); + + putKeyPrefix = benchmarkName + "-"; + } + + private SdkHttpClient configure(SdkHttpClient.Builder builder) { + AttributeMap config = AttributeMap.builder() + .put(SdkHttpConfigurationOption.MAX_CONNECTIONS, maxConnections) + .build(); + + return builder.buildWithDefaults(config); + } + + private ExecutorService createVirtualThreadExecutor() { + try { + // Use reflection to call Executors.newVirtualThreadPerTaskExecutor() + Method method = Executors.class.getMethod("newVirtualThreadPerTaskExecutor"); + return (ExecutorService) method.invoke(null); + } catch (NoSuchMethodException e) { + throw new UnsupportedOperationException( + "Virtual threads are not available in this Java version. " + + "This benchmark requires Java 21 or higher.", e); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Failed to create virtual thread executor", e); + } + } + + @Benchmark + public void getObject(Blackhole blackhole) { + safeExecute(() -> { + ResponseInputStream object = s3Client.getObject( + r -> r.bucket(benchmark.bucketName()).key(benchmark.objKey(objectSize)), + ResponseTransformer.toInputStream()); + blackhole.consume(object.response()); + IoUtils.drainInputStream(object); + }); + } + + @Benchmark + public void putObject(Blackhole blackhole) { + String jmhThreadName = Thread.currentThread().getName(); + safeExecute(() -> { + PutObjectResponse response = s3Client.putObject( + r -> r.bucket(benchmark.bucketName()).key(putKeyPrefix + jmhThreadName), + benchmark.requestBody(objectSize)); + blackhole.consume(response); + }); + } + + @TearDown(Level.Trial) + public void tearDown() { + if (virtualThreadExecutor != null) { + virtualThreadExecutor.shutdown(); + try { + if (!virtualThreadExecutor.awaitTermination(30, TimeUnit.SECONDS)) { + virtualThreadExecutor.shutdownNow(); + } + } catch (InterruptedException e) { + virtualThreadExecutor.shutdownNow(); + } + } + + if (benchmark != null) { + benchmark.cleanup(); + } + + if (s3Client != null) { + s3Client.close(); + } + } + + private void safeExecute(Runnable runnable) { + try { + virtualThreadExecutor.submit(runnable).get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException("Error during execution", e); + } + } + + SdkHttpClient.Builder httpClientBuilder() { + switch (client) { + case Apache5: + return Apache5HttpClient.builder(); + case Apache4: + return ApacheHttpClient.builder(); + case UrlConnection: + return UrlConnectionHttpClient.builder(); + case Crt: + return AwsCrtHttpClient.builder(); + default: + throw new IllegalArgumentException("Unknown HTTP client: " + client); + } + } +} diff --git a/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/apache4/Apache4Benchmark.java b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/apache4/Apache4Benchmark.java index 87dcb9ea84be..adec6b908736 100644 --- a/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/apache4/Apache4Benchmark.java +++ b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/apache4/Apache4Benchmark.java @@ -37,11 +37,16 @@ import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.Blackhole; import software.amazon.awssdk.benchmark.core.CoreBenchmark; -import software.amazon.awssdk.benchmark.core.S3BenchmarkImpl; +import software.amazon.awssdk.benchmark.core.ObjectSize; +import software.amazon.awssdk.benchmark.core.S3BenchmarkHelper; +import software.amazon.awssdk.core.ResponseInputStream; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.apache.ApacheHttpClient; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.awssdk.services.s3.model.PutObjectResponse; +import software.amazon.awssdk.utils.IoUtils; import software.amazon.awssdk.utils.Logger; @BenchmarkMode(Mode.Throughput) @@ -56,14 +61,14 @@ public class Apache4Benchmark implements CoreBenchmark { @Param({"50"}) private int maxConnections; - @Param({"5"}) - private int testDataInMB; + @Param("SMALL") + private ObjectSize objectSize; @Param({"10"}) private int threadCount; private S3Client s3Client; - private S3BenchmarkImpl benchmark; + private S3BenchmarkHelper benchmarkHelper; private ExecutorService platformThreadPool; @Setup(Level.Trial) @@ -84,9 +89,8 @@ public void setup() { .httpClient(httpClient) .build(); - // Initialize benchmark implementation - benchmark = new S3BenchmarkImpl(s3Client, new byte[testDataInMB * 1024 * 1024]); - benchmark.setup(); + benchmarkHelper = new S3BenchmarkHelper(Apache4Benchmark.class.getSimpleName(), s3Client); + benchmarkHelper.setup(); // Platform thread pool for multi-threaded tests platformThreadPool = Executors.newFixedThreadPool(threadCount, r -> { @@ -102,13 +106,13 @@ public void setup() { @Benchmark @Override public void simpleGet(Blackhole blackhole) throws Exception { - benchmark.executeGet("medium", blackhole); + executeGet(blackhole); } @Benchmark @Override public void simplePut(Blackhole blackhole) throws Exception { - benchmark.executePut("medium", blackhole); + executePut(blackhole); } @Benchmark @@ -123,6 +127,20 @@ public void multiThreadedPut(Blackhole blackhole) throws Exception { runMultiThreaded(platformThreadPool, threadCount, blackhole, false); } + private void executeGet(Blackhole blackhole) { + ResponseInputStream object = s3Client.getObject( + r -> r.bucket(benchmarkHelper.bucketName()).key(benchmarkHelper.objKey(objectSize))); + blackhole.consume(object.response()); + IoUtils.drainInputStream(object); + } + + private void executePut(Blackhole blackhole) { + PutObjectResponse response = s3Client.putObject( + r -> r.bucket(benchmarkHelper.bucketName()).key("Apache4Benchmark-" + Thread.currentThread().getName()), + benchmarkHelper.requestBody(objectSize)); + blackhole.consume(response); + } + protected void runMultiThreaded(ExecutorService executor, int threads, Blackhole blackhole, boolean isGet) throws Exception { List> futures = new ArrayList<>(threads); @@ -131,9 +149,9 @@ protected void runMultiThreaded(ExecutorService executor, int threads, futures.add(executor.submit(() -> { try { if (isGet) { - benchmark.executeGet("medium", blackhole); + executeGet(blackhole); } else { - benchmark.executePut("medium", blackhole); + executePut(blackhole); } } catch (Exception e) { throw new RuntimeException("Operation failed", e); @@ -162,8 +180,8 @@ public void tearDown() { } } - if (benchmark != null) { - benchmark.cleanup(); + if (benchmarkHelper != null) { + benchmarkHelper.cleanup(); } if (s3Client != null) { diff --git a/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/apache5/Apache5Benchmark.java b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/apache5/Apache5Benchmark.java index f80569e86d9f..a398716d1ba3 100644 --- a/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/apache5/Apache5Benchmark.java +++ b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/apache5/Apache5Benchmark.java @@ -38,11 +38,16 @@ import org.openjdk.jmh.infra.Blackhole; import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; import software.amazon.awssdk.benchmark.core.CoreBenchmark; -import software.amazon.awssdk.benchmark.core.S3BenchmarkImpl; +import software.amazon.awssdk.benchmark.core.ObjectSize; +import software.amazon.awssdk.benchmark.core.S3BenchmarkHelper; +import software.amazon.awssdk.core.ResponseInputStream; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.apache5.Apache5HttpClient; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.awssdk.services.s3.model.PutObjectResponse; +import software.amazon.awssdk.utils.IoUtils; import software.amazon.awssdk.utils.Logger; @BenchmarkMode(Mode.Throughput) @@ -57,17 +62,14 @@ public class Apache5Benchmark implements CoreBenchmark { @Param({"50"}) private int maxConnections; - @Param({"5"}) - private int testDataInMB; + @Param("SMALL") + private ObjectSize objectSize; @Param({"10"}) private int threadCount; - @Param({"platform"}) - private String executorType; - private S3Client s3Client; - private S3BenchmarkImpl benchmark; + private S3BenchmarkHelper benchmarkHelper; private ExecutorService executorService; @Setup(Level.Trial) @@ -89,9 +91,8 @@ public void setup() { .httpClient(httpClient) .build(); - // Initialize benchmark implementation - benchmark = new S3BenchmarkImpl(s3Client, new byte[this.testDataInMB * 1024 * 1024]); - benchmark.setup(); + benchmarkHelper = new S3BenchmarkHelper(Apache5Benchmark.class.getSimpleName(), s3Client); + benchmarkHelper.setup(); // Always use platform threads executorService = Executors.newFixedThreadPool(threadCount, r -> { @@ -106,15 +107,13 @@ public void setup() { } @Benchmark - @Override - public void simpleGet(Blackhole blackhole) throws Exception { - benchmark.executeGet("medium", blackhole); + public void simpleGet(Blackhole blackhole) { + executeGet(blackhole); } @Benchmark - @Override - public void simplePut(Blackhole blackhole) throws Exception { - benchmark.executePut("medium", blackhole); + public void simplePut(Blackhole blackhole) { + executePut(blackhole); } @Benchmark @@ -125,7 +124,7 @@ public void multiThreadedGet(Blackhole blackhole) throws Exception { for (int i = 0; i < threadCount; i++) { futures.add(executorService.submit(() -> { try { - benchmark.executeGet("medium", blackhole); + executeGet(blackhole); } catch (Exception e) { throw new RuntimeException("GET operation failed", e); } @@ -146,7 +145,7 @@ public void multiThreadedPut(Blackhole blackhole) throws Exception { for (int i = 0; i < threadCount; i++) { futures.add(executorService.submit(() -> { try { - benchmark.executePut("medium", blackhole); + executePut(blackhole); } catch (Exception e) { throw new RuntimeException("PUT operation failed", e); } @@ -159,6 +158,20 @@ public void multiThreadedPut(Blackhole blackhole) throws Exception { } } + private void executeGet(Blackhole blackhole) { + ResponseInputStream object = s3Client.getObject( + r -> r.bucket(benchmarkHelper.bucketName()).key(benchmarkHelper.objKey(objectSize))); + blackhole.consume(object.response()); + IoUtils.drainInputStream(object); + } + + private void executePut(Blackhole blackhole) { + PutObjectResponse response = s3Client.putObject( + r -> r.bucket(benchmarkHelper.bucketName()).key("Apache5Benchmark-" + Thread.currentThread().getName()), + benchmarkHelper.requestBody(objectSize)); + blackhole.consume(response); + } + @TearDown(Level.Trial) public void tearDown() { logger.info(() -> "Tearing down Apache5 benchmark"); @@ -174,8 +187,8 @@ public void tearDown() { } } - if (benchmark != null) { - benchmark.cleanup(); + if (benchmarkHelper != null) { + benchmarkHelper.cleanup(); } if (s3Client != null) { diff --git a/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/apache5/Apache5VirtualBenchmark.java b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/apache5/Apache5VirtualBenchmark.java deleted file mode 100644 index e0c228df3ca4..000000000000 --- a/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/apache5/Apache5VirtualBenchmark.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.benchmark.apache5; - -import static software.amazon.awssdk.benchmark.apache5.utility.BenchmarkUtilities.isJava21OrHigher; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Level; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Param; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.TearDown; -import org.openjdk.jmh.annotations.Warmup; -import org.openjdk.jmh.infra.Blackhole; -import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; -import software.amazon.awssdk.benchmark.core.CoreBenchmark; -import software.amazon.awssdk.benchmark.core.S3BenchmarkImpl; -import software.amazon.awssdk.http.SdkHttpClient; -import software.amazon.awssdk.http.apache5.Apache5HttpClient; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.utils.JavaSystemSetting; -import software.amazon.awssdk.utils.Logger; - -/** - * Apache5 benchmark using virtual threads. This class requires Java 21+. - */ -@BenchmarkMode(Mode.Throughput) -@OutputTimeUnit(TimeUnit.SECONDS) -@State(Scope.Benchmark) -@Fork(value = 1, jvmArgs = {"-Xms2G", "-Xmx2G"}) -@Warmup(iterations = 3, time = 15, timeUnit = TimeUnit.SECONDS) -@Measurement(iterations = 5, time = 10, timeUnit = TimeUnit.SECONDS) -public class Apache5VirtualBenchmark implements CoreBenchmark { - - private static final Logger logger = Logger.loggerFor(Apache5VirtualBenchmark.class); - - @Param({"50"}) - private int maxConnections; - - @Param({"5"}) - private int testDataInMB; - - @Param({"10"}) - private int threadCount; - - private S3Client s3Client; - private S3BenchmarkImpl benchmark; - private ExecutorService executorService; - - - - @Setup(Level.Trial) - public void setup() { - // Verify Java version - String version = JavaSystemSetting.JAVA_VERSION.getStringValueOrThrow(); - // Update logging call to use Supplier pattern - logger.info(() -> "Running on Java version: " + version); - - if (!isJava21OrHigher()) { - throw new UnsupportedOperationException( - "Virtual threads require Java 21 or higher. Current version: " + version); - } - - // Update logging call to use Supplier pattern - logger.info(() -> "Setting up Apache5 virtual threads benchmark with maxConnections=" + maxConnections); - - // Apache 5 HTTP client - SdkHttpClient httpClient = Apache5HttpClient.builder() - .connectionTimeout(Duration.ofSeconds(10)) - .socketTimeout(Duration.ofSeconds(30)) - .connectionAcquisitionTimeout(Duration.ofSeconds(10)) - .maxConnections(maxConnections) - .build(); - - // S3 client - s3Client = S3Client.builder() - .region(Region.US_WEST_2) - .credentialsProvider(DefaultCredentialsProvider.create()) - .httpClient(httpClient) - .build(); - - // Initialize benchmark implementation - benchmark = new S3BenchmarkImpl(s3Client, new byte[this.testDataInMB * 1024 * 1024]); - benchmark.setup(); - - // Create virtual thread executor - executorService = createVirtualThreadExecutor(); - // Update logging call to use Supplier pattern - logger.info(() -> "Using virtual thread executor"); - - // Update logging call to use Supplier pattern - logger.info(() -> "Apache5 virtual threads benchmark setup complete"); - } - - private ExecutorService createVirtualThreadExecutor() { - try { - // Use reflection to call Executors.newVirtualThreadPerTaskExecutor() - Method method = Executors.class.getMethod("newVirtualThreadPerTaskExecutor"); - return (ExecutorService) method.invoke(null); - } catch (NoSuchMethodException e) { - throw new UnsupportedOperationException( - "Virtual threads are not available in this Java version. " + - "This benchmark requires Java 21 or higher.", e); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException("Failed to create virtual thread executor", e); - } - } - - @Benchmark - @Override - public void simpleGet(Blackhole blackhole) throws Exception { - benchmark.executeGet("medium", blackhole); - } - - @Benchmark - @Override - public void simplePut(Blackhole blackhole) throws Exception { - benchmark.executePut("medium", blackhole); - } - - @Benchmark - @Override - public void multiThreadedGet(Blackhole blackhole) throws Exception { - List> futures = new ArrayList<>(threadCount); - - for (int i = 0; i < threadCount; i++) { - futures.add(executorService.submit(() -> { - try { - benchmark.executeGet("medium", blackhole); - } catch (Exception e) { - throw new RuntimeException("GET operation failed", e); - } - })); - } - - // Wait for all operations to complete - for (Future future : futures) { - future.get(); - } - } - - @Benchmark - @Override - public void multiThreadedPut(Blackhole blackhole) throws Exception { - List> futures = new ArrayList<>(threadCount); - - for (int i = 0; i < threadCount; i++) { - futures.add(executorService.submit(() -> { - try { - benchmark.executePut("medium", blackhole); - } catch (Exception e) { - throw new RuntimeException("PUT operation failed", e); - } - })); - } - - // Wait for all operations to complete - for (Future future : futures) { - future.get(); - } - } - - @TearDown(Level.Trial) - public void tearDown() { - logger.info(() -> "Tearing down Apache5 virtual threads benchmark"); - - if (executorService != null) { - executorService.shutdown(); - try { - if (!executorService.awaitTermination(30, TimeUnit.SECONDS)) { - executorService.shutdownNow(); - } - } catch (InterruptedException e) { - executorService.shutdownNow(); - } - } - - if (benchmark != null) { - benchmark.cleanup(); - } - - if (s3Client != null) { - s3Client.close(); - } - } -} diff --git a/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/core/AsyncS3Wrapper.java b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/core/AsyncS3Wrapper.java new file mode 100644 index 000000000000..71dd6c3c60eb --- /dev/null +++ b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/core/AsyncS3Wrapper.java @@ -0,0 +1,64 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.benchmark.core; + +import java.nio.file.Path; +import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.model.CreateBucketRequest; +import software.amazon.awssdk.services.s3.model.DeleteBucketRequest; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.ListObjectsV2Request; +import software.amazon.awssdk.services.s3.model.ListObjectsV2Response; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; + +public class AsyncS3Wrapper implements S3Wrapper { + private final S3AsyncClient s3; + + public AsyncS3Wrapper(S3AsyncClient s3) { + this.s3 = s3; + } + + @Override + public void createBucket(CreateBucketRequest request) { + s3.createBucket(request).join(); + } + + @Override + public void putObject(PutObjectRequest request, Path file) { + s3.putObject(request, AsyncRequestBody.fromFile(file)).join(); + } + + @Override + public ListObjectsV2Response listObjectsV2(ListObjectsV2Request request) { + return s3.listObjectsV2(request).join(); + } + + @Override + public void deleteObject(DeleteObjectRequest request) { + s3.deleteObject(request).join(); + } + + @Override + public void deleteBucket(DeleteBucketRequest request) { + s3.deleteBucket(request).join(); + } + + @Override + public void waitUntilBucketExists(String bucketName) { + s3.waiter().waitUntilBucketExists(r -> r.bucket(bucketName)).join(); + } +} diff --git a/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/core/ObjectSize.java b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/core/ObjectSize.java new file mode 100644 index 000000000000..f9dafbcb7354 --- /dev/null +++ b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/core/ObjectSize.java @@ -0,0 +1,33 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.benchmark.core; + +public enum ObjectSize { + + SMALL(1024L * 1024), + MEDIUM(8L * 1024 * 1024), + LARGE(64L * 1024 * 1024); + + private final long sizeInBytes; + + ObjectSize(long sizeInBytes) { + this.sizeInBytes = sizeInBytes; + } + + public long sizeInBytes() { + return sizeInBytes; + } +} diff --git a/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/core/S3BenchmarkHelper.java b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/core/S3BenchmarkHelper.java new file mode 100644 index 000000000000..354080990673 --- /dev/null +++ b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/core/S3BenchmarkHelper.java @@ -0,0 +1,145 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.benchmark.core; + +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.EnumMap; +import java.util.Locale; +import java.util.Random; +import software.amazon.awssdk.core.async.AsyncRequestBody; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.CreateBucketRequest; +import software.amazon.awssdk.services.s3.model.DeleteBucketRequest; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.ListObjectsV2Request; +import software.amazon.awssdk.services.s3.model.ListObjectsV2Response; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.model.S3Object; +import software.amazon.awssdk.utils.Logger; + +/** + * Shared S3 operations implementation used by all benchmark classes. + */ +public class S3BenchmarkHelper { + private static final Logger logger = Logger.loggerFor(S3BenchmarkHelper.class); + private static final String TEST_KEY_PREFIX = "benchmark-object-"; + + private final S3Wrapper s3Wrapper; + private final String name; + private String bucketName; + + private final EnumMap testFiles = new EnumMap<>(ObjectSize.class); + + public S3BenchmarkHelper(String name, S3Client s3Client) { + this.name = name; + this.s3Wrapper = new SyncS3Wrapper(s3Client); + } + + public S3BenchmarkHelper(String name, S3AsyncClient s3AsyncClient) { + this.name = name; + this.s3Wrapper = new AsyncS3Wrapper(s3AsyncClient); + } + + public void setup() { + try { + this.bucketName = name.toLowerCase(Locale.ENGLISH) + "-bucket-" + System.currentTimeMillis(); + // Create bucket + s3Wrapper.createBucket(CreateBucketRequest.builder().bucket(bucketName).build()); + + // Wait for bucket to be ready + s3Wrapper.waitUntilBucketExists(bucketName); + + byte[] testData = new byte[1024 * 1024]; + new Random().nextBytes(testData); + + for (ObjectSize size : ObjectSize.values()) { + Path p = Files.createTempFile(name + "-test-file", null); + OutputStream os = Files.newOutputStream(p); + + long chunks = size.sizeInBytes() / testData.length; + for (long i = 0; i < chunks; i++) { + os.write(testData, 0, testData.length); + } + + os.close(); + testFiles.put(size, p); + + s3Wrapper.putObject(PutObjectRequest.builder().bucket(bucketName).key(objKey(size)).build(), p); + } + } catch (Exception e) { + logger.error(() -> "Setup failed: " + e.getMessage(), e); + throw new RuntimeException("Failed to setup S3 benchmark", e); + } + } + + public String bucketName() { + return bucketName; + } + + public String objKey(ObjectSize objectSize) { + return TEST_KEY_PREFIX + objectSize.name(); + } + + public RequestBody requestBody(ObjectSize objectSize) { + Path p = testFiles.get(objectSize); + if (p == null) { + throw new RuntimeException("Test file not found: " + objectSize.name()); + } + + return RequestBody.fromFile(p); + } + + public AsyncRequestBody asyncRequestBody(ObjectSize objectSize) { + Path p = testFiles.get(objectSize); + if (p == null) { + throw new RuntimeException("Test file not found: " + objectSize.name()); + } + return AsyncRequestBody.fromFile(p); + } + + public void cleanup() { + try { + // Delete all objects (handle pagination) + ListObjectsV2Request.Builder listRequestBuilder = ListObjectsV2Request.builder() + .bucket(bucketName); + String continuationToken = null; + do { + if (continuationToken != null) { + listRequestBuilder.continuationToken(continuationToken); + } + ListObjectsV2Response listResponse = s3Wrapper.listObjectsV2(listRequestBuilder.build()); + for (S3Object object : listResponse.contents()) { + s3Wrapper.deleteObject(DeleteObjectRequest.builder() + .bucket(bucketName) + .key(object.key()) + .build()); + } + continuationToken = listResponse.nextContinuationToken(); + } while (continuationToken != null); + // Delete bucket + s3Wrapper.deleteBucket(DeleteBucketRequest.builder() + .bucket(bucketName) + .build()); + logger.info(() -> "Cleaned up bucket: " + bucketName); + } catch (Exception e) { + logger.warn(() -> "Cleanup failed: " + e.getMessage(), e); + } + } +} diff --git a/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/core/S3BenchmarkImpl.java b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/core/S3BenchmarkImpl.java deleted file mode 100644 index 9eed9e94ad60..000000000000 --- a/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/core/S3BenchmarkImpl.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.benchmark.core; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.UUID; -import java.util.concurrent.ThreadLocalRandom; -import org.openjdk.jmh.infra.Blackhole; -import software.amazon.awssdk.core.ResponseInputStream; -import software.amazon.awssdk.core.sync.RequestBody; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.CreateBucketRequest; -import software.amazon.awssdk.services.s3.model.DeleteBucketRequest; -import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; -import software.amazon.awssdk.services.s3.model.GetObjectRequest; -import software.amazon.awssdk.services.s3.model.GetObjectResponse; -import software.amazon.awssdk.services.s3.model.HeadBucketRequest; -import software.amazon.awssdk.services.s3.model.ListObjectsV2Request; -import software.amazon.awssdk.services.s3.model.ListObjectsV2Response; -import software.amazon.awssdk.services.s3.model.PutObjectRequest; -import software.amazon.awssdk.services.s3.model.PutObjectResponse; -import software.amazon.awssdk.services.s3.model.S3Object; -import software.amazon.awssdk.utils.Logger; - -/** - * Shared S3 operations implementation used by all benchmark classes. - */ -public class S3BenchmarkImpl { - private static final Logger logger = Logger.loggerFor(S3BenchmarkImpl.class); - private static final String TEST_KEY_PREFIX = "benchmark-object-"; - private static final int OBJECT_COUNT = 100; - - private final S3Client s3Client; - private final String bucketName; - private final byte[] testData; - - public S3BenchmarkImpl(S3Client s3Client, byte[] testData) { - this.s3Client = s3Client; - this.bucketName = "benchmark-bucket-" + UUID.randomUUID().toString().substring(0, 8); - this.testData = testData; - ThreadLocalRandom.current().nextBytes(testData); - } - - public void setup() { - try { - // Create bucket - s3Client.createBucket(CreateBucketRequest.builder() - .bucket(bucketName) - .build()); - - logger.info(() -> "Created bucket: " + bucketName); - - // Wait for bucket to be ready - s3Client.waiter().waitUntilBucketExists(HeadBucketRequest.builder() - .bucket(bucketName) - .build()); - - // Upload test objects - for (int i = 0; i < OBJECT_COUNT; i++) { - String key = TEST_KEY_PREFIX + i; - s3Client.putObject( - PutObjectRequest.builder() - .bucket(bucketName) - .key(key) - .build(), - RequestBody.fromBytes(testData) - ); - } - - logger.info(() -> "Uploaded " + OBJECT_COUNT + " test objects"); - - } catch (Exception e) { - logger.error(() -> "Setup failed: " + e.getMessage(), e); - throw new RuntimeException("Failed to setup S3 benchmark", e); - } - } - - public void executeGet(String size, Blackhole blackhole) throws IOException { - // Random key to avoid caching effects - String key = TEST_KEY_PREFIX + ThreadLocalRandom.current().nextInt(OBJECT_COUNT); - - GetObjectRequest request = GetObjectRequest.builder() - .bucket(bucketName) - .key(key) - .build(); - - ResponseInputStream response = null; - try { - response = s3Client.getObject(request); - byte[] data = readAllBytes(response); - blackhole.consume(data); - blackhole.consume(response.response()); - } finally { - if (response != null) { - response.close(); - } - } - } - - public void executePut(String size, Blackhole blackhole) { - String key = "put-object-" + UUID.randomUUID(); - - PutObjectRequest request = PutObjectRequest.builder() - .bucket(bucketName) - .key(key) - .build(); - - PutObjectResponse response = s3Client.putObject(request, - RequestBody.fromBytes(testData)); - - blackhole.consume(response); - - // Clean up immediately to avoid accumulating objects - s3Client.deleteObject(DeleteObjectRequest.builder() - .bucket(bucketName) - .key(key) - .build()); - } - - public void cleanup() { - try { - // Delete all objects (handle pagination) - ListObjectsV2Request.Builder listRequestBuilder = ListObjectsV2Request.builder() - .bucket(bucketName); - String continuationToken = null; - do { - if (continuationToken != null) { - listRequestBuilder.continuationToken(continuationToken); - } - ListObjectsV2Response listResponse = s3Client.listObjectsV2(listRequestBuilder.build()); - for (S3Object object : listResponse.contents()) { - s3Client.deleteObject(DeleteObjectRequest.builder() - .bucket(bucketName) - .key(object.key()) - .build()); - } - continuationToken = listResponse.nextContinuationToken(); - } while (continuationToken != null); - // Delete bucket - s3Client.deleteBucket(DeleteBucketRequest.builder() - .bucket(bucketName) - .build()); - logger.info(() -> "Cleaned up bucket: " + bucketName); - - } catch (Exception e) { - logger.warn(() -> "Cleanup failed: " + e.getMessage(), e); - } - } - - private byte[] readAllBytes(InputStream inputStream) throws IOException { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - byte[] data = new byte[8192]; - int nRead; - while ((nRead = inputStream.read(data, 0, data.length)) != -1) { - buffer.write(data, 0, nRead); - } - return buffer.toByteArray(); - } -} diff --git a/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/core/S3Wrapper.java b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/core/S3Wrapper.java new file mode 100644 index 000000000000..599e93e15094 --- /dev/null +++ b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/core/S3Wrapper.java @@ -0,0 +1,41 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.benchmark.core; + +import java.nio.file.Path; +import software.amazon.awssdk.services.s3.model.CreateBucketRequest; +import software.amazon.awssdk.services.s3.model.DeleteBucketRequest; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.ListObjectsV2Request; +import software.amazon.awssdk.services.s3.model.ListObjectsV2Response; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; + +/** + * Simple wrapper for S3 APIs so we can implement it using sync or async S3 clients. + */ +public interface S3Wrapper { + void createBucket(CreateBucketRequest request); + + void putObject(PutObjectRequest request, Path file); + + ListObjectsV2Response listObjectsV2(ListObjectsV2Request request); + + void deleteObject(DeleteObjectRequest request); + + void deleteBucket(DeleteBucketRequest request); + + void waitUntilBucketExists(String bucketName); +} diff --git a/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/core/SyncS3Wrapper.java b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/core/SyncS3Wrapper.java new file mode 100644 index 000000000000..50484e5f76d8 --- /dev/null +++ b/test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/core/SyncS3Wrapper.java @@ -0,0 +1,64 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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 software.amazon.awssdk.benchmark.core; + +import java.nio.file.Path; +import software.amazon.awssdk.core.sync.RequestBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.CreateBucketRequest; +import software.amazon.awssdk.services.s3.model.DeleteBucketRequest; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.ListObjectsV2Request; +import software.amazon.awssdk.services.s3.model.ListObjectsV2Response; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; + +public class SyncS3Wrapper implements S3Wrapper { + private final S3Client s3; + + public SyncS3Wrapper(S3Client s3) { + this.s3 = s3; + } + + @Override + public void createBucket(CreateBucketRequest request) { + s3.createBucket(request); + } + + @Override + public void putObject(PutObjectRequest request, Path file) { + s3.putObject(request, RequestBody.fromFile(file)); + } + + @Override + public ListObjectsV2Response listObjectsV2(ListObjectsV2Request request) { + return s3.listObjectsV2(request); + } + + @Override + public void deleteObject(DeleteObjectRequest request) { + s3.deleteObject(request); + } + + @Override + public void deleteBucket(DeleteBucketRequest request) { + s3.deleteBucket(request); + } + + @Override + public void waitUntilBucketExists(String bucketName) { + s3.waiter().waitUntilBucketExists(r -> r.bucket(bucketName)); + } +}