package com.ldbc.driver.runtime.metrics;
import com.google.common.collect.Ordering;
import com.ldbc.driver.Operation;
import com.ldbc.driver.control.LoggingServiceFactory;
import com.ldbc.driver.temporal.TimeSource;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static java.lang.String.format;
public class MetricsManager
{
private final TimeSource timeSource;
private final TimeUnit unit;
private long startTimeAsMilli;
private long latestFinishTimeAsMilli;
private final OperationTypeMetricsManager[] operationTypeMetricsManagers;
public static void export(
WorkloadResultsSnapshot workloadResults,
WorkloadMetricsFormatter metricsFormatter,
OutputStream outputStream,
Charset charSet )
throws MetricsCollectionException
{
try
{
String formattedMetricsGroups = metricsFormatter.format( workloadResults );
outputStream.write( formattedMetricsGroups.getBytes( charSet ) );
}
catch ( Exception e )
{
throw new MetricsCollectionException( "Error encountered writing metrics to output stream", e );
}
}
public static OperationTypeMetricsManager[] toOperationTypeMetricsManagerArray(
Map<Integer,Class<? extends Operation>> operationTypeToClassMapping,
TimeUnit unit,
long highestExpectedRuntimeDurationAsNano,
LoggingServiceFactory loggingServiceFactory ) throws MetricsCollectionException
{
if ( operationTypeToClassMapping.isEmpty() )
{
return new OperationTypeMetricsManager[]{};
}
else
{
final int minOperationType = Ordering.<Integer>natural().min( operationTypeToClassMapping.keySet() );
if ( minOperationType < 0 )
{
throw new MetricsCollectionException(
format( "Encountered Operation with type code lower than 0: %s", minOperationType ) );
}
final int maxOperationType = Ordering.<Integer>natural().max( operationTypeToClassMapping.keySet() );
OperationTypeMetricsManager[] operationTypeMetricsManagers =
new OperationTypeMetricsManager[maxOperationType + 1];
for ( int i = 0; i < operationTypeMetricsManagers.length; i++ )
{
Class<? extends Operation> operationClass = operationTypeToClassMapping.get( i );
if ( null == operationClass )
{
operationTypeMetricsManagers[i] = null;
}
else
{
operationTypeMetricsManagers[i] = new OperationTypeMetricsManager(
operationClass.getSimpleName(),
unit,
highestExpectedRuntimeDurationAsNano,
loggingServiceFactory
);
}
}
return operationTypeMetricsManagers;
}
}
public static String[] toOperationNameArray( Map<Integer,Class<? extends Operation>> operationTypeToClassMapping )
throws MetricsCollectionException
{
if ( operationTypeToClassMapping.isEmpty() )
{
return new String[]{};
}
else
{
final int minOperationType = Ordering.<Integer>natural().min( operationTypeToClassMapping.keySet() );
if ( minOperationType < 0 )
{
throw new MetricsCollectionException(
format( "Encountered Operation with type code lower than 0: %s", minOperationType ) );
}
final int maxOperationType = Ordering.<Integer>natural().max( operationTypeToClassMapping.keySet() );
String[] operationNames = new String[maxOperationType + 1];
for ( int i = 0; i < operationNames.length; i++ )
{
Class<? extends Operation> operationClass = operationTypeToClassMapping.get( i );
if ( null == operationClass )
{
operationNames[i] = null;
}
else
{
operationNames[i] = operationClass.getSimpleName();
}
}
return operationNames;
}
}
MetricsManager( TimeSource timeSource,
TimeUnit unit,
long highestExpectedRuntimeDurationAsNano,
Map<Integer,Class<? extends Operation>> operationTypeToClassMapping,
LoggingServiceFactory loggingServiceFactory ) throws MetricsCollectionException
{
operationTypeMetricsManagers = toOperationTypeMetricsManagerArray(
operationTypeToClassMapping,
unit,
highestExpectedRuntimeDurationAsNano,
loggingServiceFactory
);
this.startTimeAsMilli = Long.MAX_VALUE;
this.latestFinishTimeAsMilli = Long.MIN_VALUE;
this.timeSource = timeSource;
this.unit = unit;
}
final static long ONE_MS_AS_NS = TimeUnit.MILLISECONDS.toNanos( 1 );
void measure( long actualStartTimeAsMilli, long runDurationAsNano, int operationType )
throws MetricsCollectionException
{
if ( actualStartTimeAsMilli < startTimeAsMilli )
{
startTimeAsMilli = actualStartTimeAsMilli;
}
long operationFinishTimeAsMilli = actualStartTimeAsMilli + (runDurationAsNano / ONE_MS_AS_NS);
if ( operationFinishTimeAsMilli > latestFinishTimeAsMilli )
{
latestFinishTimeAsMilli = operationFinishTimeAsMilli;
}
operationTypeMetricsManagers[operationType].measure( runDurationAsNano );
}
private long totalOperationCount()
{
long count = 0;
for ( OperationTypeMetricsManager operationTypeMetricsManager : operationTypeMetricsManagers )
{
if ( null != operationTypeMetricsManager && operationTypeMetricsManager.count() > 0 )
{
count += operationTypeMetricsManager.count();
}
}
return count;
}
WorkloadResultsSnapshot snapshot()
{
Map<String,OperationMetricsSnapshot> operationMetricsMap = new HashMap<>();
for ( OperationTypeMetricsManager operationTypeMetricsManager : operationTypeMetricsManagers )
{
if ( null != operationTypeMetricsManager && operationTypeMetricsManager.count() > 0 )
{
OperationMetricsSnapshot snapshot = operationTypeMetricsManager.snapshot();
operationMetricsMap.put( snapshot.name(), snapshot );
}
}
return new WorkloadResultsSnapshot(
operationMetricsMap,
(startTimeAsMilli == Long.MAX_VALUE) ? -1 : startTimeAsMilli,
(latestFinishTimeAsMilli == Long.MIN_VALUE) ? -1 : latestFinishTimeAsMilli,
totalOperationCount(),
unit );
}
WorkloadStatusSnapshot status()
{
long nowAsMilli = timeSource.nowAsMilli();
if ( nowAsMilli < startTimeAsMilli )
{
long runDurationAsMilli = 0;
long operationCount = 0;
long durationSinceLastMeasurementAsMilli = 0;
double operationsPerSecond = 0;
return new WorkloadStatusSnapshot(
runDurationAsMilli,
operationCount,
durationSinceLastMeasurementAsMilli,
operationsPerSecond );
}
else
{
long runDurationAsMilli = nowAsMilli - startTimeAsMilli;
long operationCount = totalOperationCount();
long durationSinceLastMeasurementAsMilli =
(-1 == latestFinishTimeAsMilli) ? -1 : nowAsMilli - latestFinishTimeAsMilli;
double operationsPerSecond =
((double) operationCount / TimeUnit.MILLISECONDS.toNanos( runDurationAsMilli )) *
TimeUnit.SECONDS.toNanos( 1 );
return new WorkloadStatusSnapshot(
runDurationAsMilli,
operationCount,
durationSinceLastMeasurementAsMilli,
operationsPerSecond );
}
}
}