JSim is an event-oriented discrete-event simulation package It follows the classical approach, but is designed so that the source code is easily understood, facilitating student understanding and addition of new features. It is a Java version of Professor Matloff's ESim package, which is written in C++.
Though process-oriented simulation is very popular (see C++Sim and my psim), the event-oriented approach has a number of advantages:
Contents of this page:
In this document we will assume a Unix environment. This will not come into play very much, and only slight changes need be made for other platforms.
Note: No guarantees of any kind are made regarding the accuracy of this software or its documentation.
Go to Professor Matloff's simulation Web page.
Next, make a directory for JSim, say /usr/local/jsim (if you choose some other directory for JSim, change everything below accordingly), and do the following from whatever directory you've unpacked the JSim source in.
javac -g *.java mv *.java *.class /usr/local/jsim
Before beginning, note that all classes, methods and variables which begin with "JS" denote JSim built-in entities. This will be important to keep in mind.
You will need at least two source files:
This file X.java will be very short, containing some application-specific "global" variables, and code that will basically be just a wrapper for invoking JSSim, which does the main work of the simulation (and which will be subclassed to a user-written file XSim.java, as explained below).
The class XSim will extend JSim's built-in class JSSim, which contains most of the internal mechanisms which make the simulation work.
You probably will also have one or more source files which contain subclasses of the built-in JSim class JSElt. This class represents one work unit, e.g. one task for a machine or one message on a communications link. The reason for subclassing is to add application-specific information to the class. In the examples described below, MM1 has a JSElt subclass ArrivalElt, and StopAndWait has subclasses FrameElt and TimeoutElt.
You may also
Now, here is how to write main():
Declare main() in X.java as
public static void main(String[] Argv)
and make the first two executable statements within main()
XSim Sim = new XSim(); Sim.JSInit(Argv);
These set up a simulation, and perform some general initializations. Then after those two statements do your application-specific initializations, including pulling down arguments from the command-line.
If you have subclassed (i.e. extended) JSElt or have any elements of class JSFacil, be very careful in declaration and allocation. Suppose for instance you have a class ZElt which extends JSElt, and wish to have such an element Z in X. Declare it in X as
public static ZElt Z;
setting up a pointer Z to a potential ZElt, and then in main write
Z = new ZElt();
now allocating such a ZElt and pointed Z to i. Omission of such statement, or changing it, would give you problems involving incorrect pointers.
Next in main(), set up your first event (often consisting of a job arrival or creation), and then start the simulation:
Sim.JSMainLoop();
You must declare XSim (in XSim.java) as a subclass of JSSim, and you must override the dummy method JSEvntHandler() which exists in JSSim.
To set up compiling, do the following:
set JSimDir = /usr/local/jsim
and then whenever you compile X.java, type
javac -g -classpath .:$JSimDir
Of course, various shortcuts to this can be set up in Makefiles, the CLASSPATH environment variable, shell aliases and the like.
java -classpath .:$JSimDir app_name max_simtime debug_flag application_args
where "debug_flag" is 1 for debugging, 0 otherwise.
Functions:
Required. Initializes JSim.
Required. This is the core of the simulator.
Inserts the given event into the schedule list.
Removes a specified event from the event list, if it has become invalid.
Used as a mechanism for a "switch" statement within JSEvntHandler().
Appends a job to the queue for a given server.
Starts service for a given job at the given server, with a given service time.
Processes the completion of service at the given server.
Generates a U(0,1) random variable.
Generates an exponential random variable.
For debugging. Prints out this element.
For debugging. Prints out the entire event list.
For debugging. Prints out information on the given server.
Classes:
A basic event element, e.g. one task for a machine, one frame on a communications link, etc.
A server, or a set of multiple servers with a common queue.
Contains data and methods related to the general operation of JSim. Must be extended for each specific application.
"Global" variables:
Current simulated time.
Head of the event list.
Current event.
For debugging, set from command line; true means debug.
Note: The term "server" above is not necessarily meant in the computer sense, e.g. file server. It simply means something which serves, say a vending machine or a clerk.
The program simulates an M/M/1 queue. (A theoretical analysis, not needed here, is at this site.) There is a single server, to which jobs arrive at random times, with the interarrival time having an exponential distribution. The service time is also random, with an exponential distribution. If the server is busy when a job arrives, the job joins a queue. (In this simulation, we are assuming that the server is some kind of machine, so we refer to it as the machine.)
We are interested in determining the long-run average wait time per job. There is an exact mathematical formula for this quantity, but here we will find the value via simulation instead, to illustrate JSim.
We compile the program and name the executable file MM1, and run it:
java -classpath .:$JSimDir MM1 1000.0 0 1.0 0.5
Here we specify a simulation time limit of 1000.0, no debugging (0), a mean job interarrival time of 1.0, and a mean service time of 0.5. The output will be something like
mean wait = 0.968920
In other words, up to simulated time 1000.0, the program simulated the arrival of a certain number of jobs, and their mean wait in the queue was 0.97.
We'll see below how this came about, by examining the source files MM1.java and MM1Sim.java. It is assumed that you know Java, but you need not have any prior background with simulation.
The center of the operation of JSim is the function JSMainLoop(), which manages the event list. (Again, keep in mind: All function and variable names beginning with "JS" are entitities provided by the JSim package. All the rest are application-specific.)
The event list is a linear linked list of pending events, ordered by time, with the earliest event at the head of the list. JSMainLoop()'s action is to repeatedly loop around, handling one event per loop. At each iteration of the loop, the function does the following:
To make the ideas more concrete, let's first set forth an example of a typical instance of the operation of the M/M/1 system.
We start at time 0.0, and main() sets up the first arrival
Tmp = Sim.JSExpon(MeanArrive); ArrivalElt TmpEltPtr = new ArrivalElt(); TmpEltPtr.JSEvntTime = Tmp; TmpEltPtr.ArrivalTime = Tmp; TmpEltPtr.JSInsertInSchedList();
We call the exponential random number generator and store the generated number in Tmp; say this value is 1.2. This will mean that our first arrival will be at time 1.2. We set up an instance of the ArrivalElt class (which sets the event type to be "will arrive"), and set the event time to 1.2. Finally, we insert this event into the event list. The event list will now contain this one pending event.
In its next loop iteration, JSMainLoop() will take this event off the event list. It will notice that the time of the event is 1.2, so it advances the simulated time, JSSimTime, to 1.2.
JSMainLoop() then calls the user-written JSEvntHandler() to process this event. Since the event type is "will arrive", the latter will call the user-written DoArrival(), which does the following:
ArrivalElt.JSCurrEvnt.JSName = "will be done with service"; // try to serve, else add to machine queue if (MM1.Machine.JSNBusy == 0) { Tmp = JSExpon(MM1.MeanServe); MM1.Machine.JSStartServe(ArrivalElt.JSCurrEvnt,Tmp); } else MM1.Machine.JSAppendToFclQ(); // schedule the next arrival Tmp = JSExpon(MM1.MeanArrive); Tmp += JSSimTime; ArrivalElt TmpEltPtr = new ArrivalElt(); TmpEltPtr.JSEvntTime = Tmp; TmpEltPtr.ArrivalTime = Tmp; TmpEltPtr.JSInsertInSchedList();
The next event for this newly arrived job will be service by the machine, so we change its event type to "will be done with service". We check to see if the machine is busy; it isn't, so we start service for this job. If you look at the code for JSStartServe(), you will see that this causes this job to be added to the event list. We then schedule the next arrival. Make sure you understand that there will now be two items in the event list, a pending service and a pending arrival.
Later, when a service is done, JSEvntHandler() will call DoDone(), which has the following code:
MM1.TotJobs++; ArrivalElt TmpAE = (ArrivalElt) ArrivalElt.JSCurrEvnt; MM1.TotWait += JSSimTime - TmpAE.ArrivalTime; // bookkeeping, plus a check of machine's queue float TmpF = JSExpon(MM1.MeanServe); MM1.Machine.JSDoneServe(TmpF);
The first three lines do some accounting, updating the totals needed for us to compute mean waiting time at the end of the simulation.
Now that the machine has finished serving one job, it must check to see if any other jobs are waiting in the queue, and if so, start the next one. This is done by JSDoneServe() in the JSim library:
JSNBusy--; if (JSNQ == 0) return false; OldFclQHd = JSFclQHd; if (JSFclQHd == JSFclQTl) JSFclQHd = JSFclQTl = null; else JSFclQHd = JSFclQHd.JSNext; JSNQ--; JSStartServe(OldFclQHd,SrvTm); return true;
JSNBusy, the number of busy servers (we only have one in this program), is decremented. If the number in the queue is 0, then we are done; if not, we delete the head of the queue, and call JSStartServe() for that job.
Execution of the program will continue in this manner. Each time one event executes, its corresponding event handler then sets up the next event(s).
Note that we subclassed the basic event element, JSElt:
public class ArrivalElt extends JSElt { float ArrivalTime; // time this job arrived ArrivalElt() { JSName = "will arrive"; } public void JSPrintElt() { super.JSPrintElt(); System.out.println(" arrival was at "+ArrivalTime); } }
We did this in order to add the field ArrivalTime, which enables us to later compute the total time this frame job waited in the system, which in turn enables us to find the mean wait per job at the end of the simulation.
Note that we also overrode the JSim function JSPrintElt(), which is called if the debug flag is set. Our new version of JSPrintElt() calls the old one to print the common information, and then also prints out ArrivalTime.
To solidify your understanding of the role of the event list, you should rerun the simulation, in this case setting the debug flag to 1 (pipe the output through the more command).
Stop-and-Wait is a standard protocol used in computer networks. In the system being modeled here, network node A is sending a message (a frame) to node B. We have scaled time so that it takes 1.0 unit of time for the message to get onto the communications link, and Alpha amount of time to propagate across the link. Node B sends back a short reply, taking negligible time to get onto the link and Alpha time to reach node A.
The message may have been corrupted when traveling from A to B, with probability P; if so, B will so say in the reply. Also, B may be busy and have some random delay before replying to A; if the delay is too long, A will timeout and assume the message had been lost, and then send again. We assume here that the sender always has a ready pool of messages to send, so as soon as one is successfully transmitted, the next one is sent immediately.
We are interested in the mean time it takes for a frame to be successfully sent, and also the mean number of tries it takes for success.
java -classpath .:$JSimDir StopAndWait timelim dbgflag alph timeout p mndly
We've modularized a large part of the code into one function, which we have named SendFrame(). The function has a parameter which indicates whether this is a new frame, i.e. our first attempt at sending this frame, or a retry. Here's the code:
static void SendFrame(boolean New) { StopAndWait.Frame.JSName = "arrive at B"; StopAndWait.Frame.JSEvntTime = JSSimTime + 1 + StopAndWait.Alpha; if (New) { StopAndWait.Frame.FrameNumber = StopAndWait.NextFrameToBeCreated++; StopAndWait.Frame.StartTime = StopAndWait.Frame.JSEvntTime; StopAndWait.Frame.NTries = 1; } else StopAndWait.Frame.NTries++; StopAndWait.Frame.Trashed = false; StopAndWait.Frame.JSInsertInSchedList(); // set up the paired timeout StopAndWait.TmOt.JSEvntTime = JSSimTime + StopAndWait.Timeout; StopAndWait.TmOt.JSName = "timeout"; StopAndWait.TmOt.JSInsertInSchedList(); // pair them together to enable one to cancel the other StopAndWait.Frame.TimeoutEltNumber = StopAndWait.TmOt.JSEltNumber; StopAndWait.TmOt.FrameEltNumber = StopAndWait.Frame.JSEltNumber; }
Since we are sending to B, we've named the event type "arrive at B". The time that that occurs will be at JSSimTime + 1 + Alpha, where the built-in JSim variable JSSimTime is the current time.
If this is a new frame, give it the next available frame number. Record the present time as this frame's start time, i.e. the time at which we first start trying to send it. Also, set Frame.NTries to 1, as this is our first try.
On the other hand, if this is not a new frame, increment Frame.NTries.
Next, reset Frame.Trashed, which records whether the frame is corrupted. Then add this element to the event list.
Finally, set up a timeout element, and add it to the event list too.
Now, let's look at some of the event handler code.
When a new frame is created, or we do a retry on an old frame, the next event will be "arrive at B", upon which DoArrivalAtB() will be called. As you can see from the code in that function, the random delay is generated (modeling that node B is too busy to process the incoming frame immediately), the event type is changed to "done with delay", and that event is added to the event list.
What that event occurs, DoDelayDone() is called:
void DoDelayDone() { StopAndWait.Frame.Trashed = (JSRnd() < StopAndWait.P); StopAndWait.Frame.JSCurrEvnt.JSName = "arrive back at A"; StopAndWait.Frame.JSEvntTime = JSSimTime + StopAndWait.Alpha; StopAndWait.Frame.JSInsertInSchedList(); }
Node B first checks to see whether the frame arrived intact. Remember, there is a probability P that it is corrupted, a situation we simulate here by calling JSRnd(), which generates random numbers uniformly distributed on the interval (0,1). Then we set up to send the reply back to A, and add that new event to the event list.
Recall that we set a timeout. If the reply from B does not get back to A within the timeout period, A will give up the frame for lost, and retransmit it. This is modeled by DoTimeout():
void DoTimeout() { StopAndWait.Frame.JSCancel(StopAndWait.TmOt.FrameEltNumber); SendFrame(false); }
Note that DoTimeout() must remove the other event, "arrive back at A", from the event list. The reason for this is that if DoTimeout() was called, it was because the timeout occurred before B's reply got back to A. So the "arrive back at A" event is canceled. We accomplish that by calling JSCancel(). Then DoTimeout() calls SendFrame() again, so as to start a new attempt to send the frame successfully to B.
On the other hand, if the reply from B does reach A in time, DoArrivalBackToA() is called:
void DoArrivalBackToA() { StopAndWait.Frame.JSCancel(StopAndWait.Frame.TimeoutEltNumber); if (StopAndWait.Frame.Trashed) { StopAndWait.TotFrames++; StopAndWait.TotWait += JSSimTime - StopAndWait.Frame.StartTime; StopAndWait.TotTries += StopAndWait.Frame.NTries; SendFrame(false); } else SendFrame(true); }
In this case, it is the timeout event which must be canceled. The function then checks for a report that the frame had been corrupted. If it was received intact, we now do our bookkeeping, and send out a new frame. If not, we try sending this frame again.
Again note that we subclassed JSElt:
class FrameElt extends JSElt { int FrameNumber; float StartTime; // time at which this frame is first sent int NTries; // number of attempts at sending this frame so far boolean Trashed; // true means erroneous int TimeoutEltNumber; // JSEltNumber for the paired timeout public void JSPrintElt() { super.JSPrintElt(); System.out.println(" frame "+this.FrameNumber+": number of tries = " +this.NTries+", trashed = "+this.Trashed); } }
We added several field specific to this application, e.g. NTries, and again overrode JSPrintElt() in order to get better debugging information.
As with any programs, JSim code should be debugged with the aid of a good debugging tool. See my debugging Web page.
Debugging simulation programs tends to be difficult, as with any program dealing with multiple concurrent activities. The best strategy to find a bug--and in fact, to verify the correctness of the program--is to step through the simulation with the debugging tool on a small problem, verifying that the different variables do have the correct values at the times you expect them to.
JSim has a couple of debugging aids of its own:
If the "debug" command-line argument is set to 1, the event list is printed out immediately before and immediately after each event is executed. You can also call the functions JSPrintSchedList() and JSPrintElt() directly; note that the latter can be overridden so as to tailor it to your own application. There is a similar variable for printing server information.