package se.sics.gvod.ls.gossip;
import se.sics.gvod.ls.video.*;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import se.sics.gvod.common.RetryComponentDelegator;
import se.sics.gvod.common.Self;
import se.sics.gvod.ls.system.LSConfig;
import se.sics.gvod.ls.video.snapshot.VideoStats;
import se.sics.gvod.net.VodAddress;
import se.sics.gvod.net.VodNetwork;
import se.sics.gvod.timer.Timer;
import se.sics.gvod.timer.UUID;
import se.sics.gvod.video.msgs.EncodedSubPiece;
import se.sics.gvod.video.msgs.Piece;
import se.sics.gvod.video.msgs.VideoPieceMsg;
import se.sics.kompics.Positive;
/**
*
* @author Niklas Wahlén <nwahlen@kth.se>
*/
public class ThreePhaseGossip {
protected final Logger logger = LoggerFactory.getLogger(Video.class);
// References
protected RetryComponentDelegator delegator;
protected Self self;
protected Positive<VodNetwork> network;
protected Positive<Timer> timer;
protected VideoNeighbours neighbours;
// Data
protected Map<Integer, EncodedSubPiece> subPieces;
// Algorithm specific
private int f;
protected Set<Integer> subPiecesToPropose;
protected Set<EncodedSubPiece> subPiecesDelivered;
protected Set<Integer> requestedSubPieces;
// Configuration
protected int ulBwCapacity, dlBwCapacity; // bytes
protected int uploaded, downloaded;
public ThreePhaseGossip(RetryComponentDelegator delegator, Self self, Positive<VodNetwork> network, Positive<Timer> timer, VideoNeighbours neighbours, Map<Integer, EncodedSubPiece> subPieces) {
// Algorithm specific
// fanout f = ln(system size) + constant
f = ((int) Math.log(500)) + 2;
subPiecesToPropose = new HashSet<Integer>();
subPiecesDelivered = new HashSet<EncodedSubPiece>();
requestedSubPieces = new HashSet<Integer>();
// References
this.delegator = delegator;
this.self = self;
this.network = network;
this.timer = timer;
this.neighbours = neighbours;
this.subPieces = subPieces;
// Configuration
ulBwCapacity = LSConfig.VIDEO_UPLOAD_CAPACITY; // in bytes
dlBwCapacity = Integer.MAX_VALUE;
uploaded = 0;
downloaded = 0;
}
/*
* Phase 1 - Gossip chunk ids
*/
public void publish(EncodedSubPiece p) {
if (p == null) {
throw new IllegalArgumentException("null Piece not allowed");
}
deliverSubPiece(p);
Set<Integer> pieceIds = new HashSet<Integer>();
pieceIds.add(p.getGlobalId());
gossip(pieceIds);
}
public void cycle() {
if (!subPiecesToPropose.isEmpty()) {
gossip(new HashSet<Integer>(subPiecesToPropose));
subPiecesToPropose.clear();
}
VideoStats.instance(self).setUlBwBytes(uploaded);
VideoStats.instance(self).setDlBwBytes(downloaded);
VideoStats.instance(self).setFanout((short) f);
uploaded = 0;
downloaded = 0;
}
/*
* Phase 2 - Request pieces
*/
public void handleAdvertisement(VideoPieceMsg.Advertisement advertisement) {
Set<Integer> wantedSubPieces = new HashSet<Integer>();
for (Integer id : advertisement.getAdvertisedPiecesIds()) {
if (!requestedSubPieces.contains(id)) {
wantedSubPieces.add(id);
VideoStats.instance(self).incSeenSubPieces();
}
}
if (!wantedSubPieces.isEmpty()) {
requestedSubPieces.addAll(wantedSubPieces);
delegator.doTrigger(new VideoPieceMsg.Request(self.getAddress(), advertisement.getVodSource(), wantedSubPieces), network);
}
}
/*
* Phase 3 - Push payload
*/
public void handleRequest(VideoPieceMsg.Request request) {
Set<EncodedSubPiece> askedChunks = new HashSet<EncodedSubPiece>();
for (Integer id : request.getPiecesIds()) {
if (uploaded + EncodedSubPiece.getSize() > ulBwCapacity) {
break;
}
EncodedSubPiece p = getSubPiece(id);
if (p != null) {
delegator.doTrigger(new VideoPieceMsg.Response(self.getAddress(), request.getVodSource(), request.getTimeoutId(), p), network);
incSent(request.getVodSource());
uploaded += EncodedSubPiece.getSize();
}
}
if (!askedChunks.isEmpty()) {
//delegator.doTrigger(new VideoPieceMsg.Response(self.getAddress(), request.getVodSource(), request.getTimeoutId(), askedChunks), network);
}
}
public void handlePieces(VideoPieceMsg.Response pieces) {
EncodedSubPiece p = pieces.getEncodedSubPiece();
if (!subPiecesDelivered.contains(p)) {
downloaded += EncodedSubPiece.getSize();
subPiecesToPropose.add(p.getGlobalId());
deliverSubPiece(p);
}
}
// Only called after local encoding, i.e. never when receiving
public void handlePiece(EncodedSubPiece esp) {
if (!subPiecesDelivered.contains(esp)) {
subPiecesToPropose.add(esp.getGlobalId());
deliverSubPiece(esp);
}
}
/*
* Miscellaneous
*/
public Collection<VodAddress> selectNodes(int f) {
return neighbours.getNRandomNeighbours(f);
}
public EncodedSubPiece getSubPiece(Integer id) {
return subPieces.get(id);
}
public void deliverSubPiece(EncodedSubPiece p) {
subPiecesDelivered.add(p);
// requestedSubPieces is the only set used for managing (sending of)
// requests, and if a subpiece was encoded locally (not received) it
// was never added to this set previously
requestedSubPieces.add(p.getGlobalId());
subPieces.put(p.getGlobalId(), p);
}
public int getFanout() {
return f;
}
public void gossip(Set<Integer> pieceIds) {
Collection<VodAddress> communicationPartners = selectNodes(f);
String str = "[";
for (Integer i : pieceIds) {
str += i + ",";
}
str += "]";
logger.debug(self.getId() + ": gossiping Advertisement"
+ str + " to "
+ communicationPartners.size() + " peers.");
for (VodAddress p : communicationPartners) {
delegator.doTrigger(new VideoPieceMsg.Advertisement(self.getAddress(), p, pieceIds), network);
}
}
/*
* Stats
*/
private void incSent(VodAddress a) {
short distance = neighbours.getDistanceTo(a);
if (distance == 0) {
VideoStats.instance(self).incSentSubPiecesIntraAs();
} else if (distance == 1) {
VideoStats.instance(self).incSentSubPiecesNeighbourAs();
} else {
VideoStats.instance(self).incSentSubPiecesOtherAs();
}
}
}