/* * ModeShape (http://www.modeshape.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.modeshape.jcr; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.modeshape.common.annotation.Immutable; /** * A simple load-testing client. */ public class ClientLoad { public static <Result> List<Client<Result>> runSimultaneously( final int numClients, final Callable<Result> callable ) throws InterruptedException { return run(numClients, callable, 30, 50, TimeUnit.SECONDS); } public static <Result> List<Client<Result>> run( final int numClients, final Callable<Result> callable ) throws InterruptedException { return run(numClients, callable, 0, 30, TimeUnit.SECONDS); } protected static <Result> List<Client<Result>> run( final int numClients, final Callable<Result> callable, final long initialBarrier, long finishWaitTime, final TimeUnit timeUnit ) throws InterruptedException { final CyclicBarrier barrier = new CyclicBarrier(numClients / 4); final CountDownLatch latch = new CountDownLatch(numClients); final ExecutorService clientService = Executors.newFixedThreadPool(numClients + 2); final List<Future<?>> clientFutures = new CopyOnWriteArrayList<Future<?>>(); final List<Client<Result>> results = new CopyOnWriteArrayList<Client<Result>>(); try { for (int i = 0; i != numClients; ++i) { clientFutures.add(clientService.submit(new Callable<Object>() { @Override public Object call() throws Exception { Result result = null; Throwable error = null; long durationInNanos = 0L; try { if (initialBarrier > 0L) { // Wait for everyone to be ready, but no more than the initial barrier ... barrier.await(initialBarrier, timeUnit); } long start = System.nanoTime(); result = callable.call(); durationInNanos = Math.abs(System.nanoTime() - start); latch.countDown(); } catch (Exception e) { error = e; } finally { results.add(new Client<Result>(result, true, durationInNanos, error)); } return null; } })); } } finally { clientService.shutdown(); } // Wait for the clients to all finish (twice as long as they'll all wait to start ... latch.await(finishWaitTime, timeUnit); return results; } public static <Result> void assertAllSucceeded( List<Client<Result>> clientResults, long maximumDuration, TimeUnit unit ) { for (Client<Result> client : clientResults) { assertThat(client.isSuccess(), is(true)); assertThat(client.getTime(unit) < maximumDuration, is(true)); } } public static <Result> void forEachResult( List<Client<Result>> clientResults, ClientResultProcessor<Result> operation ) throws Exception { Exception firstError = null; for (Client<Result> client : clientResults) { try { operation.process(client); } catch (Exception e) { firstError = firstError != null ? firstError : e; } } if (firstError != null) throw firstError; } public static interface ClientResultProcessor<Result> { void process( Client<Result> clientResult ) throws Exception; } @Immutable public static class Client<Result> { private final Result result; private final boolean success; private final long timeInNanos; private final Throwable t; protected Client( Result result, boolean success, long timeInNanos, Throwable t ) { this.result = result; this.success = success; this.timeInNanos = timeInNanos; this.t = t; } public boolean isSuccess() { return success; } public Result getResult() { return result; } public Throwable getError() { return t; } public long getTimeInNanos() { return timeInNanos; } public long getTime( TimeUnit unit ) { return unit.convert(timeInNanos, TimeUnit.NANOSECONDS); } } }