Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions test/http-client-benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,24 @@
<version>${awsjavasdk.version}-PREVIEW</version>
</dependency>

<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>url-connection-client</artifactId>
<version>${awsjavasdk.version}</version>
</dependency>

<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>aws-crt-client</artifactId>
<version>${awsjavasdk.version}</version>
</dependency>

<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>netty-nio-client</artifactId>
<version>${awsjavasdk.version}</version>
</dependency>

<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -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);

Check warning on line 77 in test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/AsyncVirtualThreadBenchmark.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace generic exceptions with specific library exceptions or a custom exception.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZrcuCGWKgfLXMbBa0tU&open=AZrcuCGWKgfLXMbBa0tU&pullRequest=6602
}
}

public enum Client {
Netty,

Check failure on line 82 in test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/AsyncVirtualThreadBenchmark.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this constant name to match the regular expression '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$'.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZrcuCGWKgfLXMbBa0tX&open=AZrcuCGWKgfLXMbBa0tX&pullRequest=6602
Crt

Check failure on line 83 in test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/AsyncVirtualThreadBenchmark.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this constant name to match the regular expression '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$'.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZrcuCGWKgfLXMbBa0tY&open=AZrcuCGWKgfLXMbBa0tY&pullRequest=6602
}

@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())

Check warning on line 111 in test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/AsyncVirtualThreadBenchmark.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this use of "create"; it is deprecated.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZrcuCGWKgfLXMbBa0tZ&open=AZrcuCGWKgfLXMbBa0tZ&pullRequest=6602
.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);

Check warning on line 143 in test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/AsyncVirtualThreadBenchmark.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace generic exceptions with specific library exceptions or a custom exception.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZrcuCGWKgfLXMbBa0tV&open=AZrcuCGWKgfLXMbBa0tV&pullRequest=6602
}
}

@Benchmark
public void getObject(Blackhole blackhole) {
safeExecute(() -> {
ResponseInputStream<GetObjectResponse> 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) {

Check warning on line 177 in test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/AsyncVirtualThreadBenchmark.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Either re-interrupt this method or rethrow the "InterruptedException" that can be caught here.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZrcuCGWKgfLXMbBa0ta&open=AZrcuCGWKgfLXMbBa0ta&pullRequest=6602
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) {

Check warning on line 194 in test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/AsyncVirtualThreadBenchmark.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Either re-interrupt this method or rethrow the "InterruptedException" that can be caught here.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZrcuCGWKgfLXMbBa0tb&open=AZrcuCGWKgfLXMbBa0tb&pullRequest=6602
throw new RuntimeException("Error during execution", e);

Check warning on line 195 in test/http-client-benchmarks/src/main/java/software/amazon/awssdk/benchmark/AsyncVirtualThreadBenchmark.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace generic exceptions with specific library exceptions or a custom exception.

See more on https://sonarcloud.io/project/issues?id=aws_aws-sdk-java-v2&issues=AZrcuCGWKgfLXMbBa0tW&open=AZrcuCGWKgfLXMbBa0tW&pullRequest=6602
}
}

private SdkAsyncHttpClient.Builder<?> httpClientBuilder() {
switch (client) {
case Netty:
return NettyNioAsyncHttpClient.builder();
case Crt:
return AwsCrtAsyncHttpClient.builder();
default:
throw new IllegalArgumentException("Unknown HTTP client: " + client);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() + ")");
Expand Down
Loading
Loading