package com.ldbc.driver.runtime.metrics;
import com.ldbc.driver.Operation;
import com.ldbc.driver.control.LoggingServiceFactory;
import com.ldbc.driver.csv.simple.SimpleCsvFileWriter;
import com.ldbc.driver.runtime.ConcurrentErrorReporter;
import com.ldbc.driver.runtime.DefaultQueues;
import com.ldbc.driver.runtime.QueueEventSubmitter;
import com.ldbc.driver.runtime.scheduling.Spinner;
import com.ldbc.driver.temporal.TimeSource;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import static java.lang.String.format;
public class ThreadedQueuedMetricsService implements MetricsService
{
private static final long SHUTDOWN_WAIT_TIMEOUT_AS_MILLI = TimeUnit.MINUTES.toMillis( 1 );
private static final long FUTURE_GET_TIMEOUT_AS_MILLI = TimeUnit.MINUTES.toMillis( 30 );
// TODO this could come from config, if we had a max_runtime parameter. for now, it can default to something
public static final long DEFAULT_HIGHEST_EXPECTED_RUNTIME_DURATION_AS_NANO = TimeUnit.MINUTES.toNanos( 90 );
private final TimeSource timeSource;
private final QueueEventSubmitter<ThreadedQueuedMetricsEvent> queueEventSubmitter;
private final AtomicLong initiatedEvents;
private final ThreadedQueuedMetricsServiceThread threadedQueuedMetricsServiceThread;
private final AtomicBoolean shutdown = new AtomicBoolean( false );
private final ConcurrentLinkedQueue<ThreadedQueuedMetricsServiceWriter> metricsServiceWriters;
public static ThreadedQueuedMetricsService newInstanceUsingNonBlockingBoundedQueue(
TimeSource timeSource,
ConcurrentErrorReporter errorReporter,
TimeUnit unit,
long maxRuntimeDurationAsNano,
SimpleCsvFileWriter csvResultsLogWriter,
Map<Integer,Class<? extends Operation>> operationTypeToClassMapping,
LoggingServiceFactory loggingServiceFactory ) throws MetricsCollectionException
{
Queue<ThreadedQueuedMetricsEvent> queue = DefaultQueues.newBlockingBounded( 10_000 );
return new ThreadedQueuedMetricsService(
timeSource,
errorReporter,
unit,
maxRuntimeDurationAsNano,
queue,
csvResultsLogWriter,
operationTypeToClassMapping,
loggingServiceFactory
);
}
public static ThreadedQueuedMetricsService newInstanceUsingBlockingBoundedQueue(
TimeSource timeSource,
ConcurrentErrorReporter errorReporter,
TimeUnit unit,
long maxRuntimeDurationAsNano,
SimpleCsvFileWriter csvResultsLogWriter,
Map<Integer,Class<? extends Operation>> operationTypeToClassMapping,
LoggingServiceFactory loggingServiceFactory ) throws MetricsCollectionException
{
Queue<ThreadedQueuedMetricsEvent> queue = DefaultQueues.newBlockingBounded( 10000 );
return new ThreadedQueuedMetricsService(
timeSource,
errorReporter,
unit,
maxRuntimeDurationAsNano,
queue,
csvResultsLogWriter,
operationTypeToClassMapping,
loggingServiceFactory
);
}
private ThreadedQueuedMetricsService(
TimeSource timeSource,
ConcurrentErrorReporter errorReporter,
TimeUnit unit,
long maxRuntimeDurationAsNano,
Queue<ThreadedQueuedMetricsEvent> queue,
SimpleCsvFileWriter csvResultsLogWriter,
Map<Integer,Class<? extends Operation>> operationTypeToClassMapping,
LoggingServiceFactory loggingServiceFactory ) throws MetricsCollectionException
{
this.timeSource = timeSource;
queueEventSubmitter = QueueEventSubmitter.queueEventSubmitterFor( queue );
initiatedEvents = new AtomicLong( 0 );
metricsServiceWriters = new ConcurrentLinkedQueue<>();
threadedQueuedMetricsServiceThread = new ThreadedQueuedMetricsServiceThread(
errorReporter,
queue,
csvResultsLogWriter,
timeSource,
unit,
maxRuntimeDurationAsNano,
operationTypeToClassMapping,
loggingServiceFactory
);
threadedQueuedMetricsServiceThread.start();
}
@Override
synchronized public void shutdown() throws MetricsCollectionException
{
if ( shutdown.get() )
{
throw new MetricsCollectionException( "Metrics service has already been shutdown" );
}
try
{
ThreadedQueuedMetricsEvent event = new ThreadedQueuedMetricsEvent.Shutdown( initiatedEvents.get() );
queueEventSubmitter.submitEventToQueue( event );
threadedQueuedMetricsServiceThread.join( SHUTDOWN_WAIT_TIMEOUT_AS_MILLI );
}
catch ( InterruptedException e )
{
String errMsg = format( "Thread was interrupted while waiting for %s to complete",
threadedQueuedMetricsServiceThread.getClass().getSimpleName() );
throw new MetricsCollectionException( errMsg, e );
}
AlreadyShutdownPolicy alreadyShutdownPolicy = new AlreadyShutdownPolicy();
for ( ThreadedQueuedMetricsServiceWriter metricsServiceWriter : metricsServiceWriters )
{
metricsServiceWriter.setAlreadyShutdownPolicy( alreadyShutdownPolicy );
}
shutdown.set( true );
}
@Override
public MetricsServiceWriter getWriter() throws MetricsCollectionException
{
if ( shutdown.get() )
{
throw new MetricsCollectionException( "Metrics service has already been shutdown" );
}
ThreadedQueuedMetricsServiceWriter metricsServiceWriter =
new ThreadedQueuedMetricsServiceWriter( initiatedEvents, queueEventSubmitter, timeSource );
metricsServiceWriters.add( metricsServiceWriter );
return metricsServiceWriter;
}
private static class ThreadedQueuedMetricsServiceWriter implements MetricsServiceWriter
{
private final AtomicLong initiatedEvents;
private final QueueEventSubmitter<ThreadedQueuedMetricsEvent> queueEventSubmitter;
private final TimeSource timeSource;
private AlreadyShutdownPolicy alreadyShutdownPolicy = null;
private ThreadedQueuedMetricsServiceWriter( AtomicLong initiatedEvents,
QueueEventSubmitter<ThreadedQueuedMetricsEvent> queueEventSubmitter,
TimeSource timeSource )
{
this.initiatedEvents = initiatedEvents;
this.queueEventSubmitter = queueEventSubmitter;
this.timeSource = timeSource;
}
private void setAlreadyShutdownPolicy( AlreadyShutdownPolicy alreadyShutdownPolicy )
{
this.alreadyShutdownPolicy = alreadyShutdownPolicy;
}
@Override
public void submitOperationResult( int operationType, long scheduledStartTimeAsMilli,
long actualStartTimeAsMilli, long runDurationAsNano, int resultCode, long originalStartTime )
throws MetricsCollectionException
{
if ( null != alreadyShutdownPolicy )
{
alreadyShutdownPolicy.apply();
}
try
{
initiatedEvents.incrementAndGet();
ThreadedQueuedMetricsEvent event = new ThreadedQueuedMetricsEvent.SubmitOperationResult(
operationType,
scheduledStartTimeAsMilli,
actualStartTimeAsMilli,
runDurationAsNano,
resultCode,
originalStartTime
);
queueEventSubmitter.submitEventToQueue( event );
}
catch ( InterruptedException e )
{
String errMsg = format(
"Error submitting result\n"
+ "Operation Type: %s\n"
+ "Scheduled Start Time Ms: %s\n"
+ "Actual Start Time Ms: %s\n"
+ "Duration Ns: %s\n"
+ "Result Code: %s\n"
+ "Original start time: %s\n"
,
operationType,
scheduledStartTimeAsMilli,
actualStartTimeAsMilli,
runDurationAsNano,
resultCode,
originalStartTime
);
throw new MetricsCollectionException( errMsg, e );
}
}
@Override
public WorkloadStatusSnapshot status() throws MetricsCollectionException
{
if ( null != alreadyShutdownPolicy )
{
alreadyShutdownPolicy.apply();
}
try
{
MetricsStatusFuture statusFuture = new MetricsStatusFuture( timeSource );
ThreadedQueuedMetricsEvent event = new ThreadedQueuedMetricsEvent.Status( statusFuture );
queueEventSubmitter.submitEventToQueue( event );
return statusFuture.get( FUTURE_GET_TIMEOUT_AS_MILLI, TimeUnit.MILLISECONDS );
}
catch ( Exception e )
{
throw new MetricsCollectionException( "Error while submitting request for workload status", e );
}
}
@Override
public WorkloadResultsSnapshot results() throws MetricsCollectionException
{
if ( null != alreadyShutdownPolicy )
{
alreadyShutdownPolicy.apply();
}
try
{
MetricsWorkloadResultFuture workloadResultFuture = new MetricsWorkloadResultFuture( timeSource );
ThreadedQueuedMetricsEvent event =
new ThreadedQueuedMetricsEvent.GetWorkloadResults( workloadResultFuture );
queueEventSubmitter.submitEventToQueue( event );
return workloadResultFuture.get( FUTURE_GET_TIMEOUT_AS_MILLI, TimeUnit.MILLISECONDS );
}
catch ( Exception e )
{
throw new MetricsCollectionException( "Error while submitting request for workload results", e );
}
}
}
static class MetricsWorkloadResultFuture implements Future<WorkloadResultsSnapshot>
{
private final TimeSource timeSource;
private final AtomicBoolean done = new AtomicBoolean( false );
private final AtomicReference<WorkloadResultsSnapshot> startTime = new AtomicReference<>( null );
private MetricsWorkloadResultFuture( TimeSource timeSource )
{
this.timeSource = timeSource;
}
synchronized void set( WorkloadResultsSnapshot value ) throws MetricsCollectionException
{
if ( done.get() )
{
throw new MetricsCollectionException( "Value has already been set" );
}
startTime.set( value );
done.set( true );
}
@Override
public boolean cancel( boolean mayInterruptIfRunning )
{
throw new UnsupportedOperationException();
}
@Override
public boolean isCancelled()
{
return false;
}
@Override
public boolean isDone()
{
return done.get();
}
@Override
public WorkloadResultsSnapshot get()
{
while ( !done.get() )
{
// wait for value to be set
Spinner.powerNap( 1 );
}
return startTime.get();
}
@Override
public WorkloadResultsSnapshot get( long timeout, TimeUnit unit ) throws TimeoutException
{
long waitDurationMs = unit.toMillis( timeout );
long startTimeMs = timeSource.nowAsMilli();
while ( timeSource.nowAsMilli() - startTimeMs < waitDurationMs )
{
// wait for value to be set
if ( done.get() )
{
return startTime.get();
}
}
throw new TimeoutException( "Could not complete future in time" );
}
}
static class MetricsStatusFuture implements Future<WorkloadStatusSnapshot>
{
private final TimeSource timeSource;
private final AtomicBoolean done = new AtomicBoolean( false );
private final AtomicReference<WorkloadStatusSnapshot> status = new AtomicReference<>( null );
private MetricsStatusFuture( TimeSource timeSource )
{
this.timeSource = timeSource;
}
synchronized void set( WorkloadStatusSnapshot value ) throws MetricsCollectionException
{
if ( done.get() )
{
throw new MetricsCollectionException( "Value has already been set" );
}
status.set( value );
done.set( true );
}
@Override
public boolean cancel( boolean mayInterruptIfRunning )
{
throw new UnsupportedOperationException();
}
@Override
public boolean isCancelled()
{
return false;
}
@Override
public boolean isDone()
{
return done.get();
}
@Override
public WorkloadStatusSnapshot get()
{
while ( !done.get() )
{
// wait for value to be set
Spinner.powerNap( 1 );
}
return status.get();
}
@Override
public WorkloadStatusSnapshot get( long timeout, TimeUnit unit ) throws TimeoutException
{
long waitDurationMs = unit.toMillis( timeout );
long startTimeMs = timeSource.nowAsMilli();
while ( timeSource.nowAsMilli() - startTimeMs < waitDurationMs )
{
// wait for value to be set
if ( done.get() )
{
return status.get();
}
}
throw new TimeoutException( "Could not complete future in time" );
}
}
private static class AlreadyShutdownPolicy
{
void apply() throws MetricsCollectionException
{
throw new MetricsCollectionException( "Metrics service has already been shutdown" );
}
}
}