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