Skip to content

Commit b61ce81

Browse files
authored
Merge pull request #438 from Aiven-Open/jeqo/benchmarks-module
chore: add jmh benchmarks module
2 parents 28e6478 + 7f42bc9 commit b61ce81

File tree

11 files changed

+594
-0
lines changed

11 files changed

+594
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ build/
1010
rpm/
1111
rpmbuild/
1212
*.sh
13+
# ignore benchmark outputs
14+
io.aiven.kafka.tieredstorage*/

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,8 @@ docker_image: build
6868
.PHONY: docker_push
6969
docker_push:
7070
docker push $(IMAGE_TAG)
71+
72+
# Prepare kernel to capture CPU events
73+
async_profiler_cpu_kernel-prep:
74+
sudo sh -c 'echo 1 >/proc/sys/kernel/perf_event_paranoid'
75+
sudo sh -c 'echo 0 >/proc/sys/kernel/kptr_restrict'

benchmarks/README.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
### Benchmarks module
2+
3+
> Borrowed from https://github.com/apache/kafka/blob/trunk/jmh-benchmarks
4+
5+
This module contains benchmarks written using [JMH](https://openjdk.java.net/projects/code-tools/jmh/) from OpenJDK.
6+
7+
### Running benchmarks
8+
9+
If you want to set specific JMH flags or only run certain benchmarks, passing arguments via
10+
gradle tasks is cumbersome. These are simplified by the provided `jmh.sh` script.
11+
12+
The default behavior is to run all benchmarks:
13+
14+
./benchmarks/jmh.sh
15+
16+
Pass a pattern or name after the command to select the benchmarks:
17+
18+
./benchmarks/jmh.sh TransformBench
19+
20+
Check which benchmarks that match the provided pattern:
21+
22+
./benchmarks/jmh.sh -l TransformBench
23+
24+
Run a specific test and override the number of forks, iterations and warm-up iteration to `2`:
25+
26+
./benchmarks/jmh.sh -f 2 -i 2 -wi 2 TransformBench
27+
28+
Run a specific test with async and GC profilers on Linux and flame graph output:
29+
30+
./benchmarks/jmh.sh -prof gc -prof async:libPath=/path/to/libasyncProfiler.so\;output=flamegraph TransformBench
31+
32+
The following sections cover async profiler and GC profilers in more detail.
33+
34+
### Using JMH with async profiler
35+
36+
It's good practice to check profiler output for micro-benchmarks in order to verify that they represent the expected
37+
application behavior and measure what you expect to measure. Some example pitfalls include the use of expensive mocks
38+
or accidental inclusion of test setup code in the benchmarked code. JMH includes
39+
[async-profiler](https://github.com/jvm-profiling-tools/async-profiler) integration that makes this easy:
40+
41+
./benchmarks/jmh.sh -prof async:libPath=/path/to/libasyncProfiler.so
42+
43+
or if having async-profiler on environment variable `export LD_LIBRARY_PATH=/opt/async-profiler-2.9-linux-x64/build/`
44+
45+
./benchmarks/jmh.sh -prof async
46+
47+
With flame graph output (the semicolon is escaped to ensure it is not treated as a command separator):
48+
49+
./benchmarks/jmh.sh -prof async:libPath=/path/to/libasyncProfiler.so\;output=flamegraph
50+
51+
Simultaneous cpu, allocation and lock profiling with async profiler 2.0 and jfr output (the semicolon is
52+
escaped to ensure it is not treated as a command separator):
53+
54+
./benchmarks/jmh.sh -prof async:libPath=/path/to/libasyncProfiler.so\;output=jfr\;alloc\;lock TransformBench
55+
56+
A number of arguments can be passed to configure async profiler, run the following for a description:
57+
58+
./benchmarks/jmh.sh -prof async:help
59+
60+
### Using JMH GC profiler
61+
62+
It's good practice to run your benchmark with `-prof gc` to measure its allocation rate:
63+
64+
./benchmarks/jmh.sh -prof gc
65+
66+
Of particular importance is the `norm` alloc rates, which measure the allocations per operation rather than allocations
67+
per second which can increase when you have make your code faster.
68+
69+
### Running JMH outside gradle
70+
71+
The JMH benchmarks can be run outside gradle as you would with any executable jar file:
72+
73+
java -jar ./benchmarks/build/libs/kafka-benchmarks-*.jar -f2 TransformBench
74+
75+
### Gradle Tasks
76+
77+
If no benchmark mode is specified, the default is used which is throughput. It is assumed that users run
78+
the gradle tasks with `./gradlew` from the root of the Kafka project.
79+
80+
* `benchmarks:shadowJar` - creates the uber jar required to run the benchmarks.
81+
82+
* `benchmarks:jmh` - runs the `clean` and `shadowJar` tasks followed by all the benchmarks.
83+
84+
### JMH Options
85+
Some common JMH options are:
86+
87+
```text
88+
89+
-e <regexp+> Benchmarks to exclude from the run.
90+
91+
-f <int> How many times to fork a single benchmark. Use 0 to
92+
disable forking altogether. Warning: disabling
93+
forking may have detrimental impact on benchmark
94+
and infrastructure reliability, you might want
95+
to use different warmup mode instead.
96+
97+
-i <int> Number of measurement iterations to do. Measurement
98+
iterations are counted towards the benchmark score.
99+
(default: 1 for SingleShotTime, and 5 for all other
100+
modes)
101+
102+
-l List the benchmarks that match a filter, and exit.
103+
104+
-lprof List profilers, and exit.
105+
106+
-o <filename> Redirect human-readable output to a given file.
107+
108+
-prof <profiler> Use profilers to collect additional benchmark data.
109+
Some profilers are not available on all JVMs and/or
110+
all OSes. Please see the list of available profilers
111+
with -lprof.
112+
113+
-v <mode> Verbosity mode. Available modes are: [SILENT, NORMAL,
114+
EXTRA]
115+
116+
-wi <int> Number of warmup iterations to do. Warmup iterations
117+
are not counted towards the benchmark score. (default:
118+
0 for SingleShotTime, and 5 for all other modes)
119+
```
120+
121+
To view all options run jmh with the -h flag.

benchmarks/build.gradle

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2021 Aiven Oy
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// JMH execution borrowed from https://github.com/apache/kafka/blob/trunk/jmh-benchmarks
18+
19+
plugins {
20+
id 'com.github.johnrengelman.shadow' version '8.1.1'
21+
}
22+
23+
shadowJar {
24+
archiveBaseName = 'kafka-ts-benchmarks'
25+
}
26+
27+
ext {
28+
jmhVersion = "1.36"
29+
}
30+
31+
dependencies {
32+
implementation project(':core')
33+
implementation group: "org.apache.kafka", name: "kafka-storage-api", version: kafkaVersion
34+
implementation group: "org.apache.kafka", name: "kafka-clients", version: kafkaVersion
35+
36+
implementation "org.openjdk.jmh:jmh-core:$jmhVersion"
37+
implementation "org.openjdk.jmh:jmh-core-benchmarks:$jmhVersion"
38+
annotationProcessor "org.openjdk.jmh:jmh-generator-annprocess:$jmhVersion"
39+
40+
implementation "org.slf4j:slf4j-log4j12:1.7.36"
41+
}
42+
43+
jar {
44+
manifest {
45+
attributes "Main-Class": "org.openjdk.jmh.Main"
46+
}
47+
}
48+
49+
tasks.register('jmh', JavaExec) {
50+
dependsOn ':benchmarks:clean'
51+
dependsOn ':benchmarks:shadowJar'
52+
53+
mainClass = "-jar"
54+
55+
doFirst {
56+
if (System.getProperty("jmhArgs")) {
57+
args System.getProperty("jmhArgs").split(' ')
58+
}
59+
args = [shadowJar.getArchiveFile(), *args]
60+
}
61+
}
62+
63+
javadoc {
64+
enabled = false
65+
}

benchmarks/jmh.sh

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env bash
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
base_dir=$(dirname $0)
18+
jmh_project_name="benchmarks"
19+
20+
if [ ${base_dir} == "." ]; then
21+
gradlew_dir=".."
22+
elif [ ${base_dir##./} == "${jmh_project_name}" ]; then
23+
gradlew_dir="."
24+
else
25+
echo "JMH Benchmarks need to be run from the root of the kafka repository or the 'benchmarks' directory"
26+
exit
27+
fi
28+
29+
gradleCmd="${gradlew_dir}/gradlew"
30+
libDir="${base_dir}/build/libs"
31+
32+
echo "running gradlew :benchmarks:clean :benchmarks:shadowJar"
33+
34+
$gradleCmd :benchmarks:clean :benchmarks:shadowJar
35+
36+
echo "gradle build done"
37+
38+
echo "running JMH with args: $@"
39+
40+
java -jar ${libDir}/kafka-ts-benchmarks-*.jar "$@"
41+
42+
echo "JMH benchmarks done"
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2023 Aiven Oy
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.aiven.kafka.tieredstorage.benchs;
18+
19+
import javax.crypto.Cipher;
20+
import javax.crypto.NoSuchPaddingException;
21+
import javax.crypto.spec.GCMParameterSpec;
22+
import javax.crypto.spec.SecretKeySpec;
23+
24+
import java.security.InvalidAlgorithmParameterException;
25+
import java.security.InvalidKeyException;
26+
import java.security.NoSuchAlgorithmException;
27+
import java.security.SecureRandom;
28+
import java.util.Random;
29+
30+
public class AesKeyAware {
31+
protected static int ivSize;
32+
protected static SecretKeySpec secretKey;
33+
protected static byte[] aad;
34+
35+
public static void initCrypto() {
36+
// These are tests, we don't need a secure source of randomness.
37+
final Random random = new Random();
38+
39+
final byte[] dataKey = new byte[32];
40+
random.nextBytes(dataKey);
41+
secretKey = new SecretKeySpec(dataKey, "AES");
42+
43+
aad = new byte[32];
44+
random.nextBytes(aad);
45+
46+
ivSize = encryptionCipherSupplier().getIV().length;
47+
}
48+
49+
protected static Cipher encryptionCipherSupplier() {
50+
try {
51+
final Cipher encryptCipher = getCipher();
52+
encryptCipher.init(Cipher.ENCRYPT_MODE, secretKey, SecureRandom.getInstanceStrong());
53+
encryptCipher.updateAAD(aad);
54+
return encryptCipher;
55+
} catch (final NoSuchAlgorithmException | InvalidKeyException e) {
56+
throw new RuntimeException(e);
57+
}
58+
}
59+
60+
protected static Cipher decryptionCipherSupplier(final byte[] encryptedChunk) {
61+
try {
62+
final Cipher encryptCipher = getCipher();
63+
encryptCipher.init(Cipher.DECRYPT_MODE, secretKey,
64+
new GCMParameterSpec(128, encryptedChunk, 0, ivSize),
65+
SecureRandom.getInstanceStrong());
66+
encryptCipher.updateAAD(aad);
67+
return encryptCipher;
68+
} catch (final NoSuchAlgorithmException | InvalidKeyException | InvalidAlgorithmParameterException e) {
69+
throw new RuntimeException(e);
70+
}
71+
}
72+
73+
protected static Cipher getCipher() {
74+
try {
75+
return Cipher.getInstance("AES/GCM/NoPadding");
76+
} catch (final NoSuchAlgorithmException | NoSuchPaddingException e) {
77+
throw new RuntimeException(e);
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)