/**
* Copyright (c) 2015 The original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License 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 org.reveno.atp.performance_tests;
import org.reveno.atp.acceptance.tests.RevenoBaseTest;
import org.reveno.atp.api.ChannelOptions;
import org.reveno.atp.api.Configuration.CpuConsumption;
import org.reveno.atp.api.Configuration.ModelType;
import org.reveno.atp.api.domain.WriteableRepository;
import org.reveno.atp.api.transaction.TransactionInterceptor;
import org.reveno.atp.api.transaction.TransactionStage;
import org.reveno.atp.core.Engine;
import java.text.DecimalFormat;
import java.util.concurrent.ExecutionException;
public class LatencyTest extends RevenoBaseTest {
public static final long TOTAL_TRANSACTIONS = 180_000_000;
public static final long COLD_START_COUNT = 40_000_000;
public static void main(String[] args) throws Exception {
LatencyTest test = new LatencyTest();
test.modelType = ModelType.IMMUTABLE;
if (args[0].equals("low")) {
test.setUp();
test.testLow(args[1]);
test.tearDown();
}
if (args[0].equals("normal")) {
test.setUp();
test.testNormal(args[1]);
test.tearDown();
}
if (args[0].equals("high")) {
test.setUp();
test.testHigh(args[1]);
test.tearDown();
}
if (args[0].equals("phased")) {
test.setUp();
test.testPhased(args[1]);
test.tearDown();
}
}
public void testLow(String type) throws Exception {
log.info("Testing with LOW consumption:");
doTest(CpuConsumption.LOW, type);
}
public void testNormal(String type) throws Exception {
log.info("Testing with NORMAL consumption:");
doTest(CpuConsumption.NORMAL, type);
}
public void testHigh(String type) throws Exception {
log.info("Testing with HIGH consumption:");
doTest(CpuConsumption.HIGH, type);
}
public void testPhased(String type) throws Exception {
log.info("Testing with PHASED consumption:");
doTest(CpuConsumption.PHASED, type);
}
private void doTest(CpuConsumption consumption, String type) throws InterruptedException, ExecutionException {
final long[] counter = { 0L };
final long[] latency = { 0L };
final long[] worstLatency = { 0L };
final long[] worstCount = { 0L };
final long[] bestCount = { 0L };
Engine engine = createEngine(e -> {
e.domain().command(CreateAccount.class, Long.class, (c, ctx) -> {
c.id = ctx.id(Account.class);
ctx.executeTxAction(c);
return c.id;
});
e.domain().transactionAction(CreateAccount.class, (t, ctx) -> {
ctx.repo().store(t.id, new Account(t.id));
});
e.domain().command(AddBalance.class, (t, ctx) -> {
ctx.executeTxAction(t);
});
e.domain().transactionAction(AddBalance.class, (t, ctx) -> {
ctx.repo().store(t.acc, ctx.repo().get(Account.class, t.acc).add(t.amount));
});
//e.config().mutableModel();
//e.config().mutableModelFailover(Configuration.MutableModelFailover.COMPENSATING_ACTIONS);
e.config().mapCapacity(1024);
e.config().cpuConsumption(consumption);
e.config().journaling().volumes(4);
e.config().journaling().minVolumes(0);
e.config().journaling().volumesSize(2 * 1024 * 1024 * 1024L, 1024);
if (type.equals("buffering")) {
e.config().journaling().channelOptions(ChannelOptions.BUFFERING_VM);
} else {
e.config().journaling().channelOptions(ChannelOptions.BUFFERING_MMAP_OS);
}
e.interceptors().add(TransactionStage.TRANSACTION, new TransactionInterceptor() {
@Override
public void destroy() {
}
@Override
public void intercept(long transactionId, long time, long flag, WriteableRepository repository, TransactionStage stage) {
if (++counter[0] > COLD_START_COUNT && transactionId < TOTAL_TRANSACTIONS - 5) {
final long lat = System.nanoTime() - time;
latency[0] += lat;
if (lat > worstLatency[0]) {
worstLatency[0] = lat;
}
if (lat > 3_000_000) {
worstCount[0]++;
}
if (lat <= 1000) {
bestCount[0]++;
}
}
}
});
}, false);
engine.startup();
final long accountId = engine.executeSync(new CreateAccount());
AddBalance cmd = new AddBalance(accountId, 10);
Thread[] ths = new Thread[threadCount()];
for (int a = 0; a < ths.length; a++) {
Thread t1 = new Thread(() -> {
try {
for (long j = 1; j < TOTAL_TRANSACTIONS / 200_000; j++) {
for (long i = 1; i <= 200_000; i++) {
engine.executeCommand(cmd);
}
}
} catch (Throwable t) {
t.printStackTrace();
}
});
t1.start();
ths[a] = t1;
}
for (int a = 0; a < ths.length; a++) {
ths[a].join();
}
engine.executeSync(cmd);
log.info("Avg latency: " + (int)(((double)latency[0] / ((TOTAL_TRANSACTIONS) * threadCount() - COLD_START_COUNT))) + " nanos");
log.info("Worst latency: " + new DecimalFormat("##.###").format(worstLatency[0] / 1_000_000d) + " millis");
log.info("Bad perecent (>3mls): " +
new DecimalFormat("##.########").format((((double)worstCount[0] / (TOTAL_TRANSACTIONS * threadCount() - COLD_START_COUNT)) * 100)) + "%");
log.info("Best perecent (<1mcr): " +
new DecimalFormat("##.########").format((((double)bestCount[0] / (TOTAL_TRANSACTIONS * threadCount() - COLD_START_COUNT)) * 100)) + "%");
engine.shutdown();
}
public static class Account {
public final long id;
public long balance;
public Account add(long amount) {
return new Account(balance + amount);
}
public Account(long id) {
this.id = id;
}
}
public static class CreateAccount {
public long id;
}
public static class AddBalance {
public final long acc;
public final long amount;
public AddBalance(long acc, long amount) {
this.acc = acc;
this.amount = amount;
}
}
private int threadCount() {
return Math.max(1, Runtime.getRuntime().availableProcessors() / 4);
}
}