package com.ldbc.driver.runtime.coordination; import com.google.common.collect.Sets; import com.ldbc.driver.Operation; import com.ldbc.driver.Workload; import com.ldbc.driver.WorkloadException; import com.ldbc.driver.WorkloadStreams; import com.ldbc.driver.control.ConsoleAndFileDriverConfiguration; import com.ldbc.driver.control.DriverConfigurationException; import com.ldbc.driver.generator.GeneratorFactory; import com.ldbc.driver.generator.RandomDataGeneratorFactory; import com.ldbc.driver.runtime.ConcurrentErrorReporter; import com.ldbc.driver.temporal.SystemTimeSource; import com.ldbc.driver.temporal.TemporalUtil; import com.ldbc.driver.temporal.TimeSource; import com.ldbc.driver.testutils.TestUtils; import com.ldbc.driver.testutils.ThreadPoolLoadGenerator; import com.ldbc.driver.workloads.simple.SimpleWorkload; import org.junit.Ignore; import org.junit.Test; import java.io.IOException; import java.util.Iterator; import java.util.Set; import java.util.concurrent.*; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; public class CompletionTimeServiceAdvancedTest { private static final TemporalUtil TEMPORAL_UTIL = new TemporalUtil(); final TimeSource timeSource = new SystemTimeSource(); final CompletionTimeServiceAssistant completionTimeServiceAssistant = new CompletionTimeServiceAssistant(); @Ignore @Test public void stressTestThreadedQueuedConcurrentCompletionTimeService() throws InterruptedException, ExecutionException, WorkloadException, CompletionTimeException, DriverConfigurationException, IOException { ThreadPoolLoadGenerator threadPoolLoadGenerator = TestUtils.newThreadPoolLoadGenerator(128, 0); threadPoolLoadGenerator.start(); try { int testRepetitions = 10; ConcurrentErrorReporter errorReporter = new ConcurrentErrorReporter(); String otherPeerId = "somePeer"; Set<String> peerIds = Sets.newHashSet(otherPeerId); long totalTestDurationForThreadedCompletionTimeServiceAsMilli; for (int workerThreads = 1; workerThreads < 33; workerThreads = workerThreads * 2) { totalTestDurationForThreadedCompletionTimeServiceAsMilli = 0; for (int i = 0; i < testRepetitions; i++) { CompletionTimeService completionTimeService = completionTimeServiceAssistant.newThreadedQueuedConcurrentCompletionTimeServiceFromPeerIds(timeSource, peerIds, errorReporter); try { totalTestDurationForThreadedCompletionTimeServiceAsMilli += parallelCompletionTimeServiceTest(completionTimeService, otherPeerId, errorReporter, workerThreads); } finally { completionTimeService.shutdown(); } } System.out.printf("\t%s=%s\n", ThreadedQueuedCompletionTimeService.class.getSimpleName(), TEMPORAL_UTIL.milliDurationToString(totalTestDurationForThreadedCompletionTimeServiceAsMilli / testRepetitions)); } } finally { threadPoolLoadGenerator.shutdown(TimeUnit.SECONDS.toMillis(10)); } } @Test public void completionTimeServicesShouldBehaveDeterministically() throws InterruptedException, ExecutionException, WorkloadException, CompletionTimeException, DriverConfigurationException, IOException { ConcurrentErrorReporter errorReporter = new ConcurrentErrorReporter(); String otherPeerId = "somePeer"; Set<String> peerIds = Sets.newHashSet(otherPeerId); int testRepetitions = 5; long totalTestDurationForSynchronousCompletionTimeService; long totalTestDurationForThreadedCompletionTimeService; for (int workerThreads = 1; workerThreads < 33; workerThreads = workerThreads * 2) { totalTestDurationForSynchronousCompletionTimeService = 0; for (int i = 0; i < testRepetitions; i++) { CompletionTimeService concurrentCompletionTimeService = completionTimeServiceAssistant.newSynchronizedConcurrentCompletionTimeServiceFromPeerIds(peerIds); totalTestDurationForSynchronousCompletionTimeService += parallelCompletionTimeServiceTest(concurrentCompletionTimeService, otherPeerId, errorReporter, workerThreads); concurrentCompletionTimeService.shutdown(); } System.out.printf("Threads:%-2s\t%s=%s", workerThreads, SynchronizedCompletionTimeService.class.getSimpleName(), TEMPORAL_UTIL.milliDurationToString(totalTestDurationForSynchronousCompletionTimeService / testRepetitions)); totalTestDurationForThreadedCompletionTimeService = 0; for (int i = 0; i < testRepetitions; i++) { CompletionTimeService completionTimeService = completionTimeServiceAssistant.newThreadedQueuedConcurrentCompletionTimeServiceFromPeerIds(timeSource, peerIds, errorReporter); totalTestDurationForThreadedCompletionTimeService += parallelCompletionTimeServiceTest(completionTimeService, otherPeerId, errorReporter, workerThreads); completionTimeService.shutdown(); } System.out.printf("\t%s=%s\n", ThreadedQueuedCompletionTimeService.class.getSimpleName(), TEMPORAL_UTIL.milliDurationToString(totalTestDurationForThreadedCompletionTimeService / testRepetitions)); } } public long parallelCompletionTimeServiceTest(CompletionTimeService completionTimeService, String otherPeerId, ConcurrentErrorReporter errorReporter, int threadCount) throws WorkloadException, InterruptedException, ExecutionException, CompletionTimeException, DriverConfigurationException, IOException { // initialize executor ThreadFactory threadFactory = new ThreadFactory() { private final long factoryTimeStampId = System.currentTimeMillis(); int count = 0; @Override public Thread newThread(Runnable runnable) { Thread newThread = new Thread( runnable, CompletionTimeServiceAdvancedTest.class.getSimpleName() + ".parallelCompletionTimeServiceTest-id(" + factoryTimeStampId + ")" + "-thread(" + count++ + ")"); return newThread; } }; ExecutorService executorService = Executors.newFixedThreadPool(threadCount, threadFactory); CompletionService<Integer> completionService = new ExecutorCompletionService<>(executorService); // initialize workload int operationCountCheckPoint1 = 100; int operationCountCheckPoint2 = 900; long operationCountAdditionalOperations = 100000; long operationCount = operationCountCheckPoint1 + operationCountCheckPoint2 + operationCountAdditionalOperations; LocalCompletionTimeWriter localCompletionTimeWriter = completionTimeService.newLocalCompletionTimeWriter(); ExternalCompletionTimeWriter externalCompletionTimeWriter = completionTimeService; String databaseClassName = null; String workloadClassName = null; ConsoleAndFileDriverConfiguration configuration = ConsoleAndFileDriverConfiguration.fromDefaults(databaseClassName, workloadClassName, operationCount); // TODO consider using DummyWorkload instead Workload workload = new SimpleWorkload(); workload.init(configuration); GeneratorFactory gf = new GeneratorFactory(new RandomDataGeneratorFactory(42L)); Iterator<Operation> operations = gf.limit( WorkloadStreams.mergeSortedByStartTimeExcludingChildOperationGenerators(gf, workload.streams(gf, true)), configuration.operationCount() ); // measure duration of experiment long startTimeAsMilli = timeSource.nowAsMilli(); // track number of operations that have completed, i.e., that have their Completed Time submitted int completedOperations = 0; /* CREATE 1st CHECK POINT */ Operation gctCheckpointOperation1 = operations.next(); long gctCheckpointOperation1ScheduledStartTimeAsMilli = gctCheckpointOperation1.timeStamp(); externalCompletionTimeWriter.submitPeerCompletionTime(otherPeerId, gctCheckpointOperation1ScheduledStartTimeAsMilli); localCompletionTimeWriter.submitLocalInitiatedTime(gctCheckpointOperation1ScheduledStartTimeAsMilli); localCompletionTimeWriter.submitLocalCompletedTime(gctCheckpointOperation1ScheduledStartTimeAsMilli); completedOperations++; // This is only used for ensuring that time stamps are in fact monotonically increasing long lastScheduledStartTimeAsMilli = gctCheckpointOperation1ScheduledStartTimeAsMilli; for (int i = completedOperations; i < operationCountCheckPoint1; i++) { Operation operation = operations.next(); assertThat(operation.timeStamp() >= lastScheduledStartTimeAsMilli, is(true)); lastScheduledStartTimeAsMilli = operation.timeStamp(); localCompletionTimeWriter.submitLocalInitiatedTime(operation.timeStamp()); completionService.submit(new GctAccessingCallable(operation, localCompletionTimeWriter, errorReporter)); } // Wait for tasks to finish submitting Completed Times, up to 1st check point while (completedOperations < operationCountCheckPoint1) { completionService.take(); completedOperations++; } assertThat(errorReporter.toString(), errorReporter.errorEncountered(), is(false)); /* TEST 1st CHECK POINT */ Future<Long> future1WaitingForGtcCheckpointOperation1 = completionTimeService.globalCompletionTimeAsMilliFuture(); assertThat(future1WaitingForGtcCheckpointOperation1.get(), equalTo(gctCheckpointOperation1ScheduledStartTimeAsMilli)); /* CREATE 2nd CHECK POINT */ Operation gctCheckpointOperation2 = operations.next(); long gctCheckpointOperation2ScheduledStartTimeAsMilli = gctCheckpointOperation2.timeStamp(); externalCompletionTimeWriter.submitPeerCompletionTime(otherPeerId, gctCheckpointOperation2ScheduledStartTimeAsMilli); localCompletionTimeWriter.submitLocalInitiatedTime(gctCheckpointOperation2ScheduledStartTimeAsMilli); localCompletionTimeWriter.submitLocalCompletedTime(gctCheckpointOperation2ScheduledStartTimeAsMilli); completedOperations++; for (int i = completedOperations; i < operationCountCheckPoint2; i++) { Operation operation = operations.next(); assertThat(operation.timeStamp() >= lastScheduledStartTimeAsMilli, is(true)); lastScheduledStartTimeAsMilli = operation.timeStamp(); localCompletionTimeWriter.submitLocalInitiatedTime(operation.timeStamp()); completionService.submit(new GctAccessingCallable(operation, localCompletionTimeWriter, errorReporter)); } // Wait for tasks to finish submitting Completed Times, up to 2nd check point while (completedOperations < operationCountCheckPoint2) { completionService.take(); completedOperations++; } assertThat(errorReporter.toString(), errorReporter.errorEncountered(), is(false)); /* TEST 2nd CHECK POINT */ Future<Long> future2WaitingForGtcCheckpointOperation2 = completionTimeService.globalCompletionTimeAsMilliFuture(); assertThat(future2WaitingForGtcCheckpointOperation2.get(), equalTo(gctCheckpointOperation2ScheduledStartTimeAsMilli)); while (operations.hasNext()) { Operation operation = operations.next(); assertThat(operation.timeStamp() >= lastScheduledStartTimeAsMilli, is(true)); lastScheduledStartTimeAsMilli = operation.timeStamp(); localCompletionTimeWriter.submitLocalInitiatedTime(operation.timeStamp()); completionService.submit(new GctAccessingCallable(operation, localCompletionTimeWriter, errorReporter)); } // Wait for tasks to finish submitting Completed Times, up to final check point (end of workload) while (completedOperations < operationCount) { completionService.take(); completedOperations++; } assertThat(errorReporter.toString(), errorReporter.errorEncountered(), is(false)); // Submit one more local initiated time to allow local completion time to advance to the last submitted completed time long slightlyAfterLastScheduledStartTimeAsMilli = lastScheduledStartTimeAsMilli + 1; localCompletionTimeWriter.submitLocalInitiatedTime(slightlyAfterLastScheduledStartTimeAsMilli); externalCompletionTimeWriter.submitPeerCompletionTime(otherPeerId, lastScheduledStartTimeAsMilli); /* TEST 3rd CHECK POINT */ Future<Long> future3WaitingForLastOperation = completionTimeService.globalCompletionTimeAsMilliFuture(); assertThat(future3WaitingForLastOperation.get(), equalTo(lastScheduledStartTimeAsMilli)); long testDurationAsMilli = timeSource.nowAsMilli() - startTimeAsMilli; executorService.shutdown(); boolean allTasksCompletedInTime = executorService.awaitTermination(10, TimeUnit.SECONDS); assertThat(allTasksCompletedInTime, is(true)); assertThat(errorReporter.toString(), errorReporter.errorEncountered(), is(false)); workload.close(); return testDurationAsMilli; } class GctAccessingCallable implements Callable<Integer> { private final Operation operation; private final LocalCompletionTimeWriter localCompletionTimeWriter; private final ConcurrentErrorReporter errorReporter; public GctAccessingCallable(Operation operation, LocalCompletionTimeWriter localCompletionTimeWriter, ConcurrentErrorReporter errorReporter) { this.operation = operation; this.localCompletionTimeWriter = localCompletionTimeWriter; this.errorReporter = errorReporter; } public Integer call() throws Exception { try { // operation completes localCompletionTimeWriter.submitLocalCompletedTime(operation.timeStamp()); return 1; } catch (Exception e) { errorReporter.reportError(this, "Error in call()"); return -1; } } } }