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

com.rabbitmq.perf.MulticastParams Maven / Gradle / Ivy

There is a newer version: 2.22.1
Show newest version
// Copyright (c) 2007-Present Pivotal Software, Inc.  All rights reserved.
//
// This software, the RabbitMQ Java client library, is triple-licensed under the
// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2
// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see
// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2.  For the ASL,
// please see LICENSE-APACHE2.
//
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
// either express or implied. See the LICENSE file for specific language governing
// rights and limitations of this software.
//
// If you have any questions regarding licensing, please contact us at
// [email protected].

package com.rabbitmq.perf;

import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ShutdownSignalException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeoutException;

import static com.rabbitmq.perf.Recovery.setupRecoveryProcess;

public class MulticastParams {

    private long confirm = -1;
    private int confirmTimeout = 30;
    private int consumerCount = 1;
    private int producerCount = 1;
    private int consumerChannelCount = 1;
    private int producerChannelCount = 1;
    private int consumerTxSize = 0;
    private int producerTxSize = 0;
    private int channelPrefetch = 0;
    private int consumerPrefetch = 0;
    private int minMsgSize = 0;

    private int timeLimit = 0;
    private float producerRateLimit = 0.0f;
    private float consumerRateLimit = 0.0f;
    private int producerMsgCount = 0;
    private int consumerMsgCount = 0;
    private boolean consumerSlowStart = false;

    private String exchangeName = "direct";
    private String exchangeType = "direct";
    private List queueNames = new ArrayList<>();
    private String routingKey = null;
    private boolean randomRoutingKey = false;
    private boolean skipBindingQueues = false;

    private List flags = new ArrayList<>();

    private int multiAckEvery = 0;
    private boolean autoAck = false;
    private boolean autoDelete = true;

    private List bodyFiles = new ArrayList<>();
    private String bodyContentType = null;

    private boolean predeclared = false;
    private boolean useMillis = false;

    private Map queueArguments = null;

    private int consumerLatencyInMicroseconds = 0;

    private String queuePattern = null;
    private int queueSequenceFrom = -1;
    private int queueSequenceTo = -1;

    private Map messageProperties = null;

    private TopologyHandler topologyHandler;

    private int heartbeatSenderThreads = -1;

    private int routingKeyCacheSize = 0;
    private boolean exclusive = false;
    private int publishingInterval = -1;
    private int producerRandomStartDelayInSeconds;
    private int producerSchedulerThreadCount = -1;
    private int consumersThreadPools = -1;
    private int shutdownTimeout = 5;

    public void setExchangeType(String exchangeType) {
        this.exchangeType = exchangeType;
    }

    public void setExchangeName(String exchangeName) {
        this.exchangeName = exchangeName;
    }

    public void setQueueNames(List queueNames) {
        if(queueNames == null) {
            this.queueNames = new ArrayList<>();
        } else {
            this.queueNames = new ArrayList<>(queueNames);
        }
    }

    public void setRoutingKey(String routingKey) {
        this.routingKey = routingKey;
    }

    public void setRandomRoutingKey(boolean randomRoutingKey) {
        this.randomRoutingKey = randomRoutingKey;
    }
    
    public void setSkipBindingQueues(boolean skipBindingQueues) {
    	this.skipBindingQueues = skipBindingQueues;
    }

    public void setProducerRateLimit(float producerRateLimit) {
        this.producerRateLimit = producerRateLimit;
    }

    public void setProducerCount(int producerCount) {
        this.producerCount = producerCount;
    }

    public void setProducerChannelCount(int producerChannelCount) {
        this.producerChannelCount = producerChannelCount;
    }

    public void setConsumerRateLimit(float consumerRateLimit) {
        this.consumerRateLimit = consumerRateLimit;
    }

    public void setConsumerCount(int consumerCount) {
        this.consumerCount = consumerCount;
    }

    public void setConsumerChannelCount(int consumerChannelCount) {
        this.consumerChannelCount = consumerChannelCount;
    }
    
    public void setConsumerSlowStart(boolean slowStart) {
        this.consumerSlowStart = slowStart;
    }

    public void setProducerTxSize(int producerTxSize) {
        this.producerTxSize = producerTxSize;
    }

    public void setConsumerTxSize(int consumerTxSize) {
        this.consumerTxSize = consumerTxSize;
    }

    public void setConfirm(long confirm) {
        this.confirm = confirm;
    }

    public void setConfirmTimeout(int confirmTimeout) {
        this.confirmTimeout = confirmTimeout;
    }

    public void setAutoAck(boolean autoAck) {
        this.autoAck = autoAck;
    }

    public void setMultiAckEvery(int multiAckEvery) {
        this.multiAckEvery = multiAckEvery;
    }

    public void setChannelPrefetch(int channelPrefetch) {
        this.channelPrefetch = channelPrefetch;
    }

    public void setConsumerPrefetch(int consumerPrefetch) {
        this.consumerPrefetch = consumerPrefetch;
    }

    public void setMinMsgSize(int minMsgSize) {
        this.minMsgSize = minMsgSize;
    }

    public void setTimeLimit(int timeLimit) {
        this.timeLimit = timeLimit;
    }

    public void setUseMillis(boolean useMillis) {
        this.useMillis = useMillis;
    }

    public void setProducerMsgCount(int producerMsgCount) {
        this.producerMsgCount = producerMsgCount;
    }

    public void setConsumerMsgCount(int consumerMsgCount) {
        this.consumerMsgCount = consumerMsgCount;
    }

    public void setMsgCount(int msgCount) {
        setProducerMsgCount(msgCount);
        setConsumerMsgCount(msgCount);
    }

    public void setFlags(List flags) {
        this.flags = flags;
    }

    public void setAutoDelete(boolean autoDelete) {
        this.autoDelete = autoDelete;
    }

    public void setPredeclared(boolean predeclared) {
        this.predeclared = predeclared;
    }

    public void setQueueArguments(Map queueArguments) {
        this.queueArguments = queueArguments;
    }

    public void setConsumerLatencyInMicroseconds(int consumerLatencyInMicroseconds) {
        this.consumerLatencyInMicroseconds = consumerLatencyInMicroseconds;
    }

    public void setMessageProperties(Map messageProperties) {
        this.messageProperties = messageProperties;
    }

    public void setConsumersThreadPools(int consumersThreadPools) {
        this.consumersThreadPools = consumersThreadPools;
    }

    public void setShutdownTimeout(int shutdownTimeout) {
        this.shutdownTimeout = shutdownTimeout;
    }

    public int getConsumerCount() {
        return consumerCount;
    }

    public int getConsumerChannelCount() {
        return consumerChannelCount;
    }
    
    public boolean getConsumerSlowStart() {
        return consumerSlowStart;
    }

    public int getConsumerThreadCount() {
        return consumerCount * consumerChannelCount;
    }

    public int getProducerCount() {
        return producerCount;
    }

    public int getProducerChannelCount() {
        return producerChannelCount;
    }

    public int getProducerThreadCount() {
        return producerCount * producerChannelCount;
    }

    public int getMinMsgSize() {
        return minMsgSize;
    }

    public void setBodyFiles(List bodyFiles) {
        if (bodyFiles == null) {
            this.bodyFiles = new ArrayList<>();
        } else {
            this.bodyFiles = new ArrayList<>(bodyFiles);
        }
    }

    public void setBodyContentType(String bodyContentType) {
        this.bodyContentType = bodyContentType;
    }

    public void setQueuePattern(String queuePattern) {
        this.queuePattern = queuePattern;
    }

    public void setQueueSequenceFrom(int queueSequenceFrom) {
        this.queueSequenceFrom = queueSequenceFrom;
    }

    public void setQueueSequenceTo(int queueSequenceTo) {
        this.queueSequenceTo = queueSequenceTo;
    }

    public void setHeartbeatSenderThreads(int heartbeatSenderThreads) {
        this.heartbeatSenderThreads = heartbeatSenderThreads;
    }

    public int getHeartbeatSenderThreads() {
        return heartbeatSenderThreads <= 0 ? producerCount + consumerCount : this.heartbeatSenderThreads;
    }

    public int getTimeLimit() {
        return timeLimit;
    }

    public int getProducerMsgCount() {
        return producerMsgCount;
    }

    public int getConsumerMsgCount() {
        return consumerMsgCount;
    }

    public void setRoutingKeyCacheSize(int routingKeyCacheSize) {
        this.routingKeyCacheSize = routingKeyCacheSize;
    }

    public int getConsumersThreadPools() {
        return consumersThreadPools;
    }

    public int getShutdownTimeout() {
        return shutdownTimeout;
    }

    public Producer createProducer(Connection connection, Stats stats, MulticastSet.CompletionHandler completionHandler) throws IOException {
        Channel channel = connection.createChannel(); //NOSONAR
        if (producerTxSize > 0) channel.txSelect();
        if (confirm >= 0) channel.confirmSelect();
        TopologyRecording topologyRecording = new TopologyRecording();
        if (!predeclared || !exchangeExists(connection, exchangeName)) {
            channel.exchangeDeclare(exchangeName, exchangeType);
            topologyRecording.recordExchange(exchangeName, exchangeType);
        }
        MessageBodySource messageBodySource;
        TimestampProvider tsp;
        if (bodyFiles.size() > 0) {
            tsp = new TimestampProvider(useMillis, true);
            messageBodySource = new LocalFilesMessageBodySource(bodyFiles, bodyContentType);
        } else {
            tsp = new TimestampProvider(useMillis, false);
            messageBodySource = new TimeSequenceMessageBodySource(tsp, minMsgSize);
        }

        float calculatedProducerRateLimit = this.producerRateLimit;
        if (this.publishingInterval > 0) {
            calculatedProducerRateLimit = 1.0f / (float) this.publishingInterval;
        }

        Recovery.RecoveryProcess recoveryProcess = setupRecoveryProcess(connection, topologyRecording);

        final Producer producer = new Producer(new ProducerParameters()
            .setChannel(channel).setExchangeName(exchangeName).setId(this.topologyHandler.getRoutingKey())
            .setRandomRoutingKey(randomRoutingKey).setFlags(flags).setTxSize(producerTxSize)
            .setRateLimit(calculatedProducerRateLimit).setMsgLimit(producerMsgCount).setConfirm(confirm)
            .setConfirmTimeout(confirmTimeout).setMessageBodySource(messageBodySource).setTsp(tsp)
            .setStats(stats).setMessageProperties(messageProperties).setCompletionHandler(completionHandler)
            .setRoutingKeyCacheSize(this.routingKeyCacheSize)
            .setRandomStartDelayInSeconds(this.producerRandomStartDelayInSeconds)
            .setRecoveryProcess(recoveryProcess)
        );
        channel.addReturnListener(producer);
        channel.addConfirmListener(producer);
        this.topologyHandler.next();
        return producer;
    }

    public Consumer createConsumer(Connection connection, Stats stats, MulticastSet.CompletionHandler completionHandler) throws IOException {
        TopologyHandlerResult topologyHandlerResult = this.topologyHandler.configureQueuesForClient(connection);
        connection = topologyHandlerResult.connection;
        Channel channel = connection.createChannel(); //NOSONAR
        if (consumerTxSize > 0) channel.txSelect();
        if (consumerPrefetch > 0) channel.basicQos(consumerPrefetch);
        if (channelPrefetch > 0) channel.basicQos(channelPrefetch, true);

        boolean timestampInHeader;
        if (bodyFiles.size() > 0) {
            timestampInHeader = true;
        } else {
            timestampInHeader = false;
        }
        TimestampProvider tsp = new TimestampProvider(useMillis, timestampInHeader);

        Recovery.RecoveryProcess recoveryProcess = setupRecoveryProcess(connection, topologyHandlerResult.topologyRecording);

        Consumer consumer = new Consumer(channel, this.topologyHandler.getRoutingKey(), topologyHandlerResult.configuredQueues,
                                         consumerTxSize, autoAck, multiAckEvery,
                                         stats, consumerRateLimit, consumerMsgCount,
                                         consumerLatencyInMicroseconds, tsp, completionHandler, recoveryProcess);
        this.topologyHandler.next();
        return consumer;
    }

    public TopologyHandlerResult configureAllQueues(Connection connection) throws IOException {
        return this.topologyHandler.configureAllQueues(connection);
    }

    public void init() {
        if (this.queuePattern == null) {
            this.topologyHandler = new FixedQueuesTopologyHandler(this, this.routingKey, this.queueNames);
        } else {
            this.topologyHandler = new SequenceTopologyHandler(this, this.queueSequenceFrom, this.queueSequenceTo, this.queuePattern);
        }

    }

    public void resetTopologyHandler() {
        this.topologyHandler.reset();
    }

    private static boolean exchangeExists(Connection connection, final String exchangeName) throws IOException {
        if ("".equals(exchangeName)) {
            // NB: default exchange always exists
            return true;
        } else {
            return exists(connection, ch -> ch.exchangeDeclarePassive(exchangeName));
        }
    }

    private static boolean queueExists(Connection connection, final String queueName) throws IOException {
        return queueName != null && exists(connection, ch -> ch.queueDeclarePassive(queueName));
    }

    public boolean hasLimit() {
        return this.timeLimit > 0 || this.consumerMsgCount > 0 || this.producerMsgCount > 0;
    }

    public void setExclusive(boolean exclusive) {
        this.exclusive = exclusive;
    }

    public boolean isExclusive() {
        return exclusive;
    }

    public void setPublishingInterval(int publishingIntervalInSeconds) {
        this.publishingInterval = publishingIntervalInSeconds;
    }

    public int getPublishingInterval() {
        return publishingInterval;
    }

    public void setProducerRandomStartDelayInSeconds(int producerRandomStartDelayInSeconds) {
        this.producerRandomStartDelayInSeconds = producerRandomStartDelayInSeconds;
    }

    public int getProducerRandomStartDelayInSeconds() {
        return producerRandomStartDelayInSeconds;
    }

    public int getProducerSchedulerThreadCount() {
        return producerSchedulerThreadCount;
    }

    public void setProducerSchedulerThreadCount(int producerSchedulerThreadCount) {
        this.producerSchedulerThreadCount = producerSchedulerThreadCount;
    }

    private interface Checker {
        void check(Channel ch) throws IOException;
    }

    private static boolean exists(Connection connection, Checker checker) throws IOException {
        try {
            Channel ch = connection.createChannel();
            checker.check(ch);
            ch.abort();
            return true;
        }
        catch (IOException e) {
            ShutdownSignalException sse = (ShutdownSignalException) e.getCause();
            if (!sse.isHardError()) {
                AMQP.Channel.Close closeMethod = (AMQP.Channel.Close) sse.getReason();
                if (closeMethod.getReplyCode() == AMQP.NOT_FOUND) {
                    return false;
                }
            }
            throw e;
        }
    }

    /**
     * Contract to handle the creation and configuration of resources.
     * E.g. creation of queues, binding exchange to queues.
     */
    interface TopologyHandler {

        /**
         * Get the current routing key
         * @return
         */
        String getRoutingKey();

        /**
         * Configure the queues for the current client (e.g. consumer or producer)
         * @param connection
         * @return the configured queues names (can be server-generated names), connection, and topology recording
         * @throws IOException
         */
        TopologyHandlerResult configureQueuesForClient(Connection connection) throws IOException;

        /**
         * Configure all the queues for this run
         * @param connection
         * @return the configured queues names (can be server-generated names), connection, and topology recording
         * @throws IOException
         */
        TopologyHandlerResult configureAllQueues(Connection connection) throws IOException;

        /**
         * Move the cursor forward.
         * Should be called when the configuration (queues and routing key)
         * is required for the next client (consumer or producer).
         */
        void next();

        /**
         * Reset the {@link TopologyHandler}.
         * Typically reset the cursor. To call e.g. when a new set of
         * clients need to configured.
         */
        void reset();

    }

    static class TopologyHandlerResult {

        /**
         * The connection to use to work against the configured resources.
         * Useful when using exclusive resources.
         */
        final Connection connection;

        final TopologyRecording topologyRecording;

        /**
         * The configured queues.
         */
        final List configuredQueues;

        TopologyHandlerResult(Connection connection, List configuredQueues, TopologyRecording topologyRecording) {
            this.connection = connection;
            this.configuredQueues = configuredQueues;
            this.topologyRecording = topologyRecording;
        }
    }

    /**
     * Support class that contains queue configuration.
     */
    static abstract class TopologyHandlerSupport {

        protected final MulticastParams params;
        private final ConcurrentMap connectionCache = new ConcurrentHashMap<>();

        protected TopologyHandlerSupport(MulticastParams params) {
            this.params = params;
        }

        protected Connection maybeUseCachedConnection(List queues, Connection connection) throws IOException {
            // if queues are exclusive, we create them for each consumer connection or re-use them
            // in case they are more consumers than queues
            Connection connectionToUse = connectionCache.putIfAbsent(queues.toString(), connection);
            if (connectionToUse == null) {
                // not a hit in the cache, we use the one passed-in
                connectionToUse = connection;
            } else {
                // hit in the cache, we used the cached one, and close the passed-in one
                // (unless the cache one and the passed-in one are the same object, which is the case
                // when using several channels for each consumer!)
                if (connection != connectionToUse) {
                    connection.close(AMQP.REPLY_SUCCESS, "Connection not used", -1);
                }
            }
            return connectionToUse;
        }

        protected List configureQueues(Connection connection, List queues, TopologyRecording topologyRecording, Runnable afterQueueConfigurationCallback) throws IOException {
            try (Channel channel = connection.createChannel()) {
                if (!params.predeclared || !exchangeExists(connection, params.exchangeName)) {
                    channel.exchangeDeclare(params.exchangeName, params.exchangeType);
                    topologyRecording.recordExchange(params.exchangeName, params.exchangeType);
                }

                // To ensure we get at-least 1 default queue:
                // (don't declare any queues when --predeclared is passed,
                // otherwise unwanted server-named queues without consumers will pile up.
                // see https://github.com/rabbitmq/rabbitmq-perf-test/issues/25 and
                // https://github.com/rabbitmq/rabbitmq-perf-test/issues/43)
                if (!params.predeclared && queues.isEmpty()) {
                    queues = Collections.singletonList("");
                }

                List generatedQueueNames = new ArrayList<>();
                for (String qName : queues) {
                    if (!params.predeclared || !queueExists(connection, qName)) {
                        boolean serverNamed = qName == null || "".equals(qName);
                        qName = channel.queueDeclare(qName,
                                params.flags.contains("persistent"),
                                params.isExclusive(),
                                params.autoDelete,
                                params.queueArguments).getQueue();
                        topologyRecording.recordQueue(
                                qName, params.flags.contains("persistent"),
                                params.isExclusive(), params.autoDelete,
                                params.queueArguments, serverNamed
                        );
                    }
                    generatedQueueNames.add(qName);
                    // skipping binding to default exchange,
                    // as it's not possible to explicitly bind to it.
                    if (!"".equals(params.exchangeName) && !"amq.default".equals(params.exchangeName) && !params.skipBindingQueues) {
                        String routingKey = params.topologyHandler.getRoutingKey();
                        channel.queueBind(qName, params.exchangeName, routingKey);
                        topologyRecording.recordBinding(qName, params.exchangeName, routingKey);
                    }
                    afterQueueConfigurationCallback.run();
                }
                return generatedQueueNames;
            } catch (TimeoutException e) {
                throw new IOException(e);
            }
        }

    }

    /**
     * {@link TopologyHandler} implementation that contains a list of a queues and a fixed routing key.
     */
    static class FixedQueuesTopologyHandler extends TopologyHandlerSupport implements TopologyHandler {

        final String routingKey;

        final List queueNames;

        final TopologyRecording topologyRecording = new TopologyRecording();

        FixedQueuesTopologyHandler(MulticastParams params, String routingKey, List queueNames) {
            super(params);
            if (routingKey == null) {
                this.routingKey = UUID.randomUUID().toString();
            } else {
                this.routingKey = routingKey;
            }
            this.queueNames = queueNames == null ? new ArrayList<>() : queueNames;
        }

        @Override
        public String getRoutingKey() {
            return routingKey;
        }

        @Override
        public TopologyHandlerResult configureQueuesForClient(Connection connection) throws IOException {
            if (this.params.isExclusive()) {
                Connection connectionToUse = maybeUseCachedConnection(this.queueNames, connection);
                return new TopologyHandlerResult(
                    connectionToUse, configureQueues(connectionToUse, this.queueNames, topologyRecording, () -> {}), topologyRecording
                );
            } else {
                return new TopologyHandlerResult(
                    connection, configureQueues(connection, this.queueNames, topologyRecording, () -> {}), topologyRecording
                );
            }
        }

        @Override
        public TopologyHandlerResult configureAllQueues(Connection connection) throws IOException {
            if (shouldConfigureQueues() && !this.params.isExclusive()) {
                return new TopologyHandlerResult(connection, configureQueues(connection, this.queueNames, topologyRecording, () -> {}), topologyRecording);
            }
            return new TopologyHandlerResult(connection, new ArrayList<>(), new TopologyRecording());
        }

        public boolean shouldConfigureQueues() {
            // if no consumer, no queue has been configured and
            // some queues are specified, we have to configure the queues and their bindings
            return this.params.consumerCount == 0 && !(queueNames.size() == 0);
        }

        @Override
        public void next() {
            // NO OP
        }

        @Override
        public void reset() {
            // NO OP
        }
    }

    /**
     * {@link TopologyHandler} meant to use a sequence of queues and routing keys.
     * E.g. perf-test-1, perf-test-2, etc.
     * The routing key has the same value as the current queue.
     */
    static class SequenceTopologyHandler extends TopologyHandlerSupport implements TopologyHandler {

        final List queues;
        int index = 0;
        private final TopologyRecording topologyRecording = new TopologyRecording();

        public SequenceTopologyHandler(MulticastParams params, int from, int to, String queuePattern) {
            super(params);
            queues = new ArrayList<>(to - from + 1);
            for (int i = from; i <= to; i++) {
                queues.add(String.format(queuePattern, i));
            }
        }

        @Override
        public String getRoutingKey() {
            return this.getQueueNamesForClient().get(0);
        }

        @Override
        public TopologyHandlerResult configureQueuesForClient(Connection connection) throws IOException {
            if (this.params.isExclusive()) {
                Connection connectionToUse = maybeUseCachedConnection(getQueueNamesForClient(), connection);
                TopologyRecording clientTopologyRecording = new TopologyRecording();
                return new TopologyHandlerResult(
                    connectionToUse,
                    configureQueues(connectionToUse, getQueueNamesForClient(), clientTopologyRecording, () -> {}),
                    clientTopologyRecording
                );
            } else {
                List queues = getQueueNamesForClient();
                TopologyRecording clientTopologyRecording = this.topologyRecording.subRecording(queues);
                return new TopologyHandlerResult(
                    connection,
                    getQueueNamesForClient(),
                    clientTopologyRecording
                );
            }

        }

        @Override
        public TopologyHandlerResult configureAllQueues(Connection connection) throws IOException {
            // if queues are exclusive, we'll create them for each consumer connection
            if (this.params.isExclusive()) {
                return new TopologyHandlerResult(connection, new ArrayList<>(), new TopologyRecording());
            } else {
                return new TopologyHandlerResult(connection, configureQueues(connection, getQueueNames(), this.topologyRecording, () -> this.next()), this.topologyRecording);
            }
        }

        protected List getQueueNames() {
            return Collections.unmodifiableList(queues);
        }

        protected List getQueueNamesForClient() {
            return Collections.singletonList(queues.get(index % queues.size()));
        }

        @Override
        public void next() {
            index++;
        }

        @Override
        public void reset() {
            index = 0;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy