package archimulator.common;
import archimulator.util.dateTime.DateHelper;
import archimulator.util.serialization.JsonSerializationHelper;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import java.io.File;
import java.io.IOException;
import java.util.*;
/**
* Experiment.
*
* @author Min Cai
*/
public abstract class Experiment<ExperimentConfigT> {
private long createTime;
private ExperimentConfigT config;
private ExperimentState state;
private String failedReason;
private List<ExperimentStat> stats;
private Map<String, Object> statsMap;
/**
* Create an experiment.
*/
public Experiment(ExperimentConfigT config) {
this.config = config;
this.state = ExperimentState.PENDING;
this.failedReason = "";
this.createTime = DateHelper.toTick(new Date());
this.stats = new ArrayList<>();
}
/**
* Run.
*/
public void run() {
try {
simulate();
this.setState(ExperimentState.COMPLETED);
this.setFailedReason("");
} catch (Exception e) {
this.setState(ExperimentState.ABORTED);
this.setFailedReason(ExceptionUtils.getStackTrace(e));
e.printStackTrace();
}
if (this.getState() == ExperimentState.COMPLETED) {
File resultDirFile = new File(this.getOutputDirectory());
if (!resultDirFile.exists()) {
if (!resultDirFile.mkdirs()) {
throw new RuntimeException();
}
}
JsonSerializationHelper.writeJsonFile(this.getConfig(), this.getOutputDirectory(), "config.json");
JsonSerializationHelper.writeJsonFile(this.getStatsMap(), this.getOutputDirectory(), "stats.json");
}
}
/**
* Simulate.
*/
protected abstract void simulate();
/**
* Get the time in ticks when the experiment is created.
*
* @return the time in ticks when the experiment is created
*/
public long getCreateTime() {
return createTime;
}
/**
* Get the string representation of the time when the experiment is created.
*
* @return the string representation of the time when the experiment is created
*/
public String getCreateTimeAsString() {
return DateHelper.toString(createTime);
}
/**
* Get the experiment config.
*
* @return the experiment config
*/
public ExperimentConfigT getConfig() {
return config;
}
/**
* Get the experiment state.
*
* @return the experiment state
*/
public ExperimentState getState() {
return state;
}
/**
* Set the experiment state.
*
* @param state the experiment state
*/
public void setState(ExperimentState state) {
this.state = state;
}
/**
* Get the failed reason, being empty if the experiment is not failed at all.
*
* @return the failed reason
*/
public String getFailedReason() {
return failedReason;
}
/**
* Set the failed reason, being empty if the experiment is not failed at all.
*
* @param failedReason the failed reason
*/
public void setFailedReason(String failedReason) {
this.failedReason = failedReason;
}
/**
* Get the in-memory list of statistics.
*
* @return the in-memory list of statistics
*/
public List<ExperimentStat> getStats() {
return stats;
}
/**
* Get the map of statistics.
*
* @return the map of statistics
*/
public Map<String, Object> getStatsMap() {
if (statsMap != null) {
return statsMap;
} else {
Map<String, Object> result = new LinkedHashMap<>();
for(ExperimentStat stat : stats) {
result.put((stat.getPrefix() == null || stat.getPrefix().isEmpty() ? "" : stat.getPrefix() + "/") + stat.getKey(), stat.getValue());
}
return result;
}
}
/**
* Set the in-memory list of statistics.
*
* @param stats the in-memory list of statistics
*/
public void setStats(List<ExperimentStat> stats) {
this.stats = stats;
}
/**
* Get a value indicating whether the experiment is stopped or not.
*
* @return a value indicating whether the experiment is stopped or not
*/
public boolean isStopped() {
return this.state == ExperimentState.COMPLETED || this.state == ExperimentState.ABORTED;
}
/**
* Get the output directory.
*
* @return the output directory
*/
protected abstract String getOutputDirectory();
/**
* Load statistics.
*/
@SuppressWarnings("unchecked")
public void loadStats() {
File file = new File(getOutputDirectory(), "stats.json");
try {
String json = FileUtils.readFileToString(file);
statsMap = JsonSerializationHelper.fromJson(Map.class, json);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Run the specified list of experiments.
*
* @param experiments the list of experiments
* @param parallel a boolean value indicating whether runs the experiments in parallel or not
*/
public static void runExperiments(List<? extends Experiment> experiments, boolean parallel) {
if(parallel) {
experiments.parallelStream().forEach(Experiment::run);
} else {
experiments.forEach(Experiment::run);
}
}
}