package se.sics.gvod.ls.video.snapshot;
import java.text.DecimalFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import se.sics.gvod.common.Self;
import se.sics.gvod.net.VodAddress;
import java.lang.management.ManagementFactory;
import java.util.logging.Level;
import javax.management.*;
/**
*
* @author Jim Dowling <jdowling@sics.se>
* @author Niklas Wahlén <nwahlen@kth.se>
*/
public final class VideoStats {
private static Logger logger = LoggerFactory.getLogger(VideoStats.class);
// (overlayId -> (nodeId, stats))
private static final ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, Stats>> snapshotMap =
new ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, Stats>>();
private static final ConcurrentHashMap<VodAddress, Stats> nodeMap =
new ConcurrentHashMap<VodAddress, Stats>();
private static AtomicInteger counter = new AtomicInteger(0);
private static final ConcurrentHashMap<Integer, Integer> sentConnectionRequests =
new ConcurrentHashMap<Integer, Integer>();
private static final ConcurrentHashMap<Integer, Integer> receivedConnectionResponses =
new ConcurrentHashMap<Integer, Integer>();
private static final ConcurrentHashMap<Integer, Integer> connectionRequestTimeouts =
new ConcurrentHashMap<Integer, Integer>();
private static final NumPiecesComparator numPiecesComparator = new NumPiecesComparator();
private VideoStats() {
// hidden
}
public static Stats instance(Self self) {
return addNode(self.getAddress(), false);
}
public static Stats addNode(VodAddress peer, boolean source) {
int overlayId = peer.getOverlayId();
int nodeId = peer.getId();
ConcurrentHashMap<Integer, Stats> overlayStats;
if (!snapshotMap.containsKey(overlayId)) {
overlayStats = new ConcurrentHashMap<Integer, Stats>();
snapshotMap.put(overlayId, overlayStats);
} else {
overlayStats = snapshotMap.get(overlayId);
}
Stats stats;
if (!overlayStats.containsKey(nodeId)) {
stats = new Stats(nodeId, overlayId, source);
overlayStats.put(nodeId, stats);
nodeMap.put(peer, stats);
registerMBean(stats);
} else {
stats = overlayStats.get(nodeId);
}
stats.setNatType(peer.getNatType());
return stats;
}
public static boolean removeNode(int nodeId, int overlayId) {
ConcurrentHashMap<Integer, Stats> overlayStats = snapshotMap.get(overlayId);
if (overlayStats != null) {
Stats removed = overlayStats.remove(nodeId);
if(removed == null) {
return false;
} else {
unregisterMBean(removed);
return true;
}
} else {
return false;
}
}
private static Set<Stats> getNodes(int overlayId) {
Set<Stats> nodes = new HashSet<Stats>();
nodes.addAll(snapshotMap.get(overlayId).values());
return nodes;
}
private static int numNodes(int overlayId) {
return getNodes(overlayId).size();
}
private static void registerMBean(Stats stats) {
try {
// Register MBean in Platform MBeanServer
ManagementFactory.getPlatformMBeanServer().
registerMBean(new StandardMBean(stats,
StatsIntf.class),
new ObjectName("snapshot.video.ls.gvod.sics.se:type=Stats("
+ stats.getOverlayId() + "-" + stats.getNodeId() + ")"));
} catch (JMException ex) {
java.util.logging.Logger.getLogger(VideoStats.class.getName()).log(Level.SEVERE, null, ex);
}
}
private static void unregisterMBean(Stats stats) {
try {
ManagementFactory.getPlatformMBeanServer().
unregisterMBean(
new ObjectName("snapshot.video.ls.gvod.sics.se:type=Stats("
+ stats.getOverlayId() + "-" + stats.getNodeId() + ")"));
} catch (JMException ex) {
java.util.logging.Logger.getLogger(VideoStats.class.getName()).log(Level.SEVERE, null, ex);
}
}
//-------------------------------------------------------------------
public static void startCollectData() {
logger.debug("\nStart collecting data ...\n");
}
//-------------------------------------------------------------------
public static void stopCollectData() {
logger.debug("\nStop collecting data ...\n");
for (int overlayId : snapshotMap.keySet()) {
report(overlayId);
}
}
/**
* Returns all nodes with the same overlayId
*
* @param overlayId
* @return
*/
private static Set<Stats> getOverlayStats(int overlayId) {
Set<Stats> nodes = new HashSet<Stats>();
nodes.addAll(snapshotMap.get(overlayId).values());
return nodes;
}
private static void incTotal(Map<Integer, Integer> map, int overlayId, int count) {
Integer total = map.get(overlayId);
if (total == null) {
total = 0;
map.put(overlayId, total);
}
total += count;
}
private static int getNumRequestsSent(int overlayId) {
int count = 0;
Set<Stats> nodes = getOverlayStats(overlayId);
for (Stats s : nodes) {
count += (s.getConnectionRequestsSentClose() + s.getConnectionRequestsSentRandom());
}
incTotal(sentConnectionRequests, overlayId, count);
return count;
}
private static int getReceivedConnectionRequestTimeouts(int overlayId) {
int count = 0;
Set<Stats> nodes = getOverlayStats(overlayId);
for (Stats s : nodes) {
count += (s.getConnectionRequestTimeoutsClose() + s.getConnectionRequestTimeoutsRandom());
}
incTotal(connectionRequestTimeouts, overlayId, count);
return count;
}
private static int getNumReceivedResponses(int overlayId) {
int count = 0;
Set<Stats> nodes = getOverlayStats(overlayId);
for (Stats s : nodes) {
count += (s.getConnectionResponsesReceivedClose() + s.getConnectionResponsesReceivedRandom());
}
incTotal(receivedConnectionResponses, overlayId, count);
return count;
}
private static double averageIngoingConnections(int overlayId) {
double totalConnections = 0;
Set<Stats> nodes = getOverlayStats(overlayId);
for (Stats s : nodes) {
totalConnections += (s.getIngoingConnectionsClose()
+ s.getIngoingConnectionsRandom());
}
return (totalConnections / nodes.size());
}
private static int maximumIngoingConnections(int overlayId) {
int connections = -1;
Set<Stats> nodes = getOverlayStats(overlayId);
for (Stats s : nodes) {
if (connections < (s.getIngoingConnectionsClose()
+ s.getIngoingConnectionsRandom())) {
connections = (s.getIngoingConnectionsClose()
+ s.getIngoingConnectionsRandom());
}
}
return connections;
}
private static int minimumIngoingConnections(int overlayId) {
int connections = Integer.MAX_VALUE;
Set<Stats> nodes = getOverlayStats(overlayId);
for (Stats s : nodes) {
if (connections > (s.getIngoingConnectionsClose()
+ s.getIngoingConnectionsRandom())) {
connections = (s.getIngoingConnectionsClose()
+ s.getIngoingConnectionsRandom());
}
}
return connections;
}
private static int peersWithZeroConnections(int overlayId) {
int count = 0;
Set<Stats> nodes = getOverlayStats(overlayId);
for (Stats s : nodes) {
if ((s.getIngoingConnectionsClose()
+ s.getIngoingConnectionsRandom()) == 0) {
count++;
}
}
return count;
}
/*
* Pieces stats
*/
private static int maxNumPieces(int overlayId) {
int pieces = -1;
Set<Stats> nodes = getOverlayStats(overlayId);
for (Stats s : nodes) {
if (!s.isSource() && s.getCompletePieces() > pieces) {
pieces = s.getCompletePieces();
}
}
return pieces;
}
private static int minNumPieces(int overlayId) {
int pieces = Integer.MAX_VALUE;
Set<Stats> nodes = getOverlayStats(overlayId);
for (Stats s : nodes) {
if (!s.isSource() && s.getCompletePieces() < pieces) {
pieces = s.getCompletePieces();
}
}
return pieces;
}
private static double avgNumPieces(int overlayId) {
int piecesCount = 0;
Set<Stats> nodes = getOverlayStats(overlayId);
for (Stats s : nodes) {
if (!s.isSource()) {
piecesCount += s.getCompletePieces();
}
}
return ((double) ((double) piecesCount) / ((double) nodes.size()));
}
private static int maxNumSubPiecesSeen(int overlayId) {
int pieces = -1;
Set<Stats> nodes = getOverlayStats(overlayId);
for (Stats s : nodes) {
if (!s.isSource() && s.getSeenSubPieces() > pieces) {
pieces = s.getSeenSubPieces();
}
}
return pieces;
}
private static int minNumSubPiecesSeen(int overlayId) {
int pieces = Integer.MAX_VALUE;
Set<Stats> nodes = getOverlayStats(overlayId);
for (Stats s : nodes) {
if (!s.isSource() && s.getSeenSubPieces() < pieces) {
pieces = s.getSeenSubPieces();
}
}
return pieces;
}
private static double avgNumSubPiecesSeen(int overlayId) {
int piecesCount = 0;
Set<Stats> nodes = getOverlayStats(overlayId);
for (Stats s : nodes) {
if (!s.isSource()) {
piecesCount += s.getSeenSubPieces();
}
}
return ((double) ((double) piecesCount) / ((double) nodes.size()));
}
private static int peersWithZeroSubPiecesSeen(int overlayId) {
int count = 0;
Set<Stats> nodes = getOverlayStats(overlayId);
for (Stats s : nodes) {
if (!s.isSource() && s.getSeenSubPieces() == 0) {
count++;
}
}
return count;
}
private static List<Stats> getWorstDownloaders(int overlayId, int size) {
List<Stats> worst = new ArrayList<Stats>();
Set<Stats> nodes = getOverlayStats(overlayId);
for (Stats s : nodes) {
if (!s.isSource()) {
if (worst.size() == size) {
Collections.sort(worst, numPiecesComparator);
if (numPiecesComparator.compare(s, worst.get(worst.size() - 1)) == -1) {
worst.remove(worst.size() - 1);
worst.add(s);
}
} else {
worst.add(s);
}
}
}
return worst;
}
private static List<Stats> getBestDownloaders(int overlayId, int size) {
List<Stats> best = new ArrayList<Stats>();
Set<Stats> nodes = getOverlayStats(overlayId);
for (Stats s : nodes) {
if (!s.isSource()) {
if (best.size() == size) {
Collections.sort(best, numPiecesComparator);
if (numPiecesComparator.compare(s, best.get(0)) == 1) {
best.remove(best.get(0));
best.add(s);
}
} else {
best.add(s);
}
}
}
return best;
}
private static int[] getPieceDownloadStats(int overlayId, int pieces) {
Set<Stats> nodes = getOverlayStats(overlayId);
int pieceDownloads[] = new int[pieces];
for (Stats s : nodes) {
List<Integer> pieceStats = s.getPieceStats();
for (int i = 0; i < pieceDownloads.length; i++) {
if (pieceStats.contains(i)) {
pieceDownloads[i]++;
}
}
}
return pieceDownloads;
}
//-------------------------------------------------------------------
public static void report(int overlayId) {
StringBuilder sb = new StringBuilder("\n");
sb.append("Video current step: ").append(counter.get()).
append(", current time: ").append(System.currentTimeMillis()).
append(", number of nodes: ").append(numNodes(overlayId));
incStepCounter(overlayId);
sb.append(reportStats(overlayId));
logger.info(sb.toString());
}
private static void incStepCounter(int overlayId) {
Set<Stats> stats = getOverlayStats(overlayId);
for (Stats s : stats) {
s.setStep(counter.get());
}
counter.incrementAndGet();
}
private static String reportStats(int overlayId) {
StringBuilder sb = new StringBuilder();
int t = getReceivedConnectionRequestTimeouts(overlayId);
int sentRequests = getNumRequestsSent(overlayId);
int recvd = getNumReceivedResponses(overlayId);
int sz = numNodes(overlayId);
DecimalFormat format = new DecimalFormat("#.##");
String timedoutPercent = sentRequests == 0 ? "0" : format.format(100 * (double) ((double) t / (double) sentRequests));
String avgConns = format.format(averageIngoingConnections(overlayId));
int maxConns = maximumIngoingConnections(overlayId);
int minConns = minimumIngoingConnections(overlayId);
int zeroConns = peersWithZeroConnections(overlayId);
int maxNumPieces = maxNumPieces(overlayId);
int minNumPieces = minNumPieces(overlayId);
String avgNumPieces = format.format(avgNumPieces(overlayId));
int maxNumPiecesSeen = maxNumSubPiecesSeen(overlayId);
int minNumPiecesSeen = minNumSubPiecesSeen(overlayId);
int zeroPiecesSeen = peersWithZeroSubPiecesSeen(overlayId);
String avgNumPiecesSeen = format.format(avgNumSubPiecesSeen(overlayId));
List<Stats> worstDownloaders = getWorstDownloaders(overlayId, 5);
List<Stats> bestDownloaders = getBestDownloaders(overlayId, 5);
if (sz != 0) {
sb.append("---\n");
sb.append("Video msg stats: ").append("avg recvd(").append((recvd / sz)).
append("), sReq(").append(sentRequests).
append("), t(").append(t).append(" (").append(timedoutPercent).append("%)").
append("), rResp(").append(recvd).
append(")\n");
sb.append("Video connection stats: ").append("avg(").append(avgConns).
append("), max(").append(maxConns).
append("), min(").append(minConns).
append("), zero(").append(zeroConns).
append(")\n");
sb.append("Video pieces stats: ").append("downloaded(").append(avgNumPieces).
append("/").append(maxNumPieces).
append("/").append(minNumPieces).
append("), seen(").append(avgNumPiecesSeen).
append("/").append(maxNumPiecesSeen).
append("/").append(minNumPiecesSeen).
append("), zero(").append(zeroPiecesSeen).
append(")\n");
sb.append("Worst downloaders:\n");
Iterator<Stats> it = worstDownloaders.iterator();
while (it.hasNext()) {
Stats worstDownloader = it.next();
int totalDlSubPieces = worstDownloader.getDownloadedSubPiecesIntraAs() + worstDownloader.getDownloadedSubPiecesNeighbourAs() + worstDownloader.getDownloadedSubPiecesOtherAs();
String worstDlPercent = maxNumPieces == 0 ? "0" : format.format(100 * (double) ((double) worstDownloader.getCompletePieces() / (double) maxNumPieces));
sb.append(worstDownloader.getNodeId()).
append(" (").append(worstDownloader.getNatType()).
append(")\t").append(worstDownloader.getCompletePieces()).append(" pieces").
append(" (").append(worstDlPercent).append("%) ").
append(" ").append(totalDlSubPieces).append(" sub-pieces").
append("\tingoing").
append("(").append(worstDownloader.getIngoingConnectionsClose()).append(" close").
append(", ").append(worstDownloader.getIngoingConnectionsRandom()).append(" random").
append(")\toutgoing(").
append(worstDownloader.getOutgoingConnectionsClose()).append(" close, ").
append(worstDownloader.getOutgoingConnectionsRandom()).append(" random").
append(")\n");
}
sb.append("Best downloaders:\n");
Iterator<Stats> it2 = bestDownloaders.iterator();
while (it2.hasNext()) {
Stats bestDownloader = it2.next();
int totalDlSubPieces = bestDownloader.getDownloadedSubPiecesIntraAs() + bestDownloader.getDownloadedSubPiecesNeighbourAs() + bestDownloader.getDownloadedSubPiecesOtherAs();
String bestDlPercent = maxNumPieces == 0 ? "0" : format.format(100 * (double) ((double) bestDownloader.getCompletePieces() / (double) maxNumPieces));
sb.append(bestDownloader.getNodeId()).
append(" (").append(bestDownloader.getNatType()).
append(")\t").append(bestDownloader.getCompletePieces()).append(" pieces").
append(" (").append(bestDlPercent).append("%) ").
append(" ").append(totalDlSubPieces).append(" sub-pieces").
append("\tingoing").
append("(").append(bestDownloader.getIngoingConnectionsClose()).append(" close").
append(", ").append(bestDownloader.getIngoingConnectionsRandom()).append(" random").
append(")\toutgoing(").
append(bestDownloader.getOutgoingConnectionsClose()).append(" close, ").
append(bestDownloader.getOutgoingConnectionsRandom()).append(" random").
append(")\n");
}
sb.append("Piece download stats:\n");
int[] downloadStats = getPieceDownloadStats(overlayId, maxNumPieces);
sb.append("[");
for (int i = 0; i < downloadStats.length; i++) {
sb.append(i).append(":").append(downloadStats[i]);
if (i < downloadStats.length - 1) {
sb.append(",");
}
}
sb.append("]");
}
return sb.toString();
}
private static class NumPiecesComparator implements Comparator<Stats> {
@Override
public int compare(Stats t, Stats t1) {
if (t.getCompletePieces() > t1.getCompletePieces()) {
return 1;
} else if (t.getCompletePieces() < t1.getCompletePieces()) {
return -1;
} else {
return 0;
}
}
}
}