blob: 49f6ddfb6151c1bf098ff8c7645bd839e0d4af51 [file] [log] [blame]
/*********************************************************************
* Copyright (c) 2019 The University of York.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipse.epsilon.executors;
import static org.eclipse.epsilon.common.util.OperatingSystem.getJavaVersion;
import static org.eclipse.epsilon.common.util.OperatingSystem.getOsNameAndVersion;
import static org.eclipse.epsilon.common.util.profiling.BenchmarkUtils.getCpuName;
import static org.eclipse.epsilon.common.util.profiling.BenchmarkUtils.getNumberOfHardwareThreads;
import static org.eclipse.epsilon.common.util.profiling.BenchmarkUtils.getTime;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import org.eclipse.epsilon.common.parse.problem.ParseProblem;
import org.eclipse.epsilon.eol.exceptions.EolRuntimeException;
import org.eclipse.epsilon.eol.models.IModel;
import org.eclipse.epsilon.eol.types.IToolNativeTypeDelegate;
import org.eclipse.epsilon.erl.dom.NamedRule;
import org.eclipse.epsilon.erl.execute.RuleProfiler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The Epsilon Executor is used to run the different Language executors.
*
* @author Horacio Hoyos Rodriguez
*
*/
public class EpsilonExecutor {
public static final ExecutionTimeData NO_TIME_DATA = new ExecutionTimeData();
private static final Logger logger = LoggerFactory.getLogger(EpsilonExecutor.class);
/**
* The Epsilon Module that implements the specific language engine.
*/
private final EpsilonLanguageExecutor<?> languageExecutor;
/**
* The Script to be executed. Alternatively a block of code can be provided, see {@link #code}
*/
private final Optional<Path> script;
/**
* The Code to be executed. Alternatively a script file can be provided, see {@link #script}.
*/
private final Optional<String> code;
/**
* The runtime parameters.
*/
private final Map<String, Object> parameters; // = new HashMap<>(4);
/**
* The Models used during execution.
*/
private final Set<IModel> models; // = new HashSet<>(4);
/**
* The Result of the execution. Not all models provide a result.
*/
//private Object result;
/**
* The Native Type delegates used during execution.
*/
private final Set<IToolNativeTypeDelegate> nativeDelegates; // = new HashSet<>(8);
/**
* The Dispose Models flag indicates if models will be disposed after execution.
*/
private final boolean disposeModels; // = true;
/**
* The Profile Execution flag indicates if the execution should be measured.
*/
private final boolean profileExecution; // = false;
private final Optional<ExecutionTimeData> timeData;
// Constructors with script
public EpsilonExecutor(
EpsilonLanguageExecutor<?> languageExecutor,
Path scriptPath) throws EpsilonExecutorException {
this(languageExecutor, scriptPath, Collections.emptySet(), Collections.emptyMap(), Collections.emptySet(), true, false);
}
public EpsilonExecutor(
EpsilonLanguageExecutor<?> languageExecutor,
Path scriptPath,
Collection<IModel> models) throws EpsilonExecutorException {
this(languageExecutor, scriptPath, models, Collections.emptyMap(), Collections.emptySet(), true, false);
}
public EpsilonExecutor(
EpsilonLanguageExecutor<?> languageExecutor,
Path scriptPath,
Collection<IModel> models,
Map<String, Object> parameters,
Collection<IToolNativeTypeDelegate> nativeDelegates) throws EpsilonExecutorException {
this(languageExecutor, scriptPath, models, parameters, nativeDelegates, true, false);
}
public EpsilonExecutor(
EpsilonLanguageExecutor<?> languageExecutor,
Path scriptPath,
Collection<IModel> models,
Map<String, Object> parameters,
Collection<IToolNativeTypeDelegate> nativeDelegates,
boolean disposeModels,
boolean profileExecution) throws EpsilonExecutorException {
super();
this.languageExecutor = languageExecutor;
this.script = Optional.of(scriptPath);
this.code = Optional.empty();
this.models = new HashSet<>(models);
this.parameters = parameters;
this.nativeDelegates = new HashSet<>(nativeDelegates);
this.disposeModels = disposeModels;
this.profileExecution = profileExecution;
this.timeData = profileExecution ? Optional.of(new ExecutionTimeData()) : Optional.empty();
parseSource();
}
// Constructors with code
public EpsilonExecutor(
EpsilonLanguageExecutor<?> languageExecutor,
String code) throws EpsilonExecutorException {
this(languageExecutor, code, Collections.emptySet(), Collections.emptyMap(), Collections.emptySet(), true, false);
}
public EpsilonExecutor(
EpsilonLanguageExecutor<?> languageExecutor,
String code,
Collection<IModel> models) throws EpsilonExecutorException {
this(languageExecutor, code, models, Collections.emptyMap(), Collections.emptySet(), true, false);
}
public EpsilonExecutor(
EpsilonLanguageExecutor<?> languageExecutor,
String code,
Collection<IModel> models,
Map<String, Object> parameters,
Collection<IToolNativeTypeDelegate> nativeDelegates) throws EpsilonExecutorException {
this(languageExecutor, code, models, parameters, nativeDelegates, true, false);
}
public EpsilonExecutor(
EpsilonLanguageExecutor<?> languageExecutor,
String code,
Collection<IModel> models,
Map<String, Object> parameters,
Collection<IToolNativeTypeDelegate> nativeDelegates,
boolean disposeModels,
boolean profileExecution) throws EpsilonExecutorException {
super();
this.languageExecutor = languageExecutor;
this.script = Optional.empty();
this.code = Optional.of(code);
parseSource();
this.models = new HashSet<>(models);
this.parameters = parameters;
this.nativeDelegates = new HashSet<>(nativeDelegates);
this.disposeModels = disposeModels;
this.profileExecution = profileExecution;
this.timeData = profileExecution ? Optional.of(new ExecutionTimeData()) : Optional.empty();
}
/**
* A class that keeps track of the execution durations of the different stages of execution, and
* about the execution of the scripts and its rules (if rule based language).
* @author Horacio Hoyos Rodriguez
*
*/
public static class ExecutionTimeData {
private static String logMessagesSeparator = "-----------------------------------------------------";
private final String osNameAndVersion;
private final String javaVersion;
private final String cpuName;
private final int logicalProcessors;
private final String date;
private Long startNanos;
/**
* The measured execution information.
*/
private Map<String, Duration> profiledStages = new HashMap<>();
private Map<String, Duration> profiledRules = new HashMap<>();
private Map<String, Long> started = new HashMap<>();
private Duration duration;
ExecutionTimeData() {
osNameAndVersion = getOsNameAndVersion();
javaVersion = getJavaVersion();
cpuName = getCpuName();
logicalProcessors = getNumberOfHardwareThreads();
date = getTime();
}
void logStart() {
startNanos = System.nanoTime();
logger.info(buildLines(
osNameAndVersion,
javaVersion,
cpuName,
"Logical processors: " + logicalProcessors,
"Starting execution at " + date,
logMessagesSeparator
));
}
void logEnd() {
long endTimeNanos = System.nanoTime();
this.duration = Duration.ofNanos(endTimeNanos-startNanos);
logger.info(buildLines("",
"Profiled processes:",
profiledStages.entrySet().stream().map(e -> String.format("%s:%s", e.getKey(), e.getValue())),
"Finished execution at " + getTime(),
logMessagesSeparator
));
logger.info(buildLines("",
"Profiled rules:",
profiledRules.entrySet().stream().map(e -> String.format("%s:%s", e.getKey(), e.getValue())),
logMessagesSeparator
));
}
String buildLines(Object... lines) {
StringBuilder linesAsStr = new StringBuilder();
String nL = System.lineSeparator();
for (Object line : lines) {
linesAsStr.append(line).append(nL);
}
return linesAsStr.toString();
}
void startStage(String name) {
started.put(name, System.nanoTime());
}
void endStage(String name) {
long endTimeNanos = System.nanoTime();
Long startTime = started.getOrDefault(name, endTimeNanos);
profiledStages.put(name, Duration.ofNanos(endTimeNanos-startTime));
}
void endModule(EpsilonLanguageExecutor<?> languageExecutor) {
Optional<RuleProfiler> ruleProfiler = languageExecutor.getRuleProfiler();
if(ruleProfiler.isPresent()) {
for (Entry<NamedRule, Duration> entry : ruleProfiler.get().getExecutionTimes().entrySet()) {
Duration oldValue = profiledRules.put(entry.getKey().getName(), entry.getValue());
if (oldValue != null) {
System.err.println("Value for rule " + entry.getKey().getName() + " was replaced.");
}
}
}
}
/**
* Return the duration of the "prepareExecution" stage. A negative value indicates that the
* stage was not signalled as finished.
* @return
*/
public Optional<Duration> getPrepareExecutionDuration() {
return Optional.ofNullable(profiledStages.get("prepareExecution"));
}
/**
* Return the duration of the "preProcess" stage. A negative value indicates that the
* stage was not signalled as finished.
* @return
*/
public Optional<Duration> getPreProcessDuration() {
return Optional.ofNullable(profiledStages.get("preProcess"));
}
/**
* Return the duration of the "postProcess" stage. A negative value indicates that the
* stage was not signalled as finished.
* @return
*/
public Optional<Duration> getPostProcessDuration() {
return Optional.ofNullable(profiledStages.get("postProcess"));
}
/**
* Return the duration of the Epsilon module execution. A negative value indicates that the
* stage was not signalled as finished.
* @return
*/
public Optional<Duration> getScrpitExecutionDuration() {
return Optional.ofNullable(profiledStages.get("execute"));
}
public Optional<Duration> getTotalDuration() {
return Optional.ofNullable(this.duration);
}
public Optional<Duration> getRuleDuration(String name) {
return Optional.ofNullable(profiledRules.get(name));
}
public Iterator<Entry<String, Duration>> getRulesDurations() {
return profiledRules.entrySet().iterator();
}
}
public Optional<ExecutionTimeData> getExecutionTimeData() {
return timeData;
}
/**
* Create a Runnable so the executor can be executed in a thread. Internally the
* {@link #invoqueExecutor()} is called. Any exceptions will be wrapped inside a
* {@link RuntimeException}.
*/
public <T> Runnable executeInThread() {
class OneShotTask implements Runnable {
private T result;
public void run() {
try {
result = invoqueExecutor();
} catch (EpsilonExecutorException e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unused")
public T getResult() { return result;}
}
return new OneShotTask();
}
/**
* Execute the specific executor by executing all the execution stages.
* @param executor
* @return
* @throws EpsilonExecutorException
*/
public <R> R invoqueExecutor() throws EpsilonExecutorException {
logger.info("Executing engine.");
preProfile();
prepareExecution();
logger.info("Pre-process execution");
timeData.ifPresent(td -> td.startStage("preProcess"));
languageExecutor.preProcess();
R result;
if (profileExecution) {
timeData.ifPresent(td -> td.endStage("preProcess"));
}
try {
if (profileExecution) {
timeData.ifPresent(td -> td.startStage("execute"));
}
result = (R) languageExecutor.execute();
}
catch (EolRuntimeException e) {
String msg = "Error executing the module.";
logger.error(msg, e);
throw new EpsilonExecutorException(msg, e);
}
finally {
if (profileExecution) {
timeData.ifPresent(td -> td.endStage("execute"));
}
}
logger.info("Post-process execution.");
if (profileExecution) {
timeData.ifPresent(td -> td.startStage("postProcess"));
}
languageExecutor.postProcess();
if (profileExecution) {
timeData.ifPresent(td -> td.endStage("postProcess"));
}
if (profileExecution) {
timeData.ifPresent(td -> td.endModule(languageExecutor));
timeData.ifPresent(td -> td.logEnd());
}
return result;
}
/**
* Disposes the executor. Implementing classes should perform any clean actions.
* This method should be invoked after execute.
* It is not invoked automatically because in some cases the user may need
* to access execution information that is still in the executor's context.
* The disposeModels flag determines if the models used by the module are also
* disposed (default true).
*/
public void dispose() {
if (disposeModels) {
logger.info("Disposing models");
languageExecutor.disposeModelRepository();
}
else {
logger.info("Removing models from context models");
languageExecutor.clearModelRepository();
}
models.clear();
parameters.clear();
logger.info("Dispose context");
languageExecutor.dispose();
}
private void preProfile() {
timeData.ifPresent(ExecutionTimeData::logStart);
}
private void prepareExecution() throws EpsilonExecutorException {
if (!(script.isPresent() || code.isPresent())) {
throw new EpsilonExecutorException("No script or code to execute");
}
timeData.ifPresent(ei -> ei.startStage("prepareExecution"));
logger.info("Adding models to executor");
languageExecutor.addModels(models);
logger.info("Adding parameters to context.");
languageExecutor.addParamters(parameters);
logger.info("Adding Native Type Delegates");
languageExecutor.addNativeTypeDelegates(nativeDelegates);
if (profileExecution) {
timeData.ifPresent(td -> td.endStage("prepareExecution"));
}
}
// public Object getResult() {
// return result;
//}
private void parseSource() throws EpsilonExecutorException {
// Think this should be part of constructor, so object is correct at construction
try {
if (script.isPresent()) {
languageExecutor.parse(script.get().toFile());
}
else {
languageExecutor.parse(code.get());
}
}
catch (Exception e) {
String culprit = script.isPresent() ? "script" : "code";
logger.error("Failed to parse provided {}", culprit, e);
throw new EpsilonExecutorException("Failed to parse script or code", e);
}
if (!languageExecutor.getParseProblems().isEmpty()) {
logger.error("Parse errors occurred");
System.err.println("Parse errors occurred...");
for (ParseProblem problem : languageExecutor.getParseProblems()) {
System.err.println(problem);
}
throw new EpsilonExecutorException("Parse errors occurred.");
}
}
}