/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.testing.framework; import java.util.*; /** * This test compares the concurrency of an operation defined run(). * It compares the performance of performing the task with 1, 2, 4, and 16 threads. * On a single CPU machine all of the results should be similar, * on a multi-CPU machine the 2,4,16 should be faster if the task can be performed concurrently. */ public abstract class ConcurrentPerformanceRegressionTest extends PerformanceRegressionTestCase { public static int DEFAULT_THREADS = 32; protected int maxThreads; protected Exception caughtException; protected List workerThreads; public ConcurrentPerformanceRegressionTest() { this.maxThreads = DEFAULT_THREADS; } /** * Return the maximum number of threads the test should run to. */ public int getMaxThreads() { return maxThreads; } /** * Set the maximum number of threads the test should run to. */ public void setMaxThreads(int maxThreads) { this.maxThreads = maxThreads; } /** * Increment the iteration count. * This is maintained by the test to allow concurrent tests to manage the count. */ public synchronized void incrementIterations() { this.iterations++; } /** * Reset the iteration count. * This is maintained by the test to allow concurrent tests to manage the count. */ public synchronized void resetIterations() { this.iterations = 0; } /** * Start 1 thread test. */ public void startTest() { startTest(getMaxThreads()); } /** * Start each worker thread. */ public void startTest(int numberOfThreads) { caughtException = null; // Spawn worker threads. for (int index = 0; index < (numberOfThreads - 1); index++) { WorkerThread thread = (WorkerThread)getWorkerThreads().get(index); thread.resumeExecution(); } } /** * Allows any test specific setup before starting the test run. */ public void endTest() { // Suspend the worker threads, // suspend all no matter what number of threads was to even out performance. for (int index = 0; index < getMaxThreads(); index++) { WorkerThread thread = (WorkerThread)getWorkerThreads().get(index); thread.suspendExecution(); } // Let workers finish. Thread.yield(); // Wait for workers to finish, // wait all no matter what number of threads was to even out performance. for (int index = 0; index < getMaxThreads(); index++) { WorkerThread thread = (WorkerThread)getWorkerThreads().get(index); thread.joinExecution(); } } public List getWorkerThreads() { return workerThreads; } /** * Start the worker threads. */ public void setup() { this.workerThreads = new ArrayList(getMaxThreads()); for (int index = 0; index < getMaxThreads(); index++) { WorkerThread thread = new WorkerThread(); this.workerThreads.add(thread); thread.start(); } } /** * Count REPEATS runs of the run method with 1 thread. */ public void test() throws Exception { test(getMaxThreads()); } /** * Count REPEATS runs of the run method with n threads. */ public void test(int numberOfThreads) throws Exception { if (caughtException != null) { throw caughtException; } runTask(); } /** * Stop the worker threads. */ public void reset() { // Stop the worker threads. for (int index = 0; index < getWorkerThreads().size(); index++) { WorkerThread thread = (WorkerThread)getWorkerThreads().get(index); thread.stopExecution(); } // Let workers finish. Thread.yield(); this.workerThreads = null; } /** * Perform the task. */ public abstract void runTask() throws Exception; /** * Defines the work thread. * A worker thread calls the run method in a loop, * until it is suspended. */ protected class WorkerThread extends Thread { protected boolean isSuspended = true; protected boolean isDead = false; /** * After the next completion of the run method, suspend execution. * The thread is still alive, but waiting to be signaled to resumed. */ public void stopExecution() { isDead = true; } /** * After the next completion of the run method, suspend execution. * The thread is still alive, but waiting to be signaled to resumed. */ public void suspendExecution() { isSuspended = true; } /** * Wait for the thread to suspend. * Synchronize will wait for wait to release synchornization. */ public synchronized void joinExecution() { if (!isSuspended) { throw new RuntimeException("Must suspend first"); } } /** * Continue running the run method. */ public synchronized void resumeExecution() { isSuspended = false; try { notify(); } catch (Exception exception) { throw new RuntimeException(exception.getMessage()); } } /** * Run the test run method in a loop until killed. */ public synchronized void run() { try { while (!isDead) { // Allows the thread to suspend itself when the current test is done. if (isSuspended) { wait(); } ConcurrentPerformanceRegressionTest.this.runTask(); ConcurrentPerformanceRegressionTest.this.incrementIterations(); } } catch (Exception exception) { ConcurrentPerformanceRegressionTest.this.caughtException = exception; } } } }