com.ragstorooks.testrr.Runner.java Source code

Java tutorial

Introduction

Here is the source code for com.ragstorooks.testrr.Runner.java

Source

/*
Copyright 2008 Raghav Ramesh
    
This file is part of TestRR.
    
TestRR is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
TestRR is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with TestRR.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.ragstorooks.testrr;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Runner is the main class which does all the work for TestRR. Scenarios are executed concurrently to simulate a load
 * on the system under test. The number of times a scenario is executed depends on its weighting. The preferred mode of
 * execution can be configured explicitly using the exposed parameters.
 */
public class Runner implements ScenarioListener {
    private static final Log log = LogFactory.getLog(Runner.class);
    private static final int HUNDRED = 100;
    private static final Random randomGenerator = new Random();

    private int numberOfRuns = 500;
    private int numberOfConcurrentStarts = 5;
    private long coolDownPeriod = 1000;
    private boolean synchronizedScheduling = false;
    private int synchronizeWaitMilliSeconds = 1000;
    private long totalRunTimeMilliSeconds = 0;

    private Map<ScenarioBase, Integer> scenarioWeightings = null;
    private ScheduledExecutorService scheduledExecutorService = null;

    private ArrayList<ScenarioBase> scenariosList;
    private ArrayList<ScenarioBase> weightedScenariosList;
    private final Map<String, ScenarioResult> scenarioSuccesses = new ConcurrentHashMap<String, ScenarioResult>();
    private final Map<String, ScenarioResult> scenarioFailures = new ConcurrentHashMap<String, ScenarioResult>();

    private CountDownLatch scenarioStartLatch;
    private CountDownLatch scenarioCompleteLatch;
    private CountDownLatch allScenariosCompleteLatch;

    private boolean useDeterministicNumberOfRuns = false;

    /**
     * To be invoked when all the parameters have been configured, to generate the load on the system under test.
     */
    public void run() {
        log.info(String.format("RPRunner(numberOfRuns=%d, numberOfConcurrentStarts=%d)", numberOfRuns,
                numberOfConcurrentStarts));

        if (useDeterministicNumberOfRuns) {
            scenariosList = new ArrayList<ScenarioBase>();
            for (ScenarioBase scenario : scenarioWeightings.keySet()) {
                scenariosList.add(scenario);
            }
        }

        allScenariosCompleteLatch = new CountDownLatch(numberOfRuns);
        normalizeWeightings();

        long startTime = System.currentTimeMillis();

        if (synchronizedScheduling)
            runWithSynchronizedScheduling();
        else
            runWithAdHocScheduling();

        waitForCoolDownPeriod();

        totalRunTimeMilliSeconds = System.currentTimeMillis() - startTime;

    }

    /**
     * Get the overall rate of success for the executed scenarios.
     * 
     * @return the success percentage as a value between 0 and 100.
     */
    public double getSuccessRate() {
        return 100.0 * scenarioSuccesses.size() / numberOfRuns;
    }

    /**
     * Get information about the successful scenarios.
     * 
     * @return a map keyed on scenario id, containing information about the successful scenarios.
     */
    public Map<String, ScenarioResult> getScenarioSuccesses() {
        return scenarioSuccesses;
    }

    /**
     * Get information about the failed scenarios
     * 
     * @return a map keyed on scenario id, containing information about the failed scenarios.
     */
    public Map<String, ScenarioResult> getScenarioFailures() {
        return scenarioFailures;
    }

    /**
     * Get the total run time
     * 
     * @return the total time, in milliseconds, taken to run all scenarios, including the cool down period
     */
    public long getTotalRunTimeMilliSeconds() {
        return totalRunTimeMilliSeconds;
    }

    /**
     * Report a scenario as failed to the runner.
     * 
     * @param scenarioId
     *            the id of the failed scenario.
     * @param message
     *            a message describing the failed scenario. This could either be the reason for failure, or the last
     *            known successful state.
     */
    public void scenarioFailure(String scenarioId, String message) {
        ScenarioResult result = scenarioFailures.get(scenarioId);
        log.info(String.format("Scenario %s (%s) failed", scenarioId, result.getScenarioType().getSimpleName()));
        setResults(result, message);

        notifyCountDownLatch();
    }

    /**
     * Report a scenario as successful to the runner.
     * 
     * @param scenarioId
     *            the id of the successful scenario.
     */
    public void scenarioSuccess(String scenarioId) {
        ScenarioResult result = scenarioFailures.remove(scenarioId);
        log.info(String.format("Scenario %s (%s) succeeded", scenarioId, result.getScenarioType().getSimpleName()));
        setResults(result, null);
        scenarioSuccesses.put(scenarioId, result);

        notifyCountDownLatch();
    }

    private void setResults(ScenarioResult result, String message) {
        result.setEndTime(System.currentTimeMillis());
        result.setMessage(message);
    }

    private void notifyCountDownLatch() {
        allScenariosCompleteLatch.countDown();
        if (scenarioCompleteLatch != null)
            scenarioCompleteLatch.countDown();
    }

    private void normalizeWeightings() {
        weightedScenariosList = new ArrayList<ScenarioBase>(HUNDRED);
        int cumulativeWeights = 0;
        for (Integer weight : scenarioWeightings.values())
            cumulativeWeights += weight;
        double normalizationFactor = 1.0 * HUNDRED / cumulativeWeights;
        for (ScenarioBase scenario : scenarioWeightings.keySet()) {
            double normalizedWeight = normalizationFactor * scenarioWeightings.get(scenario);
            for (int i = 0; i < normalizedWeight; i++)
                weightedScenariosList.add(scenario);
        }
    }

    private void runWithAdHocScheduling() {
        for (int i = 0; i < numberOfRuns; i++) {
            ScenarioBase scenario = getNextScenario(i);
            String scenarioId = getScenarioId(i, scenario);

            scenarioFailures.get(scenarioId).setStartTime(System.currentTimeMillis());
            runScenario(scenario, scenarioId);
        }
    }

    private void runWithSynchronizedScheduling() {
        for (int i = 0; i < numberOfRuns; i += numberOfConcurrentStarts) {
            ScenarioBase scenario = null;
            String scenarioId = null;

            int startsThisRun;
            if ((i + numberOfConcurrentStarts) > numberOfRuns) {
                startsThisRun = (numberOfRuns - i);
            } else {
                startsThisRun = numberOfConcurrentStarts;
            }

            scenarioStartLatch = new CountDownLatch(startsThisRun);
            scenarioCompleteLatch = new CountDownLatch(startsThisRun);

            for (int j = 0; j < startsThisRun; j++) {
                Map<String, ScenarioBase> concurrentStarts = new HashMap<String, ScenarioBase>(startsThisRun);
                scenario = getNextScenario(i);
                scenarioId = getScenarioId(i + j, scenario);
                concurrentStarts.put(scenarioId, scenario);
                runSynchronizedScenario(scenario, scenarioId);
                scenarioStartLatch.countDown();
            }

            try {
                scenarioCompleteLatch.await(synchronizeWaitMilliSeconds, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                log.error(String.format("Interrupted while waiting for latch"), e);
            }
        }
    }

    protected ScenarioBase getNextScenario(int i) {
        ScenarioBase scenario;

        if (useDeterministicNumberOfRuns) {
            if (i < scenariosList.size()) {
                scenario = scenariosList.get(i);
            } else {
                throw new RuntimeException(
                        "Attempting to run with deterministic number of runs that exceeds the number of scenarios.");
            }
        } else {
            int random = randomGenerator.nextInt(HUNDRED);
            scenario = weightedScenariosList.get(random);
        }

        scenario.setScenarioListener(this);
        scenario.setStartLatch(scenarioStartLatch);
        return scenario;
    }

    protected ScenarioBase getRandomScenario() {
        int random = randomGenerator.nextInt(HUNDRED);
        ScenarioBase scenario = weightedScenariosList.get(random);
        scenario.setScenarioListener(this);
        scenario.setStartLatch(scenarioStartLatch);
        return scenario;
    }

    private String getScenarioId(int index, final ScenarioBase scenario) {
        String scenarioId = String.format("%d", index);
        scenarioFailures.put(scenarioId, new ScenarioResult(scenario.getClass(), "Failed to complete"));
        return scenarioId;
    }

    private void runScenario(final ScenarioBase scenario, final String scenarioId) {
        RunnableScenario runnableScenario = new RunnableScenario(scenario, scenarioId);
        scheduledExecutorService.execute(runnableScenario);
    }

    private void runSynchronizedScenario(final ScenarioBase scenario, final String scenarioId) {
        RunnableSynchronizedScenario runnableScenario = new RunnableSynchronizedScenario(scenario, scenarioId);
        scheduledExecutorService.execute(runnableScenario);
    }

    private void waitForCoolDownPeriod() {
        try {
            allScenariosCompleteLatch.await(coolDownPeriod, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            log.error("Interrupted while cooling down at the end", e);
        }
    }

    /**
     * Set useDeterminnisticNumberOfRuns
     * 
     * @param bool
     *            Umm yeah. I will leave this for rags to write. :D.
     */
    public void setUseDeterministicNumberOfRuns(boolean bool) {
        this.useDeterministicNumberOfRuns = bool;
    }

    /**
     * Set numberBeforeRuns before execution.
     * 
     * @param numberOfRuns
     *            the total number of scenarios to execute. Default is 500.
     */
    public void setNumberOfRuns(int numberOfRuns) {
        this.numberOfRuns = numberOfRuns;
    }

    /**
     * Set numberOfConcurrentStarts before execution.
     * 
     * @param numberOfConcurrentStarts
     *            the number of threads to be used for concurrent execution of scenarios. Default is 5.
     */
    public void setNumberOfConcurrentStarts(int numberOfConcurrentStarts) {
        this.numberOfConcurrentStarts = numberOfConcurrentStarts;
    }

    /**
     * Set scenarioWeightings before execution
     * 
     * @param scenarioWeightings
     *            a map keyed on the scenarios to execute, along with a relative weighting, represented by an integer.
     *            Default is null.
     */
    public void setScenarioWeightings(Map<ScenarioBase, Integer> scenarioWeightings) {
        this.scenarioWeightings = scenarioWeightings;
    }

    /**
     * Set scheduledExecutorService before execution
     * 
     * @param scheduledExecutorService
     *            an ExecutorService with a thread pool that can be used to submit scenarios for execution. A simple
     *            implementation that can be used for this purpose can be got by new
     *            java.util.concurrent.ScheduledThreadPoolExecutor(<thread pool size>). Default is null.
     */
    public void setScheduledExecutorService(ScheduledExecutorService scheduledExecutorService) {
        this.scheduledExecutorService = scheduledExecutorService;
    }

    /**
     * Set coolDownPeriod before execution
     * 
     * @param coolDownPeriod
     *            the duration, in milliseconds, that the runner should wait after initiating execution of the final
     *            scenario for all scenarios to complete. If a scenario does not complete within the coolDownPeriod, it
     *            is marked as a failure. Default is 1000.
     */
    public void setCoolDownPeriod(long coolDownPeriod) {
        this.coolDownPeriod = coolDownPeriod;
    }

    /**
     * Set synchronizedScheduling before execution
     * 
     * @param synchronizedScheduling
     *            a boolean, which if set to true, means that when a thread executes one scenario, it does not
     *            immediately start the next scenario until the other threads in the pool have finished execution of
     *            their respective scenarios. This guarantees that the system under test will always have to initiate
     *            the same number of scenarios in parallel. Alternatively, if this value is set to false, a thread will
     *            move on to the execution of the next scenario independent of other threads; this offers a more varied
     *            test of the system and probably a different set of race conditions. Default is false.
     */
    public void setSynchronizedScheduling(boolean synchronizedScheduling) {
        this.synchronizedScheduling = synchronizedScheduling;
    }

    /**
     * Set synchronizeWaitMilliSeconds
     * 
     * @param synchronizeWaitMilliSeconds
     *            the maximum duration, in milliseconds, that a thread should wait for other threads to finish execution
     *            of their respective scenarios to synchronize the start of the next scenario before assuming the other
     *            threads to be infinite and progressing with its next scenario. Default is 1000.
     */
    public void setSynchronizeWaitMilliSeconds(int synchronizeWaitMilliSeconds) {
        this.synchronizeWaitMilliSeconds = synchronizeWaitMilliSeconds;
    }

    private static class RunnableScenario implements Runnable {
        ScenarioBase scenario;
        String scenarioId;

        public RunnableScenario(ScenarioBase aScenario, String aScenarioId) {
            scenario = aScenario;
            scenarioId = aScenarioId;
        }

        public void run() {
            scenario.run(scenarioId);
        }
    }

    private static class RunnableSynchronizedScenario implements Runnable {
        ScenarioBase scenario;
        String scenarioId;

        public RunnableSynchronizedScenario(ScenarioBase aScenario, String aScenarioId) {
            scenario = aScenario;
            scenarioId = aScenarioId;
        }

        public void run() {
            scenario.runSynchronized(scenarioId);
        }
    }
}