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