/*-------------------------------------------------------------------------
 * rbe.EBStats.java
 * Timothy Heil
 * 10/19/99
 *
 * ECE902 Fall '99
 *
 * Collects statistics for TPC-W.
 *------------------------------------------------------------------------
 *
 * This is part of the the Java TPC-W distribution,
 * written by Harold Cain, Tim Heil, Milo Martin, Eric Weglarz, and Todd
 * Bezenek.  University of Wisconsin - Madison, Computer Sciences
 * Dept. and Dept. of Electrical and Computer Engineering, as a part of
 * Prof. Mikko Lipasti's Fall 1999 ECE 902 course.
 *
 * Copyright (C) 1999, 2000 by Harold Cain, Timothy Heil, Milo Martin, 
 *                             Eric Weglarz, Todd Bezenek.
 *
 * This source code is distributed "as is" in the hope that it will be
 * useful.  It comes with no warranty, and no author or distributor
 * accepts any responsibility for the consequences of its use.
 *
 * Everyone is granted permission to copy, modify and redistribute
 * this code under the following conditions:
 *
 * This code is distributed for non-commercial use only.
 * Please contact the maintainer for restrictions applying to 
 * commercial use of these tools.
 *
 * Permission is granted to anyone to make or distribute copies
 * of this code, either as received or modified, in any
 * medium, provided that all copyright notices, permission and
 * nonwarranty notices are preserved, and that the distributor
 * grants the recipient permission for further redistribution as
 * permitted by this document.
 *
 * Permission is granted to distribute this code in compiled
 * or executable form under the same conditions that apply for
 * source code, provided that either:
 *
 * A. it is accompanied by the corresponding machine-readable
 *    source code,
 * B. it is accompanied by a written offer, with no time limit,
 *    to give anyone a machine-readable copy of the corresponding
 *    source code in return for reimbursement of the cost of
 *    distribution.  This written offer must permit verbatim
 *    duplication by anyone, or
 * C. it is distributed by someone who received only the
 *    executable form, and is accompanied by a copy of the
 *    written offer of source code that they received concurrently.
 *
 * In other words, you are welcome to use, share and improve this codes.
 * You are forbidden to forbid anyone else to use, share and improve what
 * you give them.
 *
 ************************************************************************/

package rbe;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import rbe.util.Histogram;
import rbe.util.Pad;

public class EBStats {

  public  static int maxError = 0;
  public  static int errorCnt = 0;
  private static boolean DEBUG = false;
  
  private static final int TRANSACTION_TYPES_AMOUNT = 15;

  private final Histogram [] wirt;     // Luis Rodero-Merino: WIRT stands for 'Web Interaction Response Time', see TPC-W Spec.  
  private final Histogram tt;          // Think Time.
  private final int [][] trans;
  private final List<Long> start_times = new ArrayList<Long>();
  private final List<Long> end_times = new ArrayList<Long>();
  private RBE rbe;

  // Luis Rodero-Merino: to stored all transaction times split by type of transaction
  public List[] transactionTimesArray = (ArrayList[])Array.newInstance(ArrayList.class, TRANSACTION_TYPES_AMOUNT);
  public List[] transactionInitTimesArray = (ArrayList[])Array.newInstance(ArrayList.class, TRANSACTION_TYPES_AMOUNT);
  public List<Long> allTimesArray = new ArrayList<Long>(); 
  public List<Long> allInitTimesArray = new ArrayList<Long>(); 
  
  // Luis Rodero-Merino: to write 'live' monitoring data on a certain file
  private LiveMonitor liveMonitor = null;
  
  // List of retries/errors encount.
  public final Vector errors = new Vector(0);  

  // Web-interaction throughput over time, stored in an histogram
  // Sampled continuously at 1 second intervals for upto two hours, one bin per second. Each bin contains the amount of operations done at that second.
  public final int [] through = new int[7200];


  // The following four times are absolute times (UTC), millis.
  //  They are corrected for the slow-down factor (rbe.slowDown).

  long start;    // When to start test
  
  // WIRT and think times are only measured over the measurement interval
  //  (MI)
  long startMI;  // When measurement interval starts.
  long startRD;  // When measurement interval ends.
  long term;     // When to terminate test
  
  // Times are all expressed in milliseconds.
  public EBStats(RBE rbe,
					  int wirt_max, int wirt_binSize,
					  int tt_max, int tt_binSize,
					  int states,
					  long start,    // When to start test.
					  long miWait,   // Length of ramp-up
					  long miDur,    // Duration of measurement interval
					  long rampDown, // Length of ramp-down
					  int secondsBetweenMeasurements,
					  File liveMeasurementsDestFileCSV,
					  File liveMeasurementsDestFileTxt)
  {
	 this.rbe = rbe;
	 this.start =  start;
	 startMI = start + rbe.slow(miWait);
	 startRD   = startMI + rbe.slow(miDur);
	 term    = startRD + rbe.slow(rampDown);

	 if (DEBUG)
		System.out.println("start " + start + " startMI " + startMI + " startRD " + startRD);

	 // Force loading of EBError class ???
	 new EBError("temp", "dud");

	 /////////////////////////////////////////
	 // Luis Rodero-Merino: WHY 15 HISTOGRAMS? BEAUSE OF THE 15 POSSIBLE TRANSITIONS I GUESS (INIT + 14 FROM THE TPC-W SPEC)
	 // For each transaction type, each one of the bins contains the amount of transactions of that type that lasted the amount of time given by the bin 
	 /////////////////////////////////////////
	 wirt = new Histogram[TRANSACTION_TYPES_AMOUNT];
	 for (int h=0;h<wirt.length;h++)
		 wirt[h] = new Histogram(wirt_max/wirt_binSize, wirt_binSize);
	 tt   = new Histogram(tt_max/tt_binSize, tt_binSize);
	 
	 // Luis Rodero-Merino: init transaction times arrays
	 for(int index=0; index < transactionTimesArray.length; index++) {
		 transactionTimesArray[index] = new ArrayList();
		 transactionInitTimesArray[index] = new ArrayList();
	 }
	 
	 // Luis Rodero-Merino, start thread that writes 'live' monitoring data
	 try {
		liveMonitor = new LiveMonitor(liveMeasurementsDestFileCSV, liveMeasurementsDestFileTxt, secondsBetweenMeasurements);
	} catch (FileNotFoundException exception) {
		throw new Error("Could not create live monitor, exception caught", exception);
	}

	 trans = new int[states][states];
  }

  public final synchronized void transition(int cur, int next) {
	 if (System.currentTimeMillis() < start) // Luis Rodero-Merino: ignoring if event happened before 'start' time
		 return;
    trans[cur][next]++;
  }
  
  // Luis Rodero-Merino, report to live monitor
  public synchronized void startedInteraction() {
	  liveMonitor.txStarted();
  }

  public synchronized void finishedInteraction(int state, long wirt_t1, long wirt_t2, long itt) {
	  
	  // Luis Rodero-Merino, report to the live monitor
	  liveMonitor.txOver(wirt_t2-wirt_t1);
	  
	 // Discard interactions that completed before the start
	 //  of the ramp-up period.
	 if (wirt_t2 < start)
		 return;
	  //HWC
	 start_times.add(wirt_t1);
	 end_times.add(wirt_t2);
	 //end HWC
	 
	 int throughputHistogramBin= ((int) (rbe.speed(wirt_t2-start)/1000L)); // Throughput bin.
	 if (throughputHistogramBin < through.length)
	     through[throughputHistogramBin]++; // Throughput histogram
	 
	 if (DEBUG) {
	     System.out.println("t2 " + wirt_t2 + " startMI " + startMI + " startRD " + startRD);
	     System.out.println("Interact " + ((wirt_t2-start)/1000L) + " b " + throughputHistogramBin);
	 }

	 if ((wirt_t2 >= startMI) && (wirt_t2 <= startRD)) {
	     if (DEBUG)
		    System.out.println("adding...");		
	      wirt[state].add((int) rbe.speed((wirt_t2-wirt_t1))); // Transaction histogram and bin
	      tt.add((int) rbe.speed(itt));
	 	 // Luis Rodero-Merino: store transaction time
	     long wirt = wirt_t2-wirt_t1;
	 	 transactionTimesArray[state].add(wirt);
	 	 transactionInitTimesArray[state].add(wirt_t1);
	 	 allTimesArray.add(wirt);
	 	 allInitTimesArray.add(wirt_t1);
	 }
	 
  }

  public void error(String message, String url) 
  {
    EBError error = new EBError(message, url);
    errors.addElement(error);
    System.out.println(error);
	 errorCnt++;
	 if ((errorCnt >= maxError) && (maxError>0)) {
		System.exit(-1);
	 }
  };


  public void print(PrintStream out) {
	 int h;

	 out.println("function [dat] = tpcw()");

	 for (h=0;h<wirt.length;h++) {
		wirt[h].printMFile(out, "dat.wirt{" + (h+1) + "}");
	 }

	 tt.printMFile(out, "dat.tt");

    int i,j,tot;

    out.println("dat.trans = [");
    for (i=0;i<trans.length;i++) {
      for (j=0;j<trans.length;j++) {
		  out.print(" " + Pad.l(6, "" + trans[i][j]));
      }
		out.println();
    }
	 out.println("];\n");

	 out.print("dat.interact = [");
    for (j=0;j<trans.length;j++) {
      for (i=0,tot=0;i<trans.length;tot=tot + trans[i][j],i++);
		out.print(" " + Pad.l(6, "" + tot));
	 }
    out.println("];\n");

	 out.println("dat.wips = [");
	 for (j=0;j<through.length;j++) {
		out.println(""+through[j]);
	 }
    out.println("];\n");

    out.println("dat.starttimes = [");
    for(int index = 0; index < start_times.size(); index++)
    	out.println(""+(start_times.get(index) - start_times.get(0)));
    
    out.println("];\n");
    out.println("dat.endtimes = [");
    for(int index = 0; index < start_times.size(); index++)
    	out.println(""+(end_times.get(index) - start_times.get(0)));
    
    out.println("];\n");
    out.println("dat.numinteractions = " + start_times.size() + ";");
	 // Absolute UTC time (millis).
	 out.println("dat.startRU =  " + start + ";");

	 // Slowdown corrected UTC time (millis).
	 out.println("dat.startMI =  " + (start + rbe.speed(startMI-start)) + ";");
	 out.println("dat.startRD =  " + (start + rbe.speed(startRD-start)) + ";");
	 out.println("dat.term    =  " + (start + rbe.speed(term-start)) + ";");

	 // slow down factor.
	 out.println("dat.slowDown = " + rbe.slowDown + ";");

    out.println("% Errors");
    for (i=0;i<errors.size();i++) {
      out.println("%" + errors.elementAt(i));
    }
    out.println("% Total Errors: " + errors.size());
  }

  public void waitForRampDown()
  {
	 try {
		waitForStart();
		long w = term - System.currentTimeMillis();
		if (w<0) return;
			Thread.currentThread().sleep(w);
	 } catch (InterruptedException ie) {
	     System.out.println("In waitforrampdown, caught interrupted exception");
	 }
	 liveMonitor.stop();
  }

  public void waitForStart()
		 throws InterruptedException
  {
	 long w = start - System.currentTimeMillis();
	 if (w<0) return;

	 Thread.currentThread().sleep(w);
  }
}


class EBError {
  public String message;
  public String url;

  public EBError(String message, String url) {
    this.message = message;
    this.url = url;
  }

  public String toString() {
    return( "EB Error: " + message + "(" + url + ")");
  }
}

//Luis Rodero-Merino, LSD at UPM, 13 April 2012
//This class shows every 'x' seconds the throughput and average tx time
//of all tx run during those last x seconds
class LiveMonitor implements Runnable {
	
	private final static int DEFAULT_SECONDS_BETWEEN_MEASUREMENTS = 5;
	
	private boolean stopped = false;
	private int secondsBetweenMeasurements = -1;
	private PrintWriter monitoringResultsDestFileWriterCSV = null;
	private PrintWriter monitoringResultsHomeFileWriterCSV = null;
	private PrintWriter monitoringResultsDestFileWriterTxt = null;
	private PrintWriter monitoringResultsHomeFileWriterTxt = null;
	private List<Long> txDurations = new ArrayList<Long>();
	private Thread thread = new Thread(this, "LiveMeasurementsThread");
	private int measurementsSoFar = 0;
	private int totalTxStarted = 0;
	private int totalTxFinished = 0;
	
	public LiveMonitor(File monitoringResultsDestFileCSV, File monitoringResultsDestFileTxt) throws FileNotFoundException {
		this(monitoringResultsDestFileCSV, monitoringResultsDestFileTxt, DEFAULT_SECONDS_BETWEEN_MEASUREMENTS);
	}
	
	public LiveMonitor(File monitoringResultsDestFileCSV, File monitoringResultsDestFileTxt, int secondsBetweenMeasurements) throws FileNotFoundException {
		this.secondsBetweenMeasurements = secondsBetweenMeasurements;
		monitoringResultsDestFileWriterCSV = new PrintWriter(new FileOutputStream(monitoringResultsDestFileCSV), true);
		File monitoringResultsHomeFileCSV = new File(System.getProperty("user.home"), monitoringResultsDestFileCSV.getName());
		try {
			if(!monitoringResultsHomeFileCSV.getCanonicalPath().equals(monitoringResultsDestFileCSV.getCanonicalPath()))
				monitoringResultsHomeFileWriterCSV = new PrintWriter(new FileOutputStream(monitoringResultsHomeFileCSV), true);
		} catch (IOException exception) {
		}
		monitoringResultsDestFileWriterTxt = new PrintWriter(new FileOutputStream(monitoringResultsDestFileTxt), true);
		File monitoringResultsHomeFileTxt = new File(System.getProperty("user.home"), monitoringResultsDestFileTxt.getName());
		try {
			if(!monitoringResultsHomeFileTxt.getCanonicalPath().equals(monitoringResultsDestFileTxt.getCanonicalPath()))
				monitoringResultsHomeFileWriterTxt = new PrintWriter(new FileOutputStream(monitoringResultsHomeFileTxt), true);
		} catch (IOException exception) {
		}
		thread.setDaemon(true);
		thread.start();
	}
	
	protected synchronized void stop() {
		stopped = true;
		notify();
	}
	
	protected synchronized void txStarted() {
		totalTxStarted++;
	}
	
	protected synchronized void txOver(long duration) {
		totalTxFinished++;
		txDurations.add(duration);
	}

	public void run() {
		// Writing headers
		String csvLiveStatsHeader = "timestamp, time, txstarted, txfinished, throughput, avtxduration";
		monitoringResultsDestFileWriterCSV.println(csvLiveStatsHeader);
		if(monitoringResultsHomeFileWriterCSV != null)
			monitoringResultsHomeFileWriterCSV.println(csvLiveStatsHeader);
		String txtLiveStatsHeader = "#Timestamp\tTime\tTxStarted\tTxFinished\tThroughput\tAvTxDuration";
		monitoringResultsDestFileWriterTxt.println(txtLiveStatsHeader);
		if(monitoringResultsHomeFileWriterTxt != null)
			monitoringResultsHomeFileWriterTxt.println(txtLiveStatsHeader);
		while(!stopped) {
			try {
				Thread.sleep(secondsBetweenMeasurements * 1000);
			} catch (InterruptedException exception) {
				if(!stopped) {
					System.err.println("Something wrong!, interrupted thread");
					exception.printStackTrace(System.err);
				}
				break;
			}
			synchronized(this) {
				measurementsSoFar++;
				int secondsSoFar = measurementsSoFar * secondsBetweenMeasurements;
				int totalTx = txDurations.size();
				double throughput = (totalTx*1.0) / secondsBetweenMeasurements;
				double allTxDuration = 0.0;
				for(long txDuration: txDurations)
					allTxDuration += txDuration;
				double avTxDuration = (totalTx > 0 ? allTxDuration/totalTx : 0.0);
				long time = System.currentTimeMillis();
				String csvLiveStatsLine = time + ", " + secondsSoFar + ", " + totalTxStarted + ", " + totalTxFinished + ", " + throughput + ", " + avTxDuration;
				monitoringResultsDestFileWriterCSV.println(csvLiveStatsLine);
				if(monitoringResultsHomeFileWriterCSV != null)
					monitoringResultsHomeFileWriterCSV.println(csvLiveStatsLine);
				String txtLiveStatsLine = time + "\t" + secondsSoFar + "\t" + totalTxStarted + "\t" + totalTxFinished + "\t" + throughput + "\t" + avTxDuration;
				monitoringResultsDestFileWriterTxt.println(txtLiveStatsLine);
				if(monitoringResultsHomeFileWriterTxt != null)
					monitoringResultsHomeFileWriterTxt.println(txtLiveStatsLine);
				txDurations.clear();
				totalTxStarted = 0;
				totalTxFinished = 0;
			}
		}
		monitoringResultsDestFileWriterCSV.close();
		if(monitoringResultsHomeFileWriterCSV != null)
			monitoringResultsHomeFileWriterCSV.close();
	}
	
}
