From 7581ab95f5ac01b21f1768f156230a7eaf116f7e Mon Sep 17 00:00:00 2001 From: joshtch Date: Mon, 19 Dec 2016 03:55:01 -0500 Subject: [PATCH] Working simulator and visualizer --- .gitignore | 3 ++ cloud.py | 87 ++++++++++----------------------------- data.py | 41 ++++++++++++------- device.py | 48 +++++++++++----------- location.py | 6 --- network.py | 12 ------ packet.py | 33 +++++++++++---- simulator.py | 104 ++++++++++++++++------------------------------- testlocation.py | 8 ++++ testnetwork.py | 14 +++++++ visualizer.py | 23 ++++------- vr_simulation.py | 12 +++--- 12 files changed, 171 insertions(+), 220 deletions(-) create mode 100644 testlocation.py create mode 100644 testnetwork.py diff --git a/.gitignore b/.gitignore index 473c568..42ac046 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ __pycache__/ *.py[cod] *$py.class +# ctags tagfile +tags + # C extensions *.so diff --git a/cloud.py b/cloud.py index 919ecc5..c9c6d26 100644 --- a/cloud.py +++ b/cloud.py @@ -1,81 +1,36 @@ -import Queue -if __name__ == "__main__": import packet +from packet import Packet,PacketQueue # these delays can be seen as the time response time of the server for each game # being played +MAX_PACKETS_PER_STEP = 350 TRAFFIC_DELAY = { 'high' : 10, 'med' : 5, 'low' : 3 } class Cloud(object): def __init__(self, gameTraffic, location, timeout, num_players): - - self.requestList = Queue.Queue() - self.timeToProcess = TRAFFIC_DELAY[gameTraffic] - self.num_players = num_players self.location = location self.timeout = timeout - - def responseAt(self, time): - if time % self.timeToProcess == 0 and not self.requestList.empty() : - headPacket = self.requestList.get(); - if (time - headPacket.timestamp) > self.timeout: - return None - else: - responsePackets = [] - for i in range(1,self.num_players): - newPacket = headPacket - - # update packet - newPacket.sender = 0 - newPacket.receiver = i - - responsePackets.append(newPacket) - return responsePackets - else: - return None + self.requestList = PacketQueue() + self.time = 0 + + def step(self): + self.time += 1 + responsePackets = [] + while not self.requestList.empty() \ + and self.requestList.next().isReady(self.time) \ + and len(responsePackets) <= MAX_PACKETS_PER_STEP: + headPacket = self.requestList.pop() + #if headPacket.arriveTime() + self.timeout <= self.time: + for i in xrange(1, self.num_players + 1): + newPacket = headPacket.deepcopy() + newPacket.sender = 0 + newPacket.receiver = i + responsePackets.append(newPacket) + return responsePackets def receivePacket(self, packet): - self.requestList.put(packet) - - -if __name__ == "__main__": - packet1 = packet.Packet(100, 0, 0, 1) - packet2 = packet.Packet( 0, 0, 0, 2) - packet3 = packet.Packet(100, 0, 0, 1) - packet4 = packet.Packet( 100, 0, 0, 2) - - cloud = cloud.Cloud(0, 0, 10, 10) - - cloud.receivePacket(packet1) - cloud.receivePacket(packet4) - cloud.receivePacket(packet3) - cloud.receivePacket(packet2) - - time = 100 - print "\ntest 1: should return 3 packets" - for i in range(0,20): - #print time - packetList = cloud.updateTime(time) - if packetList != None: - print packetList[0].packet_id - time = time + 1 - - cloud.receivePacket(packet1) - cloud.receivePacket(packet2) - cloud.receivePacket(packet3) - cloud.receivePacket(packet4) - - time = 100 - print "\ntest 2: should return 2 packets" - - for i in range(0,20): - #print time - packetList = cloud.updateTime(time) - if packetList != None: - print packetList[0].packet_id - time = time + 1 - print "\n" - + packet.addLatency(self.timeToProcess) + self.requestList.push(packet) diff --git a/data.py b/data.py index 9b64fcd..278f207 100644 --- a/data.py +++ b/data.py @@ -4,39 +4,50 @@ class Data(object): def __init__(self): self.d = {} - def putSend(self, id, sendTime): + def putSendTime(self, id, sendTime): if id not in self.d: - self.d[id] = { 'sendTime' : sendTime, 'receiveTime' : None } + self.d[id] = { 'sendTime' : sendTime, 'finalTime' : None } else: self.d[id]['sendTime'] = sendTime - def putReceive(self, id, receiveTime): - print "received: " + str(id) + ", " + str(receiveTime) + def putFinalTime(self, id, finalTime): if id not in self.d: - self.d[id] = { 'sendTime' : None, 'receiveTime' : receiveTime } + self.d[id] = { 'sendTime' : None, 'finalTime' : finalTime } else: - self.d[id]['receiveTime'] = receiveTime + self.d[id]['finalTime'] = finalTime def rawData(self): return self.d - def sendTime(self, id): - return self.d[id]['sendTime'] + def getSendTime(self, id): + if id in self.d: + return self.d[id]['sendTime'] + else: + return None - def receiveTime(self, id): - return self.d[id]['receiveTime'] + def getFinalTime(self, id): + if id in self.d: + return self.d[id]['finalTime'] + else: + return None def latency(self, id): - if self.receiveTime(id) is not None and self.sendTime(id) is not None: - return self.d[id]['receiveTime'] - self.d[id]['sendTime'] + if id in self.d and self.getFinalTime(id) is not None \ + and self.getSendTime(id) is not None: + return self.getFinalTime(id) - self.getSendTime(id) else: return None def latencyList(self): - return [self.latency(x) for x in self.d.keys() if self.latency(x) is not None] + return [self.latency(x) for x in self.d.keys() \ + if self.latency(x) is not None] def averageLatency(self): - return statistics.median(self.latencyList()) + latlist = self.latencyList() + if len(latlist) > 0: + return statistics.median(self.latencyList()) + else: + return 0 def numDropped(self): - return len([self.receiveTime(x) for x in self.d if self.receiveTime(x) is None]) + return len([self.getFinalTime(x) for x in self.d if self.getFinalTime(x) is None]) diff --git a/device.py b/device.py index 0a5b4c2..7b1a085 100644 --- a/device.py +++ b/device.py @@ -2,55 +2,55 @@ from random import randint from data import Data -TICKS_PER_EVENT = 20 +TICKS_PER_EVENT = 500 class Device(object): + cloud_id = 0 + rendering_time = 5 #ms - def __init__(self, deviceID, fps, ticksPerEvent, location): - self.deviceID = deviceID + def __init__(self, fps, ticksPerEvent, location): self.fps = fps self.ticksPerEvent = ticksPerEvent self.firstEventOffset = randint(0, ticksPerEvent - 1) self.location = location self.data = Data() + self.time = 0 - def responseAt(self, time): - if (time + self.firstEventOffset) % self.ticksPerEvent == 0: - packet = Packet(time, 0, self.deviceID) + def step(self): + self.time += 1 + if self.time % self.ticksPerEvent == self.firstEventOffset: + packet = Packet(self.time, Device.cloud_id, self.id) id = packet.packet_id - self.data.putSend(id, time) - print 'Device: %d. Location: %s. Packet sent: %s. Timestamp: %s' % (self.deviceID, self.location, packet.packet_id, packet.timestamp) + self.data.putSendTime(id, self.time) return [packet] else: return [] def receivePacket(self, packet): id = packet.packet_id - arrival = packet.arriveTime() - self.data.putReceive(id, arrival) - print 'Device: %d. Location: %s. Packet received: %s. Timestamp: %s' % (self.deviceID, self.location, packet.packet_id, packet.timestamp) - + finalTime = packet.arriveTime() + Device.rendering_time + self.data.putFinalTime(id, finalTime) class OculusRift(Device): - def __init__(self, deviceID, location): - super(OculusRift, self).__init__(deviceID, 90, TICKS_PER_EVENT, location) + def __init__(self, location): + super(OculusRift, self).__init__(90, TICKS_PER_EVENT, location) class HTCVive(Device): - def __init__(self, deviceID, location): - super(HTCVive, self).__init__(deviceID, 90, TICKS_PER_EVENT, location) + def __init__(self, location): + super(HTCVive, self).__init__(90, TICKS_PER_EVENT, location) class PlayStationVR(Device): - def __init__(self, deviceID, location): - super(PlayStationVR, self).__init__(deviceID, 120, TICKS_PER_EVENT, location) + def __init__(self, location): + super(PlayStationVR, self).__init__(120, TICKS_PER_EVENT, location) class LG360VR(Device): - def __init__(self, deviceID, location): - super(LG360VR, self).__init__(deviceID, 120, TICKS_PER_EVENT, location) + def __init__(self, location): + super(LG360VR, self).__init__(120, TICKS_PER_EVENT, location) class GearVR(Device): - def __init__(self, deviceID, location): - super(GearVR, self).__init__(deviceID, 60, TICKS_PER_EVENT, location) + def __init__(self, location): + super(GearVR, self).__init__(60, TICKS_PER_EVENT, location) class VisusVR(Device): - def __init__(self, deviceID, location): - super(VisusVR, self).__init__(deviceID, 60, TICKS_PER_EVENT, location) + def __init__(self, location): + super(VisusVR, self).__init__(60, TICKS_PER_EVENT, location) diff --git a/location.py b/location.py index aed1188..f48973d 100644 --- a/location.py +++ b/location.py @@ -112,9 +112,3 @@ def createPropagationMatrix(self): self.addPropagation("San Francsico", "Washington", 41) self.addPropagation("Seattle", "Washington", 68) - -if __name__ == "__main__": - loc1 = Location("Washington") - loc2 = Location("Chicago") - print "Delay from " + loc1.city + " to " + loc2.city + " is" \ - + str(loc1.propagationDelayFrom(loc2)) + " ms" diff --git a/network.py b/network.py index 572f07f..4604bf3 100644 --- a/network.py +++ b/network.py @@ -44,15 +44,3 @@ def networkDelay(self, loc1, loc2): return propDelay + self.HEADER_PROCESSING else: return None - -if __name__ == "__main__": - - loc1 = Location("Washington") - loc2 = Location("Chicago") - - # Timeout test - network = Network(2) # 2 percent chance to drop packet - attempts = 0 - while network.networkDelay(loc1, loc2) != None: - attempts += 1 - print "Network dropped packet after " + str(attempts + 1) + " packets sent" diff --git a/packet.py b/packet.py index fa7a6f4..d3a7eec 100644 --- a/packet.py +++ b/packet.py @@ -1,24 +1,43 @@ import uuid +import heapq class Packet(object): - def __init__(self, timestamp, receiver, sender): - self._init_packet_id() + def __init__(self, timestamp, receiver, sender, packet_id = None): + self.packet_id = packet_id if packet_id is not None else uuid.uuid4() self.timestamp = timestamp self.receiver = receiver self.sender = sender self.elapsedTime = 0 - self.phase = 'network->cloud' - - def _init_packet_id(self): - self.packet_id = uuid.uuid4() # generate a random UUID def addLatency(self, elapsedTime): self.elapsedTime += elapsedTime def isReady(self, time): - return self.timestamp + self.elapsedTime >= time + return self.arriveTime() >= time def arriveTime(self): return self.timestamp + self.elapsedTime + def deepcopy(self): + return Packet(self.timestamp, self.receiver, self.sender, self.packet_id) + +# Packet priority queue, with packets ordered by arrival time +class PacketQueue(object): + + def __init__(self): + self._packets = [] + + def push(self, packet): + toAdd = (packet.arriveTime(), packet) + heapq.heappush(self._packets, toAdd) + + def pop(self): + return (heapq.heappop(self._packets))[1] + + def next(self): + return self._packets[0][1] + + def empty(self): + return len(self._packets) == 0 + diff --git a/simulator.py b/simulator.py index 097d1fa..f5b3b25 100644 --- a/simulator.py +++ b/simulator.py @@ -1,96 +1,64 @@ import cloud, device, network, packet, data -from packet import Packet -import heapq +from packet import Packet,PacketQueue CLOUD = 0 MAX_PACKETS_PER_STEP = 350 # https://blog.cloudflare.com/how-to-receive-a-million-packets/ class Simulator(object): - def __init__(self, cloud, network, devices, simTime): + def __init__(self, cloud, network, devices): self.endpoints = [cloud] + devices + for i in xrange(self.numEndpoints()): self.endpoints[i].id = i self.network = network + self.packetqueue = PacketQueue() self.time = 0 - self.endTime = simTime - # Priority queue of tuples (timestamp+elapsedtime, packet) - self.activePackets = [] - - def run(self): - totalTimeStr = str(self.endTime) - for timeStep in xrange(self.endTime): - if timeStep % 100 == 0: - print "\r" + "Computing time step: " + str(timeStep) + " out of " + totalTimeStr, - self.runStep(timeStep) + def runFor(self, time): + for t in xrange(self.time, self.time + time + 1): + if t % 256 == 0 or t == time: _printProgress(t, self.time + time + 1) + self.step() def getResults(self): - results = [] - for deviceID in xrange(1, self.numEndpoints()): - results.append(self.endpoints[deviceID].data) - return results + return [self.endpoints[i].data for i in xrange(1, self.numEndpoints())] + + def step(self): + self.time += 1 + + deliveryCounts = [0] * self.numEndpoints() + while not self.packetqueue.empty() \ + and self.packetqueue.next().isReady(self.time): + packet = self.packetqueue.pop() + dest = packet.receiver + if deliveryCounts[dest] <= MAX_PACKETS_PER_STEP: + self.endpoints[dest].receivePacket(packet) + deliveryCounts[dest] += 1 + else: + pass # Packet lost due to buffer overflow - def numEndpoints(self): - return len(self.endpoints) - - def runStep(self, step): - self.deliverReadyPackets(step) - self.updateActivePackets(step) + for endpoint in self.endpoints: + response = endpoint.step() + for packet in response: + networkResponse = self.sendThruNetwork(packet) + if networkResponse is not None: + self.packetqueue.push(networkResponse) def sendThruNetwork(self, packet): senderLocation = self.locationOf(packet.sender) receiverLocation = self.locationOf(packet.receiver) response = self.network.networkDelay(senderLocation, receiverLocation) - if self.dropped(response): + if response is None: return None else: delay = response packet.addLatency(delay) return packet - def dropped(self, response): - return response is None - - def addActivePacket(self, packet): - toAdd = (packet.arriveTime(), packet) - heapq.heappush(self.activePackets, toAdd) - - def nextActivePacket(self): - return self.activePackets[0][1] - - def hasActivePackets(self): - return len(self.activePackets) > 0 - - def popActivePacket(self): - return heapq.heappop(self.activePackets)[1] - - def deliverReadyPackets(self, step): - self.resetDeliveryCounts() - if self.hasActivePackets() and self.nextActivePacket().isReady(step): - self.deliverPacket(self.popActivePacket()) - - def deliverPacket(self, packet): - dest = packet.receiver - if self.deliveryCounts[dest] <= MAX_PACKETS_PER_STEP: - self.endpoints[dest].receivePacket(packet) - self.incrementDeliveryCountFor(dest) - - def incrementDeliveryCountFor(self, deviceID): - self.deliveryCounts[deviceID] += 1 - - def updateActivePackets(self, step): - for endpoint in self.endpoints: - response = endpoint.responseAt(step) - if response is not None: - for packet in response: - self.queuePacket(packet) + def locationOf(self, deviceID): + return self.endpoints[deviceID].location - def queuePacket(self, packet): - networkResponse = self.sendThruNetwork(packet) - if isinstance(networkResponse, Packet): - self.addActivePacket(networkResponse) + def numEndpoints(self): + return len(self.endpoints) - def resetDeliveryCounts(self): - self.deliveryCounts = [0] * len(self.endpoints) +def _printProgress(currstep, total): + print "\rComputing time step: %d to %d" % (currstep, total), - def locationOf(self, deviceID): - return self.endpoints[deviceID].location diff --git a/testlocation.py b/testlocation.py new file mode 100644 index 0000000..353d931 --- /dev/null +++ b/testlocation.py @@ -0,0 +1,8 @@ +from location import Location + +if __name__ == "__main__": + loc1 = Location("Washington") + loc2 = Location("Chicago") + print "Delay from " + loc1.city + " to " + loc2.city + " is" \ + + str(loc1.propagationDelayFrom(loc2)) + " ms" + diff --git a/testnetwork.py b/testnetwork.py new file mode 100644 index 0000000..b61e598 --- /dev/null +++ b/testnetwork.py @@ -0,0 +1,14 @@ +from network import Network +from location import Location + +if __name__ == "__main__": + + loc1 = Location("Washington") + loc2 = Location("Chicago") + + # Timeout test + network = Network(2) # 2 percent chance to drop packet + attempts = 0 + while network.networkDelay(loc1, loc2) != None: + attempts += 1 + print "Network dropped packet after " + str(attempts + 1) + " packets sent" diff --git a/visualizer.py b/visualizer.py index 3c15696..ca320b4 100644 --- a/visualizer.py +++ b/visualizer.py @@ -1,7 +1,4 @@ -from plotly.plotly import iplot as plot -from plotly import graph_objs as graph - -import statistics +import pandas, ggplot class Visualizer(object): # data should be a list of Data, where each list element corresponds to @@ -10,16 +7,10 @@ def __init__(self, data, labels): self.data = data self.labels = labels - def visualize(data, labels, file = 'results/tmp'): - data = [graph.Bar( - x = labels, - y = data - )] - plot(data, filename=file) - def plotAverageLatency(self): - labels = map(str, range(len(self.data))) # "0", "1", "2", ... - data = [device.averageLatency() for device in self.data] - outputFile = 'average_latency' - visualize(data, labels, outputFile) - + averages = [d.averageLatency() for d in self.data] + dat = { "device" : range(1, len(averages) + 1), "average" : averages } + dataframe = pandas.DataFrame(dat) + chart = ggplot.ggplot(ggplot.aes(x="device", weight="average"), dataframe) + \ + ggplot.geom_bar(stat="identity") + chart.show() diff --git a/vr_simulation.py b/vr_simulation.py index 993df73..4d87ac9 100644 --- a/vr_simulation.py +++ b/vr_simulation.py @@ -13,12 +13,12 @@ for i in xrange(1, 11): device = random.choice(DEVICE_OPTS) loc = Location() # Select at random - devices.append(device(i, loc)) + devices.append(device(loc)) # Initialize cloud traffic_level = 'low' cloud_loc = Location() -timeout = 2000 # 2 seconds +timeout = 10000 # 10 seconds num_players = len(devices) cloud = Cloud(traffic_level, cloud_loc, timeout, num_players) @@ -26,13 +26,13 @@ packet_loss_probability = 0 # % chance of network-related dropped packet network = net.TCP(packet_loss_probability); -sim_length = 4 * 1000 # 4 seconds -sim = Simulator(cloud, network, devices, sim_length) +sim = Simulator(cloud, network, devices) + +sim.runFor(20 * 60 * 1000) # Number of milliseconds -sim.run() results = sim.getResults() -viz = Visualizer(results, map(str, range(1,11))) +viz = Visualizer(results, map(lambda n: "Device " + str(n), range(1,11))) viz.plotAverageLatency() # TODO: Be run with passed configurations, create simulator, and produce results