/*******************************************************************************
* Copyright (c) 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Zend Technologies
*******************************************************************************/
package org.eclipse.php.core.tests.performance;
import java.io.File;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import junit.framework.Assert;
import org.h2.jdbcx.JdbcConnectionPool;
import org.osgi.framework.Bundle;
/**
* This monitor is created per testing plug-in for storing various performance
* information on tests execution. This class isn't thread safe, as only one
* test should be performed at a time in a single testing plug-in.
*
* @author Michael
*
*/
public class PerformanceMonitor {
private static final int AVG_SAMPLES = 5;
private static final int HISTORY_SIZE = 50;
private JdbcConnectionPool pool;
private int executionId;
public PerformanceMonitor(Bundle bundle) throws Exception {
Class.forName("org.h2.Driver");
File resultsDir = new File(System.getProperty("user.home")
+ File.separator + ".perf_results" + File.separator
+ bundle.getSymbolicName() + bundle.getVersion());
if (!resultsDir.exists()) {
resultsDir.mkdirs();
}
pool = JdbcConnectionPool.create("jdbc:h2:"
+ new File(resultsDir, "db").getAbsolutePath(), "test", "");
createSchema();
executionId = getExecutionId();
compact();
}
/**
* Global destruction method, which should be called when testing plug-in
* stops.
*/
public void dispose() {
try {
pool.dispose();
} catch (Exception e) {
e.printStackTrace();
}
}
private void createSchema() throws SQLException {
Connection connection = pool.getConnection();
try {
String sql = "CREATE TABLE IF NOT EXISTS TESTS(ID INT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR NOT NULL);\n"
+ "CREATE TABLE IF NOT EXISTS EXECUTIONS(ID INT AUTO_INCREMENT PRIMARY KEY, DATE TIMESTAMP NOT NULL);\n"
+ "CREATE TABLE IF NOT EXISTS RESULTS(EXECUTION_ID INT NOT NULL, TEST_ID INT NOT NULL, TIME LONG NOT NULL, FOREIGN KEY(EXECUTION_ID) REFERENCES EXECUTIONS(ID) ON UPDATE CASCADE ON DELETE CASCADE, FOREIGN KEY(TEST_ID) REFERENCES TESTS(ID) ON UPDATE CASCADE ON DELETE CASCADE);\n";
Statement statement = connection.createStatement();
try {
statement.executeUpdate(sql);
} finally {
statement.close();
}
} finally {
connection.close();
}
}
private void compact() throws SQLException {
Connection connection = pool.getConnection();
try {
PreparedStatement statement = connection
.prepareStatement("DELETE FROM EXECUTIONS WHERE ID IN (SELECT ID FROM (SELECT ROWNUM() AS R,ID FROM (SELECT ID FROM EXECUTIONS ORDER BY DATE DESC)) WHERE R > ?);");
statement.setInt(1, HISTORY_SIZE);
try {
statement.executeUpdate();
} finally {
statement.close();
}
} finally {
connection.close();
}
}
private int getExecutionId() throws Exception {
Connection connection = pool.getConnection();
try {
PreparedStatement statement = connection.prepareStatement(
"INSERT INTO EXECUTIONS(DATE) VALUES(?);",
Statement.RETURN_GENERATED_KEYS);
try {
statement.setTimestamp(1,
new Timestamp(System.currentTimeMillis()));
statement.executeUpdate();
ResultSet result = statement.getGeneratedKeys();
try {
result.next();
return result.getInt(1);
} finally {
result.close();
}
} finally {
statement.close();
}
} finally {
connection.close();
}
}
private int getTestId(String testId) throws Exception {
Connection connection = pool.getConnection();
try {
PreparedStatement statement = connection
.prepareStatement("SELECT ID FROM TESTS WHERE NAME=?;");
statement.setString(1, testId);
try {
ResultSet result = statement.executeQuery();
try {
if (result.next()) {
return result.getInt(1);
}
} finally {
result.close();
}
} finally {
statement.close();
}
statement = connection.prepareStatement(
"INSERT INTO TESTS(NAME) VALUES(?);",
Statement.RETURN_GENERATED_KEYS);
try {
statement.setString(1, testId);
statement.executeUpdate();
ResultSet result = statement.getGeneratedKeys();
try {
result.next();
return result.getInt(1);
} finally {
result.close();
}
} finally {
statement.close();
}
} finally {
connection.close();
}
}
private void writeResult(int testId, long time) throws SQLException {
Connection connection = pool.getConnection();
try {
PreparedStatement statement = connection
.prepareStatement("INSERT INTO RESULTS(EXECUTION_ID, TEST_ID, TIME) VALUES(?,?,?);");
try {
statement.setInt(1, executionId);
statement.setInt(2, testId);
statement.setLong(3, time);
statement.executeUpdate();
} finally {
statement.close();
}
} finally {
connection.close();
}
}
/**
* Returns average time of running a test.
*
* @param testId
* Test ID
* @param samples
* Number of invocations to take into account
* @return average time
* @throws SQLException
*/
private long getAverage(int testId, int samples) throws SQLException {
Connection connection = pool.getConnection();
try {
PreparedStatement statement = connection
.prepareStatement("SELECT COUNT(*),AVG(TIME) FROM ("
+ "SELECT R.TIME FROM RESULTS AS R JOIN TESTS AS T ON R.TEST_ID = T.ID AND T.ID=? "
+ "JOIN EXECUTIONS AS E ON R.EXECUTION_ID = E.ID "
+ "ORDER BY E.DATE DESC LIMIT ?);");
statement.setInt(1, testId);
statement.setInt(2, samples);
try {
ResultSet result = statement.executeQuery();
try {
if (result.next()) {
if (result.getInt(1) == samples) {
return result.getLong(2);
}
}
} finally {
result.close();
}
} finally {
statement.close();
}
} finally {
connection.close();
}
return -1;
}
/**
* Executes test operation, and measure execution time
*
* @param id
* Test identifier within the bounds of testing plug-in.
* @param operation
* Operation to test
* @param times
* How many times the operation will be executed for calculating
* average execution time
* @param threshold
* Threshold in percents
* @throws Exception
*/
public void execute(String id, Operation operation, int times, int threshold)
throws Exception {
int testId = getTestId(id);
long testTimeSum = 0;
for (int i = 0; i < times; ++i) {
long testStart = System.currentTimeMillis();
operation.run();
testTimeSum += System.currentTimeMillis() - testStart;
}
long testAverage = testTimeSum / times;
long savedAverage = getAverage(testId, AVG_SAMPLES);
if (savedAverage != 0 && savedAverage != -1
&& testAverage > savedAverage) {
long diff = testAverage - savedAverage;
if (diff * 100 / savedAverage > threshold) {
Assert.fail("Average execution time (" + testAverage
+ "ms) is greater by more than " + threshold
+ "% than the saved average (" + savedAverage + "ms)");
}
}
writeResult(testId, testAverage);
}
public interface Operation {
public void run() throws Exception;
}
}