io.rouz.flo.context.InstrumentedContext Maven / Gradle / Ivy
package io.rouz.flo.context;
import java.util.Objects;
import io.rouz.flo.Fn;
import io.rouz.flo.Task;
import io.rouz.flo.TaskContext;
import io.rouz.flo.TaskId;
/**
* A {@link TaskContext} that instruments the task expansion and invocation process.
*
* This context will invoke methods on a {@link Listener} when tasks are evaluated through the
* {@link #evaluate(Task)} method.
*
*
The {@link Listener#edge(TaskId, TaskId)} method is called for each discovered
* upstream-downstream relation between two tasks that are about to be evaluated. This call is
* always made before the upstream is evaluated.
*
*
The {@link Listener#status(TaskId, Listener.Phase)} method is called when a task is actually
* being processed, i.e. when the {@link #invokeProcessFn(TaskId, Fn)} of that task in called in
* the {@link TaskContext}. There will be at most two calls made for
* each task. First with {@link Listener.Phase#START}, when evaluation starts. Then with either
* {@link Listener.Phase#SUCCESS} or {@link Listener.Phase#FAILURE} depending on the success or
* failure of the task {@link TaskContext.Value}.
*
*/
public class InstrumentedContext extends ForwardingTaskContext {
/**
* A listener for instrumented evaluation. See {@link InstrumentedContext} for more details.
*/
public interface Listener {
/**
* Called when an edge (dependency) between two tasks are discovered. The relation is that
* the downstream task depends on the upstream task. Also that the upstream task has to be
* evaluated first, before the downstream task can be evaluated.
*
* @param upstream The id of the upstream task
* @param downstream The id of the downstream task
*/
void edge(TaskId upstream, TaskId downstream);
/**
* Called when a task starts and finished it's evaluation. This will be called exactly twice
* per evaluation of a task.
*
* @param task The task that is being evaluated
* @param phase The phase of evaluation
*/
void status(TaskId task, Phase phase);
/**
* The different phases of task evaluation.
*/
enum Phase {
/**
* The task has started evaluating
*/
START,
/**
* The task completed evaluating successfully
*/
SUCCESS,
/**
* The task completed evaluating with a failure
*/
FAILURE
}
}
private final Listener listener;
private InstrumentedContext(TaskContext baseContext, Listener listener) {
super(baseContext);
this.listener = Objects.requireNonNull(listener);
}
public static TaskContext composeWith(TaskContext baseContext, Listener listener) {
return new InstrumentedContext(baseContext, listener);
}
@Override
public Value evaluateInternal(Task task, TaskContext context) {
task.inputs().stream()
.map(Task::id)
.distinct()
.forEach(upstream -> listener.edge(upstream, task.id()));
return delegate.evaluateInternal(task, context);
}
@Override
public Value invokeProcessFn(TaskId taskId, Fn> processFn) {
listener.status(taskId, Listener.Phase.START);
final Value tValue = delegate.invokeProcessFn(taskId, processFn);
tValue.consume(v -> listener.status(taskId, Listener.Phase.SUCCESS));
tValue.onFail(t -> listener.status(taskId, Listener.Phase.FAILURE));
return tValue;
}
}