Java tutorial
/** * This file is part of the Kompics P2P Framework. * * Copyright (C) 2009 Swedish Institute of Computer Science (SICS) Copyright (C) * 2009 Royal Institute of Technology (KTH) * * Kompics is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 59 Temple * Place - Suite 330, Boston, MA 02111-1307, USA. */ package se.sics.kompics.p2p.experiment.bittorrent; import java.math.BigInteger; import java.util.HashMap; import java.util.Map; import org.apache.commons.math.stat.descriptive.SummaryStatistics; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import se.sics.kompics.Component; import se.sics.kompics.ComponentDefinition; import se.sics.kompics.Handler; import se.sics.kompics.Positive; import se.sics.kompics.Start; import se.sics.kompics.Stop; import se.sics.kompics.address.Address; import se.sics.kompics.network.Message; import se.sics.kompics.network.Network; import se.sics.kompics.p2p.cdn.bittorrent.BitTorrentConfiguration; import se.sics.kompics.p2p.cdn.bittorrent.TorrentMetadata; import se.sics.kompics.p2p.cdn.bittorrent.address.BitTorrentAddress; import se.sics.kompics.p2p.cdn.bittorrent.client.BitTorrentClient; import se.sics.kompics.p2p.cdn.bittorrent.client.BitTorrentClientInit; import se.sics.kompics.p2p.cdn.bittorrent.client.BitTorrentClientPort; import se.sics.kompics.p2p.cdn.bittorrent.client.Bitfield; import se.sics.kompics.p2p.cdn.bittorrent.client.DownloadCompleted; import se.sics.kompics.p2p.cdn.bittorrent.client.JoinSwarm; import se.sics.kompics.p2p.cdn.bittorrent.message.BitTorrentMessage; import se.sics.kompics.p2p.experiment.bittorrent.bw.model.BwDelayedMessage; import se.sics.kompics.p2p.experiment.bittorrent.bw.model.Link; import se.sics.kompics.p2p.experiment.dsl.events.TerminateExperiment; import se.sics.kompics.timer.ScheduleTimeout; import se.sics.kompics.timer.Timer; /** * The * <code>BitTorrentSimulator</code> class. * * @author Cosmin Arad <cosmin@sics.se> * @version $Id$ */ public final class BitTorrentSimulator extends ComponentDefinition { Positive<BitTorrentSimulatorPort> simulator = positive(BitTorrentSimulatorPort.class); Positive<Network> network = positive(Network.class); Positive<Timer> timer = positive(Timer.class); private static final Logger logger = LoggerFactory.getLogger(BitTorrentSimulator.class); private final HashMap<BigInteger, Component> peers; private final HashMap<BigInteger, Link> uploadLink; private final HashMap<BigInteger, Link> downloadLink; // peer initialization state private BitTorrentAddress trackerAddress; private TorrentMetadata torrent; private BitTorrentConfiguration btConfiguration; private int peerIdSequence; private int initialSeeds = 0, seedsExpected = 0; private ConsistentHashtable<BigInteger> peersView; private boolean selfishPeers; // statistics private HashMap<BigInteger, Long> leecherDownloadTime; private HashMap<Class<? extends Message>, ReceivedMessage> messageHistogram; public BitTorrentSimulator(BitTorrentSimulatorInit init) { peers = new HashMap<BigInteger, Component>(); uploadLink = new HashMap<BigInteger, Link>(); downloadLink = new HashMap<BigInteger, Link>(); peersView = new ConsistentHashtable<BigInteger>(); leecherDownloadTime = new HashMap<BigInteger, Long>(); messageHistogram = new HashMap<Class<? extends Message>, ReceivedMessage>(); subscribe(handleJoin, simulator); subscribe(handleMessageReceived, network); subscribe(handleDelayedMessage, timer); // INIT peers.clear(); peerIdSequence = 0; btConfiguration = init.getBtConfiguration(); torrent = init.getTorrent(); trackerAddress = torrent.getTracker(); selfishPeers = init.isSelfishPeers(); } Handler<BitTorrentPeerJoin> handleJoin = new Handler<BitTorrentPeerJoin>() { public void handle(BitTorrentPeerJoin event) { BigInteger id = event.getPeerId(); // join with the next id if this id is taken BigInteger successor = peersView.getNode(id); while (successor != null && successor.equals(id)) { id = id.add(BigInteger.ONE); successor = peersView.getNode(id); } logger.debug("JOIN@{}", id); Component newPeer = createAndStartNewPeer(id, event.getDownloaded(), event.getDownloadBw(), event.getUploadBw()); peersView.addNode(id); trigger(new JoinSwarm(), newPeer.getPositive(BitTorrentClientPort.class)); if (event.getDownloaded().allSet()) { initialSeeds++; } else { long startedDownloadAt = System.currentTimeMillis(); leecherDownloadTime.put(id, startedDownloadAt); } seedsExpected++; } }; Handler<DownloadCompleted> handleCompleted = new Handler<DownloadCompleted>() { public void handle(DownloadCompleted event) { seedsExpected--; if (initialSeeds > 0) { // one of the initial seeds tells us it has all pieces initialSeeds--; } else { // a leecher just became a seed logger.debug("Peer {} completed download.", event.getPeer()); BigInteger peerId = event.getPeer().getPeerId(); long now = System.currentTimeMillis(); long started = leecherDownloadTime.get(peerId); leecherDownloadTime.put(peerId, now - started); if (selfishPeers) { // if leecher is selfish we remove it from the swarm stopAndDestroyPeer(peerId); } if (seedsExpected == 0) { // all joined peers have completed the download so we can // terminate the simulation trigger(new TerminateExperiment(), simulator); logStatistics(); } } } }; private void logStatistics() { SummaryStatistics downloadTime = new SummaryStatistics(); for (long time : leecherDownloadTime.values()) { downloadTime.addValue(time); } int messages = 0; long traffic = 0; for (ReceivedMessage rm : messageHistogram.values()) { messages += rm.getTotalCount(); traffic += rm.getTotalSize(); } long torrentSize = torrent.getPieceCount() * torrent.getPieceSize(); logger.info("================================================="); logger.info("Content size: {} bytes", torrentSize); logger.info("Piece size: {} bytes", torrent.getPieceSize()); logger.info("Piece count: {}", torrent.getPieceCount()); logger.info("================================================="); logger.info("Number of leechers: {}", downloadTime.getN()); logger.info("Min download time: {} ms ({})", downloadTime.getMin(), durationToString(Math.round(downloadTime.getMin()))); logger.info("Max download time: {} ms ({})", downloadTime.getMax(), durationToString(Math.round(downloadTime.getMax()))); logger.info("Avg download time: {} ms ({})", downloadTime.getMean(), durationToString(Math.round(downloadTime.getMean()))); logger.info("Std download time: {} ms ({})", downloadTime.getStandardDeviation(), durationToString(Math.round(downloadTime.getStandardDeviation()))); logger.info("Min download rate: {} Bps", torrentSize / downloadTime.getMax() * 1000); logger.info("Max download rate: {} Bps", torrentSize / downloadTime.getMin() * 1000); logger.info("Avg download rate: {} Bps", torrentSize / downloadTime.getMean() * 1000); logger.info("================================================="); logger.info("Total number of messages: {}", messages); logger.info("Total amount of traffic: {} bytes", traffic); for (Map.Entry<Class<? extends Message>, ReceivedMessage> entry : messageHistogram.entrySet()) { logger.info("{}: #={} \t bytes={}", new Object[] { String.format("%22s", entry.getKey().getSimpleName()), entry.getValue().getTotalCount(), entry.getValue().getTotalSize() }); } logger.info("================================================="); } Handler<BitTorrentMessage> handleMessageSent = new Handler<BitTorrentMessage>() { public void handle(BitTorrentMessage message) { // message just sent by some peer goes into peer's up pipe Link link = uploadLink.get(message.getBitTorrentSource().getPeerId()); long delay = link.addMessage(message); if (delay == 0) { // immediately send to cloud trigger(message, network); return; } ScheduleTimeout st = new ScheduleTimeout(delay); st.setTimeoutEvent(new BwDelayedMessage(st, message, true)); trigger(st, timer); } }; Handler<BitTorrentMessage> handleMessageReceived = new Handler<BitTorrentMessage>() { public void handle(BitTorrentMessage message) { // traffic stats ReceivedMessage rm = messageHistogram.get(message.getClass()); if (rm == null) { rm = new ReceivedMessage(message.getClass(), 0, 0); messageHistogram.put(message.getClass(), rm); } rm.incrementCount(); rm.incrementSize(message.getSize()); // message to be received by some peer goes into peer's down pipe Link link = downloadLink.get(message.getBitTorrentDestination().getPeerId()); if (link == null) { return; } long delay = link.addMessage(message); if (delay == 0) { // immediately deliver to peer Component peer = peers.get(message.getBitTorrentDestination().getPeerId()); trigger(message, peer.getNegative(Network.class)); return; } ScheduleTimeout st = new ScheduleTimeout(delay); st.setTimeoutEvent(new BwDelayedMessage(st, message, false)); trigger(st, timer); } }; Handler<BwDelayedMessage> handleDelayedMessage = new Handler<BwDelayedMessage>() { public void handle(BwDelayedMessage delayedMessage) { if (delayedMessage.isBeingSent()) { // message comes out of upload pipe BitTorrentMessage message = delayedMessage.getMessage(); // and goes to the network cloud trigger(message, network); } else { // message comes out of download pipe BitTorrentMessage message = delayedMessage.getMessage(); Component peer = peers.get(message.getBitTorrentDestination().getPeerId()); if (peer != null) { // and goes to the peer trigger(message, peer.getNegative(Network.class)); } } } }; private final Component createAndStartNewPeer(BigInteger id, Bitfield initialPieces, long downloadBw, long uploadBw) { int peerId = ++peerIdSequence; Address peerAddress = new Address(trackerAddress.getPeerAddress().getIp(), trackerAddress.getPeerAddress().getPort(), peerId); Component peer = create(BitTorrentClient.class, new BitTorrentClientInit( new BitTorrentAddress(peerAddress, id), initialPieces, torrent, btConfiguration)); connect(timer, peer.getNegative(Timer.class)); subscribe(handleCompleted, peer.getPositive(BitTorrentClientPort.class)); subscribe(handleMessageSent, peer.getNegative(Network.class)); trigger(new Start(), peer.getControl()); peers.put(id, peer); uploadLink.put(id, new Link(uploadBw)); downloadLink.put(id, new Link(downloadBw)); return peer; } private final void stopAndDestroyPeer(BigInteger id) { Component peer = peers.get(id); trigger(new Stop(), peer.getControl()); unsubscribe(handleCompleted, peer.getPositive(BitTorrentClientPort.class)); unsubscribe(handleMessageSent, peer.getNegative(Network.class)); disconnect(timer, peer.getNegative(Timer.class)); peers.remove(id); uploadLink.remove(id); downloadLink.remove(id); destroy(peer); } public static final String durationToString(long duration) { StringBuilder sb = new StringBuilder(); int ms = 0, s = 0, m = 0, h = 0, d = 0, y = 0; ms = (int) (duration % 1000); // get duration in seconds duration /= 1000; s = (int) (duration % 60); // get duration in minutes duration /= 60; if (duration > 0) { m = (int) (duration % 60); // get duration in hours duration /= 60; if (duration > 0) { h = (int) (duration % 24); // get duration in days duration /= 24; if (duration > 0) { d = (int) (duration % 365); // get duration in years y = (int) (duration / 365); } } } boolean printed = false; if (y > 0) { sb.append(y).append("y "); printed = true; } if (d > 0) { sb.append(d).append("d "); printed = true; } if (h > 0) { sb.append(h).append("h "); printed = true; } if (m > 0) { sb.append(m).append("m "); printed = true; } if (s > 0 || !printed) { sb.append(s); if (ms > 0) { sb.append(".").append(String.format("%03d", ms)); } sb.append("s"); } return sb.toString(); } }