package org.infinispan.counter; import static org.infinispan.counter.EmbeddedCounterManagerFactory.asCounterManager; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.counter.api.CounterConfiguration; import org.infinispan.counter.api.CounterManager; import org.infinispan.counter.api.CounterType; import org.infinispan.counter.util.StrongTestCounter; import org.infinispan.counter.util.TestCounter; import org.infinispan.counter.util.WeakTestCounter; import org.infinispan.manager.EmbeddedCacheManager; import org.infinispan.test.MultipleCacheManagersTest; import org.testng.AssertJUnit; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; /** * Stress test for the multiple counter types. * * @author Pedro Ruivo * @since 9.0 */ @Test(groups = "stress", testName = "counter.CounterStressTest") public class CounterStressTest extends MultipleCacheManagersTest { private static final long TEST_DURATION_MILLIS = TimeUnit.MINUTES.toMillis(2); private static final double NANOS_TO_MILLIS = 0.000001; private static final double MILLIS_TO_SEC = 0.001; private static final int CLUSTER_SIZE = 8; private Reports report; //input(nanoseconds) => output(milliseconds) private static double[] awaitResults(List<Future<Long>> results) throws ExecutionException, InterruptedException { double[] millis = new double[results.size()]; int idx = 0; for (Future<Long> result : results) { millis[idx++] = result.get() * NANOS_TO_MILLIS; } return millis; } private static void printRow(int threads, Result result) { double sum = 0; double min = Double.MAX_VALUE; double max = Double.MIN_VALUE; for (double d : result.millis) { sum += d; min = Math.min(d, min); max = Math.max(d, max); } System.out.printf("%d | %s |", threads, result.factoryName); double avg = sum / result.millis.length; System.out.printf("avg=%,.2f, min=%,.2f, max=%,.2f |", avg, min, max); double avgSec = avg * MILLIS_TO_SEC; System.out.printf("%,.2f%n", result.operations / avgSec); } @BeforeClass(alwaysRun = true) public void init() { report = new Reports(); } @AfterClass(alwaysRun = true) public void report() { report.printReport(); } @Test(dataProvider = "threads") public void stress(final int threadsPerNode, final TestCounterFactory factory) throws ExecutionException, InterruptedException { final int threads = threadsPerNode * CLUSTER_SIZE; final CyclicBarrier barrier = new CyclicBarrier(threads); final List<Future<Long>> results = new ArrayList<>(threads); final String counterName = String.format("%s_%d", factory.factoryName(), threadsPerNode); final AtomicBoolean stop = new AtomicBoolean(false); System.out.println("== STRESS TEST STARTED =="); System.out.printf("Factory='%s'%nThreads/Node=%d%nCluster=%d%nCounter name='%s'%n", factory.factoryName(), threadsPerNode, CLUSTER_SIZE, counterName); for (int c = 0; c < CLUSTER_SIZE; ++c) { final TestCounter counter = factory.getCounter(manager(c), counterName); for (int t = 0; t < threadsPerNode; ++t) { results.add(fork(new StressCallable(counter, barrier, stop))); } } System.out.printf("== THREADS CREATED (%d/%d) ==%n", results.size(), threads); Thread.sleep(TEST_DURATION_MILLIS); stop.set(true); double[] millis = awaitResults(results); System.out.println("== STRESS TEST FINISHED =="); long[] countersValues = new long[CLUSTER_SIZE]; for (int c = 0; c < CLUSTER_SIZE; ++c) { countersValues[c] = factory.getCounter(manager(c), counterName).getValue(); } this.report.add(threads, factory, millis, countersValues[0]); for (int c = 1; c < CLUSTER_SIZE; ++c) { AssertJUnit.assertEquals("StrongCounter mismatch for manager " + c, countersValues[0], countersValues[c]); } } @Override protected void createCacheManagers() throws Throwable { ConfigurationBuilder builder = getDefaultClusteredCacheConfig(CacheMode.DIST_SYNC); builder.clustering().hash().numOwners(2); createClusteredCaches(CLUSTER_SIZE, builder); } @DataProvider(name = "threads") private Object[][] threadPerNode() { return new Object[][]{ {2, Factories.ATOMIC}, {2, Factories.THRESHOLD}, {2, Factories.WEAK}, {4, Factories.ATOMIC}, {4, Factories.THRESHOLD}, {4, Factories.WEAK}, {8, Factories.ATOMIC}, {8, Factories.THRESHOLD}, {8, Factories.WEAK}, {16, Factories.ATOMIC}, {16, Factories.THRESHOLD}, {16, Factories.WEAK}}; } private enum Factories implements TestCounterFactory { ATOMIC { @Override public TestCounter getCounter(EmbeddedCacheManager manager, String counterName) { CounterManager counterManager = asCounterManager(manager); counterManager .defineCounter(counterName, CounterConfiguration.builder(CounterType.UNBOUNDED_STRONG).build()); return new StrongTestCounter(counterManager.getStrongCounter(counterName)); } }, THRESHOLD { @Override public TestCounter getCounter(EmbeddedCacheManager manager, String counterName) { CounterManager counterManager = asCounterManager(manager); counterManager.defineCounter(counterName, CounterConfiguration.builder(CounterType.BOUNDED_STRONG).upperBound(Long.MAX_VALUE) .lowerBound(Long.MIN_VALUE) .build()); return new StrongTestCounter(counterManager.getStrongCounter(counterName)); } }, WEAK { @Override public TestCounter getCounter(EmbeddedCacheManager manager, String counterName) { CounterManager counterManager = asCounterManager(manager); counterManager.defineCounter(counterName, CounterConfiguration.builder(CounterType.WEAK).build()); return new WeakTestCounter(asCounterManager(manager).getWeakCounter(counterName)); } }; @Override public String factoryName() { return name(); } } private interface TestCounterFactory { TestCounter getCounter(EmbeddedCacheManager manager, String counterName); String factoryName(); } private static class Reports { private final Map<Integer, List<Result>> reports; private Reports() { reports = new HashMap<>(); } void add(int threads, TestCounterFactory factory, double[] rawResultsMillis, long operations) { List<Result> resultList = reports.computeIfAbsent(threads, t -> new ArrayList<>(3)); resultList.add(new Result(factory.factoryName(), rawResultsMillis, operations)); } void printReport() { System.out.println("== RESULTS =="); System.out.println("Threads | Factory | Total Time (ms) | Throughput (op/sec)"); for (Map.Entry<Integer, List<Result>> entry : reports.entrySet()) { int threads = entry.getKey(); List<Result> results = entry.getValue(); for (Result result : results) { printRow(threads, result); } } System.out.println("== RESULTS =="); } } private static class Result { private final String factoryName; private final double[] millis; private final long operations; private Result(String factoryName, double[] millis, long operations) { this.factoryName = factoryName; this.millis = millis; this.operations = operations; } } private class StressCallable implements Callable<Long> { private final TestCounter counter; private final CyclicBarrier barrier; private final AtomicBoolean stop; private StressCallable(TestCounter counter, CyclicBarrier barrier, AtomicBoolean stop) { this.counter = counter; this.barrier = barrier; this.stop = stop; } @Override public Long call() throws Exception { try { barrier.await(); final long start = System.nanoTime(); while (!stop.get()) { try { counter.increment(); } catch (Exception e) { log.error("Error incrementing counter.", e); } } final long end = System.nanoTime(); barrier.await(); return end - start; } catch (Exception e) { log.error("Unexpected Exception", e); throw e; } } } }