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

Source Code for Module SimPy.SimulationTrace

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