Package SimPy :: Module SimulationTrace |
[hide private]
[frames] | no frames]

# Source Code for Module SimPy.SimulationTrace

       1  #!/usr/bin/env python
       2  from SimPy.Lister import *
       3  import bisect
       4  import types
       5  import sys
       6  import new
       7  import random
       8  import inspect
       9
      10  # $Revision: 1.1.1.27 $ $Date: 2007/01/18 14:43:59 $ kgm
      11  """SimulationTrace 1.8 Traces execution of SimPy models.
      12  Implements SimPy Processes, Resources, Buffers, and the backbone simulation
      13  scheduling by coroutine calls. Provides data collection through classes
      14  Monitor and Tally.
      15  Based on generators (Python 2.3 and later)
      16
      17  LICENSE:
      18  Copyright (C) 2002,2005,2006,2007  Klaus G. Muller, Tony Vignaux
      19  mailto: kgmuller@xs4all.nl and Tony.Vignaux@vuw.ac.nz
      20
      21      This library is free software; you can redistribute it and/or
      22      modify it under the terms of the GNU Lesser General Public
      23      License as published by the Free Software Foundation; either
      24      version 2.1 of the License, or (at your option) any later version.
      25
      26      This library is distributed in the hope that it will be useful,
      27      but WITHOUT ANY WARRANTY; without even the implied warranty of
      28      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      29      Lesser General Public License for more details.
      30
      31      You should have received a copy of the GNU Lesser General Public
      32      License along with this library; if not, write to the Free Software
      33      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
      34  END OF LICENSE
      35
      36
      37  **Change history:**
      38      9 May 03: SimulationTrace module based on SimPy 1.3
      39
      40      12/5/2003: Changed eventlist handling from dictionary to bisect
      41
      42      9/6/2003: - Changed eventlist handling from pure dictionary to bisect-
      43                  sorted "timestamps" list of keys, resulting in greatly
      44                  improved performance for models with large
      45                  numbers of event notices with differing event times.
      46                  =========================================================
      47                  This great change was suggested by Prof. Simon Frost.
      48                  Thank you, Simon! This version 1.3 is dedicated to you!
      49                  =========================================================
      50                - Added import of Lister which supports well-structured
      51                  printing of all attributes of Process and Resource instances.
      52
      53      November 03: Brought up to Simulation 1.4alpha
      54
      55      13 Dec 2003: Merged in Monitor and Histogram
      56
      57      27 Feb 2004: Repaired bug in activeQ monitor of class Resource. Now actMon
      58                   correctly records departures from activeQ.
      59
      60      19 May 2004: Added erroneously omitted Histogram class.
      61
      62      5 Sep 2004: Added SimEvents synchronization constructs
      63
      64      17 Sep 2004: Added waituntil synchronization construct
      65
      66      01 Dec 2004: SimPy version 1.5
      67                   Changes in this module: Repaired SimEvents bug re proc.eventsFired
      68
      69      12 Jan 2005: SimPy version 1.5.1
      70                   Changes in this module: Monitor objects now have a default name
      71                                           'a_Monitor'
      72
      73      29 Mar 2005: Start SimPy 1.6: compound "yield request" statements
      74
      75      05 Jun 2005: Fixed bug in _request method -- waitMon did not work properly in
      76                   preemption case
      77
      78      09 Jun 2005: Added test in 'activate' to see whether 'initialize()' was called first.
      79
      80      23 Aug 2005: - Added Tally data collection class
      81                   - Adjusted Resource to work with Tally
      82                   - Redid function allEventNotices() (returns prettyprinted string with event
      83                     times and names of process instances
      84                   - Added function allEventTimes (returns event times of all scheduled events)
      85
      86      16 Mar 2006: - Added Store and Level classes
      87                   - Added 'yield get' and 'yield put'
      88
      89      10 May 2006: - Repaired bug in Store._get method
      90                   - Repaired Level to allow initialBuffered have float value
      91                   - Added type test for Level get parameter 'nrToGet'
      92
      93      06 Jun 2006: - To improve pretty-printed output of 'Level' objects, changed attribute
      94                     _nrBuffered to nrBuffered (synonym for amount property)
      95                   - To improve pretty-printed output of 'Store' objects, added attribute
      96                     buffered (which refers to _theBuffer)
      97
      98      25 Aug 2006: - Start of version 1.8
      99                   - made 'version' public
     100                   - corrected condQ initialization bug
     101
     102      30 Sep 2006: - Introduced checks to ensure capacity of a Buffer > 0
     103                   - Removed from __future__ import (so Python 2.3 or later needed)
     104
     105      15 Oct 2006: - Added code to register all Monitors and all Tallies in variables
     106                     'allMonitors' and 'allTallies'
     107                   - Added function 'startCollection' to activate Monitors and Tallies at a
     108                     specified time (e.g. after warmup period)
     109                   - Moved all test/demo programs to after 'if __name__=="__main__":'.
     110
     111      17 Oct 2006: - Added compound 'put' and 'get' statements for Level and Store.
     112
     113      18 Oct 2006: - Repaired bug: self.eventsFired now gets set after an event fires
     114                     in a compound yield get/put with a waitevent clause (reneging case).
     115
     116      21 Oct 2006: - Introduced Store 'yield get' with a filter function.
     117
     118      22 Oct 2006: - Repaired bug in prettyprinting of Store objects (the buffer
     119                     content==._theBuffer was not shown) by changing ._theBuffer
     120                     to .theBuffer.
     121
     122      04 Dec 2006: - Added printHistogram method to Tally and Monitor (generates
     123                     table-form histogram)
     124
     125      07 Dec 2006: - Changed the __str__ method of Histogram to print a table
     126                     (like printHistogram).
     127
     128      18 Dec 2006: - Added trace printing of Buffers' "unitName" for yield get and put.
     129  """
     130  __TESTING=False
     131  version=__version__="1.8 $Revision: 1.1.1.27 $ $Date: 2007/01/18 14:43:59 $"
     132  if __TESTING:
     133      print "SimPy.SimulationTrace %s" %__version__,
     134      if __debug__:
     135          print "__debug__ on"
     136      else:
     137          print
     138
     139  # yield keywords
     140  hold=1
     141  passivate=2
     142  request=3
     143  release=4
     144  waitevent=5
     145  queueevent=6
     146  waituntil=7
     147  get=8
     148  put=9
     149
     150  _endtime=0
     151  _t=0
     152  _e=None
     153  _stop=True
     154  _wustep=False #controls per event stepping for waituntil construct; not for user API
     155  True=1
     156  False=0
     157  condQ=[]
     158  allMonitors=[]
     159  allTallies=[]
     160
     161 -def initialize():
     162      global _e,_t,_stop,condQ,allMonitors,allTallies
     163      _e=__Evlist()
     164      _t=0
     165      _stop=False
     166      condQ=[]
     167      allMonitors=[]
     168      allTallies=[]
     169
     170 -def now():
     171      return _t
     172
     173 -def stopSimulation():
     174      """Application function to stop simulation run"""
     175      global _stop
     176      _stop=True
     177
     178 -def _startWUStepping():
     179      """Application function to start stepping through simulation for waituntil construct."""
     180      global _wustep
     181      _wustep=True
     182
     183 -def _stopWUStepping():
     184      """Application function to stop stepping through simulation."""
     185      global _wustep
     186      _wustep=False
     187
     188 -class Simerror(Exception):
     189 -    def __init__(self,value):
     190          self.value=value
     191
     192 -    def __str__(self):
     193          return `self.value`
     194
     195 -class FatalSimerror(Simerror):
     196 -    def __init__(self,value):
     197          Simerror.__init__(self,value)
     198          self.value=value
     199
     200 -class Process(Lister):
     201      """Superclass of classes which may use generator functions"""
     202 -    def __init__(self,name="a_process"):
     203          #the reference to this Process instances single process (==generator)
     204          self._nextpoint=None
     205          self.name=name
     206          self._nextTime=None #next activation time
     207          self._remainService=0
     208          self._preempted=0
     209          self._priority={}
     210          self._getpriority={}
     211          self._putpriority={}
     212          self._terminated= False
     213          self._inInterrupt= False
     214          self.eventsFired=[] #events waited/queued for occurred
     215
     216 -    def active(self):
     217          return self._nextTime <> None and not self._inInterrupt
     218
     219 -    def passive(self):
     220          return self._nextTime is None and not self._terminated
     221
     222 -    def terminated(self):
     223          return self._terminated
     224
     225 -    def interrupted(self):
     226          return self._inInterrupt and not self._terminated
     227
     228 -    def queuing(self,resource):
     229          return self in resource.waitQ
     230  # TODO: Review whether this should include waiting for a buffer; or throw away?
     231 -    def cancel(self,victim):
     232          """Application function to cancel all event notices for this Process instance;(should be all event
     233          notices for the _generator_)."""
     234          _e._unpost(whom=victim)
     235
     236 -    def _hold(self,a):
     237          if len(a[0]) == 3:
     238              delay=abs(a[0][2])
     239          else:
     240              delay=0
     241          who=a[1]
     242          self.interruptLeft=delay
     243          self._inInterrupt=False
     244          self.interruptCause=None
     245          _e._post(what=who,at=_t+delay)
     246
     247 -    def _passivate(self,a):
     248          a[0][1]._nextTime=None
     249
     250 -    def interrupt(self,victim):
     251          """Application function to interrupt active processes"""
     252          # can't interrupt terminated/passive/interrupted process
     253          if victim.active():
     254              save=trace._comment
     255              trace._comment=None
     256              victim.interruptCause=self  # self causes interrupt
     257              left=victim._nextTime-_t
     258              victim.interruptLeft=left   # time left in current 'hold'
     259              victim._inInterrupt=True
     260              reactivate(victim)
     261              trace._comment=save
     262              trace.recordInterrupt(self,victim)
     263              return left
     264          else: #victim not active -- can't interrupt
     265              return None
     266
     267 -    def interruptReset(self):
     268          """
     269          Application function for an interrupt victim to get out of
     270          'interrupted' state.
     271          """
     272          self._inInterrupt= False
     273
     274 -    def acquired(self,res):
     275          """Multi-functional test for reneging for 'request' and 'get':
     276          (1)If res of type Resource:
     277              Tests whether resource res was acquired when proces reactivated.
     278              If yes, the parallel wakeup process is killed.
     279              If not, process is removed from res.waitQ (reneging).
     280          (2)If res of type Store:
     281              Tests whether item(s) gotten from Store res.
     282              If yes, the parallel wakeup process is killed.
     283              If no, process is removed from res.getQ
     284          (3)If res of type Level:
     285              Tests whether units gotten from Level res.
     286              If yes, the parallel wakeup process is killed.
     287              If no, process is removed from res.getQ.
     288          """
     289          if isinstance(res,Resource):
     290              test=self in res.activeQ
     291              if test:
     292                  self.cancel(self._holder)
     293              else:
     294                  res.waitQ.remove(self)
     295                  if res.monitored:
     296                      res.waitMon.observe(len(res.waitQ),t=now())
     297              return test
     298          elif isinstance(res,Store):
     299              test=len(self.got)
     300              if test:
     301                  self.cancel(self._holder)
     302                  self.cancel(self._holder)
     303              else:
     304                  res.getQ.remove(self)
     305                  if res.monitored:
     306                      res.getQMon.observe(len(res.getQ),t=now())
     307              return test
     308          elif isinstance(res,Level):
     309              test=not (self.got is None)
     310              if test:
     311                  self.cancel(self._holder)
     312              else:
     313                  res.getQ.remove(self)
     314                  if res.monitored:
     315                      res.getQMon.observe(len(res.getQ),t=now())
     316              return test
     317
     318 -    def stored(self,buffer):
     319          """Test for reneging for 'yield put . . .' compound statement (Level and
     320          Store. Returns True if not reneged.
     321          If self not in buffer.putQ, kill wakeup process, else take self out of
     322          buffer.putQ (reneged)"""
     323          test=self in buffer.putQ
     324          if test:    #reneged
     325              buffer.putQ.remove(self)
     326              if buffer.monitored:
     327                  buffer.putQMon.observe(len(buffer.putQ),t=now())
     328          else:
     329              self.cancel(self._holder)
     330          return not test
     331
     332 -def allEventNotices():
     333      """Returns string with eventlist as
     334              t1: [procname,procname2]
     335              t2: [procname4,procname5, . . . ]
     336              . . .  .
     337      """
     338      ret=""
     339      for t in _e.timestamps:
     340          ret+="%s:%s\n"%(t,[ev.who.name for ev in _e.events[t]])
     341      return ret[:-1]
     342
     343 -def allEventTimes():
     344      """Returns list of all times for which events are scheduled.
     345      """
     346      return _e.timestamps
     347
     348
     349 -class __Evlist:
     350      """Defines event list and operations on it"""
     351 -    def __init__(self):
     352          #has the structure {<time1>:(ev notice1, ev notice2, . . ),
     353          #                   <time2>:(ev notice 3,...),..}
     354          self.events={}
     355          #always sorted list of event times (keys for self.events)
     356          self.timestamps=[]
     357
     358 -    def _post(self,what,at,prior=False):
     359          """Post an event notice for process what for time at"""
     360          # event notices are _Action instances
     361          if at < _t:
     362              raise Simerror("Attempt to schedule event in the past")
     363          if at in self.events:
     364              if prior:
     365                  #before all other event notices at this time
     366                  self.events[at][0:0]= [what]
     367              else:
     368                  self.events[at].append(what)
     369          else: # first event notice at this time
     370              self.events[at]=[what]
     371              bisect.insort(self.timestamps,at)
     372          what.who._nextTime=at
     373
     374 -    def _unpost(self,whom):
     375          """
     376          Search through all event notices at whom's event time and remove whom's
     377          event notice if whom is a suspended process
     378          """
     379          thistime=whom._nextTime
     380          if thistime is None: #check if whom was actually active
     381              return
     382          else:
     383              thislist=self.events[thistime]
     384              for n in thislist:
     385                  if n.who==whom:
     386                      self.events[thistime].remove(n)
     387                      whom._nextTime = None
     388                      if not self.events[thistime]:
     389                          # no other ev notices for thistime
     390                          del(self.events[thistime])
     391                          item_delete_point = bisect.bisect(self.timestamps,
     392                                                thistime)
     393                          del self.timestamps[item_delete_point-1]
     394
     395 -    def _nextev(self):
     396          """Retrieve next event from event list"""
     397          global _t, _stop
     398          if not self.events: raise Simerror("No more events at time %s" %now())
     399          earliest=self.timestamps[0]
     400          _t=max(earliest,_t)
     401          temp=self.events[earliest] #list of actions for this time _t
     402          tempev=temp[0] #first action
     403          del(self.events[earliest][0])
     404          if self.events[earliest]==[]:
     405              del(self.events[earliest]) #delete empty list of actions
     406              del(self.timestamps[0])
     407          if _t > _endtime:
     408              _t = _endtime
     409              _stop = True
     410              return (None,)
     411          tempwho=tempev.who
     412          try:
     413              tt=tempwho._nextpoint.next()
     414          except StopIteration:
     415              tempwho._nextpoint=None
     416              tempwho._terminated=True
     417              tempwho._nextTime=None
     418              tt=None
     419              tempev=tempwho
     420          return tt,tempev
     421
     422 -class _Action:
     423      """Structure (who=process owner, generator=process)"""
     424 -    def __init__(self,who,generator):
     425          self.who=who
     426          self.process=generator
     427
     428 -    def __str__(self):
     429          return "Action %s %s" %((self.who.name,self.process))
     430
     431 -def activate(object,process,at="undefined",delay="undefined",prior=False):
     432      """Application function to activate passive process."""
     433      if _e is None:
     434          raise FatalSimerror\
     435         ("Fatal error: simulation is not initialized (call initialize() first)")
     436      if not (type(process) == types.GeneratorType):
     437          raise FatalSimerror("Activating function which"+
     438                         " is not a generator (contains no 'yield')")
     439      if not object._terminated and not object._nextTime:
     440          #store generator reference in object; needed for reactivation
     441          object._nextpoint=process
     442          if at=="undefined":
     443              at=_t
     444          if delay=="undefined":
     445              zeit=max(_t,at)
     446          else:
     447              zeit=max(_t,_t+delay)
     448          _e._post(_Action(who=object,generator=process),at=zeit,prior=prior)
     449
     450
     451 -def reactivate(obj,at="undefined",delay="undefined",prior=False):
     452      """Application function to reactivate a process which is active,
     453      suspended or passive."""
     454      # Object may be active, suspended or passive
     455      if not obj._terminated:
     456          a=Process("SimPysystem")
     457          a.cancel(obj)
     458          # object now passive
     459          if at=="undefined":
     460              at=_t
     461          if delay=="undefined":
     462              zeit=max(_t,at)
     463          else:
     464              zeit=max(_t,_t+delay)
     465          _e._post(_Action(who=obj,generator=obj._nextpoint),at=zeit,
     466                   prior=prior)
     467
     468 -class Histogram(list):
     469      """ A histogram gathering and sampling class"""
     470
     471 -    def __init__(self,name = '',low=0.0,high=100.0,nbins=10):
     472          list.__init__(self)
     473          self.name  = name
     474          self.low   = float(low)
     475          self.high  = float(high)
     476          self.nbins = nbins
     477          self.binsize=(self.high-self.low)/nbins
     478          self._nrObs=0
     479          self._sum=0
     480          self[:] =[[low+(i-1)*self.binsize,0] for i in range(self.nbins+2)]
     481
     482 -    def addIn(self,y):
     483          """ add a value into the correct bin"""
     484          self._nrObs+=1
     485          self._sum+=y
     486          b = int((y-self.low+self.binsize)/self.binsize)
     487          if b < 0: b = 0
     488          if b > self.nbins+1: b = self.nbins+1
     489          assert 0 <= b <=self.nbins+1,'Histogram.addIn: b out of range: %s'%b
     490          self[b][1]+=1
     491
     492 -    def __str__(self):
     493          histo=self
     494          ylab="value"
     495          nrObs=self._nrObs
     496          width=len(str(nrObs))
     497          res=[]
     498          res.append("<Histogram %s:"%self.name)
     499          res.append("\nNumber of observations: %s"%nrObs)
     500          if nrObs:
     501              su=self._sum
     502              cum=histo[0][1]
     503              fmt="%s"
     504              line="\n%s <= %s < %s: %s (cum: %s/%s%s)"\
     505                   %(fmt,"%s",fmt,"%s","%s","%5.1f","%s")
     506              line1="\n%s%s < %s: %s (cum: %s/%s%s)"\
     507                   %("%s","%s",fmt,"%s","%s","%5.1f","%s")
     508              l1width=len(("%s <= "%fmt)%histo[1][0])
     509              res.append(line1\
     510                         %(" "*l1width,ylab,histo[1][0],str(histo[0][1]).rjust(width),\
     511                           str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
     512                        )
     513              for i in range(1,len(histo)-1):
     514                  cum+=histo[i][1]
     515                  res.append(line\
     516                         %(histo[i][0],ylab,histo[i+1][0],str(histo[i][1]).rjust(width),\
     517                           str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
     518                            )
     519              cum+=histo[-1][1]
     520              linen="\n%s <= %s %s : %s (cum: %s/%s%s)"\
     521                    %(fmt,"%s","%s","%s","%s","%5.1f","%s")
     522              lnwidth=len(("<%s"%fmt)%histo[1][0])
     523              res.append(linen\
     524                         %(histo[-1][0],ylab," "*lnwidth,str(histo[-1][1]).rjust(width),\
     525                         str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
     526                         )
     527          res.append("\n>")
     528          return " ".join(res)
     529
     530 -def startCollection(when=0.0,monitors=None,tallies=None):
     531      """Starts data collection of all designated Monitor and Tally objects
     532      (default=all) at time 'when'.
     533      """
     534      class Starter(Process):
     535          def collect(self,monitors,tallies):
     536              for m in monitors:
     537                  print m.name
     538                  m.reset()
     539              for t in tallies:
     540                  t.reset()
     541              yield hold,self
     542      if monitors is None:
     543          monitors=allMonitors
     544      if tallies is None:
     545          tallies=allTallies
     546      s=Starter()
     547      activate(s,s.collect(monitors=monitors,tallies=tallies),at=when)
     548
     549 -class Monitor(list):
     550      """ Monitored variables
     551
     552      A Class for monitored variables, that is, variables that allow one
     553      to gather simple statistics.  A Monitor is a subclass of list and
     554      list operations can be performed on it. An object is established
     555      using m= Monitor(name = '..'). It can be given a
     556      unique name for use in debugging and in tracing and ylab and tlab
     557      strings for labelling graphs.
     558      """
     559 -    def __init__(self,name='a_Monitor',ylab='y',tlab='t'):
     560          list.__init__(self)
     561          self.startTime = 0.0
     562          self.name = name
     563          self.ylab = ylab
     564          self.tlab = tlab
     565          allMonitors.append(self)
     566
     567 -    def setHistogram(self,name = '',low=0.0,high=100.0,nbins=10):
     568          """Sets histogram parameters.
     569          Must be called before call to getHistogram"""
     570          if name=='':
     571              histname=self.name
     572          else:
     573              histname=name
     574          self.histo=Histogram(name=histname,low=low,high=high,nbins=nbins)
     575
     576 -    def observe(self,y,t=None):
     577          """record y and t"""
     578          if t is  None: t = now()
     579          self.append([t,y])
     580
     581 -    def tally(self,y):
     582          """ deprecated: tally for backward compatibility"""
     583          self.observe(y,0)
     584
     585 -    def accum(self,y,t=None):
     586          """ deprecated:  accum for backward compatibility"""
     587          self.observe(y,t)
     588
     589 -    def reset(self,t=None):
     590          """reset the sums and counts for the monitored variable """
     591          self[:]=[]
     592          if t is None: t = now()
     593          self.startTime = t
     594
     595 -    def tseries(self):
     596          """ the series of measured times"""
     597          return list(zip(*self)[0])
     598
     599 -    def yseries(self):
     600          """ the series of measured values"""
     601          return list(zip(*self)[1])
     602
     603 -    def count(self):
     604          """ deprecated: the number of observations made """
     605          return self.__len__()
     606
     607 -    def total(self):
     608          """ the sum of the y"""
     609          if self.__len__()==0:  return 0
     610          else:
     611              sum = 0.0
     612              for i in range(self.__len__()):
     613                  sum += self[i][1]
     614              return sum # replace by sum() later
     615
     616 -    def mean(self):
     617          """ the simple average of the monitored variable"""
     618          try: return 1.0*self.total()/self.__len__()
     619          except:  print 'SimPy: No observations  for mean'
     620
     621 -    def var(self):
     622          """ the sample variance of the monitored variable """
     623          n = len(self)
     624          tot = self.total()
     625          ssq=0.0
     626          ##yy = self.yseries()
     627          for i in range(self.__len__()):
     628              ssq += self[i][1]**2 # replace by sum() eventually
     629          try: return (ssq - float(tot*tot)/n)/n
     630          except: print 'SimPy: No observations for sample variance'
     631
     632 -    def timeAverage(self,t=None):
     633          """ the time-average of the monitored variable.
     634
     635              If t is used it is assumed to be the current time,
     636              otherwise t =  now()
     637          """
     638          N = self.__len__()
     639          if N  == 0:
     640              print 'SimPy: No observations for timeAverage'
     641              return None
     642
     643          if t is None: t = now()
     644          sum = 0.0
     645          tlast = self.startTime
     646          #print 'DEBUG: timave ',t,tlast
     647          ylast = 0.0
     648          for i in range(N):
     649              ti,yi = self[i]
     650              sum += ylast*(ti-tlast)
     651              tlast = ti
     652              ylast = yi
     653          sum += ylast*(t-tlast)
     654          T = t - self.startTime
     655          if T == 0:
     656               print 'SimPy: No elapsed time for timeAverage'
     657               return None
     658          #print 'DEBUG: timave ',sum,t,T
     659          return sum/float(T)
     660
     661 -    def histogram(self,low=0.0,high=100.0,nbins=10):
     662          """ A histogram of the monitored y data values.
     663          """
     664          h = Histogram(name=self.name,low=low,high=high,nbins=nbins)
     665          ys = self.yseries()
     666          for y in ys: h.addIn(y)
     667          return h
     668
     669 -    def getHistogram(self):
     670          """Returns a histogram based on the parameters provided in
     671          preceding call to setHistogram.
     672          """
     673          ys = self.yseries()
     674          h=self.histo
     675          for y in ys: h.addIn(y)
     676          return h
     677
     678 -    def printHistogram(self,fmt="%s"):
     679          """Returns formatted frequency distribution table string from Monitor.
     680          Precondition: setHistogram must have been called.
     681          fmt==format of bin range values
     682          """
     683          try:
     684              histo=self.getHistogram()
     685          except:
     686              raise FatalSimerror("histogramTable: call setHistogram first"\
     687                                  " for Monitor %s"%self.name)
     688          ylab=self.ylab
     689          nrObs=self.count()
     690          width=len(str(nrObs))
     691          res=[]
     692          res.append("\nHistogram for %s:"%histo.name)
     693          res.append("\nNumber of observations: %s"%nrObs)
     694          su=sum(self.yseries())
     695          cum=histo[0][1]
     696          line="\n%s <= %s < %s: %s (cum: %s/%s%s)"\
     697               %(fmt,"%s",fmt,"%s","%s","%5.1f","%s")
     698          line1="\n%s%s < %s: %s (cum: %s/%s%s)"\
     699               %("%s","%s",fmt,"%s","%s","%5.1f","%s")
     700          l1width=len(("%s <= "%fmt)%histo[1][0])
     701          res.append(line1\
     702                     %(" "*l1width,ylab,histo[1][0],str(histo[0][1]).rjust(width),\
     703                       str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
     704                    )
     705          for i in range(1,len(histo)-1):
     706              cum+=histo[i][1]
     707              res.append(line\
     708                     %(histo[i][0],ylab,histo[i+1][0],str(histo[i][1]).rjust(width),\
     709                       str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
     710                        )
     711          cum+=histo[-1][1]
     712          linen="\n%s <= %s %s : %s (cum: %s/%s%s)"\
     713                %(fmt,"%s","%s","%s","%s","%5.1f","%s")
     714          lnwidth=len(("<%s"%fmt)%histo[1][0])
     715          res.append(linen\
     716                     %(histo[-1][0],ylab," "*lnwidth,str(histo[-1][1]).rjust(width),\
     717                     str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
     718                     )
     719          return " ".join(res)
     720
     721 -class Tally:
     722 -    def __init__(self, name="a_Tally", ylab="y",tlab="t"):
     723          self.name = name
     724          self.ylab = ylab
     725          self.tlab = tlab
     726          self.reset()
     727          self.startTime=0.0
     728          self.histo=None
     729          self._sum_of_squares=0
     730          allTallies.append(self)
     731
     732 -    def setHistogram(self,name = '',low=0.0,high=100.0,nbins=10):
     733          """Sets histogram parameters.
     734          Must be called to prior to observations initiate data collection
     735          for histogram.
     736          """
     737          if name=='':
     738              hname=self.name
     739          else:
     740              hname=name
     741          self.histo=Histogram(name=hname,low=low,high=high,nbins=nbins)
     742
     743 -    def observe(self, y, t=None):
     744          if t is None:
     745              t = now()
     746          self._total += y
     747          self._count += 1
     748          self._integral += (t - self._last_timestamp) * self._last_observation
     749          self._last_timestamp = t
     750          self._last_observation = y
     751          self._sum += y
     752          self._sum_of_squares += y * y
     753          if self.histo:
     754              self.histo.addIn(y)
     755
     756 -    def reset(self, t=None):
     757          if t is None:
     758              t = now()
     759          self.startTime = t
     760          self._last_timestamp = t
     761          self._last_observation = 0.0
     762          self._count = 0
     763          self._total = 0.0
     764          self._integral = 0.0
     765          self._sum = 0.0
     766          self._sum_of_squares = 0.0
     767
     768 -    def count(self):
     769          return self._count
     770
     771 -    def total(self):
     772          return self._total
     773
     774 -    def mean(self):
     775          return 1.0 * self._total / self._count
     776
     777 -    def timeAverage(self,t=None):
     778          if t is None:
     779              t=now()
     780          integ=self._integral+(t - self._last_timestamp) * self._last_observation
     781          if (t > self.startTime):
     782              return 1.0 * integ/(t - self.startTime)
     783          else:
     784              print 'SimPy: No elapsed time for timeAverage'
     785              return None
     786
     787 -    def var(self):
     788          return 1.0 * (self._sum_of_squares - (1.0 * (self._sum * self._sum)\
     789                 / self._count)) / (self._count)
     790
     791 -    def __len__(self):
     792          return self._count
     793
     794 -    def __eq__(self, l):
     795          return len(l) == self._count
     796
     797 -    def getHistogram(self):
     798          return self.histo
     799
     800 -    def printHistogram(self,fmt="%s"):
     801          """Returns formatted frequency distribution table string from Tally.
     802          Precondition: setHistogram must have been called.
     803          fmt==format of bin range values
     804          """
     805          try:
     806              histo=self.getHistogram()
     807          except:
     808              raise FatalSimerror("histogramTable: call setHistogram first"\
     809                                  " for Tally %s"%self.name)
     810          ylab=self.ylab
     811          nrObs=self.count()
     812          width=len(str(nrObs))
     813          res=[]
     814          res.append("\nHistogram for %s:"%histo.name)
     815          res.append("\nNumber of observations: %s"%nrObs)
     816          su=self.total()
     817          cum=histo[0][1]
     818          line="\n%s <= %s < %s: %s (cum: %s/%s%s)"\
     819               %(fmt,"%s",fmt,"%s","%s","%5.1f","%s")
     820          line1="\n%s%s < %s: %s (cum: %s/%s%s)"\
     821               %("%s","%s",fmt,"%s","%s","%5.1f","%s")
     822          l1width=len(("%s <= "%fmt)%histo[1][0])
     823          res.append(line1\
     824                     %(" "*l1width,ylab,histo[1][0],str(histo[0][1]).rjust(width),\
     825                       str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
     826                    )
     827          for i in range(1,len(histo)-1):
     828              cum+=histo[i][1]
     829              res.append(line\
     830                     %(histo[i][0],ylab,histo[i+1][0],str(histo[i][1]).rjust(width),\
     831                       str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
     832                        )
     833          cum+=histo[-1][1]
     834          linen="\n%s <= %s %s : %s (cum: %s/%s%s)"\
     835                %(fmt,"%s","%s","%s","%s","%5.1f","%s")
     836          lnwidth=len(("<%s"%fmt)%histo[1][0])
     837          res.append(linen\
     838                     %(histo[-1][0],ylab," "*lnwidth,str(histo[-1][1]).rjust(width),\
     839                     str(cum).rjust(width),(float(cum)/nrObs)*100,"%")
     840                     )
     841          return " ".join(res)
     842
     843 -class Queue(list):
     844 -    def __init__(self,res,moni):
     845          if not moni is None: #moni==[]:
     846              self.monit=True # True if a type of Monitor/Tally attached
     847          else:
     848              self.monit=False
     849          self.moni=moni # The Monitor/Tally
     850          self.resource=res # the resource/buffer this queue belongs to
     851
     852 -    def enter(self,obj):
     853          pass
     854
     855 -    def leave(self):
     856          pass
     857
     858 -    def takeout(self,obj):
     859          self.remove(obj)
     860          if self.monit:
     861              self.moni.observe(len(self),t=now())
     862
     863 -class FIFO(Queue):
     864 -    def __init__(self,res,moni):
     865          Queue.__init__(self,res,moni)
     866
     867 -    def enter(self,obj):
     868          self.append(obj)
     869          if self.monit:
     870              self.moni.observe(len(self),t=now())
     871
     872 -    def enterGet(self,obj):
     873          self.enter(obj)
     874
     875 -    def enterPut(self,obj):
     876          self.enter(obj)
     877
     878 -    def leave(self):
     879          a= self.pop(0)
     880          if self.monit:
     881              self.moni.observe(len(self),t=now())
     882          return a
     883
     884 -class PriorityQ(FIFO):
     885      """Queue is always ordered according to priority.
     886      Higher value of priority attribute == higher priority.
     887      """
     888 -    def __init__(self,res,moni):
     889          FIFO.__init__(self,res,moni)
     890
     891 -    def enter(self,obj):
     892          """Handles request queue for Resource"""
     893          if len(self):
     894              ix=self.resource
     895              if self[-1]._priority[ix] >= obj._priority[ix]:
     896                  self.append(obj)
     897              else:
     898                  z=0
     899                  while self[z]._priority[ix] >= obj._priority[ix]:
     900                      z += 1
     901                  self.insert(z,obj)
     902          else:
     903              self.append(obj)
     904          if self.monit:
     905              self.moni.observe(len(self),t=now())
     906
     907 -    def enterGet(self,obj):
     908          """Handles getQ in Buffer"""
     909          if len(self):
     910              ix=self.resource
     911              #print "priority:",[x._priority[ix] for x in self]
     912              if self[-1]._getpriority[ix] >= obj._getpriority[ix]:
     913                  self.append(obj)
     914              else:
     915                  z=0
     916                  while self[z]._getpriority[ix] >= obj._getpriority[ix]:
     917                      z += 1
     918                  self.insert(z,obj)
     919          else:
     920              self.append(obj)
     921          if self.monit:
     922              self.moni.observe(len(self),t=now())
     923
     924 -    def enterPut(self,obj):
     925          """Handles putQ in Buffer"""
     926          if len(self):
     927              ix=self.resource
     928              #print "priority:",[x._priority[ix] for x in self]
     929              if self[-1]._putpriority[ix] >= obj._putpriority[ix]:
     930                  self.append(obj)
     931              else:
     932                  z=0
     933                  while self[z]._putpriority[ix] >= obj._putpriority[ix]:
     934                      z += 1
     935                  self.insert(z,obj)
     936          else:
     937              self.append(obj)
     938          if self.monit:
     939              self.moni.observe(len(self),t=now())
     940
     941 -class Resource(Lister):
     942      """Models shared, limited capacity resources with queuing;
     943      FIFO is default queuing discipline.
     944      """
     945
     946 -    def __init__(self,capacity=1,name="a_resource",unitName="units",
     947                   qType=FIFO,preemptable=0,monitored=False,monitorType=Monitor):
     948          """
     949          monitorType={Monitor(default)|Tally}
     950          """
     951          self.name=name          # resource name
     952          self.capacity=capacity  # resource units in this resource
     953          self.unitName=unitName  # type name of resource units
     954          self.n=capacity         # uncommitted resource units
     955          self.monitored=monitored
     956
     957          if self.monitored:           # Monitor waitQ, activeQ
     958              self.actMon=monitorType(name="Active Queue Monitor %s"%self.name,
     959                                   ylab="nr in queue",tlab="time")
     960              monact=self.actMon
     961              self.waitMon=monitorType(name="Wait Queue Monitor %s"%self.name,
     962                                   ylab="nr in queue",tlab="time")
     963              monwait=self.waitMon
     964          else:
     965              monwait=None
     966              monact=None
     967          self.waitQ=qType(self,monwait)
     968          self.preemptable=preemptable
     969          self.activeQ=qType(self,monact)
     970          self.priority_default=0
     971
     972 -    def _request(self,arg):
     973          """Process request event for this resource"""
     974          object=arg[1].who
     975          if len(arg[0]) == 4:        # yield request,self,resource,priority
     976              object._priority[self]=arg[0][3]
     977          else:                       # yield request,self,resource
     978              object._priority[self]=self.priority_default
     979          if self.preemptable and self.n == 0: # No free resource
     980              # test for preemption condition
     981              preempt=object._priority[self] > self.activeQ[-1]._priority[self]
     982              # If yes:
     983              if preempt:
     984                  z=self.activeQ[-1]
     985                  # suspend lowest priority process being served
     986                  ##suspended = z
     987                  # record remaining service time
     988                  z._remainService = z._nextTime - _t
     989                  Process().cancel(z)
     990                  # remove from activeQ
     991                  self.activeQ.remove(z)
     992                  # put into front of waitQ
     993                  self.waitQ.insert(0,z)
     994                  # if self is monitored, update waitQ monitor
     995                  if self.monitored:
     996                      self.waitMon.observe(len(self.waitQ),now())
     997                  # record that it has been preempted
     998                  z._preempted = 1
     999                  # passivate re-queued process
    1000                  z._nextTime=None
    1001                  # assign resource unit to preemptor
    1002                  self.activeQ.enter(object)
    1003                  # post event notice for preempting process
    1004                  _e._post(_Action(who=object,generator=object._nextpoint),
    1005                           at=_t,prior=1)
    1006              else:
    1007                  self.waitQ.enter(object)
    1008                  # passivate queuing process
    1009                  object._nextTime=None
    1010          else: # treat non-preemption case
    1011              if self.n == 0:
    1012                  self.waitQ.enter(object)
    1013                  # passivate queuing process
    1014                  object._nextTime=None
    1015              else:
    1016                  self.n -= 1
    1017                  self.activeQ.enter(object)
    1018                  _e._post(_Action(who=object,generator=object._nextpoint),
    1019                  at=_t,prior=1)
    1020
    1021 -    def _release(self,arg):
    1022          """Process release request for this resource"""
    1023          self.n += 1
    1024          self.activeQ.remove(arg[1].who)
    1025          if self.monitored:
    1026              self.actMon.observe(len(self.activeQ),t=now())
    1027          #reactivate first waiting requestor if any; assign Resource to it
    1028          if self.waitQ:
    1029              object=self.waitQ.leave()
    1030              self.n -= 1             #assign 1 resource unit to object
    1031              self.activeQ.enter(object)
    1032              # if resource preemptable:
    1033              if self.preemptable:
    1034                  # if object had been preempted:
    1035                  if object._preempted:
    1036                      object._preempted = 0
    1037                      # reactivate object delay= remaining service time
    1038                      reactivate(object,delay=object._remainService)
    1039                  # else reactivate right away
    1040                  else:
    1041                      reactivate(object,delay=0,prior=1)
    1042              # else:
    1043              else:
    1044                  reactivate(object,delay=0,prior=1)
    1045          _e._post(_Action(who=arg[1].who,generator=arg[1].who._nextpoint),
    1046                  at=_t,prior=1)
    1047
    1048 -class Buffer(Lister):
    1049      """Abstract class for buffers
    1050      Blocks a process when a put would cause buffer overflow or a get would cause
    1051      buffer underflow.
    1052      Default queuing discipline for blocked processes is FIFO."""
    1053
    1054      priorityDefault=0
    1055 -    def __init__(self,name=None,capacity="unbounded",unitName="units",
    1056                  putQType=FIFO,getQType=FIFO,
    1057                  monitored=False,monitorType=Monitor,initialBuffered=None):
    1058          if capacity=="unbounded": capacity=sys.maxint
    1059          self.capacity=capacity
    1060          self.name=name
    1061          self.putQType=putQType
    1062          self.getQType=getQType
    1063          self.monitored=monitored
    1064          self.initialBuffered=initialBuffered
    1065          self.unitName=unitName
    1066          if self.monitored:
    1067              ## monitor for Producer processes' queue
    1068              self.putQMon=monitorType(name="Producer Queue Monitor %s"%self.name,
    1069                                      ylab="nr in queue",tlab="time")
    1070              ## monitor for Consumer processes' queue
    1071              self.getQMon=monitorType(name="Consumer Queue Monitor %s"%self.name,
    1072                                      ylab="nr in queue",tlab="time")
    1073              ## monitor for nr items in buffer
    1074              self.bufferMon=monitorType(name="Buffer Monitor %s"%self.name,
    1075                                      ylab="nr in buffer",tlab="time")
    1076          else:
    1077              self.putQMon=None
    1078              self.getQMon=None
    1079              self.bufferMon=None
    1080          self.putQ=self.putQType(res=self,moni=self.putQMon)
    1081          self.getQ=self.getQType(res=self,moni=self.getQMon)
    1082          if self.monitored:
    1083              self.putQMon.observe(y=len(self.putQ),t=now())
    1084              self.getQMon.observe(y=len(self.getQ),t=now())
    1085          self._putpriority={}
    1086          self._getpriority={}
    1087
    1088          def _put(self):
    1089              pass
    1090          def _get(self):
    1091              pass
    1092
    1093 -class Level(Buffer):
    1094      """Models buffers for processes putting/getting un-distinguishable items.
    1095      """
    1096 -    def getamount(self):
    1097          return self.nrBuffered
    1098
    1099 -    def gettheBuffer(self):
    1100          return self.nrBuffered
    1101
    1102      theBuffer=property(gettheBuffer)
    1103
    1104 -    def __init__(self,**pars):
    1105          Buffer.__init__(self,**pars)
    1106          if self.name is None:
    1107              self.name="a_level"   ## default name
    1108
    1109          if (type(self.capacity)!=type(1.0) and\
    1110                  type(self.capacity)!=type(1)) or\
    1111                  self.capacity<=0:
    1112                  raise FatalSimerror\
    1113                      ("Level: capacity parameter not a positive number > 0: %s"\
    1114                      %self.initialBuffered)
    1115
    1116          if type(self.initialBuffered)==type(1.0) or\
    1117                  type(self.initialBuffered)==type(1):
    1118              if self.initialBuffered>self.capacity:
    1119                  raise FatalSimerror("initialBuffered exceeds capacity")
    1120              if self.initialBuffered>=0:
    1121                  self.nrBuffered=self.initialBuffered ## nr items initially in buffer
    1122                                          ## buffer is just a counter (int type)
    1123              else:
    1124                  raise FatalSimerror\
    1125                  ("initialBuffered param of Level negative: %s"\
    1126                  %self.initialBuffered)
    1127          elif self.initialBuffered is None:
    1128              self.initialBuffered=0
    1129              self.nrBuffered=0
    1130          else:
    1131              raise FatalSimerror\
    1132                  ("Level: wrong type of initialBuffered (parameter=%s)"\
    1133                  %self.initialBuffered)
    1134          if self.monitored:
    1135              self.bufferMon.observe(y=self.amount,t=now())
    1136      amount=property(getamount)
    1137
    1138 -    def _put(self,arg):
    1139          """Handles put requests for Level instances"""
    1140          obj=arg[1].who
    1141          if len(arg[0]) == 5:        # yield put,self,buff,whattoput,priority
    1142              obj._putpriority[self]=arg[0][4]
    1143              whatToPut=arg[0][3]
    1144          elif len(arg[0]) == 4:      # yield get,self,buff,whattoput
    1145              obj._putpriority[self]=Buffer.priorityDefault #default
    1146              whatToPut=arg[0][3]
    1147          else:                       # yield get,self,buff
    1148              obj._putpriority[self]=Buffer.priorityDefault #default
    1149              whatToPut=1
    1150          if type(whatToPut)!=type(1) and type(whatToPut)!=type(1.0):
    1151              raise FatalSimerror("Level: put parameter not a number")
    1152          if not whatToPut>=0.0:
    1153              raise FatalSimerror("Level: put parameter not positive number")
    1154          whatToPutNr=whatToPut
    1155          if whatToPutNr+self.amount>self.capacity:
    1156              obj._nextTime=None      #passivate put requestor
    1157              obj._whatToPut=whatToPutNr
    1158              self.putQ.enterPut(obj)    #and queue, with size of put
    1159          else:
    1160              self.nrBuffered+=whatToPutNr
    1161              if self.monitored:
    1162                  self.bufferMon.observe(y=self.amount,t=now())
    1163              # service any getters waiting
    1164              # service in queue-order; do not serve second in queue before first
    1165              # has been served
    1166              while len(self.getQ) and self.amount>0:
    1167                  proc=self.getQ[0]
    1168                  if proc._nrToGet<=self.amount:
    1169                      proc.got=proc._nrToGet
    1170                      self.nrBuffered-=proc.got
    1171                      if self.monitored:
    1172                          self.bufferMon.observe(y=self.amount,t=now())
    1173                      self.getQ.takeout(proc) # get requestor's record out of queue
    1174                      _e._post(_Action(who=proc,generator=proc._nextpoint),
    1175                              at=_t) # continue a blocked get requestor
    1176                  else:
    1177                      break
    1178              _e._post(_Action(who=obj,generator=obj._nextpoint),
    1179                      at=_t,prior=1) # continue the put requestor
    1180
    1181 -    def _get(self,arg):
    1182          """Handles get requests for Level instances"""
    1183          obj=arg[1].who
    1184          obj.got=None
    1185          if len(arg[0]) == 5:        # yield get,self,buff,whattoget,priority
    1186              obj._getpriority[self]=arg[0][4]
    1187              nrToGet=arg[0][3]
    1188          elif len(arg[0]) == 4:      # yield get,self,buff,whattoget
    1189              obj._getpriority[self]=Buffer.priorityDefault #default
    1190              nrToGet=arg[0][3]
    1191          else:                       # yield get,self,buff
    1192              obj._getpriority[self]=Buffer.priorityDefault
    1193              nrToGet=1
    1194          if type(nrToGet)!=type(1.0) and type(nrToGet)!=type(1):
    1195              raise FatalSimerror\
    1196                  ("Level: get parameter not a number: %s"%nrToGet)
    1197          if nrToGet<0:
    1198              raise FatalSimerror\
    1199                  ("Level: get parameter not positive number: %s"%nrToGet)
    1200          if self.amount < nrToGet:
    1201              obj._nrToGet=nrToGet
    1202              self.getQ.enterGet(obj)
    1203              # passivate queuing process
    1204              obj._nextTime=None
    1205          else:
    1206              obj.got=nrToGet
    1207              self.nrBuffered-=nrToGet
    1208              if self.monitored:
    1209                  self.bufferMon.observe(y=self.amount,t=now())
    1210              _e._post(_Action(who=obj,generator=obj._nextpoint),
    1211                  at=_t,prior=1)
    1212              # reactivate any put requestors for which space is now available
    1213              # service in queue-order; do not serve second in queue before first
    1214              # has been served
    1215              while len(self.putQ): #test for queued producers
    1216                  proc=self.putQ[0]
    1217                  if proc._whatToPut+self.amount<=self.capacity:
    1218                      self.nrBuffered+=proc._whatToPut
    1219                      if self.monitored:
    1220                          self.bufferMon.observe(y=self.amount,t=now())
    1221                      self.putQ.takeout(proc)#requestor's record out of queue
    1222                      _e._post(_Action(who=proc,generator=proc._nextpoint),
    1223                              at=_t) # continue a blocked put requestor
    1224                  else:
    1225                      break
    1226
    1227 -class Store(Buffer):
    1228      """Models buffers for processes coupled by putting/getting distinguishable
    1229      items.
    1230      Blocks a process when a put would cause buffer overflow or a get would cause
    1231      buffer underflow.
    1232      Default queuing discipline for blocked processes is priority FIFO.
    1233      """
    1234 -    def getnrBuffered(self):
    1235          return len(self.theBuffer)
    1236      nrBuffered=property(getnrBuffered)
    1237
    1238 -    def getbuffered(self):
    1239          return self.theBuffer
    1240      buffered=property(getbuffered)
    1241
    1242 -    def __init__(self,**pars):
    1243          Buffer.__init__(self,**pars)
    1244          self.theBuffer=[]
    1245          if self.name is None:
    1246              self.name="a_store" ## default name
    1247          if type(self.capacity)!=type(1) or self.capacity<=0:
    1248              raise FatalSimerror\
    1249                  ("Store: capacity parameter not a positive integer > 0: %s"\
    1250                      %self.initialBuffered)
    1251          if type(self.initialBuffered)==type([]):
    1252              if len(self.initialBuffered)>self.capacity:
    1253                  raise FatalSimerror("initialBuffered exceeds capacity")
    1254              else:
    1255                  self.theBuffer[:]=self.initialBuffered##buffer==list of objects
    1256          elif self.initialBuffered is None:
    1257              self.theBuffer=[]
    1258          else:
    1259              raise FatalSimerror\
    1260                  ("Store: initialBuffered not a list")
    1261          if self.monitored:
    1262              self.bufferMon.observe(y=self.nrBuffered,t=now())
    1263          self._sort=None
    1264
    1265
    1266
    1267 -    def addSort(self,sortFunc):
    1268          """Adds buffer sorting to this instance of Store. It maintains
    1269          theBuffer sorted by the sortAttr attribute of the objects in the
    1270          buffer.
    1271          The user-provided 'sortFunc' must look like this:
    1272
    1273          def mySort(self,par):
    1274              tmplist=[(x.sortAttr,x) for x in par]
    1275              tmplist.sort()
    1276              return [x for (key,x) in tmplist]
    1277
    1278          """
    1279
    1280          self._sort=new.instancemethod(sortFunc,self,self.__class__)
    1281          self.theBuffer=self._sort(self.theBuffer)
    1282
    1283 -    def _put(self,arg):
    1284          """Handles put requests for Store instances"""
    1285          obj=arg[1].who
    1286          if len(arg[0]) == 5:        # yield put,self,buff,whattoput,priority
    1287              obj._putpriority[self]=arg[0][4]
    1288              whatToPut=arg[0][3]
    1289          elif len(arg[0]) == 4:      # yield put,self,buff,whattoput
    1290              obj._putpriority[self]=Buffer.priorityDefault #default
    1291              whatToPut=arg[0][3]
    1292          else:                       # error, whattoput missing
    1293              raise FatalSimerror("Item to put missing in yield put stmt")
    1294          if type(whatToPut)!=type([]):
    1295              raise FatalSimerror("put parameter is not a list")
    1296          whatToPutNr=len(whatToPut)
    1297          if whatToPutNr+self.nrBuffered>self.capacity:
    1298              obj._nextTime=None      #passivate put requestor
    1299              obj._whatToPut=whatToPut
    1300              self.putQ.enterPut(obj) #and queue, with items to put
    1301          else:
    1302              self.theBuffer.extend(whatToPut)
    1303              if not(self._sort is None):
    1304                  self.theBuffer=self._sort(self.theBuffer)
    1305              if self.monitored:
    1306                  self.bufferMon.observe(y=self.nrBuffered,t=now())
    1307              _e._post(_Action(who=obj,generator=obj._nextpoint),
    1308                  at=_t,prior=1) # continue the put requestor
    1309
    1310              # service any waiting getters
    1311              # service in queue order: do not serve second in queue before first
    1312              # has been served
    1313              block=False
    1314              buffQ=self.getQ
    1315              for getter in buffQ:
    1316                  if self.nrBuffered>0 and len(self.getQ):
    1317                      proc=getter
    1318                      if inspect.isfunction(proc._nrToGet):
    1319                          movCand=proc._nrToGet(self.theBuffer) #predicate parameter
    1320                          if movCand:
    1321                              proc.got=movCand[:]
    1322                              for i in movCand:
    1323                                  self.theBuffer.remove(i)
    1324                              self.getQ.takeout(proc)
    1325                              if self.monitored:
    1326                                  self.bufferMon.observe(y=self.nrBuffered,t=now())
    1327                              _e._post(_Action(who=proc,generator=proc._nextpoint),
    1328                                   at=_t) # continue a blocked get requestor
    1329                      else: #numerical parameter
    1330                          if not block and proc._nrToGet<=self.nrBuffered:
    1331                              nrToGet=proc._nrToGet
    1332                              proc.got=[]
    1333                              proc.got[:]=self.theBuffer[0:nrToGet]
    1334                              self.theBuffer[:]=self.theBuffer[nrToGet:]
    1335                              if self.monitored:
    1336                                  self.bufferMon.observe(y=self.nrBuffered,t=now())
    1337                              # take this get requestor's record out of queue:
    1338                              self.getQ.takeout(proc)
    1339                              _e._post(_Action(who=proc,generator=proc._nextpoint),
    1340                                      at=_t) # continue a blocked get requestor
    1341                          else:
    1342                              # block subsequent numerically specified get's in getQ
    1343                              # to prevent starvation of larger gets by smaller ones
    1344                              block=True
    1345                  else:
    1346                      break # either out of items in buffer or out of getters in getQ
    1347
    1348 -    def _get(self,arg):
    1349          """Handles get requests"""
    1350          filtfunc=None
    1351          obj=arg[1].who
    1352          obj.got=[]                  # the list of items retrieved by 'get'
    1353          if len(arg[0]) == 5:        # yield get,self,buff,whattoget,priority
    1354              obj._getpriority[self]=arg[0][4]
    1355              if inspect.isfunction(arg[0][3]):
    1356                  filtfunc=arg[0][3]
    1357              else:
    1358                  nrToGet=arg[0][3]
    1359          elif len(arg[0]) == 4:      # yield get,self,buff,whattoget
    1360              obj._getpriority[self]=Buffer.priorityDefault #default
    1361              if inspect.isfunction(arg[0][3]):
    1362                  filtfunc=arg[0][3]
    1363              else:
    1364                  nrToGet=arg[0][3]
    1365          else:                       # yield get,self,buff
    1366              obj._getpriority[self]=Buffer.priorityDefault
    1367              nrToGet=1
    1368          if not filtfunc: #number specifies nr items to get
    1369              if nrToGet<0:
    1370                  raise FatalSimerror\
    1371                      ("Store: get parameter not positive number: %s"%nrToGet)
    1372              if self.nrBuffered < nrToGet:
    1373                  obj._nrToGet=nrToGet
    1374                  self.getQ.enterGet(obj)
    1375                  # passivate/block queuing 'get' process
    1376                  obj._nextTime=None
    1377              else:
    1378                  for i in range(nrToGet):
    1379                      obj.got.append(self.theBuffer.pop(0)) # move items from
    1380                                                  # buffer to requesting process
    1381                  if self.monitored:
    1382                      self.bufferMon.observe(y=self.nrBuffered,t=now())
    1383                  _e._post(_Action(who=obj,generator=obj._nextpoint),
    1384                      at=_t,prior=1)
    1385                  # reactivate any put requestors for which space is now available
    1386                  # serve in queue order: do not serve second in queue before first
    1387                  # has been served
    1388                  while len(self.putQ):
    1389                      proc=self.putQ[0]
    1390                      if len(proc._whatToPut)+self.nrBuffered<=self.capacity:
    1391                          for i in proc._whatToPut:
    1392                              self.theBuffer.append(i) #move items to buffer
    1393                          if not(self._sort is None):
    1394                              self.theBuffer=self._sort(self.theBuffer)
    1395                          if self.monitored:
    1396                              self.bufferMon.observe(y=self.nrBuffered,t=now())
    1397                          self.putQ.takeout(proc) # dequeue requestor's record
    1398                          _e._post(_Action(who=proc,generator=proc._nextpoint),
    1399                                  at=_t) # continue a blocked put requestor
    1400                      else:
    1401                          break
    1402          else: # items to get determined by filtfunc
    1403              movCand=filtfunc(self.theBuffer)
    1404              if movCand: # get succeded
    1405                  _e._post(_Action(who=obj,generator=obj._nextpoint),
    1406                      at=_t,prior=1)
    1407                  obj.got=movCand[:]
    1408                  for item in movCand:
    1409                      self.theBuffer.remove(item)
    1410                  if self.monitored:
    1411                      self.bufferMon.observe(y=self.nrBuffered,t=now())
    1412                  # reactivate any put requestors for which space is now available
    1413                  # serve in queue order: do not serve second in queue before first
    1414                  # has been served
    1415                  while len(self.putQ):
    1416                      proc=self.putQ[0]
    1417                      if len(proc._whatToPut)+self.nrBuffered<=self.capacity:
    1418                          for i in proc._whatToPut:
    1419                              self.theBuffer.append(i) #move items to buffer
    1420                          if not(self._sort is None):
    1421                              self.theBuffer=self._sort(self.theBuffer)
    1422                          if self.monitored:
    1423                              self.bufferMon.observe(y=self.nrBuffered,t=now())
    1424                          self.putQ.takeout(proc) # dequeue requestor's record
    1425                          _e._post(_Action(who=proc,generator=proc._nextpoint),
    1426                                  at=_t) # continue a blocked put requestor
    1427                      else:
    1428                          break
    1429              else: # get did not succeed, block
    1430                  obj._nrToGet=filtfunc
    1431                  self.getQ.enterGet(obj)
    1432                  # passivate/block queuing 'get' process
    1433                  obj._nextTime=None
    1434
    1435
    1436
    1437
    1438 -class SimEvent(Lister):
    1439      """Supports one-shot signalling between processes. All processes waiting for an event to occur
    1440      get activated when its occurrence is signalled. From the processes queuing for an event, only
    1441      the first gets activated.
    1442      """
    1443 -    def __init__(self,name="a_SimEvent"):
    1444          self.name=name
    1445          self.waits=[]
    1446          self.queues=[]
    1447          self.occurred=False
    1448          self.signalparam=None
    1449
    1450 -    def signal(self,param=None):
    1451          """Produces a signal to self;
    1452          Fires this event (makes it occur).
    1453          Reactivates ALL processes waiting for this event. (Cleanup waits lists
    1454          of other events if wait was for an event-group (OR).)
    1455          Reactivates the first process for which event(s) it is queuing for
    1456          have fired. (Cleanup queues of other events if wait was for an event-group (OR).)
    1457          """
    1458          self.signalparam=param
    1459
    1460          trace.recordSignal(self)
    1461          if not self.waits and not self.queues:
    1462              self.occurred=True
    1463          else:
    1464              #reactivate all waiting processes
    1465              for p in self.waits:
    1466                  p[0].eventsFired.append(self)
    1467                  reactivate(p[0],prior=True)
    1468                  #delete waits entries for this process in other events
    1469                  for ev in p[1]:
    1470                      if ev!=self:
    1471                          if ev.occurred:
    1472                              p[0].eventsFired.append(ev)
    1473                          for iev in ev.waits:
    1474                              if iev[0]==p[0]:
    1475                                  ev.waits.remove(iev)
    1476                                  break
    1477              self.waits=[]
    1478              if self.queues:
    1479                  proc=self.queues.pop(0)[0]
    1480                  proc.eventsFired.append(self)
    1481                  reactivate(proc,prior=True)
    1482
    1483 -    def _wait(self,par):
    1484          """Consumes a signal if it has occurred, otherwise process 'proc'
    1485          waits for this event.
    1486          """
    1487          proc=par[0][1] #the process issuing the yield waitevent command
    1488          proc.eventsFired=[]
    1489          if not self.occurred:
    1490              self.waits.append([proc,[self]])
    1491              proc._nextTime=None #passivate calling process
    1492          else:
    1493              proc.eventsFired.append(self)
    1494              self.occurred=False
    1495              _e._post(_Action(who=proc,generator=proc._nextpoint),
    1496                  at=_t,prior=1)
    1497
    1498 -    def _waitOR(self,par):
    1499          """Handles waiting for an OR of events in a tuple/list.
    1500          """
    1501          proc=par[0][1]
    1502          evlist=par[0][2]
    1503          proc.eventsFired=[]
    1504          anyoccur=False
    1505          for ev in evlist:
    1506              if ev.occurred:
    1507                  anyoccur=True
    1508                  proc.eventsFired.append(ev)
    1509                  ev.occurred=False
    1510          if anyoccur: #at least one event has fired; continue process
    1511              _e._post(_Action(who=proc,generator=proc._nextpoint),
    1512                  at=_t,prior=1)
    1513
    1514          else: #no event in list has fired, enter process in all 'waits' lists
    1515              proc.eventsFired=[]
    1516              proc._nextTime=None #passivate calling process
    1517              for ev in evlist:
    1518                  ev.waits.append([proc,evlist])
    1519
    1520 -    def _queue(self,par):
    1521          """Consumes a signal if it has occurred, otherwise process 'proc'
    1522          queues for this event.
    1523          """
    1524          proc=par[0][1] #the process issuing the yield queueevent command
    1525          proc.eventsFired=[]
    1526          if not self.occurred:
    1527              self.queues.append([proc,[self]])
    1528              proc._nextTime=None #passivate calling process
    1529          else:
    1530              proc.eventsFired.append(self)
    1531              self.occurred=False
    1532              _e._post(_Action(who=proc,generator=proc._nextpoint),
    1533                  at=_t,prior=1)
    1534
    1535 -    def _queueOR(self,par):
    1536          """Handles queueing for an OR of events in a tuple/list.
    1537          """
    1538          proc=par[0][1]
    1539          evlist=par[0][2]
    1540          proc.eventsFired=[]
    1541          anyoccur=False
    1542          for ev in evlist:
    1543              if ev.occurred:
    1544                  anyoccur=True
    1545                  proc.eventsFired.append(ev)
    1546                  ev.occurred=False
    1547          if anyoccur: #at least one event has fired; continue process
    1548              _e._post(_Action(who=proc,generator=proc._nextpoint),
    1549                  at=_t,prior=1)
    1550
    1551          else: #no event in list has fired, enter process in all 'waits' lists
    1552              proc.eventsFired=[]
    1553              proc._nextTime=None #passivate calling process
    1554              for ev in evlist:
    1555                  ev.queues.append([proc,evlist])
    1556
    1557  ## begin waituntil functionality
    1558 -def _test():
    1559      """
    1560      Gets called by simulate after every event, as long as there are processes
    1561      waiting in condQ for a condition to be satisfied.
    1562      Tests the conditions for all waiting processes. Where condition satisfied,
    1563      reactivates that process immediately and removes it from queue.
    1564      """
    1565      global condQ
    1566      rList=[]
    1567      for el in condQ:
    1568          if el.cond():
    1569              rList.append(el)
    1570              reactivate(el)
    1571      for i in rList:
    1572          condQ.remove(i)
    1573
    1574      if not condQ:
    1575          _stopWUStepping()
    1576
    1577 -def _waitUntilFunc(proc,cond):
    1578      global condQ
    1579      """
    1580      Puts a process 'proc' waiting for a condition into a waiting queue.
    1581      'cond' is a predicate function which returns True if the condition is
    1582      satisfied.
    1583      """
    1584      if not cond():
    1585          condQ.append(proc)
    1586          proc.cond=cond
    1587          _startWUStepping()         #signal 'simulate' that a process is waiting
    1588          # passivate calling process
    1589          proc._nextTime=None
    1590      else:
    1591          #schedule continuation of calling process
    1592          _e._post(_Action(who=proc,generator=proc._nextpoint),
    1593                  at=_t,prior=1)
    1594
    1595
    1596  ##end waituntil functionality
    1597
    1598 -def scheduler(till=0):
    1599      """Schedules Processes/semi-coroutines until time 'till'.
    1600      Deprecated since version 0.5.
    1601      """
    1602      simulate(until=till)
    1603
    1604 -def holdfunc(a):
    1605      a[0][1]._hold(a)
    1606
    1607 -def requestfunc(a):
    1608      """Handles 'yield request,self,res' and 'yield (request,self,res),(<code>,self,par)'.
    1609      <code> can be 'hold' or 'waitevent'.
    1610      """
    1611      if type(a[0][0])==tuple:
    1612          ## Compound yield request statement
    1613          ## first tuple in ((request,self,res),(xx,self,yy))
    1614          b=a[0][0]
    1615          ## b[2]==res (the resource requested)
    1616          ##process the first part of the compound yield statement
    1617          ##a[1] is the _Action instance (a[1].who==process owner, a[1].process==generator)
    1618          b[2]._request(arg=(b,a[1]))
    1619          ##deal with add-on condition to command
    1620          ##Trigger processes for reneging
    1621          class _Holder(Process):
    1622              """Provides timeout process"""
    1623              def trigger(self,delay):
    1624                  yield hold,self,delay
    1625                  if not proc in b[2].activeQ:
    1626                      reactivate(proc)
    1627
    1628          class _EventWait(Process):
    1629              """Provides event waiting process"""
    1630              def trigger(self,event):
    1631                  yield waitevent,self,event
    1632                  if not proc in b[2].activeQ:
    1633                      a[1].who.eventsFired=self.eventsFired
    1634                      reactivate(proc)
    1635
    1636          #activate it
    1637          proc=a[0][0][1] # the process to be woken up
    1638          actCode=a[0][1][0]
    1639          trace.tstop()
    1640          if actCode==hold:
    1641              proc._holder=_Holder(name="RENEGE-hold for %s"%proc.name)
    1642              ##                                          the timeout delay
    1643              activate(proc._holder,proc._holder.trigger(a[0][1][2]))
    1644          elif actCode==waituntil:
    1645              raise FatalSimerror("Illegal code for reneging: waituntil")
    1646          elif actCode==waitevent:
    1647              proc._holder=_EventWait(name="RENEGE-waitevent for %s"%proc.name)
    1648              ##                                          the event
    1649              activate(proc._holder,proc._holder.trigger(a[0][1][2]))
    1650          elif actCode==queueevent:
    1651              raise FatalSimerror("Illegal code for reneging: queueevent")
    1652          else:
    1653              raise FatalSimerror("Illegal code for reneging %s"%actCode)
    1654          trace.tstart()
    1655      else:
    1656          ## Simple yield request command
    1657          a[0][2]._request(a)
    1658
    1659 -def releasefunc(a):
    1660      a[0][2]._release(a)
    1661
    1662 -def passivatefunc(a):
    1663      a[0][1]._passivate(a)
    1664
    1665 -def waitevfunc(a):
    1666      #if waiting for one event only (not a tuple or list)
    1667      evtpar=a[0][2]
    1668      if isinstance(evtpar,SimEvent):
    1669          a[0][2]._wait(a)
    1670      # else, if waiting for an OR of events (list/tuple):
    1671      else: #it should be a list/tuple of events
    1672          # call _waitOR for first event
    1673          evtpar[0]._waitOR(a)
    1674
    1675 -def queueevfunc(a):
    1676      #if queueing for one event only (not a tuple or list)
    1677      evtpar=a[0][2]
    1678      if isinstance(evtpar,SimEvent):
    1679          a[0][2]._queue(a)
    1680      #else, if queueing for an OR of events (list/tuple):
    1681      else: #it should be a list/tuple of events
    1682          # call _queueOR for first event
    1683          evtpar[0]._queueOR(a)
    1684
    1685 -def waituntilfunc(par):
    1686      _waitUntilFunc(par[0][1],par[0][2])
    1687
    1688 -def getfunc(a):
    1689      """Handles 'yield get,self,buffer,what,priority' and
    1690      'yield (get,self,buffer,what,priority),(<code>,self,par)'.
    1691      <code> can be 'hold' or 'waitevent'.
    1692      """
    1693      if type(a[0][0])==tuple:
    1694          ## Compound yield request statement
    1695          ## first tuple in ((request,self,res),(xx,self,yy))
    1696          b=a[0][0]
    1697          ## b[2]==res (the resource requested)
    1698          ##process the first part of the compound yield statement
    1699          ##a[1] is the _Action instance (a[1].who==process owner, a[1].process==generator)
    1700          b[2]._get(arg=(b,a[1]))
    1701          ##deal with add-on condition to command
    1702          ##Trigger processes for reneging
    1703          class _Holder(Process):
    1704              """Provides timeout process"""
    1705              def trigger(self,delay):
    1706                  yield hold,self,delay
    1707                  #if not proc in b[2].activeQ:
    1708                  if proc in b[2].getQ:
    1709                      reactivate(proc)
    1710
    1711          class _EventWait(Process):
    1712              """Provides event waiting process"""
    1713              def trigger(self,event):
    1714                  yield waitevent,self,event
    1715                  if proc in b[2].getQ:
    1716                      a[1].who.eventsFired=self.eventsFired
    1717                      reactivate(proc)
    1718
    1719          #activate it
    1720          proc=a[0][0][1] # the process to be woken up
    1721          actCode=a[0][1][0]
    1722          if actCode==hold:
    1723              proc._holder=_Holder("RENEGE-hold for %s"%proc.name)
    1724              ##                                          the timeout delay
    1725              activate(proc._holder,proc._holder.trigger(a[0][1][2]))
    1726          elif actCode==waituntil:
    1727              raise FatalSimerror("Illegal code for reneging: waituntil")
    1728          elif actCode==waitevent:
    1729              proc._holder=_EventWait(proc.name)
    1730              ##                                          the event
    1731              activate(proc._holder,proc._holder.trigger(a[0][1][2]))
    1732          elif actCode==queueevent:
    1733              raise FatalSimerror("Illegal code for reneging: queueevent")
    1734          else:
    1735              raise FatalSimerror("Illegal code for reneging %s"%actCode)
    1736      else:
    1737          ## Simple yield request command
    1738          a[0][2]._get(a)
    1739
    1740
    1741 -def putfunc(a):
    1742      """Handles 'yield put' (simple and compound hold/waitevent)
    1743      """
    1744      if type(a[0][0])==tuple:
    1745          ## Compound yield request statement
    1746          ## first tuple in ((request,self,res),(xx,self,yy))
    1747          b=a[0][0]
    1748          ## b[2]==res (the resource requested)
    1749          ##process the first part of the compound yield statement
    1750          ##a[1] is the _Action instance (a[1].who==process owner, a[1].process==generator)
    1751          b[2]._put(arg=(b,a[1]))
    1752          ##deal with add-on condition to command
    1753          ##Trigger processes for reneging
    1754          class _Holder(Process):
    1755              """Provides timeout process"""
    1756              def trigger(self,delay):
    1757                  yield hold,self,delay
    1758                  #if not proc in b[2].activeQ:
    1759                  if proc in b[2].putQ:
    1760                      reactivate(proc)
    1761
    1762          class _EventWait(Process):
    1763              """Provides event waiting process"""
    1764              def trigger(self,event):
    1765                  yield waitevent,self,event
    1766                  if proc in b[2].putQ:
    1767                      a[1].who.eventsFired=self.eventsFired
    1768                      reactivate(proc)
    1769
    1770          #activate it
    1771          proc=a[0][0][1] # the process to be woken up
    1772          actCode=a[0][1][0]
    1773          if actCode==hold:
    1774              proc._holder=_Holder("RENEGE-hold for %s"%proc.name)
    1775              ##                                          the timeout delay
    1776              activate(proc._holder,proc._holder.trigger(a[0][1][2]))
    1777          elif actCode==waituntil:
    1778              raise FatalSimerror("Illegal code for reneging: waituntil")
    1779          elif actCode==waitevent:
    1780              proc._holder=_EventWait("RENEGE-waitevent for %s"%proc.name)
    1781              ##                                          the event
    1782              activate(proc._holder,proc._holder.trigger(a[0][1][2]))
    1783          elif actCode==queueevent:
    1784              raise FatalSimerror("Illegal code for reneging: queueevent")
    1785          else:
    1786              raise FatalSimerror("Illegal code for reneging %s"%actCode)
    1787      else:
    1788          ## Simple yield request command
    1789          a[0][2]._put(a)
    1790
    1791 -def simulate(until=0):
    1792      """Schedules Processes/semi-coroutines until time 'until'"""
    1793
    1794      """Gets called once. Afterwards, co-routines (generators) return by
    1795      'yield' with a cargo:
    1796      yield hold, self, <delay>: schedules the "self" process for activation
    1797                                 after <delay> time units.If <,delay> missing,
    1798                                 same as "yield hold,self,0"
    1799
    1800      yield passivate,self    :  makes the "self" process wait to be re-activated
    1801
    1802      yield request,self,<Resource>[,<priority>]: request 1 unit from <Resource>
    1803          with <priority> pos integer (default=0)
    1804
    1805      yield release,self,<Resource> : release 1 unit to <Resource>
    1806
    1807      yield waitevent,self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]:
    1808          wait for one or more of several events
    1809
    1810
    1811      yield queueevent,self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]:
    1812          queue for one or more of several events
    1813
    1814      yield waituntil,self,cond : wait for arbitrary condition
    1815
    1816      yield get,self,<buffer>[,<WhatToGet>[,<priority>]]
    1817          get <WhatToGet> items from buffer (default=1);
    1818          <WhatToGet> can be a pos integer or a filter function
    1819          (Store only)
    1820
    1821      yield put,self,<buffer>[,<WhatToPut>[,priority]]
    1822          put <WhatToPut> items into buffer (default=1);
    1823          <WhatToPut> can be a pos integer (Level) or a list of objects
    1824          (Store)
    1825
    1826      EXTENSIONS:
    1827      Request with timeout reneging:
    1828      yield (request,self,<Resource>),(hold,self,<patience>) :
    1829          requests 1 unit from <Resource>. If unit not acquired in time period
    1830          <patience>, self leaves waitQ (reneges).
    1831
    1832      Request with event-based reneging:
    1833      yield (request,self,<Resource>),(waitevent,self,<eventlist>):
    1834          requests 1 unit from <Resource>. If one of the events in <eventlist> occurs before unit
    1835          acquired, self leaves waitQ (reneges).
    1836
    1837      Get with timeout reneging (for Store and Level):
    1838      yield (get,self,<buffer>,nrToGet etc.),(hold,self,<patience>)
    1839          requests <nrToGet> items/units from <buffer>. If not acquired <nrToGet> in time period
    1840          <patience>, self leaves <buffer>.getQ (reneges).
    1841
    1842      Get with event-based reneging (for Store and Level):
    1843      yield (get,self,<buffer>,nrToGet etc.),(waitevent,self,<eventlist>)
    1844          requests <nrToGet> items/units from <buffer>. If not acquired <nrToGet> before one of
    1845          the events in <eventlist> occurs, self leaves <buffer>.getQ (reneges).
    1846
    1847
    1848
    1849      Event notices get posted in event-list by scheduler after "yield" or by
    1850      "activate"/"reactivate" functions.
    1851
    1852      """
    1853      global _endtime,_e,_stop,_t,_wustep
    1854      _stop=False
    1855
    1856      if _e is None:
    1857          raise FatalSimerror("Simulation not initialized")
    1858      if _e.events == {}:
    1859          message="SimPy: No activities scheduled"
    1860          return message
    1861
    1862      _endtime=until
    1863      message="SimPy: Normal exit"
    1864      dispatch={hold:holdfunc,request:requestfunc,release:releasefunc,
    1865                passivate:passivatefunc,waitevent:waitevfunc,queueevent:queueevfunc,
    1866                waituntil:waituntilfunc,get:getfunc,put:putfunc}
    1867      commandcodes=dispatch.keys()
    1868      commandwords={hold:"hold",request:"request",release:"release",passivate:"passivate",
    1869          waitevent:"waitevent",queueevent:"queueevent",waituntil:"waituntil",
    1870          get:"get",put:"put"}
    1871      while not _stop and _t<=_endtime:
    1872          try:
    1873              a=_e._nextev()
    1874              if not a[0] is None:
    1875                  ## 'a' is tuple "(<yield command>, <action>)"
    1876                  if type(a[0][0])==tuple:
    1877                      ##allowing for yield (request,self,res),(waituntil,self,cond)
    1878                      command=a[0][0][0]
    1879                  else:
    1880                      command = a[0][0]
    1881                  if __debug__:
    1882                      if not command in commandcodes:
    1883                          raise FatalSimerror("Illegal command: yield %s"%command)
    1884                  dispatch[command](a)
    1885                  trace.recordEvent(command,a)
    1886              else:
    1887                  if not a==(None,): #not at endtime!
    1888                      trace.tterminated(a[1])
    1889          except FatalSimerror,error:
    1890              print "SimPy: "+error.value
    1891              sys.exit(1)
    1892          except Simerror,error:
    1893              message="SimPy: "+error.value
    1894              _stop = True
    1895          if _wustep:
    1896              _test()
    1897      _stopWUStepping()
    1898      _e=None
    1899      if not(trace.outfile is sys.stdout):
    1900          trace.outfile.close()
    1901      return message
    1902
    1903 -class Trace(Lister):
    1904      commands={hold:"hold",passivate:"passivate",request:"request",release:"release",
    1905                waitevent:"waitevent",queueevent:"queueevent",waituntil:"waituntil",
    1906              get:"get",put:"put"}
    1907
    1908 -    def __init__(self,start=0,end=10000000000L,toTrace=\
    1909                   ["hold","activate","cancel","reactivate","passivate","request",
    1910                    "release","interrupt","terminated","waitevent","queueevent",
    1911                    "signal","waituntil","put","get"
    1912                   ],outfile=sys.stdout):
    1913
    1914          Trace.commandsproc={hold:Trace.thold,passivate:Trace.tpassivate,
    1915                              request:Trace.trequest,release:Trace.trelease,
    1916                              waitevent:Trace.twaitevent,
    1917                              queueevent:Trace.tqueueevent,
    1918                              waituntil:Trace.twaituntil,
    1919                              get:Trace.tget,put:Trace.tput}
    1920          self.start=start
    1921          self.end=end
    1922          self.toTrace=toTrace
    1923          self.tracego=True
    1924          self.outfile=outfile
    1925          self._comment=None
    1926
    1927 -    def treset(self):
    1928          Trace.commandsproc={hold:Trace.thold,passivatre:Trace.tpassivate,
    1929                              request:Trace.trequest,release:Trace.trelease,
    1930                              waitevent:Trace.twaitevent,
    1931                              queueevent:Trace.tqueueevent,
    1932                              waituntil:Trace.twaituntil,
    1933                              get:Trace.tget,put:Trace.tput}
    1934          self.start=0
    1935          self.end=10000000000L
    1936          self.toTrace=["hold","activate","cancel","reactivate","passivate","request",
    1937                          "release","interrupt","terminated","waitevent","queueevent",
    1938                          "signal","waituntil","put","get"]
    1939          self.tracego=True
    1940          self.outfile=sys.stdout
    1941          self._comment=None
    1942
    1943 -    def tchange(self,**kmvar):
    1944          for v in kmvar.keys():
    1945              if v=="start":
    1946                  self.start=kmvar[v]
    1947              elif v=="end":
    1948                  self.end=kmvar[v]
    1949              elif v=="toTrace":
    1950                  self.toTrace=kmvar[v]
    1951              elif v=="outfile":
    1952                  self.outfile=kmvar[v]
    1953
    1954 -    def tstart(self):
    1955          self.tracego=True
    1956
    1957 -    def tstop(self):
    1958          self.tracego=False
    1959
    1960 -    def ifTrace(self,cond):
    1961          if self.tracego and (self.start <= now() <= self.end) and cond:
    1962              return True
    1963
    1964 -    def thold(self,par):
    1965          try:
    1966              return "delay: %s"%par[0][2]
    1967          except:
    1968              return 0
    1969      thold=classmethod(thold)
    1970
    1971 -    def trequest(self,par):
    1972          res=par[0][2]
    1973          if len(par[0])==4:
    1974              priority=" priority: "+str(par[0][3])
    1975          else:
    1976              priority=" priority: default"
    1977          wQ=[x.name for x in res.waitQ]
    1978          aQ=[x.name for x in res.activeQ]
    1979          return "<%s> %s \n. . .waitQ: %s \n. . .activeQ: %s"%(res.name,priority,wQ,aQ)
    1980      trequest=classmethod(trequest)
    1981
    1982 -    def trelease(self,par):
    1983          res=par[0][2]
    1984          wQ=[x.name for x in res.waitQ]
    1985          aQ=[x.name for x in res.activeQ]
    1986          return "<%s> \n. . .waitQ: %s \n. . .activeQ: %s"%(res.name,wQ,aQ)
    1987      trelease=classmethod(trelease)
    1988
    1989 -    def tpassivate(self,par):
    1990          return ""
    1991      tpassivate=classmethod(tpassivate)
    1992
    1993 -    def tactivate(self,par):
    1994          pass
    1995      tactivate=classmethod(tactivate)
    1996
    1997 -    def twaitevent(self,par):
    1998          evt=par[0][2]
    1999          if type(evt)==list or type(evt)==tuple:
    2000              enames=[x.name for x in evt]
    2001              return "waits for events <%s>"%enames
    2002          else:
    2003              return "waits for event <%s>"%evt.name
    2004      twaitevent=classmethod(twaitevent)
    2005
    2006 -    def tqueueevent(self,par):
    2007          evt=par[0][2]
    2008          if type(evt)==list or type(evt)==tuple:
    2009              enames=[x.name for x in evt]
    2010              return "queues for events <%s>"%enames
    2011          else:
    2012              return "queues for event <%s>"%evt.name
    2013      tqueueevent=classmethod(tqueueevent)
    2014
    2015 -    def tsignal(self,evt):
    2016          wQ=[x.name for x in evt.waits]
    2017          qQ=[x.name for x in evt.queues]
    2018          return "<%s> \n. . .  occurred: %s\n. . .  waiting: %s\n. . .  queueing: %s"\
    2019                  %(evt.name,evt.occurred,wQ,qQ)
    2020          pass
    2021      tsignal=classmethod(tsignal)
    2022
    2023 -    def twaituntil(self,par):
    2024          condition=par[0][2]
    2025          return "for condition <%s>"%condition.func_name
    2026      twaituntil=classmethod(twaituntil)
    2027
    2028 -    def tget(self,par):
    2029          buff=par[0][2]
    2030          if len(par[0])==5:
    2031              priority=" priority: "+str(par[0][4])
    2032          else:
    2033              priority=" priority: default"
    2034          if len(par[0])==3:
    2035              nrToGet=1
    2036          else:
    2037              nrToGet=par[0][3]
    2038          toGet="to get: %s %s from"%(nrToGet,buff.unitName)
    2039          getQ=[x.name for x in buff.getQ]
    2040          putQ=[x.name for x in buff.putQ]
    2041          try:
    2042              inBuffer=buff.amount
    2043          except:
    2044              inBuffer=buff.nrBuffered
    2045          return "%s <%s> %s \n. . .getQ: %s \n. . .putQ: %s \n. . .in buffer: %s"\
    2046              %(toGet,buff.name,priority,getQ,putQ,inBuffer)
    2047      tget=classmethod(tget)
    2048
    2049 -    def tput(self,par):
    2050          buff=par[0][2]
    2051          if len(par[0])==5:
    2052              priority=" priority: "+str(par[0][4])
    2053          else:
    2054              priority=" priority: default"
    2055          if len(par[0])==3:
    2056              nrToPut=1
    2057          else:
    2058              if type(par[0][3])==type([]):
    2059                  nrToPut=len(par[0][3])
    2060              else:
    2061                  nrToPut=par[0][3]
    2062          getQ=[x.name for x in buff.getQ]
    2063          putQ=[x.name for x in buff.putQ]
    2064          toPut="to put: %s %s into"%(nrToPut,buff.unitName)
    2065          try:
    2066              inBuffer=buff.amount
    2067          except:
    2068              inBuffer=buff.nrBuffered
    2069          return "%s <%s> %s \n. . .getQ: %s \n. . .putQ: %s \n. . .in buffer: %s"\
    2070              %(toPut,buff.name,priority,getQ,putQ,inBuffer)
    2071      tput=classmethod(tput)
    2072
    2073 -    def recordEvent(self,command,whole):
    2074          if self.ifTrace(Trace.commands[command] in self.toTrace):
    2075              if not type(whole[0][0])==tuple:
    2076                  try:
    2077                      print >>self.outfile, now(),Trace.commands[command],\
    2078                      "<"+whole[0][1].name+">",\
    2079                        Trace.commandsproc[command](whole)
    2080                  except TypeError:
    2081                      print "l.1649: whole[0][1].name",whole[0][1].name,\
    2082                          Trace.commands[command],Trace.commandsproc[command]
    2083                  Trace.commands[command],Trace.commandsproc[command]
    2084                  if self._comment:
    2085                      print >>self.outfile,"----",self._comment
    2086              else:
    2087                  ##print >>self.outfile, "[WHOLE]",whole,"\n[END WHOLE]"
    2088                  print >>self.outfile, now(),Trace.commands[command],\
    2089                      "<"+whole[0][0][1].name+">"+\
    2090                        Trace.commandsproc[command](whole[0])
    2091                  print >>self.outfile,"|| RENEGE COMMAND:"
    2092                  command1=whole[0][1][0]
    2093                  print >>self.outfile,"||\t",Trace.commands[command1],\
    2094                      "<"+whole[0][1][1].name+">",\
    2095                        Trace.commandsproc[command1]((whole[0][1],))
    2096                  if self._comment:
    2097                      print >>self.outfile,"----",self._comment
    2098
    2099          self._comment=None
    2100
    2101 -    def recordInterrupt(self,who,victim):
    2102          if self.ifTrace("interrupt" in self.toTrace):
    2103              print >>self.outfile,"%s interrupt by: <%s> of: <%s>"%(now(),who.name,victim.name)
    2104              if self._comment:
    2105                  print >>self.outfile,"----",self._comment
    2106          self._comment=None
    2107
    2108 -    def recordCancel(self,who,victim):
    2109          if self.ifTrace("cancel" in self.toTrace):
    2110              print >>self.outfile,"%s cancel by: <%s> of: <%s>"%(now(),who.name,victim.name)
    2111              if self._comment:
    2112                  print >>self.outfile,"----",self._comment
    2113          self._comment=None
    2114
    2115 -    def recordActivate(self,who,when,prior):
    2116          if self.ifTrace("activate" in self.toTrace):
    2117              print >>self.outfile,"%s activate <%s> at time: %s prior: %s"%(now(),who.name,\
    2118                                                                         when, prior)
    2119              if self._comment:
    2120                  print >>self.outfile,"----",self._comment
    2121          self._comment=None
    2122
    2123 -    def recordReactivate(self,who,when,prior):
    2124          if self.ifTrace("reactivate" in self.toTrace):
    2125              print >>self.outfile,"%s reactivate <%s> time: %s prior: %s"%(now(),who.name,\
    2126                                                                          when, prior)
    2127              if self._comment:
    2128                  print >>self.outfile,"----",self._comment
    2129          self._comment=None
    2130
    2131 -    def recordSignal(self,evt):
    2132          if self.ifTrace("signal" in self.toTrace):
    2133              print >>self.outfile,"%s event <%s> is signalled"%(now(),evt.name)
    2134              if self._comment:
    2135                  print >>self.outfile,"----",self._comment
    2136          self._comment=None
    2137
    2138 -    def tterminated(self,who):
    2139          if self.ifTrace("terminated" in self.toTrace):
    2140              print >>self.outfile,"%s <%s> terminated"%(now(),who.name)
    2141              if self._comment:
    2142                  print >>self.outfile,"----",self._comment
    2143          self._comment=None
    2144
    2145 -    def ttext(self,par):
    2146          self._comment=par
    2147
    2148 -class Histogram(list):
    2149      """ A histogram gathering and sampling class"""
    2150
    2151 -    def __init__(self,name = '',low=0.0,high=100.0,nbins=10):
    2152          list.__init__(self)
    2153          self.name  = name
    2154          self.low   = low
    2155          self.high  = high
    2156          self.nbins = nbins
    2157          self.binsize=(self.high-self.low)/nbins
    2158          #self[:] = [[1,2],[3,4]]
    2159          self[:] =[[low+(i-1)*self.binsize,0] for i in range(self.nbins+2)]
    2160          #print '__init__ :', self[0],self
    2161
    2162 -    def addIn(self,y):
    2163          """ add a value into the correct bin"""
    2164          b = int((y-self.low+self.binsize)/self.binsize)
    2165          if b < 0: b = 0
    2166          if b > self.nbins+1: b = self.nbins+1
    2167          assert 0 <= b <=self.nbins+1,'Histogram.addIn: b out of range: %s'%b
    2168          self[b][1]+=1
    2169
    2170
    2171
    2172  if __name__ == "__main__":
    2173      print "SimPy.SimulationTrace %s" %__version__
    2174      ############# Test/demo functions #############
    2175 -    def test_demo():
    2176          class Aa(Process):
    2177              sequIn=[]
    2178              sequOut=[]
    2179              def __init__(self,holdtime,name):
    2180                  Process.__init__(self,name)
    2181                  self.holdtime=holdtime
    2182
    2183              def life(self,priority):
    2184                  for i in range(1):
    2185                      Aa.sequIn.append(self.name)
    2186                      print now(),rrr.name,"waitQ:",len(rrr.waitQ),"activeQ:",\
    2187                            len(rrr.activeQ)
    2188                      print "waitQ: ",[(k.name,k._priority[rrr]) for k in rrr.waitQ]
    2189                      print "activeQ: ",[(k.name,k._priority[rrr]) \
    2190                                 for k in rrr.activeQ]
    2191                      assert rrr.n+len(rrr.activeQ)==rrr.capacity, \
    2192                     "Inconsistent resource unit numbers"
    2193                      print now(),self.name,"requests 1 ", rrr.unitName
    2194                      yield request,self,rrr,priority
    2195                      print now(),self.name,"has 1 ",rrr.unitName
    2196                      print now(),rrr.name,"waitQ:",len(rrr.waitQ),"activeQ:",\
    2197                            len(rrr.activeQ)
    2198                      print now(),rrr.name,"waitQ:",len(rrr.waitQ),"activeQ:",\
    2199                            len(rrr.activeQ)
    2200                      assert rrr.n+len(rrr.activeQ)==rrr.capacity, \
    2201                     "Inconsistent resource unit numbers"
    2202                      yield hold,self,self.holdtime
    2203                      print now(),self.name,"gives up 1",rrr.unitName
    2204                      yield release,self,rrr
    2205                      Aa.sequOut.append(self.name)
    2206                      print now(),self.name,"has released 1 ",rrr.unitName
    2207                      print "waitQ: ",[(k.name,k._priority[rrr]) for k in rrr.waitQ]
    2208                      print now(),rrr.name,"waitQ:",len(rrr.waitQ),"activeQ:",\
    2209                            len(rrr.activeQ)
    2210                      assert rrr.n+len(rrr.activeQ)==rrr.capacity, \
    2211                             "Inconsistent resource unit numbers"
    2212
    2213          class Observer(Process):
    2214              def __init__(self):
    2215                  Process.__init__(self)
    2216
    2217              def observe(self,step,processes,res):
    2218                  while now()<11:
    2219                      for i in processes:
    2220                          print "++ %s process: %s: active:%s, passive:%s, terminated: %s,interrupted:%s, queuing:%s"\
    2221                                %(now(),i.name,i.active(),i.passive(),i.terminated(),i.interrupted(),i.queuing(res))
    2222                      print
    2223                      yield hold,self,step
    2224
    2225          print"\n+++test_demo output"
    2226          print "****First case == priority queue, resource service not preemptable"
    2227          initialize()
    2228          rrr=Resource(5,name="Parking",unitName="space(s)", qType=PriorityQ,
    2229                       preemptable=0)
    2230          procs=[]
    2231          for i in range(10):
    2232              z=Aa(holdtime=i,name="Car "+str(i))
    2233              procs.append(z)
    2234              activate(z,z.life(priority=i))
    2235          o=Observer()
    2236          activate(o,o.observe(1,procs,rrr))
    2237          a=simulate(until=10000)
    2238          print a
    2239          print "Input sequence: ",Aa.sequIn
    2240          print "Output sequence: ",Aa.sequOut
    2241
    2242          print "\n****Second case == priority queue, resource service preemptable"
    2243          initialize()
    2244          rrr=Resource(5,name="Parking",unitName="space(s)", qType=PriorityQ,
    2245                       preemptable=1)
    2246          procs=[]
    2247          for i in range(10):
    2248              z=Aa(holdtime=i,name="Car "+str(i))
    2249              procs.append(z)
    2250              activate(z,z.life(priority=i))
    2251          o=Observer()
    2252          activate(o,o.observe(1,procs,rrr))
    2253          Aa.sequIn=[]
    2254          Aa.sequOut=[]
    2255          a=simulate(until=10000)
    2256          print a
    2257          print "Input sequence: ",Aa.sequIn
    2258          print "Output sequence: ",Aa.sequOut
    2259
    2260 -    def test_interrupt():
    2261          class Bus(Process):
    2262              def __init__(self,name):
    2263                  Process.__init__(self,name)
    2264
    2265              def operate(self,repairduration=0):
    2266                  print now(),">> %s starts" %(self.name)
    2267                  tripleft = 1000
    2268                  while tripleft > 0:
    2269                      yield hold,self,tripleft
    2270                      if self.interrupted():
    2271                          print "interrupted by %s" %self.interruptCause.name
    2272                          print "%s: %s breaks down " %(now(),self.name)
    2273                          tripleft=self.interruptLeft
    2274                          self.interruptReset()
    2275                          print "tripleft ",tripleft
    2276                          reactivate(br,delay=repairduration) # breakdowns only during operation
    2277                          yield hold,self,repairduration
    2278                          print now()," repaired"
    2279                      else:
    2280                          break # no breakdown, ergo bus arrived
    2281                  print now(),"<< %s done" %(self.name)
    2282
    2283          class Breakdown(Process):
    2284              def __init__(self,myBus):
    2285                  Process.__init__(self,name="Breakdown "+myBus.name)
    2286                  self.bus=myBus
    2287
    2288              def breakBus(self,interval):
    2289
    2290                  while True:
    2291                      yield hold,self,interval
    2292                      if self.bus.terminated(): break
    2293                      self.interrupt(self.bus)
    2294
    2295          print"\n\n+++test_interrupt"
    2296          initialize()
    2297          b=Bus("Bus 1")
    2298          activate(b,b.operate(repairduration=20))
    2299          br=Breakdown(b)
    2300          activate(br,br.breakBus(200))
    2301          print simulate(until=4000)
    2302
    2303 -    def testSimEvents():
    2304          class Waiter(Process):
    2305              def waiting(self,theSignal):
    2306                  while True:
    2307                      yield waitevent,self,theSignal
    2308                      print "%s: process '%s' continued after waiting for %s"%(now(),self.name,theSignal.name)
    2309                      yield queueevent,self,theSignal
    2310                      print "%s: process '%s' continued after queueing for %s"%(now(),self.name,theSignal.name)
    2311
    2312          class ORWaiter(Process):
    2313              def waiting(self,signals):
    2314                  while True:
    2315                      yield waitevent,self,signals
    2316                      print now(),"one of %s signals occurred"%[x.name for x in signals]
    2317                      print "\t%s (fired/param)"%[(x.name,x.signalparam) for x in self.eventsFired]
    2318                      yield hold,self,1
    2319
    2320          class Caller(Process):
    2321              def calling(self):
    2322                  while True:
    2323                      signal1.signal("wake up!")
    2324                      print "%s: signal 1 has occurred"%now()
    2325                      yield hold,self,10
    2326                      signal2.signal("and again")
    2327                      signal2.signal("sig 2 again")
    2328                      print "%s: signal1, signal2 have occurred"%now()
    2329                      yield hold,self,10
    2330          print"\n+++testSimEvents output"
    2331          initialize()
    2332          signal1=SimEvent("signal 1")
    2333          signal2=SimEvent("signal 2")
    2334          signal1.signal("startup1")
    2335          signal2.signal("startup2")
    2336          w1=Waiter("waiting for signal 1")
    2337          activate(w1,w1.waiting(signal1))
    2338          w2=Waiter("waiting for signal 2")
    2339          activate(w2,w2.waiting(signal2))
    2340          w3=Waiter("also waiting for signal 2")
    2341          activate(w3,w3.waiting(signal2))
    2342          w4=ORWaiter("waiting for either signal 1 or signal 2")
    2343          activate(w4,w4.waiting([signal1,signal2]),prior=True)
    2344          c=Caller("Caller")
    2345          activate(c,c.calling())
    2346          print simulate(until=100)
    2347
    2348 -    def testwaituntil():
    2349          """
    2350          Demo of waitUntil capability.
    2351
    2352          Scenario:
    2353          Three workers require sets of tools to do their jobs. Tools are shared, scarce
    2354          resources for which they compete.
    2355          """
    2356
    2357
    2358          class Worker(Process):
    2359              def __init__(self,name,heNeeds=[]):
    2360                  Process.__init__(self,name)
    2361                  self.heNeeds=heNeeds
    2362              def work(self):
    2363
    2364                  def workerNeeds():
    2365                      for item in self.heNeeds:
    2366                          if item.n==0:
    2367                              return False
    2368                      return True
    2369
    2370                  while now()<8*60:
    2371                      yield waituntil,self,workerNeeds
    2372                      for item in self.heNeeds:
    2373                          yield request,self,item
    2374                      print "%s %s has %s and starts job" %(now(),self.name,
    2375                          [x.name for x in self.heNeeds])
    2376                      yield hold,self,random.uniform(10,30)
    2377                      for item in self.heNeeds:
    2378                          yield release,self,item
    2379                      yield hold,self,2 #rest
    2380
    2381          print "\n+++ nwaituntil demo output"
    2382          initialize()
    2383          brush=Resource(capacity=1,name="brush")
    2384          ladder=Resource(capacity=2,name="ladder")
    2385          hammer=Resource(capacity=1,name="hammer")
    2386          saw=Resource(capacity=1,name="saw")
    2387          painter=Worker("painter",[brush,ladder])
    2388          activate(painter,painter.work())
    2389          roofer=Worker("roofer",[hammer,ladder,ladder])
    2390          activate(roofer,roofer.work())
    2391          treeguy=Worker("treeguy",[saw,ladder])
    2392          activate(treeguy,treeguy.work())
    2393          for who in (painter,roofer,treeguy):
    2394              print "%s needs %s for his job" %(who.name,[x.name for x in who.heNeeds])
    2395          print
    2396          print simulate(until=9*60)
    2397
    2398      ## -------------------------------------------------------------
    2399      ##                    TEST COMPOUND "YIELD REQUEST" COMMANDS
    2400      ## -------------------------------------------------------------
    2401
    2402      ## -------------------------------------------------------------
    2403      ##             TEST "yield (request,self,res),(hold,self,delay)"
    2404      ##                   == timeout renege
    2405      ## -------------------------------------------------------------
    2406
    2407 -    class JobTO(Process):
    2408         """ Job class for testing timeout reneging
    2409         """
    2410 -       def __init__(self,server=None,name=""):
    2411              Process.__init__(self,name)
    2412              self.res=server
    2413              self.gotResource=None
    2414
    2415 -       def execute(self,timeout,usetime):
    2416              yield (request,self,self.res),(hold,self,timeout)
    2417              if self.acquired(self.res):
    2418                  self.gotResource=True
    2419                  yield hold,self,usetime
    2420                  yield release,self,self.res
    2421              else:
    2422                  self.gotResource=False
    2423
    2424
    2425 -    def testNoTimeout():
    2426          """Test that resource gets acquired without timeout
    2427          """
    2428          res=Resource(name="Server",capacity=1)
    2429          initialize()
    2430          usetime=5
    2431          timeout=1000000
    2432          j1=JobTO(server=res,name="Job_1")
    2433          activate(j1,j1.execute(timeout=timeout,usetime=usetime))
    2434          j2=JobTO(server=res,name="Job_2")
    2435          activate(j2,j2.execute(timeout=timeout,usetime=usetime))
    2436          simulate(until=2*usetime)
    2437          assert now()==2*usetime,"time not ==2*usetime"
    2438          assert j1.gotResource and j2.gotResource,\
    2439              "at least one job failed to get resource"
    2440          assert not (res.waitQ or res.activeQ),\
    2441              "job waiting or using resource"
    2442
    2443 -    def testTimeout1():
    2444          """Test that timeout occurs when resource busy
    2445          """
    2446          res=Resource(name="Server",capacity=1,monitored=True)
    2447          initialize()
    2448          usetime=5
    2449          timeout=3
    2450          j1=JobTO(server=res,name="Job_1")
    2451          activate(j1,j1.execute(timeout=timeout,usetime=usetime))
    2452          j2=JobTO(server=res,name="Job_2")
    2453          activate(j2,j2.execute(timeout=timeout,usetime=usetime))
    2454          simulate(until=2*usetime)
    2455          assert(now()==usetime),"time not ==usetime"
    2456          assert(j1.gotResource),"Job_1 did not get resource"
    2457          assert(not j2.gotResource),"Job_2 did not renege"
    2458          assert not (res.waitQ or res.activeQ),\
    2459              "job waiting or using resource"
    2460
    2461 -    def testTimeout2():
    2462          """Test that timeout occurs when resource has no capacity free
    2463          """
    2464          res=Resource(name="Server",capacity=0)
    2465          initialize()
    2466          usetime=5
    2467          timeout=3
    2468          j1=JobTO(server=res,name="Job_1")
    2469          activate(j1,j1.execute(timeout=timeout,usetime=usetime))
    2470          j2=JobTO(server=res,name="Job_2")
    2471          activate(j2,j2.execute(timeout=timeout,usetime=usetime))
    2472          simulate(until=2*usetime)
    2473          assert now()==timeout,"time %s not == timeout"%now()
    2474          assert not j1.gotResource,"Job_1 got resource"
    2475          assert not j2.gotResource,"Job_2 got resource"
    2476          assert not (res.waitQ or res.activeQ),\
    2477              "job waiting or using resource"
    2478
    2479      ## ------------------------------------------------------------------
    2480      ##             TEST "yield (request,self,res),(waitevent,self,event)"
    2481      ##                   == event renege
    2482      ## ------------------------------------------------------------------
    2483 -    class JobEvt(Process):
    2484         """ Job class for testing event reneging
    2485         """
    2486 -       def __init__(self,server=None,name=""):
    2487              Process.__init__(self,name)
    2488              self.res=server
    2489              self.gotResource=None
    2490
    2491 -       def execute(self,event,usetime):
    2492              yield (request,self,self.res),(waitevent,self,event)
    2493              if self.acquired(self.res):
    2494                  self.gotResource=True
    2495                  yield hold,self,usetime
    2496                  yield release,self,self.res
    2497              else:
    2498                  self.gotResource=False
    2499
    2500 -    class JobEvtMulti(Process):
    2501         """ Job class for testing event reneging with multi-event lists
    2502         """
    2503 -       def __init__(self,server=None,name=""):
    2504              Process.__init__(self,name)
    2505              self.res=server
    2506              self.gotResource=None
    2507
    2508 -       def execute(self,eventlist,usetime):
    2509              yield (request,self,self.res),(waitevent,self,eventlist)
    2510              if self.acquired(self.res):
    2511                  self.gotResource=True
    2512                  yield hold,self,usetime
    2513                  yield release,self,self.res
    2514              else:
    2515                  self.gotResource=False
    2516
    2517 -    class FireEvent(Process):
    2518          """Fires reneging event
    2519          """
    2520 -        def fire(self,fireDelay,event):
    2521              yield hold,self,fireDelay
    2522              event.signal()
    2523
    2524 -    def testNoEvent():
    2525          """Test that processes acquire resource normally if no event fires
    2526          """
    2527          res=Resource(name="Server",capacity=1)
    2528          event=SimEvent("Renege_trigger") #never gets fired
    2529          initialize()
    2530          usetime=5
    2531          j1=JobEvt(server=res,name="Job_1")
    2532          activate(j1,j1.execute(event=event,usetime=usetime))
    2533          j2=JobEvt(server=res,name="Job_2")
    2534          activate(j2,j2.execute(event=event,usetime=usetime))
    2535          simulate(until=2*usetime)
    2536          # Both jobs should get server (in sequence)
    2537          assert now()==2*usetime,"time not ==2*usetime"
    2538          assert j1.gotResource and j2.gotResource,\
    2539              "at least one job failed to get resource"
    2540          assert not (res.waitQ or res.activeQ),\
    2541              "job waiting or using resource"
    2542
    2543 -    def testWaitEvent1():
    2544          """Test that signalled event leads to renege when resource busy
    2545          """
    2546          res=Resource(name="Server",capacity=1)
    2547          initialize()
    2548          event=SimEvent("Renege_trigger")
    2549          usetime=5
    2550          eventtime=1
    2551          j1=JobEvt(server=res,name="Job_1")
    2552          activate(j1,j1.execute(event=event,usetime=usetime))
    2553          j2=JobEvt(server=res,name="Job_2")
    2554          activate(j2,j2.execute(event=event,usetime=usetime))
    2555          f=FireEvent(name="FireEvent")
    2556          activate(f,f.fire(fireDelay=eventtime,event=event))
    2557          simulate(until=2*usetime)
    2558          # Job_1 should get server, Job_2 renege
    2559          assert(now()==usetime),"time not ==usetime"
    2560          assert(j1.gotResource),"Job_1 did not get resource"
    2561          assert(not j2.gotResource),"Job_2 did not renege"
    2562          assert not (res.waitQ or res.activeQ),\
    2563              "job waiting or using resource"
    2564
    2565 -    def testWaitEvent2():
    2566          """Test that renege-triggering event can be one of an event list
    2567          """
    2568          res=Resource(name="Server",capacity=1)
    2569          initialize()
    2570          event1=SimEvent("Renege_trigger_1")
    2571          event2=SimEvent("Renege_trigger_2")
    2572          usetime=5
    2573          eventtime=1 #for both events
    2574          j1=JobEvtMulti(server=res,name="Job_1")
    2575          activate(j1,j1.execute(eventlist=[event1,event2],usetime=usetime))
    2576          j2=JobEvtMulti(server=res,name="Job_2")
    2577          activate(j2,j2.execute(eventlist=[event1,event2],usetime=usetime))
    2578          f1=FireEvent(name="FireEvent_1")
    2579          activate(f1,f1.fire(fireDelay=eventtime,event=event1))
    2580          f2=FireEvent(name="FireEvent_2")
    2581          activate(f2,f2.fire(fireDelay=eventtime,event=event2))
    2582          simulate(until=2*usetime)
    2583          # Job_1 should get server, Job_2 should renege
    2584          assert(now()==usetime),"time not ==usetime"
    2585          assert(j1.gotResource),"Job_1 did not get resource"
    2586          assert(not j2.gotResource),"Job_2 did not renege"
    2587          assert not (res.waitQ or res.activeQ),\
    2588              "job waiting or using resource"
    2589      trace=Trace()
    2590      testNoTimeout()
    2591      testTimeout1()
    2592      testTimeout2()
    2593      testNoEvent()
    2594      testWaitEvent1()
    2595      testWaitEvent2()
    2596      trace=Trace(end=4000)
    2597      test_demo()
    2598      trace=Trace(end=2000)
    2599      test_interrupt()
    2600      testSimEvents()
    2601      testwaituntil()
    2602
    2603
    2604  else:
    2605      trace=Trace()
    2606