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