/*
* Copyright 2011 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.config;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.regex.Pattern;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
import org.optaplanner.benchmark.api.PlannerBenchmark;
import org.optaplanner.benchmark.config.blueprint.SolverBenchmarkBluePrintConfig;
import org.optaplanner.benchmark.config.report.BenchmarkReportConfig;
import org.optaplanner.benchmark.impl.DefaultPlannerBenchmark;
import org.optaplanner.benchmark.impl.report.BenchmarkReport;
import org.optaplanner.benchmark.impl.result.PlannerBenchmarkResult;
import org.optaplanner.core.config.SolverConfigContext;
import org.optaplanner.core.config.util.ConfigUtils;
import org.optaplanner.core.impl.solver.thread.DefaultSolverThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.commons.lang3.ObjectUtils.*;
@XStreamAlias("plannerBenchmark")
public class PlannerBenchmarkConfig {
public static final String PARALLEL_BENCHMARK_COUNT_AUTO = "AUTO";
public static final Pattern VALID_NAME_PATTERN = Pattern.compile("(?U)^[\\w\\d _\\-\\.\\(\\)]+$");
private static final Logger logger = LoggerFactory.getLogger(PlannerBenchmarkConfig.class);
private String name = null;
private File benchmarkDirectory = null;
private Class<? extends ThreadFactory> threadFactoryClass = null;
private String parallelBenchmarkCount = null;
private Long warmUpMillisecondsSpentLimit = null;
private Long warmUpSecondsSpentLimit = null;
private Long warmUpMinutesSpentLimit = null;
private Long warmUpHoursSpentLimit = null;
private Long warmUpDaysSpentLimit = null;
@XStreamAlias("benchmarkReport")
private BenchmarkReportConfig benchmarkReportConfig = null;
@XStreamAlias("inheritedSolverBenchmark")
private SolverBenchmarkConfig inheritedSolverBenchmarkConfig = null;
@XStreamImplicit(itemFieldName = "solverBenchmarkBluePrint")
private List<SolverBenchmarkBluePrintConfig> solverBenchmarkBluePrintConfigList = null;
@XStreamImplicit(itemFieldName = "solverBenchmark")
private List<SolverBenchmarkConfig> solverBenchmarkConfigList = null;
// ************************************************************************
// Constructors and simple getters/setters
// ************************************************************************
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public File getBenchmarkDirectory() {
return benchmarkDirectory;
}
public void setBenchmarkDirectory(File benchmarkDirectory) {
this.benchmarkDirectory = benchmarkDirectory;
}
public Class<? extends ThreadFactory> getThreadFactoryClass() {
return threadFactoryClass;
}
public void setThreadFactoryClass(Class<? extends ThreadFactory> threadFactoryClass) {
this.threadFactoryClass = threadFactoryClass;
}
/**
* Using multiple parallel benchmarks can decrease the reliability of the results.
* <p>
* If there aren't enough processors available, it will be decreased.
* @return null, a number, {@value #PARALLEL_BENCHMARK_COUNT_AUTO} or a JavaScript calculation using
* {@value org.optaplanner.core.config.util.ConfigUtils#AVAILABLE_PROCESSOR_COUNT}.
*/
public String getParallelBenchmarkCount() {
return parallelBenchmarkCount;
}
public void setParallelBenchmarkCount(String parallelBenchmarkCount) {
this.parallelBenchmarkCount = parallelBenchmarkCount;
}
public Long getWarmUpMillisecondsSpentLimit() {
return warmUpMillisecondsSpentLimit;
}
public void setWarmUpMillisecondsSpentLimit(Long warmUpMillisecondsSpentLimit) {
this.warmUpMillisecondsSpentLimit = warmUpMillisecondsSpentLimit;
}
public Long getWarmUpSecondsSpentLimit() {
return warmUpSecondsSpentLimit;
}
public void setWarmUpSecondsSpentLimit(Long warmUpSecondsSpentLimit) {
this.warmUpSecondsSpentLimit = warmUpSecondsSpentLimit;
}
public Long getWarmUpMinutesSpentLimit() {
return warmUpMinutesSpentLimit;
}
public void setWarmUpMinutesSpentLimit(Long warmUpMinutesSpentLimit) {
this.warmUpMinutesSpentLimit = warmUpMinutesSpentLimit;
}
public Long getWarmUpHoursSpentLimit() {
return warmUpHoursSpentLimit;
}
public void setWarmUpHoursSpentLimit(Long warmUpHoursSpentLimit) {
this.warmUpHoursSpentLimit = warmUpHoursSpentLimit;
}
public Long getWarmUpDaysSpentLimit() {
return warmUpDaysSpentLimit;
}
public void setWarmUpDaysSpentLimit(Long warmUpDaysSpentLimit) {
this.warmUpDaysSpentLimit = warmUpDaysSpentLimit;
}
public BenchmarkReportConfig getBenchmarkReportConfig() {
return benchmarkReportConfig;
}
public void setBenchmarkReportConfig(BenchmarkReportConfig benchmarkReportConfig) {
this.benchmarkReportConfig = benchmarkReportConfig;
}
public SolverBenchmarkConfig getInheritedSolverBenchmarkConfig() {
return inheritedSolverBenchmarkConfig;
}
public void setInheritedSolverBenchmarkConfig(SolverBenchmarkConfig inheritedSolverBenchmarkConfig) {
this.inheritedSolverBenchmarkConfig = inheritedSolverBenchmarkConfig;
}
public List<SolverBenchmarkBluePrintConfig> getSolverBenchmarkBluePrintConfigList() {
return solverBenchmarkBluePrintConfigList;
}
public void setSolverBenchmarkBluePrintConfigList(List<SolverBenchmarkBluePrintConfig> solverBenchmarkBluePrintConfigList) {
this.solverBenchmarkBluePrintConfigList = solverBenchmarkBluePrintConfigList;
}
public List<SolverBenchmarkConfig> getSolverBenchmarkConfigList() {
return solverBenchmarkConfigList;
}
public void setSolverBenchmarkConfigList(List<SolverBenchmarkConfig> solverBenchmarkConfigList) {
this.solverBenchmarkConfigList = solverBenchmarkConfigList;
}
// ************************************************************************
// Builder methods
// ************************************************************************
public PlannerBenchmark buildPlannerBenchmark() {
return buildPlannerBenchmark(new SolverConfigContext());
}
public PlannerBenchmark buildPlannerBenchmark(SolverConfigContext solverConfigContext) {
validate();
generateSolverBenchmarkConfigNames();
List<SolverBenchmarkConfig> effectiveSolverBenchmarkConfigList = buildEffectiveSolverBenchmarkConfigList();
PlannerBenchmarkResult plannerBenchmarkResult = new PlannerBenchmarkResult();
plannerBenchmarkResult.setName(name);
plannerBenchmarkResult.setAggregation(false);
int parallelBenchmarkCount = resolveParallelBenchmarkCount();
plannerBenchmarkResult.setParallelBenchmarkCount(parallelBenchmarkCount);
plannerBenchmarkResult.setWarmUpTimeMillisSpentLimit(defaultIfNull(calculateWarmUpTimeMillisSpentLimit(), 30L));
plannerBenchmarkResult.setUnifiedProblemBenchmarkResultList(new ArrayList<>());
plannerBenchmarkResult.setSolverBenchmarkResultList(new ArrayList<>(
effectiveSolverBenchmarkConfigList.size()));
for (SolverBenchmarkConfig solverBenchmarkConfig : effectiveSolverBenchmarkConfigList) {
solverBenchmarkConfig.buildSolverBenchmark(solverConfigContext, plannerBenchmarkResult);
}
BenchmarkReportConfig benchmarkReportConfig_ = benchmarkReportConfig == null ? new BenchmarkReportConfig()
: benchmarkReportConfig;
BenchmarkReport benchmarkReport = benchmarkReportConfig_.buildBenchmarkReport(plannerBenchmarkResult);
return new DefaultPlannerBenchmark(
plannerBenchmarkResult, solverConfigContext, benchmarkDirectory,
buildExecutorService(parallelBenchmarkCount), buildExecutorService(parallelBenchmarkCount),
benchmarkReport);
}
private ExecutorService buildExecutorService(int parallelBenchmarkCount) {
ThreadFactory threadFactory;
if (threadFactoryClass != null) {
threadFactory = ConfigUtils.newInstance(this, "threadFactoryClass", threadFactoryClass);
} else {
threadFactory = new DefaultSolverThreadFactory("BenchmarkThread");
}
return Executors.newFixedThreadPool(parallelBenchmarkCount, threadFactory);
}
protected void validate() {
if (name != null) {
if (!PlannerBenchmarkConfig.VALID_NAME_PATTERN.matcher(name).matches()) {
throw new IllegalStateException("The plannerBenchmark name (" + name
+ ") is invalid because it does not follow the nameRegex ("
+ PlannerBenchmarkConfig.VALID_NAME_PATTERN.pattern() + ")" +
" which might cause an illegal filename.");
}
if (!name.trim().equals(name)) {
throw new IllegalStateException("The plannerBenchmark name (" + name
+ ") is invalid because it starts or ends with whitespace.");
}
}
if (ConfigUtils.isEmptyCollection(solverBenchmarkBluePrintConfigList)
&& ConfigUtils.isEmptyCollection(solverBenchmarkConfigList)) {
throw new IllegalArgumentException(
"Configure at least 1 <solverBenchmark> (or 1 <solverBenchmarkBluePrint>)"
+ " in the <plannerBenchmark> configuration.");
}
}
protected void generateSolverBenchmarkConfigNames() {
if (solverBenchmarkConfigList != null) {
Set<String> nameSet = new HashSet<>(solverBenchmarkConfigList.size());
Set<SolverBenchmarkConfig> noNameBenchmarkConfigSet = new LinkedHashSet<>(solverBenchmarkConfigList.size());
for (SolverBenchmarkConfig solverBenchmarkConfig : solverBenchmarkConfigList) {
if (solverBenchmarkConfig.getName() != null) {
boolean unique = nameSet.add(solverBenchmarkConfig.getName());
if (!unique) {
throw new IllegalStateException("The benchmark name (" + solverBenchmarkConfig.getName()
+ ") is used in more than 1 benchmark.");
}
} else {
noNameBenchmarkConfigSet.add(solverBenchmarkConfig);
}
}
int generatedNameIndex = 0;
for (SolverBenchmarkConfig solverBenchmarkConfig : noNameBenchmarkConfigSet) {
String generatedName = "Config_" + generatedNameIndex;
while (nameSet.contains(generatedName)) {
generatedNameIndex++;
generatedName = "Config_" + generatedNameIndex;
}
solverBenchmarkConfig.setName(generatedName);
generatedNameIndex++;
}
}
}
protected List<SolverBenchmarkConfig> buildEffectiveSolverBenchmarkConfigList() {
List<SolverBenchmarkConfig> effectiveSolverBenchmarkConfigList = new ArrayList<>(0);
if (solverBenchmarkConfigList != null) {
effectiveSolverBenchmarkConfigList.addAll(solverBenchmarkConfigList);
}
if (solverBenchmarkBluePrintConfigList != null) {
for (SolverBenchmarkBluePrintConfig solverBenchmarkBluePrintConfig : solverBenchmarkBluePrintConfigList) {
effectiveSolverBenchmarkConfigList.addAll(
solverBenchmarkBluePrintConfig.buildSolverBenchmarkConfigList());
}
}
if (inheritedSolverBenchmarkConfig != null) {
for (SolverBenchmarkConfig solverBenchmarkConfig : effectiveSolverBenchmarkConfigList) {
// Side effect: changes the unmarshalled solverBenchmarkConfig
solverBenchmarkConfig.inherit(inheritedSolverBenchmarkConfig);
}
}
return effectiveSolverBenchmarkConfigList;
}
protected int resolveParallelBenchmarkCount() {
int availableProcessorCount = Runtime.getRuntime().availableProcessors();
int resolvedParallelBenchmarkCount;
if (parallelBenchmarkCount == null) {
resolvedParallelBenchmarkCount = 1;
} else if (parallelBenchmarkCount.equals(PARALLEL_BENCHMARK_COUNT_AUTO)) {
resolvedParallelBenchmarkCount = resolveParallelBenchmarkCountAutomatically(availableProcessorCount);
} else {
resolvedParallelBenchmarkCount = ConfigUtils.resolveThreadPoolSizeScript(
"parallelBenchmarkCount", parallelBenchmarkCount, PARALLEL_BENCHMARK_COUNT_AUTO);
}
if (resolvedParallelBenchmarkCount < 1) {
throw new IllegalArgumentException("The parallelBenchmarkCount (" + parallelBenchmarkCount
+ ") resulted in a resolvedParallelBenchmarkCount (" + resolvedParallelBenchmarkCount
+ ") that is lower than 1.");
}
if (resolvedParallelBenchmarkCount > availableProcessorCount) {
logger.warn("Because the resolvedParallelBenchmarkCount (" + resolvedParallelBenchmarkCount
+ ") is higher than the availableProcessorCount (" + availableProcessorCount
+ "), it is reduced to availableProcessorCount.");
resolvedParallelBenchmarkCount = availableProcessorCount;
}
return resolvedParallelBenchmarkCount;
}
protected int resolveParallelBenchmarkCountAutomatically(int availableProcessorCount) {
// Tweaked based on experience
if (availableProcessorCount <= 2) {
return 1;
} else if (availableProcessorCount <= 4) {
return 2;
} else {
return (availableProcessorCount / 2) + 1;
}
}
protected Long calculateWarmUpTimeMillisSpentLimit() {
if (warmUpMillisecondsSpentLimit == null && warmUpSecondsSpentLimit == null
&& warmUpMinutesSpentLimit == null && warmUpHoursSpentLimit == null && warmUpDaysSpentLimit == null) {
return null;
}
long warmUpTimeMillisSpentLimit = 0L;
if (warmUpMillisecondsSpentLimit != null) {
if (warmUpMillisecondsSpentLimit < 0L) {
throw new IllegalArgumentException("The warmUpMillisecondsSpentLimit (" + warmUpMillisecondsSpentLimit
+ ") cannot be negative.");
}
warmUpTimeMillisSpentLimit += warmUpMillisecondsSpentLimit;
}
if (warmUpSecondsSpentLimit != null) {
if (warmUpSecondsSpentLimit < 0L) {
throw new IllegalArgumentException("The warmUpSecondsSpentLimit (" + warmUpSecondsSpentLimit
+ ") cannot be negative.");
}
warmUpTimeMillisSpentLimit += warmUpSecondsSpentLimit * 1_000L;
}
if (warmUpMinutesSpentLimit != null) {
if (warmUpMinutesSpentLimit < 0L) {
throw new IllegalArgumentException("The warmUpMinutesSpentLimit (" + warmUpMinutesSpentLimit
+ ") cannot be negative.");
}
warmUpTimeMillisSpentLimit += warmUpMinutesSpentLimit * 60_000L;
}
if (warmUpHoursSpentLimit != null) {
if (warmUpHoursSpentLimit < 0L) {
throw new IllegalArgumentException("The warmUpHoursSpentLimit (" + warmUpHoursSpentLimit
+ ") cannot be negative.");
}
warmUpTimeMillisSpentLimit += warmUpHoursSpentLimit * 3_600_000L;
}
if (warmUpDaysSpentLimit != null) {
if (warmUpDaysSpentLimit < 0L) {
throw new IllegalArgumentException("The warmUpDaysSpentLimit (" + warmUpDaysSpentLimit
+ ") cannot be negative.");
}
warmUpTimeMillisSpentLimit += warmUpDaysSpentLimit * 86_400_000L;
}
return warmUpTimeMillisSpentLimit;
}
}