All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oracle.coherence.common.net.exabus.util.MessageBusTest Maven / Gradle / Ivy

There is a newer version: 24.03
Show newest version
/*
 * Copyright (c) 2000, 2023, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * https://oss.oracle.com/licenses/upl.
 */
package com.oracle.coherence.common.net.exabus.util;

import com.oracle.coherence.common.base.Blocking;
import com.oracle.coherence.common.base.Hasher;

import com.oracle.coherence.common.net.SSLSocketProvider;
import com.oracle.coherence.common.net.exabus.Bus;
import com.oracle.coherence.common.net.exabus.Depot;
import com.oracle.coherence.common.net.exabus.EndPoint;
import com.oracle.coherence.common.net.exabus.Event;
import com.oracle.coherence.common.net.exabus.MemoryBus;
import com.oracle.coherence.common.net.exabus.MessageBus;


import com.oracle.coherence.common.internal.util.Histogram;
import com.oracle.coherence.common.internal.util.ScaledHistogram;

import com.oracle.coherence.common.net.SSLSettings;

import com.oracle.coherence.common.base.Collector;
import com.oracle.coherence.common.base.Disposable;
import com.oracle.coherence.common.base.Factory;
import com.oracle.coherence.common.base.Notifier;
import com.oracle.coherence.common.base.Pollable;
import com.oracle.coherence.common.base.SingleWaiterCooperativeNotifier;

import com.oracle.coherence.common.collections.SingleConsumerBlockingQueue;

import com.oracle.coherence.common.internal.Platform;
import com.oracle.coherence.common.internal.net.socketbus.SocketBusDriver;

import com.oracle.coherence.common.io.BufferManager;
import com.oracle.coherence.common.io.BufferManagers;
import com.oracle.coherence.common.io.BufferSequence;
import com.oracle.coherence.common.io.BufferSequenceInputStream;
import com.oracle.coherence.common.io.BufferSequenceOutputStream;
import com.oracle.coherence.common.io.Buffers;
import com.oracle.coherence.common.io.MultiBufferSequence;
import com.oracle.coherence.common.io.SingleBufferSequence;

import com.oracle.coherence.common.net.SocketSettings;
import com.oracle.coherence.common.net.exabus.spi.Driver;

import com.oracle.coherence.common.util.Bandwidth;
import com.oracle.coherence.common.util.Bandwidth.Rate;
import com.oracle.coherence.common.util.Duration;
import com.oracle.coherence.common.util.MemorySize;

import java.io.DataInput;
import java.io.IOException;
import java.io.PrintStream;
import java.net.SocketOptions;
import java.net.URL;
import java.nio.ByteBuffer;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.*;

import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;

/**
 * MessageBusTest is an application for testing the performance characteristics
 * of MessageBus implementations and the network on which they operate.
 *
 * @author mf 2010.12.14
 */
public class MessageBusTest
    {
    // ----- inner class: Receipt -------------------------------------------

    /**
     * Receipt object used in the test.
     */
    public static class Receipt
        implements Disposable
        {
        public Receipt(long ldtNanos, Disposable garbage)
            {
            m_ldtNanos = ldtNanos;
            m_garbage  = garbage;
            }

        public long getTimestampNanos()
            {
            return m_ldtNanos;
            }

        @Override
        public void dispose()
            {
            Disposable garbage = m_garbage;
            if (garbage != null)
                {
                m_garbage = null;
                garbage.dispose();
                }
            }

        // ----- data members ------------------------------------------

        long m_ldtNanos;
        Disposable m_garbage;
        }

    // ----- inner class: StampedEvent --------------------------------------

    /**
     * StampedEvent adds a nano-resolution timestamp to events at the time
     * of construction.
     */
    public static class StampedEvent
            implements Event
        {
        public StampedEvent(Event evt)
            {
            m_evt = evt;
            m_ldtNanos = System.nanoTime();
            }

        @Override
        public Type getType()
            {
            return m_evt.getType();
            }

        @Override
        public EndPoint getEndPoint()
            {
            return m_evt.getEndPoint();
            }

        @Override
        public Object getContent()
            {
            return m_evt.getContent();
            }

        @Override
        public Object dispose(boolean fTakeContent)
            {
            return m_evt.dispose(fTakeContent);
            }

        @Override
        public void dispose()
            {
            m_evt.dispose();
            }

        public long getTimestampNanos()
            {
            return m_ldtNanos;
            }

        // ----- data members ------------------------------------------

        protected final Event m_evt;
        protected long m_ldtNanos;
        }

    // ----- inner class: EventProcessor ------------------------------------

    /**
     * EventProcessor is the basis for a thread which will handle bus event
     * streams.
     */
    public static class EventProcessor
            extends Thread
            implements Runnable
        {
        // ----- constructors -------------------------------------------

        /**
         * Construct an EventProcessor.
         *
         * @param bus             the Bus for this processor
         * @param setPeer         the peers to transmit to
         * @param setReady        the set that is ready to be transmitted to
         * @param aTransmitter    the associated transmitters for this bus
         * @param cbsIn           the target inbound data rate
         * @param nFlushOn        the number of response messages to send before flushing
         * @param fBacklogGlobal  an AtomicInteger to serve as a flag; non-zero value
         *                        if the bus has declared a global backlog
         */
        public EventProcessor(Bus bus, Set setPeer, Set setReady,
                              Transmitter[] aTransmitter, long cbsIn, int nFlushOn, AtomicInteger fBacklogGlobal)
            {
            super("EventProcessor(" + bus.getLocalEndPoint() + ")");

            m_bus            = bus;
            m_busMsg         = bus instanceof MessageBus ? (MessageBus) bus : null;
            f_fBacklogGlobal = fBacklogGlobal;
            m_setPeer        = setPeer;
            m_setReady       = setReady;
            m_cbsIn          = cbsIn;
            m_nFlushOn       = nFlushOn;
            m_aTransmitter   = aTransmitter;
            m_abChunk        = new byte[s_cbChunk];
            }

        // ----- EventProcessor interface -------------------------------

        /**
         * Return the Bus used by this processor.
         *
         * @return  the bus
         */
        public Bus getBus()
            {
            return m_bus;
            }

        /**
         * Return the MessageBus used by this processor if any.
         *
         * @return  the message bus
         */
        public MessageBus getMessageBus()
            {
            return m_busMsg;
            }

        /**
         * Return the set of peers this processor is configured to transmit
         * to
         *
         * @return the peer set.
         */
        public Set getPeers()
            {
            return m_setPeer;
            }

        /**
         * Return the number of bytes received.
         *
         * @return the number of bytes received
         */
        public long getBytesIn()
            {
            return m_cbIn;
            }

        /**
         * Return the number of bytes sent.
         *
         * @return the number of bytes sent
         */
        public long getBytesOut()
            {
            return m_cbOut;
            }

        /**
         * Return the number of messages received.
         *
         * @return the number of messages received
         */
        public long getMessagesIn()
            {
            return m_cMsgIn;
            }

        /**
         * Return the number of Messages sent.
         *
         * @return the number of Messages sent
         */
        public long getMessagesOut()
            {
            return m_cMsgOut;
            }

        /**
         * Return the number of receipt samples
         *
         * @return the number of receipt samples
         */
        public long getReceiptSamples()
            {
            return m_cReceiptTimings;
            }

        /**
         * Return the cumulative receipts time.
         *
         * @return the cumulative receipts time.
         */
        public long getReceiptNanos()
            {
            return m_cReceiptsNanos;
            }

        /**
         * Return the number of returned receipts.
         *
         * @return the number of returned receipts
         */
        public long getReceiptsIn()
            {
            return m_cReceiptsIn;
            }

        /**
         * Return the number of received responses.
         *
         * @return the number of received responses
         */
        public long getResponsesIn()
            {
            return m_cResponseIn;
            }

        /**
         * Return the cumulative response time.
         *
         * @return the cumulative response time.
         */
        public long getResponseNanos()
            {
            return m_cResponseNanos;
            }

        /**
         * Return they latency histogram.
         *
         * @return the latency Histogram
         */
        public Histogram getResponseLatencyHistogram()
            {
            return f_histLatency;
            }

        /**
         * Return the number of local backlog events received.
         *
         * @return the number of local backlog events received
         */
        public long getLocalBacklogEvents()
            {
            return m_cBacklogEventsLocal;
            }

        /**
         * Return the total duration of local backlogs.
         *
         * @return the local backlog duration
         */
        public long getLocalBacklogMillis()
            {
            return m_cBacklogMillisLocal;
            }

        /**
         * Return the number of remote backlog events received.
         *
         * @return the number of remote backlog events received
         */
        public long getRemoteBacklogEvents()
            {
            return m_cBacklogEventsRemote;
            }

        /**
         * Return the number of connections.
         *
         * @return the connection count
         */
        public long getConnectionCount()
            {
            return m_cConnections;
            }

        /**
         * Handle an inbound message.
         *
         * @param event  the event
         *
         * @return true if a flush is required
         *
         * @throws IOException if an IO error occurs
         */
        protected boolean onMessage(Event event)
                throws IOException
            {
            BufferSequence bufseq = (BufferSequence) event.getContent();

            DataInput in = new BufferSequenceInputStream(bufseq);

            // format:
            // 4B transmitter ID
            // 1B msg type
            // 8B timestamp (or zero)
            // 8B payload size
            // payload
            long cbMessage = bufseq.getLength();

            m_cbIn += cbMessage;

            long    cMsgIn    = m_cMsgIn++;
            int     nId       = in.readInt();
            boolean fResp     = in.readBoolean();
            long    ldtNanos  = in.readLong();
            long    cbPayload = in.readLong();

            if (MSG_HEADER_SIZE + cbPayload != cbMessage)
                {
                s_cErrors.incrementAndGet();
                System.err.println("unexpecteded message size " + cbMessage + " rather then " +
                        (MSG_HEADER_SIZE + cbPayload) + ", msg=" + Buffers.toString(bufseq) + " from " + event);
                throw new IllegalStateException("unexpected message size in "
                        + event);
                }

            if (s_fVerbose && (cMsgIn % 10000) == 0)
                {
                // update thread-name to show movement
                Thread thread = Thread.currentThread();
                String sName  = thread.getName();
                int    of     = sName.lastIndexOf('#');

                thread.setName(sName.substring(
                        0, of == -1 ? sName.length() : of) +
                        "#" + cMsgIn);
                }

            // TODO: validate message ordering, this could be a bit problematic
            // since the test allows multiple threads to send to the same
            // peer over the same local bus, so we'd need to improve the
            // test's message header

            // TODO: optionally verify contents to account for
            int cbChunk = s_cbChunk;
            switch (cbChunk)
                {
                default:
                    for (; cbPayload >= cbChunk; cbPayload -= cbChunk)
                        {
                        in.readFully(m_abChunk);
                        }
                    // fall through

                case 8:
                    for (; cbPayload >= 8; cbPayload -= 8)
                        {
                        in.readLong();
                        }
                    // fall through

                case 4:
                    for (; cbPayload >= 4; cbPayload -= 4)
                        {
                        in.readInt();
                        }
                    // fall through

                case 2:
                    for (; cbPayload >= 2; cbPayload -= 2)
                        {
                        in.readShort();
                        }
                    // fall through

                case 1:
                    for (; cbPayload >= 1; cbPayload -= 1)
                        {
                        in.readByte();
                        }
                    // fall through

                case 0:
                    // read nothing
                    break;
                }

            EndPoint epResponse = null;
            boolean  fFlush     = false;

            if (fResp)
                {
                // this is an response
                if (s_fRelay)
                    {
                    // server receiving response to relayed request
                    if (s_fBlock)
                        {
                        RelayResponse resp = s_mapRelayResponse.remove(nId);
                        epResponse = resp.peer;
                        nId        = resp.nId;
                        }
                    // else; response was sent immediately during relay
                    }
                else // client awaiting response
                    {
                    if (ldtNanos > 0)
                        {
                        ++m_cResponseIn;
                        long cNanos = System.nanoTime() - ldtNanos;
                        m_cResponseNanos += cNanos;
                        f_histLatency.addSample((int) (cNanos / 1000));

                        if (cNanos > m_cResponseNanosMax)
                            {
                            m_cResponseNanosMax = cNanos;
                            }
                        if (cNanos < m_cResponseNanosMin)
                            {
                            m_cResponseNanosMin = cNanos;
                            }
                        }

                    Transmitter tx = m_aTransmitter[nId & 0x0FFFF]; // upper 16 bits are just a request counter for the thread

                    if (s_fBlock)
                        {
                        tx.signalResult(nId >>> 16);
                        }
                    }
                }
            else if (s_fRelay && !m_setPeer.contains(event.getEndPoint()))
                {
                // relay message to a configured peer

                Iterator iterRelay = m_iterPeerRelay;
                EndPoint           epRelay;
                if (iterRelay == null || !iterRelay.hasNext())
                    {
                    iterRelay = m_iterPeerRelay = m_setPeer.iterator();
                    }
                epRelay = iterRelay.next();

                if (ldtNanos != 0)
                    {
                    if (s_fBlock)
                        {
                        // defer response like coherence backups
                        RelayResponse resp = new RelayResponse(event.getEndPoint(), nId);
                        nId = s_atomicRelayId.incrementAndGet();
                        s_mapRelayResponse.put(nId, resp);
                        }
                    else
                        {
                        // relay plus immediate response (like async backups)
                        epResponse = event.getEndPoint();
                        }
                    }

                BufferSequence seqRelay = getMessage(nId, /*fResp*/ false, ldtNanos);

                ++m_cMsgOut;
                m_cbOut += seqRelay.getLength();

                // TODO: consider option to send reply upon delivery confirmation?
                getMessageBus().send(epRelay, seqRelay, s_fReceipts ? new Receipt(0, seqRelay) : null);
                fFlush = true;
                }
            else if (ldtNanos != 0)
                {
                // this is a non-relay request
                epResponse = event.getEndPoint();
                }

            if (epResponse != null)
                {
                // send a response message
                if (s_fPrompt)
                    {
                    System.out.println("Press ENTER to send messge to " + event.getEndPoint());
                    System.in.read();
                    }

                BufferSequence seqresponse = getMessage(nId, /*fResp*/ true, ldtNanos);

                ++m_cMsgOut;
                m_cbOut += seqresponse.getLength();

                getMessageBus().send(epResponse, seqresponse, s_fReceipts ? new Receipt(0, seqresponse) : null);
                fFlush = true;
                }

            return fFlush;
            }

        /**
         * Process an event
         *
         * @param event  the event to process
         *
         * @return true if a flush is required
         */
        public boolean onEvent(Event event)
            {
            try
                {
                Bus           bus             = m_bus;
                EndPoint      bindEp          = bus.getLocalEndPoint();
                Set setPeer         = m_setPeer;
                Set setReady        = m_setReady;
                boolean       fFlush          = false;

                EndPoint ep = event.getEndPoint();

                switch (event.getType())
                    {
                    case OPEN:
                    case CLOSE:
                        System.err.println(event);
                        break;

                    case CONNECT:
                        if (!s_fSingleUseConnection)
                            {
                            System.err.println(event + " on " + bindEp);
                            }
                        ++m_cConnections;
                        // fall-through to BACKLOG_NORMAL

                    case BACKLOG_NORMAL:
                        if (s_fFlowControl || event.getType() == Event.Type.CONNECT)
                            {
                            if (ep == null)
                                {
                                synchronized (f_fBacklogGlobal)
                                    {
                                    if (!f_fBacklogGlobal.compareAndSet(1, 0))
                                        {
                                        System.err.println("received out of order event " + event + " on " + bindEp);
                                        s_cErrors.incrementAndGet();
                                        }
                                    f_fBacklogGlobal.notifyAll();
                                    }
                                }
                            else if (ep == bindEp)
                                {
                                long ldtStart = m_ldtBacklogLocalStart;
                                if (ldtStart == 0)
                                    {
                                    System.err.println("received out of order event " + event + " on " + bindEp);
                                    s_cErrors.incrementAndGet();
                                    }
                                else
                                    {
                                    m_cBacklogMillisLocal += System.currentTimeMillis() - ldtStart;
                                    m_ldtBacklogLocalStart = 0;
                                    }
                                }
                            else if (setPeer.contains(ep))
                                {
                                boolean fAdded;
                                if (setReady.isEmpty())
                                    {
                                    synchronized (setReady)
                                        {
                                        fAdded = setReady.add(ep);
                                        setReady.notifyAll();
                                        }
                                    }
                                else
                                    {
                                    fAdded = setReady.add(ep);
                                    }

                                if (!fAdded)
                                    {
                                    System.err.println("received out of order event " + event + " on " + bindEp);
                                    s_cErrors.incrementAndGet();
                                    }
                                }
                            }
                        break;

                    case BACKLOG_EXCESSIVE:
                        if (s_fFlowControl)
                            {
                            if (ep == null)
                                {
                                ++m_cBacklogEventsRemote;
                                if (!f_fBacklogGlobal.compareAndSet(0, 1))
                                    {
                                    // event appear to be out of sequence
                                    System.err.println("received out of order event " + event + " on " + bindEp);
                                    s_cErrors.incrementAndGet();
                                    }
                                }
                            else if (bindEp == ep)
                                {
                                ++m_cBacklogEventsLocal;
                                if (m_ldtBacklogLocalStart != 0)
                                    {
                                    System.err.println("received out of order event " + event + " on " + bindEp);
                                    s_cErrors.incrementAndGet();
                                    }
                                m_ldtBacklogLocalStart = System.currentTimeMillis();
                                }
                            else
                                {
                                ++m_cBacklogEventsRemote;
                                if (!setReady.remove(ep) && setPeer.contains(ep))
                                    {
                                    // event appear to be out of sequence
                                    System.err.println("received out of order event " + event + " on " + bindEp);
                                    s_cErrors.incrementAndGet();
                                    }
                                // else; not a peer we're sending to; ordering not tracked by test
                                }
                            }

                        break;

                    case DISCONNECT:
                        if (!s_fSingleUseConnection)
                            {
                            System.err.println(event + " on " + bindEp);
                            Throwable t = (Throwable) event.getContent();
                            if (t != null && (!(t instanceof IOException) || s_fVerbose))
                                {
                                t.printStackTrace(System.err);
                                }
                            }
                        bus.release(ep);
                        break;

                    case RELEASE:
                        if (!s_fSingleUseConnection)
                            {
                            System.err.println(event + " on " + bindEp);
                            }
                        --m_cConnections;
                        synchronized (setReady)
                            {
                            setReady.remove(ep);
                            setReady.notifyAll();
                            }
                        break;

                    case MESSAGE:
                        fFlush = onMessage(event);
                        break;

                    case RECEIPT:
                        Receipt receipt      = (Receipt) event.getContent();
                        long    ldtNanosSent = receipt.getTimestampNanos();
                        ++m_cReceiptsIn;
                        if (ldtNanosSent != 0)
                            {
                            long cNanos = (s_fPollingCollector ? System.nanoTime() : ((StampedEvent) event).getTimestampNanos()) - ldtNanosSent;
                            m_cReceiptsNanos += cNanos;
                            ++m_cReceiptTimings;

                            if (m_busMsg == null)
                                {
                                f_histLatency.addSample((int) (cNanos / 1000L));
                                }
                            }

                        receipt.dispose();
                        break;

                    default:
                        System.err.println(event + " on " + bindEp);
                        break;
                    }

                event.dispose();
                return fFlush;
                }
            catch (Throwable e)
                {
                s_cErrors.incrementAndGet();
                System.err.println("fatal error after receiving " + m_cMsgIn + " messages");
                e.printStackTrace(System.err);
                throw new IllegalStateException(e);
                }
            }

        /**
         * Add an event for the processor to handle.
         *
         * @param event  the event to process
         *
         * @return  true if a flush is required
         */
        public boolean add(Event event)
            {
            BlockingQueue queue = m_queue;
            if (queue == null)
                {
                // This synchronization should be mostly uncontended. Contention is only possible
                // if we are running in reentrant mode, have multiple peers sending to us, and
                // they bus impl uses different threads to deliver their events, and the
                // DemultiplexingCollector happens to hash them to the same EventProcessor. The
                // level of contention can be roughly controlled via -rxThreads, setting to a
                // large negative value. Note that the default is such a value.

                // Note: we break the event stream up into events related to transmit and receive
                // to allow this independent streams to be processed concurrently, this is important
                // for buses which may emit these events from different threads, as we don't want
                // the test to add unecessary contention
                switch (event.getType())
                    {
                    case BACKLOG_EXCESSIVE:
                    case BACKLOG_NORMAL:
                        if (event.getEndPoint() == m_bus.getLocalEndPoint())
                            {
                            synchronized (f_syncRxEvents)
                                {
                                return onEvent(event);
                                }
                            }
                        // else; fall through

                    case RECEIPT:
                        synchronized (f_syncTxEvents)
                            {
                            return onEvent(event);
                            }

                    case MESSAGE:
                        synchronized (f_syncRxEvents)
                            {
                            return onEvent(event);
                            }

                    default:
                        synchronized (f_syncTxEvents)
                            {
                            synchronized (f_syncRxEvents)
                                {
                                return onEvent(event);
                                }
                            }
                    }
                }
            else
                {
                queue.add(event);
                return false;
                }
            }


        // ----- Runnable interface -------------------------------------

        /**
         * {@inheritDoc}
         */
        @Override
        public void run()
            {
            Bus                        bus       = m_bus;
            final BlockingQueue queue     = m_queue;
            int                  nFlushOn        = m_nFlushOn;
            int                  cFlushEvent     = 0;
            long                 cbsInTarget     = m_cbsIn;
            long                 cbEval          = cbsInTarget / 8;
            long                 ldtLast         = 0;
            long                 cbInLast        = 0;
            long                 cThrottleMillis = 1;
            int                  nThrottle       = 1000; // random
            Pollable      poll            = s_fPollingCollector
                    ? (QueueingEventCollector) bus.getEventCollector()
                    : new Pollable()
                    {
                    @Override
                    public Event poll()
                        {
                        return queue.poll();
                        }

                    @Override
                    public Event poll(long timeout, TimeUnit unit)
                            throws InterruptedException
                        {
                        return timeout == Long.MAX_VALUE
                                ? queue.take()
                                : queue.poll(timeout, unit);
                        }
                    };

            try
                {
                for (int i = 0; ; ++i)
                    {
                    Event event = poll.poll();
                    while (event == null)
                        {
                        if (cFlushEvent > 0)
                            {
                            cFlushEvent = 0;
                            bus.flush();
                            }
                        SingleWaiterCooperativeNotifier.flush();

                        event = poll.poll(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
                        }

                    if (onEvent(event) && ++cFlushEvent > nFlushOn)
                        {
                        cFlushEvent = 0;
                        bus.flush();
                        }

                    if (cbsInTarget > 0)
                        {
                        // throttle the input rate; this allows simulating
                        // a transmitter which outpaces the receiver, we want
                        // to see that the transmitter will detect our backlog
                        // and throttle itself accordingly
                        if ((i % nThrottle) == 0)
                            {
                            if (cFlushEvent > 0)
                                {
                                cFlushEvent = 0;
                                bus.flush();
                                }
                            Blocking.sleep(cThrottleMillis);
                            }
                        long cbIn    = m_cbIn;
                        long cbDelta = cbIn - cbInLast;

                        if (cbInLast == 0 && ldtLast == 0)
                            {
                            // start the clock
                            ldtLast = System.currentTimeMillis();
                            }
                        else if (cbDelta > cbEval)
                            {
                            long   ldtNow  = System.currentTimeMillis();
                            long   cMillis = Math.max(1, ldtNow - ldtLast);
                            double cbs     = (cbDelta * 1000) / cMillis;
                            double dfl     = cbsInTarget / cbs;

                            nThrottle = Math.max(1, (int) Math.round(nThrottle * dfl));
                            int nThrottleNew = (int) Math.round(nThrottle * dfl);
                            if (nThrottleNew == 0)
                                {
                                nThrottleNew = 1;
                                ++cThrottleMillis; // go slower still
                                }
                            else if (nThrottleNew == nThrottle)
                                {
                                if (dfl > 1.01)
                                    {
                                    ++cThrottleMillis;
                                    }
                                else if (dfl < 0.09)
                                    {
                                    --cThrottleMillis;
                                    }
                                }

                            cbInLast = cbIn;
                            ldtLast  = ldtNow;
                            i        = 0;
                            }
                        }

                    }
                }
            catch (Exception e)
                {
                throw new IllegalStateException(e);
                }
            }

        // ----- Thread interface ---------------------------------------

        @Override
        public void start()
            {
            m_queue = new SingleConsumerBlockingQueue();

            super.start();
            }


        /**
         * Marker class for profiling.
         */
        public static class TxEventSynchronizer
            {
            };

        /**
         * Marker class for profiling.
         */
        public static class RxEventSynchronizer
            {
            };

        public static class RelayResponse
            {
            public RelayResponse(EndPoint peer, int nId)
                {
                this.peer = peer;
                this.nId  = nId;
                }

            EndPoint peer;
            int      nId;
            }

        // ----- data members -------------------------------------------

        /**
         * The Bus for this processor.
         */
        protected final Bus m_bus;

        protected final MessageBus m_busMsg;

        /**
         * Monitor protecting processing of transmit related events.
         */
        protected final Object f_syncTxEvents = new TxEventSynchronizer();

        /**
         * Monitor protecting processing of receive related events.
         */
        protected final Object f_syncRxEvents = new RxEventSynchronizer();

        /**
         * AtomicInteger that serves as a flag; non-zero if the bus has
         * declared a global backlog.
         */
        protected final AtomicInteger f_fBacklogGlobal;

        /**
         * The event queue, or null for reentrant processing.
         */
        protected BlockingQueue m_queue;

        /**
         * Set of peer's to transmit to.
         */
        protected final Set m_setPeer;

        /**
         * Iterator over the peer set for use as a relay
         */
        protected Iterator m_iterPeerRelay;

        /**
         * Used by a relay to identify which client a response needs to be sent to.
         */
        protected final static Map s_mapRelayResponse = new ConcurrentHashMap();

        /**
         * Relay message ID generator.
         */
        protected final static AtomicInteger s_atomicRelayId = new AtomicInteger();

        /**
         * Subset of m_setPeer that ready to be transmitted to, i.e. connected
         * and not backlogged.
         */
        protected final Set m_setReady;

        /**
         * The target inbound data rate.
         */
        protected final long m_cbsIn;

        /**
         * The number of messages to send before flushing.
         */
        protected final int m_nFlushOn;

        /**
         * The number of transmit threads to run.
         */
        protected final Transmitter[] m_aTransmitter;

        /**
         * The number of open connections.
         */
        protected long m_cConnections;

        /**
         * The number of bytes read.
         */
        protected long m_cbIn;

        /**
         * The number of received messages.
         */
        protected long m_cMsgIn;

        /**
         * The total number of received responses.
         */
        protected long m_cResponseIn;

        /**
         * The maximum response time.
         */
        protected long m_cResponseNanosMax = -1;

        /**
         * The minimum response time.
         */
        protected long m_cResponseNanosMin = Long.MAX_VALUE;

        /**
         * Histogram of all latencies.
         */
        protected final Histogram f_histLatency = makeLatencyHistogram();
        /**
         * The total RTT time for all receied responses.
         */
        protected long m_cResponseNanos;

        /**
         * The total number of received timed receipts.
         */
        protected long m_cReceiptTimings;

        /**
         * The total RTT time for all received receipts.
         */
        protected long m_cReceiptsNanos;

        /**
         * The total number of returned receipts.
         */
        protected long m_cReceiptsIn;

        /**
         * The number of bytes written.
         */
        protected long m_cbOut;

        /**
         * The number of sent messages.
         */
        protected long m_cMsgOut;

        /**
         * The total number of local backlog events received.
         */
        protected long m_cBacklogEventsLocal;

        /**
         * The time at which the current local backlog condition started, or zero if there is none.
         */
        protected long m_ldtBacklogLocalStart;

        /**
         * The total number of milliseconds for which the EventProcessor was
         * locally backlogged.
         */
        protected long m_cBacklogMillisLocal;

        /**
         * The total number of remote backlog events received.
         */
        protected long m_cBacklogEventsRemote;

        /**
         * to be used for chunked reads, we don't reuse s_abChunk in order to avoid introducing
         * false memory contention
         */
        protected final byte[] m_abChunk;
        }

    // ----- inner class: DemultiplexingCollector ---------------------------

    public static class DemultiplexingCollector implements Collector
        {
        // ----- constructors -------------------------------------------

        /**
         * Construct a DemultiplexingCollector which dispatches to the
         * specified queues.
         *
         * @param bus           the bus associated with all the processors
         * @param aProcessor    the queues to dispatch other events to
         */
        public DemultiplexingCollector(Bus bus, EventProcessor[] aProcessor)
            {
            m_bus = bus;
            m_aProcessor = aProcessor;
            m_acbReceived = new AtomicLong[aProcessor.length];

            for (int i = 0, c = m_acbReceived.length; i < c; ++i)
                {
                m_acbReceived[i] = new AtomicLong();
                }
            }

        // ----- Collector interface ------------------------------------

        /**
         * {@inheritDoc}
         */
        @Override
        public void add(Event event)
            {
            EndPoint pointSrc = event.getEndPoint();
            int nHash = pointSrc == null
                    ? 0
                    : pointSrc.hashCode();

            switch (event.getType())
                {
                case RECEIPT:
                    if (((Receipt) event.getContent()).getTimestampNanos() != 0)
                        {
                        // the event contains a timestamped receipt, insert the
                        // corresponding "end" receipt here as it is the first point
                        // at which the application code sees the response.
                        event = new StampedEvent(event);
                        }
                    break;

                case MESSAGE:
                    m_acbReceived[Hasher.mod(nHash, m_acbReceived.length)]
                            .addAndGet(((BufferSequence) event.getContent()).getLength());
                    break;

                default:
                    break;
                }

            if (m_aProcessor[Hasher.mod(nHash, m_aProcessor.length)].add(event))
                {
                m_fFlush = true;
                }
            }

        /**
         * {@inheritDoc}
         */
        @Override
        public void flush()
            {
            if (m_fFlush)
                {
                m_fFlush = false;
                m_bus.flush();
                }

            SingleWaiterCooperativeNotifier.flush();
            }

        // ----- helpers ------------------------------------------------

        /**
         * Return the total number of received bytes.
         *
         * @return the total number of received bytes
         */
        public long getReceivedBytes()
            {
            long cb = 0;
            for (int i = 0, c = m_acbReceived.length; i < c; ++i)
                {
                cb += m_acbReceived[i].get();
                }
            return cb;
            }


        // ----- data members -------------------------------------------

        /**
         * The Bus associated with the EventProcessors.
         */
        protected final Bus m_bus;

        /**
         * The array of processors to dispatch to.
         */
        protected final EventProcessor[] m_aProcessor;

        /**
         * Array containing received message size totals.
         */
        protected final AtomicLong[] m_acbReceived;


        /**
         * True if the collector requires a bus.flush to be performed.
         */
        protected boolean m_fFlush;
        }


    // ----- inner class: Transmitter ---------------------------------------

    /**
     * A Transmitter is reponsible for sending messages on a bus to a series
     * of peers.
     */
    public static class Transmitter extends Thread
        {
        // ----- constructors -------------------------------------------

        /**
         * Construct a Transmitter.
         *
         * @param bus             the associated bus
         * @param nId             the transmitter id
         * @param setReady        the peers to send to
         * @param nFlushOn        the number of messages to send before flushing
         * @param fBacklogGlobal  an AtomicInteger to serve as a flag; non-zero value
         *                        if the bus has declared a global backlog
         * @param cbBacklog       the maximum tx backlog, or -1 for no limit
         *
         * @throws IOException if an IO error occurs
         */
        public Transmitter(
                Bus bus, int nId, Set setReady, int nFlushOn, AtomicInteger fBacklogGlobal, long cbBacklog)
                throws IOException
            {
            super("Transmitter(" + bus.getLocalEndPoint() + ")");

            f_bus = bus;
            f_bufSeqCached = s_fCached
                    ? MessageBusTest.getMessage(nId, /*fResp*/ false, 0)
                    : null;
            f_aBufCached = s_fCached
                    ? f_bufSeqCached.getBuffers()
                    : null;
            f_fBacklogGlobal = fBacklogGlobal;
            f_nId = nId;
            f_setReady = setReady;
            f_nFlushOn = nFlushOn;
            f_cbTxMaxBacklog = cbBacklog;
            }

        // ----- Transmitter interface ----------------------------------

        private volatile Object   m_oResult;
        private final    Notifier f_notifier = new SingleWaiterCooperativeNotifier();

        public void signalResult(Object oResult)
            {
            long cPending = f_cPendingResponses.decrementAndGet();
            if (cPending == 0)
                {
                m_oResult = oResult;
                f_notifier.signal();
                }
            else if (cPending < 0)
                {
                throw new IllegalStateException();
                }
            }

        public Object awaitResult()
                throws InterruptedException
            {
            Object oResult = m_oResult;
            if (oResult == null)
                {
                if (AWAIT_SPIN_NANOS > 0)
                    {
                    for (long ldtEnd = System.nanoTime() + AWAIT_SPIN_NANOS;
                         oResult == null && System.nanoTime() < ldtEnd;
                         oResult = m_oResult) {}
                    }

                for (; oResult == null; oResult = m_oResult)
                    {
                    f_notifier.await();
                    }
                }

            m_oResult = null;
            return oResult;
            }

        /**
         * Reset the transmit rate.
         *
         * Note this may only be reset before starting the thread.
         *
         * @param cbs  the data rate
         */
        public void setTransmitRate(long cbs)
            {
            f_cbs = cbs;
            }

        /**
         * Return the number of bytes sent.
         *
         * @return the number of bytes sent
         */
        public long getBytesOut()
            {
            return m_cbOut;
            }

        /**
         * Return the number of Messages sent.
         *
         * @return the number of Messages sent
         */
        public long getMessagesOut()
            {
            return m_cMsgOut;
            }

        /**
         * Return the total number of milliseconds for which the transmitter
         * was blocked.
         *
         * @return the total blocked time
         */
        public long getRemoteBacklogMillis()
            {
            long ldtBacklogStart = m_ldtBacklogStart;
            long cMillisBacklog = m_cMillisBacklog;

            return ldtBacklogStart == 0 || m_cbOut == 0
                    ? cMillisBacklog
                    : cMillisBacklog + (System.currentTimeMillis() - ldtBacklogStart);
            }

        public class BacklogTrackingReceipt
                extends Receipt
            {
            public BacklogTrackingReceipt(long ldtNanos, Disposable garbage)
                {
                super(ldtNanos, garbage);
                }

            @Override
            public void dispose()
                {
                super.dispose();
                if (f_cbTxMaxBacklog != -1)
                    {
                    long cbBacklogPre = f_cbTxBacklog.getAndAdd(-s_cbMsgAvg);
                    long cbThreshold  = f_cbTxMaxBacklog / 3;
                    if (cbBacklogPre > cbThreshold && cbBacklogPre - s_cbMsgAvg < f_cbTxMaxBacklog)
                        {
                        f_notifierBacklog.signal();
                        }
                    }
                }
            }

        // ----- Runnable interface -------------------------------------

        /**
         * {@inheritDoc}
         */
        @Override
        public void run()
            {
            try
                {
                Bus bus = f_bus;

                MessageBus busMsg = null;
                MemoryBus busMem = null;

                if (bus instanceof MessageBus)
                    {
                    busMsg = (MessageBus) bus;
                    }
                else if (bus instanceof MemoryBus)
                    {
                    busMem = (MemoryBus) bus;
                    }

                Set setReady = f_setReady;
                long cbsTarget = f_cbs;
                long cbEval = cbsTarget / 8;
                boolean fBlock = s_fBlock;
                long nFlushOn = Math.max(fBlock
                        ? 1
                        : 0, f_nFlushOn);
                AtomicInteger cPending = f_cPendingResponses;
                long lSeq = 0;

                long cThrottleMillis = 1;
                long nThrottle = Math.max(1, ((int) cbEval / (s_cbMsgMax - (s_cbMsgMax - s_cbMsgMin) / 2)) / 8);
                long ldtLast = System.currentTimeMillis();
                long cbThis = 0;
                long cSendThrottle = 0;
                long cMsgOut = 0;

                Iterator iterPeer = setReady.iterator();

                while (true)
                    {
                    if (f_fBacklogGlobal.get() == 1)
                        {
                        long ldtStartWait = m_ldtBacklogStart = System.currentTimeMillis();
                        synchronized (f_fBacklogGlobal)
                            {
                            while (f_fBacklogGlobal.get() == 1)
                                {
                                Blocking.wait(f_fBacklogGlobal);
                                }
                            }
                        if (cbThis > 0)
                            {
                            m_cMillisBacklog += System.currentTimeMillis() - ldtStartWait;
                            }
                        }

                    if (!iterPeer.hasNext()) // iterator exhausted
                        {
                        iterPeer = setReady.iterator();

                        if (!iterPeer.hasNext())
                            {
                            // all members in backlog
                            bus.flush();
                            synchronized (setReady)
                                {
                                for (iterPeer = setReady.iterator(); !iterPeer.hasNext(); iterPeer = setReady
                                        .iterator())
                                    {
                                    long ldtStartWait = m_ldtBacklogStart = System.currentTimeMillis();
                                    Blocking.wait(setReady);
                                    m_ldtBacklogStart = 0;
                                    if (cbThis > 0)
                                        {
                                        m_cMillisBacklog += System.currentTimeMillis() - ldtStartWait;
                                        }
                                    }
                                }
                            }
                        }

                    // create and send message
                    BufferSequence bufseq = null;
                    try
                        {
                        if (fBlock)
                            {
                            cPending.incrementAndGet();
                            }

                        EndPoint peer = iterPeer.next();

                        if (s_fPrompt)
                            {
                            System.out.println("Press ENTER to send messge to " + peer);
                            System.in.read();
                            }

                        // determine if we will take a latency measurement
                        long ldtNanos = s_nLatencyFreq != 0 && (++lSeq % s_nLatencyFreq == 0)
                                ? System.nanoTime() // latency measurement
                                : fBlock
                                    ? -1 // blocking request
                                    : 0; // streaming with no latency measurement

                        bufseq = getMessage(ldtNanos);
                        long cb = bufseq.getLength();

                        Receipt receipt = s_fReceipts
                                ? new BacklogTrackingReceipt(Math.max(0, ldtNanos), bufseq == f_bufSeqCached ? null : bufseq)
                                : null;

                        if (busMsg != null)
                            {
                            busMsg.send(peer, bufseq, receipt);
                            }
                        else // RDMA test
                            {
                            if (s_fBlock)
                                {
                                // on MemoryBus use receipts as responses when in blocking mode
                                final Disposable garbage = receipt.m_garbage;
                                receipt.m_garbage = new Disposable()
                                    {
                                    @Override
                                    public void dispose()
                                        {
                                        signalResult(Integer.valueOf(0));

                                        if (garbage != null)
                                            {
                                            garbage.dispose();
                                            }
                                        }
                                    };
                                }

                            long cbPeer = busMem.getCapacity(peer);
                            long lOffset = Hasher.mod(s_rand.nextLong(), (cbPeer - bufseq.getLength()));
                            switch ((int) busMem.getCapacity(null))
                                {
                                case 0:
                                    {
                                    if (cbPeer == 0) // test signaling
                                        {
                                        cb = 8;
                                        busMem.signal(peer, 1234, receipt);
                                        }
                                    else // test RDMA writes
                                        {
                                        busMem.write(peer, lOffset, bufseq, receipt);
                                        }
                                    break;
                                    }

                                case 8: // test ATOMICs
                                    {
                                    cb = 16;
                                    busMem.getAndAdd(peer, 0, 1, /*result*/ null, receipt);
                                    break;
                                    }

                                default: // test RDMA reads: TODO: read into hosted memory?
                                    {
                                    busMem.read(peer, lOffset, bufseq, receipt);
                                    break;
                                    }
                                }
                            }

                        cMsgOut = m_cMsgOut++;
                        cbThis += cb;
                        m_cbOut += cb;

                        if (f_cbTxMaxBacklog != -1 && f_cbTxBacklog.addAndGet(s_cbMsgAvg) > f_cbTxMaxBacklog)
                            {
                            bus.flush();
                            f_notifierBacklog.await();
                            }

                        // periodic flush and block
                        if (nFlushOn != 0 && (cMsgOut % nFlushOn) == 0)
                            {
                            bus.flush();

                            if (fBlock)
                                {
                                // wait for response(s)
                                int nSeq = (Integer) awaitResult();
                                int nExp = (int) ((m_cMsgOut - 1) & 0x0FFFF);
                                if (nSeq != 0 && nExp != nSeq)
                                    {
                                    throw new IllegalStateException("unexpected response id " + nSeq + " expected " + nExp);
                                    }
                                }
                            }

                        if (s_fSingleUseConnection)
                            {
                            bus.disconnect(peer);
                            synchronized (setReady)
                                {
                                // wait for release to be processed
                                while (setReady.contains(peer))
                                    {
                                    setReady.wait();
                                    }
                                }
                            bus.connect(peer);
                            }
                        }
                    catch (IllegalArgumentException e)
                        {
                        // should be from a concurrent release; not an error
                        if (fBlock)
                            {
                            cPending.decrementAndGet();
                            }

                        if (bufseq != null && bufseq != f_bufSeqCached)
                            {
                            bufseq.dispose();
                            }
                        }

                    // transmit throttle
                    if (cbsTarget > 0 && (++cSendThrottle % nThrottle) == 0)
                        {
                        bus.flush();
                        Blocking.sleep(cThrottleMillis);

                        if (cbThis >= cbEval)
                            {
                            // evaluate data rate against the target and adjust
                            long ldtNow = System.currentTimeMillis();
                            long cMillis = ldtNow - ldtLast;
                            if (cMillis == 0)
                                {
                                ++nThrottle;
                                }
                            else
                                {
                                double cbs = (cbThis * 1000) / cMillis;
                                double dfl = cbsTarget / cbs;
                                int nThrottleNew = (int) Math.round(nThrottle * dfl);
                                if (nThrottleNew == 0)
                                    {
                                    nThrottleNew = 1;
                                    ++cThrottleMillis; // go slower still
                                    }
                                else if (nThrottleNew == nThrottle)
                                    {
                                    if (dfl > 1.01)
                                        {
                                        ++cThrottleMillis;
                                        }
                                    else if (dfl < 0.09)
                                        {
                                        --cThrottleMillis;
                                        }
                                    }
                                nThrottle = nThrottleNew;
                                }

                            cSendThrottle = 0;
                            cbThis = 0;
                            ldtLast = ldtNow;
                            }
                        }

                    // simple stats updating in thread name
                    if (s_fVerbose && (cMsgOut % 10000) == 0)
                        {
                        // update thread-name to show movement
                        Thread thread = Thread.currentThread();
                        String sName = thread.getName();
                        int of = sName.lastIndexOf('#');

                        thread.setName(sName.substring(0, of == -1
                                ? sName.length()
                                : of) +
                                "#" + cMsgOut);
                        }
                    }
                }
            catch (Throwable e)
                {
                s_cErrors.incrementAndGet();
                System.err.println("fatal error after sending " + m_cMsgOut + " messages");
                throw new IllegalStateException(e);
                }
            }

        // ----- helpers ------------------------------------------------

        /**
         * Construct a message.
         *
         * @param ldtSent  the send time if a latency measurement is required, or 0 for none
         *
         * @return the message
         *
         * @throws IOException if an IO error occurs
         */
        public BufferSequence getMessage(long ldtSent)
                throws IOException
            {
            if (s_fCached)
                {
                if (s_fBlock)
                    {
                    // since we're blocking and asking for a new message the old one is free for re-use; just
                    // overwrite the timestamp
                    // Note that reusing the same bufseq would produce ByteBuffer.duplicates which are expensive to
                    // GC, so instead we produce new sequences of the now unused buffers
                    for (ByteBuffer buf : f_aBufCached)
                        {
                        buf.position(0);
                        }
                    f_aBufCached[0].putLong(5, ldtSent);
                    return f_aBufCached.length == 1
                            ? new SingleBufferSequence(null, f_aBufCached[0])
                            : new MultiBufferSequence(null, f_aBufCached);
                    }
                else if (ldtSent == 0)
                    {
                    // non-blocking cached request in always non-tied
                    return f_bufSeqCached;
                    }
                // else; timed non-blocking request, not cached; fall through
                }

            // upper 16 bits of id are a message counter, lower are a thread id
            return MessageBusTest.getMessage(((int) (m_cMsgOut << 16)) | f_nId, /*fResp*/ false, ldtSent);
            }

        // ----- data members -------------------------------------------

        /**
         * The associated Bus.
         */
        protected final Bus f_bus;

        /**
         * The cached message to use for untimed sends
         */
        protected final BufferSequence f_bufSeqCached;

        /**
         * The buffers in the cached sequence
         */
        protected final ByteBuffer[] f_aBufCached;

        /**
         * Non-zero if the bus has declared a global backlog.
         */
        protected final AtomicInteger f_fBacklogGlobal;

        /**
         * The transmitter ID.
         */
        protected final int f_nId;

        /**
         * The EndPoints to send to.
         */
        protected final Set f_setReady;

        /**
         * The target data rate.
         */
        protected long f_cbs;

        /**
         * The number of messages to send before flushing.
         */
        protected final int f_nFlushOn;

        /**
         * The maximum allowed transmit backlog, or -1 for no limit.
         */
        protected final long f_cbTxMaxBacklog;

        /**
         * The current backlog.
         */
        protected final AtomicLong f_cbTxBacklog = new AtomicLong();

        /**
         * Backlog notifier.
         */
        protected final Notifier f_notifierBacklog = new SingleWaiterCooperativeNotifier();

        /**
         * The number of pending responses.
         */
        protected final AtomicInteger f_cPendingResponses = new AtomicInteger();

        /**
         * The number of bytes written.
         */
        protected long m_cbOut;

        /**
         * The number of sent messages.
         */
        protected long m_cMsgOut;

        /**
         * The number of milliseconds the transmitter was blocked waiting
         * for peers.
         */
        protected long m_cMillisBacklog;

        /**
         * The start time of the current backlog, or 0 if none is active
         */
        protected volatile long m_ldtBacklogStart;

        protected final long s_cbMsgAvg = s_cbMsgMin + (s_cbMsgMax - s_cbMsgMin) / 2;
        }

    // ----- inner class: SkipStream ----------------------------------------

    /**
     * SkipStream in an OutputStream with the ability to skip a number of
     * bytes.  This provides a cheap way to write large multi-buffer messages
     * without serialization overhead.
     */
    public static class SkipStream
            extends BufferSequenceOutputStream
        {
        // ----- constructors -------------------------------------------

        /**
         * Construct a SkipStream
         *
         * @param manager  the manager to allocate the buffers from
         */
        public SkipStream(BufferManager manager)
            {
            super(manager);
            }

        /**
         * Construct a SkipStream
         *
         * @param manager  the manager to allocate the buffers from
         * @param cb       the estimated size of the stream
         */
        public SkipStream(BufferManager manager, long cb)
            {
            super(manager, cb);
            }

        // ----- SkipStream interface -----------------------------------

        /**
         * Skip over the specified number of output bytes.
         *
         * @param lcb  the number of bytes to skip over.
         *
         * @throws IOException on an I/O error
         */
        public void skip(long lcb)
                throws IOException
            {
            while (lcb > 0)
                {
                ByteBuffer buf = ensureSpace(lcb);
                int cb = (int) Math.min(lcb, buf.remaining());
                buf.position(buf.position() + cb);
                lcb -= cb;
                }
            }
        }


    /**
     * EchoBus is a simple MessageBus implementation which echos all messages back to itself.
     */
    public static class EchoBus
            implements MessageBus
        {
        public EchoBus(EndPoint pointSelf)
            {
            m_pointSelf = pointSelf;
            }

        // ---- MessageBus interface ----------------------------------------

        @Override
        public void send(final EndPoint peer, BufferSequence bufseq, final Object receipt)
            {
            final Collector collector = getEventCollector();
            collector.add(new SimpleEvent(Event.Type.MESSAGE, peer, bufseq)
                {
                @Override
                public Object dispose(boolean fTakeContent)
                    {
                    if (receipt != null)
                        {
                        // echo bus isn't done with source message until it has been disposed
                        collector.add(new SimpleEvent(Event.Type.RECEIPT, peer, receipt));
                        }
                    return super.dispose(fTakeContent);
                    }
                });
            }

        @Override
        public EndPoint getLocalEndPoint()
            {
            return m_pointSelf;
            }

        @Override
        public void open()
            {
            getEventCollector().add(new SimpleEvent(Event.Type.OPEN, getLocalEndPoint()));
            flush();
            }

        @Override
        public void close()
            {
            getEventCollector().add(new SimpleEvent(Event.Type.CLOSE, getLocalEndPoint()));
            flush();
            }

        @Override
        public void connect(EndPoint peer)
            {
            getEventCollector().add(new SimpleEvent(Event.Type.CONNECT, peer));
            flush();
            }

        @Override
        public void disconnect(EndPoint peer)
            {
            getEventCollector().add(new SimpleEvent(Event.Type.DISCONNECT, peer));
            flush();
            }

        @Override
        public void release(EndPoint peer)
            {
            getEventCollector().add(new SimpleEvent(Event.Type.RELEASE, peer));
            flush();
            }

        @Override
        public void flush()
            {
            getEventCollector().flush();
            }

        @Override
        public void setEventCollector(Collector collector)
            {
            m_collector = collector;
            }

        @Override
        public Collector getEventCollector()
            {
            return m_collector;
            }

        public static class EchoDriver
                implements Driver
            {
            @Override
            public void setDepot(Depot depot)
                {
                m_depot = depot;
                }

            @Override
            public Depot getDepot()
                {
                return m_depot;
                }

            @Override
            public EndPoint resolveEndPoint(String sName)
                {
                if (sName == null || !sName.equals("echo"))
                    {
                    return null;
                    }

                return new EndPoint()
                {
                @Override
                public String getCanonicalName()
                    {
                    return "echo";
                    }

                @Override
                public String toString()
                    {
                    return getCanonicalName();
                    }
                };
                }

            @Override
            public boolean isSupported(EndPoint point)
                {
                return point != null && point.getCanonicalName().equals("echo");
                }

            @Override
            public Bus createBus(EndPoint pointLocal)
                {
                if (isSupported(pointLocal))
                    {
                    return new EchoBus(pointLocal);
                    }
                throw new IllegalArgumentException("unsupported");
                }

            // ----- data members ---------------------------------------

            protected Depot m_depot;
            }
        // ----- data members -----------------------------------------------

        protected EndPoint         m_pointSelf;
        protected Collector m_collector;
        }

    // ----- helper methods -------------------------------------------------

    /**
     * Return a newly configured Histogram for measuring latencies.  Samples added to this histogram must be
     * measured in microseconds.
     *
     * @return the histogram
     */
    public static Histogram makeLatencyHistogram()
        {
        return new ScaledHistogram(10*1000000) // 10s ceiling
                .setFormatter((v) -> new Duration(v, Duration.Magnitude.MICRO).toString());
        }

    /**
     * Construct a message.
     *
     * @param nId      the requester id
     * @param fResp    true for response
     * @param ldtSent  the send time if a latency measurement is required, or 0 for none
     *
     * @return the message
     *
     * @throws IOException if an IO error occurs
     */
    public static BufferSequence getMessage(int nId, boolean fResp, long ldtSent)
            throws IOException
        {
        long cb = s_cbMsgMin;
        long cbDelta = s_cbMsgMax - s_cbMsgMin;
        if (cbDelta > 0)
            {
            cb += s_rand.nextLong() % cbDelta;
            }
        cb = Math.max(cb, MSG_HEADER_SIZE);

        SkipStream out = null;
        do
            {
            try
                {
                out = new SkipStream(s_manager, cb); // TODO: optionally don't supply a hint
                }
            catch (OutOfMemoryError e)
                {
                System.err.println(Thread.currentThread().getName() + " handling error: " + e);
                System.err.println(s_manager);
                s_cErrors.incrementAndGet();
                Thread.yield();
                }
            }
        while (out == null);

        // create message
        // format:
        // 4B transmitter ID
        // 1B msg type
        // 8B timestamp (or zero)
        // 8B payload size
        // payload
        long cbPayload = cb - MSG_HEADER_SIZE;

        out.writeInt(nId);
        out.writeBoolean(fResp);
        out.writeLong(ldtSent);
        out.writeLong(cbPayload);

        int cbChunk = s_cbChunk;
        switch (cbChunk)
            {
            default:
                for (; cbPayload >= cbChunk; cbPayload -= cbChunk)
                    {
                    out.write(s_abChunk);
                    }
                // fall through

            case 8:
                for (; cbPayload >= 8; cbPayload -= 8)
                    {
                    out.writeLong(0);
                    }
                // fall through

            case 4:
                for (; cbPayload >= 4; cbPayload -= 4)
                    {
                    out.writeInt(0);
                    }
                // fall through

            case 2:
                for (; cbPayload >= 2; cbPayload -= 2)
                    {
                    out.writeShort(0);
                    }
                // fall through

            case 1:
                for (; cbPayload >= 1; cbPayload -= 1)
                    {
                    out.write(0);
                    }
                break;

            case 0:
                // skip remainder
                out.skip(cbPayload);
                break;
            }

        return out.toBufferSequence();
        }


    /**
     * Configure a SocketBusDriver from the provided propeties.
     *
     * @param sPrefix  the property prefix
     * @param props    the properties
     * @param deps     the Dependencies object to populate
     *
     * @return the configured Dependencies
     *
     * @throws Exception on an error
     */
    public static SocketBusDriver.DefaultDependencies applyDriverProperties(
            String sPrefix, Properties props, SocketBusDriver.DefaultDependencies deps)
            throws Exception
        {
        deps.setBufferManager(s_manager);

        // SocketOptions
        SocketSettings sockOpts = new SocketSettings(SocketBusDriver.DefaultDependencies.DEFAULT_OPTIONS);
        String sName = sPrefix + "socket.rxbuffer";
        if (props.containsKey(sName))
            {
            sockOpts.set(SocketOptions.SO_RCVBUF, (int) new MemorySize(props.getProperty(sName)).getByteCount());
            }
        sName = sPrefix + "socket.txbuffer";
        if (props.containsKey(sName))
            {
            sockOpts.set(SocketOptions.SO_SNDBUF, (int) new MemorySize(props.getProperty(sName)).getByteCount());
            }
        sName = sPrefix + "socket.nodelay";
        if (props.containsKey(sName))
            {
            sockOpts.set(SocketOptions.TCP_NODELAY, Boolean.parseBoolean(props.getProperty(sName)));
            }
        sName = sPrefix + "socket.linger";
        if (props.containsKey(sName))
            {
            sockOpts.set(SocketOptions.SO_LINGER, Boolean.parseBoolean(props.getProperty(sName)));
            }
        deps.setSocketOptions(sockOpts);

        return deps;
        }


    /**
     * Parse the supplied properties object into a SimpleDepot.Dependencies
     * object.
     * 

* This allows for advanced bus properties to be configured. * * @param sPrefix the property prefix * @param props the properties * * @return the Dependencies object * * @throws Exception on error */ public static SimpleDepot.Dependencies parseDependencies(String sPrefix, final Properties props) throws Exception { SimpleDepot.DefaultDependencies depsDepot = new SimpleDepot.DefaultDependencies(); String sSslKeystore = sPrefix + "ssl.keystore"; if (props.containsKey(sSslKeystore)) { // SSL options String sKeystore = props.getProperty(sSslKeystore); String sPassword = props.getProperty(sPrefix + "ssl.password", "password"); SSLContext ctx = SSLContext.getInstance("TLS"); KeyManagerFactory keymanager = KeyManagerFactory.getInstance("SunX509"); TrustManagerFactory trustmanager = TrustManagerFactory.getInstance("SunX509"); KeyStore keystore = KeyStore.getInstance("JKS"); char[] achPassword = sPassword.toCharArray(); keystore.load(new URL("file:" + sKeystore).openStream(), achPassword); keymanager.init(keystore, achPassword); trustmanager.init(keystore); ctx.init(keymanager.getKeyManagers(), trustmanager.getTrustManagers(), new SecureRandom()); SSLSettings sslSettings = new SSLSettings().setSSLContext(ctx); String sClientAuth = props.getProperty(sPrefix + "ssl.clientauth", "none").toLowerCase(); switch (sClientAuth) { case "wanted": sslSettings.setClientAuth(SSLSocketProvider.ClientAuthMode.wanted); break; case "required": case "true": sslSettings.setClientAuth(SSLSocketProvider.ClientAuthMode.required); break; case "none": case "false": default: sslSettings.setClientAuth(SSLSocketProvider.ClientAuthMode.none); break; } depsDepot.setSSLSettings(sslSettings); } Map mapDriver = new HashMap<>(depsDepot.getDrivers()); for (Map.Entry entry : mapDriver.entrySet()) { Driver driver = entry.getValue(); if (driver instanceof SocketBusDriver) { SocketBusDriver sbDriver = (SocketBusDriver) driver; entry.setValue(new SocketBusDriver(applyDriverProperties( sPrefix, props, new SocketBusDriver.DefaultDependencies(sbDriver.getDependencies())))); } } mapDriver.put("EchoBus", new EchoBus.EchoDriver()); depsDepot.setDrivers(mapDriver); return depsDepot; } /** * Parse command line argments into key value pairs. * * @param asArg the command line arguments * * @return the key value map */ public static Map parseArgs(String[] asArg) { Map mapArgs = new HashMap(); for (int i = 0, c = asArg.length; i < c; ++i) { String arg = asArg[i]; if (arg.startsWith("-")) { String sKey = arg; String sVal = null; for (; i + 1 < c; ++i) { arg = asArg[i + 1]; if (arg.startsWith("-")) { try { Integer.valueOf(arg); } catch (NumberFormatException e) { break; // new key } } sVal = sVal == null ? arg : sVal + " " + arg; } mapArgs.put(sKey, sVal == null ? "true" : sVal); } else { throw new IllegalArgumentException("unepxected paramter " + arg); } } return mapArgs; } /** * Parse a string containing a space seperated list of EndPoint names. *

* This method supports range based endpoints for EndPoints which end in * :port. The range can be specified using ..port, * allowing the specification of a range such as: * http://localhost:80..89 to result in ten addresses. * * @param depot the Depot to use in resolving the names * @param sEps the EndPoint name string * * @return a List resolved EndPoints */ public static List parseEndPoints(Depot depot, String sEps) { List listEp = new ArrayList(); for (StringTokenizer tok = new StringTokenizer(sEps); tok.hasMoreElements(); ) { String sTok = tok.nextToken(); int of = sTok.indexOf(".."); if (of == -1) { listEp.add(depot.resolveEndPoint(sTok)); } else // range based endpoint, assumes name:port formatting { String sName = sTok.substring(0, of); int ofPort = Math.max(sName.lastIndexOf('.'), sName.lastIndexOf(':')); String sPrefix = sName.substring(0, ofPort + 1); int nPort = Integer.parseInt(sName.substring(ofPort + 1)); int nPortEnd = Integer.parseInt(sTok.substring(of + 2)); if (nPort < nPortEnd) { for (; nPort <= nPortEnd; ++nPort) { listEp.add(depot.resolveEndPoint(sPrefix + nPort)); } } else { for (; nPort >= nPortEnd; --nPort) { listEp.add(depot.resolveEndPoint(sPrefix + nPort)); } } } } return listEp; } public static void printHelp(PrintStream out) { out.println("MessageBusTest parameters:"); out.println("\t-bind list of one or more local EndPoints to create"); out.println("\t-peer list of one or more remote EndPoints to send to"); out.println("\t-rxThreads number of receive threads per bound EndPoint (negative for reentrant)"); out.println("\t-txThreads number of transmit threads per bound EndPoint"); out.println("\t-msgSize range of message sizes to send, expressed as min[..max]"); out.println("\t-chunkSize defines the number of bytes to process as a single unit, i.e. 1 for byte, 8 for long, 0 to disable"); out.println("\t-cached re-use message objects where possible, reducing buffer manager overhead"); out.println("\t-txRate target outbound data rate"); out.println("\t-txMaxBacklog the maximum backlog the test should produce per tx thread"); out.println("\t-rxRate target inbound data rate"); out.println("\t-flushFreq number of messages to send before flushing, or 0 for auto"); out.println("\t-latencyFreq number of messages to send before sampling latency"); out.println("\t-noReceipts specified if receipts should not be used, relies on GC to reclaim messages"); out.println("\t-manager buffer manager to utilize (net, direct, heap)"); out.println("\t-polite if specified this instance will not start sending until connected to"); out.println("\t-depotFactory the fully qualified class name of the Factory to use to obtain the Depot"); out.println("\t-reportInterval the report interval"); out.println("\t-polite if specified this instance will not start sending until connected to"); out.println("\t-block if specified a transmit thread will block while awaiting a response, optional value of spin duration"); out.println("\t-relay if specified then the process will relay any received messages to one of its peers"); out.println("\t-ignoreFlowControl if flow control events are to be ignored, use -txMaxBacklog to prevent OutOfMemory"); out.println("\t-poll is specified PollingEventCollector will be utilized"); out.println("\t-prompt if specified the user will be prompted before each send"); out.println("\t-tabular if specified the output will be in tabular format"); out.println("\t-warmup time duration or message count which will be discarded for warmup"); out.println("\t-single if specified an outgoing connection will emit just one message, then reconnect"); out.println("\t-verbose to enable verbose debugging output"); } /** * Run the MessageBusTest application. * * @param asArg the program arguments * * @throws IOException if an IO error occurs */ public static void main(String[] asArg) throws Exception { Map mapArgs = parseArgs(asArg); String sEpLocal = mapArgs.remove("-bind"); String sEpPeer = mapArgs.remove("-peer"); String sRxThreads = mapArgs.remove("-rxThreads"); String sTxThreads = mapArgs.remove("-txThreads"); String sMsgSize = mapArgs.remove("-msgSize"); String sChunkSize = mapArgs.remove("-chunkSize"); String sCached = mapArgs.remove("-cached"); String sFlushFreq = mapArgs.remove("-flushFreq"); String sTxRate = mapArgs.remove("-txRate"); String sTxMaxBacklog = mapArgs.remove("-txMaxBacklog"); String sRxRate = mapArgs.remove("-rxRate"); String sLatFreq = mapArgs.remove("-latencyFreq"); String sNoReceipt = mapArgs.remove("-noReceipts"); String sManager = mapArgs.remove("-manager"); String sVerbose = mapArgs.remove("-verbose"); String sPolite = mapArgs.remove("-polite"); String sBlock = mapArgs.remove("-block"); String sRelay = mapArgs.remove("-relay"); String sIgnoreFC = mapArgs.remove("-ignoreFlowControl"); String sTabular = mapArgs.remove("-tabular"); String sFactoryDepot = mapArgs.remove("-depotFactory"); String sReportInterval = mapArgs.remove("-reportInterval"); String sPollingColl = mapArgs.remove("-poll"); String sPrompt = mapArgs.remove("-prompt"); String sWarmup = mapArgs.remove("-warmup"); String sSingle = mapArgs.remove("-single"); if (sTxRate != null && Character.isDigit(sTxRate.charAt(sTxRate.length() - 1))) { sTxRate += "MBps"; } if (sRxRate != null && Character.isDigit(sRxRate.charAt(sRxRate.length() - 1))) { sRxRate += "MBps"; } if (sReportInterval == null) { sReportInterval = "5s"; } else if (Character.isDigit(sReportInterval.charAt(sReportInterval.length() - 1))) { sReportInterval += "s"; } s_fVerbose = sVerbose != null; s_fFlowControl = sIgnoreFC == null; s_fCached = sCached != null; s_fPollingCollector = sPollingColl != null; s_fPrompt = sPrompt != null; s_fSingleUseConnection = sSingle != null; long cReportMillis = new Duration(sReportInterval).as(Duration.Magnitude.MILLI); boolean fPolite = sPolite != null && sPolite.equals("true"); boolean fBlock = sBlock != null && !sBlock.equals("false"); boolean fRelay = sRelay != null && !sRelay.equals("false"); boolean fTabular = sTabular != null && sTabular.equals("true"); int cRxThreads = Math.abs(sRxThreads == null ? 1 : Integer.parseInt(sRxThreads)); int cTxThreads = Math.abs(sTxThreads == null ? fRelay ? 0 : 1 : Integer.parseInt(sTxThreads)); boolean fReentrant = sRxThreads != null && sRxThreads.startsWith("-"); long cbsOut = sTxRate == null ? -1 : new Bandwidth(sTxRate).as(Rate.BYTES); long cbsIn = sRxRate == null ? -1 : new Bandwidth(sRxRate).as(Rate.BYTES); long cbMaxBacklog = sTxMaxBacklog == null ? -1 : new MemorySize(sTxMaxBacklog).getByteCount(); long cMsgWarmup = 0; long cMillisWarmup = 0; if (sWarmup != null) { try { cMsgWarmup = Integer.parseInt(sWarmup); } catch (Exception e) { cMillisWarmup = new Duration(sWarmup).as(Duration.Magnitude.MILLI); } } if (fBlock && !(sBlock.equals("true"))) { AWAIT_SPIN_NANOS = new Duration(sBlock).getNanos(); } s_fBlock = fBlock; if (fRelay) { if (cTxThreads != 0) { System.err.println("-relay and -txThreads cannot both be specified"); System.exit(1); } } s_fRelay = fRelay; // select default thread counts if (sRxThreads == null || cRxThreads == 0) { // no rx threads specified, default to reentrant mode on all available cores // note that because we are reentrant the actual number of threads is up to the bus // impl; this is only selecting how many can execute concurrently cRxThreads = Platform.getPlatform().getFairShareProcessors() * 17; fReentrant = true; } if (s_fPollingCollector && cRxThreads != 1) { System.err.println("\nWARNING: polling collector generally requires -rxThreads 1\n"); } if (fReentrant && cbsIn != -1) { if (sRxThreads == null) { // user asked for rx throttling and didn't specify rxThreads, thus didn't ask for reentrancy; disable fReentrant = false; cRxThreads = 1; } else { System.err.println("inbound throttling (-rxRate) not available with reentrant processing (-rxThreads <= 0)"); System.exit(1); } } if (sLatFreq == null) { s_nLatencyFreq = 100; } else { s_nLatencyFreq = Integer.parseInt(sLatFreq); } s_fReceipts = sNoReceipt == null || !sNoReceipt.equals("true"); if (!s_fReceipts && cbMaxBacklog != -1) { throw new IllegalArgumentException("-txMaxBacklog requires receipts"); } if (sManager == null || sManager.equals("net")) { s_manager = BufferManagers.getNetworkDirectManager(); } else if (sManager.equals("direct")) { s_manager = BufferManagers.getDirectManager(); } else if (sManager.equals("heap")) { s_manager = BufferManagers.getHeapManager(); } else { throw new IllegalArgumentException("unknown heap manager: " + sManager); } long cbMin, cbMax, cbAvg; if (sMsgSize == null) { cbMin = cbMax = cbAvg = 4096; } else { int ofMsgDelim = sMsgSize.indexOf(".."); if (ofMsgDelim == -1) { // fixed message size cbMin = cbMax = cbAvg = new MemorySize(sMsgSize).getByteCount(); } else if (s_fCached) { System.err.println("-cached does not support variable sized messaging"); throw new IllegalArgumentException(); } else { // range based cbMin = new MemorySize(sMsgSize.substring(0, ofMsgDelim)).getByteCount(); cbMax = new MemorySize(sMsgSize.substring(ofMsgDelim + 2)).getByteCount(); if (cbMax < cbMin) { // wrong order, swap them long n = cbMax; cbMax = cbMin; cbMin = n; } cbAvg = cbMax - ((cbMax - cbMin) / 2); } } if (cbMin < MSG_HEADER_SIZE) { System.out.println("increasing minimum message size to " + MSG_HEADER_SIZE + " bytes to satisfy test requirements\n"); cbMin = MSG_HEADER_SIZE; } if (cbMax < MSG_HEADER_SIZE) { cbMax = MSG_HEADER_SIZE; } if (cbAvg < MSG_HEADER_SIZE) { cbAvg = MSG_HEADER_SIZE; } s_cbMsgMin = cbMin; s_cbMsgMax = cbMax; int cbChunk = 0; if (sChunkSize != null) { cbChunk = (int) new MemorySize(sChunkSize).getByteCount(); } s_cbChunk = (int) Math.min(cbMin, cbChunk); s_abChunk = new byte[cbChunk]; int nFlushOn = sFlushFreq == null ? 0 : Integer.parseInt(sFlushFreq); if (!mapArgs.isEmpty()) { System.err.println("unknown parameter " + mapArgs.keySet().iterator().next()); System.err.println(); printHelp(System.err); System.exit(1); } // create local busses Depot depot; if (sFactoryDepot == null) { depot = new SimpleDepot(parseDependencies("depot.", System.getProperties())); } else { depot = ((Factory) Class.forName(sFactoryDepot).newInstance()).create(); } // resolve peer EndPoints EndPoint[] aPeer = new EndPoint[0]; if (sEpPeer != null) { aPeer = parseEndPoints(depot, sEpPeer).toArray(aPeer); } List listBus = new ArrayList(); if (sEpLocal == null) { if (aPeer.length == 0) { listBus.add(depot.createMessageBus(null)); } else { // attempt to compute a local endpoint from remote endpoint String sPeer = aPeer[0].getCanonicalName(); if (sPeer.contains(":")) { listBus.add(depot.createMessageBus( depot.resolveEndPoint(sPeer.substring(0, sPeer.indexOf(':')) + "://0.0.0.0:0"))); } } } else { for (EndPoint epBind : parseEndPoints(depot, sEpLocal)) { try { listBus.add(depot.createMessageBus(epBind)); } catch (Exception e) { try { // "hidden" MemoryBus test mode listBus.add(depot.createMemoryBus(epBind)); } catch (Exception e2) { e2.printStackTrace(); throw e; } } } } // bring each bus up int cBus = listBus.size(); Set setDemuxer = new HashSet(); EventProcessor[] aProcessor = new EventProcessor[cBus * cRxThreads]; Transmitter[] aTransmitter = new Transmitter[cBus * cTxThreads]; long cbsInProc = cbsIn == -1 ? cbsIn : Math.max(1, cbsIn / aProcessor.length); int iProc = 0; int iTrans = 0; for (Bus bus : listBus) { AtomicInteger fBacklogLocal = new AtomicInteger(); Set setReady = Collections.newSetFromMap(new ConcurrentHashMap()); EndPoint boundEp = bus.getLocalEndPoint(); EventProcessor[] aProc = new EventProcessor[cRxThreads]; Transmitter[] aTrans = new Transmitter[cTxThreads]; // select peers to operate against Set setPeer = new HashSet(); for (EndPoint peer : aPeer) { if (!peer.equals(boundEp)) { setPeer.add(peer); } } setPeer = Collections.unmodifiableSet(setPeer); if (setPeer.isEmpty()) { Transmitter[] aTransNew = new Transmitter[aTransmitter.length - cTxThreads]; System.arraycopy(aTransmitter, 0, aTransNew, 0, aTransNew.length); aTransmitter = aTransNew; aTrans = null; } else { // construct transmitters for (int i = 0, c = aTrans.length; i < c; ++i) { aTransmitter[iTrans++] = aTrans[i] = new Transmitter(bus, i, setReady, nFlushOn, fBacklogLocal, cbMaxBacklog); } } // construct processors for (int i = 0; i < cRxThreads; ++i) { aProcessor[iProc++] = aProc[i] = new EventProcessor(bus, setPeer, setReady, aTrans, cbsInProc, nFlushOn, fBacklogLocal); } if (s_fPollingCollector) { bus.setEventCollector(new QueueingEventCollector()); } else { DemultiplexingCollector collector = new DemultiplexingCollector(bus, aProc); setDemuxer.add(collector); bus.setEventCollector(collector); } bus.open(); if (!fPolite) { // TODO: wait for open events // establish connections for (EndPoint peer : setPeer) { bus.connect(peer); } } } // start processors if (!fReentrant) { for (EventProcessor proc : aProcessor) { proc.start(); } } // start transmitters; they will wait for connections before sending long cbsOutTrans = cbsOut == -1 ? cbsOut : Math.max(1, cbsOut / aTransmitter.length); for (Transmitter trans : aTransmitter) { trans.setTransmitRate(cbsOutTrans); trans.start(); } // stats logging // add a blank line printout during shutdown Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { System.out.println(); } }); class Stats { public Stats(long ldt) { this.ldt = ldt; } long ldt; long cMsgIn; long cMsgOut; long cbIn; long cbOut; long cbInCollected; long cReceipts; long cReceiptSamples; long cReceiptNanos; long cResponses; long cResponseNanos; long cResponseNanosMin = Long.MAX_VALUE; long cResponseNanosMax = -1; long cBacklogLocal; long cMillisBacklogLocal; long cBacklogRemote; long cMillisBacklogRemote; long cbInPendingLife; long cbOutPendingLife; long cErrors; long cConnections; Histogram histLatency = makeLatencyHistogram(); } if (fTabular) { System.out.println( "msg/s in\t" + "bytes/s in\t" + "msg/s out\t" + "bytes/s out\t" + "avg receipt latency nanos\t" + "min response latency nanos\t" + "avg response latency nanos\t" + "effective latency nanos\t" + "max response latency nanos\t" + "in backlog percentage\t" + "in backlog events\t" + "in backlog bytes\t" + "out backlog percentage\t" + "out backlog events\t" + "out backlog bytes\t" + "connections\t" + "errors"); } long ldtWarmStart = 0; Stats statsWarm = null; // base stats after warmup has completed Stats statsPrev = null; // stats from prev cycle Stats stats = null; // stats from this cycle for (int iReport = 0; ; ++iReport) { Blocking.sleep(statsWarm == null ? 10 : cReportMillis); stats = new Stats(System.currentTimeMillis()); for (EventProcessor proc : aProcessor) { stats.cReceipts += proc.getReceiptsIn(); stats.cReceiptSamples += proc.getReceiptSamples(); stats.cReceiptNanos += proc.getReceiptNanos(); stats.cResponses += proc.getResponsesIn(); stats.cResponseNanos += proc.getResponseNanos(); stats.histLatency.addSamples(proc.getResponseLatencyHistogram()); stats.cResponseNanosMax = Math.max(proc.m_cResponseNanosMax, stats.cResponseNanosMax); proc.m_cResponseNanosMax = -1; stats.cResponseNanosMin = Math.min(proc.m_cResponseNanosMin, stats.cResponseNanosMin); proc.m_cResponseNanosMin = Long.MAX_VALUE; stats.cMsgIn += proc.getMessagesIn(); stats.cMsgOut += proc.getMessagesOut(); stats.cbIn += proc.getBytesIn(); stats.cbOut += proc.getBytesOut(); stats.cBacklogLocal += proc.getLocalBacklogEvents(); stats.cMillisBacklogLocal += proc.getLocalBacklogMillis(); stats.cBacklogRemote += proc.getRemoteBacklogEvents(); stats.cConnections += proc.getConnectionCount(); } for (Transmitter trans : aTransmitter) { stats.cMsgOut += trans.getMessagesOut(); stats.cbOut += trans.getBytesOut(); stats.cMillisBacklogRemote += trans.getRemoteBacklogMillis(); } if (setDemuxer != null) { for (DemultiplexingCollector collector : setDemuxer) { stats.cbInCollected += collector.getReceivedBytes(); } } stats.cErrors = s_cErrors.get(); if (statsWarm == null) { if (stats.cConnections > 0 && Math.max(stats.cMsgIn, stats.cMsgOut) > cMsgWarmup) { if (ldtWarmStart == 0) { ldtWarmStart = stats.ldt; } if (stats.ldt - ldtWarmStart >= cMillisWarmup) { // we've completed warmup, note that the duration between ldtWarmStart and stastWarm.ldt is // questionable, but also never used statsPrev = statsWarm = stats; } iReport = 0; } continue; } else if (stats.cConnections == 0) { statsWarm = null; ldtWarmStart = 0; continue; } for (Stats statsComp : new Stats[]{statsPrev, statsWarm}) { long cDeltaMillis = stats.ldt - statsComp.ldt; long cMsgInDelta = stats.cMsgIn - statsComp.cMsgIn; long cMsgOutDelta = stats.cMsgOut - statsComp.cMsgOut; long cbInDelta = stats.cbIn - statsComp.cbIn; long cbOutDelta = stats.cbOut - statsComp.cbOut; long cReceiptDelta = stats.cReceiptSamples - statsComp.cReceiptSamples; long cReceiptNanosDelta = stats.cReceiptNanos - statsComp.cReceiptNanos; long cResponseDelta = stats.cResponses - statsComp.cResponses; long cResponseNanosDelta = stats.cResponseNanos - statsComp.cResponseNanos; long cBacklogLocalDelta = stats.cBacklogLocal - statsComp.cBacklogLocal; long cMillisBacklogLocalDelta = stats.cMillisBacklogLocal - statsComp.cMillisBacklogLocal; long cBacklogRemoteDelta = stats.cBacklogRemote - statsComp.cBacklogRemote; long cMillisBacklogRemoteDelta = stats.cMillisBacklogRemote - statsComp.cMillisBacklogRemote; long cErrorsDelta = stats.cErrors - statsComp.cErrors; long cbOutPending = (stats.cMsgOut - stats.cReceipts) * cbAvg; long cbInPending = s_fPollingCollector ? -1 : stats.cbInCollected - stats.cbIn; double dflSeconds = cDeltaMillis / 1000.0; long MSGsIn = Math.round(cMsgInDelta / dflSeconds); long MSGsOut = Math.round(cMsgOutDelta / dflSeconds); long BLsLocal = Math.round(cBacklogLocalDelta / dflSeconds); long BLsRemote = Math.round(cBacklogRemoteDelta / dflSeconds); long lPctBacklogLocal = (100 * cMillisBacklogLocalDelta) / (cDeltaMillis * cBus); long lPctBacklogRemote = aTransmitter.length == 0 ? -1 : (100 * cMillisBacklogRemoteDelta) / (cDeltaMillis * aTransmitter.length); long cResponseNanosDeltaEff = (long) (cResponseNanosDelta * (1.0 / (1.0 - lPctBacklogRemote / 100.0))); if (statsComp == statsWarm) { // for lifetime we compute the average cost stats.cbOutPendingLife = statsPrev.cbOutPendingLife + cbOutPending; cbOutPending = stats.cbOutPendingLife / (iReport + 1); stats.cbInPendingLife = statsPrev.cbInPendingLife + cbInPending; cbInPending = stats.cbInPendingLife / (iReport + 1); // carry min/max across iterations stats.cConnections = Math.max(stats.cConnections, statsPrev.cConnections); stats.cResponseNanosMin = Math.min(stats.cResponseNanosMin, statsPrev.cResponseNanosMin); stats.cResponseNanosMax = Math.max(stats.cResponseNanosMax, statsPrev.cResponseNanosMax); } if (fTabular) { System.out.println("" + MSGsIn + '\t' + cbInDelta / dflSeconds + '\t' + MSGsOut + '\t' + cbOutDelta / dflSeconds + '\t' + (cReceiptDelta == 0 ? -1 : cReceiptNanosDelta / cReceiptDelta) + '\t' + (cResponseDelta == 0 ? -1 : stats.cResponseNanosMin) + '\t' + (cResponseDelta == 0 ? -1 : cResponseNanosDelta / cResponseDelta) + '\t' + (cResponseDelta == 0 ? -1 : cResponseNanosDeltaEff / cResponseDelta) + '\t' + (cResponseDelta == 0 ? -1 : stats.cResponseNanosMax) + '\t' + lPctBacklogLocal + '\t' + BLsLocal + '\t' + cbInPending + '\t' + lPctBacklogRemote + '\t' + BLsRemote + '\t' + cbOutPending + '\t' + stats.cConnections + '\t' + cErrorsDelta); break; // skip over lifetime step } else { System.out.println((statsComp == statsPrev ? "now: " : "life: ") + "throughput(" + "out " + MSGsOut + "msg/s " + new Bandwidth(8 * cbOutDelta / dflSeconds, Rate.BITS) + ", in " + MSGsIn + "msg/s " + new Bandwidth(8 * cbInDelta / dflSeconds, Rate.BITS) + "), " + "latency(" + "response" + (cResponseDelta == 0 ? " n/a" : "(avg " + new Duration(cResponseNanosDelta / cResponseDelta) + ", effective " + new Duration(cResponseNanosDeltaEff / cResponseDelta) + ", min " + new Duration(stats.cResponseNanosMin) + ", max " + new Duration(stats.cResponseNanosMax) + ")") + ", receipt " + (cReceiptDelta == 0 ? "n/a" : new Duration(cReceiptNanosDelta / cReceiptDelta)) + "), " + "backlog(" + "out " + (lPctBacklogRemote < 0 ? "n/a " : lPctBacklogRemote + "% ") + BLsRemote + "/s " + (s_fReceipts ? new MemorySize(cbOutPending) : "n/a") + ", in " + lPctBacklogLocal + "% " + BLsLocal + "/s " + (cbInPending < 0 ? "n/a" : new MemorySize(cbInPending)) + "), " + "connections " + stats.cConnections + ", errors " + cErrorsDelta); if (s_fVerbose && stats.histLatency.getSampleCount() > 0) { System.out.println("\tlatency detail: " + stats.histLatency.compare(statsComp.histLatency)); } } } if (s_fVerbose) { for (Bus bus : listBus) { System.out.println("bus: " + bus); } System.out.println("mgr: " + s_manager); RuntimeMXBean beanRuntime = ManagementFactory.getRuntimeMXBean(); System.out.print("jvm: " + beanRuntime.getSpecVersion() + " " + beanRuntime.getVmVersion() + " "); for (String s : beanRuntime.getInputArguments()) { System.out.print(s + " "); } System.out.print("\ncmd: "); for (String s : asArg) { System.out.print(s + " "); } System.out.println(); System.out.println("time: " + new Date() + "/" + new Duration(System.currentTimeMillis() - statsWarm.ldt, Duration.Magnitude.MILLI)); } if (!fTabular) { System.out.println(); } statsPrev = stats; } } // ---- constants ------------------------------------------------------- /** * The message header size used in the test. */ public static final int MSG_HEADER_SIZE = 21; // ---- static data members --------------------------------------------- /** * The BufferManager to use in the test. */ protected static BufferManager s_manager; /** * Flag for verbose logging. */ protected static boolean s_fVerbose; /** * True if user should be prompted before sending. */ protected static boolean s_fPrompt; /** * Flag for using cached messages */ protected static boolean s_fCached; /** * Flag for using polling collector */ protected static boolean s_fPollingCollector; /** * The frequency (in messages), at which latency will be sampled. */ public static int s_nLatencyFreq = 100; /** * True if receipts should be used. */ public static boolean s_fReceipts = true; /** * The minimum message size. */ public static long s_cbMsgMin; /** * The maximum message size */ public static long s_cbMsgMax; /** * The unit size to process at. */ public static int s_cbChunk; /** * To be used for chunked writes. */ public static byte[] s_abChunk; /** * How long to spin waiting for results */ protected static long AWAIT_SPIN_NANOS; /** * Switch govering if flow control events should be respected. */ public static boolean s_fFlowControl = true; /** * True if this instance is to act as a message relay */ public static boolean s_fRelay = false; /** * True if this instance is in blocking mode. */ public static boolean s_fBlock = false; /** * True to use each connection for just one message. */ public static boolean s_fSingleUseConnection; /** * Count of the number of errors encountered during the test. */ public static AtomicLong s_cErrors = new AtomicLong(); /** * Shared randomizer. */ public static Random s_rand = new Random(); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy