| /********************************************************************* |
| * 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."); |
| } |
| } |
| } |