package com.ldbc.driver.statistics; import com.google.common.base.Function; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.ldbc.driver.Operation; import com.ldbc.driver.runtime.metrics.ContinuousMetricManager; import com.ldbc.driver.runtime.metrics.ContinuousMetricSnapshot; import com.ldbc.driver.temporal.TemporalUtil; import com.ldbc.driver.util.Bucket; import com.ldbc.driver.util.Histogram; import com.ldbc.driver.util.MapUtils; import java.text.DecimalFormat; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import static java.lang.String.format; public class WorkloadStatistics { private final Map<Class,Long> firstStartTimesAsMilliByOperationType; private final Map<Class,Long> lastStartTimesAsMilliByOperationType; private final Histogram<Class,Long> operationMixHistogram; private final ContinuousMetricManager operationInterleaves; private final Map<Class,ContinuousMetricManager> operationInterleavesByOperationType; private final Set<Class> dependencyOperationTypes; private final Set<Class> dependentOperationTypes; private final Map<Class,Long> lowestDependencyDurationAsMilliByOperationType; public WorkloadStatistics( Map<Class,Long> firstStartTimesAsMilliByOperationType, Map<Class,Long> lastStartTimesAsMilliByOperationType, Histogram<Class,Long> operationMixHistogram, ContinuousMetricManager operationInterleaves, Map<Class,ContinuousMetricManager> operationInterleavesByOperationType, Set<Class> dependencyOperationTypes, Set<Class> dependentOperationTypes, Map<Class,Long> lowestDependencyDurationAsMilliByOperationType ) { this.firstStartTimesAsMilliByOperationType = firstStartTimesAsMilliByOperationType; this.lastStartTimesAsMilliByOperationType = lastStartTimesAsMilliByOperationType; this.operationMixHistogram = operationMixHistogram; this.operationInterleaves = operationInterleaves; this.operationInterleavesByOperationType = operationInterleavesByOperationType; this.dependencyOperationTypes = dependencyOperationTypes; this.dependentOperationTypes = dependentOperationTypes; this.lowestDependencyDurationAsMilliByOperationType = lowestDependencyDurationAsMilliByOperationType; } public long totalCount() { long count = 0; for ( Map.Entry<Class,ContinuousMetricManager> operationInterleaveForOperationType : operationInterleavesByOperationType .entrySet() ) { count += operationInterleaveForOperationType.getValue().snapshot().count(); // because interleaves are the durations BETWEEN operation occurrences they will be off by one // if there are no occurrences there will be no start time for the operation type // if there ARE occurrences, we should increment count by 1 if ( firstStartTimesAsMilliByOperationType.containsKey( operationInterleaveForOperationType.getKey() ) ) { count += 1; } } return count; } public long totalDurationAsMilli() { long firstStartTimeAsMilli = firstStartTimeAsMilli(); if ( -1 == firstStartTimeAsMilli ) { return -1; } long lastStartTimeAsMilli = lastStartTimeAsMilli(); if ( -1 == lastStartTimeAsMilli ) { return -1; } return lastStartTimeAsMilli - firstStartTimeAsMilli; } public int operationTypeCount() { return Math.max( firstStartTimesAsMilliByOperationType().size(), operationMix().getBucketCount() ); } public long firstStartTimeAsMilli() { long firstStartTime = -1; for ( Map.Entry<Class,Long> firstStartTimeForOperationType : firstStartTimesAsMilliByOperationType.entrySet() ) { if ( -1 == firstStartTime || firstStartTimeForOperationType.getValue() < firstStartTime ) { firstStartTime = firstStartTimeForOperationType.getValue(); } } return firstStartTime; } public long lastStartTimeAsMilli() { long lastStartTimeAsMilli = -1; for ( Map.Entry<Class,Long> lastStartTimeForOperationType : lastStartTimesAsMilliByOperationType.entrySet() ) { if ( -1 == lastStartTimeAsMilli || lastStartTimeForOperationType.getValue() > lastStartTimeAsMilli ) { lastStartTimeAsMilli = lastStartTimeForOperationType.getValue(); } } return lastStartTimeAsMilli; } public Map<Class,Long> firstStartTimesAsMilliByOperationType() { return firstStartTimesAsMilliByOperationType; } public Map<Class,Long> lastStartTimesAsMilliByOperationType() { return lastStartTimesAsMilliByOperationType; } public Histogram<Class,Long> operationMix() { return operationMixHistogram; } public ContinuousMetricManager operationInterleaves() { return operationInterleaves; } public Map<Class,ContinuousMetricManager> operationInterleavesByOperationType() { return operationInterleavesByOperationType; } public Set<Class> dependencyOperationTypes() { return dependencyOperationTypes; } public Set<Class> dependentOperationTypes() { return dependentOperationTypes; } public Map<Class,Long> lowestDependencyDurationAsMilliByOperationType() { return lowestDependencyDurationAsMilliByOperationType; } @Override public String toString() { TemporalUtil temporalUtil = new TemporalUtil(); DecimalFormat integralFormat = new DecimalFormat( "###,###,###,###,###" ); DecimalFormat floatFormat = new DecimalFormat( "###,###,###,###,#00.00" ); int padRightDistance = 40; StringBuilder sb = new StringBuilder(); sb.append( "********************************************************\n" ); sb.append( "************ Calculated Workload Statistics ************\n" ); sb.append( "********************************************************\n" ); sb.append( " ------------------------------------------------------\n" ); sb.append( " GENERAL\n" ); sb.append( " ------------------------------------------------------\n" ); double opsPerS = totalCount() / (double) totalDurationAsMilli() * 1000; sb.append( format( "%1$-" + padRightDistance + "s", " Operation Count:" ) ) .append( integralFormat.format( totalCount() ) ).append( "\n" ); sb.append( format( "%1$-" + padRightDistance + "s", " Total Duration:" ) ) .append( temporalUtil.milliDurationToString( totalDurationAsMilli() ) ).append( "\n" ); sb.append( format( "%1$-" + padRightDistance + "s", " Throughput:" ) ) .append( floatFormat.format( opsPerS ) ).append( " (op/s)\n" ); sb.append( format( "%1$-" + padRightDistance + "s", " Unique Operation Types:" ) ) .append( integralFormat.format( operationTypeCount() ) ).append( "\n" ); sb.append( format( "%1$-" + padRightDistance + "s", " Time Span:" ) ) .append( temporalUtil.milliTimeToDateTimeString( firstStartTimeAsMilli() ) ).append( " ===> " ) .append( temporalUtil.milliTimeToDateTimeString( lastStartTimeAsMilli() ) ).append( "\n" ); sb.append( " Operation Mix:\n" ); List<Map.Entry<Bucket<Class>,Long>> absoluteOperationMixEntryList = Lists.newArrayList( MapUtils.sortedEntries( operationMix().getAllBuckets() ) ); List<Map.Entry<Bucket<Class>,Double>> percentageOperationMixEntryList = Lists.newArrayList( MapUtils.sortedEntries( operationMix().toPercentageValues().getAllBuckets() ) ); for ( int i = 0; i < absoluteOperationMixEntryList.size(); i++ ) { Map.Entry<Bucket<Class>,Long> absoluteOperationMixEntry = absoluteOperationMixEntryList.get( i ); Map.Entry<Bucket<Class>,Double> percentageOperationMixEntry = percentageOperationMixEntryList.get( i ); Bucket.DiscreteBucket<Class> bucket = (Bucket.DiscreteBucket<Class>) absoluteOperationMixEntry.getKey(); Class<Operation> operationType = bucket.getId(); long absoluteOperationCount = absoluteOperationMixEntry.getValue(); double percentageOperationCount = percentageOperationMixEntry.getValue(); sb.append( format( "%1$-" + padRightDistance + "s", " " + operationType.getSimpleName() + ":" ) ); sb.append( format( "%1$-" + 20 + "s", integralFormat.format( absoluteOperationCount ) ) ); sb.append( floatFormat.format( percentageOperationCount * 100 ) ).append( " %" ).append( "\n" ); } sb.append( " Operation By Dependency Mode:\n" ); sb.append( format( "%1$-" + padRightDistance + "s", " All Operations:" ) ) .append( toSortedClassNames( firstStartTimesAsMilliByOperationType.keySet() ) ).append( "\n" ); sb.append( format( "%1$-" + padRightDistance + "s", " Dependency Operations:" ) ) .append( toSortedClassNames( dependencyOperationTypes ) ).append( "\n" ); sb.append( format( "%1$-" + padRightDistance + "s", " Dependent Operations:" ) ) .append( toSortedClassNames( dependentOperationTypes ) ).append( "\n" ); sb.append( " ------------------------------------------------------\n" ); sb.append( " INTERLEAVES\n" ); sb.append( " ------------------------------------------------------\n" ); ContinuousMetricSnapshot interleavesSnapshot = operationInterleaves().snapshot(); sb.append( format( "%1$-" + padRightDistance + "s", " All Operations:" ) ). append( "min = " ).append( temporalUtil.milliDurationToString( interleavesSnapshot.min() ) ) .append( " / " ). append( "mean = " ) .append( temporalUtil.milliDurationToString( Math.round( interleavesSnapshot.mean() ) ) ) .append( " / " ). append( "max = " ).append( temporalUtil.milliDurationToString( interleavesSnapshot.max() ) ) .append( "\n" ); sb.append( " ------------------------------------------------------\n" ); sb.append( " BY OPERATION TYPE\n" ); sb.append( " ------------------------------------------------------\n" ); for ( Map.Entry<Class,Long> lowestDependencyDurationAsMilliForOperationType : MapUtils .sortedEntrySet( lowestDependencyDurationAsMilliByOperationType() ) ) { Class<Operation> operationType = lowestDependencyDurationAsMilliForOperationType.getKey(); long firstStartAsMilliTypeForOperationType = firstStartTimesAsMilliByOperationType().get( operationType ); long lastStartAsMilliTypeForOperationType = lastStartTimesAsMilliByOperationType().get( operationType ); sb.append( format( "%1$-" + padRightDistance + "s", " " + operationType.getSimpleName() + ":" ) ). append( "Min Dependency Duration(" ).append( temporalUtil.milliDurationToString( lowestDependencyDurationAsMilliForOperationType.getValue() ) ) .append( ") " ); if ( operationInterleavesByOperationType().containsKey( operationType ) ) { ContinuousMetricSnapshot interleavesForOperationTypeSnapshot = operationInterleavesByOperationType().get( operationType ).snapshot(); sb. append( "Time Span(" ). append( temporalUtil.milliTimeToTimeString( firstStartAsMilliTypeForOperationType ) ) .append( ", " ) .append( temporalUtil.milliTimeToTimeString( lastStartAsMilliTypeForOperationType ) ) .append( ") " ). append( "Interleave(" ). append( "min = " ) .append( temporalUtil.milliDurationToString( interleavesForOperationTypeSnapshot.min() ) ) .append( " / " ). append( "mean = " ).append( temporalUtil.milliDurationToString( Math.round( interleavesForOperationTypeSnapshot.mean() ) ) ) .append( " / " ). append( "max = " ) .append( temporalUtil.milliDurationToString( interleavesForOperationTypeSnapshot.max() ) ) .append( ")" ); } sb.append( "\n" ); } sb.append( "********************************************************" ); return sb.toString(); } private List<String> toSortedClassNames( Iterable<Class> classes ) { List<String> classNames = Lists.newArrayList( Iterables.transform( classes, new Function<Class,String>() { @Override public String apply( Class aClass ) { return aClass.getSimpleName(); } } ) ); Collections.sort( classNames ); return classNames; } }