/*
* 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.performance.tests.network;
import net.openhft.chronicle.bytes.Bytes;
import net.openhft.chronicle.core.threads.EventLoop;
import net.openhft.chronicle.core.threads.ThreadDump;
import net.openhft.chronicle.network.*;
import net.openhft.chronicle.network.connection.TcpChannelHub;
import net.openhft.chronicle.threads.EventGroup;
import net.openhft.chronicle.wire.*;
import org.jetbrains.annotations.NotNull;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.Collection;
import java.util.function.Function;
import static net.openhft.performance.tests.network.LegacyHanderFactory.simpleTcpEventHandlerFactory;
/*
Running on an i7-3970X
TextWire: Loop back echo latency was 7.4/8.9 12/20 108/925 us for 50/90 99/99.9 99.99/worst %tile
BinaryWire: Loop back echo latency was 6.6/8.0 9/11 19/3056 us for 50/90 99/99.9 99.99/worst %tile
RawWire: Loop back echo latency was 5.9/6.8 8/10 12/80 us for 50/90 99/99.9 99.99/worst %tile
*/
@RunWith(value = Parameterized.class)
public class WireTcpHandlerTest {
public static final int SIZE_OF_SIZE = 4;
private final String desc;
private final WireType wireType;
private ThreadDump threadDump;
public WireTcpHandlerTest(String desc, WireType wireWrapper) {
this.desc = desc;
this.wireType = wireWrapper;
}
@Parameterized.Parameters
public static Collection<Object[]> combinations() {
return Arrays.asList(
new Object[]{"TextWire", WireType.TEXT},
new Object[]{"BinaryWire", WireType.BINARY}
//,
// new Object[]{"RawWire", WireType.RAW}
);
}
private static void testLatency(String desc, @NotNull Function<Bytes, Wire> wireWrapper, @NotNull SocketChannel... sockets) throws IOException {
// System.out.println("Starting latency test");
int tests = 40000;
@NotNull long[] times = new long[tests * sockets.length];
int count = 0;
ByteBuffer out = ByteBuffer.allocateDirect(64 * 1024);
Bytes outBytes = Bytes.wrapForWrite(out);
Wire outWire = wireWrapper.apply(outBytes);
ByteBuffer in = ByteBuffer.allocateDirect(64 * 1024);
Bytes inBytes = Bytes.wrapForRead(in);
Wire inWire = wireWrapper.apply(inBytes);
@NotNull TestData td = new TestData();
@NotNull TestData td2 = new TestData();
for (int i = -12000; i < tests; i++) {
long now = System.nanoTime();
for (@NotNull SocketChannel socket : sockets) {
out.clear();
outBytes.clear();
td.value3 = td.value2 = td.value1 = i;
td.write(outWire);
out.limit((int) outBytes.writePosition());
socket.write(out);
if (out.remaining() > 0)
throw new AssertionError("Unable to write in one go.");
}
for (@NotNull SocketChannel socket : sockets) {
in.clear();
inBytes.clear();
while (true) {
int read = socket.read(in);
inBytes.readLimit(in.position());
if (inBytes.readRemaining() >= SIZE_OF_SIZE) {
int header = inBytes.readInt(0);
final int len = Wires.lengthOf(header);
if (inBytes.readRemaining() >= len) {
td2.read(inWire);
}
break;
}
if (read < 0)
throw new AssertionError("Unable to read in one go.");
}
if (i >= 0)
times[count++] = System.nanoTime() - now;
}
}
Arrays.sort(times);
System.out.printf("%s: Loop back echo latency was %.1f/%.1f %,d/%,d %,d/%d us for 50/90 99/99.9 99.99/worst %%tile%n",
desc,
times[times.length / 2] / 1e3,
times[times.length * 9 / 10] / 1e3,
times[times.length - times.length / 100] / 1000,
times[times.length - times.length / 1000] / 1000,
times[times.length - times.length / 10000] / 1000,
times[times.length - 1] / 1000
);
}
@Before
public void threadDump() {
threadDump = new ThreadDump();
}
@After
public void checkThreadDump() {
threadDump.assertNoNewThreads();
}
@Ignore("todo fix")
@Test
public void testProcess() throws IOException {
@NotNull EventLoop eg = new EventGroup(true);
eg.start();
TCPRegistry.createServerSocketChannelFor(desc);
@NotNull AcceptorEventHandler eah = new AcceptorEventHandler(desc,
simpleTcpEventHandlerFactory(EchoRequestHandler::new, wireType),
VanillaNetworkContext::new);
eg.addHandler(eah);
SocketChannel sc = TCPRegistry.createSocketChannel(desc);
sc.configureBlocking(false);
// testThroughput(sc);
testLatency(desc, wireType, sc);
eg.stop();
TcpChannelHub.closeAllHubs();
TCPRegistry.reset();
}
static class EchoRequestHandler extends WireTcpHandler {
private final TestData td = new TestData();
public EchoRequestHandler(NetworkContext networkContext) {
}
@Override
protected void onRead(@NotNull DocumentContext in, @NotNull WireOut out) {
td.read(in.wire());
td.write(outWire);
}
@Override
protected void onInitialize() {
throw new UnsupportedOperationException("todo");
}
}
}