package org.prevayler.demos.scalability;
import org.prevayler.foundation.Cool;
import org.prevayler.foundation.StopWatch;
import java.text.DecimalFormat;
import java.util.LinkedList;
import java.util.List;
/**
* Represents a single run of a scalability test. To understand the implementation of this class, you must be familiar with Prevayler's Scalability Test (run org.prevayler.test.scalability.ScalabilityTest).
*/
abstract class ScalabilityTestRun {
static private final long ROUND_DURATION_MILLIS=1000 * 20;
private final ScalabilityTestSubject subject;
protected final int numberOfObjects;
private double bestRoundOperationsPerSecond;
private int bestRoundThreads;
private final List connectionCache=new LinkedList();
private long operationCount=0;
private long lastOperation=0;
private boolean isRoundFinished;
private int activeRoundThreads=0;
/**
* @return Example: "123.12 operations/second (12 threads)".
*/
public String getResult(){
return toResultString(bestRoundOperationsPerSecond,bestRoundThreads);
}
public double getOperationsPerSecond(){
return bestRoundOperationsPerSecond;
}
protected ScalabilityTestRun( ScalabilityTestSubject subject, int numberOfObjects, int minThreads, int maxThreads){
if (minThreads > maxThreads) throw new IllegalArgumentException("The minimum number of threads cannot be greater than the maximum number.");
if (minThreads < 1) throw new IllegalArgumentException("The minimum number of threads cannot be smaller than one.");
this.subject=subject;
this.numberOfObjects=numberOfObjects;
out("\n\n========= Running " + name() + " ("+ (maxThreads - minThreads + 1)+ " rounds). Subject: "+ subject.name()+ "...");
prepare();
out("Each round will take approx. " + ROUND_DURATION_MILLIS / 1000 + " seconds to run...");
performTest(minThreads,maxThreads);
out("\n----------- BEST ROUND: " + getResult());
}
protected void prepare(){
subject.replaceAllRecords(numberOfObjects);
System.gc();
}
/**
* @return The name of the test to be executed. Example: "Prevayler Query Test".
*/
protected abstract String name();
private void performTest( int minThreads, int maxThreads){
int threads=minThreads;
while (threads <= maxThreads) {
double operationsPerSecond=performRound(threads);
if (operationsPerSecond > bestRoundOperationsPerSecond) {
bestRoundOperationsPerSecond=operationsPerSecond;
bestRoundThreads=threads;
}
threads++;
}
}
/**
* @return The number of operations the test managed to execute per second with the given number of threads.
*/
private double performRound( int threads){
long initialOperationCount=operationCount;
StopWatch stopWatch=StopWatch.start();
startThreads(threads);
sleep();
stopThreads();
double secondsEllapsed=stopWatch.secondsEllapsed();
double operationsPerSecond=(operationCount - initialOperationCount) / secondsEllapsed;
out("\nMemory used: " + Runtime.getRuntime().totalMemory());
subject.reportResourcesUsed(System.out);
out("Seconds ellapsed: " + secondsEllapsed);
out("--------- Round Result: " + toResultString(operationsPerSecond,threads));
return operationsPerSecond;
}
private void startThreads( int threads){
isRoundFinished=false;
int i=1;
while (i <= threads) {
startThread(lastOperation + i,threads);
i++;
}
}
private void startThread( final long startingOperation, final int operationIncrement){
(new Thread(){
public void run(){
try {
Object connection=acquireConnection();
long operation=startingOperation;
while (!isRoundFinished) {
executeOperation(connection,operation);
operation+=operationIncrement;
}
synchronized (connectionCache) {
connectionCache.add(connection);
operationCount+=(operation - startingOperation) / operationIncrement;
if (lastOperation < operation) lastOperation=operation;
activeRoundThreads--;
}
}
catch ( OutOfMemoryError err) {
outOfMemory();
}
}
}
).start();
activeRoundThreads++;
}
protected abstract void executeOperation( Object connection, long operation);
private Object acquireConnection(){
synchronized (connectionCache) {
return connectionCache.isEmpty() ? subject.createTestConnection() : connectionCache.remove(0);
}
}
private void stopThreads(){
isRoundFinished=true;
while (activeRoundThreads != 0) {
Thread.yield();
}
}
static private String toResultString( double operationsPerSecond, int threads){
String operations=new DecimalFormat("0.00").format(operationsPerSecond);
return "" + operations + " operations/second ("+ threads+ " threads)";
}
static void outOfMemory(){
System.gc();
out("\n\nOutOfMemoryError.\n" + "===========================================================\n" + "The VM must be started with a sufficient maximum heap size.\n"+ "Example for Linux and Windows: java -Xmx512000000 ...\n\n");
}
static private void sleep(){
Cool.sleep(ROUND_DURATION_MILLIS);
}
static private void out( Object obj){
System.out.println(obj);
}
}