/*
 * Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
 * Distributed under the terms of either:
 * - the common development and distribution license (CDDL), v1.0; or
 * - the GNU Lesser General Public License, v2.1 or later
 */
package winstone.testCase.load;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import winstone.Logger;
import winstone.WinstoneResourceBundle;

import com.meterware.httpunit.WebConversation;
import winstone.cmdline.Option;

/**
 * This class is an attempt to benchmark performance under load for winstone. It
 * works by hitting a supplied URL with parallel threads (with keep-alives or
 * without) at an escalating rate, and counting the no of failures.
 * 
 * It uses HttpUnit's WebConversation class for the connection.
 * 
 * @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a>
 * @version $Id: LoadTest.java,v 1.2 2006/02/28 07:32:49 rickknowles Exp $
 */
public class LoadTest {
    private String url;
    private boolean useKeepAlives;
    private int startThreads;
    private int endThreads;
    private int stepSize;
    private long stepPeriod;
    private long gracePeriod;
    private long successTimeTotal;
    private int successCount;
    private WinstoneResourceBundle resources;

    private static String LOCAL_RESOURCE_FILE = "winstone.testCase.load.LocalStrings";

    public LoadTest(WinstoneResourceBundle resources, String url,
            boolean useKeepAlives, int startThreads, int endThreads,
            int stepSize, long stepPeriod, long gracePeriod) {
        this.resources = resources;
        this.url = url;
        this.useKeepAlives = useKeepAlives;
        this.startThreads = startThreads;
        this.endThreads = endThreads;
        this.stepSize = stepSize;
        this.stepPeriod = stepPeriod;
        this.gracePeriod = gracePeriod;

        Logger.log(Logger.INFO, resources, "LoadTest.Config", this.url, this.useKeepAlives + "", this.startThreads + "",
                this.endThreads + "", this.stepSize + "", this.stepPeriod + "",
                this.gracePeriod + "");
    }

    public void test() throws InterruptedException {
        WebConversation wc = null;

        // Loop through in steps
        for (int n = this.startThreads; n <= this.endThreads; n += this.stepSize) {
            if (this.useKeepAlives)
                wc = new WebConversation();

            // Spawn the threads
            int noOfSeconds = (int) this.stepPeriod / 1000;
            List threads = new ArrayList();
            for (int m = 0; m < n; m++)
                threads.add(new LoadTestThread(this.url, this, this.resources,
                        wc, noOfSeconds - 1));

            // Sleep for step period
            Thread.sleep(this.stepPeriod + gracePeriod);

            // int errorCount = (noOfSeconds * n) - this.successCount;
            Long averageSuccessTime = this.successCount == 0 ? null : this.successTimeTotal / this.successCount;

            // Write out results
            Logger.log(Logger.INFO, resources, "LoadTest.LineResult",
                    n + "", this.successCount + "",
                    ((noOfSeconds * n) - this.successCount) + "",
                    averageSuccessTime + "");

            // Close threads
            for (Object thread : threads) ((LoadTestThread) thread).destroy();

            this.successTimeTotal = 0;
            this.successCount = 0;

        }
    }

    public void incTimeTotal(long amount) {
        this.successTimeTotal += amount;
    }

    public void incSuccessCount() {
        this.successCount++;
    }

    public static void main(String args[]) throws Exception {
        WinstoneResourceBundle resources = new WinstoneResourceBundle(
                LOCAL_RESOURCE_FILE);

        // Loop for args
        Map options = new HashMap();
        // String operation = "";
        for (String option : args) {
            if (option.startsWith("--")) {
                int equalPos = option.indexOf('=');
                String paramName = option.substring(2, equalPos == -1 ? option
                        .length() : equalPos);
                String paramValue = (equalPos == -1 ? "true" : option
                        .substring(equalPos + 1));
                options.put(paramName, paramValue);
            }
        }

        if (options.size() == 0) {
            printUsage(resources);
            return;
        }
        Logger.setCurrentDebugLevel(Integer.parseInt(Option.stringArg(options, "debug", "5")));

        String url = Option.stringArg(options, "url", "http://localhost:8080/");
        boolean keepAlive = Option.booleanArg(options, "keepAlive", true);
        String startThreads = Option.stringArg(options, "startThreads", "20");
        String endThreads = Option.stringArg(options, "endThreads", "1000");
        String stepSize = Option.stringArg(options, "stepSize", "20");
        String stepPeriod = Option.stringArg(options, "stepPeriod", "5000");
        String gracePeriod = Option.stringArg(options, "gracePeriod", "5000");

        LoadTest lt = new LoadTest(resources, url, keepAlive, Integer
                .parseInt(startThreads), Integer.parseInt(endThreads), Integer
                .parseInt(stepSize), Integer.parseInt(stepPeriod), Integer
                .parseInt(gracePeriod));

        lt.test();
    }

    /**
     * Displays the usage message
     */
    private static void printUsage(WinstoneResourceBundle resources) {
        System.out.println(resources.getString("LoadTest.Usage"));
    }

}
