/*- * #%L * RPC Benchmark: Aeron with SBE * %% * Copyright (C) 2016 - 2020 Acegi Technology Pty Limited * %% * 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. * #L% */ package au.com.acegi.rpcbench.aeron; import static au.com.acegi.rpcbench.aeron.codecs.MessageHeaderEncoder.ENCODED_LENGTH; import static java.lang.System.nanoTime; import static java.lang.System.setProperty; import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.locks.LockSupport.parkNanos; import static org.agrona.BitUtil.CACHE_LINE_LENGTH; import static org.agrona.BufferUtil.allocateDirectAligned; import static org.agrona.concurrent.UnsafeBuffer.DISABLE_BOUNDS_CHECKS_PROP_NAME; import java.io.File; import java.io.FileNotFoundException; import java.io.PrintStream; import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicLong; import au.com.acegi.rpcbench.aeron.codecs.MessageHeaderDecoder; import au.com.acegi.rpcbench.aeron.codecs.MessageHeaderEncoder; import au.com.acegi.rpcbench.aeron.codecs.PingEncoder; import au.com.acegi.rpcbench.aeron.codecs.PongDecoder; import au.com.acegi.rpcbench.aeron.codecs.PriceDecoder; import au.com.acegi.rpcbench.aeron.codecs.SizeEncoder; import io.aeron.Aeron; import io.aeron.FragmentAssembler; import io.aeron.Image; import io.aeron.Publication; import io.aeron.Subscription; import io.aeron.driver.MediaDriver; import io.aeron.logbuffer.FragmentHandler; import io.aeron.logbuffer.Header; import org.HdrHistogram.Histogram; import org.agrona.CloseHelper; import org.agrona.DirectBuffer; import org.agrona.concurrent.BusySpinIdleStrategy; import org.agrona.concurrent.IdleStrategy; import org.agrona.concurrent.UnsafeBuffer; @SuppressWarnings("checkstyle:JavadocType") public final class BenchClient { private static final UnsafeBuffer BUFFER; private static final boolean EMBEDDED_MEDIA_DRIVER = false; private static final int FRAGMENT_LIMIT = 256; private static final MessageHeaderDecoder HDR_D; private static final MessageHeaderEncoder HDR_E; private static final Histogram HISTOGRAM; private static final int HISTOGRAM_MAX_VAL_SEC = 60; private static final IdleStrategy IDLE = new BusySpinIdleStrategy(); private static final CountDownLatch LATCH = new CountDownLatch(1); private static final AtomicLong PENDING = new AtomicLong(); private static final long PINGS_PER_SECOND = 10_000; private static final PingEncoder PING_E; private static final int PING_LEN; private static final PongDecoder POND_D; private static final PriceDecoder PRICE_D; private static final int PRICE_LEN; private static final String REP_CHAN = Configuration.REP_CHANNEL; private static final int REP_STREAM_ID = Configuration.REP_STREAM_ID; private static final String REQ_CHAN = Configuration.REQ_CHANNEL; private static final int REQ_STREAM_ID = Configuration.REQ_STREAM_ID; private static final long SCHEDULE_INTERVAL_NS; private static final SizeEncoder SIZE_E; private final Aeron aeron; private final Aeron.Context ctx; private final MediaDriver driver; private final FragmentHandler fragmentHandler; private final Publication publication; private final Subscription subscription; static { setProperty(DISABLE_BOUNDS_CHECKS_PROP_NAME, "true"); SCHEDULE_INTERVAL_NS = SECONDS.toNanos(1) / PINGS_PER_SECOND; PING_LEN = ENCODED_LENGTH + PingEncoder.BLOCK_LENGTH; PRICE_LEN = ENCODED_LENGTH + SizeEncoder.BLOCK_LENGTH; final ByteBuffer bb = allocateDirectAligned(PRICE_LEN, CACHE_LINE_LENGTH); BUFFER = new UnsafeBuffer(bb); HISTOGRAM = new Histogram(SECONDS.toNanos(HISTOGRAM_MAX_VAL_SEC), 3); HDR_D = new MessageHeaderDecoder(); HDR_E = new MessageHeaderEncoder(); PING_E = new PingEncoder(); POND_D = new PongDecoder(); PRICE_D = new PriceDecoder(); SIZE_E = new SizeEncoder(); HDR_E.wrap(BUFFER, 0); PING_E.wrap(BUFFER, ENCODED_LENGTH); SIZE_E.wrap(BUFFER, ENCODED_LENGTH); HDR_E.schemaId(PingEncoder.SCHEMA_ID); HDR_E.version(PingEncoder.SCHEMA_VERSION); } @SuppressWarnings("PMD.NullAssignment") public BenchClient() { driver = EMBEDDED_MEDIA_DRIVER ? MediaDriver.launchEmbedded() : null; ctx = new Aeron.Context().availableImageHandler(this::imageHandler); if (EMBEDDED_MEDIA_DRIVER) { ctx.aeronDirectoryName(driver.aeronDirectoryName()); } fragmentHandler = new FragmentAssembler(this::onMessage); aeron = Aeron.connect(ctx); publication = aeron.addPublication(REQ_CHAN, REQ_STREAM_ID); subscription = aeron.addSubscription(REP_CHAN, REP_STREAM_ID); } @SuppressWarnings("checkstyle:UncommentedMain") public static void main(final String... args) throws InterruptedException, FileNotFoundException { final BenchClient client = new BenchClient(); client.execute(); } private static void writeResults(final String filename) throws FileNotFoundException { final File file = new File(filename); try (PrintStream ps = new PrintStream(file)) { HISTOGRAM.outputPercentileDistribution(ps, 5, 1000.0); } } public void shutdown() throws InterruptedException { CloseHelper.quietClose(subscription); CloseHelper.quietClose(publication); CloseHelper.quietClose(aeron); CloseHelper.quietClose(ctx); CloseHelper.quietClose(driver); } private void execute() throws InterruptedException, FileNotFoundException { try { if (!LATCH.await(5, SECONDS)) { throw new IllegalStateException("Couldn't connect to server"); } pingPong(100_000); // warm up parkNanos(SECONDS.toNanos(5)); HISTOGRAM.reset(); pingPong(1_000_000); writeResults("aeron-ping-pong-1M.txt"); priceStream(100_000); // warm up HISTOGRAM.reset(); parkNanos(SECONDS.toNanos(5)); priceStream(100_000_000); writeResults("aeron-price-stream-100M.txt"); } finally { shutdown(); } } private void imageHandler(final Image image) { final Subscription sub = image.subscription(); if (REP_STREAM_ID == sub.streamId() && REP_CHAN.equals(sub.channel())) { LATCH.countDown(); } } @SuppressWarnings("PMD.UnusedFormalParameter") private void onMessage(final DirectBuffer buffer, final int offset, final int length, final Header header) { final int msgOffset = MessageHeaderDecoder.ENCODED_LENGTH + offset; HDR_D.wrap(buffer, offset); switch (HDR_D.templateId()) { case PongDecoder.TEMPLATE_ID: onPong(buffer, msgOffset, HDR_D.blockLength(), HDR_D.version()); break; case PriceDecoder.TEMPLATE_ID: onPrice(buffer, msgOffset, HDR_D.blockLength(), HDR_D.version()); break; default: throw new IllegalStateException("Unknown message template"); } } private void onPong(final DirectBuffer buffer, final int offset, final int actingBlockLength, final int actingVersion) { POND_D.wrap(buffer, offset, actingBlockLength, actingVersion); final long rtt = nanoTime() - POND_D.timestamp(); HISTOGRAM.recordValue(rtt); PENDING.decrementAndGet(); } private void onPrice(final DirectBuffer buffer, final int offset, final int actingBlockLength, final int actingVersion) { PRICE_D.wrap(buffer, offset, actingBlockLength, actingVersion); if (PRICE_D.iid() % 100_000 == 0) { final long rtt = nanoTime() - PRICE_D.tod(); HISTOGRAM.recordValue(rtt); } PENDING.decrementAndGet(); } @SuppressWarnings("PMD.DoNotUseThreads") private void pingPong(final int messages) throws InterruptedException { HDR_E.blockLength(PingEncoder.BLOCK_LENGTH); HDR_E.templateId(PingEncoder.TEMPLATE_ID); PENDING.set(messages); final Thread pongThread = new Thread(new PongReceiver()); pongThread.start(); parkNanos(SECONDS.toNanos(1)); final long start = nanoTime(); long nextSendAt = start + SCHEDULE_INTERVAL_NS; for (int i = 0; i < messages; i++) { while (nanoTime() < nextSendAt) { // busy spin IDLE.idle(); } PING_E.timestamp(nextSendAt); nextSendAt += SCHEDULE_INTERVAL_NS; do { // busy spin IDLE.idle(); } while (publication.offer(BUFFER, 0, PING_LEN) < 0L); } pongThread.join(); } private void priceStream(final int messages) throws InterruptedException { PENDING.set(messages); HDR_E.blockLength(SizeEncoder.BLOCK_LENGTH); HDR_E.templateId(SizeEncoder.TEMPLATE_ID); SIZE_E.messages(messages); SIZE_E.tod(nanoTime()); while (publication.offer(BUFFER, 0, PRICE_LEN) < 0L) { IDLE.idle(); } IDLE.reset(); while (PENDING.get() > 0) { while (subscription.poll(fragmentHandler, FRAGMENT_LIMIT) <= 0) { IDLE.idle(); } } } @SuppressWarnings("PMD.DoNotUseThreads") private class PongReceiver implements Runnable { @Override public void run() { while (PENDING.get() > 0) { subscription.poll(fragmentHandler, FRAGMENT_LIMIT); } } } }