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); } }