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

# Source Code for Module SimPy.SimulationTrace

       1  #!/usr / bin / env python
       2  # $Revision: 163 $ $Date: 2008-12-15 12:47:44 +0100 (Mo, 15 Dez 2008) $ kgm
       3  """SimulationTrace 2.0 Traces execution of SimPy models.
       4  Implements SimPy Processes, Resources, Buffers, and the backbone simulation
       5  scheduling by coroutine calls. Provides data collection through classes
       6  Monitor and Tally.
       7  Based on generators (Python 2.3 and later; not 3.0)
       8
       9  LICENSE:
      10  Copyright (C) 2002, 2005, 2006, 2007, 2008  Klaus G. Muller, Tony Vignaux
      11  mailto: kgmuller@xs4all.nl and Tony.Vignaux@vuw.ac.nz
      12
      13      This library is free software; you can redistribute it and / or
      14      modify it under the terms of the GNU Lesser General Public
      15      License as published by the Free Software Foundation; either
      16      version 2.1 of the License, or (at your option) any later version.
      17
      18      This library is distributed in the hope that it will be useful,
      19      but WITHOUT ANY WARRANTY; without even the implied warranty of
      20      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      21      Lesser General Public License for more details.
      22
      23      You should have received a copy of the GNU Lesser General Public
      24      License along with this library; if not, write to the Free Software
      25      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111 - 1307  USA
      26  END OF LICENSE
      27
      28
      29  **Change history:**
      30      9 May 03: SimulationTrace module based on SimPy 1.3
      31
      32      12 / 5/2003: Changed eventlist handling from dictionary to bisect
      33
      34      9 / 6/2003: - Changed eventlist handling from pure dictionary to bisect-
      35                  sorted 'timestamps' list of keys, resulting in greatly
      36                  improved performance for models with large
      37                  numbers of event notices with differing event times.
      38                  =========================================================
      39                  This great change was suggested by Prof. Simon Frost.
      40                  Thank you, Simon! This version 1.3 is dedicated to you!
      41                  =========================================================
      42                - Added import of Lister which supports well - structured
      43                  printing of all attributes of Process and Resource instances.
      44
      45      November 03: Brought up to Simulation 1.4alpha
      46
      47      13 Dec 2003: Merged in Monitor and Histogram
      48
      49      27 Feb 2004: Repaired bug in activeQ monitor of class Resource. Now actMon
      50                   correctly records departures from activeQ.
      51
      52      19 May 2004: Added erroneously omitted Histogram class.
      53
      54      5 Sep 2004: Added SimEvents synchronization constructs
      55
      56      17 Sep 2004: Added waituntil synchronization construct
      57
      58      01 Dec 2004: SimPy version 1.5
      59                   Changes in this module: Repaired SimEvents bug re proc.eventsFired
      60
      61      12 Jan 2005: SimPy version 1.5.1
      62                   Changes in this module: Monitor objects now have a default name
      63                                           'a_Monitor'
      64
      65      29 Mar 2005: Start SimPy 1.6: compound 'yield request' statements
      66
      67      05 Jun 2005: Fixed bug in _request method -- waitMon did not work properly in
      68                   preemption case
      69
      70      09 Jun 2005: Added test in 'activate' to see whether 'initialize()' was called first.
      71
      72      23 Aug 2005: - Added Tally data collection class
      73                   - Adjusted Resource to work with Tally
      74                   - Redid function allEventNotices() (returns prettyprinted string with event
      75                     times and names of process instances
      76                   - Added function allEventTimes (returns event times of all scheduled events)
      77
      78      16 Mar 2006: - Added Store and Level classes
      79                   - Added 'yield get' and 'yield put'
      80
      81      10 May 2006: - Repaired bug in Store._get method
      82                   - Repaired Level to allow initialBuffered have float value
      83                   - Added type test for Level get parameter 'nrToGet'
      84
      85      06 Jun 2006: - To improve pretty - printed output of 'Level' objects, changed attribute
      86                     _nrBuffered to nrBuffered (synonym for amount property)
      87                   - To improve pretty - printed output of 'Store' objects, added attribute
      88                     buffered (which refers to _theBuffer)
      89
      90      25 Aug 2006: - Start of version 1.8
      91                   - made 'version' public
      92                   - corrected condQ initialization bug
      93
      94      30 Sep 2006: - Introduced checks to ensure capacity of a Buffer > 0
      95                   - Removed from __future__ import (so Python 2.3 or later needed)
      96
      97      15 Oct 2006: - Added code to register all Monitors and all Tallies in variables
      98                     'allMonitors' and 'allTallies'
      99                   - Added function 'startCollection' to activate Monitors and Tallies at a
     100                     specified time (e.g. after warmup period)
     101                   - Moved all test / demo programs to after 'if __name__ == '__main__':'.
     102
     103      17 Oct 2006: - Added compound 'put' and 'get' statements for Level and Store.
     104
     105      18 Oct 2006: - Repaired bug: self.eventsFired now gets set after an event fires
     106                     in a compound yield get / put with a waitevent clause (reneging case).
     107
     108      21 Oct 2006: - Introduced Store 'yield get' with a filter function.
     109
     110      22 Oct 2006: - Repaired bug in prettyprinting of Store objects (the buffer
     111                     content==._theBuffer was not shown) by changing ._theBuffer
     112                     to .theBuffer.
     113
     114      04 Dec 2006: - Added printHistogram method to Tally and Monitor (generates
     115                     table - form histogram)
     116
     117      07 Dec 2006: - Changed the __str__ method of Histogram to print a table
     118                     (like printHistogram).
     119
     120      18 Dec 2006: - Added trace printing of Buffers' 'unitName' for yield get and put.
     121
     122      09 Jun 2007: - Enabled tracing of 'activate' and 'passivate'.
     123                   - Cleaned out all uses of 'object' to prevent name clash.
     124      18 Nov 2007: - Start of 1.9 development
     125                   - Added 'start' method (alternative to activate) to Process
     126
     127      22 Nov 2007: - Major change to event list handling to speed up larger models:
     128                      * Drop dictionary
     129                      * Replace bisect by heapq
     130                      * Mark cancelled event notices in unpost and skip them in
     131                        nextev (great idea of Tony Vignaux))
     132
     133      4 Dec 2007: - Added twVariance calculation for both Monitor and Tally (gav)
     134
     135      5 Dec 2007: - Changed name back to timeVariance (gav)
     136
     137      1 Mar 2008: - Start of 1.9.1 bugfix release
     138                  - Delete circular reference in Process instances when event
     139                    notice has been processed (caused much circular garbage)
     140                  - Added capability for multiple preempts of a process
     141
     142      14 Aug 2008: - Removed most classes / methods and imported them from
     143                     Simulation.py instead (Stefan Scherfke)
     144                   - Moved remaining functions to SimulationTrace and added some
     145                     methods for backward compatibility
     146
     147  """
     148
     149  from SimPy.Lister import *
     150  from SimPy.Simulation import *
     151
     152
     153  __TESTING = False
     154  version = __version__ = '2.0 $Revision: 163 $ $Date: 2008-12-15 12:47:44 +0100 (Mo, 15 Dez 2008) $'
     155  if __TESTING:
     156      print 'SimPy.SimulationTrace %s' %__version__,
     157      if __debug__:
     158          print '__debug__ on'
     159      else:
     160          print
     161
     162 -class SimulationTrace(Simulation):
     163
     164 -    def __init__(self):
     165          Simulation.__init__(self)
     166          self.trace = Trace(sim=self)
     167
     168 -    def initialize(self):
     169          Simulation.initialize(self)
     170          self.trace = Trace(sim=self)
     171
     172 -    def activate(self, obj, process, at = 'undefined', delay = 'undefined', prior = False):
     173          """Application function to activate passive process."""
     174          if self._e is None:
     175              raise FatalSimerror\
     176                      ('Fatal error: simulation is not initialized (call initialize() first)')
     177          if not (type(process) == types.GeneratorType):
     178              raise FatalSimerror('Activating function which'+
     179                  ' is not a generator (contains no \'yield\')')
     180          if not obj._terminated and not obj._nextTime:
     181              #store generator reference in object; needed for reactivation
     182              obj._nextpoint = process
     183              if at == 'undefined':
     184                  at = self._t
     185              if delay == 'undefined':
     186                  zeit = max(self._t, at)
     187              else:
     188                  zeit = max(self._t, self._t + delay)
     189              self.trace.recordActivate(who = obj, when = zeit, prior = prior)
     190              self._e._post(obj, at = zeit, prior = prior)
     191
     192 -    def reactivate(self, obj, at = 'undefined', delay = 'undefined', prior = False):
     193          """Application function to reactivate a process which is active,
     194          suspended or passive."""
     195          # Object may be active, suspended or passive
     196          if not obj._terminated:
     197              a = Process('SimPysystem',sim=self)
     198              a.cancel(obj)
     199              # object now passive
     200              if at == 'undefined':
     201                  at = self._t
     202              if delay == 'undefined':
     203                  zeit = max(self._t, at)
     204              else:
     205                  zeit = max(self._t, self._t + delay)
     206              self.trace.recordReactivate(who = obj, when = zeit, prior = prior)
     207              self._e._post(obj, at = zeit, prior = prior)
     208
     209 -    def simulate(self, until = 0):
     210          """Schedules Processes / semi - coroutines until time 'until'"""
     211
     212          """Gets called once. Afterwards, co - routines (generators) return by
     213          'yield' with a cargo:
     214          yield hold, self, <delay>: schedules the 'self' process for activation
     215                                  after < delay > time units.If <,delay > missing,
     216                                  same as 'yield hold, self, 0'
     217
     218          yield passivate, self    :  makes the 'self' process wait to be re - activated
     219
     220          yield request, self,<Resource > [,<priority>]: request 1 unit from < Resource>
     221              with < priority > pos integer (default = 0)
     222
     223          yield release, self,<Resource> : release 1 unit to < Resource>
     224
     225          yield waitevent, self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]:
     226              wait for one or more of several events
     227
     228
     229          yield queueevent, self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]:
     230              queue for one or more of several events
     231
     232          yield waituntil, self, cond : wait for arbitrary condition
     233
     234          yield get, self,<buffer > [,<WhatToGet > [,<priority>]]
     235              get < WhatToGet > items from buffer (default = 1);
     236              <WhatToGet > can be a pos integer or a filter function
     237              (Store only)
     238
     239          yield put, self,<buffer > [,<WhatToPut > [,priority]]
     240              put < WhatToPut > items into buffer (default = 1);
     241              <WhatToPut > can be a pos integer (Level) or a list of objects
     242              (Store)
     243
     244          EXTENSIONS:
     245          Request with timeout reneging:
     246          yield (request, self,<Resource>),(hold, self,<patience>) :
     247              requests 1 unit from < Resource>. If unit not acquired in time period
     248              <patience>, self leaves waitQ (reneges).
     249
     250          Request with event - based reneging:
     251          yield (request, self,<Resource>),(waitevent, self,<eventlist>):
     252              requests 1 unit from < Resource>. If one of the events in < eventlist > occurs before unit
     253              acquired, self leaves waitQ (reneges).
     254
     255          Get with timeout reneging (for Store and Level):
     256          yield (get, self,<buffer>,nrToGet etc.),(hold, self,<patience>)
     257              requests < nrToGet > items / units from < buffer>. If not acquired < nrToGet > in time period
     258              <patience>, self leaves < buffer>.getQ (reneges).
     259
     260          Get with event - based reneging (for Store and Level):
     261          yield (get, self,<buffer>,nrToGet etc.),(waitevent, self,<eventlist>)
     262              requests < nrToGet > items / units from < buffer>. If not acquired < nrToGet > before one of
     263              the events in < eventlist > occurs, self leaves < buffer>.getQ (reneges).
     264
     265
     266
     267          Event notices get posted in event - list by scheduler after 'yield' or by
     268          'activate' / 'reactivate' functions.
     269
     270          """
     271          self._stop = False
     272
     273          if self._e is None:
     274              raise FatalSimerror('Simulation not initialized')
     275          if self._e._isEmpty():
     276              message = 'SimPy: No activities scheduled'
     277              return message
     278
     279          self._endtime = until
     280          message = 'SimPy: Normal exit'
     281          dispatch={hold:holdfunc, request:requestfunc, release:releasefunc,
     282          passivate:passivatefunc, waitevent:waitevfunc, queueevent:queueevfunc,
     283          waituntil:waituntilfunc, get:getfunc, put:putfunc}
     284          commandcodes = dispatch.keys()
     285          commandwords={hold:'hold', request:'request', release:'release', passivate:'passivate',
     286          waitevent:'waitevent', queueevent:'queueevent', waituntil:'waituntil',
     287          get:'get', put:'put'}
     288          nextev = self._e._nextev ## just a timesaver
     289          while not self._stop and self._t <= self._endtime:
     290              try:
     291                  a = nextev()
     292                  if not a[0] is None:
     293                      ## 'a' is tuple '(<yield command>, <action>)'
     294                      if type(a[0][0]) == tuple:
     295                          ##allowing for yield (request, self, res),(waituntil, self, cond)
     296                          command = a[0][0][0]
     297                      else:
     298                          command = a[0][0]
     299                      if __debug__:
     300                          if not command in commandcodes:
     301                              raise FatalSimerror('Illegal command: yield %s'%command)
     302                      dispatch[command](a)
     303                      self.trace.recordEvent(command, a)
     304                  else:
     305                      if not a == (None,): #not at endtime!
     306                          self.trace.tterminated(a[1])
     307              except FatalSimerror, error:
     308                  print 'SimPy: ' + error.value
     309                  sys.exit(1)
     310              except Simerror, error:
     311                  message = 'SimPy: ' + error.value
     312                  self._stop = True
     313              if self._wustep:
     314                  self._test()
     315          self._stopWUStepping()
     316          self._e = None
     317          if not(self.trace.outfile is sys.stdout):
     318              self.trace.outfile.close()
     319          return message
     320
     321 -def requestfunc(a):
     322      """Handles 'yield request, self, res' and 'yield (request, self, res),(<code>,self, par)'.
     323      <code > can be 'hold' or 'waitevent'.
     324      """
     325      if type(a[0][0]) == tuple:
     326          ## Compound yield request statement
     327          ## first tuple in ((request, self, res),(xx, self, yy))
     328          b = a[0][0]
     329          ## b[2] == res (the resource requested)
     330          ##process the first part of the compound yield statement
     331          ##a[1] is the Process instance
     332          b[2]._request(arg = (b, a[1]))
     333          ##deal with add - on condition to command
     334          ##Trigger processes for reneging
     335          class _Holder(Process):
     336              """Provides timeout process"""
     337              def __init__(self,name,sim=None):
     338                  Process.__init__(self,name=name,sim=sim)
     339              def trigger(self, delay):
     340                  yield hold, self, delay
     341                  if not proc in b[2].activeQ:
     342                      proc.sim.reactivate(proc)
     343
     344          class _EventWait(Process):
     345              """Provides event waiting process"""
     346              def __init__(self,name,sim=None):
     347                  Process.__init__(self,name=name,sim=sim)
     348              def trigger(self, event):
     349                  yield waitevent, self, event
     350                  if not proc in b[2].activeQ:
     351                      proc.eventsFired = self.eventsFired
     352                      proc.sim.reactivate(proc)
     353
     354          #activate it
     355          proc = a[0][0][1] # the process to be woken up
     356          actCode = a[0][1][0]
     357          trace.tstop()
     358          if actCode == hold:
     359              proc._holder = _Holder(name = 'RENEGE - hold for %s'%proc.name,
     360                                     sim=proc.sim)
     361              ##                                          the timeout delay
     362              proc.sim.activate(proc._holder, proc._holder.trigger(a[0][1][2]))
     363          elif actCode == waituntil:
     364              raise FatalSimerror('Illegal code for reneging: waituntil')
     365          elif actCode == waitevent:
     366              proc._holder = _EventWait(name = 'RENEGE - waitevent for %s'\
     367                                        %proc.name,sim=proc.sim)
     368              ##                                          the event
     369              proc.sim.activate(proc._holder, proc._holder.trigger(a[0][1][2]))
     370          elif actCode == queueevent:
     371              raise FatalSimerror('Illegal code for reneging: queueevent')
     372          else:
     373              raise FatalSimerror('Illegal code for reneging %s'%actCode)
     374          trace.tstart()
     375      else:
     376          ## Simple yield request command
     377          a[0][2]._request(a)
     378
     379 -class Trace(Lister):
     380      commands={hold:'hold', passivate:'passivate', request:'request', release:'release',
     381                waitevent:'waitevent', queueevent:'queueevent', waituntil:'waituntil',
     382              get:'get', put:'put'}
     383
     384 -    def __init__(self, start = 0, end = 10000000000L, toTrace=\
     385                   ['hold', 'activate', 'cancel', 'reactivate', 'passivate', 'request',
     386                    'release', 'interrupt', 'terminated', 'waitevent', 'queueevent',
     387                    'signal', 'waituntil', 'put', 'get'
     388                   ],outfile = sys.stdout,sim=None):
     389
     390          Trace.commandsproc={hold:Trace.thold, passivate:Trace.tpassivate,
     391                              request:Trace.trequest, release:Trace.trelease,
     392                              waitevent:Trace.twaitevent,
     393                              queueevent:Trace.tqueueevent,
     394                              waituntil:Trace.twaituntil,
     395                              get:Trace.tget, put:Trace.tput}
     396          if sim is None: sim=Globals.sim
     397          self.sim=sim
     398          self.start = start
     399          self.end = end
     400          self.toTrace = toTrace
     401          self.tracego = True
     402          self.outfile = outfile
     403          self._comment = None
     404
     405 -    def treset(self):
     406          Trace.commandsproc={hold:Trace.thold, passivatre:Trace.tpassivate,
     407                              request:Trace.trequest, release:Trace.trelease,
     408                              waitevent:Trace.twaitevent,
     409                              queueevent:Trace.tqueueevent,
     410                              waituntil:Trace.twaituntil,
     411                              get:Trace.tget, put:Trace.tput}
     412          self.start = 0
     413          self.end = 10000000000L
     414          self.toTrace = ['hold', 'activate', 'cancel', 'reactivate', 'passivate', 'request',
     415                          'release', 'interrupt', 'terminated', 'waitevent', 'queueevent',
     416                          'signal', 'waituntil', 'put', 'get']
     417          self.tracego = True
     418          self.outfile = sys.stdout
     419          self._comment = None
     420
     421 -    def tchange(self,**kmvar):
     422          for v in kmvar.keys():
     423              if v == 'start':
     424                  self.start = kmvar[v]
     425              elif v == 'end':
     426                  self.end = kmvar[v]
     427              elif v == 'toTrace':
     428                  self.toTrace = kmvar[v]
     429              elif v == 'outfile':
     430                  self.outfile = kmvar[v]
     431
     432 -    def tstart(self):
     433          self.tracego = True
     434
     435 -    def tstop(self):
     436          self.tracego = False
     437
     438 -    def ifTrace(self, cond):
     439          if self.tracego and (self.start <= self.sim.now() <= self.end)\
     440             and cond:
     441              return True
     442
     443 -    def thold(self, par):
     444          try:
     445              return 'delay: %s'%par[0][2]
     446          except:
     447              return 0
     448      thold = classmethod(thold)
     449
     450 -    def trequest(self, par):
     451          res = par[0][2]
     452          if len(par[0]) == 4:
     453              priority = ' priority: ' + str(par[0][3])
     454          else:
     455              priority = ' priority: default'
     456          wQ = [x.name for x in res.waitQ]
     457          aQ = [x.name for x in res.activeQ]
     458          return '<%s> %s \n. . .waitQ: %s \n. . .activeQ: %s' % (res.name, priority, wQ, aQ)
     459      trequest = classmethod(trequest)
     460
     461 -    def trelease(self, par):
     462          res = par[0][2]
     463          wQ = [x.name for x in res.waitQ]
     464          aQ = [x.name for x in res.activeQ]
     465          return '<%s> \n. . .waitQ: %s \n. . .activeQ: %s' % (res.name, wQ, aQ)
     466      trelease = classmethod(trelease)
     467
     468 -    def tpassivate(self, par):
     469          return ""
     470      tpassivate = classmethod(tpassivate)
     471
     472 -    def tactivate(self, par):
     473          pass
     474      tactivate = classmethod(tactivate)
     475
     476 -    def twaitevent(self, par):
     477          evt = par[0][2]
     478          if type(evt) == list or type(evt) == tuple:
     479              enames = [x.name for x in evt]
     480              return 'waits for events <%s > '%enames
     481          else:
     482              return 'waits for event <%s > '%evt.name
     483      twaitevent = classmethod(twaitevent)
     484
     485 -    def tqueueevent(self, par):
     486          evt = par[0][2]
     487          if type(evt) == list or type(evt) == tuple:
     488              enames = [x.name for x in evt]
     489              return 'queues for events <%s > '%enames
     490          else:
     491              return 'queues for event <%s > '%evt.name
     492      tqueueevent = classmethod(tqueueevent)
     493
     494 -    def tsignal(self, evt):
     495          wQ = [x.name for x in evt.waits]
     496          qQ = [x.name for x in evt.queues]
     497          return '<%s> \n. . .  occurred: %s\n. . .  waiting: %s\n. . .  queueing: %s'\
     498                  %(evt.name, evt.occurred, wQ, qQ)
     499          pass
     500      tsignal = classmethod(tsignal)
     501
     502 -    def twaituntil(self, par):
     503          condition = par[0][2]
     504          return 'for condition <%s > '%condition.func_name
     505      twaituntil = classmethod(twaituntil)
     506
     507 -    def tget(self, par):
     508          buff = par[0][2]
     509          if len(par[0]) == 5:
     510              priority = ' priority: ' + str(par[0][4])
     511          else:
     512              priority = ' priority: default'
     513          if len(par[0]) == 3:
     514              nrToGet = 1
     515          else:
     516              nrToGet = par[0][3]
     517          toGet = 'to get: %s %s from' % (nrToGet, buff.unitName)
     518          getQ = [x.name for x in buff.getQ]
     519          putQ = [x.name for x in buff.putQ]
     520          try:
     521              inBuffer = buff.amount
     522          except:
     523              inBuffer = buff.nrBuffered
     524          return '%s <%s> %s \n. . .getQ: %s \n. . .putQ: %s \n. . .in buffer: %s'\
     525              %(toGet, buff.name, priority, getQ, putQ, inBuffer)
     526      tget = classmethod(tget)
     527
     528 -    def tput(self, par):
     529          buff = par[0][2]
     530          if len(par[0]) == 5:
     531              priority = ' priority: ' + str(par[0][4])
     532          else:
     533              priority = ' priority: default'
     534          if len(par[0]) == 3:
     535              nrToPut = 1
     536          else:
     537              if type(par[0][3]) == type([]):
     538                  nrToPut = len(par[0][3])
     539              else:
     540                  nrToPut = par[0][3]
     541          getQ = [x.name for x in buff.getQ]
     542          putQ = [x.name for x in buff.putQ]
     543          toPut = 'to put: %s %s into' % (nrToPut, buff.unitName)
     544          try:
     545              inBuffer = buff.amount
     546          except:
     547              inBuffer = buff.nrBuffered
     548          return '%s <%s> %s \n. . .getQ: %s \n. . .putQ: %s \n. . .in buffer: %s'\
     549              %(toPut, buff.name, priority, getQ, putQ, inBuffer)
     550      tput = classmethod(tput)
     551
     552 -    def recordEvent(self, command, whole):
     553          if self.ifTrace(Trace.commands[command] in self.toTrace):
     554              if not type(whole[0][0]) == tuple:
     555                  try:
     556                      print >> self.outfile, whole[0][1].sim.now(),\
     557                               Trace.commands[command],\
     558                               ' < ' + whole[0][1].name + ' > ',\
     559                               Trace.commandsproc[command](whole)
     560                  except TypeError:
     561                      print 'l.1649: whole[0][1].name', whole[0][1].name,\
     562                          Trace.commands[command],Trace.commandsproc[command]
     563                  Trace.commands[command],Trace.commandsproc[command]
     564                  if self._comment:
     565                      print >> self.outfile, '----', self._comment
     566              else:
     567                  ##print >> self.outfile, '[WHOLE]', whole, '\n[END WHOLE]'
     568                  print >> self.outfile, whole[0][0][1].sim.now(),\
     569                           Trace.commands[command],\
     570                           ' < ' + whole[0][0][1].name + ' > '+\
     571                           Trace.commandsproc[command](whole[0])
     572                  print >> self.outfile, '|| RENEGE COMMAND:'
     573                  command1 = whole[0][1][0]
     574                  print >> self.outfile, '||\t', Trace.commands[command1],\
     575                      ' < ' + whole[0][1][1].name + ' > ',\
     576                        Trace.commandsproc[command1]((whole[0][1],))
     577                  if self._comment:
     578                      print >> self.outfile, '----', self._comment
     579
     580          self._comment = None
     581
     582 -    def recordInterrupt(self, who, victim):
     583          if self.ifTrace('interrupt' in self.toTrace):
     584              print >> self.outfile, '%s interrupt by: <%s > of: <%s >'\
     585                                     %(who.sim.now(),who.name, victim.name)
     586              if self._comment:
     587                  print >> self.outfile, '----', self._comment
     588          self._comment = None
     589
     590 -    def recordCancel(self, who, victim):
     591          if self.ifTrace('cancel' in self.toTrace):
     592              print >> self.outfile, '%s cancel by: <%s > of: <%s > '\
     593              %(who.sim.now(),who.name, victim.name)
     594              if self._comment:
     595                  print >> self.outfile, '----', self._comment
     596          self._comment = None
     597
     598 -    def recordActivate(self, who, when, prior):
     599          if self.ifTrace('activate' in self.toTrace):
     600              print >> self.outfile, '%s activate <%s > at time: %s prior: %s'\
     601                       %(who.sim.now(),who.name,when, prior)
     602              if self._comment:
     603                  print >> self.outfile, '----', self._comment
     604          self._comment = None
     605
     606 -    def recordReactivate(self, who, when, prior):
     607          if self.ifTrace('reactivate' in self.toTrace):
     608              print >> self.outfile, '%s reactivate <%s > time: %s prior: %s'\
     609                       %(who.sim.now(),who.name,when, prior)
     610              if self._comment:
     611                  print >> self.outfile, '----', self._comment
     612          self._comment = None
     613
     614 -    def recordSignal(self, evt):
     615          if self.ifTrace('signal' in self.toTrace):
     616              print >> self.outfile, '%s event <%s > is signalled' \
     617                                     %(evt.sim.now(),evt.name)
     618              if self._comment:
     619                  print >> self.outfile, '----', self._comment
     620          self._comment = None
     621
     622 -    def tterminated(self, who):
     623          if self.ifTrace('terminated' in self.toTrace):
     624              print >> self.outfile, '%s <%s > terminated'\
     625                       %(who.sim.now(),who.name)
     626              if self._comment:
     627                  print >> self.outfile, '----', self._comment
     628          self._comment = None
     629
     630 -    def ttext(self, par):
     631          self._comment = par
     632
     633  # For backward compatibility
     634  Globals.sim = SimulationTrace()
     635  trace = Globals.sim.trace
     636  # End backward compatibility
     637
     638  if __name__ == '__main__':
     639      print 'SimPy.SimulationTrace %s' %__version__
     640      ############# Test / demo functions #############
     641 -    def test_demo():
     642          class Aa(Process):
     643              sequIn = []
     644              sequOut = []
     645              def __init__(self, holdtime, name,sim=None):
     646                  Process.__init__(self, name,sim=sim)
     647                  self.holdtime = holdtime
     648
     649              def life(self, priority):
     650                  for i in range(1):
     651                      Aa.sequIn.append(self.name)
     652                      print self.sim.now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
     653                            len(rrr.activeQ)
     654                      print 'waitQ: ',[(k.name, k._priority[rrr]) for k in rrr.waitQ]
     655                      print 'activeQ: ',[(k.name, k._priority[rrr]) \
     656                                 for k in rrr.activeQ]
     657                      assert rrr.n + len(rrr.activeQ) == rrr.capacity, \
     658                     'Inconsistent resource unit numbers'
     659                      print self.sim.now(),self.name, 'requests 1 ', rrr.unitName
     660                      yield request, self, rrr, priority
     661                      print self.sim.now(),self.name, 'has 1 ', rrr.unitName
     662                      print self.sim.now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
     663                            len(rrr.activeQ)
     664                      print self.sim.now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
     665                            len(rrr.activeQ)
     666                      assert rrr.n + len(rrr.activeQ) == rrr.capacity, \
     667                     'Inconsistent resource unit numbers'
     668                      yield hold, self, self.holdtime
     669                      print self.sim.now(),self.name, 'gives up 1', rrr.unitName
     670                      yield release, self, rrr
     671                      Aa.sequOut.append(self.name)
     672                      print self.sim.now(),self.name, 'has released 1 ', rrr.unitName
     673                      print 'waitQ: ',[(k.name, k._priority[rrr]) for k in rrr.waitQ]
     674                      print self.sim.now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
     675                            len(rrr.activeQ)
     676                      assert rrr.n + len(rrr.activeQ) == rrr.capacity, \
     677                             'Inconsistent resource unit numbers'
     678
     679          class Observer(Process):
     680              def __init__(self,**vars):
     681                  Process.__init__(self,**vars)
     682
     683              def observe(self, step, processes, res):
     684                  while self.sim.now() < 11:
     685                      for i in processes:
     686                          print '++ %s process: %s: active:%s, passive:%s, terminated: %s, interrupted:%s, queuing:%s'\
     687                                %(self.sim.now(),i.name, i.active(),i.passive(),\
     688                                  i.terminated(),i.interrupted(),i.queuing(res))
     689                      print
     690                      yield hold, self, step
     691
     692          print'\n+++test_demo output'
     693          print '****First case == priority queue, resource service not preemptable'
     694          s=SimulationTrace()
     695          s.initialize()
     696          rrr = Resource(5, name = 'Parking', unitName = 'space(s)', qType = PriorityQ,
     697                       preemptable = 0,sim=s)
     698          procs = []
     699          for i in range(10):
     700              z = Aa(holdtime = i, name = 'Car ' + str(i),sim=s)
     701              procs.append(z)
     702              s.activate(z, z.life(priority = i))
     703          o = Observer(sim=s)
     704          s.activate(o, o.observe(1, procs, rrr))
     705          a = s.simulate(until = 10000)
     706          print a
     707          print 'Input sequence: ', Aa.sequIn
     708          print 'Output sequence: ', Aa.sequOut
     709
     710          print '\n****Second case == priority queue, resource service preemptable'
     711          s=SimulationTrace()
     712          s.initialize()
     713          rrr = Resource(5, name = 'Parking', unitName = 'space(s)', qType = PriorityQ,
     714                       preemptable = 1,sim=s)
     715          procs = []
     716          for i in range(10):
     717              z = Aa(holdtime = i, name = 'Car ' + str(i),sim=s)
     718              procs.append(z)
     719              s.activate(z, z.life(priority = i))
     720          o = Observer(sim=s)
     721          s.activate(o, o.observe(1, procs, rrr))
     722          Aa.sequIn = []
     723          Aa.sequOut = []
     724          a = s.simulate(until = 10000)
     725          print a
     726          print 'Input sequence: ', Aa.sequIn
     727          print 'Output sequence: ', Aa.sequOut
     728
     729 -    def test_interrupt():
     730          class Bus(Process):
     731              def __init__(self, **vars):
     732                  Process.__init__(self, **vars)
     733
     734              def operate(self, repairduration = 0):
     735                  print self.sim.now(),'>> %s starts' % (self.name)
     736                  tripleft = 1000
     737                  while tripleft > 0:
     738                      yield hold, self, tripleft
     739                      if self.interrupted():
     740                          print 'interrupted by %s' %self.interruptCause.name
     741                          print '%s: %s breaks down ' %(now(),self.name)
     742                          tripleft = self.interruptLeft
     743                          self.interruptReset()
     744                          print 'tripleft ', tripleft
     745                          s.reactivate(br, delay = repairduration) # breakdowns only during operation
     746                          yield hold, self, repairduration
     747                          print self.sim.now(),' repaired'
     748                      else:
     749                          break # no breakdown, ergo bus arrived
     750                  print self.sim.now(),'<< %s done' % (self.name)
     751
     752          class Breakdown(Process):
     753              def __init__(self, myBus,sim=None):
     754                  Process.__init__(self, name = 'Breakdown ' + myBus.name,sim=sim)
     755                  self.bus = myBus
     756
     757              def breakBus(self, interval):
     758
     759                  while True:
     760                      yield hold, self, interval
     761                      if self.bus.terminated(): break
     762                      self.interrupt(self.bus)
     763
     764          print'\n\n+++test_interrupt'
     765          s=SimulationTrace()
     766          s.initialize()
     767          b = Bus(name='Bus 1',sim=s)
     768          s.activate(b, b.operate(repairduration = 20))
     769          br = Breakdown(b,sim=s)
     770          s.activate(br, br.breakBus(200))
     771          print s.simulate(until = 4000)
     772
     773 -    def testSimEvents():
     774          class Waiter(Process):
     775              def __init__(self,**vars):
     776                  Process.__init__(self,**vars)
     777              def waiting(self, theSignal):
     778                  while True:
     779                      yield waitevent, self, theSignal
     780                      print '%s: process \'%s\' continued after waiting for %s' %\
     781                            (self.sim.now(),self.name, theSignal.name)
     782                      yield queueevent, self, theSignal
     783                      print '%s: process \'%s\' continued after queueing for %s' % (now(),self.name, theSignal.name)
     784
     785          class ORWaiter(Process):
     786              def __init__(self,**vars):
     787                  Process.__init__(self,**vars)
     788              def waiting(self, signals):
     789                  while True:
     790                      yield waitevent, self, signals
     791                      print self.sim.now(),'one of %s signals occurred' %\
     792                            [x.name for x in signals]
     793                      print '\t%s (fired / param)'%\
     794                            [(x.name, x.signalparam) for x in self.eventsFired]
     795                      yield hold, self, 1
     796
     797          class Caller(Process):
     798              def __init__(self,**vars):
     799                  Process.__init__(self,**vars)
     800              def calling(self):
     801                  while True:
     802                      signal1.signal('wake up!')
     803                      print '%s: signal 1 has occurred'%now()
     804                      yield hold, self, 10
     805                      signal2.signal('and again')
     806                      signal2.signal('sig 2 again')
     807                      print '%s: signal1, signal2 have occurred'%now()
     808                      yield hold, self, 10
     809          print'\n+++testSimEvents output'
     810          s=SimulationTrace()
     811          s.initialize()
     812          signal1 = SimEvent('signal 1',sim=s)
     813          signal2 = SimEvent('signal 2',sim=s)
     814          signal1.signal('startup1')
     815          signal2.signal('startup2')
     816          w1 = Waiter(name='waiting for signal 1',sim=s)
     817          s.activate(w1, w1.waiting(signal1))
     818          w2 = Waiter(name='waiting for signal 2',sim=s)
     819          s.activate(w2, w2.waiting(signal2))
     820          w3 = Waiter(name='also waiting for signal 2',sim=s)
     821          s.activate(w3, w3.waiting(signal2))
     822          w4 = ORWaiter(name='waiting for either signal 1 or signal 2',sim=s)
     823          s.activate(w4, w4.waiting([signal1, signal2]),prior = True)
     824          c = Caller(name='Caller',sim=s)
     825          s.activate(c, c.calling())
     826          print s.simulate(until = 100)
     827
     828 -    def testwaituntil():
     829          """
     830          Demo of waitUntil capability.
     831
     832          Scenario:
     833          Three workers require sets of tools to do their jobs. Tools are shared,
     834          scarce resources for which they compete.
     835          """
     836          class Worker(Process):
     837              def __init__(self, name, heNeeds = [],sim=None):
     838                  Process.__init__(self, name,sim=sim)
     839                  self.heNeeds = heNeeds
     840              def work(self):
     841                  def workerNeeds():
     842                      for item in self.heNeeds:
     843                          if item.n == 0:
     844                              return False
     845                      return True
     846
     847                  while self.sim.now() < 8 * 60:
     848                      yield waituntil, self, workerNeeds
     849                      for item in self.heNeeds:
     850                          yield request, self, item
     851                      print '%s %s has %s and starts job' % (self.sim.now(),self.name,
     852                          [x.name for x in self.heNeeds])
     853                      yield hold, self, random.uniform(10, 30)
     854                      for item in self.heNeeds:
     855                          yield release, self, item
     856                      yield hold, self, 2 #rest
     857
     858          print '\n+++ nwaituntil demo output'
     859          random.seed(12345)
     860          s=SimulationTrace()
     861          s.initialize()
     862          brush = Resource(capacity = 1, name = 'brush',sim=s)
     863          ladder = Resource(capacity = 2, name = 'ladder',sim=s)
     864          hammer = Resource(capacity = 1, name = 'hammer',sim=s)
     865          saw = Resource(capacity = 1, name = 'saw',sim=s)
     866          painter = Worker('painter',[brush, ladder],sim=s)
     867          s.activate(painter, painter.work())
     868          roofer = Worker('roofer',[hammer, ladder, ladder],sim=s)
     869          s.activate(roofer, roofer.work())
     870          treeguy = Worker('treeguy',[saw, ladder],sim=s)
     871          s.activate(treeguy, treeguy.work())
     872          for who in (painter, roofer, treeguy):
     873              print '%s needs %s for his job' %\
     874                    (who.name,[x.name for x in who.heNeeds])
     875          print
     876          print s.simulate(until = 9 * 60)
     877
     878      ## -------------------------------------------------------------
     879      ##                    TEST COMPOUND 'YIELD REQUEST' COMMANDS
     880      ## -------------------------------------------------------------
     881
     882      ## -------------------------------------------------------------
     883      ##             TEST 'yield (request, self, res),(hold, self, delay)'
     884      ##                   == timeout renege
     885      ## -------------------------------------------------------------
     886
     887 -    class JobTO(Process):
     888         """ Job class for testing timeout reneging
     889         """
     890 -       def __init__(self, server = None, name = '',sim=None):
     891              Process.__init__(self, name,sim=sim)
     892              self.res = server
     893              self.gotResource = None
     894
     895 -       def execute(self, timeout, usetime):
     896              yield (request, self, self.res),(hold, self, timeout)
     897              if self.acquired(self.res):
     898                  self.gotResource = True
     899                  yield hold, self, usetime
     900                  yield release, self, self.res
     901              else:
     902                  self.gotResource = False
     903
     904
     905 -    def testNoTimeout():
     906          """Test that resource gets acquired without timeout
     907          """
     908          s=SimulationTrace()
     909          s.initialize()
     910          res = Resource(name = 'Server', capacity = 1,sim=s)
     911          usetime = 5
     912          timeout = 1000000
     913          j1 = JobTO(server = res, name = 'Job_1',sim=s)
     914          s.activate(j1, j1.execute(timeout = timeout, usetime = usetime))
     915          j2 = JobTO(server = res, name = 'Job_2',sim=s)
     916          s.activate(j2, j2.execute(timeout = timeout, usetime = usetime))
     917          s.simulate(until = 2 * usetime)
     918          assert s.now() == 2 * usetime, 'time not == 2 * usetime'
     919          assert j1.gotResource and j2.gotResource,\
     920              'at least one job failed to get resource'
     921          assert not (res.waitQ or res.activeQ),\
     922              'job waiting or using resource'
     923
     924 -    def testTimeout1():
     925          """Test that timeout occurs when resource busy
     926          """
     927          s=SimulationTrace()
     928          s.initialize()
     929          res = Resource(name = 'Server', capacity = 1, monitored = True,sim=s)
     930          usetime = 5
     931          timeout = 3
     932          j1 = JobTO(server = res, name = 'Job_1',sim=s)
     933          s.activate(j1, j1.execute(timeout = timeout, usetime = usetime))
     934          j2 = JobTO(server = res, name = 'Job_2',sim=s)
     935          s.activate(j2, j2.execute(timeout = timeout, usetime = usetime))
     936          s.simulate(until = 2 * usetime)
     937          assert(s.now() == usetime),'time not == usetime'
     938          assert(j1.gotResource),'Job_1 did not get resource'
     939          assert(not j2.gotResource),'Job_2 did not renege'
     940          assert not (res.waitQ or res.activeQ),\
     941              'job waiting or using resource'
     942
     943 -    def testTimeout2():
     944          """Test that timeout occurs when resource has no capacity free
     945          """
     946          s=SimulationTrace()
     947          s.initialize()
     948          res = Resource(name = 'Server', capacity = 0,sim=s)
     949          usetime = 5
     950          timeout = 3
     951          j1 = JobTO(server = res, name = 'Job_1',sim=s)
     952          s.activate(j1, j1.execute(timeout = timeout, usetime = usetime))
     953          j2 = JobTO(server = res, name = 'Job_2',sim=s)
     954          s.activate(j2, j2.execute(timeout = timeout, usetime = usetime))
     955          s.simulate(until = 2 * usetime)
     956          assert s.now() == timeout, 'time %s not == timeout'%now()
     957          assert not j1.gotResource, 'Job_1 got resource'
     958          assert not j2.gotResource, 'Job_2 got resource'
     959          assert not (res.waitQ or res.activeQ),\
     960              'job waiting or using resource'
     961
     962      ## ------------------------------------------------------------------
     963      ##             TEST 'yield (request, self, res),(waitevent, self, event)'
     964      ##                   == event renege
     965      ## ------------------------------------------------------------------
     966 -    class JobEvt(Process):
     967         """ Job class for testing event reneging
     968         """
     969 -       def __init__(self, server = None, name = '',sim=None):
     970              Process.__init__(self, name,sim=sim)
     971              self.res = server
     972              self.gotResource = None
     973
     974 -       def execute(self, event, usetime):
     975              yield (request, self, self.res),(waitevent, self, event)
     976              if self.acquired(self.res):
     977                  self.gotResource = True
     978                  yield hold, self, usetime
     979                  yield release, self, self.res
     980              else:
     981                  self.gotResource = False
     982
     983 -    class JobEvtMulti(Process):
     984         """ Job class for testing event reneging with multi - event lists
     985         """
     986 -       def __init__(self, server = None, name = '',sim=None):
     987              Process.__init__(self, name,sim=sim)
     988              self.res = server
     989              self.gotResource = None
     990
     991 -       def execute(self, eventlist, usetime):
     992              yield (request, self, self.res),(waitevent, self, eventlist)
     993              if self.acquired(self.res):
     994                  self.gotResource = True
     995                  yield hold, self, usetime
     996                  yield release, self, self.res
     997              else:
     998                  self.gotResource = False
     999
    1000 -    class FireEvent(Process):
    1001          """Fires reneging event
    1002          """
    1003 -        def __init__(self,**vars):
    1004              Process.__init__(self,**vars)
    1005 -        def fire(self, fireDelay, event):
    1006              yield hold, self, fireDelay
    1007              event.signal()
    1008
    1009 -    def testNoEvent():
    1010          """Test that processes acquire resource normally if no event fires
    1011          """
    1012          s=SimulationTrace()
    1013          s.initialize()
    1014          res = Resource(name = 'Server', capacity = 1,sim=s)
    1015          event = SimEvent(name='Renege_trigger',sim=s) #never gets fired
    1016          usetime = 5
    1017          j1 = JobEvt(server = res, name = 'Job_1',sim=s)
    1018          s.activate(j1, j1.execute(event = event, usetime = usetime))
    1019          j2 = JobEvt(server = res, name = 'Job_2',sim=s)
    1020          s.activate(j2, j2.execute(event = event, usetime = usetime))
    1021          s.simulate(until = 2 * usetime)
    1022          # Both jobs should get server (in sequence)
    1023          assert s.now() == 2 * usetime, 'time not == 2 * usetime'
    1024          assert j1.gotResource and j2.gotResource,\
    1025              'at least one job failed to get resource'
    1026          assert not (res.waitQ or res.activeQ),\
    1027              'job waiting or using resource'
    1028
    1029 -    def testWaitEvent1():
    1030          """Test that signalled event leads to renege when resource busy
    1031          """
    1032          s=SimulationTrace()
    1033          s.initialize()
    1034          res = Resource(name = 'Server', capacity = 1,sim=s)
    1035          event = SimEvent('Renege_trigger',sim=s)
    1036          usetime = 5
    1037          eventtime = 1
    1038          j1 = JobEvt(server = res, name = 'Job_1',sim=s)
    1039          s.activate(j1, j1.execute(event = event, usetime = usetime))
    1040          j2 = JobEvt(server = res, name = 'Job_2',sim=s)
    1041          s.activate(j2, j2.execute(event = event, usetime = usetime))
    1042          f = FireEvent(name = 'FireEvent',sim=s)
    1043          s.activate(f, f.fire(fireDelay = eventtime, event = event))
    1044          s.simulate(until = 2 * usetime)
    1045          # Job_1 should get server, Job_2 renege
    1046          assert(s.now() == usetime),'time not == usetime'
    1047          assert(j1.gotResource),'Job_1 did not get resource'
    1048          assert(not j2.gotResource),'Job_2 did not renege'
    1049          assert not (res.waitQ or res.activeQ),\
    1050              'job waiting or using resource'
    1051
    1052 -    def testWaitEvent2():
    1053          """Test that renege - triggering event can be one of an event list
    1054          """
    1055          s=SimulationTrace()
    1056          s.initialize()
    1057          res = Resource(name = 'Server', capacity = 1,sim=s)
    1058          event1 = SimEvent('Renege_trigger_1',sim=s)
    1059          event2 = SimEvent('Renege_trigger_2',sim=s)
    1060          usetime = 5
    1061          eventtime = 1 #for both events
    1062          j1 = JobEvtMulti(server = res, name = 'Job_1',sim=s)
    1063          s.activate(j1, j1.execute(eventlist = [event1, event2],usetime = usetime))
    1064          j2 = JobEvtMulti(server = res, name = 'Job_2',sim=s)
    1065          s.activate(j2, j2.execute(eventlist = [event1, event2],usetime = usetime))
    1066          f1 = FireEvent(name = 'FireEvent_1',sim=s)
    1067          s.activate(f1, f1.fire(fireDelay = eventtime, event = event1))
    1068          f2 = FireEvent(name = 'FireEvent_2',sim=s)
    1069          s.activate(f2, f2.fire(fireDelay = eventtime, event = event2))
    1070          s.simulate(until = 2 * usetime)
    1071          # Job_1 should get server, Job_2 should renege
    1072          assert(s.now() == usetime),'time not == usetime'
    1073          assert(j1.gotResource),'Job_1 did not get resource'
    1074          assert(not j2.gotResource),'Job_2 did not renege'
    1075          assert not (res.waitQ or res.activeQ),\
    1076              'job waiting or using resource'
    1077
    1078      testNoTimeout()
    1079      testTimeout1()
    1080      testTimeout2()
    1081      testNoEvent()
    1082      testWaitEvent1()
    1083      testWaitEvent2()
    1084      test_demo()
    1085      test_interrupt()
    1086      testSimEvents()
    1087      testwaituntil()
    1088