/*
  Copyright (c) 2002, 2014, Oracle and/or its affiliates. All rights reserved.

  The MySQL Connector/J is licensed under the terms of the GPLv2
  <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors.
  There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
  this software, see the FLOSS License Exception
  <http://www.mysql.com/about/legal/licensing/foss-exception.html>.

  This program 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; version 2
  of the License.

  This program 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 this
  program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
  Floor, Boston, MA 02110-1301  USA

 */

package testsuite.regression;

import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Map;

import testsuite.BaseTestCase;

/**
 * Microperformance benchmarks to track increase/decrease in performance of core
 * methods in the driver over time.
 * 
 * @author Mark Matthews
 * 
 * @version $Id: MicroPerformanceRegressionTest.java,v 1.1.2.1 2005/05/13
 *          18:58:38 mmatthews Exp $
 */
public class MicroPerformanceRegressionTest extends BaseTestCase {

	private double scaleFactor = 1.0;

	private final static int ORIGINAL_LOOP_TIME_MS = 2300;

	private final static double LEEWAY = 10.0; // account for VMs

	private final static Map<String, Double> BASELINE_TIMES = new HashMap<String, Double>();

	static {
		BASELINE_TIMES.put("ResultSet.getInt()", new Double(0.00661));
		BASELINE_TIMES.put("ResultSet.getDouble()", new Double(0.00671));
		BASELINE_TIMES.put("ResultSet.getTime()", new Double(0.02033));
		BASELINE_TIMES.put("ResultSet.getTimestamp()", new Double(0.02363));
		BASELINE_TIMES.put("ResultSet.getDate()", new Double(0.02223));
		BASELINE_TIMES.put("ResultSet.getString()", new Double(0.00982));
		BASELINE_TIMES.put("ResultSet.getObject() on a string", new Double(
				0.00861));
		BASELINE_TIMES
				.put("Connection.prepareStatement()", new Double(0.18547));
		BASELINE_TIMES.put("single selects", new Double(46));
		BASELINE_TIMES.put("5 standalone queries", new Double(146));
		BASELINE_TIMES.put("total time all queries", new Double(190));
		if (com.mysql.jdbc.Util.isJdbc4()) {
			BASELINE_TIMES
					.put("PreparedStatement.setInt()", new Double(0.0014));
			BASELINE_TIMES.put("PreparedStatement.setTime()",
					new Double(0.0107));
			BASELINE_TIMES.put("PreparedStatement.setTimestamp()", new Double(
					0.0182));
			BASELINE_TIMES.put("PreparedStatement.setDate()",
					new Double(0.0819));
			BASELINE_TIMES.put("PreparedStatement.setString()", new Double(
					0.0081));
			BASELINE_TIMES.put("PreparedStatement.setObject() on a string",
					new Double(0.00793));
			BASELINE_TIMES.put("PreparedStatement.setDouble()", new Double(
					0.0246));
		} else {
			BASELINE_TIMES
					.put("PreparedStatement.setInt()", new Double(0.0011));
			BASELINE_TIMES.put("PreparedStatement.setTime()",
					new Double(0.0642));
			BASELINE_TIMES.put("PreparedStatement.setTimestamp()", new Double(
					0.03184));
			BASELINE_TIMES.put("PreparedStatement.setDate()", new Double(
					0.12248));
			BASELINE_TIMES.put("PreparedStatement.setString()", new Double(
					0.01512));
			BASELINE_TIMES.put("PreparedStatement.setObject() on a string",
					new Double(0.01923));
			BASELINE_TIMES.put("PreparedStatement.setDouble()", new Double(
					0.00671));
		}
	}

	public MicroPerformanceRegressionTest(String name) {
		super(name);
	}

	/**
	 * Runs all test cases in this test suite
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		junit.textui.TestRunner.run(MicroPerformanceRegressionTest.class);
	}

	/**
	 * Tests result set accessors performance.
	 * 
	 * @throws Exception
	 *             if the performance of these methods does not meet
	 *             expectations.
	 */
	public void testResultSetAccessors() throws Exception {
		createTable(
				"marktest",
				"(intField INT, floatField DOUBLE, timeField TIME, datetimeField DATETIME, stringField VARCHAR(64))");
		this.stmt
				.executeUpdate("INSERT INTO marktest VALUES (123456789, 12345.6789, NOW(), NOW(), 'abcdefghijklmnopqrstuvABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@')");

		this.rs = this.stmt
				.executeQuery("SELECT intField, floatField, timeField, datetimeField, stringField FROM marktest");

		this.rs.next();

		int numLoops = 100000;

		long start = currentTimeMillis();

		for (int i = 0; i < numLoops; i++) {
			this.rs.getInt(1);
		}

		double getIntAvgMs = (double) (currentTimeMillis() - start) / numLoops;

		checkTime("ResultSet.getInt()", getIntAvgMs);

		start = currentTimeMillis();

		for (int i = 0; i < numLoops; i++) {
			this.rs.getDouble(2);
		}

		double getDoubleAvgMs = (double) (currentTimeMillis() - start)
				/ numLoops;

		checkTime("ResultSet.getDouble()", getDoubleAvgMs);

		start = currentTimeMillis();

		for (int i = 0; i < numLoops; i++) {
			this.rs.getTime(3);
		}

		double getTimeAvgMs = (double) (currentTimeMillis() - start) / numLoops;

		checkTime("ResultSet.getTime()", getTimeAvgMs);

		start = currentTimeMillis();

		for (int i = 0; i < numLoops; i++) {
			this.rs.getTimestamp(4);
		}

		double getTimestampAvgMs = (double) (currentTimeMillis() - start)
				/ numLoops;

		checkTime("ResultSet.getTimestamp()", getTimestampAvgMs);

		start = currentTimeMillis();

		for (int i = 0; i < numLoops; i++) {
			this.rs.getDate(4);
		}

		double getDateAvgMs = (double) (currentTimeMillis() - start) / numLoops;

		checkTime("ResultSet.getDate()", getDateAvgMs);

		start = currentTimeMillis();

		for (int i = 0; i < numLoops; i++) {
			this.rs.getString(5);
		}

		double getStringAvgMs = (double) (currentTimeMillis() - start)
				/ numLoops;

		checkTime("ResultSet.getString()", getStringAvgMs);

		start = currentTimeMillis();

		for (int i = 0; i < numLoops; i++) {
			this.rs.getObject(5);
		}

		double getStringObjAvgMs = (double) (currentTimeMillis() - start)
				/ numLoops;

		checkTime("ResultSet.getObject() on a string", getStringObjAvgMs);
	}

	public void testPreparedStatementTimes() throws Exception {
		createTable(
				"marktest",
				"(intField INT, floatField DOUBLE, timeField TIME, datetimeField DATETIME, stringField VARCHAR(64))");
		this.stmt
				.executeUpdate("INSERT INTO marktest VALUES (123456789, 12345.6789, NOW(), NOW(), 'abcdefghijklmnopqrstuvABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@')");

		long start = currentTimeMillis();

		long blockStart = currentTimeMillis();
		long lastBlock = 0;

		int numLoops = 100000;

		int numPrepares = 100000;

		if (versionMeetsMinimum(4, 1)) {
			numPrepares = 10000; // we don't need to do so many for
			// server-side prep statements...
		}

		for (int i = 0; i < numPrepares; i++) {
			if (i % 1000 == 0) {

				long blockEnd = currentTimeMillis();

				long totalTime = blockEnd - blockStart;

				blockStart = blockEnd;

				StringBuffer messageBuf = new StringBuffer();

				messageBuf.append(i + " prepares, the last 1000 prepares took "
						+ totalTime + " ms");

				if (lastBlock == 0) {
					lastBlock = totalTime;
					messageBuf.append(".");
				} else {
					double diff = (double) totalTime / (double) lastBlock;

					messageBuf.append(", difference is " + diff + " x");

					lastBlock = totalTime;
				}

				System.out.println(messageBuf.toString());

			}

			PreparedStatement pStmt = this.conn
					.prepareStatement("INSERT INTO test.marktest VALUES (?, ?, ?, ?, ?)");
			pStmt.close();
		}

		@SuppressWarnings("unused")
		double getPrepareStmtAvgMs = (double) (currentTimeMillis() - start) / numPrepares;

		// checkTime("Connection.prepareStatement()", getPrepareStmtAvgMs);

		PreparedStatement pStmt = this.conn
				.prepareStatement("INSERT INTO marktest VALUES (?, ?, ?, ?, ?)");

		System.out.println(pStmt.toString());

		start = currentTimeMillis();

		for (int i = 0; i < numLoops; i++) {
			pStmt.setInt(1, 1);
		}

		System.out.println(pStmt.toString());

		double setIntAvgMs = (double) (currentTimeMillis() - start) / numLoops;

		checkTime("PreparedStatement.setInt()", setIntAvgMs);

		start = currentTimeMillis();

		for (int i = 0; i < numLoops; i++) {
			pStmt.setDouble(2, 1234567890.1234);
		}

		double setDoubleAvgMs = (double) (currentTimeMillis() - start)
				/ numLoops;

		checkTime("PreparedStatement.setDouble()", setDoubleAvgMs);

		start = currentTimeMillis();

		Time tm = new Time(start);

		for (int i = 0; i < numLoops; i++) {
			pStmt.setTime(3, tm);
		}

		double setTimeAvgMs = (double) (currentTimeMillis() - start) / numLoops;

		checkTime("PreparedStatement.setTime()", setTimeAvgMs);

		start = currentTimeMillis();

		Timestamp ts = new Timestamp(start);

		for (int i = 0; i < numLoops; i++) {
			pStmt.setTimestamp(4, ts);
		}

		double setTimestampAvgMs = (double) (currentTimeMillis() - start)
				/ numLoops;

		checkTime("PreparedStatement.setTimestamp()", setTimestampAvgMs);

		start = currentTimeMillis();

		Date dt = new Date(start);

		for (int i = 0; i < numLoops; i++) {
			pStmt.setDate(4, dt);
		}

		double setDateAvgMs = (double) (currentTimeMillis() - start) / numLoops;

		checkTime("PreparedStatement.setDate()", setDateAvgMs);

		start = currentTimeMillis();

		for (int i = 0; i < numLoops; i++) {
			pStmt.setString(5,
					"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@");
		}

		double setStringAvgMs = (double) (currentTimeMillis() - start)
				/ numLoops;

		checkTime("PreparedStatement.setString()", setStringAvgMs);

		start = currentTimeMillis();

		for (int i = 0; i < numLoops; i++) {
			pStmt.setObject(5,
					"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@");
		}

		double setStringObjAvgMs = (double) (currentTimeMillis() - start)
				/ numLoops;

		checkTime("PreparedStatement.setObject() on a string",
				setStringObjAvgMs);

		start = currentTimeMillis();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see junit.framework.TestCase#setUp()
	 */
	public void setUp() throws Exception {
		super.setUp();

		System.out.println("Calculating performance scaling factor...");
		// Run this simple test to get some sort of performance scaling factor,
		// compared to
		// the development environment. This should help reduce false-positives
		// on this test.
		int numLoops = 10000;

		long start = currentTimeMillis();

		for (int j = 0; j < 2000; j++) {
			StringBuffer buf = new StringBuffer(numLoops);

			for (int i = 0; i < numLoops; i++) {
				buf.append('a');
			}
		}

		long elapsedTime = currentTimeMillis() - start;

		System.out.println("Elapsed time for factor: " + elapsedTime);

		this.scaleFactor = (double) elapsedTime
				/ (double) ORIGINAL_LOOP_TIME_MS;

		System.out
				.println("Performance scaling factor is: " + this.scaleFactor);
	}

	private void checkTime(String testType, double avgExecTimeMs)
			throws Exception {

		double adjustForVendor = 1.0D;

		if (isRunningOnJRockit()) {
			adjustForVendor = 4.0D;
		}

		Double baselineExecTimeMs = BASELINE_TIMES.get(testType);

		if (baselineExecTimeMs == null) {
			throw new Exception("No baseline time recorded for test '"
					+ testType + "'");
		}

		double acceptableTime = LEEWAY * baselineExecTimeMs.doubleValue()
				* this.scaleFactor * adjustForVendor;

		assertTrue("Average execution time of " + avgExecTimeMs
				+ " ms. exceeded baseline * leeway of " + acceptableTime
				+ " ms.", (avgExecTimeMs <= acceptableTime));
	}

	public void testBug6359() throws Exception {
		if (runLongTests()) {
			int numRows = 550000;
			int numSelects = 100000;

			createTable(
					"testBug6359",
					"(pk_field INT PRIMARY KEY NOT NULL AUTO_INCREMENT, field1 INT, field2 INT, field3 INT, field4 INT, field5 INT, field6 INT, field7 INT, field8 INT, field9 INT,  INDEX (field1))");

			PreparedStatement pStmt = this.conn
					.prepareStatement("INSERT INTO testBug6359 (field1, field2, field3, field4, field5, field6, field7, field8, field9) VALUES (?, 1, 2, 3, 4, 5, 6, 7, 8)");

			logDebug("Loading " + numRows + " rows...");

			for (int i = 0; i < numRows; i++) {
				pStmt.setInt(1, i);
				pStmt.executeUpdate();

				if ((i % 10000) == 0) {
					logDebug(i + " rows loaded so far");
				}
			}

			logDebug("Finished loading rows");

			long begin = currentTimeMillis();

			long beginSingleQuery = currentTimeMillis();

			for (int i = 0; i < numSelects; i++) {
				this.rs = this.stmt
						.executeQuery("SELECT pk_field FROM testBug6359 WHERE field1 BETWEEN 1 AND 5");
			}

			long endSingleQuery = currentTimeMillis();

			double secondsSingleQuery = ((double) endSingleQuery - (double) beginSingleQuery) / 1000;

			logDebug("time to execute " + numSelects + " single queries: "
					+ secondsSingleQuery + " seconds");

			checkTime("single selects", secondsSingleQuery);

			PreparedStatement pStmt2 = this.conn
					.prepareStatement("SELECT field2, field3, field4, field5 FROM testBug6359 WHERE pk_field=?");

			long beginFiveQueries = currentTimeMillis();

			for (int i = 0; i < numSelects; i++) {

				for (int j = 0; j < 5; j++) {
					pStmt2.setInt(1, j);
					pStmt2.executeQuery();
				}
			}

			long endFiveQueries = currentTimeMillis();

			double secondsFiveQueries = ((double) endFiveQueries - (double) beginFiveQueries) / 1000;

			logDebug("time to execute " + numSelects
					+ " 5 standalone queries: " + secondsFiveQueries
					+ " seconds");

			checkTime("5 standalone queries", secondsFiveQueries);

			long end = currentTimeMillis();

			double seconds = ((double) end - (double) begin) / 1000;

			logDebug("time to execute " + numSelects + " selects: " + seconds
					+ " seconds");

			checkTime("total time all queries", seconds);
		}
	}

}
