#!/usr/bin/env python

# QoS.py:  illustration of non-FCFS priorities in Resource class

# Communications channel, shared by video and data.  Video packets
# arrive every 1.0 amount of time, and have transmission time 1.0.  Data
# packets follow a Poisson process with intensity parameter DArrRate,
# and their transmission time is uniformaly distributed on {1,2,3,4,5}.
# Video packets have priority over data packets but the latter are not
# pre-emptable.  A video packet is discarded upon arrival if it would be
# sent L or more amount of time late.

# usage:  python QoS.py DArrRate L MaxSimTime

from SimPy.Simulation import *
from random import Random,expovariate

class G:  # globals
   Rnd = Random(12345)
   Chnl = None  # our one channel
   VA = None  # our one video arrivals process
   DA = None  # our one video arrivals process

class ChannelClass(Resource):
   def __init__(self):  
      # note arguments to parent constructor:
      Resource.__init__(self,capacity=1,qType=PriorityQ)
      # if a packet is currently being sent, here is when transmit will end
      self.TimeEndXMit = None
      self.NWaitingVid = 0  # number of video packets in queue

class VidJob(Process):
   def __init__(self):
      Process.__init__(self)  
   def Run(self):
      Lost = False
      # if G.Chnl.TimeEndXMit is None, then no jobs in the system
      # now, so this job will start right away (handled below); 
      # otherwise:
      if G.Chnl.TimeEndXMit != None:  
         # first check for loss
         TimeThisPktStartXMit = G.Chnl.TimeEndXMit + G.Chnl.NWaitingVid
         if TimeThisPktStartXMit - now() > VidArrivals.L:
            Lost = True
            VidArrivals.NLost += 1
            return
      G.Chnl.NWaitingVid += 1
      yield request,self,G.Chnl,1  # higher priority
      G.Chnl.NWaitingVid -= 1
      G.Chnl.TimeEndXMit = now() + 1.0
      yield hold,self,0.999999999999  # to avoid coding "ties"
      G.Chnl.TimeEndXMit = None
      yield release,self,G.Chnl

class VidArrivals(Process):
   L = None  # threshold for discarding packet
   NArrived = 0  # number of video packets arrived
   NLost = 0  # number of video packets lost
   def __init__(self):
      Process.__init__(self)  
   def Run(self):
      while 1:
         yield hold,self,2.0
         VidArrivals.NArrived += 1
         V = VidJob()
         activate(V,V.Run())

class DataJob(Process):
   def __init__(self):
      Process.__init__(self)  
      self.ArrivalTime = now()
   def Run(self):
      yield request,self,G.Chnl,0  # lower priority
      XMitTime = G.Rnd.randint(1,5) - 0.000000000001
      G.Chnl.TimeEndXMit = now() + XMitTime
      yield hold,self,XMitTime
      G.Chnl.TimeEndXMit = None
      DataArrivals.NSent += 1
      DataArrivals.TotWait += now() - self.ArrivalTime
      yield release,self,G.Chnl

class DataArrivals(Process):
   DArrRate = None  #  data arrival rate
   NSent = 0  # number of video packets arrived
   TotWait = 0.0  # number of video packets lost
   def __init__(self):
      Process.__init__(self)  
   def Run(self):
      while 1:
         yield hold,self,G.Rnd.expovariate(DataArrivals.DArrRate)
         D = DataJob()
         activate(D,D.Run()) 

# def ShowStatus():
#    print 'time', now()
#    print 'current xmit ends at', G.Chnl.TimeEndXMit
#    print 'there are now',len(G.Chnl.waitQ), 'in the wait queue'
#    print G.Chnl.NWaitingVid, 'of those are video packets'

def main():
   initialize()  
   VidArrivals.L = float(sys.argv[1])
   DataArrivals.DArrRate = float(sys.argv[2])
   G.Chnl = ChannelClass()
   G.VA = VidArrivals()
   activate(G.VA,G.VA.Run())
   G.DA = DataArrivals()
   activate(G.DA,G.DA.Run())
   MaxSimtime = float(sys.argv[3])
   simulate(until=MaxSimtime)
   print 'proportion of video packets lost:', \
      float(VidArrivals.NLost)/VidArrivals.NArrived
   print 'mean delay for data packets:', \
      DataArrivals.TotWait/DataArrivals.NSent

if __name__ == '__main__':  main()
