/*
+----------------------------------------------------------------------+
|                    Class: CLauncherImpl                              |
|                                                                      |
| Developper:  Yoann Gil                                               |
|              Eric Gavaldo (eric.gavaldo@xqual.com)                   |
| Version:     1.1                                                     |
| License:     LGPL (http://www.gnu.org/licenses/lgpl.html)            |
+----------------------------------------------------------------------+
*/
package com.xqual.xlauncher.jmeter_web;

import java.awt.Color;
import java.awt.Image;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Vector;

import javax.imageio.ImageIO;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.annotations.XYAnnotation;
import org.jfree.chart.annotations.XYTextAnnotation;
import org.jfree.chart.axis.AxisLocation;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.Align;

import com.xqual.xagent.launcher.CExecutionStep;
import com.xqual.xagent.launcher.CLauncher;
import com.xqual.xagent.launcher.CReturnStatus;
import com.xqual.xagent.launcher.runner.CRunner;
import com.xqual.xagent.launcher.runner.IRunner;
import com.xqual.xcommon.CAttribute;
import com.xqual.xcommon.IConstantsResults;
import com.xqual.xcommon.type.CParam;
import com.xqual.xcommon.type.CParamParsingException;
import com.xqual.xcommon.utils.CCalendarUtils;
import com.xqual.xcommon.utils.CFileUtils;
import com.xqual.xcommon.utils.CUtils;

//+===============================================================+
//| * On the fly generation of .jmx files (JMeter test)           |
//| * Execution of the .jmx                                       |
//| * Log results to a .jtl file (XML)                            |
//| * Generation of a chart                                       |
//+===============================================================+

/**
 * The <code>CLauncherImpl</code> implementation of <code>ILauncher</code> for JMeter for web site load tests.
 * @author Yoann Gil, eric Gavaldo
 */
public class CLauncherImpl extends CLauncher implements IConstantsResults {

    // +==============================================================+
    // | Attributes                                                   |
    // +==============================================================+

    static final String TRACE_HEADER = "{jmeter_web    }  ";

    static final String JAVA_INTERPRETER_EXE         = CUtils.getExecutableName("java");
    static final String JMETER_SERVER_LOG_NAME       = "jmeter-server.log";
    static final String JMETER_SERVER_ARGUMENTS      = "-XX:+HeapDumpOnOutOfMemoryError -Xms512m -Xmx512m -XX:NewSize=128m -XX:MaxNewSize=128m " +
    		                                           "-XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=50 -XX:MaxTenuringThreshold=2 -XX:MaxLiveObjectEvacuationRatio=20 " +
    		                                           "-Dsun.rmi.dgc.client.gcInterval=600000 -Dsun.rmi.dgc.server.gcInterval=600000 " +
    		                                           "-XX:PermSize=64m -XX:MaxPermSize=64m " +
    		                                           "-jar \"ApacheJMeter.jar\" -s -j " + JMETER_SERVER_LOG_NAME;
    
    static final String JMETER_CLIENT_CMD_NAME   = "jmeter-n-r.cmd";
    
    private String jMeterServerInterpreter;
    
    private String javaInstallPath;
    private String testRootPath;

    private int timeoutRequest;
    private int timeoutConnection;
    
    private Float acceptableFailureRate;
    private int timeToReachMaxLoad;
    private int nbSimulatedUsers;
    private int nbRequestsPerUser;
    private int nbValuesToComputeMaxTime;

    private Vector<Vector<String>> httpRequestVector;

    private String uniqueIdentifier;
    
    private File workingDir;
    private File serverLogFile;
    private File execFile;
    private File execDir;
    
    private float average             = 0;
    private float maximum             = 0;
    private float measuredFailureRate = 0;
    
    private CRunner jmeterServer = null;
    private File jmeterServerTraceFile;
    
	// +==============================================================+
	// | Constructors                                                 |
	// +==============================================================+

    public CLauncherImpl() {
            super(TRACE_HEADER);
    }

	// +==============================================================+
	// | Methods                                                      |
	// +==============================================================+

    @Override
	public CReturnStatus initialize(int sutId, String sutName, String sutVersion) {
        setSutDetails(sutId, sutName, sutVersion);
        printConfiguration();
        
        httpRequestVector = new Vector<Vector<String>>();
        Vector<CExecutionStep> executionSteps = new Vector<CExecutionStep>();
        try {
        	// retrieve the parameters we need
			javaInstallPath          = getStringParamValue("General",  "Java install path");    // i.e. C:/Program Files/Java/jdk1.6.0_17
        	testRootPath        	 = getStringParamValue("General",  "Test root path");       // i.e. C:/test_repository/tests/jmeter
            timeoutConnection        = getIntegerParamValue("General", "Connection timeout (seconds)");
            timeoutRequest           = getIntegerParamValue("General", "Request timeout (seconds)");
            
            acceptableFailureRate    = Float.parseFloat(getStringParamValue("General",  "Acceptable failure rate (%)"));
            nbSimulatedUsers         = getIntegerParamValue("General",  "Nb simulated users");
            timeToReachMaxLoad       = getIntegerParamValue("General",  "Time to reach max load (seconds)");
            nbRequestsPerUser        = getIntegerParamValue("General",  "Nb requests per user");
            nbValuesToComputeMaxTime = getIntegerParamValue("General",  "Nb values to compute max time");

            workingDir               = new File(getStringParamValue("JMeter",  "Temporary folder"));
            execDir                  = new File(getStringParamValue("JMeter",  "JMeter install directory") + "/bin");
            serverLogFile            = new File(getStringParamValue("JMeter",  "JMeter install directory") + "/bin/" + JMETER_SERVER_LOG_NAME);
            execFile                 = new File(getStringParamValue("JMeter",  "JMeter install directory") + "/bin/" + JMETER_CLIENT_CMD_NAME);

			jMeterServerInterpreter = CFileUtils.quoteFilePath(javaInstallPath + "/bin/" + JAVA_INTERPRETER_EXE) + " " + JMETER_SERVER_ARGUMENTS;

        } catch (CParamParsingException e) {
			traceln(LOG_PRIORITY_SEVERE, "parsing error during initialization");
			executionSteps.add(new CExecutionStep(RESULT_FAILURE, "Exception during initialize: " + e.getMessage()));
            return new CReturnStatus(RESULT_UNKNOWN, executionSteps);
        }
        
		// +------------------------------------+
		// | Run the JMeter server
		// +------------------------------------+
		jmeterServerTraceFile = new File("jmeter_server_traces.txt");
		if (jmeterServerTraceFile.exists()) jmeterServerTraceFile.delete();
		try {
			jmeterServerTraceFile.createNewFile();
		} catch (IOException e) {
			traceln(LOG_PRIORITY_SEVERE, "Could not create file " + jmeterServerTraceFile);
			executionSteps.add(new CExecutionStep(RESULT_UNKNOWN, "Could not create file " + jmeterServerTraceFile));
            return new CReturnStatus(RESULT_UNKNOWN, executionSteps);
		}

		if (serverLogFile.exists()) serverLogFile.delete();
		jmeterServer = new CRunner("[JMeter Server] ",
		                           jMeterServerInterpreter,
		                           execDir);

		short result = jmeterServer.requestAction(IRunner.START_PROCESS, IRunner.DO_NOT_WAIT_END_OF_EXECUTION, jmeterServerTraceFile);
		if (result == RESULT_SUCCESS) {
			traceln(LOG_PRIORITY_INFO, "jmeter server launched successfully");
			executionSteps.add(new CExecutionStep(RESULT_SUCCESS, "initialize: jmeter server successfully launched!"));
		} else {
			traceln(LOG_PRIORITY_SEVERE, "couldn't run jmeter server!");
			executionSteps.add(new CExecutionStep(RESULT_FAILURE, "initialize: couldn't run jmeter server! Maybe a server is already running"));
			return new CReturnStatus(RESULT_UNKNOWN, executionSteps);
		}

		// let the time to the server to initialize (this is done only once per campaign session)
		CUtils.sleep(3000);
        
		return new CReturnStatus(RESULT_SUCCESS, executionSteps);
    }

    
    @Override
	public CReturnStatus preRun(int testId, String testPath, String testName, Vector<CAttribute> attributes, String additionalInfo) {
		traceln(LOG_PRIORITY_INFO, "preRun testId=" + testId + " testPath=" + testPath + " [" + testName + "]...");
		
		uniqueIdentifier = testName + "_" + testId;
		
		Vector<CExecutionStep> executionSteps = new Vector<CExecutionStep>();
	
        // clean all the files related to this test in the working directory
        if (workingDir.isDirectory()) {
        	File[] files = this.workingDir.listFiles();
            for (int i=0; i<files.length; i++) {
                if(files[i].getName().contains(testName)) {
            		traceln(LOG_PRIORITY_INFO, "deleting file " + files[i] + "...");
                    files[i].delete();
                }
            }
        }

        // search for the test script in <testRootPath>/<testPath>/<testName>.web
        File testFile = new File(testRootPath + "/" + testPath + "/" + testName + ".web");
        traceln(LOG_PRIORITY_INFO, "reading test " + testFile + "...");
        try {
            BufferedReader r = new BufferedReader(new FileReader(testFile));
            String currentLine;
            String parameter = "";
            Vector<String> httpRequestParamVector = new Vector<String>();

            currentLine = r.readLine();
            while (currentLine != null) {
                if (currentLine.contains("url=")) { // URL = Server + path + port
                	
                	// store all the previously read params
                    if (httpRequestParamVector.size()>1) {
                        httpRequestVector.add(httpRequestParamVector);
                        httpRequestParamVector = new Vector<String>();
                    }

                	traceln(LOG_PRIORITY_INFO, "> url: " + currentLine);
                    parameter = currentLine.replaceAll("^url=(.*?) .*?$", "$1");     // i.e. parameter = "http://xqual.com/view_command.php port=80"
                    parameter = parameter.replaceAll("^http://(.*?)$", "$1");        //      parameter = "xqual.com/view_command.php port=80"
                    parameter = parameter.replaceAll("^http://(.*?) .*?$", "$1");    //      parameter = "xqual.com/view_command.php"
                    
                    if (parameter.contains("/")) {
                        String server = parameter.substring(0, parameter.indexOf("/"));
                    	httpRequestParamVector.add(server);
                        traceln(LOG_PRIORITY_INFO, "   - server: " + server);
                        
                        String path = parameter.substring(parameter.indexOf("/"), parameter.length());
                        httpRequestParamVector.add(path);
                        traceln(LOG_PRIORITY_INFO, "   - path  : " + path);
                    }
                    
                    String port = currentLine.replaceAll("^.*? port=(.*?)$", "$1");
                    httpRequestParamVector.add(port);
                    traceln(LOG_PRIORITY_INFO, "   - port  : " + port);

                } else if (currentLine.contains("method=")) { // Method
                	String method = currentLine.replaceAll("^method=(.*?)$", "$1");
                    httpRequestParamVector.add(method);
                    traceln(LOG_PRIORITY_INFO, "   - method: " + method);
                    
                } else if (currentLine.contains("param=")) { // Params
	                while (currentLine!=null && currentLine.contains("param=")) {
	                	String param = currentLine.replaceAll("^param=(.*?) value=(.*?)$", "$1|$2");
	                    httpRequestParamVector.add(param);
	                    traceln(LOG_PRIORITY_INFO, "      * param: " + param);
	                    currentLine = r.readLine();
	                }

	                if (httpRequestParamVector.size()>1) {
	                    httpRequestVector.add(httpRequestParamVector);
	                    httpRequestParamVector = new Vector<String>();
	                }
	                
	                continue; // do not read again a new line
                }
                
                currentLine = r.readLine();
            }
        } catch (Exception e) {
            traceln(LOG_PRIORITY_SEVERE, "couldn't find the test script : " + e.getMessage());
            return new CReturnStatus(RESULT_UNKNOWN, executionSteps);
        }

        traceln(LOG_PRIORITY_INFO, "http request vector = " + httpRequestVector);

        
        // generate .jmx file
        try {
            if (generateJmxFile(executionSteps)) {
            	return new CReturnStatus(RESULT_SUCCESS, executionSteps);
            } else {
                return new CReturnStatus(RESULT_UNKNOWN, executionSteps);            	
            } 	
        } catch (Exception e) {
            return new CReturnStatus(RESULT_UNKNOWN, executionSteps);
        }
    }


    @Override
	public CReturnStatus run(int testId, String testPath, String testName, int testcaseId, int testcaseIndex, String testcaseName, Vector<CParam> params, String additionalInfo) {
        traceln(LOG_PRIORITY_INFO, "run testId=" + testId + " Test:" + testPath + "/" + testName + " testcaseIndex=" + testcaseIndex + "...");

        // best effort
		if (serverLogFile.exists()) serverLogFile.delete();
		
        Vector<CExecutionStep> executionSteps = new Vector<CExecutionStep>();

        if (execFile.exists()) {
            CRunner exeRunner = new CRunner("[" + testId + "] "+ testPath + ":" + testName + "." + testcaseIndex,
                                            execFile + " \"" + workingDir.getPath() + "/" + uniqueIdentifier + ".jmx\"",
                                            execDir);
            
            short result = exeRunner.requestAction(IRunner.START_PROCESS, IRunner.WAIT_END_OF_EXECUTION);
            
            try {
            	parseResults(executionSteps);
                File f = new File(workingDir.getPath() + "/" + uniqueIdentifier + ".jtl");
                if (!f.exists()) {
                    return new CReturnStatus(RESULT_FAILURE, executionSteps);
                }
            } catch (FileNotFoundException e) {
                traceln(LOG_PRIORITY_SEVERE, "couldn't find the result file");
                executionSteps.add(new CExecutionStep(RESULT_UNKNOWN, "Couldn't find the result file"));
                return new CReturnStatus(RESULT_UNKNOWN, executionSteps);
            } catch (IOException e) {
                traceln(LOG_PRIORITY_SEVERE, "couldn't read the result file");
                executionSteps.add(new CExecutionStep(RESULT_UNKNOWN, "Couldn't read the result file"));
                return new CReturnStatus(RESULT_UNKNOWN, executionSteps);
            }
            
            // check the result and if the acceptable failure rate is reached or not
            if (result==RESULT_FAILURE || (acceptableFailureRate.floatValue()<measuredFailureRate )) {
                if(result == RESULT_FAILURE) {
                    executionSteps.add(new CExecutionStep(RESULT_UNKNOWN, "Execution failed"));
                    return new CReturnStatus(RESULT_UNKNOWN, executionSteps);
                } else {
                    //processExecutedTillTheEnd = true;
                    executionSteps.add(new CExecutionStep(RESULT_FAILURE, "Failure rate exceeded: " + measuredFailureRate + "% - acceptable failure rate: " + acceptableFailureRate + "%"));
                    return new CReturnStatus(RESULT_FAILURE, executionSteps);
                }
            } else {
                executionSteps.add(new CExecutionStep(RESULT_SUCCESS, "Acceptable failure rate: " + measuredFailureRate + "% - average time to execute a request: " + average + "ms"));
                //processExecutedTillTheEnd = true;
                return new CReturnStatus(RESULT_SUCCESS, executionSteps);
            }
        } else {
            traceln(LOG_PRIORITY_SEVERE, "Could not find JMeter binary. Please, check it is correctly installed in " + execDir.toString());
            executionSteps.add(new CExecutionStep(RESULT_UNKNOWN, "Could not find JMeter binary. Please, check it is correctly installed in " + execDir.toString()));
            return new CReturnStatus(RESULT_UNKNOWN, executionSteps);
        }

    }

    @Override
	public CReturnStatus postRun(int testId, String testPath, String testName) {
        Vector<CExecutionStep> executionSteps = new Vector<CExecutionStep>();
        
        // cleanup of generated files
        if (this.execFile.exists()) {
            traceln(LOG_PRIORITY_INFO, "Suppression des fichiers temporaires...");
            File jmxFile = new File(workingDir.getPath() + "/" + testName + "_" + testId+ ".jmx");
            if (!jmxFile.delete()) traceln(LOG_PRIORITY_SEVERE, "Couldn't delete file " + jmxFile);

            File jtlFile = new File(workingDir.getPath() + "/" + testName + "_" + testId+ ".jtl");
            if (!jtlFile.delete()) traceln(LOG_PRIORITY_SEVERE, "Couldn't delete file " + jtlFile);

            File logFile = new File(workingDir.getPath() + "/" + testName + "_" + testId+ ".log");
            if (!logFile.delete()) traceln(LOG_PRIORITY_SEVERE, "Couldn't delete file " + logFile);
        }
        return new CReturnStatus(RESULT_SUCCESS, executionSteps);
    }

	@Override
	public CReturnStatus terminate() {
		traceln(LOG_PRIORITY_INFO, "terminate killing jmeter server...");
		// +------------------------------------+
		// | kill the JMeter server
		// +------------------------------------+
		jmeterServer.killProcess();

		Vector<CExecutionStep> executionSteps = new Vector<CExecutionStep>();
		executionSteps.add(new CExecutionStep(RESULT_SUCCESS, "Terminate"));
		return new CReturnStatus(RESULT_SUCCESS, executionSteps);
	}

    // +--------------------------------------------------------------+
    // | Generation of the .jmx file from:                            |
    // | - the parameters in the configuration                        |
    // | - the http requests defined in the test scripts              |
    // +--------------------------------------------------------------+
    private boolean generateJmxFile(Vector<CExecutionStep> executionSteps) {
    	System.out.println("- - - - - generateJMXFile");
        StringBuffer outputBuffer = new StringBuffer();
        outputBuffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        outputBuffer.append("<jmeterTestPlan version=\"1.2\" properties=\"2.1\">");
        outputBuffer.append("  <hashTree>"); // 1
        outputBuffer.append("    <TestPlan guiclass=\"TestPlanGui\" testclass=\"TestPlan\" testname=\"Test plan\" enabled=\"true\">");
        outputBuffer.append("      <stringProp name=\"TestPlan.comments\"></stringProp>");
        outputBuffer.append("      <boolProp name=\"TestPlan.functional_mode\">false</boolProp>");
        outputBuffer.append("      <boolProp name=\"TestPlan.serialize_threadgroups\">false</boolProp>");
        outputBuffer.append("      <elementProp name=\"TestPlan.user_defined_variables\" elementType=\"Arguments\" guiclass=\"ArgumentsPanel\" testclass=\"Arguments\" testname=\"Predefined variables\" enabled=\"true\">");
        outputBuffer.append("        <collectionProp name=\"Arguments.arguments\"/>");
        outputBuffer.append("      </elementProp>");
        outputBuffer.append("      <stringProp name=\"TestPlan.user_define_classpath\"></stringProp>");
        outputBuffer.append("    </TestPlan>");
        outputBuffer.append("    <hashTree>"); // 2
        outputBuffer.append("      <ThreadGroup guiclass=\"ThreadGroupGui\" testclass=\"ThreadGroup\" testname=\"Groupe d&apos;unités\" enabled=\"true\">");
        outputBuffer.append("        <elementProp name=\"ThreadGroup.main_controller\" elementType=\"LoopController\" guiclass=\"LoopControlPanel\" testclass=\"LoopController\" testname=\"Loop controller\" enabled=\"true\">");
        outputBuffer.append("          <boolProp name=\"LoopController.continue_forever\">false</boolProp>");
        outputBuffer.append("          <stringProp name=\"LoopController.loops\">" + nbRequestsPerUser + "</stringProp>"); // nbRequestsPerUser
        outputBuffer.append("        </elementProp>");
        outputBuffer.append("        <stringProp name=\"ThreadGroup.num_threads\">" + nbSimulatedUsers + "</stringProp>"); // nbSimulatedUsers
        outputBuffer.append("        <stringProp name=\"ThreadGroup.ramp_time\">" + timeToReachMaxLoad + "</stringProp>"); // timeToReachMaxLoad
        outputBuffer.append("        <longProp name=\"ThreadGroup.start_time\">1272958113000</longProp>");                 // TODO
        outputBuffer.append("        <longProp name=\"ThreadGroup.end_time\">1272958113000</longProp>");                   // TODO
        outputBuffer.append("        <boolProp name=\"ThreadGroup.scheduler\">false</boolProp>");
        outputBuffer.append("        <stringProp name=\"ThreadGroup.on_sample_error\">continue</stringProp>");
        outputBuffer.append("        <stringProp name=\"ThreadGroup.duration\"></stringProp>");
        outputBuffer.append("        <stringProp name=\"ThreadGroup.delay\"></stringProp>");
        outputBuffer.append("      </ThreadGroup>");
		outputBuffer.append("      <hashTree>"); // 3
                
        for (int i=0; i<this.httpRequestVector.size(); i++) {
        	String domain = httpRequestVector.get(i).get(0);
        	String path   = httpRequestVector.get(i).get(1);
        	String port   = httpRequestVector.get(i).get(2);
        	String method = httpRequestVector.get(i).get(3);
        	
        	System.out.println("\n");
        	System.out.println("- - - - - domain = " + domain);
        	System.out.println("- - - - - path   = " + path);
        	System.out.println("- - - - - port   = " + port);
        	System.out.println("- - - - - method = " + method);
        	
			outputBuffer.append("        <HTTPSampler guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSampler\" testname=\"Page Web\" enabled=\"true\">");
			outputBuffer.append("          <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Predefined variables\" enabled=\"true\">");
			outputBuffer.append("            <collectionProp name=\"Arguments.arguments\">");

			for (int j=4; j<httpRequestVector.get(i).size(); j++) {
				String param = httpRequestVector.get(i).get(j).substring(0, httpRequestVector.get(i).get(j).indexOf("|"));
				String value = httpRequestVector.get(i).get(j).substring(httpRequestVector.get(i).get(j).indexOf("|")+1, httpRequestVector.get(i).get(j).length());
				
				traceln(LOG_PRIORITY_FINE, "size=" + httpRequestVector.get(i).size() + " param=" + param + " value=" + value);
				
				outputBuffer.append("              <elementProp name=\""+ param+"\" elementType=\"HTTPArgument\">");
				outputBuffer.append("                <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>");
				outputBuffer.append("                <stringProp name=\"Argument.value\">" + value + "</stringProp>");
				outputBuffer.append("                <stringProp name=\"Argument.metadata\">=</stringProp>");
				outputBuffer.append("                <boolProp name=\"HTTPArgument.use_equals\">true</boolProp>");
				outputBuffer.append("                <stringProp name=\"Argument.name\">" + param + "</stringProp>");
				outputBuffer.append("              </elementProp>");
			}
	         
			outputBuffer.append("            </collectionProp>");
			outputBuffer.append("          </elementProp>");
			outputBuffer.append("          <stringProp name=\"HTTPSampler.domain\">" + domain + "</stringProp>");                              // domain
			outputBuffer.append("          <stringProp name=\"HTTPSampler.port\">" + port + "</stringProp>");                                  // port
			outputBuffer.append("          <stringProp name=\"HTTPSampler.connect_timeout\">" + (timeoutRequest*1000) + "</stringProp>");      // timeoutRequest
			outputBuffer.append("          <stringProp name=\"HTTPSampler.response_timeout\">" + (timeoutConnection*1000) + "</stringProp>");  // timeoutConnection
			outputBuffer.append("          <stringProp name=\"HTTPSampler.protocol\"></stringProp>");
			outputBuffer.append("          <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>");
			outputBuffer.append("          <stringProp name=\"HTTPSampler.path\">" + path + "</stringProp>");                                  // path
			outputBuffer.append("          <stringProp name=\"HTTPSampler.method\">" + method + "</stringProp>");                              // method
			outputBuffer.append("          <boolProp name=\"HTTPSampler.follow_redirects\">false</boolProp>");
			outputBuffer.append("          <boolProp name=\"HTTPSampler.auto_redirects\">true</boolProp>");
			outputBuffer.append("          <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>");
			outputBuffer.append("          <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>");
			outputBuffer.append("          <stringProp name=\"HTTPSampler.FILE_NAME\"></stringProp>");
			outputBuffer.append("          <stringProp name=\"HTTPSampler.FILE_FIELD\"></stringProp>");
			outputBuffer.append("          <stringProp name=\"HTTPSampler.mimetype\"></stringProp>");
			outputBuffer.append("          <boolProp name=\"HTTPSampler.monitor\">false</boolProp>");
			outputBuffer.append("          <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>");
			outputBuffer.append("        </HTTPSampler>");
			outputBuffer.append("        <hashTree/>");
		}

		outputBuffer.append("        <ResultCollector guiclass=\"GraphVisualizer\" testclass=\"ResultCollector\" testname=\"Results Chart\" enabled=\"true\">");
		outputBuffer.append("          <boolProp name=\"ResultCollector.error_logging\">false</boolProp>");
		outputBuffer.append("          <objProp>");
		outputBuffer.append("            <name>saveConfig</name>");
		outputBuffer.append("            <value class=\"SampleSaveConfiguration\">");
		outputBuffer.append("              <time>true</time>");
		outputBuffer.append("              <latency>true</latency>");
		outputBuffer.append("              <timestamp>true</timestamp>");
		outputBuffer.append("              <success>true</success>");
		outputBuffer.append("              <label>true</label>");
		outputBuffer.append("              <code>true</code>");
		outputBuffer.append("              <message>true</message>");
		outputBuffer.append("              <threadName>true</threadName>");
		outputBuffer.append("              <dataType>true</dataType>");
		outputBuffer.append("              <encoding>false</encoding>");
		outputBuffer.append("              <assertions>true</assertions>");
		outputBuffer.append("              <subresults>true</subresults>");
		outputBuffer.append("              <responseData>false</responseData>");
		outputBuffer.append("              <samplerData>false</samplerData>");
		outputBuffer.append("              <xml>true</xml>");
		outputBuffer.append("              <fieldNames>false</fieldNames>");
		outputBuffer.append("              <responseHeaders>false</responseHeaders>");
		outputBuffer.append("              <requestHeaders>false</requestHeaders>");
		outputBuffer.append("              <responseDataOnError>false</responseDataOnError>");
		outputBuffer.append("              <saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>");
		outputBuffer.append("              <assertionsResultsToSave>0</assertionsResultsToSave>");
		outputBuffer.append("              <bytes>true</bytes>");
		outputBuffer.append("            </value>");
		outputBuffer.append("          </objProp>");
		outputBuffer.append("          <stringProp name=\"filename\"></stringProp>");
		outputBuffer.append("        </ResultCollector>");
		outputBuffer.append("        <hashTree/>"); 
		outputBuffer.append("      </hashTree>"); // -3
		outputBuffer.append("    </hashTree>"); // -2
		outputBuffer.append("  </hashTree>"); // -1
		outputBuffer.append("</jmeterTestPlan>");
        
        String jmxFilePath = workingDir.getPath() + "/" + uniqueIdentifier + ".jmx";
        try {
            // store the .jmx file in the temporary folder
            FileWriter fileWriter         = new FileWriter(jmxFilePath);
            BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
            bufferedWriter.write(outputBuffer.toString());
            bufferedWriter.close();
            fileWriter.close();
            traceln(LOG_PRIORITY_INFO, jmxFilePath + " successfully generated");
            executionSteps.add(new CExecutionStep(RESULT_SUCCESS, jmxFilePath + " successfully generated"));
            traceln(LOG_PRIORITY_INFO, outputBuffer.toString());
            return true;
        } catch (Exception e) {
            traceln(LOG_PRIORITY_SEVERE, "couldn't write " + jmxFilePath);
            executionSteps.add(new CExecutionStep(RESULT_UNKNOWN, "couldn't write " + jmxFilePath));
            return false;
        }
    }

    private void parseResults(Vector<CExecutionStep> executionSteps) throws FileNotFoundException, IOException {
        String currentLine, status, time;
        int index=0, nbFailures=0, sum=0, max=0;
        
        Vector<Float> maxTimeVector  = new Vector<Float>();
        Vector<Number> timeVector    = new Vector<Number>();
        Vector<Number> averageVector = new Vector<Number>();

        File jtlFile = new File(workingDir.getPath() + "/" + uniqueIdentifier + ".jtl");
        if (jtlFile.exists()) traceln(LOG_PRIORITY_INFO, "Result file " + jtlFile + " opened");

        try {
            FileReader fileReader         = new FileReader(jtlFile);
            BufferedReader bufferedReader = new BufferedReader(fileReader);
            
            while((currentLine = bufferedReader.readLine()) != null) {
                currentLine = new String(currentLine.getBytes(), "UTF-8");
                
                if(currentLine.contains("<httpSample")) { // if a <httpSample> tag is found, it's a result line
                    index++;

                    // +-----------------+
                	// | status          |
                	// +-----------------+
                    status = currentLine.replaceAll("^.*?rm=\"(.*?)\" .*?$", "$1"); // remove what's surrounding the "rm" attribute
                    if (!status.equals("OK")) nbFailures++;
                    
                	// +-----------------+
                	// | execution time  |
                	// +-----------------+
                    time = currentLine.replaceAll("^.*?t=\"(.*?)\" .*?$", "$1"); // remove what's surrounding the "t" attribute
                    if (Double.parseDouble(time) > max) maxTimeVector.add(Float.parseFloat(time));
                    timeVector.add(Double.parseDouble(time));
                    sum += Integer.parseInt(time);
                    averageVector.add(((float)sum/(float)index));
                }
            }
            
            int nbTimes = timeVector.size();
            float[] maxArray = new float[nbTimes];
            for (int i=0; i<timeVector.size(); i++) {
                maxArray[i] = timeVector.get(i).floatValue();
            }
            Arrays.sort(maxArray);
            
            float maxi = 0;
            nbTimes = maxArray.length > nbValuesToComputeMaxTime ? nbValuesToComputeMaxTime : maxArray.length;
            
            for (int i=maxArray.length-1; i>(maxArray.length-1-nbTimes); i--) {
                maxi += maxArray[i];
            }
            maxi    = maxi/nbTimes;
            maximum = maxi;
            traceln(LOG_PRIORITY_INFO, "Maximum = " + maximum);
            
            bufferedReader.close();
            fileReader.close();

            
            // +--------------------------------------------+
            // |  generate the chart from these statistics  |
            // +--------------------------------------------+
            XYSeries timeSeries    = new XYSeries("Execution time per request (1 dot = 1 request)");
            XYSeries averageSeries = new XYSeries("Trend of the average execution time of 1 request");
            for (int i=0; i<timeVector.size(); i++) {
                timeSeries.add(i, timeVector.get(i));
                averageSeries.add(i, averageVector.get(i));
            }

            XYSeriesCollection dataset = new XYSeriesCollection();
            dataset.addSeries(averageSeries);
            dataset.addSeries(timeSeries);
            
            NumberAxis rangeAxis1 = new NumberAxis("Time (ms)");
            NumberAxis rangeAxis2 = new NumberAxis("Requests (#)");
            
            XYItemRenderer renderer1 = new StandardXYItemRenderer();

            XYPlot xyplot = new XYPlot(dataset, rangeAxis2, rangeAxis1, renderer1);
            xyplot.setRangeAxisLocation(AxisLocation.BOTTOM_OR_LEFT);

            // create the chart
            JFreeChart chart = ChartFactory.createXYLineChart("Trend of the duration and averages for the test " + uniqueIdentifier, "Requests #", "Time (ms)", dataset, PlotOrientation.VERTICAL, true, true, true);
            chart.setBackgroundPaint(Color.white);
            XYPlot plot = (XYPlot) chart.getPlot();
            XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
            
            // average: we draw red lines
            renderer.setSeriesLinesVisible(0, true);
            renderer.setSeriesShapesVisible(0, false);
            renderer.setSeriesPaint(0, Color.red);

            // times:   we draw blue dots (1px ellipse)
            renderer.setSeriesLinesVisible(1, false);
            renderer.setSeriesShape(1, new Ellipse2D.Float(0, 0, 1, 2));
            renderer.setSeriesShapesVisible(1, true);
            renderer.setSeriesPaint(1, Color.blue);
            
            // annotation for the average
            String averageStr = averageVector.lastElement().toString();
            if(!averageStr.contains(".")) averageStr += ".";
            XYAnnotation annotation = new XYTextAnnotation("Average = " + Integer.parseInt(averageStr.substring(0, averageStr.indexOf("."))) + "ms", index*0.9, Float.parseFloat(averageStr)*1.1);
            plot.addAnnotation(annotation);
            
            // annotation for the maximum
            annotation = new XYTextAnnotation("Max (" + nbValuesToComputeMaxTime + " biggest values) = " + maxi + "ms", index*0.9, maxi*1.02);
            plot.addAnnotation(annotation);
            
            plot.setBackgroundAlpha(new Float(0.2));
            try {
                Image image = ImageIO.read(new File("export/resources/images/xqual_thumbnail_graphics.gif"));
                chart.setBackgroundImage(image);
                chart.setBackgroundImageAlpha(new Float(0.8));
                chart.setBackgroundImageAlignment(Align.TOP_LEFT);
            } catch (Exception e) {}
            
            
            plot.setRenderer(renderer);
            BufferedImage bufferedImage = chart.createBufferedImage(1024, 768);
            Calendar calendar = Calendar.getInstance();
            String filename = uniqueIdentifier + "_" + CCalendarUtils.calendarToStringForFileName(calendar) +".png";
            File imageFile = null;
            try {
                imageFile = new File(workingDir + "/" + filename);
                ImageIO.write(bufferedImage, "png", imageFile);
                addAttachment(imageFile);
                addAttachment(serverLogFile);
                addAttachment(new File(workingDir.getPath() + "/" + uniqueIdentifier + ".jmx"));
                addAttachment(new File(workingDir.getPath() + "/" + uniqueIdentifier + ".jtl"));
                addAttachment(new File(workingDir.getPath() + "/" + uniqueIdentifier + ".log"));                
            } catch (Exception e) {
                traceln(LOG_PRIORITY_SEVERE, "Couldn't save " + imageFile);
                executionSteps.add(new CExecutionStep(RESULT_UNKNOWN, "Couldn't save " + imageFile));
            }
        } catch (FileNotFoundException e) {
            traceln(LOG_PRIORITY_SEVERE, "Couldn't find " + jtlFile);
            executionSteps.add(new CExecutionStep(RESULT_UNKNOWN, "Couldn't find " + jtlFile));
        }
        
        // if we found at least once the tag <httpSample> we return the average time else 0
        if (index>0)
            average = ((float)sum/(float)index);
        else
            average = (new Float(0));

        measuredFailureRate = ((((float)nbFailures*100)/(this.nbRequestsPerUser*this.nbSimulatedUsers)));
    }
}

