/*
* Copyright 2014 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;
import java.io.File;
import java.time.OffsetDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import org.optaplanner.benchmark.api.PlannerBenchmark;
import org.optaplanner.benchmark.api.PlannerBenchmarkException;
import org.optaplanner.benchmark.impl.report.BenchmarkReport;
import org.optaplanner.benchmark.impl.result.BenchmarkResultIO;
import org.optaplanner.benchmark.impl.result.PlannerBenchmarkResult;
import org.optaplanner.benchmark.impl.result.ProblemBenchmarkResult;
import org.optaplanner.benchmark.impl.result.SingleBenchmarkResult;
import org.optaplanner.benchmark.impl.result.SolverBenchmarkResult;
import org.optaplanner.benchmark.impl.result.SubSingleBenchmarkResult;
import org.optaplanner.benchmark.impl.statistic.ProblemStatistic;
import org.optaplanner.benchmark.impl.statistic.PureSubSingleStatistic;
import org.optaplanner.core.config.SolverConfigContext;
import org.optaplanner.core.config.solver.termination.TerminationConfig;
import org.optaplanner.core.config.util.ConfigUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DefaultPlannerBenchmark implements PlannerBenchmark {
protected final transient Logger logger = LoggerFactory.getLogger(getClass());
protected final transient Logger singleBenchmarkRunnerExceptionLogger = LoggerFactory.getLogger(
getClass().getName() + ".singleBenchmarkRunnerException");
private final PlannerBenchmarkResult plannerBenchmarkResult;
private final SolverConfigContext solverConfigContext;
private final File benchmarkDirectory;
private final ExecutorService warmUpExecutorService;
private final ExecutorCompletionService<SubSingleBenchmarkRunner> warmUpExecutorCompletionService;
private final ExecutorService executorService;
private final BenchmarkResultIO benchmarkResultIO;
private final BenchmarkReport benchmarkReport;
private long startingSystemTimeMillis = -1L;
private SubSingleBenchmarkRunner firstFailureSubSingleBenchmarkRunner = null;
public DefaultPlannerBenchmark(
PlannerBenchmarkResult plannerBenchmarkResult, SolverConfigContext solverConfigContext,
File benchmarkDirectory,
ExecutorService warmUpExecutorService, ExecutorService executorService,
BenchmarkReport benchmarkReport) {
this.plannerBenchmarkResult = plannerBenchmarkResult;
this.solverConfigContext = solverConfigContext;
this.benchmarkDirectory = benchmarkDirectory;
this.warmUpExecutorService = warmUpExecutorService;
warmUpExecutorCompletionService = new ExecutorCompletionService<>(warmUpExecutorService);
this.executorService = executorService;
this.benchmarkReport = benchmarkReport;
benchmarkResultIO = new BenchmarkResultIO();
}
public PlannerBenchmarkResult getPlannerBenchmarkResult() {
return plannerBenchmarkResult;
}
public File getBenchmarkDirectory() {
return benchmarkDirectory;
}
public BenchmarkReport getBenchmarkReport() {
return benchmarkReport;
}
// ************************************************************************
// Benchmark methods
// ************************************************************************
@Override
public void benchmark() {
benchmarkingStarted();
warmUp();
runSingleBenchmarks();
benchmarkingEnded();
}
public void benchmarkingStarted() {
if (startingSystemTimeMillis >= 0L) {
throw new IllegalStateException("This benchmark has already ran before.");
}
startingSystemTimeMillis = System.currentTimeMillis();
plannerBenchmarkResult.setStartingTimestamp(OffsetDateTime.now());
List<SolverBenchmarkResult> solverBenchmarkResultList = plannerBenchmarkResult.getSolverBenchmarkResultList();
if (ConfigUtils.isEmptyCollection(solverBenchmarkResultList)) {
throw new IllegalArgumentException(
"The solverBenchmarkResultList (" + solverBenchmarkResultList + ") cannot be empty.");
}
initBenchmarkDirectoryAndSubdirectories();
plannerBenchmarkResult.initSystemProperties();
logger.info("Benchmarking started: parallelBenchmarkCount ({})"
+ " for problemCount ({}), solverCount ({}), totalSubSingleCount ({}).",
plannerBenchmarkResult.getParallelBenchmarkCount(),
plannerBenchmarkResult.getUnifiedProblemBenchmarkResultList().size(),
solverBenchmarkResultList.size(),
plannerBenchmarkResult.getTotalSubSingleCount());
}
private void initBenchmarkDirectoryAndSubdirectories() {
if (benchmarkDirectory == null) {
throw new IllegalArgumentException("The benchmarkDirectory (" + benchmarkDirectory + ") must not be null.");
}
// benchmarkDirectory usually already exists
benchmarkDirectory.mkdirs();
plannerBenchmarkResult.initBenchmarkReportDirectory(benchmarkDirectory);
}
private void warmUp() {
if (plannerBenchmarkResult.getWarmUpTimeMillisSpentLimit() <= 0L) {
return;
}
logger.info("================================================================================");
logger.info("Warm up started");
logger.info("================================================================================");
long timeLeftTotal = plannerBenchmarkResult.getWarmUpTimeMillisSpentLimit();
int parallelBenchmarkCount = plannerBenchmarkResult.getParallelBenchmarkCount();
int solverBenchmarkResultCount = plannerBenchmarkResult.getSolverBenchmarkResultList().size();
int cyclesCount = ConfigUtils.ceilDivide(solverBenchmarkResultCount, parallelBenchmarkCount);
long timeLeftPerCycle = Math.floorDiv(timeLeftTotal, cyclesCount);
Map<ProblemBenchmarkResult, List<ProblemStatistic>> originalProblemStatisticMap
= new HashMap<>(plannerBenchmarkResult.getUnifiedProblemBenchmarkResultList().size());
ConcurrentMap<SolverBenchmarkResult, Integer> singleBenchmarkResultIndexMap
= new ConcurrentHashMap<>(solverBenchmarkResultCount);
Map<SolverBenchmarkResult, WarmUpConfigBackup> warmUpConfigBackupMap = WarmUpConfigBackup.backupBenchmarkConfig(plannerBenchmarkResult, originalProblemStatisticMap);
SolverBenchmarkResult[] solverBenchmarkResultCycle = new SolverBenchmarkResult[parallelBenchmarkCount];
int solverBenchmarkResultIndex = 0;
for (int i = 0; i < cyclesCount; i++) {
long timeCycleEnd = System.currentTimeMillis() + timeLeftPerCycle;
for (int j = 0; j < parallelBenchmarkCount; j++) {
solverBenchmarkResultCycle[j] = plannerBenchmarkResult.
getSolverBenchmarkResultList().get(solverBenchmarkResultIndex % solverBenchmarkResultCount);
solverBenchmarkResultIndex++;
}
ConcurrentMap<Future<SubSingleBenchmarkRunner>, SubSingleBenchmarkRunner> futureMap
= new ConcurrentHashMap<>(parallelBenchmarkCount);
warmUpPopulate(futureMap, singleBenchmarkResultIndexMap, solverBenchmarkResultCycle, timeLeftPerCycle);
warmUp(futureMap, singleBenchmarkResultIndexMap, timeCycleEnd);
}
WarmUpConfigBackup.restoreBenchmarkConfig(plannerBenchmarkResult, originalProblemStatisticMap, warmUpConfigBackupMap);
List<Runnable> notFinishedWarmUpList = warmUpExecutorService.shutdownNow();
if (!notFinishedWarmUpList.isEmpty()) {
throw new IllegalStateException("Impossible state: notFinishedWarmUpList (" + notFinishedWarmUpList
+ ") is not empty.");
}
logger.info("================================================================================");
logger.info("Warm up ended");
logger.info("================================================================================");
}
private void warmUpPopulate(Map<Future<SubSingleBenchmarkRunner>, SubSingleBenchmarkRunner> futureMap,
ConcurrentMap<SolverBenchmarkResult, Integer> singleBenchmarkResultIndexMap,
SolverBenchmarkResult[] solverBenchmarkResultArray, long timeLeftPerSolverConfig) {
for (SolverBenchmarkResult solverBenchmarkResult : solverBenchmarkResultArray) {
TerminationConfig originalTerminationConfig = solverBenchmarkResult.getSolverConfig().getTerminationConfig();
TerminationConfig tmpTerminationConfig = new TerminationConfig();
if (originalTerminationConfig != null) {
tmpTerminationConfig.inherit(originalTerminationConfig);
}
tmpTerminationConfig.shortenTimeMillisSpentLimit(timeLeftPerSolverConfig);
solverBenchmarkResult.getSolverConfig().setTerminationConfig(tmpTerminationConfig);
Integer singleBenchmarkResultIndex = singleBenchmarkResultIndexMap.get(solverBenchmarkResult);
singleBenchmarkResultIndex = (singleBenchmarkResultIndex == null) ? 0 : singleBenchmarkResultIndex % solverBenchmarkResult.getSingleBenchmarkResultList().size();
SingleBenchmarkResult singleBenchmarkResult
= solverBenchmarkResult.getSingleBenchmarkResultList().get(singleBenchmarkResultIndex);
// Just take the first subSingle, we don't need to warm up each one
SubSingleBenchmarkRunner subSingleBenchmarkRunner = new SubSingleBenchmarkRunner(
singleBenchmarkResult.getSubSingleBenchmarkResultList().get(0), true, solverConfigContext);
Future<SubSingleBenchmarkRunner> future = warmUpExecutorCompletionService.submit(subSingleBenchmarkRunner);
futureMap.put(future, subSingleBenchmarkRunner);
singleBenchmarkResultIndexMap.put(solverBenchmarkResult, singleBenchmarkResultIndex + 1);
}
}
private void warmUp(Map<Future<SubSingleBenchmarkRunner>, SubSingleBenchmarkRunner> futureMap,
ConcurrentMap<SolverBenchmarkResult, Integer> singleBenchmarkResultIndexMap, long timePhaseEnd) {
// Wait for the warm up benchmarks to complete
int tasksCount = futureMap.size();
// Use a counter because completion order of futures is different from input order
for (int i = 0; i < tasksCount; i++) {
Future<SubSingleBenchmarkRunner> future;
try {
future = warmUpExecutorCompletionService.take();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Waiting for a warm up singleBenchmarkRunner was interrupted.", e);
}
Throwable failureThrowable = null;
SubSingleBenchmarkRunner subSingleBenchmarkRunner;
try {
// Explicitly returning it in the Callable guarantees memory visibility
subSingleBenchmarkRunner = future.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
subSingleBenchmarkRunner = futureMap.get(future);
singleBenchmarkRunnerExceptionLogger.error("The warm up singleBenchmarkRunner ({}) was interrupted.",
subSingleBenchmarkRunner, e);
failureThrowable = e;
} catch (ExecutionException e) {
Throwable cause = e.getCause();
subSingleBenchmarkRunner = futureMap.get(future);
singleBenchmarkRunnerExceptionLogger.warn("The warm up singleBenchmarkRunner ({}) failed.",
subSingleBenchmarkRunner, cause);
failureThrowable = cause;
}
if (failureThrowable != null) {
subSingleBenchmarkRunner.setFailureThrowable(failureThrowable);
if (firstFailureSubSingleBenchmarkRunner == null) {
firstFailureSubSingleBenchmarkRunner = subSingleBenchmarkRunner;
}
}
SolverBenchmarkResult solverBenchmarkResult = subSingleBenchmarkRunner.getSubSingleBenchmarkResult().getSingleBenchmarkResult().getSolverBenchmarkResult();
long timeLeftInCycle = timePhaseEnd - System.currentTimeMillis();
if (timeLeftInCycle > 0L) {
SolverBenchmarkResult[] solverBenchmarkResultSingleton = new SolverBenchmarkResult[]{solverBenchmarkResult};
warmUpPopulate(futureMap, singleBenchmarkResultIndexMap, solverBenchmarkResultSingleton, timeLeftInCycle);
tasksCount++;
}
}
}
protected void runSingleBenchmarks() {
Map<SubSingleBenchmarkRunner, Future<SubSingleBenchmarkRunner>> futureMap = new HashMap<>();
for (ProblemBenchmarkResult<Object> problemBenchmarkResult : plannerBenchmarkResult.getUnifiedProblemBenchmarkResultList()) {
for (SingleBenchmarkResult singleBenchmarkResult : problemBenchmarkResult.getSingleBenchmarkResultList()) {
for (SubSingleBenchmarkResult subSingleBenchmarkResult : singleBenchmarkResult.getSubSingleBenchmarkResultList()) {
SubSingleBenchmarkRunner subSingleBenchmarkRunner = new SubSingleBenchmarkRunner(
subSingleBenchmarkResult, false, solverConfigContext);
Future<SubSingleBenchmarkRunner> future = executorService.submit(subSingleBenchmarkRunner);
futureMap.put(subSingleBenchmarkRunner, future);
}
}
}
// Wait for the benchmarks to complete
for (Map.Entry<SubSingleBenchmarkRunner, Future<SubSingleBenchmarkRunner>> futureEntry : futureMap.entrySet()) {
SubSingleBenchmarkRunner subSingleBenchmarkRunner = futureEntry.getKey();
Future<SubSingleBenchmarkRunner> future = futureEntry.getValue();
Throwable failureThrowable = null;
try {
// Explicitly returning it in the Callable guarantees memory visibility
subSingleBenchmarkRunner = future.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
singleBenchmarkRunnerExceptionLogger.error("The subSingleBenchmarkRunner ({}) was interrupted.",
subSingleBenchmarkRunner, e);
failureThrowable = e;
} catch (ExecutionException e) {
Throwable cause = e.getCause();
singleBenchmarkRunnerExceptionLogger.warn("The subSingleBenchmarkRunner ({}) failed.",
subSingleBenchmarkRunner, cause);
failureThrowable = cause;
}
if (failureThrowable == null) {
subSingleBenchmarkRunner.getSubSingleBenchmarkResult().setSucceeded(true);
} else {
subSingleBenchmarkRunner.getSubSingleBenchmarkResult().setSucceeded(false);
subSingleBenchmarkRunner.setFailureThrowable(failureThrowable);
if (firstFailureSubSingleBenchmarkRunner == null) {
firstFailureSubSingleBenchmarkRunner = subSingleBenchmarkRunner;
}
}
}
}
public void benchmarkingEnded() {
List<Runnable> notExecutedBenchmarkList = executorService.shutdownNow();
if (!notExecutedBenchmarkList.isEmpty()) {
throw new IllegalStateException("Impossible state: notExecutedBenchmarkList size ("
+ notExecutedBenchmarkList + ").");
}
plannerBenchmarkResult.setBenchmarkTimeMillisSpent(calculateTimeMillisSpent());
benchmarkResultIO.writePlannerBenchmarkResult(plannerBenchmarkResult.getBenchmarkReportDirectory(),
plannerBenchmarkResult);
benchmarkReport.writeReport();
if (plannerBenchmarkResult.getFailureCount() == 0) {
logger.info("Benchmarking ended: time spent ({}), favoriteSolverBenchmark ({}), statistic html overview ({}).",
plannerBenchmarkResult.getBenchmarkTimeMillisSpent(),
plannerBenchmarkResult.getFavoriteSolverBenchmarkResult().getName(),
benchmarkReport.getHtmlOverviewFile().getAbsolutePath());
} else {
logger.info("Benchmarking failed: time spent ({}), failureCount ({}), statistic html overview ({}).",
plannerBenchmarkResult.getBenchmarkTimeMillisSpent(),
plannerBenchmarkResult.getFailureCount(),
benchmarkReport.getHtmlOverviewFile().getAbsolutePath());
throw new PlannerBenchmarkException("Benchmarking failed: failureCount ("
+ plannerBenchmarkResult.getFailureCount() + ")." +
" The exception of the firstFailureSingleBenchmarkRunner ("
+ firstFailureSubSingleBenchmarkRunner.getName() + ") is chained.",
firstFailureSubSingleBenchmarkRunner.getFailureThrowable());
}
}
public long calculateTimeMillisSpent() {
long now = System.currentTimeMillis();
return now - startingSystemTimeMillis;
}
private static final class WarmUpConfigBackup {
private final TerminationConfig terminationConfig;
private final Map<SubSingleBenchmarkResult, List<PureSubSingleStatistic>> pureSubSingleStatisticMap;
public WarmUpConfigBackup(TerminationConfig terminationConfig) {
this.terminationConfig = terminationConfig;
this.pureSubSingleStatisticMap = new HashMap<>();
}
public Map<SubSingleBenchmarkResult, List<PureSubSingleStatistic>> getPureSubSingleStatisticMap() {
return pureSubSingleStatisticMap;
}
public TerminationConfig getTerminationConfig() {
return terminationConfig;
}
private static void restoreBenchmarkConfig(PlannerBenchmarkResult plannerBenchmarkResult, Map<ProblemBenchmarkResult, List<ProblemStatistic>> originalProblemStatisticMap, Map<SolverBenchmarkResult, WarmUpConfigBackup> warmUpConfigBackupMap) {
for (SolverBenchmarkResult solverBenchmarkResult : plannerBenchmarkResult.getSolverBenchmarkResultList()) {
WarmUpConfigBackup warmUpConfigBackup = warmUpConfigBackupMap.get(solverBenchmarkResult);
TerminationConfig originalTerminationConfig = warmUpConfigBackup.getTerminationConfig();
solverBenchmarkResult.getSolverConfig().setTerminationConfig(originalTerminationConfig);
for (SingleBenchmarkResult singleBenchmarkResult : solverBenchmarkResult.getSingleBenchmarkResultList()) {
ProblemBenchmarkResult problemBenchmarkResult = singleBenchmarkResult.getProblemBenchmarkResult();
if (problemBenchmarkResult.getProblemStatisticList() == null || problemBenchmarkResult.getProblemStatisticList().size() <= 0) {
problemBenchmarkResult.setProblemStatisticList(originalProblemStatisticMap.get(problemBenchmarkResult));
}
for (SubSingleBenchmarkResult subSingleBenchmarkResult : singleBenchmarkResult.getSubSingleBenchmarkResultList()) {
List<PureSubSingleStatistic> pureSubSingleStatisticList = warmUpConfigBackup.getPureSubSingleStatisticMap().get(subSingleBenchmarkResult);
subSingleBenchmarkResult.setPureSubSingleStatisticList(pureSubSingleStatisticList);
subSingleBenchmarkResult.initSubSingleStatisticMap();
}
singleBenchmarkResult.initSubSingleStatisticMaps();
}
}
}
private static Map<SolverBenchmarkResult, WarmUpConfigBackup> backupBenchmarkConfig(PlannerBenchmarkResult plannerBenchmarkResult, Map<ProblemBenchmarkResult, List<ProblemStatistic>> originalProblemStatisticMap) { // backup & remove stats, backup termination config
Map<SolverBenchmarkResult, WarmUpConfigBackup> warmUpConfigBackupMap = new HashMap<>(plannerBenchmarkResult.getSolverBenchmarkResultList().size());
for (SolverBenchmarkResult solverBenchmarkResult : plannerBenchmarkResult.getSolverBenchmarkResultList()) {
TerminationConfig originalTerminationConfig = solverBenchmarkResult.getSolverConfig().getTerminationConfig();
WarmUpConfigBackup warmUpConfigBackup = new WarmUpConfigBackup(originalTerminationConfig);
for (SingleBenchmarkResult singleBenchmarkResult : solverBenchmarkResult.getSingleBenchmarkResultList()) {
for (SubSingleBenchmarkResult subSingleBenchmarkResult : singleBenchmarkResult.getSubSingleBenchmarkResultList()) {
List<PureSubSingleStatistic> originalPureSubSingleStatisticList = subSingleBenchmarkResult.getPureSubSingleStatisticList();
List<PureSubSingleStatistic> subSingleBenchmarkStatisticPutResult = warmUpConfigBackup.getPureSubSingleStatisticMap().put(subSingleBenchmarkResult, originalPureSubSingleStatisticList);
if (subSingleBenchmarkStatisticPutResult != null) {
throw new IllegalStateException("SubSingleBenchmarkStatisticMap of WarmUpConfigBackup (" + warmUpConfigBackup
+ ") already contained key (" + subSingleBenchmarkResult + ") with value ("
+ subSingleBenchmarkStatisticPutResult + ").");
}
}
ProblemBenchmarkResult problemBenchmarkResult = singleBenchmarkResult.getProblemBenchmarkResult();
originalProblemStatisticMap.putIfAbsent(problemBenchmarkResult, problemBenchmarkResult.getProblemStatisticList());
singleBenchmarkResult.getProblemBenchmarkResult().setProblemStatisticList(Collections.emptyList());
for (SubSingleBenchmarkResult subSingleBenchmarkResult : singleBenchmarkResult.getSubSingleBenchmarkResultList()) { // needs to happen after all problem stats
subSingleBenchmarkResult.setPureSubSingleStatisticList(Collections.emptyList());
subSingleBenchmarkResult.initSubSingleStatisticMap();
}
}
WarmUpConfigBackup warmUpConfigBackupPutResult = warmUpConfigBackupMap.put(solverBenchmarkResult, warmUpConfigBackup);
if (warmUpConfigBackupPutResult != null) {
throw new IllegalStateException("WarmUpConfigBackupMap already contained key (" + solverBenchmarkResult
+ ") with value (" + warmUpConfigBackupPutResult + ").");
}
}
return warmUpConfigBackupMap;
}
}
}