com.ibm.og.cli.ObjectGenerator.java Source code

Java tutorial

Introduction

Here is the source code for com.ibm.og.cli.ObjectGenerator.java

Source

/* Copyright (c) IBM Corporation 2016. All Rights Reserved.
 * Project name: Object Generator
 * This project is licensed under the Apache License 2.0, see LICENSE.
 */

package com.ibm.og.cli;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.Thread.interrupted;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import com.google.common.util.concurrent.ListenableFuture;
import com.ibm.og.api.Request;
import com.ibm.og.api.Response;
import com.ibm.og.guice.ListModule;
import com.ibm.og.json.OGConfig;
import com.ibm.og.json.type.FilesizeConfigTypeAdapterFactory;
import com.ibm.og.test.condition.LoadTestResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ibm.og.cli.Application.Cli;
import com.ibm.og.guice.OGModule;
import com.ibm.og.json.type.ChoiceConfigTypeAdapterFactory;
import com.ibm.og.json.type.ContainerConfigTypeAdapterFactory;
import com.ibm.og.json.type.OperationConfigTypeAdapterFactory;
import com.ibm.og.json.type.SelectionConfigTypeAdapterFactory;
import com.ibm.og.util.json.type.SizeUnitTypeAdapter;
import com.ibm.og.util.json.type.TimeUnitTypeAdapter;
import com.ibm.og.util.json.type.CaseInsensitiveEnumTypeAdapterFactory;
import com.ibm.og.object.ObjectManager;
import com.ibm.og.statistic.Statistics;
import com.ibm.og.test.LoadTest;
import com.ibm.og.util.SizeUnit;
import com.ibm.og.util.Version;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Uninterruptibles;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;

import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import com.google.inject.spi.Message;
import com.google.inject.CreationException;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.ConfigurationException;
import com.google.inject.ProvisionException;
import com.google.inject.Stage;

/**
 * A cli for the Object Generator load tool
 * 
 * @since 1.0
 */
public class ObjectGenerator {
    private static final Logger _logger = LoggerFactory.getLogger(ObjectGenerator.class);
    private static final Logger _consoleLogger = LoggerFactory.getLogger("ConsoleLogger");
    private static final Logger _ogJsonLogger = LoggerFactory.getLogger("OGJsonLogger");
    private static final Logger _summaryJsonLogger = LoggerFactory.getLogger("SummaryJsonLogger");
    private static final Logger _exceptionLogger = LoggerFactory.getLogger("ExceptionLogger");
    private static final Logger _ogstatsLogger = LoggerFactory.getLogger("OgStatsLogger");
    private static final String LINE_SEPARATOR = "-------------------------------------------------------------------------------";

    private static final Gson gson = createGson();
    private static final Gson intervalGson = createIntervalGson();
    private static Injector injector;
    private static LoadTest test;
    private static ObjectManager objectManager;
    private static Statistics statistics;
    private static OGConfig ogConfig;
    private static Thread statsLogger;

    private static long timestampStart;
    private static long timestampStop;
    private static long timestampIntervalStart;

    private ObjectGenerator() {
    }

    public static void main(final String[] args) {
        timestampStart = System.currentTimeMillis();
        timestampIntervalStart = timestampStart;
        final OGGetOpt getopt = new OGGetOpt();
        final Cli cli = Application.cli("og", getopt, args);
        if (cli.shouldStop()) {
            if (cli.help()) {
                cli.printUsage();
            } else if (cli.version()) {
                cli.printVersion();
            } else if (cli.error()) {
                cli.printErrors();
                cli.printUsage();
                timestampStop = System.currentTimeMillis();
                logSummary(timestampStart, timestampStop, Application.TEST_CONFIG_ERROR,
                        ImmutableList.of("Invalid Arguments"));
                Application.exit(Application.TEST_CONFIG_ERROR);

            }
            timestampStop = System.currentTimeMillis();
            logSummary(timestampStart, timestampStop, Application.TEST_SUCCESS,
                    ImmutableList.of(Application.TEST_SUCCESS_MSG));
            Application.exit(Application.TEST_SUCCESS);
        }

        final CountDownLatch shutdownLatch = new CountDownLatch(1);

        logBanner();
        _consoleLogger.info("Configuring...");

        try {

            try {
                wireDependencies(cli, getopt);
            } catch (Exception e) {
                _logger.error("Exception while setting up dependencies", e);
                _consoleLogger.error("Test Error. See og.log for details");
                logConsoleException(e);
                logExceptionToFile(e);
                timestampStop = System.currentTimeMillis();
                logSummary(timestampStart, timestampStop, Application.TEST_CONFIG_ERROR,
                        ImmutableList.of(String.format("Configuration error %s", e.getMessage())));
                Application.exit(Application.TEST_CONFIG_ERROR);
            }

            OGLog4jShutdownCallbackRegistry.setOGShutdownHook((new ShutdownHook(test, shutdownLatch)));
            // start log dump thread
            if (ogConfig.statsLogInterval > 0) {
                statsLogger = new Thread(new StatsLogger(), "stats-logger");
                statsLogger.start();
            }
            final LoadTestResult result = run(test, objectManager, statistics, gson);

            shutdownLatch.countDown();

            if (ogConfig.statsLogInterval > 0 && statsLogger.isAlive()) {
                statsLogger.interrupt();
            }

            // slight race here; if shutdown hook completes prior to the exit line below
            // if the test completes whether it passes or fails, the summary is written in the test results callback
            if (result.result < 0) {
                // Error while executing test
                Application.exit(Application.TEST_ERROR);
            } else if (result.result > 0) {
                // Error while shutting down ApacheClient
                _logger.error("Test shutdown unsuccessful, terminated {} requests", result.result);
                Application.exit(Application.TEST_SHUTDOWN_ERROR);
            }
        } catch (final Exception e) {
            try {
                _logger.error("Exception while configuring and running test", e);
                _consoleLogger.error("Test Error. See og.log for details");
                if (ogConfig.statsLogInterval > 0 && statsLogger.isAlive()) {
                    statsLogger.interrupt();
                }
                logConsoleException(e);
                logExceptionToFile(e);
                timestampStop = System.currentTimeMillis();
                logSummary(timestampStart, timestampStop, Application.TEST_ERROR,
                        ImmutableList.of(String.format("Test error %s", e.getMessage())));
                _logger.warn("countdown shutdown latch in exception block");
            } finally {
                shutdownLatch.countDown();
                Application.exit(Application.TEST_ERROR);
            }
        }

        Application.exit(Application.TEST_SUCCESS);
    }

    /**
     *
     * @param cli CLI object created for this test
     * @param getopt Getoptions object reference
     *
     *
     * Note: If the dependency injection fails it would throw ConfigurationException or
     * ProvisionException.  In other cases, it throws ConfigurationException.
     *
     */
    private static void wireDependencies(Cli cli, OGGetOpt getopt) {

        final File json = new File(getopt.getOGConfigFileName());
        if (json == null) {
            _consoleLogger.error("A json configuration file is required");
            cli.printUsage();
            throw new ConfigurationException(
                    ImmutableSet.of(new Message("OG Configuration json file is required")));
        }

        try {
            ogConfig = Application.fromJson(json, OGConfig.class, gson);
            _ogJsonLogger.info(gson.toJson(ogConfig));
        } catch (FileNotFoundException fe) {
            throw new RuntimeException("OGConfig file not found");
        }

        // dependency injection
        injector = createInjector(ogConfig);
        test = injector.getInstance(LoadTest.class);
        objectManager = injector.getInstance(ObjectManager.class);
        statistics = injector.getInstance(Statistics.class);

    }

    public static LoadTestResult run(final LoadTest test, final ObjectManager objectManager,
            final Statistics statistics, final Gson gson) {
        _logger.info("{}", test);
        _logger.info("{}", objectManager);
        _consoleLogger.info("Configured.");
        _consoleLogger.info("Test Running...");

        final LoadTestResult result = test.call();

        if (result.result == 0) {
            _consoleLogger.info("Test Completed.");
        } else {
            _consoleLogger.error("Test ended unsuccessfully. See og.log or exception.log for details");
        }

        shutdownObjectManager(objectManager);

        final Summary summary = logSummary(statistics, result.timestampStart, result.timestampFinish, result);

        logSummaryBanner();
        _consoleLogger.info("{}", summary.getSummaryStats().condensedSummary());

        return result;
    }

    public static void logConsoleException(final Exception e) {
        if (e instanceof ProvisionException) {
            logConsoleGuiceMessages(((ProvisionException) e).getErrorMessages());
        } else if (e instanceof CreationException) {
            logConsoleGuiceMessages(((CreationException) e).getErrorMessages());
        } else {
            _consoleLogger.error(e.getMessage());
        }
    }

    public static void logConsoleGuiceMessages(final Collection<Message> messages) {
        // guice exceptions contain many duplicate messages with slightly differing causes; we only want
        // unique messages logged to console so filter them here
        final Set<String> uniqueMessages = Sets.newHashSet();
        for (final Message message : messages) {
            if (!uniqueMessages.contains(message.getMessage())) {
                _consoleLogger.error(message.getMessage());
            }
            uniqueMessages.add(message.getMessage());
        }
    }

    public static void logExceptionToFile(final Exception e) {
        _exceptionLogger.error("Exception: ", e);
    }

    public static Gson createGson() {
        return new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
                .registerTypeAdapterFactory(new CaseInsensitiveEnumTypeAdapterFactory())
                // SizeUnit and TimeUnit TypeAdapters must be registered after
                // CaseInsensitiveEnumTypeAdapterFactory in order to override the registration
                .registerTypeHierarchyAdapter(SizeUnit.class, new SizeUnitTypeAdapter().nullSafe())
                .registerTypeHierarchyAdapter(TimeUnit.class, new TimeUnitTypeAdapter().nullSafe())
                .registerTypeAdapterFactory(new OperationConfigTypeAdapterFactory())
                .registerTypeAdapterFactory(new SelectionConfigTypeAdapterFactory())
                .registerTypeAdapterFactory(new ChoiceConfigTypeAdapterFactory())
                .registerTypeAdapterFactory(new FilesizeConfigTypeAdapterFactory())
                .registerTypeAdapterFactory(new ContainerConfigTypeAdapterFactory()).setPrettyPrinting().create();
    }

    public static Gson createIntervalGson() {
        return new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
                .registerTypeAdapterFactory(new CaseInsensitiveEnumTypeAdapterFactory())
                // SizeUnit and TimeUnit TypeAdapters must be registered after
                // CaseInsensitiveEnumTypeAdapterFactory in order to override the registration
                .registerTypeHierarchyAdapter(SizeUnit.class, new SizeUnitTypeAdapter().nullSafe())
                .registerTypeHierarchyAdapter(TimeUnit.class, new TimeUnitTypeAdapter().nullSafe())
                .registerTypeAdapterFactory(new OperationConfigTypeAdapterFactory())
                .registerTypeAdapterFactory(new SelectionConfigTypeAdapterFactory())
                .registerTypeAdapterFactory(new ChoiceConfigTypeAdapterFactory())
                .registerTypeAdapterFactory(new FilesizeConfigTypeAdapterFactory())
                .registerTypeAdapterFactory(new ContainerConfigTypeAdapterFactory()).create();
    }

    public static Injector createInjector(final OGConfig ogConfig) {
        return Guice.createInjector(Stage.PRODUCTION, new OGModule(ogConfig), new ListModule(ogConfig));
    }

    public static void shutdownObjectManager(final ObjectManager objectManager) {
        try {
            objectManager.shutdown();
        } catch (final Exception e) {
            _logger.error("Error shutting down object manager", e);
        }
    }

    private static void logBanner() {
        final String bannerFormat = "%s%nObject Generator (%s)%n%s";
        final String banner = String.format(bannerFormat, LINE_SEPARATOR, Version.displayVersion(), LINE_SEPARATOR);
        _consoleLogger.info(banner);
    }

    private static void logSummaryBanner() {
        final String bannerFormat = "%s%nSummary%n%s";
        final String banner = String.format(bannerFormat, LINE_SEPARATOR, LINE_SEPARATOR);
        _consoleLogger.info(banner);
    }

    private static void logIntervalStatsBanner() {
        final String bannerFormat = "%s%nIntervalStats%n%s";
        final String banner = String.format(bannerFormat, LINE_SEPARATOR, LINE_SEPARATOR);
        _consoleLogger.info(banner);
    }

    private static Summary logSummary(final Statistics stats, final long timestampStart, final long timestampFinish,
            final LoadTestResult testResult) {
        int exitCode = Application.TEST_ERROR;
        if (testResult.result == 0) {
            exitCode = Application.TEST_SUCCESS;
        } else if (testResult.result > 0) {
            exitCode = Application.TEST_SHUTDOWN_ERROR;
        }
        final int requestsAborted = testResult.result > 0 ? testResult.result : 0;

        final Summary summary = new Summary(stats, timestampStart, timestampFinish, exitCode,
                testResult.result == 0 ? ImmutableList.of(Application.TEST_SUCCESS_MSG) : testResult.messages,
                requestsAborted);
        _summaryJsonLogger.info(gson.toJson(summary.getSummaryStats()));
        return summary;
    }

    private static Summary logSummary(final long timestampStart, final long timestampFinish, final int exitCode,
            ImmutableList<String> messages) {
        final Summary summary = new Summary(new Statistics(), timestampStart, timestampFinish, exitCode, messages,
                0);
        _summaryJsonLogger.info(gson.toJson(summary.getSummaryStats()));
        return summary;
    }

    private static void dumpSummaryStats(Gson gson, Summary.SummaryOperationStats intervalStats,
            final long timestampStart, final long timestampFinish, final int testStatus) {
        logIntervalStatsBanner();
        _ogstatsLogger.info(intervalGson.toJson(intervalStats));
        //_consoleLogger.info(gson.toJson(intervalStats));
        _consoleLogger.info("{}", intervalStats.toString());

    }

    private static class ShutdownHook extends Thread {
        private final LoadTest test;
        private final CountDownLatch shutdownLatch;

        public ShutdownHook(final LoadTest test, final CountDownLatch shutdownLatch) {
            this.test = checkNotNull(test);
            this.shutdownLatch = checkNotNull(shutdownLatch);
        }

        @Override
        public void run() {
            _logger.debug("og shutdown hook triggered, stopping test");
            this.test.stopTest();

            _logger.info("Waiting on shutdown lock");
            Uninterruptibles.awaitUninterruptibly(this.shutdownLatch);
            _logger.debug("Shutdown lock released, exiting og shutdown hook");
        }
    }

    private static class StatsLogger implements Runnable {
        IntervalSummary intervalSummary;

        @Override
        public void run() {
            boolean running = true;
            while (!interrupted()) {
                timestampStop = System.currentTimeMillis();
                if (intervalSummary == null) {
                    intervalSummary = new IntervalSummary(statistics, timestampIntervalStart, timestampStop);
                } else {
                    Summary.SummaryOperationStats istats = intervalSummary.intervalStats(statistics,
                            timestampIntervalStart, timestampStop);
                    dumpSummaryStats(gson, istats, timestampStart, timestampStop, Application.TEST_SUCCESS);
                    timestampIntervalStart = System.currentTimeMillis();
                }
                try {
                    Thread.sleep(ogConfig.statsLogInterval * 1000);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    _logger.info("StatsLogger thread interrupted");
                }
            }
            _logger.info("StatsLogger Thread done after interruption");
        }
    }

}