/*
* Copyright 2016 higherfrequencytrading.com
*
* 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 net.openhft.chronicle.engine;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.OS;
import net.openhft.chronicle.core.onoes.ExceptionKey;
import net.openhft.chronicle.core.pool.ClassAliasPool;
import net.openhft.chronicle.core.threads.ThreadDump;
import net.openhft.chronicle.engine.api.EngineReplication;
import net.openhft.chronicle.engine.api.map.KeyValueStore;
import net.openhft.chronicle.engine.api.map.MapView;
import net.openhft.chronicle.engine.api.tree.Asset;
import net.openhft.chronicle.engine.api.tree.AssetTree;
import net.openhft.chronicle.engine.fs.*;
import net.openhft.chronicle.engine.map.CMap2EngineReplicator;
import net.openhft.chronicle.engine.map.ChronicleMapKeyValueStore;
import net.openhft.chronicle.engine.map.VanillaMapView;
import net.openhft.chronicle.engine.server.ServerEndpoint;
import net.openhft.chronicle.engine.tree.VanillaAssetTree;
import net.openhft.chronicle.network.TCPRegistry;
import net.openhft.chronicle.wire.WireType;
import net.openhft.chronicle.wire.YamlLogging;
import org.jetbrains.annotations.NotNull;
import org.junit.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* Created by Rob Austin
*/
public class RoundTripTest {
public static final WireType WIRE_TYPE = WireType.BINARY;
public static final int ENTRIES = 200;
public static final int TIMES = 10000;
public static final String basePath = OS.TARGET + '/' + System.getProperty("server", "one");
public static final String CLUSTER = System.getProperty("cluster", "clusterFive");
public static final String SIMPLE_NAME = "/ChMaps/test1";
public static final String NAME = SIMPLE_NAME +
"?putReturnsNull=true";
static final int VALUE_SIZE = 2 << 20;
public static ServerEndpoint serverEndpoint;
static int counter = 0;
@NotNull
private static String CONNECTION_1 = "CONNECTION_1";
@NotNull
private static String CONNECTION_2 = "CONNECTION_2";
@NotNull
private static String CONNECTION_3 = "CONNECTION_3";
private ThreadDump threadDump;
private Map<ExceptionKey, Integer> exceptions;
@NotNull
static AssetTree create(final int hostId, WireType writeType, @NotNull final List<EngineHostDetails> hostDetails) {
@NotNull AssetTree tree = new VanillaAssetTree((byte) hostId)
.forTesting();
@NotNull Map<String, EngineHostDetails> hostDetailsMap = new ConcurrentSkipListMap<>();
for (@NotNull EngineHostDetails hd : hostDetails) {
hostDetailsMap.put(hd.toString(), hd);
}
@NotNull final Clusters testCluster = new Clusters(Collections.singletonMap("test",
new EngineCluster("test")));
tree.root().addWrappingRule(MapView.class, "map directly to KeyValueStore",
VanillaMapView::new,
KeyValueStore.class);
tree.root().addLeafRule(EngineReplication.class, "Engine replication holder",
CMap2EngineReplicator::new);
tree.root().addLeafRule(KeyValueStore.class, "KVS is Chronicle Map", (context, asset) ->
new ChronicleMapKeyValueStore(context.wireType(writeType)
.cluster("test")
.entries(ENTRIES)
.averageValueSize(VALUE_SIZE),
asset));
@NotNull Asset asset1 = tree.acquireAsset(SIMPLE_NAME);
asset1.addView(Clusters.class, testCluster);
// VanillaAssetTreeEgMain.registerTextViewofTree("host " + hostId, tree);
return tree;
}
@NotNull
public static String getKey(int i) {
return "" + i;
}
@NotNull
public static String generateValue(char c, int size) {
@NotNull char[] chars = new char[size - 7];
Arrays.fill(chars, c);
return counter++ + " " + new String(chars);
}
@Before
public void threadDump() {
threadDump = new ThreadDump();
}
@After
public void checkThreadDump() {
threadDump.assertNoNewThreads();
}
@Before
public void recordException() {
exceptions = Jvm.recordExceptions();
}
private void checkForThrowablesInOtherThreads() {
if (!exceptions.isEmpty()) {
Jvm.dumpException(exceptions);
Jvm.resetExceptionHandlers();
Assert.fail();
}
}
@After
public void afterMethod() {
checkForThrowablesInOtherThreads();
}
@Test
@Ignore("Long running")
public void test() throws IOException, InterruptedException {
System.out.println("Using cluster " + CLUSTER + " basePath: " + basePath);
YamlLogging.setAll(false);
TCPRegistry.createServerSocketChannelFor(CONNECTION_1);
TCPRegistry.createServerSocketChannelFor(CONNECTION_2);
TCPRegistry.createServerSocketChannelFor(CONNECTION_3);
@NotNull final List<EngineHostDetails> hostDetails = new ArrayList<>();
hostDetails.add(new EngineHostDetails(1, 8 << 20, CONNECTION_1));
hostDetails.add(new EngineHostDetails(2, 8 << 20, CONNECTION_2));
hostDetails.add(new EngineHostDetails(3, 8 << 20, CONNECTION_3));
@NotNull AssetTree serverAssetTree1 = create(1, WireType.BINARY, hostDetails);
@NotNull AssetTree serverAssetTree2 = create(2, WireType.BINARY, hostDetails);
@NotNull AssetTree serverAssetTree3 = create(3, WireType.BINARY, hostDetails);
@NotNull ServerEndpoint serverEndpoint1 = new ServerEndpoint(CONNECTION_1, serverAssetTree1);
@NotNull ServerEndpoint serverEndpoint2 = new ServerEndpoint(CONNECTION_2, serverAssetTree2);
@NotNull ServerEndpoint serverEndpoint3 = new ServerEndpoint(CONNECTION_3, serverAssetTree3);
ClassAliasPool.CLASS_ALIASES.addAlias(ChronicleMapGroupFS.class);
ClassAliasPool.CLASS_ALIASES.addAlias(FilePerKeyGroupFS.class);
//Delete any files from the last run
Files.deleteIfExists(Paths.get(basePath, "test"));
try {
// configure them
serverAssetTree1.acquireMap(NAME, String.class, String.class).size();
serverAssetTree2.acquireMap(NAME, String.class, String.class).size();
serverAssetTree3.acquireMap(NAME, String.class, String.class).size();
@NotNull final ConcurrentMap<CharSequence, CharSequence> map1 = serverAssetTree1.acquireMap(NAME, CharSequence.class, CharSequence.class);
@NotNull final ConcurrentMap<CharSequence, CharSequence> map2 = serverAssetTree2.acquireMap(NAME, CharSequence.class, CharSequence.class);
@NotNull final ConcurrentMap<CharSequence, CharSequence> map3 = serverAssetTree3.acquireMap(NAME, CharSequence.class, CharSequence.class);
map1.size();
map2.size();
map3.size();
ClassAliasPool.CLASS_ALIASES.addAlias(ChronicleMapGroupFS.class);
ClassAliasPool.CLASS_ALIASES.addAlias(FilePerKeyGroupFS.class);
@NotNull CountDownLatch l = new CountDownLatch(ENTRIES * TIMES);
@NotNull VanillaAssetTree treeC1 = new VanillaAssetTree("tree1")
.forRemoteAccess(CONNECTION_1, WIRE_TYPE);
@NotNull VanillaAssetTree treeC3 = new VanillaAssetTree("tree1")
.forRemoteAccess(CONNECTION_3, WIRE_TYPE);
@NotNull AtomicReference<CountDownLatch> latchRef = new AtomicReference<>();
treeC3.registerSubscriber(NAME, String.class, z -> {
latchRef.get().countDown();
});
@NotNull final ConcurrentMap<CharSequence, CharSequence> mapC1 = treeC1.acquireMap(NAME, CharSequence.class, CharSequence
.class);
long start = System.currentTimeMillis();
long min = Long.MAX_VALUE;
long max = Long.MIN_VALUE;
@NotNull String[] keys = new String[ENTRIES];
@NotNull String[] values0 = new String[ENTRIES];
@NotNull String[] values1 = new String[ENTRIES];
for (int i = 0; i < ENTRIES; i++) {
keys[i] = getKey(i);
values0[i] = generateValue('X', VALUE_SIZE);
values1[i] = generateValue('-', VALUE_SIZE);
}
for (int j = 0; j < TIMES; j++) {
long timeTakenI = System.currentTimeMillis();
latchRef.set(new CountDownLatch(ENTRIES));
@NotNull String[] values = j % 2 == 0 ? values0 : values1;
for (int i = 0; i < ENTRIES; i++) {
mapC1.put(keys[i], values[i]);
}
while (!latchRef.get().await(1, TimeUnit.MILLISECONDS)) {
checkForThrowablesInOtherThreads();
}
final long timeTakenIteration = System.currentTimeMillis() -
timeTakenI;
max = Math.max(max, timeTakenIteration);
min = Math.min(min, timeTakenIteration);
System.out.println(" - round trip latency=" + timeTakenIteration + "ms");
// Jvm.pause(10);
}
long timeTaken = System.currentTimeMillis() - start;
final double target = 1000 * TIMES;
System.out.println("TOTAL round trip latency=" + timeTaken + "ms (target = " +
(int) target + "ms) " +
"for " + TIMES + " times. Arg=" + (timeTaken / TIMES) + ", max=" + max + ", " +
"min=" + min);
Assert.assertTrue(timeTaken <= target);
} finally {
serverEndpoint1.close();
serverEndpoint2.close();
serverEndpoint3.close();
}
}
}