/*
* Copyright 2010 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.optaplanner.benchmark.impl.result;
import java.io.File;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.optaplanner.benchmark.impl.report.BenchmarkReport;
import org.optaplanner.core.api.score.Score;
import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.config.solver.EnvironmentMode;
import org.optaplanner.core.config.util.ConfigUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Represents the benchmarks on multiple {@link Solver} configurations on multiple problem instances (data sets).
*/
@XStreamAlias("plannerBenchmarkResult")
public class PlannerBenchmarkResult {
private String name;
private Boolean aggregation;
@XStreamOmitField // Moving or renaming a report directory after creation is allowed
private File benchmarkReportDirectory;
// If it is an aggregation, many properties can stay null
private Integer availableProcessors = null;
private String loggingLevelOptaPlannerCore = null;
private String loggingLevelDroolsCore = null;
private Long maxMemory = null;
private String optaPlannerVersion = null;
private String javaVersion = null;
private String javaVM = null;
private String operatingSystem = null;
private Integer parallelBenchmarkCount = null;
private Long warmUpTimeMillisSpentLimit = null;
private EnvironmentMode environmentMode = null;
@XStreamImplicit(itemFieldName = "solverBenchmarkResult")
private List<SolverBenchmarkResult> solverBenchmarkResultList = null;
@XStreamImplicit(itemFieldName = "unifiedProblemBenchmarkResult")
private List<ProblemBenchmarkResult> unifiedProblemBenchmarkResultList = null;
private OffsetDateTime startingTimestamp = null;
private Long benchmarkTimeMillisSpent = null;
// ************************************************************************
// Report accumulates
// ************************************************************************
private Integer failureCount = null;
private Long averageProblemScale = null;
private Score averageScore = null;
private SolverBenchmarkResult favoriteSolverBenchmarkResult = null;
// ************************************************************************
// Constructors and simple getters/setters
// ************************************************************************
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getAggregation() {
return aggregation;
}
public void setAggregation(Boolean aggregation) {
this.aggregation = aggregation;
}
public File getBenchmarkReportDirectory() {
return benchmarkReportDirectory;
}
public void setBenchmarkReportDirectory(File benchmarkReportDirectory) {
this.benchmarkReportDirectory = benchmarkReportDirectory;
}
public Integer getAvailableProcessors() {
return availableProcessors;
}
public String getLoggingLevelOptaPlannerCore() {
return loggingLevelOptaPlannerCore;
}
public String getLoggingLevelDroolsCore() {
return loggingLevelDroolsCore;
}
public Long getMaxMemory() {
return maxMemory;
}
public String getJavaVersion() {
return javaVersion;
}
public String getJavaVM() {
return javaVM;
}
public String getOperatingSystem() {
return operatingSystem;
}
public String getOptaPlannerVersion() {
return optaPlannerVersion;
}
public Integer getParallelBenchmarkCount() {
return parallelBenchmarkCount;
}
public void setParallelBenchmarkCount(Integer parallelBenchmarkCount) {
this.parallelBenchmarkCount = parallelBenchmarkCount;
}
public Long getWarmUpTimeMillisSpentLimit() {
return warmUpTimeMillisSpentLimit;
}
public void setWarmUpTimeMillisSpentLimit(Long warmUpTimeMillisSpentLimit) {
this.warmUpTimeMillisSpentLimit = warmUpTimeMillisSpentLimit;
}
public EnvironmentMode getEnvironmentMode() {
return environmentMode;
}
public List<SolverBenchmarkResult> getSolverBenchmarkResultList() {
return solverBenchmarkResultList;
}
public void setSolverBenchmarkResultList(List<SolverBenchmarkResult> solverBenchmarkResultList) {
this.solverBenchmarkResultList = solverBenchmarkResultList;
}
public List<ProblemBenchmarkResult> getUnifiedProblemBenchmarkResultList() {
return unifiedProblemBenchmarkResultList;
}
public void setUnifiedProblemBenchmarkResultList(List<ProblemBenchmarkResult> unifiedProblemBenchmarkResultList) {
this.unifiedProblemBenchmarkResultList = unifiedProblemBenchmarkResultList;
}
public OffsetDateTime getStartingTimestamp() {
return startingTimestamp;
}
public void setStartingTimestamp(OffsetDateTime startingTimestamp) {
this.startingTimestamp = startingTimestamp;
}
public Long getBenchmarkTimeMillisSpent() {
return benchmarkTimeMillisSpent;
}
public void setBenchmarkTimeMillisSpent(Long benchmarkTimeMillisSpent) {
this.benchmarkTimeMillisSpent = benchmarkTimeMillisSpent;
}
public Integer getFailureCount() {
return failureCount;
}
public Long getAverageProblemScale() {
return averageProblemScale;
}
public Score getAverageScore() {
return averageScore;
}
public SolverBenchmarkResult getFavoriteSolverBenchmarkResult() {
return favoriteSolverBenchmarkResult;
}
// ************************************************************************
// Smart getters
// ************************************************************************
public boolean hasMultipleParallelBenchmarks() {
return parallelBenchmarkCount == null || parallelBenchmarkCount > 1;
}
public boolean hasAnyFailure() {
return failureCount > 0;
}
public int getMaximumSubSingleCount() {
int maximumSubSingleCount = 0;
for (ProblemBenchmarkResult problemBenchmarkResult : unifiedProblemBenchmarkResultList) {
int problemMaximumSubSingleCount = problemBenchmarkResult.getMaximumSubSingleCount();
if (problemMaximumSubSingleCount > maximumSubSingleCount) {
maximumSubSingleCount = problemMaximumSubSingleCount;
}
}
return maximumSubSingleCount;
}
public String findScoreLevelLabel(int scoreLevel) {
String[] levelLabels = solverBenchmarkResultList.get(0).getScoreDefinition().getLevelLabels();
return levelLabels[scoreLevel];
}
public String getStartingTimestampAsMediumString() {
return startingTimestamp == null ? null : startingTimestamp.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
}
// ************************************************************************
// Accumulate methods
// ************************************************************************
public void initBenchmarkReportDirectory(File benchmarkDirectory) {
String timestampString = startingTimestamp.format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HHmmss"));
if (StringUtils.isEmpty(name)) {
name = timestampString;
}
if (!benchmarkDirectory.mkdirs()) {
if (!benchmarkDirectory.isDirectory()) {
throw new IllegalArgumentException("The benchmarkDirectory (" + benchmarkDirectory
+ ") already exists, but is not a directory.");
}
if (!benchmarkDirectory.canWrite()) {
throw new IllegalArgumentException("The benchmarkDirectory (" + benchmarkDirectory
+ ") already exists, but is not writable.");
}
}
int duplicationIndex = 0;
do {
String directoryName = timestampString + (duplicationIndex == 0 ? "" : "_" + duplicationIndex);
duplicationIndex++;
benchmarkReportDirectory = new File(benchmarkDirectory,
BooleanUtils.isFalse(aggregation) ? directoryName : directoryName + "_aggregation");
} while (!benchmarkReportDirectory.mkdir());
for (ProblemBenchmarkResult problemBenchmarkResult : unifiedProblemBenchmarkResultList) {
problemBenchmarkResult.makeDirs();
}
}
public void initSystemProperties() {
availableProcessors = Runtime.getRuntime().availableProcessors();
loggingLevelOptaPlannerCore = resolveLoggingLevel("org.optaplanner.core");
loggingLevelDroolsCore = resolveLoggingLevel("org.drools.core");
maxMemory = Runtime.getRuntime().maxMemory();
optaPlannerVersion = SolverFactory.class.getPackage().getImplementationVersion();
if (optaPlannerVersion == null) {
optaPlannerVersion = "Unjarred development snapshot";
}
javaVersion = "Java " + System.getProperty("java.version") + " (" + System.getProperty("java.vendor") + ")";
javaVM = "Java " + System.getProperty("java.vm.name") + " " + System.getProperty("java.vm.version")
+ " (" + System.getProperty("java.vm.vendor") + ")";
operatingSystem = System.getProperty("os.name") + " " + System.getProperty("os.arch")
+ " " + System.getProperty("os.version");
}
private String resolveLoggingLevel(String loggerName) {
Logger logger = LoggerFactory.getLogger(loggerName);
if (logger.isTraceEnabled()) {
return "trace";
} else if (logger.isDebugEnabled()) {
return "debug";
} else if (logger.isInfoEnabled()) {
return "info";
} else if (logger.isWarnEnabled()) {
return "warn";
} else if (logger.isErrorEnabled()) {
return "error";
} else {
throw new IllegalStateException("Logging level for loggerName (" + loggerName + ") cannot be determined.");
}
}
public int getTotalSubSingleCount() {
int totalSubSingleCount = 0;
for (ProblemBenchmarkResult problemBenchmarkResult : unifiedProblemBenchmarkResultList) {
totalSubSingleCount += problemBenchmarkResult.getTotalSubSingleCount();
}
return totalSubSingleCount;
}
public void accumulateResults(BenchmarkReport benchmarkReport) {
for (ProblemBenchmarkResult problemBenchmarkResult : unifiedProblemBenchmarkResultList) {
problemBenchmarkResult.accumulateResults(benchmarkReport);
}
for (SolverBenchmarkResult solverBenchmarkResult : solverBenchmarkResultList) {
solverBenchmarkResult.accumulateResults(benchmarkReport);
}
determineTotalsAndAverages();
determineSolverRanking(benchmarkReport);
}
private void determineTotalsAndAverages() {
failureCount = 0;
long totalProblemScale = 0L;
int problemScaleCount = 0;
for (ProblemBenchmarkResult problemBenchmarkResult : unifiedProblemBenchmarkResultList) {
Long problemScale = problemBenchmarkResult.getProblemScale();
if (problemScale != null && problemScale >= 0L) {
totalProblemScale += problemScale;
problemScaleCount++;
}
failureCount += problemBenchmarkResult.getFailureCount();
}
averageProblemScale = problemScaleCount == 0 ? null : totalProblemScale / (long) problemScaleCount;
Score totalScore = null;
int solverBenchmarkCount = 0;
boolean firstSolverBenchmarkResult = true;
for (SolverBenchmarkResult solverBenchmarkResult : solverBenchmarkResultList) {
EnvironmentMode solverEnvironmentMode = solverBenchmarkResult.getEnvironmentMode();
if (firstSolverBenchmarkResult && solverEnvironmentMode != null) {
environmentMode = solverEnvironmentMode;
firstSolverBenchmarkResult = false;
} else if (!firstSolverBenchmarkResult && solverEnvironmentMode != environmentMode) {
environmentMode = null;
}
Score score = solverBenchmarkResult.getAverageScore();
if (score != null) {
if (totalScore != null && !totalScore.isCompatibleArithmeticArgument(score)) {
// Mixing different use cases with different score definitions.
totalScore = null;
break;
}
totalScore = (totalScore == null) ? score : totalScore.add(score);
solverBenchmarkCount++;
}
}
if (totalScore != null) {
averageScore = totalScore.divide(solverBenchmarkCount);
}
}
private void determineSolverRanking(BenchmarkReport benchmarkReport) {
List<SolverBenchmarkResult> rankableSolverBenchmarkResultList = new ArrayList<>(solverBenchmarkResultList);
// Do not rank a SolverBenchmarkResult that has a failure
rankableSolverBenchmarkResultList.removeIf(SolverBenchmarkResult::hasAnyFailure);
List<List<SolverBenchmarkResult>> sameRankingListList = createSameRankingListList(
benchmarkReport, rankableSolverBenchmarkResultList);
int ranking = 0;
for (List<SolverBenchmarkResult> sameRankingList : sameRankingListList) {
for (SolverBenchmarkResult solverBenchmarkResult : sameRankingList) {
solverBenchmarkResult.setRanking(ranking);
}
ranking += sameRankingList.size();
}
favoriteSolverBenchmarkResult = sameRankingListList.isEmpty() ? null
: sameRankingListList.get(0).get(0);
}
private List<List<SolverBenchmarkResult>> createSameRankingListList(
BenchmarkReport benchmarkReport, List<SolverBenchmarkResult> rankableSolverBenchmarkResultList) {
List<List<SolverBenchmarkResult>> sameRankingListList = new ArrayList<>(
rankableSolverBenchmarkResultList.size());
if (benchmarkReport.getSolverRankingComparator() != null) {
Comparator<SolverBenchmarkResult> comparator = Collections.reverseOrder(
benchmarkReport.getSolverRankingComparator());
rankableSolverBenchmarkResultList.sort(comparator);
List<SolverBenchmarkResult> sameRankingList = null;
SolverBenchmarkResult previousSolverBenchmarkResult = null;
for (SolverBenchmarkResult solverBenchmarkResult : rankableSolverBenchmarkResultList) {
if (previousSolverBenchmarkResult == null
|| comparator.compare(previousSolverBenchmarkResult, solverBenchmarkResult) != 0) {
// New rank
sameRankingList = new ArrayList<>();
sameRankingListList.add(sameRankingList);
}
sameRankingList.add(solverBenchmarkResult);
previousSolverBenchmarkResult = solverBenchmarkResult;
}
} else if (benchmarkReport.getSolverRankingWeightFactory() != null) {
SortedMap<Comparable, List<SolverBenchmarkResult>> rankedMap
= new TreeMap<>(Collections.reverseOrder());
for (SolverBenchmarkResult solverBenchmarkResult : rankableSolverBenchmarkResultList) {
Comparable rankingWeight = benchmarkReport.getSolverRankingWeightFactory()
.createRankingWeight(rankableSolverBenchmarkResultList, solverBenchmarkResult);
List<SolverBenchmarkResult> sameRankingList = rankedMap.computeIfAbsent(rankingWeight,
k -> new ArrayList<>());
sameRankingList.add(solverBenchmarkResult);
}
for (Map.Entry<Comparable, List<SolverBenchmarkResult>> entry : rankedMap.entrySet()) {
sameRankingListList.add(entry.getValue());
}
} else {
throw new IllegalStateException("Ranking is impossible" +
" because solverRankingComparator and solverRankingWeightFactory are null.");
}
return sameRankingListList;
}
// ************************************************************************
// Merger methods
// ************************************************************************
public static PlannerBenchmarkResult createMergedResult(
List<SingleBenchmarkResult> singleBenchmarkResultList) {
PlannerBenchmarkResult mergedResult = createMergeSingleton(singleBenchmarkResultList);
Map<SolverBenchmarkResult, SolverBenchmarkResult> solverMergeMap
= SolverBenchmarkResult.createMergeMap(mergedResult, singleBenchmarkResultList);
Map<ProblemBenchmarkResult, ProblemBenchmarkResult> problemMergeMap
= ProblemBenchmarkResult.createMergeMap(mergedResult, singleBenchmarkResultList);
for (SingleBenchmarkResult singleBenchmarkResult : singleBenchmarkResultList) {
SolverBenchmarkResult solverBenchmarkResult = solverMergeMap.get(
singleBenchmarkResult.getSolverBenchmarkResult());
ProblemBenchmarkResult problemBenchmarkResult = problemMergeMap.get(
singleBenchmarkResult.getProblemBenchmarkResult());
SingleBenchmarkResult.createMerge(
solverBenchmarkResult, problemBenchmarkResult, singleBenchmarkResult);
}
return mergedResult;
}
protected static PlannerBenchmarkResult createMergeSingleton(List<SingleBenchmarkResult> singleBenchmarkResultList) {
PlannerBenchmarkResult newResult = null;
Map<PlannerBenchmarkResult, PlannerBenchmarkResult> mergeMap
= new IdentityHashMap<>();
for (SingleBenchmarkResult singleBenchmarkResult : singleBenchmarkResultList) {
PlannerBenchmarkResult oldResult = singleBenchmarkResult
.getSolverBenchmarkResult().getPlannerBenchmarkResult();
if (!mergeMap.containsKey(oldResult)) {
if (newResult == null) {
newResult = new PlannerBenchmarkResult();
newResult.setAggregation(true);
newResult.availableProcessors = oldResult.availableProcessors;
newResult.loggingLevelOptaPlannerCore = oldResult.loggingLevelOptaPlannerCore;
newResult.loggingLevelDroolsCore = oldResult.loggingLevelDroolsCore;
newResult.maxMemory = oldResult.maxMemory;
newResult.optaPlannerVersion = oldResult.optaPlannerVersion;
newResult.javaVersion = oldResult.javaVersion;
newResult.javaVM = oldResult.javaVM;
newResult.operatingSystem = oldResult.operatingSystem;
newResult.parallelBenchmarkCount = oldResult.parallelBenchmarkCount;
newResult.warmUpTimeMillisSpentLimit = oldResult.warmUpTimeMillisSpentLimit;
newResult.environmentMode = oldResult.environmentMode;
newResult.solverBenchmarkResultList = new ArrayList<>();
newResult.unifiedProblemBenchmarkResultList = new ArrayList<>();
newResult.startingTimestamp = null;
newResult.benchmarkTimeMillisSpent = null;
} else {
newResult.availableProcessors = ConfigUtils.mergeProperty(
newResult.availableProcessors, oldResult.availableProcessors);
newResult.loggingLevelOptaPlannerCore = ConfigUtils.mergeProperty(
newResult.loggingLevelOptaPlannerCore, oldResult.loggingLevelOptaPlannerCore);
newResult.loggingLevelDroolsCore = ConfigUtils.mergeProperty(
newResult.loggingLevelDroolsCore, oldResult.loggingLevelDroolsCore);
newResult.maxMemory = ConfigUtils.mergeProperty(
newResult.maxMemory, oldResult.maxMemory);
newResult.optaPlannerVersion = ConfigUtils.mergeProperty(
newResult.optaPlannerVersion, oldResult.optaPlannerVersion);
newResult.javaVersion = ConfigUtils.mergeProperty(
newResult.javaVersion, oldResult.javaVersion);
newResult.javaVM = ConfigUtils.mergeProperty(
newResult.javaVM, oldResult.javaVM);
newResult.operatingSystem = ConfigUtils.mergeProperty(
newResult.operatingSystem, oldResult.operatingSystem);
newResult.parallelBenchmarkCount = ConfigUtils.mergeProperty(
newResult.parallelBenchmarkCount, oldResult.parallelBenchmarkCount);
newResult.warmUpTimeMillisSpentLimit = ConfigUtils.mergeProperty(
newResult.warmUpTimeMillisSpentLimit, oldResult.warmUpTimeMillisSpentLimit);
newResult.environmentMode = ConfigUtils.mergeProperty(
newResult.environmentMode, oldResult.environmentMode);
}
mergeMap.put(oldResult, newResult);
}
}
return newResult;
}
public static PlannerBenchmarkResult createUnmarshallingFailedResult(String benchmarkReportDirectoryName) {
PlannerBenchmarkResult result = new PlannerBenchmarkResult();
result.setName("Failed unmarshalling " + benchmarkReportDirectoryName);
result.setSolverBenchmarkResultList(Collections.emptyList());
result.setUnifiedProblemBenchmarkResultList(Collections.emptyList());
return result;
}
@Override
public String toString() {
return getName();
}
}