Tracing

Up Next

Using tracing to debug a SimPy program

(Author: Klaus G. Muller; kgmuller at users.sourceforge.net)

Debugging programs is a difficult art, and debugging simulation programs, with their parallel processes, time dependencies and synchronization issues is even more difficult. 

A fine introduction to debugging of SimPy programs can be found in Prof. Matloff's SimPy tutorial

This HowTo will look at a powerful debugging tool built into SimPy, namely the SimulationTrace module. It provides insight into the execution of the quasi-parallel processes, the events in SimPy programs. This can help to gain insight into the logic of simulations written by others and also into what is going on in a malfunctioning program one has written.

Let's look at a trivial example: a workshop has two machines in series which produce widgets. Each machine can only process one raw widget at a time. This is modelled by the following (erroneous) SimPy program: 

from SimPy.Simulation import *

class Widget(Process):
   def lifetime(self):
      yield request,self,machine0
      yield hold,self,12
      yield request,self,machine1
      yield hold,self,24
      yield release,self,machine1
      print "Widget %s produced"%self.name

initialize()
machine0=Resource(name="Machine 0")
machine1=Resource(name="Machine 1")
for i in range(4):
   w=Widget(name="Widget %s"%i)
   activate(w,w.lifetime())
simulate(until=1000)

OUTPUT:

Widget 0 produced

 

Although 4 raw widgets go into the workshop, only 1 finished widget comes out. In this small program, the reason is easy to spot, but in larger, more complex programs, with random arrivals and delays, it may not be so easy.

To trace the event sequence in order to find the bug(s), we change just the second line to 'from SimPy.SimulationTrace import *'. That gives us an automatic trace of all the 'yield' (scheduling) statements. So here are the changed program and its output:

from SimPy.SimulationTrace import * ## Only change in program!

class Widget(Process):
   def lifetime(self):
      yield request,self,machine0
      yield hold,self,12
      yield request,self,machine1
      yield hold,self,24
      yield release,self,machine1
      print "Widget %s produced"%self.name

initialize()
machine0=Resource(name="Machine 0")
machine1=Resource(name="Machine 1")
for i in range(4):
   w=Widget(name="Widget %s"%i)
   activate(w,w.lifetime())
simulate(until=1000)

OUTPUT:

0 activate <Widget 0> at time: 0 prior: 0
0 activate <Widget 1> at time: 0 prior: 0
0 activate <Widget 2> at time: 0 prior: 0
0 activate <Widget 3> at time: 0 prior: 0
0 request <Widget 0> <Machine 0> priority: default 
. . .waitQ: [] 
. . .activeQ: ['Widget 0']
0 hold <Widget 0> delay: 12
0 request <Widget 1> <Machine 0> priority: default 
. . .waitQ: ['Widget 1'] 
. . .activeQ: ['Widget 0']
0 request <Widget 2> <Machine 0> priority: default 
. . .waitQ: ['Widget 1', 'Widget 2'] 
. . .activeQ: ['Widget 0']
0 request <Widget 3> <Machine 0> priority: default 
. . .waitQ: ['Widget 1', 'Widget 2', 'Widget 3'] 
. . .activeQ: ['Widget 0']
12 request <Widget 0> <Machine 1> priority: default 
. . .waitQ: [] 
. . .activeQ: ['Widget 0']
12 hold <Widget 0> delay: 24
36 release <Widget 0> <Machine 1> 
. . .waitQ: [] 
. . .activeQ: []
Widget 0 produced
36 <Widget 0> terminated

 

The first column is the event time. We see the process activations, request and release of resources and hold delays.

The program ends at time 36. By this time, only Widget 0 has been finished. Why are the other widgets not being finished? At time 0, they were waiting for Machine 0 (waitQ), while Widget 0 was being processed (activeQ). Why are they not progressing further to Machine 1?

At the interactive prompt (say, in IDLE), we inspect the Machine 1 object's queues:

>>> machine0
<Instance of Resource, id 19883880:
   .preemptable=0
   .capacity=1
   .name=Machine 0
   .waitQ=[<Instance of Widget, id 19884328:
                .name=Widget 1
    >, <Instance of Widget, id 12128032:
          .name=Widget 2
    >, <Instance of Widget, id 19892736:
   .name=Widget 3
   >]
   .unitName=units
   .priority_default=0
   .activeQ=[<Instance of Widget, id 19872904:
               .interruptCause=None
               .name=Widget 0
               .interruptLeft=24
   >]
   .n=0
>

So, Widget 1, Widget 2 and Widget 3 are still queuing (waitQ), while Widget 0 is still occupying the machine (activeQ). Widget 0 has never released Machine 0!

The solution is simple -- just add the missing 'yield release' statement and run:

from SimPy.SimulationTrace import *

class Widget(Process):
   def lifetime(self):
      yield request,self,machine0
      yield hold,self,12
      yield release,self,machine0 ##this cured the bug!
      yield request,self,machine1
      yield hold,self,24
      yield release,self,machine1
      print "%s produced"%self.name

initialize()
machine0=Resource(name="Machine 0")
machine1=Resource(name="Machine 1")
for i in range(4):
   w=Widget(name="Widget %s"%i)
   activate(w,w.lifetime())
simulate(until=1000)

OUTPUT:

0 activate <Widget 0> at time: 0 prior: 0
0 activate <Widget 1> at time: 0 prior: 0
0 activate <Widget 2> at time: 0 prior: 0
0 activate <Widget 3> at time: 0 prior: 0
0 request <Widget 0> <Machine 0> priority: default 
. . .waitQ: [] 
. . .activeQ: ['Widget 0']
0 hold <Widget 0> delay: 12
0 request <Widget 1> <Machine 0> priority: default 
. . .waitQ: ['Widget 1'] 
. . .activeQ: ['Widget 0']
0 request <Widget 2> <Machine 0> priority: default 
. . .waitQ: ['Widget 1', 'Widget 2'] 
. . .activeQ: ['Widget 0']
0 request <Widget 3> <Machine 0> priority: default 
. . .waitQ: ['Widget 1', 'Widget 2', 'Widget 3'] 
. . .activeQ: ['Widget 0']
12 cancel by: <a_process> of: <Widget 1>
12 reactivate <Widget 1> time: 12 prior: 1
12 release <Widget 0> <Machine 0> 
. . .waitQ: ['Widget 2', 'Widget 3'] 
. . .activeQ: ['Widget 1']
12 request <Widget 0> <Machine 1> priority: default 
. . .waitQ: [] 
. . .activeQ: ['Widget 0']
12 hold <Widget 0> delay: 24
12 hold <Widget 1> delay: 12
24 cancel by: <a_process> of: <Widget 2>
24 reactivate <Widget 2> time: 24 prior: 1
24 release <Widget 1> <Machine 0> 
. . .waitQ: ['Widget 3'] 
. . .activeQ: ['Widget 2']
24 request <Widget 1> <Machine 1> priority: default 
. . .waitQ: ['Widget 1'] 
. . .activeQ: ['Widget 0']
24 hold <Widget 2> delay: 12
36 cancel by: <a_process> of: <Widget 1>
36 reactivate <Widget 1> time: 36 prior: 1
36 release <Widget 0> <Machine 1> 
. . .waitQ: [] 
. . .activeQ: ['Widget 1']
Widget 0 produced
36 <Widget 0> terminated
36 hold <Widget 1> delay: 24
36 cancel by: <a_process> of: <Widget 3>
36 reactivate <Widget 3> time: 36 prior: 1
36 release <Widget 2> <Machine 0> 
. . .waitQ: [] 
. . .activeQ: ['Widget 3']
36 request <Widget 2> <Machine 1> priority: default 
. . .waitQ: ['Widget 2'] 
. . .activeQ: ['Widget 1']
36 hold <Widget 3> delay: 12
48 release <Widget 3> <Machine 0> 
. . .waitQ: [] 
. . .activeQ: []
48 request <Widget 3> <Machine 1> priority: default 
. . .waitQ: ['Widget 2', 'Widget 3'] 
. . .activeQ: ['Widget 1']
60 cancel by: <a_process> of: <Widget 2>
60 reactivate <Widget 2> time: 60 prior: 1
60 release <Widget 1> <Machine 1> 
. . .waitQ: ['Widget 3'] 
. . .activeQ: ['Widget 2']
Widget 1 produced
60 <Widget 1> terminated
60 hold <Widget 2> delay: 24
84 cancel by: <a_process> of: <Widget 3>
84 reactivate <Widget 3> time: 84 prior: 1
84 release <Widget 2> <Machine 1> 
. . .waitQ: [] 
. . .activeQ: ['Widget 3']
Widget 2 produced
84 <Widget 2> terminated
84 hold <Widget 3> delay: 24
108 release <Widget 3> <Machine 1> 
. . .waitQ: [] 
. . .activeQ: []
Widget 3 produced
108 <Widget 3> terminated

All four widgets get produced, the program runs correctly. Mission accomplished!

For the full range of SimPy's tracing capabilities, have a look at the SimulationTrace manual.

 

horizontal rule

©Copyright 2004, 2005, 2006, 2007, 2008 SimPy Developer Team.
For problems or questions regarding this web contact SimPy webmaster.
Last updated: 20/06/08.