package com.ldbc.driver.runtime.scheduling; import com.ldbc.driver.Operation; import com.ldbc.driver.temporal.ManualTimeSource; import com.ldbc.driver.temporal.SystemTimeSource; import com.ldbc.driver.temporal.TemporalUtil; import com.ldbc.driver.temporal.TimeSource; import com.ldbc.driver.workloads.dummy.NothingOperation; import com.ldbc.driver.workloads.dummy.TimedNamedOperation1; import org.junit.Ignore; import org.junit.Test; import java.text.DecimalFormat; import java.util.Iterator; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import static java.lang.String.format; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; public class SpinnerTests { private static final TemporalUtil TEMPORAL_UTIL = new TemporalUtil(); long ENOUGH_MILLISECONDS_FOR_SPINNER_THREAD_TO_DO_ITS_THING = 500; ManualTimeSource timeSource = new ManualTimeSource( 0 ); DecimalFormat integerFormat = new DecimalFormat( "###,###,###,###,###" ); @Test public void shouldPassWhenNoCheckAndStartTimeArrives() throws InterruptedException { // Given timeSource.setNowFromMilli( 0 ); boolean ignoreScheduledStartTime = false; long spinnerSleepDuration = 0l; Spinner spinner = new Spinner( timeSource, spinnerSleepDuration, ignoreScheduledStartTime ); long scheduledStartTime = 10l; Operation operation = new TimedNamedOperation1( scheduledStartTime, scheduledStartTime, 0l, "name" ); SpinningThread spinningThread = new SpinningThread( spinner, operation ); // When spinningThread.start(); // Then // should not return before start time Thread.sleep( ENOUGH_MILLISECONDS_FOR_SPINNER_THREAD_TO_DO_ITS_THING ); assertThat( spinningThread.spinnerHasCompleted(), is( false ) ); assertThat( spinningThread.isFineToExecuteOperation(), is( false ) ); timeSource.setNowFromMilli( scheduledStartTime ); // should return when start time reached Thread.sleep( ENOUGH_MILLISECONDS_FOR_SPINNER_THREAD_TO_DO_ITS_THING ); assertThat( spinningThread.spinnerHasCompleted(), is( true ) ); assertThat( spinningThread.isFineToExecuteOperation(), is( true ) ); spinningThread.join( ENOUGH_MILLISECONDS_FOR_SPINNER_THREAD_TO_DO_ITS_THING ); } @Test public void shouldPassOnlyWhenCheckPassesAndStartTimeArrives() throws InterruptedException { // Given timeSource.setNowFromMilli( 0 ); boolean ignoreScheduledStartTime = false; long spinnerSleepDuration = 0l; SettableSpinnerCheck check = new SettableSpinnerCheck( SpinnerCheck.SpinnerCheckResult.STILL_CHECKING ); Spinner spinner = new Spinner( timeSource, spinnerSleepDuration, ignoreScheduledStartTime ); long scheduledStartTime = 10l; Operation operation = new TimedNamedOperation1( scheduledStartTime, scheduledStartTime, 0l, "name" ); SpinningThread spinningThread = new SpinningThread( spinner, operation, check ); // When spinningThread.start(); // Then // time = no, check = not yet Thread.sleep( ENOUGH_MILLISECONDS_FOR_SPINNER_THREAD_TO_DO_ITS_THING ); assertThat( spinningThread.spinnerHasCompleted(), is( false ) ); assertThat( spinningThread.isFineToExecuteOperation(), is( false ) ); timeSource.setNowFromMilli( scheduledStartTime - 1 ); // time = no, check = not yet Thread.sleep( ENOUGH_MILLISECONDS_FOR_SPINNER_THREAD_TO_DO_ITS_THING ); assertThat( spinningThread.spinnerHasCompleted(), is( false ) ); assertThat( spinningThread.isFineToExecuteOperation(), is( false ) ); timeSource.setNowFromMilli( scheduledStartTime ); // time = yes, check = not yet Thread.sleep( ENOUGH_MILLISECONDS_FOR_SPINNER_THREAD_TO_DO_ITS_THING ); assertThat( spinningThread.spinnerHasCompleted(), is( false ) ); assertThat( spinningThread.isFineToExecuteOperation(), is( false ) ); check.setResult( SpinnerCheck.SpinnerCheckResult.PASSED ); // time = yes, check = yes Thread.sleep( ENOUGH_MILLISECONDS_FOR_SPINNER_THREAD_TO_DO_ITS_THING ); assertThat( spinningThread.spinnerHasCompleted(), is( true ) ); assertThat( spinningThread.isFineToExecuteOperation(), is( true ) ); spinningThread.join( ENOUGH_MILLISECONDS_FOR_SPINNER_THREAD_TO_DO_ITS_THING ); } @Test public void shouldFailWhenCheckFails() throws InterruptedException { // Given timeSource.setNowFromMilli( 0 ); boolean ignoreScheduledStartTime = false; long spinnerSleepDuration = 0l; SettableSpinnerCheck check = new SettableSpinnerCheck( SpinnerCheck.SpinnerCheckResult.STILL_CHECKING ); Spinner spinner = new Spinner( timeSource, spinnerSleepDuration, ignoreScheduledStartTime ); long scheduledStartTime = 10l; Operation operation = new TimedNamedOperation1( scheduledStartTime, scheduledStartTime, 0l, "name" ); SpinningThread spinningThread = new SpinningThread( spinner, operation, check ); // When spinningThread.start(); // Then // time = no, check = not yet Thread.sleep( ENOUGH_MILLISECONDS_FOR_SPINNER_THREAD_TO_DO_ITS_THING ); assertThat( spinningThread.spinnerHasCompleted(), is( false ) ); assertThat( spinningThread.isFineToExecuteOperation(), is( false ) ); timeSource.setNowFromMilli( scheduledStartTime - 1 ); // time = no, check = not yet Thread.sleep( ENOUGH_MILLISECONDS_FOR_SPINNER_THREAD_TO_DO_ITS_THING ); assertThat( spinningThread.spinnerHasCompleted(), is( false ) ); assertThat( spinningThread.isFineToExecuteOperation(), is( false ) ); timeSource.setNowFromMilli( scheduledStartTime ); // time = yes, check = not yet Thread.sleep( ENOUGH_MILLISECONDS_FOR_SPINNER_THREAD_TO_DO_ITS_THING ); assertThat( spinningThread.spinnerHasCompleted(), is( false ) ); assertThat( spinningThread.isFineToExecuteOperation(), is( false ) ); check.setResult( SpinnerCheck.SpinnerCheckResult.FAILED ); // time = yes, check = no Thread.sleep( ENOUGH_MILLISECONDS_FOR_SPINNER_THREAD_TO_DO_ITS_THING ); assertThat( spinningThread.spinnerHasCompleted(), is( true ) ); assertThat( spinningThread.isFineToExecuteOperation(), is( false ) ); spinningThread.join( ENOUGH_MILLISECONDS_FOR_SPINNER_THREAD_TO_DO_ITS_THING ); } private static class SpinningThread extends Thread { private final Spinner spinner; private final Operation operation; private final AtomicBoolean isFineToExecuteOperation; private final AtomicBoolean spinnerHasCompleted; private final SpinnerCheck check; SpinningThread( Spinner spinner, Operation operation ) { this( spinner, operation, null ); } SpinningThread( Spinner spinner, Operation operation, SpinnerCheck check ) { this.spinner = spinner; this.operation = operation; this.check = check; this.spinnerHasCompleted = new AtomicBoolean( false ); this.isFineToExecuteOperation = new AtomicBoolean( false ); } @Override public void run() { try { if ( null == check ) { isFineToExecuteOperation.set( spinner.waitForScheduledStartTime( operation ) ); } else { isFineToExecuteOperation.set( spinner.waitForScheduledStartTime( operation, check ) ); } spinnerHasCompleted.set( true ); } catch ( Throwable e ) { e.printStackTrace(); } } boolean spinnerHasCompleted() { return spinnerHasCompleted.get(); } boolean isFineToExecuteOperation() { return isFineToExecuteOperation.get(); } } // This testing methodology seems bad, not enough iterations or something, the numbers are dependent on order // things are done @Ignore @Test public void measureCostOfSpinnerWithNoSleepAndPassingCheckAndAtScheduledStartTime() { TimeSource systemTimeSource = new SystemTimeSource(); timeSource.setNowFromMilli( 0 ); long scheduledStartTime = this.timeSource.nowAsMilli(); long operationCount = 100000000; int experimentCount = 10; boolean ignoreScheduledStartTime; ignoreScheduledStartTime = false; Spinner spinnerWithStartTimeCheck = new Spinner( timeSource, 0l, ignoreScheduledStartTime ); SpinnerCheck singleTrueCheck = new SettableSpinnerCheck( SpinnerCheck.SpinnerCheckResult.PASSED ); long singleCheckWithStartTimeCheckTestDuration = 0l; long singleCheckWithoutStartTimeCheckTestDuration = 0l; for ( int i = 0; i < experimentCount; i++ ) { FastSameOperationIterator operationsSingleCheckWithStartTimeCheck = new FastSameOperationIterator( scheduledStartTime, operationCount ); long singleCheckWithStartTimeCheckTestStartTime = systemTimeSource.nowAsMilli(); while ( operationsSingleCheckWithStartTimeCheck.hasNext() ) { spinnerWithStartTimeCheck .waitForScheduledStartTime( operationsSingleCheckWithStartTimeCheck.next(), singleTrueCheck ); } singleCheckWithStartTimeCheckTestDuration = singleCheckWithStartTimeCheckTestDuration + (systemTimeSource.nowAsMilli() - singleCheckWithStartTimeCheckTestStartTime); FastSameOperationIterator operationsSingleCheckWithoutStartTimeCheck = new FastSameOperationIterator( scheduledStartTime, operationCount ); long singleCheckWithoutStartTimeCheckTestStartTime = systemTimeSource.nowAsMilli(); while ( operationsSingleCheckWithoutStartTimeCheck.hasNext() ) { spinnerWithStartTimeCheck.waitForScheduledStartTime( operationsSingleCheckWithoutStartTimeCheck.next(), singleTrueCheck ); } singleCheckWithoutStartTimeCheckTestDuration = singleCheckWithoutStartTimeCheckTestDuration + (systemTimeSource.nowAsMilli() - singleCheckWithoutStartTimeCheckTestStartTime); } singleCheckWithStartTimeCheckTestDuration = singleCheckWithStartTimeCheckTestDuration / experimentCount; singleCheckWithoutStartTimeCheckTestDuration = singleCheckWithoutStartTimeCheckTestDuration / experimentCount; System.out.println( format( "Spinner(start time check = true) (1 true check) processed %s operations in %s: %s ops/ms, %s" + " ns/op", operationCount, TEMPORAL_UTIL.milliDurationToString( singleCheckWithStartTimeCheckTestDuration ), integerFormat.format( operationCount / singleCheckWithStartTimeCheckTestDuration ), TimeUnit.MILLISECONDS.toNanos( singleCheckWithStartTimeCheckTestDuration ) / operationCount ) ); System.out.println( format( "Spinner(start time check = false) (1 true check) processed %s operations in %s: %s ops/ms, " + "%s ns/op", operationCount, TEMPORAL_UTIL.milliDurationToString( singleCheckWithoutStartTimeCheckTestDuration ), integerFormat.format( operationCount / singleCheckWithoutStartTimeCheckTestDuration ), TimeUnit.MILLISECONDS.toNanos( singleCheckWithoutStartTimeCheckTestDuration ) / operationCount ) ); } private static class FastSameOperationIterator implements Iterator<Operation> { private final Operation operation; private long currentOperationCount = 0; private final long operationCount; FastSameOperationIterator( long scheduledStartTime, long operationCount ) { operation = new NothingOperation(); operation.setScheduledStartTimeAsMilli( scheduledStartTime ); operation.setTimeStamp( scheduledStartTime ); this.operationCount = operationCount; } @Override public boolean hasNext() { return currentOperationCount < operationCount; } @Override public Operation next() { currentOperationCount++; return operation; } @Override public void remove() { } } }