/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.view.cycle;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.bp.Instant;
import com.opengamma.engine.cache.CacheSelectHint;
import com.opengamma.engine.cache.ViewComputationCache;
import com.opengamma.engine.calcnode.CalculationJob;
import com.opengamma.engine.calcnode.CalculationJobItem;
import com.opengamma.engine.calcnode.CalculationJobResult;
import com.opengamma.engine.calcnode.CalculationJobResultItem;
import com.opengamma.engine.calcnode.MutableExecutionLog;
import com.opengamma.engine.depgraph.DependencyGraph;
import com.opengamma.engine.exec.DefaultAggregatedExecutionLog;
import com.opengamma.engine.exec.DependencyGraphExecutionFuture;
import com.opengamma.engine.exec.DependencyGraphExecutor;
import com.opengamma.engine.exec.DependencyNodeJobExecutionResult;
import com.opengamma.engine.exec.DependencyNodeJobExecutionResultCache;
import com.opengamma.engine.function.FunctionDefinition;
import com.opengamma.engine.function.FunctionParameters;
import com.opengamma.engine.value.ComputedValueResult;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.engine.view.AggregatedExecutionLog;
import com.opengamma.engine.view.ExecutionLog;
import com.opengamma.engine.view.ExecutionLogMode;
import com.opengamma.engine.view.impl.ExecutionLogModeSource;
import com.opengamma.engine.view.impl.InMemoryViewComputationResultModel;
import com.opengamma.util.async.Cancelable;
import com.opengamma.util.log.LogLevel;
import com.opengamma.util.log.SimpleLogEvent;
import com.opengamma.util.tuple.Pair;
/**
* State required by {@link SingleComputationCycle} during its execution only.
* <p>
* An instance of this object, owned by the cycle, will only exist while the {@link SingleComputationCycle#execute} method is being called.
*/
/* package */class SingleComputationCycleExecutor implements DependencyGraphExecutionFuture.Listener {
private static final Logger s_logger = LoggerFactory.getLogger(SingleComputationCycleExecutor.class);
private abstract static class Event {
public abstract void run(SingleComputationCycleExecutor executorComputation);
}
private static class GraphExecutionComplete extends Event {
private final String _calculationConfiguration;
public GraphExecutionComplete(final String calculationConfiguration) {
_calculationConfiguration = calculationConfiguration;
}
@Override
public void run(final SingleComputationCycleExecutor executor) {
s_logger.info("Execution of {} complete", _calculationConfiguration);
final ExecutingCalculationConfiguration calcConfig = executor._executing.remove(_calculationConfiguration);
if (calcConfig != null) {
SingleComputationCycle cycle = executor.getCycle();
final InMemoryViewComputationResultModel fragmentResultModel = cycle.constructTemplateResultModel();
calcConfig.buildResults(fragmentResultModel, cycle.getResultModel());
// TODO: Populate with durations from the component jobs
fragmentResultModel.setCalculationTime(Instant.now());
cycle.notifyFragmentCompleted(fragmentResultModel);
}
}
}
private static class CalculationJobComplete extends Event {
private final CalculationJob _job;
private final CalculationJobResult _jobResult;
public CalculationJobComplete(final CalculationJob job, final CalculationJobResult jobResult) {
_job = job;
_jobResult = jobResult;
}
@Override
public void run(final SingleComputationCycleExecutor executor) {
s_logger.debug("Execution of {} complete", _job);
executor.buildResults(_job, _jobResult);
}
}
private static class ExecutingCalculationConfiguration {
private final Cancelable _handle;
private final DependencyGraph _graph;
private final DependencyNodeJobExecutionResultCache _resultCache;
private final ViewComputationCache _computationCache;
private final Set<ValueSpecification> _terminalOutputs = new HashSet<ValueSpecification>();
public ExecutingCalculationConfiguration(final SingleComputationCycle cycle, final DependencyGraph graph, final Cancelable handle) {
_handle = handle;
_graph = graph;
_resultCache = cycle.getJobExecutionResultCache(graph.getCalculationConfigurationName());
_computationCache = cycle.getComputationCache(graph.getCalculationConfigurationName());
}
public void cancel() {
_handle.cancel(true);
}
public DependencyGraph getDependencyGraph() {
return _graph;
}
public DependencyNodeJobExecutionResultCache getResultCache() {
return _resultCache;
}
public Set<ValueSpecification> getTerminalOutputs() {
return _terminalOutputs;
}
public void buildResults(final InMemoryViewComputationResultModel fragmentResultModel, final InMemoryViewComputationResultModel fullResultModel) {
if (_terminalOutputs.isEmpty()) {
return;
}
final String calculationConfiguration = _graph.getCalculationConfigurationName();
for (Pair<ValueSpecification, Object> value : _computationCache.getValues(_terminalOutputs, CacheSelectHint.allShared())) {
final ValueSpecification valueSpec = value.getFirst();
final Object calculatedValue = value.getSecond();
if (calculatedValue != null) {
final ComputedValueResult computedValueResult = SingleComputationCycle.createComputedValueResult(valueSpec, calculatedValue, _resultCache.get(valueSpec));
fragmentResultModel.addValue(calculationConfiguration, computedValueResult);
fullResultModel.addValue(calculationConfiguration, computedValueResult);
}
}
_terminalOutputs.clear();
}
}
private final BlockingQueue<Event> _events = new LinkedBlockingQueue<Event>();
private final Map<String, ExecutingCalculationConfiguration> _executing = new HashMap<String, ExecutingCalculationConfiguration>();
private final SingleComputationCycle _cycle;
private boolean _issueFragmentResults;
public SingleComputationCycleExecutor(final SingleComputationCycle cycle) {
_cycle = cycle;
}
private SingleComputationCycle getCycle() {
return _cycle;
}
public void execute() throws InterruptedException {
final DependencyGraphExecutor executor = getCycle().getViewProcessContext().getDependencyGraphExecutorFactory().createExecutor(getCycle());
for (final String calcConfigurationName : getCycle().getAllCalculationConfigurationNames()) {
s_logger.info("Executing plans for calculation configuration {}", calcConfigurationName);
final DependencyGraph depGraph = getCycle().getDependencyGraph(calcConfigurationName);
final Set<ValueSpecification> sharedData = getCycle().getSharedValues(calcConfigurationName);
final Map<ValueSpecification, FunctionParameters> parameters = getCycle().createFunctionParameters(calcConfigurationName);
s_logger.info("Submitting {} for execution by {}", depGraph, executor);
final DependencyGraphExecutionFuture future = executor.execute(depGraph, sharedData, parameters);
_executing.put(calcConfigurationName, new ExecutingCalculationConfiguration(getCycle(), depGraph, future));
future.setListener(this);
}
try {
while (!_executing.isEmpty()) {
// Block for the first event
_events.take().run(this);
// Then run through any others as quickly as possible before dispatching a notification
Event e = _events.poll();
while (e != null) {
e.run(this);
e = _events.poll();
}
if (_issueFragmentResults) {
if (_executing.isEmpty()) {
s_logger.info("Discarding fragment completion message - overall execution is complete");
} else {
s_logger.debug("Building result fragment");
final InMemoryViewComputationResultModel fragmentResultModel = getCycle().constructTemplateResultModel();
final InMemoryViewComputationResultModel fullResultModel = getCycle().getResultModel();
for (ExecutingCalculationConfiguration calcConfig : _executing.values()) {
calcConfig.buildResults(fragmentResultModel, fullResultModel);
}
s_logger.info("Fragment execution complete");
// TODO: Populate the calculation duration with information from the component jobs
fragmentResultModel.setCalculationTime(Instant.now());
getCycle().notifyFragmentCompleted(fragmentResultModel);
}
_issueFragmentResults = false;
}
}
} catch (final InterruptedException e) {
Thread.interrupted();
// Cancel all outstanding jobs to free up resources
for (ExecutingCalculationConfiguration execution : _executing.values()) {
execution.cancel();
}
throw e;
}
}
private String toString(final String prefix, final Collection<?> values) {
final StringBuilder sb = new StringBuilder(prefix);
if (values.size() > 1) {
sb.append("s - { ");
int count = 0;
for (Object value : values) {
count++;
if (count > 1) {
sb.append(", ");
if (count > 10) {
sb.append("...");
break;
}
}
sb.append(value.toString());
}
sb.append(" }");
} else {
sb.append(" - ").append(values.iterator().next().toString());
}
return sb.toString();
}
private void buildResults(final CalculationJob job, final CalculationJobResult jobResult) {
final String calculationConfiguration = jobResult.getSpecification().getCalcConfigName();
final ExecutingCalculationConfiguration calcConfig = _executing.get(calculationConfiguration);
if (calcConfig == null) {
s_logger.warn("Job fragment result for already completed configuration {}", calculationConfiguration);
return;
}
final DependencyGraph graph = calcConfig.getDependencyGraph();
final Map<ValueSpecification, ?> graphTerminalOutputs = graph.getTerminalOutputs();
final Iterator<CalculationJobItem> jobItemItr = job.getJobItems().iterator();
final Iterator<CalculationJobResultItem> jobResultItr = jobResult.getResultItems().iterator();
final ExecutionLogModeSource logModes = getCycle().getLogModeSource();
final DependencyNodeJobExecutionResultCache jobExecutionResultCache = calcConfig.getResultCache();
final Set<ValueSpecification> executedTerminalOutputs = calcConfig.getTerminalOutputs();
final String computeNodeId = jobResult.getComputeNodeId();
while (jobItemItr.hasNext()) {
assert jobResultItr.hasNext();
final CalculationJobItem jobItem = jobItemItr.next();
final CalculationJobResultItem jobResultItem = jobResultItr.next();
// Process the streamed result fragment
final ExecutionLogMode executionLogMode = logModes.getLogMode(calculationConfiguration, jobItem.getOutputs()[0]);
final AggregatedExecutionLog aggregatedExecutionLog;
if (executionLogMode == ExecutionLogMode.FULL) {
final ExecutionLog log = jobResultItem.getExecutionLog();
MutableExecutionLog logCopy = null;
final Set<AggregatedExecutionLog> inputLogs = new LinkedHashSet<AggregatedExecutionLog>();
Set<ValueSpecification> missing = jobResultItem.getMissingInputs();
if (!missing.isEmpty()) {
logCopy = new MutableExecutionLog(log);
logCopy.add(new SimpleLogEvent(log.hasException() ? LogLevel.WARN : LogLevel.INFO, toString("Missing input", missing)));
}
missing = jobResultItem.getMissingOutputs();
if (!missing.isEmpty()) {
if (logCopy == null) {
logCopy = new MutableExecutionLog(log);
}
logCopy.add(new SimpleLogEvent(LogLevel.WARN, toString("Failed to produce output", missing)));
}
for (final ValueSpecification inputValueSpec : jobItem.getInputs()) {
final DependencyNodeJobExecutionResult nodeResult = jobExecutionResultCache.get(inputValueSpec);
if (nodeResult == null) {
// Market data
continue;
}
inputLogs.add(nodeResult.getAggregatedExecutionLog());
}
final String functionName;
final FunctionDefinition function = getCycle().getViewProcessContext().getFunctionResolver().getFunction(jobItem.getFunctionUniqueIdentifier());
if (function != null) {
functionName = function.getShortName();
} else {
functionName = jobItem.getFunctionUniqueIdentifier();
}
aggregatedExecutionLog = DefaultAggregatedExecutionLog.fullLogMode(functionName, jobItem.getComputationTargetSpecification(), (logCopy != null) ? logCopy : log, inputLogs);
} else {
EnumSet<LogLevel> logs = jobResultItem.getExecutionLog().getLogLevels();
boolean copied = false;
for (final ValueSpecification inputValueSpec : jobItem.getInputs()) {
final DependencyNodeJobExecutionResult nodeResult = jobExecutionResultCache.get(inputValueSpec);
if (nodeResult == null) {
// Market data
continue;
}
if (logs.containsAll(nodeResult.getAggregatedExecutionLog().getLogLevels())) {
continue;
}
if (!copied) {
copied = true;
logs = EnumSet.copyOf(logs);
}
logs.addAll(nodeResult.getAggregatedExecutionLog().getLogLevels());
}
aggregatedExecutionLog = DefaultAggregatedExecutionLog.indicatorLogMode(logs);
}
final DependencyNodeJobExecutionResult jobExecutionResult = new DependencyNodeJobExecutionResult(computeNodeId, jobResultItem, aggregatedExecutionLog);
for (final ValueSpecification outputValueSpec : jobItem.getOutputs()) {
if (graphTerminalOutputs.containsKey(outputValueSpec)) {
executedTerminalOutputs.add(outputValueSpec);
}
jobExecutionResultCache.put(outputValueSpec, jobExecutionResult);
}
}
_issueFragmentResults |= !executedTerminalOutputs.isEmpty();
}
/**
* Receives a job result fragment. These will be streamed in by the execution framework. Only one notification per job will be received (for example the execution framework might have
* repeated/duplicated jobs to handle node failures).
*
* @param job the job that was executed, not null
* @param jobResult the job result, not null
*/
public void jobCompleted(final CalculationJob job, final CalculationJobResult jobResult) {
_events.add(new CalculationJobComplete(job, jobResult));
}
@Override
public void graphCompleted(final String calculationConfiguration) {
_events.add(new GraphExecutionComplete(calculationConfiguration));
}
}