-
Notifications
You must be signed in to change notification settings - Fork 84
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is not utilizing maven submodules, that would require a lot of work. This commits add a new subproject in the `tests` folder to run benchmarks written with JMH. The benchmark we added here is not "micro". It uses `RaftHandle` object to replicate data with Raft, pretty much the way a user would. We vary the cluster and data size in the benchmark. Manually, we can tune the RAFT instance, changing the log implementation, fsync, etc.
- Loading branch information
Showing
4 changed files
with
332 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# JGroups Raft benchmarks | ||
|
||
This subproject includes all the benchmarks created utilizing JMH. | ||
|
||
## How to | ||
|
||
This specific repository uses the latest Java version available. Currently, Java 21. | ||
To compile the project and runs the benchmarks. | ||
First, execute: | ||
|
||
```bash | ||
$ mvn clean verify | ||
``` | ||
|
||
The output is located at `./target/benchmarks.jar`. | ||
Now, to execute a benchmark, check the current implementations and select the name. | ||
For example, for `MyBenchmark`: | ||
|
||
```bash | ||
$ java -jar target/benchmarks.jar "MyBenchmark" | ||
``` | ||
|
||
To pass configuration for `MyBenchmark`, execute: | ||
|
||
```bash | ||
$ java -jar target/benchmarks.jar -pkey1=value1 -pkey2=value2 "MyBenchmark" | ||
``` | ||
|
||
For more options, related to JMH: | ||
|
||
```bash | ||
$ java -jar target/benchmarks.jar -h | ||
``` | ||
|
||
## Current benchmarks | ||
|
||
The current list of benchmarks include: | ||
|
||
1. `DataReplicationBenchmark`: Benchmark the complete data replication utilizing `RaftHandle`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<groupId>org.jgroups.raft</groupId> | ||
<artifactId>benchmark</artifactId> | ||
<version>1.0.13.Final-SNAPSHOT</version> | ||
<packaging>jar</packaging> | ||
|
||
<name>JMH benchmark sample: Java</name> | ||
|
||
<properties> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
<java.version>21</java.version> | ||
<javac.target>21</javac.target> | ||
|
||
<jmh.version>1.37</jmh.version> | ||
<uberjar.name>benchmarks</uberjar.name> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.openjdk.jmh</groupId> | ||
<artifactId>jmh-core</artifactId> | ||
<version>${jmh.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.openjdk.jmh</groupId> | ||
<artifactId>jmh-generator-annprocess</artifactId> | ||
<version>${jmh.version}</version> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.jgroups</groupId> | ||
<artifactId>jgroups-raft</artifactId> | ||
<version>${project.version}</version> | ||
<scope>compile</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<version>3.8.0</version> | ||
<configuration> | ||
<compilerVersion>${javac.target}</compilerVersion> | ||
<source>${javac.target}</source> | ||
<target>${javac.target}</target> | ||
</configuration> | ||
</plugin> | ||
<plugin> | ||
<groupId>org.apache.maven.plugins</groupId> | ||
<artifactId>maven-shade-plugin</artifactId> | ||
<version>3.2.1</version> | ||
<executions> | ||
<execution> | ||
<phase>package</phase> | ||
<goals> | ||
<goal>shade</goal> | ||
</goals> | ||
<configuration> | ||
<finalName>${uberjar.name}</finalName> | ||
<transformers> | ||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> | ||
<mainClass>org.openjdk.jmh.Main</mainClass> | ||
</transformer> | ||
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/> | ||
</transformers> | ||
<filters> | ||
<filter> | ||
<!-- | ||
Shading signed JARs will fail without this. | ||
http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar | ||
--> | ||
<artifact>*:*</artifact> | ||
<excludes> | ||
<exclude>META-INF/*.SF</exclude> | ||
<exclude>META-INF/*.DSA</exclude> | ||
<exclude>META-INF/*.RSA</exclude> | ||
</excludes> | ||
</filter> | ||
</filters> | ||
</configuration> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
</plugins> | ||
<pluginManagement> | ||
<plugins> | ||
<plugin> | ||
<artifactId>maven-clean-plugin</artifactId> | ||
<version>2.5</version> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-deploy-plugin</artifactId> | ||
<version>2.8.1</version> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-install-plugin</artifactId> | ||
<version>2.5.1</version> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-jar-plugin</artifactId> | ||
<version>2.4</version> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-javadoc-plugin</artifactId> | ||
<version>2.9.1</version> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-resources-plugin</artifactId> | ||
<version>2.6</version> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-site-plugin</artifactId> | ||
<version>3.3</version> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-source-plugin</artifactId> | ||
<version>2.2.1</version> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-surefire-plugin</artifactId> | ||
<version>2.17</version> | ||
</plugin> | ||
</plugins> | ||
</pluginManagement> | ||
</build> | ||
|
||
</project> |
159 changes: 159 additions & 0 deletions
159
tests/benchmark/src/main/java/org/jgroups/raft/DataReplicationBenchmark.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
package org.jgroups.raft; | ||
|
||
import org.jgroups.JChannel; | ||
import org.jgroups.protocols.raft.FileBasedLog; | ||
import org.jgroups.protocols.raft.RAFT; | ||
import org.jgroups.raft.testfwk.RaftTestUtils; | ||
import org.jgroups.util.Util; | ||
|
||
import java.io.DataInput; | ||
import java.io.DataOutput; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.concurrent.ThreadLocalRandom; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.function.BooleanSupplier; | ||
import java.util.function.Supplier; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.IntStream; | ||
|
||
import org.openjdk.jmh.annotations.Benchmark; | ||
import org.openjdk.jmh.annotations.BenchmarkMode; | ||
import org.openjdk.jmh.annotations.Fork; | ||
import org.openjdk.jmh.annotations.Measurement; | ||
import org.openjdk.jmh.annotations.Mode; | ||
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; | ||
|
||
/** | ||
* Benchmark the replication throughput. | ||
* | ||
* @author José Bolina | ||
* @since 1.0.13 | ||
*/ | ||
@BenchmarkMode({Mode.Throughput}) | ||
@Warmup(iterations = 10, time = 5) | ||
@Measurement(iterations = 5, time = 10) | ||
@Fork(value = 3, jvmArgsPrepend = "-Djava.net.preferIPv4Stack=true -Djgroups.udp.ip_ttl=0") | ||
public class DataReplicationBenchmark { | ||
|
||
@Benchmark | ||
public byte[] testReplication(ClusterState state) throws Exception { | ||
return state.leader.set(state.data, 0, state.dataSize); | ||
} | ||
|
||
@State(Scope.Benchmark) | ||
public static class ClusterState { | ||
|
||
// Change the majority between values. | ||
@Param({"3", "5"}) | ||
public int clusterSize; | ||
|
||
// Keep a factor of 4 between values. | ||
@Param({"256", "1024", "4096"}) | ||
public int dataSize; | ||
|
||
// Storage implementations that write to disk can enable/disable fsync. | ||
@Param({"false", "true"}) | ||
public boolean useFsync; | ||
|
||
public byte[] data; | ||
|
||
public RaftHandle[] members; | ||
|
||
public RaftHandle leader; | ||
|
||
|
||
@Setup | ||
public void initialize() throws Exception { | ||
List<String> memberList = IntStream.range(0, clusterSize) | ||
.mapToObj(i -> Character.toString('A' + i)) | ||
.collect(Collectors.toList()); | ||
|
||
members = new RaftHandle[clusterSize]; | ||
for (int i = 0; i < clusterSize; i++) { | ||
String name = Character.toString('A' + i); | ||
|
||
// Utilize the default configuration shipped with jgroups-raft. | ||
JChannel ch = new JChannel("raft.xml"); | ||
ch.name(name); | ||
|
||
// A no-op state machine. | ||
RaftHandle handler = new RaftHandle(ch, new EmptyStateMachine(dataSize)); | ||
RAFT raft = handler.raft(); | ||
|
||
// Default configuration. | ||
raft.raftId(name); | ||
raft.members(memberList); | ||
|
||
// Fine-tune the RAFT protocol below. | ||
raft.logClass(FileBasedLog.class.getCanonicalName()); | ||
raft.logUseFsync(useFsync); | ||
|
||
members[i] = handler; | ||
ch.connect("jmh-replication"); | ||
} | ||
|
||
data = new byte[dataSize]; | ||
ThreadLocalRandom.current().nextBytes(data); | ||
|
||
// Block until ALL members have a leader installed. | ||
BooleanSupplier bs = () -> Arrays.stream(members) | ||
.map(RaftHandle::raft) | ||
.allMatch(r -> r.leader() != null); | ||
Supplier<String> message = () -> Arrays.stream(members) | ||
.map(RaftHandle::raft) | ||
.map(r -> String.format("%s: %s", r.raftId(), r.leader())) | ||
.collect(Collectors.joining(System.lineSeparator())); | ||
assert RaftTestUtils.eventually(bs, 1, TimeUnit.MINUTES) : message.get(); | ||
|
||
for (RaftHandle member : members) { | ||
if (member.isLeader()) { | ||
leader = member; | ||
break; | ||
} | ||
} | ||
|
||
assert leader != null : "Leader not found"; | ||
} | ||
|
||
@TearDown | ||
public void tearDown() throws Exception { | ||
for (int i = clusterSize - 1; i >= 0; i--) { | ||
RaftHandle rh = members[i]; | ||
Util.close(rh.channel()); | ||
} | ||
|
||
for (RaftHandle member : members) { | ||
RaftTestUtils.deleteRaftLog(member.raft()); | ||
} | ||
} | ||
} | ||
|
||
private static final class EmptyStateMachine implements StateMachine { | ||
private static final byte[] RESPONSE = new byte[0]; | ||
|
||
private final int dataSize; | ||
|
||
public EmptyStateMachine(int dataSize) { | ||
this.dataSize = dataSize; | ||
} | ||
|
||
@Override | ||
public byte[] apply(byte[] data, int offset, int length, boolean serialize_response) throws Exception { | ||
if (data.length != dataSize) throw new IllegalArgumentException("Data size does not match"); | ||
|
||
return RESPONSE; | ||
} | ||
|
||
@Override | ||
public void readContentFrom(DataInput in) throws Exception { } | ||
|
||
@Override | ||
public void writeContentTo(DataOutput out) throws Exception { } | ||
} | ||
} |