diff --git a/.github/scripts/benchmark.sh b/.github/scripts/benchmark.sh index 917af516..832c63db 100755 --- a/.github/scripts/benchmark.sh +++ b/.github/scripts/benchmark.sh @@ -110,7 +110,7 @@ BM4b="ArjunaJTA/jta io.narayana.perf.product.GeronimoComparison.* 1" BM4c="ArjunaJTA/jta io.narayana.perf.product.NarayanaComparison.* 1" BM4d="ArjunaJTA/jta io.narayana.perf.product.AtomikosComparison.* 1" #ArjunaJTA/jta/tests/classes/io/narayana/perf/product/ProductComparison.java -BM5="ArjunaJTA/jta com.arjuna.ats.jta.xa.performance.*StoreBenchmark.* 5" +BM5="ArjunaJTA/jta com.arjuna.ats.jta.xa.performance.*StoreBenchmark.* 9" #ArjunaJTA/jta/tests/classes/com/arjuna/ats/jta/xa/performance/HQStoreBenchmark.java #ArjunaJTA/jta/tests/classes/com/arjuna/ats/jta/xa/performance/VolatileStoreBenchmark.java #ArjunaJTA/jta/tests/classes/com/arjuna/ats/jta/xa/performance/ShadowNoFileLockStoreBenchmark.java diff --git a/narayana/ArjunaJTA/jta/etc/jgroups-raft.xml b/narayana/ArjunaJTA/jta/etc/jgroups-raft.xml new file mode 100644 index 00000000..dca1789f --- /dev/null +++ b/narayana/ArjunaJTA/jta/etc/jgroups-raft.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/narayana/ArjunaJTA/jta/etc/jgroups.xml b/narayana/ArjunaJTA/jta/etc/jgroups.xml new file mode 100644 index 00000000..d2edf45c --- /dev/null +++ b/narayana/ArjunaJTA/jta/etc/jgroups.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/narayana/ArjunaJTA/jta/pom.xml b/narayana/ArjunaJTA/jta/pom.xml index 219a3461..8ac45ea7 100644 --- a/narayana/ArjunaJTA/jta/pom.xml +++ b/narayana/ArjunaJTA/jta/pom.xml @@ -43,6 +43,7 @@ 6.0.0 6.0.0 2.1.4 + 1.1.4.Final 1.0.9 1.0.16 @@ -380,6 +381,12 @@ jcommon ${version.jfreecommon} + + org.jgroups + jgroups-raft + ${version.org.jgroups.raft} + true + diff --git a/narayana/ArjunaJTA/jta/tests/classes/com/arjuna/ats/jta/xa/performance/JGroupsRaftClusterStoreBenchmark.java b/narayana/ArjunaJTA/jta/tests/classes/com/arjuna/ats/jta/xa/performance/JGroupsRaftClusterStoreBenchmark.java new file mode 100644 index 00000000..bf9082ed --- /dev/null +++ b/narayana/ArjunaJTA/jta/tests/classes/com/arjuna/ats/jta/xa/performance/JGroupsRaftClusterStoreBenchmark.java @@ -0,0 +1,216 @@ +/* + * Copyright The Narayana Authors + * SPDX short identifier: Apache-2.0 + */ + +package com.arjuna.ats.jta.xa.performance; + +import com.arjuna.ats.internal.arjuna.objectstore.slot.jgroups.JGroupsRaftSlots; +import com.arjuna.ats.internal.arjuna.objectstore.slot.jgroups.JGroupsRaftStoreEnvironmentBean; +import org.jgroups.JChannel; +import org.jgroups.protocols.raft.RAFT; +import org.jgroups.protocols.raft.Role; +import org.jgroups.raft.blocks.ReplicatedStateMachine; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Level; +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.infra.Blackhole; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.openjdk.jmh.runner.options.TimeValue; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Multi-node Raft cluster benchmark measuring the impact of {@code allowDirtyReads}. + *

+ * Creates a 3-node Raft cluster in-process (via SHARED_LOOPBACK), writes pre-populated + * data through the leader, then benchmarks reads from a follower node. With dirty reads + * enabled, reads hit the follower's local in-memory map; with dirty reads disabled, + * reads go through the Raft leader via consensus. + *

+ * The benchmark measures raw slot I/O (slots.read() and slots.write()) with no transaction overhead. + * The *StoreBenchmark classes measure end-to-end transaction throughput through the full JTA stack. + * Consequently one should expect significant performance difference between the benchmarks. + *

+ * Run: {@code java -jar benchmarks.jar "JGroupsRaftClusterStoreBenchmark" -t 10 -f 1 -i 5 -wi 2 -r 3} + */ +@State(Scope.Benchmark) +public class JGroupsRaftClusterStoreBenchmark { + + private static final String STORE_BASE_DIR = System.getProperty("user.dir") + "/target/jgroups-raft-cluster-benchmark"; + private static final String[] NODE_NAMES = {"node1", "node2", "node3"}; + private static final String RAFT_MEMBERS = String.join(",", NODE_NAMES); + private static final int NUM_SLOTS = 256; + private static final int PREPOPULATE_SLOTS = 64; + private static final byte[] SLOT_DATA = "benchmark-data-payload".getBytes(); + + @Param({"true", "false"}) + private boolean allowDirtyReads; + + private JChannel[] channels; + private JGroupsRaftSlots[] allSlots; + private JGroupsRaftSlots leaderSlots; + private JGroupsRaftSlots followerSlots; + + private final AtomicInteger readCounter = new AtomicInteger(0); + private final AtomicInteger writeCounter = new AtomicInteger(0); + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(JGroupsRaftClusterStoreBenchmark.class.getSimpleName()) + .timeUnit(TimeUnit.SECONDS) + .threads(10) + .forks(1) + .mode(Mode.Throughput) + .warmupIterations(2) + .warmupTime(TimeValue.seconds(2)) + .measurementIterations(5) + .measurementTime(TimeValue.seconds(3)) + .shouldDoGC(true) + .build(); + + new Runner(opt).run(); + } + + @Setup(Level.Trial) + @SuppressWarnings("unchecked") + public void setup() throws Exception { + purgeDirectory(new File(STORE_BASE_DIR)); + + String clusterName = "raft-cluster-bm-" + System.currentTimeMillis(); + channels = new JChannel[3]; + allSlots = new JGroupsRaftSlots[3]; + ReplicatedStateMachine[] stateMachines = new ReplicatedStateMachine[3]; + JGroupsRaftStoreEnvironmentBean[] configs = new JGroupsRaftStoreEnvironmentBean[3]; + + // Phase 1: Create and connect all channels before init — Raft needs a quorum + // for leader election, so all nodes must be connected before any can elect. + for (int i = 0; i < 3; i++) { + String storeDir = STORE_BASE_DIR + "/" + NODE_NAMES[i]; + channels[i] = new JChannel("jgroups-raft.xml").name(NODE_NAMES[i]); + + RAFT raft = channels[i].getProtocolStack().findProtocol(RAFT.class); + raft.logDir(storeDir); + raft.logUseFsync(false); + raft.members(Arrays.asList(RAFT_MEMBERS.split(","))); + + stateMachines[i] = new ReplicatedStateMachine<>(channels[i]); + stateMachines[i].raftId(NODE_NAMES[i]); + stateMachines[i].allowDirtyReads(allowDirtyReads); + stateMachines[i].timeout(5000); + + channels[i].connect(clusterName); + } + + // Wait for leader election across the cluster + waitForLeader(channels, 10_000); + + // Phase 2: Create JGroupsRaftSlots with pre-configured channels + for (int i = 0; i < 3; i++) { + configs[i] = new JGroupsRaftStoreEnvironmentBean(); + configs[i].setJGroupsConfigFileName("jgroups-raft.xml"); + configs[i].setNodeAddress(NODE_NAMES[i]); + configs[i].setClusterName(clusterName); + configs[i].setCacheName(clusterName); + configs[i].setStoreDir(STORE_BASE_DIR + "/" + NODE_NAMES[i]); + configs[i].setNumberOfSlots(NUM_SLOTS); + configs[i].setRaftMembers(RAFT_MEMBERS); + configs[i].setRaftLogFsync(false); + configs[i].setRaftTimeout(5000); + configs[i].setAllowDirtyReads(allowDirtyReads); + configs[i].setPreConfiguredChannel(channels[i]); + configs[i].setPreConfiguredStateMachine(stateMachines[i]); + + allSlots[i] = new JGroupsRaftSlots(); + allSlots[i].init(configs[i]); + } + + // Identify leader and a follower + for (JGroupsRaftSlots slots : allSlots) { + String role = slots.getRole(); + if (Role.Leader.name().equals(role) && leaderSlots == null) { + leaderSlots = slots; + } else if (Role.Follower.name().equals(role) && followerSlots == null) { + followerSlots = slots; + } + } + + if (leaderSlots == null || followerSlots == null) { + throw new IllegalStateException("Cluster did not form correctly: leader=" + leaderSlots + " follower=" + followerSlots); + } + + // Pre-populate slots via the leader + for (int i = 0; i < PREPOPULATE_SLOTS; i++) { + leaderSlots.write(i, SLOT_DATA, true); + } + + // Brief pause for replication to followers + TimeUnit.MILLISECONDS.sleep(500); + } + + @TearDown(Level.Trial) + public void tearDown() { + if (channels != null) { + for (JChannel ch : channels) { + if (ch != null) { + try { + ch.close(); + } catch (Exception ignore) { + } + } + } + } + + purgeDirectory(new File(STORE_BASE_DIR)); + } + + @Benchmark + public void readFromFollower(Blackhole bh) throws IOException { + int slotId = readCounter.getAndIncrement() % PREPOPULATE_SLOTS; + bh.consume(followerSlots.read(slotId)); + } + + @Benchmark + public void writeToLeader(Blackhole bh) throws IOException { + int slotId = writeCounter.getAndIncrement() % NUM_SLOTS; + leaderSlots.write(slotId, SLOT_DATA, true); + } + + private static void waitForLeader(JChannel[] channels, long timeoutMs) throws InterruptedException { + long deadline = System.currentTimeMillis() + timeoutMs; + while (System.currentTimeMillis() < deadline) { + for (JChannel ch : channels) { + RAFT raft = ch.getProtocolStack().findProtocol(RAFT.class); + if (raft != null && raft.leader() != null) { + return; + } + } + TimeUnit.MILLISECONDS.sleep(50); + } + throw new IllegalStateException("No Raft leader elected within " + timeoutMs + "ms"); + } + + private static void purgeDirectory(File dir) { + if (dir.exists()) { + File[] files = dir.listFiles(); + if (files != null) { + for (File file : files) { + purgeDirectory(file); + } + } + dir.delete(); + } + } +} diff --git a/narayana/ArjunaJTA/jta/tests/classes/com/arjuna/ats/jta/xa/performance/JGroupsRaftSlotsStoreBenchmark.java b/narayana/ArjunaJTA/jta/tests/classes/com/arjuna/ats/jta/xa/performance/JGroupsRaftSlotsStoreBenchmark.java new file mode 100644 index 00000000..279c59d7 --- /dev/null +++ b/narayana/ArjunaJTA/jta/tests/classes/com/arjuna/ats/jta/xa/performance/JGroupsRaftSlotsStoreBenchmark.java @@ -0,0 +1,171 @@ +/* + * Copyright The Narayana Authors + * SPDX short identifier: Apache-2.0 + */ + +package com.arjuna.ats.jta.xa.performance; + +import com.arjuna.ats.arjuna.common.CoreEnvironmentBeanException; +import com.arjuna.ats.arjuna.objectstore.StoreManager; +import com.arjuna.ats.internal.arjuna.objectstore.slot.SlotStoreAdaptor; +import com.arjuna.ats.internal.arjuna.objectstore.slot.SlotStoreEnvironmentBean; +import com.arjuna.ats.internal.arjuna.objectstore.slot.jgroups.JGroupsRaftSlots; +import com.arjuna.ats.internal.arjuna.objectstore.slot.jgroups.JGroupsRaftStoreEnvironmentBean; +import com.arjuna.common.internal.util.propertyservice.BeanPopulator; +import jakarta.transaction.HeuristicMixedException; +import jakarta.transaction.HeuristicRollbackException; +import jakarta.transaction.NotSupportedException; +import jakarta.transaction.RollbackException; +import jakarta.transaction.SystemException; +import org.junit.BeforeClass; +import org.openjdk.jmh.annotations.AuxCounters; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Mode; +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 org.openjdk.jmh.profile.JavaFlightRecorderProfiler; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.openjdk.jmh.runner.options.TimeValue; + +import java.nio.file.Paths; +import java.util.concurrent.TimeUnit; + +/** + * JMH Benchmark for the JGroupsRaftSlots store (Raft consensus with a persistent write-ahead log). + *

The benchmark uses a single-node Raft cluster for performance testing (in production, Raft requires 3 or more nodes) + *

Run from IDE: Run main() method + *

Run from command line: See performance/README.md e.g. + *

+ *   java -jar narayana/ArjunaJTA/jta/target/benchmarks.jar \
+ *     "JGroupsRaftSlotsStoreBenchmark|JGroupsSlotsStoreBenchmark|HQStoreBenchmark|ShadowNoFileLockStoreBenchmark" \
+ *     -t 10 -f 1 -wi 3 -i 5 -r 10
+ * 
+ *

which produces: + * 3 warmup iterations (lets JIT stabilize), 5 measurement iterations (more data points), 10-second windows (I/O stalls average out). + *

It takes a bit longer (~4 minutes on a typical development machine) but the error margins drop to + * single-digit percentages of the score. + *

The quickest run, for these fsync-heavy benchmarks, that still produces tolerable variance is -wi 2 -i 5 -r 5 + */ +@State(Scope.Benchmark) +public class JGroupsRaftSlotsStoreBenchmark extends JTAStoreBase { + static final int THREADS = 240; + static final String BM_CLASS_NAME = JGroupsRaftSlotsStoreBenchmark.class.getSimpleName(); + + static final int FORKS = 1; + static final int ITERATIONS = 5; + static final int TIME_PER_ITER = 2; + + public static void main(String[] args) throws RunnerException { + // Sometimes it is useful to run the benchmark directly from an IDE: + Options opt = new OptionsBuilder() + .include(BM_CLASS_NAME + ".testJGroupsRaftSlotsStore") + .timeUnit(TimeUnit.SECONDS) + .threads(THREADS) + .forks(FORKS) + .mode(Mode.Throughput) + .warmupIterations(1) + .warmupTime(TimeValue.seconds(1)) + .measurementIterations(ITERATIONS) + .measurementTime(TimeValue.seconds(TIME_PER_ITER)) + .shouldDoGC(true) + // use JFR as the profiler, the recording will appear in the User working directory with the + // name "-xxx/profile.jfr", which you can change to "wherever" using + // addProfiler(JavaFlightRecorderProfiler.class, "dir=wherever"). Java Flight Recorder data files + // can be viewed with the jmc graphical tool or with the jfr command line tool which is in the java + // bin directory + .addProfiler(JavaFlightRecorderProfiler.class) + .jvmArgs("-Djmh.executor=FJP") // ForkJoinPool + // to debug the forks use "-agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=y" + .build(); + + new Runner(opt).run(); + } + + @Setup(Level.Trial) + @BeforeClass + public static void setup() throws CoreEnvironmentBeanException { + JTAStoreBase.setup(SlotStoreAdaptor.class.getName()); + JGroupsRaftStoreEnvironmentBean configBean = BeanPopulator.getDefaultInstance(JGroupsRaftStoreEnvironmentBean.class); + SlotStoreEnvironmentBean slotStoreBean = BeanPopulator.getDefaultInstance(SlotStoreEnvironmentBean.class); + slotStoreBean.setBackingSlotsClassName(JGroupsRaftSlots.class.getName()); + int threadCount = getThreadCountFromProperties(THREADS); + // Raft serializes all writes through a single consensus thread, so very high thread + // counts produce slot exhaustion rather than meaningful throughput data. + // Cap the slot allocation to THREADS to keep the benchmark stable at all -t (no of worker threads) values. + int effectiveThreadCount = Math.min(threadCount, THREADS); + + // JGroups configuration + configBean.setJGroupsConfigFileName("jgroups-raft.xml"); + configBean.setNodeAddress("raft-benchmark-node"); // TODO configure 3 node cluster + configBean.setClusterName("jgroups-raft-benchmark-" + System.currentTimeMillis()); + + // Note: Using default key generator (Uid-based) for single-node benchmark + + // Raft configuration (single-node cluster for benchmark) + configBean.setRaftMembers("raft-benchmark-node"); // Single node + configBean.setRaftLogFsync(true); // durable writes for fault tolerance + configBean.setAllowDirtyReads(false); + + configBean.setRecycleFailedSlots(true); // otherwise failed slot writes will lead to slot exhaustion + + // SlotStoreAdaptor creates the SlotStore using the base SlotStoreEnvironmentBean, + // so numberOfSlots must be set there (not just on the JGroups config bean) + int numberOfSlots = roundUp(256, effectiveThreadCount); + slotStoreBean.setNumberOfSlots(numberOfSlots); + configBean.setNumberOfSlots(numberOfSlots); + configBean.setBackingSlotsClassName(JGroupsRaftSlots.class.getName()); + + cleanStore(Paths.get(configBean.getStoreDir()).toFile()); + } + + @TearDown(Level.Trial) + public static void tearDown() { + StoreManager.shutdown(); + JGroupsRaftStoreEnvironmentBean configBean = BeanPopulator.getDefaultInstance(JGroupsRaftStoreEnvironmentBean.class); + cleanStore(Paths.get(configBean.getStoreDir()).toFile()); + } + + @AuxCounters(AuxCounters.Type.OPERATIONS) + @State(Scope.Thread) + /* + TxnCounters is a JMH @AuxCounters class that breaks the benchmark's throughput into two separate metrics in the JMH output: + - committed — incremented when jtaTest() succeeds (transaction committed) + - rolledBack — incremented when jtaTest() throws RollbackException (slot exhaustion under Raft contention) + + JMH treats both fields as operation counts and reports them as separate ops/sec columns alongside the main throughput metric. + This lets you see at a glance how much of the reported throughput is real committed work versus failed transactions + (important for Raft at high thread counts where many transactions roll back due to slot exhaustion). + + An example from a run output with "-t 10 -f 2 -wi 3 -i 10 -r 10" shows no rolledBack counts + (the Cnt column is the number of data points, forks (-f) x measurement iterations (-i)): + + Benchmark Mode Cnt Score Error Units + JGroupsRaftSlotsStoreBenchmark.testJGroupsRaftSlotsStore thrpt 20 1780.754 ± 276.787 ops/s + JGroupsRaftSlotsStoreBenchmark.testJGroupsRaftSlotsStore:committed thrpt 20 1782.559 ± 276.783 ops/s + JGroupsRaftSlotsStoreBenchmark.testJGroupsRaftSlotsStore:rolledBack thrpt 20 ≈ 0 ops/s + + Without @AuxCounters, catching RollbackException silently would inflate the throughput number (JMH counts every benchmark method invocation as one operation, whether it committed or not). With them, you get + three columns: total ops/sec, committed ops/sec, and rolledBack ops/sec. + */ + public static class TxnCounters { + public long committed; + public long rolledBack; + } + + @Benchmark + public void testJGroupsRaftSlotsStore(Blackhole bh, TxnCounters counters) throws HeuristicRollbackException, SystemException, HeuristicMixedException, NotSupportedException { + try { + bh.consume(super.jtaTest()); + counters.committed++; + } catch (RollbackException e) { + counters.rolledBack++; + } + } +} diff --git a/narayana/ArjunaJTA/jta/tests/classes/com/arjuna/ats/jta/xa/performance/JGroupsSlotsStoreBenchmark.java b/narayana/ArjunaJTA/jta/tests/classes/com/arjuna/ats/jta/xa/performance/JGroupsSlotsStoreBenchmark.java new file mode 100644 index 00000000..b6f7a359 --- /dev/null +++ b/narayana/ArjunaJTA/jta/tests/classes/com/arjuna/ats/jta/xa/performance/JGroupsSlotsStoreBenchmark.java @@ -0,0 +1,128 @@ +/* + * Copyright The Narayana Authors + * SPDX short identifier: Apache-2.0 + */ + +package com.arjuna.ats.jta.xa.performance; + +import com.arjuna.ats.arjuna.common.CoreEnvironmentBeanException; +import com.arjuna.ats.arjuna.objectstore.StoreManager; +import com.arjuna.ats.internal.arjuna.objectstore.slot.SlotStoreAdaptor; +import com.arjuna.ats.internal.arjuna.objectstore.slot.SlotStoreEnvironmentBean; +import com.arjuna.ats.internal.arjuna.objectstore.slot.jgroups.JGroupsSlots; +import com.arjuna.ats.internal.arjuna.objectstore.slot.jgroups.JGroupsStoreEnvironmentBean; +import com.arjuna.common.internal.util.propertyservice.BeanPopulator; +import jakarta.transaction.HeuristicMixedException; +import jakarta.transaction.HeuristicRollbackException; +import jakarta.transaction.NotSupportedException; +import jakarta.transaction.RollbackException; +import jakarta.transaction.SystemException; +import org.junit.BeforeClass; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Mode; +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 org.openjdk.jmh.profile.JavaFlightRecorderProfiler; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.openjdk.jmh.runner.options.TimeValue; + +import java.nio.file.Paths; +import java.util.concurrent.TimeUnit; + +/** + * JMH Benchmark for the JGroupsSlots store (with a persistent write-ahead log). + *

The benchmark uses a single-node cluster for performance testing + *

Run from IDE: Run main() method + *

Run from command line: See performance/README.md + */ +@State(Scope.Benchmark) +public class JGroupsSlotsStoreBenchmark extends JTAStoreBase { + static final int THREADS = 240; + static final String BM_CLASS_NAME = JGroupsSlotsStoreBenchmark.class.getSimpleName(); + + static final int FORKS = 1; + static final int ITERATIONS = 5; + static final int TIME_PER_ITER = 2; + + public static void main(String[] args) throws RunnerException { + // Sometimes it is useful to run the benchmark directly from an IDE: + Options opt = new OptionsBuilder() + .include(BM_CLASS_NAME + ".testJGroupsSlotsStore") + .timeUnit(TimeUnit.SECONDS) + .threads(THREADS) + .forks(FORKS) + .mode(Mode.Throughput) + .warmupIterations(1) + .warmupTime(TimeValue.seconds(1)) + .measurementIterations(ITERATIONS) + .measurementTime(TimeValue.seconds(TIME_PER_ITER)) + .shouldDoGC(true) + // use JFR as the profiler, the recording will appear in the User working directory with the + // name "-xxx/profile.jfr", which you can change to "wherever" using + // addProfiler(JavaFlightRecorderProfiler.class, "dir=wherever"). Java Flight Recorder data files + // can be viewed with the jmc graphical tool or with the jfr command line tool which is in the java + // bin directory + .addProfiler(JavaFlightRecorderProfiler.class) + .jvmArgs("-Djmh.executor=FJP") // ForkJoinPool + // to debug the forks use "-agentlib:jdwp=transport=dt_socket,address=5005,server=y,suspend=y" + .build(); + + new Runner(opt).run(); + } + + @Setup(Level.Trial) + @BeforeClass + public static void setup() throws CoreEnvironmentBeanException { + JTAStoreBase.setup(SlotStoreAdaptor.class.getName()); + JGroupsStoreEnvironmentBean configBean = BeanPopulator.getDefaultInstance(JGroupsStoreEnvironmentBean.class); + SlotStoreEnvironmentBean slotStoreBean = BeanPopulator.getDefaultInstance(SlotStoreEnvironmentBean.class); + slotStoreBean.setBackingSlotsClassName(JGroupsSlots.class.getName()); + int threadCount = getThreadCountFromProperties(THREADS); + + // JGroups configuration + configBean.setJGroupsConfigFileName("jgroups.xml"); + configBean.setNodeAddress("benchmark-node"); + configBean.setClusterName("jgroups-slots-benchmark-" + System.currentTimeMillis()); + configBean.setReplicationCount((short) -1); // Full replication + + // write-ahead log configuration + configBean.setWalEnabled(true); // set to true for durability of transaction logs + configBean.setWalSyncWrites(true); // set to true for maximum durability + configBean.setWalSyncDeletes(false); + + configBean.setRecycleFailedSlots(true); // otherwise failed slot writes will lead to slot exhaustion + + configBean.setWalAsyncIO(false); // requires libaio to be on the LD_LIBRARY_PATH + + // L2 cache configuration (disabled for consistency) + configBean.setCachingTime(0L); + + // SlotStoreAdaptor creates the SlotStore using the base SlotStoreEnvironmentBean, + // so numberOfSlots must be set there (not just on the JGroups config bean) + int numberOfSlots = roundUp(256, threadCount); + slotStoreBean.setNumberOfSlots(numberOfSlots); + configBean.setNumberOfSlots(numberOfSlots); + configBean.setBackingSlotsClassName(JGroupsSlots.class.getName()); + + cleanStore(Paths.get(configBean.getStoreDir()).toFile()); + } + + @TearDown(Level.Trial) + public static void tearDown() { + StoreManager.shutdown(); + JGroupsStoreEnvironmentBean configBean = BeanPopulator.getDefaultInstance(JGroupsStoreEnvironmentBean.class); + cleanStore(Paths.get(configBean.getStoreDir()).toFile()); + } + + @Benchmark + public void testJGroupsSlotsStore(Blackhole bh) throws HeuristicRollbackException, SystemException, HeuristicMixedException, NotSupportedException, RollbackException { + bh.consume(super.jtaTest()); + } +} diff --git a/narayana/ArjunaJTA/jta/tests/classes/com/arjuna/ats/jta/xa/performance/JTAStoreBase.java b/narayana/ArjunaJTA/jta/tests/classes/com/arjuna/ats/jta/xa/performance/JTAStoreBase.java index d8cf6d92..ea5eefd4 100644 --- a/narayana/ArjunaJTA/jta/tests/classes/com/arjuna/ats/jta/xa/performance/JTAStoreBase.java +++ b/narayana/ArjunaJTA/jta/tests/classes/com/arjuna/ats/jta/xa/performance/JTAStoreBase.java @@ -83,11 +83,11 @@ protected static boolean purgeFiles(File storeDir) { protected static int roundUp(int increment, int value) { int res = value % increment; - // round up to nearest multiple of increment (eg a h/w page size) + // round up to nearest multiple of increment with one page of headroom if (res == 0) { - return value; + return value + increment; } else { - return value + increment - res; + return ((value + increment) - res) + increment; } } @@ -101,18 +101,19 @@ protected static void setup(String storeType) throws CoreEnvironmentBeanExceptio } public boolean jtaTest() throws HeuristicRollbackException, SystemException, HeuristicMixedException, NotSupportedException, RollbackException { + tm.begin(); try { - tm.begin(); - tm.getTransaction().enlistResource(resource1); tm.getTransaction().enlistResource(resource2); - tm.commit(); - } catch(Exception e) { - log.fatal("JTAStoreTests#jtaTest%n", e); - throw new Error(e); + } catch (Exception e) { + try { + tm.rollback(); + } catch (Exception suppressed) { + e.addSuppressed(suppressed); + } + throw e; } - return true; } }