package com.zillabyte.motherbrain.flow.local;
import java.util.Collection;
import java.util.List;
import org.apache.log4j.Logger;
import org.javatuples.Pair;
import com.google.common.collect.ArrayListMultimap;
import com.google.monitoring.runtime.instrumentation.common.com.google.common.collect.Lists;
import com.zillabyte.motherbrain.flow.App;
import com.zillabyte.motherbrain.flow.FlowInstance;
import com.zillabyte.motherbrain.flow.MapTuple;
import com.zillabyte.motherbrain.flow.graph.Connection;
import com.zillabyte.motherbrain.flow.graph.FlowGraph;
import com.zillabyte.motherbrain.flow.operations.Operation;
import com.zillabyte.motherbrain.utils.Utils;
public class LocalFlowController {
private FlowInstance _flowInstance;
private App _flow;
private ArrayListMultimap<String, LocalOperationSlot> _operationInstanceMap = ArrayListMultimap.create();
private final int _maxParallelism = 1;
private boolean _exitOnError = true;
private boolean _running = false;
private static Logger _log = Utils.getLogger(LocalFlowController.class);
public LocalFlowController(FlowInstance inst) {
_flowInstance = inst;
_flow = inst.flow();
}
public synchronized void start() {
buildSlots();
for(LocalOperationSlot o : _operationInstanceMap.values()) {
o.prepare();
}
for(LocalOperationSlot o : _operationInstanceMap.values()) {
o.start();
}
_running = true;
}
public synchronized void stop() {
_running = false;
for(LocalOperationSlot o : _operationInstanceMap.values()) {
o.stop();
}
_operationInstanceMap.clear();
}
public Collection<LocalOperationSlot> getSlotsFor(Operation op) {
return _operationInstanceMap.get(op.namespaceName());
}
public LocalOperationSlot getOneSlotFor(Operation op) {
List<LocalOperationSlot> c = _operationInstanceMap.get(op.namespaceName());
if (c.size() != 1) throw new IllegalStateException("unexpected number of slots");
return c.get(0);
}
public Collection<Pair<Connection, LocalOperationSlot>> getDownstreamSlots(LocalOperationSlot origin) {
List<Connection> cs = graph().connectionsFrom(origin.operation());
List<Pair<Connection, LocalOperationSlot>> slots = Lists.newLinkedList();
for(Connection c : cs) {
slots.add(new Pair<>(c, getOneSlotFor(c.dest())));
}
return slots;
}
public Collection<Pair<Connection, LocalOperationSlot>> getUpstreamSlots(LocalOperationSlot origin) {
List<Connection> cs = graph().connectionsTo(origin.operation());
List<Pair<Connection, LocalOperationSlot>> slots = Lists.newLinkedList();
for(Connection c : cs) {
slots.add(new Pair<>(c, getOneSlotFor(c.source())));
}
return slots;
}
public List<Integer> emitToStream(String streamName, MapTuple tuple, Integer sourceTask) {
// Find the operation this stream goes to..
List<Connection> connections = graph().getConnectionByStream(streamName);
if (connections == null || connections.isEmpty()) throw new RuntimeException("Stream not found: " + streamName);
List<Integer> ret = Lists.newLinkedList();
for(Connection connection : connections) {
String destOpId = connection.destId();
// Find all the instances of the dest..
List<LocalOperationSlot> slots = _operationInstanceMap.get(destOpId);
if (slots.size() == 0) throw new RuntimeException("no slots for operation: " + destOpId);
// How shall we route this tuple?
if (slots.size() == 1) {
LocalOperationSlot slot = slots.get(0);
slot.enqueueTuple(sourceTask, streamName, tuple);
ret.add(slot.task());
} else {
Utils.TODO("implement round-robin and hash-based routing");
}
}
return ret; // TODO: return list of tasks
}
private FlowGraph graph() {
return this._flow.graph();
}
private void buildSlots() {
// Build the slots...
Collection<Operation> ops = _flow.graph().allOperations();
int count = 0;
for(Operation original : ops) {
// Parallelism...
int parallelism = Math.min(_maxParallelism, original.getMaxParallelism());
original.setActualParallelism(parallelism);
// Clone it...
byte[] serialized = Utils.serialize(original);
// create an instance for each parallel
for (int i=0;i<parallelism;i++) {
Operation cloned = (Operation)Utils.deserialize(serialized);
LocalOperationSlot slot = new LocalOperationSlot(cloned, count++, this);
_operationInstanceMap.put(original.namespaceName(), slot);
}
}
}
public FlowInstance flowInstance() {
return this._flowInstance;
}
public Collection<LocalOperationSlot> allSlots() {
return this._operationInstanceMap.values();
}
void handleSlotError(LocalOperationSlot slot, Exception e) {
if (this._exitOnError) {
e.printStackTrace();
_log.error("Exiting because _exitOnError is set. Slot '" + slot + "' had exception: " + e.getMessage());
System.exit(1);
} else {
Utils.TODO("how to handle error?");
}
}
public LocalOperationSlot getSlotByTask(Integer task) {
for(LocalOperationSlot o : _operationInstanceMap.values()) {
if (o.task() == task) {
return o;
}
}
return null;
}
public void emitDirect(Integer sourceTask, Integer destTask, String stream, Object tuple) {
if (_running) {
LocalOperationSlot slot = getSlotByTask(destTask);
if (slot == null)
throw new NullPointerException("could not find slot " + destTask);
slot.enqueueTuple(sourceTask, stream, tuple);
} else {
// maybe we're just shutting down...
_log.warn("tuple emitted while slot is not running: " + tuple);
}
}
}