com.pushtechnology.consulting.Benchmarker.java Source code

Java tutorial

Introduction

Here is the source code for com.pushtechnology.consulting.Benchmarker.java

Source

/*******************************************************************************
 * Copyright (C) 2017 Push Technology Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/

package com.pushtechnology.consulting;

import static com.pushtechnology.diffusion.client.topics.details.TopicType.JSON;
import static java.lang.Integer.parseInt;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.apache.commons.lang3.StringUtils.join;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;

import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.pushtechnology.diffusion.api.APIException;
import com.pushtechnology.diffusion.api.config.ConfigManager;
import com.pushtechnology.diffusion.api.config.ThreadPoolConfig;
import com.pushtechnology.diffusion.api.config.ThreadsConfig;
import com.pushtechnology.diffusion.client.topics.details.TopicType;

/**
 * Bootstrap class.
 *
 * @author Push Technology Consulting
 */
public final class Benchmarker {

    private Benchmarker() {
        /* namespace */
    }

    private static final Logger LOG = LoggerFactory.getLogger(Benchmarker.class);

    private static final int CLIENT_INBOUND_QUEUE_QUEUE_SIZE = 5000000;
    private static final int CLIENT_INBOUND_QUEUE_CORE_SIZE = 16;
    private static final int CLIENT_INBOUND_QUEUE_MAX_SIZE = 16;
    private static final String CLIENT_INBOUND_THREAD_POOL_NAME = "JavaBenchmarkInboundThreadPool";
    private static final CountDownLatch RUNNING_LATCH = new CountDownLatch(1);

    public enum CreatorState {
        SHUTDOWN, INITIALISED, STOPPED, STARTED,
    }

    private static ScheduledFuture<?> publisherMonitor;
    private static SessionCreator sessionCreator;
    private static ScheduledFuture<?> sessionsCounter;

    public static ScheduledExecutorService globalThreadPool = Executors.newScheduledThreadPool(10);
    public static ScheduledExecutorService connectThreadPool;

    public static void main(String[] args) throws InterruptedException {
        LOG.info("Starting Java Benchmark Suite v{}", Benchmarker.class.getPackage().getImplementationVersion());

        final Arguments arguments = Arguments.parse(args);

        try {
            LOG.debug("Trying to set client InboundThreadPool queue size to '{}'", CLIENT_INBOUND_QUEUE_QUEUE_SIZE);
            final ThreadsConfig threadsConfig = ConfigManager.getConfig().getThreads();
            final ThreadPoolConfig inboundPool = threadsConfig.addPool(CLIENT_INBOUND_THREAD_POOL_NAME);
            inboundPool.setQueueSize(CLIENT_INBOUND_QUEUE_QUEUE_SIZE);
            inboundPool.setCoreSize(CLIENT_INBOUND_QUEUE_CORE_SIZE);
            inboundPool.setMaximumSize(CLIENT_INBOUND_QUEUE_MAX_SIZE);
            threadsConfig.setInboundPool(CLIENT_INBOUND_THREAD_POOL_NAME);
            LOG.debug("Successfully set client InboundThreadPool queue size to '{}'",
                    CLIENT_INBOUND_QUEUE_QUEUE_SIZE);
        } catch (APIException ex) {
            LOG.error("Failed to set client inbound pool size to '{}'", CLIENT_INBOUND_QUEUE_QUEUE_SIZE);
            ex.printStackTrace();
        }

        connectThreadPool = Executors.newScheduledThreadPool(arguments.connectThreadPoolSize);
        final Publisher publisher;

        if (arguments.doPublish) {
            LOG.info("Creating Publisher with connection string: '{}'", arguments.publisherConnectionString);
            publisher = new Publisher(arguments.publisherConnectionString, arguments.publisherUsername,
                    arguments.publisherPassword, arguments.topics, arguments.topicType);
            publisher.start();

            publisherMonitor = globalThreadPool.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    LOG.trace("publisherMonitor fired");
                    LOG.info("There are {} publishers running for topics: '{}'",
                            publisher.getUpdaterFuturessByTopic().size(),
                            ArrayUtils.toString(publisher.getUpdaterFuturessByTopic().keySet()));
                    for (ScheduledFuture<?> svc : publisher.getUpdaterFuturessByTopic().values()) {
                        if (svc.isCancelled()) {
                            LOG.debug("Service is cancelled...");
                        }
                        if (svc.isDone()) {
                            LOG.debug("Service is done...");
                        }
                    }
                    LOG.trace("Done publisherMonitor fired");
                }
            }, 2L, 5L, SECONDS);
        } else {
            publisher = null;
        }

        /* Create subscribing sessions. Finite or churning */
        if (arguments.doCreateSessions) {
            if (arguments.maxNumSessions > 0) {
                LOG.info("Creating {} Sessions with connection string: '{}'", arguments.maxNumSessions,
                        arguments.sessionConnectionString);
            } else {
                LOG.info("Creating Sessions with connection string: '{}s'", arguments.sessionConnectionString);
                LOG.info("Creating Sessions at {}/second, disconnecting after {} seconds", arguments.sessionRate,
                        arguments.sessionDuration);
            }
            sessionCreator = new SessionCreator(arguments.sessionConnectionString, arguments.myTopics,
                    arguments.topicType);

            LOG.info(
                    "Sessions: [Connected] [Started] [Recovering] [Closed] [Ended] [Failed]  | Messages: [Number] [Bytes]");
            sessionsCounter = globalThreadPool.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    LOG.trace("sessionsCounter fired");
                    LOG.info("Sessions: {} {} {} {} {} {} | Messages: {} {}",
                            sessionCreator.getConnectedSessions().get(), sessionCreator.getStartedSessions().get(),
                            sessionCreator.getRecoveringSessions().get(), sessionCreator.getClosedSessions().get(),
                            sessionCreator.getEndedSessions().get(), sessionCreator.getConnectionFailures().get(),
                            sessionCreator.getMessageCount().getAndSet(0),
                            sessionCreator.getMessageByteCount().getAndSet(0));
                    LOG.trace("Done sessionsCounter fired");
                }
            }, 0L, 5L, SECONDS);

            if (arguments.maxNumSessions > 0) {
                sessionCreator.start(arguments.maxNumSessions);
            } else {
                sessionCreator.start(arguments.sessionRate, arguments.sessionDuration);
            }
        }

        RUNNING_LATCH.await();

        if (arguments.doPublish) {
            if (publisher != null) {
                publisher.shutdown();
            }
            publisherMonitor.cancel(false);
        }

        if (arguments.doCreateSessions) {
            sessionCreator.shutdown();
            sessionsCounter.cancel(false);
        }

        if (!globalThreadPool.isTerminated()) {
            try {
                globalThreadPool.awaitTermination(1L, SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static final class Arguments {
        /** Create and regularly update topics. */
        /* package */ boolean doPublish;
        /* package */ String publisherConnectionString;
        /* package */ String publisherUsername = EMPTY;
        /* package */ String publisherPassword = EMPTY;

        /** Connect sessions, and subscribe. Optionally churn those sessions */
        /* package */ boolean doCreateSessions;
        /* package */ String sessionConnectionString;
        /* package */ int maxNumSessions;

        /** Sessions created per second. */
        /* package */ int sessionRate;
        /** Session duration in ms. */
        /* package */ int sessionDuration;

        /* package */ List<String> paramTopics = new ArrayList<>();
        /** Subscribed topics. */
        /* package */ List<String> myTopics = new ArrayList<>();
        /* package */ List<String> topics = new ArrayList<>();
        /* package */ TopicType topicType = JSON;
        /* package */ int connectThreadPoolSize = 10;

        /**
         * Parse & validate command line arguments.
         */
        static Arguments parse(String[] args) {
            LOG.trace("Parsing args...");
            final Arguments result = new Arguments();

            if (args == null || args.length == 0) {
                usage(1, "Parameters are required, please ensure that these have been provided");
                return result;
            }

            final String errStr = "'%s' takes %d parameters, please check the usage and try again";
            for (int i = 0; i < args.length; i++) {
                final String arg = args[i];
                switch (arg) {

                case "-publish":
                    if (hasNext(args, i, 1)) {
                        result.doPublish = true;
                        result.publisherConnectionString = args[++i];
                        if (hasNext(args, i, 1) && !args[i + 1].startsWith("-")) {
                            result.publisherUsername = args[++i];
                            if (hasNext(args, i, 1) && !args[i + 1].startsWith("-")) {
                                result.publisherPassword = args[++i];
                            }
                        }
                        LOG.debug(
                                "Creating Publisher with connection string: '{}', username: '{}' and password: '{}'",
                                result.publisherConnectionString, result.publisherUsername,
                                result.publisherPassword);
                    } else {
                        usage(1, errStr, "-publish", 1);
                    }
                    break;

                case "-sessions":
                    if (hasNext(args, i, 1)) {
                        result.doCreateSessions = true;
                        result.sessionConnectionString = args[++i];
                        result.maxNumSessions = parseInt(args[++i]);
                        LOG.debug("Creating {} Sessions with connection string: '{}'", result.maxNumSessions,
                                result.sessionConnectionString);
                    } else {
                        usage(1, errStr, "-sessions", 2);
                    }
                    break;

                case "-sessionsRate":
                    /* fall through */
                case "-sessionRate":
                    if (hasNext(args, i, 1)) {
                        result.doCreateSessions = true;
                        result.sessionConnectionString = args[++i];
                        result.sessionRate = parseInt(args[++i]);
                        result.sessionDuration = parseInt(args[++i]);
                        if (hasNext(args, i, 1)) {
                            result.connectThreadPoolSize = parseInt(args[++i]);
                            if (result.connectThreadPoolSize > Runtime.getRuntime().availableProcessors() * 5) {
                                usage(1, errStr, "-sessionsRate", 3);
                            }
                        }
                        result.maxNumSessions = 0;
                        LOG.debug("Creating Sessions at rate {} duration {} with connection string: '{}'",
                                result.sessionRate, result.sessionDuration, result.sessionConnectionString);
                    } else {
                        usage(1, errStr, "-sessionsRate", 3);
                    }
                    break;

                case "-topics":
                    while (hasNext(args, i, 1)) {
                        if (args[i + 1].startsWith("-")) {
                            break;
                        } else {
                            result.paramTopics.add(args[++i]);
                        }
                    }

                    LOG.debug("Using topics: '{}'", join(result.paramTopics, " || "));
                    if (result.paramTopics.size() == 0) {
                        usage(1, "'-topics' requires at least 1 parameter, please check the usage and try again");
                    }
                    break;

                case "-myTopics":
                    while (hasNext(args, i, 1)) {
                        if (args[i + 1].startsWith("-")) {
                            break;
                        } else {
                            result.myTopics.add(args[++i]);
                        }
                    }

                    LOG.debug("Using user topics: '{}'", join(result.myTopics, " || "));
                    if (result.myTopics.size() == 0) {
                        usage(1, "'-myTopics' requires at least 1 parameter, please check the usage and try again");
                    }
                    break;

                case "-topicType":
                    if (hasNext(args, i, 1)) {
                        result.topicType = TopicType.valueOf(args[++i]);
                        LOG.debug("Topic type {}", result.topicType);
                    } else {
                        usage(1, errStr, "-topicType needs 1 string argument");
                    }
                    break;

                default:
                    usage(1, "Found invalid argument: '%s', please check the usage and try again", arg);
                    break;
                }
            }

            result.topics.addAll(result.paramTopics);
            result.myTopics.addAll(result.topics);

            LOG.trace("Done parsing args...");
            return result;
        }

        private static boolean hasNext(String[] args, int index, int numNeeded) {
            return args.length > (index + numNeeded);
        }

        private static void usage(int exitCode, String err, Object... args) {
            LOG.info(err, args);
            System.out.println("");
            System.out.println("Usage: JavaBenchmarkSuite.jar [params]");
            System.out
                    .println("     -publish [connectionString] <username> <password> => enables the publisher for");
            System.out.println("          the given full connection string (e.g. 'dpt://10.10.10.10:8080') with");
            System.out.println("          an optional username and password.");
            System.out.println(
                    "     -sessions [connectionString] [maxSessions] => enables creation of [maxSessions]");
            System.out.println("          Sessions (UnifiedAPI) for the given full connection string");
            System.out.println("          (e.g. 'dpt://10.10.10.10:8080' or 'ws://10.10.10.10:8080')");
            System.out.println(
                    "     -sessionsRate [connectionString] [sessionRate] [sessionDuration] <connectPoolSize> ");
            System.out.println(
                    "          => enables creation of sessions at rate of [sessionRate] per second each lasting [sessionDuration] seconds");
            System.out.println("          Sessions (UnifiedAPI) for the given full connection string");
            System.out.println("          (e.g. 'ws://10.10.10.10:8080' or 'wss://10.10.10.10:8080')");
            System.out.println(
                    "          The connectPoolSize cannot exceed 5 * number of cpus. If more connect concurrency is needed then add more cpus.");
            System.out.println(
                    "     -topics [topic] <topics...> => subscribes any created Clients or Sessions to the");
            System.out.println(
                    "          given topic(s) in the format of: '<numberOfBytesPerMessage>/<messagesPerSecond>'");
            System.out.println(
                    "          NOTE: This application will automatically add the namespace and topic selector formatting!!!");
            System.out.println("          <topics...> => additional topics to subscribe to (these are optional,");
            System.out.println("          and as many as desired can be provided)");
            System.out.println(
                    "     -myTopics [topic] <topics...> => subscribes to user defined topics, REQUIRES the");
            System.out.println("          full topic path of EACH topic (can provide as many as you would like)");
            System.out.println("     -topicType [type] => specify the topic type");
            System.out.println("");
            System.exit(exitCode);
        }
    }

}