package water.util;
import java.net.InetAddress;
import java.util.*;
import water.*;
/**
* Wrapper around timeline snapshot. Implements iterator interface (events are
* ordered according to send/receive dependencies across the nodes and trivial time
* dependencies inside node)
*
* @author tomas
*/
public final class TimelineSnapshot implements
Iterable<TimelineSnapshot.Event>, Iterator<TimelineSnapshot.Event> {
final long[][] _snapshot;
final Event[] _events;
final HashMap<Event, Event> _edges;
final public HashMap<Event, ArrayList<Event>> _sends;
final H2O _cloud;
boolean _processed;
public TimelineSnapshot(H2O cloud, long[][] snapshot) {
_cloud = cloud;
_snapshot = snapshot;
_edges = new HashMap<Event, Event>();
_sends = new HashMap<Event, ArrayList<Event>>();
_events = new Event[snapshot.length];
// DEBUG: print out the event stack as we got it
// System.out.println("# of nodes: " + _events.length);
// for (int j = 0; j < TimeLine.length(); ++j) {
// System.out.print("row# " + j + ":");
// for (int i = 0; i < _events.length; ++i) {
// System.out.print(" || " + new Event(i, j));
// }
// System.out.println(" ||");
// }
for (int i = 0; i < _events.length; ++i) {
// For a new Snapshot, most of initial entries are all zeros. Skip them
// until we start finding entries... which will be the oldest entries.
// The timeline is age-ordered (per-thread, we hope the threads are
// fairly consistent)
_events[i] = new Event(i, 0);
if (_events[i].isEmpty()) {
if (!_events[i].next())
_events[i] = null;
}
if (_events[i] != null)
processEvent(_events[i]);
assert (_events[i] == null) || (_events[i]._eventIdx < TimeLine.MAX_EVENTS);
}
// now build the graph (i.e. go through all the events once)
for (@SuppressWarnings("unused") Event e : this) ;
_processed = true;
for (int i = 0; i < _events.length; ++i) {
// For a new Snapshot, most of initial entries are all zeros. Skip them
// until we start finding entries... which will be the oldest entries.
// The timeline is age-ordered (per-thread, we hope the threads are
// fairly consistent)
_events[i] = new Event(i, 0);
if (_events[i].isEmpty()) {
if (!_events[i].next())
_events[i] = null;
}
assert (_events[i] == null) || (_events[i]._eventIdx < TimeLine.MAX_EVENTS);
}
}
// convenience wrapper around event stored in snapshot
// contains methods to access event data, move to the next previous event
// and to test whether two events form valid sender/receiver pair
//
// it is also needed to keep track of send/recv dependencies when iterating
// over events in timeline
public class Event {
public final int _nodeId; // Which node/column# in the snapshot
final long[] _val; // The column from the snapshot
int _eventIdx; // Which row in the snapshot
// For send-packets, the column# is the cloud-wide idx of the sender, and
// the packet contains the reciever. Vice-versa for received packets,
// where the column# is the cloud-wide idx of the receiver, and the packet
// contains the sender.
H2ONode _packh2o; // The H2O in the packet
boolean _blocked;
public Event(int nodeId, int eventIdx) {
_nodeId = nodeId;
_eventIdx = eventIdx;
_val = _snapshot[nodeId];
computeH2O(false);
}
@Override public final int hashCode() { return (_nodeId <<10)^_eventIdx; }
@Override public final boolean equals(Object o) {
Event e = (Event)o;
return _nodeId==e._nodeId && _eventIdx==e._eventIdx;
}
// (re)compute the correct H2ONode, if the _eventIdx changes.
private boolean computeH2O(boolean b) {
H2ONode h2o = null;
if( dataLo() != 0 ) { // Dead/initial packet
InetAddress inet = addrPack();
if( !inet.isMulticastAddress() ) { // Is multicast?
h2o = H2ONode.intern(inet,portPack());
if( isSend() && h2o == recoH2O() ) // Another multicast indicator: sending to self
h2o = null; // Flag as multicast
}
}
_packh2o = h2o;
return b; // For flow-coding
}
public final int send_recv() { return TimeLine.send_recv(_val, _eventIdx); }
public final int dropped () { return TimeLine.dropped (_val, _eventIdx); }
public final boolean isSend() { return send_recv() == 0; }
public final boolean isRecv() { return send_recv() == 1; }
public final boolean isDropped() { return dropped() != 0; }
public final InetAddress addrPack() { return TimeLine.inet(_val, _eventIdx); }
public final long dataLo() { return TimeLine.l0(_val, _eventIdx); }
public final long dataHi() { return TimeLine.l8(_val, _eventIdx); }
public final long ns() { return TimeLine.ns(_val, _eventIdx); }
public final boolean isTCP(){return (ns() & 4) != 0;}
public final long ms() { return TimeLine.ms(_val, _eventIdx) + recoH2O()._heartbeat._jvm_boot_msec; }
public H2ONode packH2O() { return _packh2o; } // H2O in packet
public H2ONode recoH2O() { return _cloud._memary[_nodeId]; } // H2O recording packet
public final int portPack() {
int i = (int) dataLo();
// 1st byte is UDP type, so shift right by 8.
// Next 2 bytes are UDP port #, so mask by 0xFFFF.
return ((0xFFFF) & (i >> 8));
}
public final String addrString() { return _packh2o==null ? "multicast" : _packh2o.toString(); }
public final String ioflavor() {
int flavor = is_io();
return flavor == -1 ? (isTCP()?"TCP":"UDP") : Value.nameOfPersist(flavor);
}
public final int is_io() {
int udp_type = (int) (dataLo() & 0xff); // First byte is UDP packet type
return UDP.udp.i_o.ordinal() == udp_type ? (int)((dataLo()>>24)&0xFF) : -1;
}
// ms doing I/O
public final int ms_io() { return (int)(dataLo()>>32); }
public final int size_io() { return (int)dataHi(); }
public String toString() {
int udp_type = (int) (dataLo() & 0xff); // First byte is UDP packet type
UDP.udp udpType = UDP.getUdp(udp_type);
String operation = isSend() ? " SEND " : " RECV ";
String host1 = addrString();
String host2 = recoH2O().toString();
String networkPart = isSend()
? (host2 + " -> " + host1)
: (host1 + " -> " + host2);
return "Node(" + _nodeId + ": " + ns() + ") " + udpType.toString()
+ operation + networkPart + (isDropped()?" DROPPED ":"") + ", data = '"
+ Long.toHexString(this.dataLo()) + ','
+ Long.toHexString(this.dataHi()) + "'";
}
/**
* Check if two events form valid sender/receiver pair.
*
* Two events are valid sender/receiver pair iff the ports, adresses and
* payload match.
*
* @param other
* @return true iff the two events form valid sender/receiver pair
*/
final boolean match(Event ev) {
// check we're matching send and receive
if (send_recv() == ev.send_recv())
return false;
// compare the packet payload matches
long myl0 = dataLo();
long evl0 = ev.dataLo();
int my_udp_type = (int) (myl0 & 0xff); // first byte is udp type
int ev_udp_type = (int) (evl0 & 0xff); // first byte is udp type
if (my_udp_type != ev_udp_type)
return false;
UDP.udp e = UDP.getUdp(my_udp_type);
switch (e) {
case rebooted:
case timeline:
// compare only first 3 bytes here (udp type and port),
// but port# is checked below as part of address
break;
case ack:
case ackack:
case fetchack:
case exec:
case heartbeat:
// compare 3 ctrl bytes + 4 bytes task #
// if ((myl0 & 0xFFFFFFFFFFFFFFl) != (evl0 & 0xFFFFFFFFFFFFFFl))
if( (int)(myl0>>24) != (int)(evl0>>24))
return false;
break;
case i_o: // Shows up as I/O-completing recorded packets
return false;
default:
throw new RuntimeException("unexpected udp packet type " + e.toString());
}
// Check that port numbers are compatible. Really check that the
// H2ONode's are compatible. The port#'s got flipped during recording to
// allow this check (and a null _packh2o is a multicast).
if( _packh2o!=null && _packh2o.index()!=ev._nodeId ) return false;
if( ev._packh2o!=null && ev._packh2o.index()!= _nodeId ) return false;
return true;
}
public final boolean isEmpty() {
return (_eventIdx < TimeLine.length()) ? TimeLine.isEmpty(_val, _eventIdx) : false;
}
public final Event clone() {
return new Event(_nodeId, _eventIdx);
}
boolean prev(int minIdx) {
int min = Math.max(minIdx, -1);
if (_eventIdx <= minIdx)
return false;
while (--_eventIdx > min)
if (!isEmpty())
return computeH2O(true);
return computeH2O(false);
}
boolean prev() {
return prev(-1);
}
Event previousEvent(int minIdx) {
Event res = new Event(_nodeId, _eventIdx);
return (res.prev(minIdx)) ? res : null;
}
Event previousEvent() {
return previousEvent(-1);
}
boolean next(int maxIdx) {
int max = Math.min(maxIdx, TimeLine.length());
if (_eventIdx >= max)
return false;
while (++_eventIdx < max)
if (!isEmpty())
return computeH2O(true);
return computeH2O(false);
}
boolean next() {
return next(TimeLine.length());
}
Event nextEvent(int maxIdx) {
Event res = new Event(_nodeId, _eventIdx);
return (res.next(maxIdx)) ? res : null;
}
Event nextEvent() {
return nextEvent(TimeLine.length());
}
/**
* Used to determine ordering of events not bound by any dependency.
*
* Events compared according to following rules:
* Receives go before sends. Since we are only here with unbound events,
* unbound receives means their sender has already appeared and they
* should go adjacent to their sender.
* For two sends, pick the one with receives with smallest timestamp (ms)
* otherwise pick the sender with smallest timestamp (ms)
*
* @param ev other Event to compare
* @return
*/
public final int compareTo(Event ev) {
if( ev == null ) return -1;
if( ev == this ) return 0;
if( ev.equals(this) ) return 0;
int res = ev.send_recv() - send_recv(); // recvs should go before sends
if( res != 0 ) return res;
if (isSend()) {
// compare by the time of receivers
long myMinMs = Long.MAX_VALUE;
long evMinMs = Long.MAX_VALUE;
ArrayList<Event> myRecvs = _sends.get(this);
ArrayList<Event> evRecvs = _sends.get(ev );
for (Event e : myRecvs)
if (e.ms() < myMinMs)
myMinMs = e.ms();
for (Event e : evRecvs)
if (e.ms() < evMinMs)
evMinMs = e.ms();
res = (int) (myMinMs - evMinMs);
if( myMinMs == Long.MAX_VALUE && evMinMs != Long.MAX_VALUE ) res = -1;
if( myMinMs != Long.MAX_VALUE && evMinMs == Long.MAX_VALUE ) res = 1;
}
if (res == 0)
res = (int) (ms() - ev.ms());
if( res == 0 )
res = (int) (ns() - ev.ns());
return res;
}
}
/**
* Check whether two events can be put together in sender/recv relationship.
*
* Events must match, also each sender can have only one receiver per node.
*
* @param senderCnd
* @param recvCnd
* @return
*/
private boolean isSenderRecvPair(Event senderCnd, Event recvCnd) {
if (senderCnd.isSend() && recvCnd.isRecv() && senderCnd.match(recvCnd)) {
ArrayList<Event> recvs = _sends.get(senderCnd);
if (recvs.isEmpty() || senderCnd.packH2O()==null ) {
for (Event e : recvs)
if (e._nodeId == recvCnd._nodeId)
return false;
return true;
}
}
return false;
}
/**
* Process new event. For sender, check if there are any blocked receives
* waiting for this send. For receiver, try to find matching sender, otherwise
* block.
*
* @param idx
*/
void processEvent(Event e) {
assert !_processed;
// Event e = _events[idx];
if (e.isSend()) {
_sends.put(e, new ArrayList<TimelineSnapshot.Event>());
for (Event otherE : _events) {
if ((otherE != null) && (otherE != e) && (!otherE.equals(e)) && otherE._blocked
&& otherE.match(e)) {
_edges.put(otherE, e);
_sends.get(e).add(otherE);
otherE._blocked = false;
}
}
} else { // look for matching send, otherwise set _blocked
assert !_edges.containsKey(e);
int senderIdx = e.packH2O().index();
if (senderIdx < 0) { // binary search did not find member, should not happen?
// no possible sender - return and do not block
Log.warn("no sender found! port = " + e.portPack() + ", ip = " + e.addrPack().toString());
return;
}
Event senderCnd = _events[senderIdx];
if (senderCnd != null) {
if (isSenderRecvPair(senderCnd, e)) {
_edges.put(e, senderCnd.clone());
_sends.get(senderCnd).add(e);
return;
}
senderCnd = senderCnd.clone();
while (senderCnd.prev()) {
if (isSenderRecvPair(senderCnd, e)) {
_edges.put(e, senderCnd);
_sends.get(senderCnd).add(e);
return;
}
}
}
e._blocked = true;
}
assert (e == null) || (e._eventIdx < TimeLine.MAX_EVENTS);
}
@Override
public Iterator<TimelineSnapshot.Event> iterator() {
return this;
}
/**
* Just check if there is any non null non-issued event.
*/
@Override
public boolean hasNext() {
for (int i = 0; i < _events.length; ++i)
if (_events[i] != null && (!_events[i].isEmpty() || _events[i].next())) {
assert (_events[i] == null)
|| ((_events[i]._eventIdx < TimeLine.MAX_EVENTS) && !_events[i].isEmpty());
return true;
} else {
assert (_events[i] == null)
|| ((_events[i]._eventIdx < TimeLine.MAX_EVENTS) && !_events[i].isEmpty());
_events[i] = null;
}
return false;
}
public Event getDependency(Event e) {
return _edges.get(e);
}
/**
* Get the next event of the timeline according to the ordering. Ordering is
* performed in this method. Basically there are n ordered stream of events
* with possible dependenencies caused by send/rcv relation.
*
* Sends are always eligible to be scheduled. Receives are eligible only if
* their matching send was already issued. In situation when current events of
* all streams are blocked (should not happen!) the oldest one is unblocked
* and issued.
*
* Out of all eligible events, the smallest one (according to Event.compareTo)
* is picked.
*/
@Override
public TimelineSnapshot.Event next() {
if (!hasNext())
throw new NoSuchElementException();
int selectedIdx = -1;
for (int i = 0; i < _events.length; ++i) {
if (_events[i] == null || _events[i]._blocked)
continue;
if (_events[i].isRecv()) { // check edge dependency
Event send = _edges.get(_events[i]);
if ((send != null) && (_events[send._nodeId] != null)
&& send._eventIdx >= _events[send._nodeId]._eventIdx)
continue;
}
selectedIdx = ((selectedIdx == -1) || _events[i]
.compareTo(_events[selectedIdx]) < 0) ? i : selectedIdx;
}
if (selectedIdx == -1) { // we did not select anything -> all event streams
// must be blocked return the oldest one (assuming
// corresponding send was in previous snapshot)
// System.out.println("*** all blocked ***");
selectedIdx = 0;
long selectedNs = (_events[selectedIdx] != null) ? _events[selectedIdx]
.ns() : Long.MAX_VALUE;
long selectedMs = (_events[selectedIdx] != null) ? _events[selectedIdx]
.ms() : Long.MAX_VALUE;
for (int i = 1; i < _events.length; ++i) {
if (_events[i] == null)
continue;
if ((_events[i].ms() < selectedMs) && (_events[i].ns() < selectedNs)) {
selectedIdx = i;
selectedNs = _events[i].ns();
selectedMs = _events[i].ms();
}
}
}
assert (selectedIdx != -1);
assert (_events[selectedIdx] != null)
&& ((_events[selectedIdx]._eventIdx < TimeLine.MAX_EVENTS) && !_events[selectedIdx]
.isEmpty());
Event res = _events[selectedIdx];
_events[selectedIdx] = _events[selectedIdx].nextEvent();
if (_events[selectedIdx] != null && !_processed)
processEvent(_events[selectedIdx]);
// DEBUG
// if (_processed)
// if (res.isRecv())
// System.out.println("# " + res + " PAIRED WITH "
// + (_edges.containsKey(res) ? _edges.get(res) : "*** NONE ****"));
// else
// System.out.println("# " + res + " receivers: "
// + _sends.get(res).toString());
return res;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}