(Last updated: October 29, 2001.)
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.
Though process-oriented simulation is very popular (see C++Sim and my psim), the event-oriented approach has a number of advantages:
In this document we will assume a Unix environment. This will not come into play very much, and only slight changes to certain parts of this document need be made for other platforms. (The code itself does not need to be changed.)
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. You may also find his Java Web page of interest.
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.
Your source file which contains main() is required to be named Main.java. This file will contain some application-specific "global" variables, and contain most of your application-specific code, especially your code to specify which events are triggered when each given event occurs.
Here is an outline of your function main():
Your class Main must also include the following functions:
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 enable adding application-specific information to the class. In the examples described below, StopAndWait has JSElt subclasses FrameElt and TimeoutElt, and MM1 has a JSElt subclass MachineJob.
If your simulation includes resources for which jobs must queue, you may wish to the JSim class JSFacil. This class represents the resource, and again is typically subclassed in order to add application-specific information. For instance, in the MM1 example below, jobs queue up to be served by a machine, and the machine is simulated by a class Machine which extends JSFacil.
java -classpath .:$JSimDir app_name max_simtime debug_flag application_args
where "debug_flag" is 1 for debugging, 0 otherwise.
To set up compiling, do the following:
set JSimDir = /usr/local/jsim
and then whenever you compile Main.java, type
javac -g -classpath .:$JSimDir Main.java
Of course, various shortcuts to this can be set up in Makefiles, the CLASSPATH environment variable, shell aliases and the like.
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 at A 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.
(Before reading further in this tutorial, take a moment to first review the outline of Main.main() above, and then see how the points in the outline correspond to lines in this particular case.)
We've modularized a large part of the code into one function, which we have named SendFrame(). The function has a parameter, New, 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) { Frame.JSName = "arrive at B"; Frame.JSEvntTime = JSSimTime + 1 + Alpha; if (New) { Frame.FrameNumber = NextFrameToBeCreated++; Frame.StartTime = JSSim.JSSimTime; Frame.NTries = 1; } else Frame.NTries++; Frame.Trashed = false; Frame.JSInsertInSchedList(); // set up the paired timeout TmOt.JSEvntTime = JSSim.JSSimTime + Timeout; TmOt.JSName = "timeout"; TmOt.JSInsertInSchedList(); // pair them together to enable one to cancel the other Frame.TimeoutEltNumber = TmOt.JSEltNumber; TmOt.FrameEltNumber = 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 JSSim.JSSimTime + 1 + Alpha, where the built-in JSim variable JSSim.JSSimTime is the current simulated time.
If this is a new frame, we give it the next available frame number. We record the present time as this frame's start time, i.e. the time at which we first start trying to send it. Also, we set Frame.NTries to 1, as this is our first try. On the other hand, if this is not a new frame, we increment Frame.NTries.
Next, we reset Frame.Trashed, which records whether the frame is corrupted. Then we call JSElt.JSInsertInSchedList() (Frame is a subclass of JSElt) to add this element to the event list (or schedule list). The event list is a linear linked list of pending events, ordered by time, with the earliest event at the head of the list (pointed to by JSSim.ScheduleListHd). The items in the event list have not occurred yet; they are merely scheduled to occur.
Finally, we set up a timeout element, and add it to the event list too.
Now, let's look at some of the event handler code. Recall that JSSim.JSMainLoop() is called from Main.main(). JSSim.JSMainLoop() then does the simulation, returning to Main.main() when the simulation is finished. Then Main.main() can print out the results. But JSSim.JSMainLoop() calls another method in Main, EvntHandler(), as can be seen in the following outline of JSSim.JSMainLoop():
{ while (JSSimTime <= JSMaxSimTime) { JSElt.JSGetNext(); JSSimTime = JSCurrEvnt.JSEvntTime; ... Main.EvntHandler(); ... } }
In other words, JSSim.JSMainLoop() (a) uses JSElt.JSGetNext() to pull the earliest event from the schedule list, then (b) advances the simulated time to the time of the pulled event -- which simulates that the event has now occurred -- and then (c) calls Main.EvntHandler() to perform whatever actions that event entails.
Main.EvntHandler() is very much application-specific. Here it is in this case:
public static void EvntHandler() // application-specific; required { if (JSSim.JSCurrEvnt.JSEvntMatch("arrive at B")) {DoArrivalAtB(); return;} if (JSSim.JSCurrEvnt.JSEvntMatch("done with delay")) {DoDelayDone(); return;} if (JSSim.JSCurrEvnt.JSEvntMatch("arrive back at A")) {DoArrivalBackToA(); return;} if (JSSim.JSCurrEvnt.JSEvntMatch("timeout")) {DoTimeout(); return;} System.out.println("shouldn't reach this point"); System.exit(1); }
As you can see, it is essentially a switch statement (we can't use a real switch, since strings are not scalar types). We are using JSEvntMatch() in a manner similar to the C library's strcmp().
When a new frame is created, or we do a retry on an old frame, the next event will be "arrive at B"; when that event occurs, DoArrivalAtB() will be called. As you can see from the code in that method,
static void DoArrivalAtB() { Frame.JSName = "done with delay"; Frame.JSEvntTime = JSSim.JSSimTime + JSSim.JSExpon(MeanDelay); Frame.JSInsertInSchedList(); }
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. Remember, the latter event has not occurred yet; it is merely scheduled.
What that event does finally occur, DoDelayDone() is called:
void DoDelayDone() { Frame.Trashed = (JSRnd() < P); Frame.JSName = "arrive back at A"; Frame.JSEvntTime = JSSimTime + Alpha; 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. The occurrence of a timeout is modeled by DoTimeout():
void DoTimeout() { Frame.JSCancel(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, which is still on the schedule list, must be 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() { JSElt.JSCancel(Frame.TimeoutEltNumber); if (Frame.Trashed) SendFrame(false); // failed, try sending again else { // succeeded // need to do some bookkeeping TotFrames++; TotWait += JSSim.JSSimTime - Frame.StartTime; TotTries += Frame.NTries; // done, so send the next frame SendFrame(true); }
In this case, it is the timeout event which must be canceled. The method then checks for a report that the frame had been corrupted. If so, we need to send it again; if it was received intact, we do our bookkeeping, and send out a new frame. If not, we try sending this frame 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 fields specific to this application, e.g. NTries, and overrode JSPrintElt() in order to get additional, application-specific debugging information.
java -classpath .:$JSimDir Main timelim dbgflag alph timeout p mndly
In a typical run (recall the effect of randomness), we got the following results:
% java Main 1000 0 0.2 2.5 0.0 0.2 total frames sent = 622 mean wait = 1.6076623 mean number of xmits per frame = 1.0016078
The first two command-line arguments, timelim and dbgflag, are general JSim quantities, while the remaining arguments are specific to this application. The command says to run the simulation up to a simulated time of 1000, with no debugging information, with alpha = 0.2, etc.
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).
The program simulates an M/M/1 queue. 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 at this site, but it is not needed here, as we will find the value via simulation instead, in order to illustrate JSim.
java -classpath .:$JSimDir Main 1000.0 0 1.0 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. (You may not get exactly the same answer if you run this program, due to randomness.)
First, this example uses a JSim construct not used in Stop-and-Wait, JSFacil. We are representing the machine using that class:
public static JSFacil Machine;
We do this in order to take advantage of the queuie-management capabilities in JSFacil.
We have one JSElt type here, which we name MachineJob:
public class MachineJob extends JSElt { float ArrivalTime; // time this job arrived // constructor MachineJob() { JSName = "arrive"; } public void JSPrintElt() { super.JSPrintElt(); System.out.println(" arrival was at "+ArrivalTime); } }
As in the Stop-and-Wait example, we add some information not in an ordinary JSElt, the float variable ArrivalTime. (We need this in order to compute how long each job resides in our system, from arrival to completion of service.) We also enhance the debugging method JSPrintElt().
Note that in our outline of the typical Main.main() above, we stated that this method will place the first event into the event list before starting the simulation. That is certainly true in Main.main() for our MM1 example here:
// "prime" the system: set up first arrival Tmp = JSSim.JSExpon(MeanArrive); MachineJob TmpEltPtr = new MachineJob(); TmpEltPtr.JSEvntTime = Tmp; TmpEltPtr.ArrivalTime = Tmp; TmpEltPtr.JSInsertInSchedList(); // now start the simulation; required call JSSim.JSMainLoop(Argv);
Now let's look first at Main.EvntHandler():
public static void EvntHandler() { if (JSSim.JSCurrEvnt.JSEvntMatch("arrive")) DoArrival(); else DoDone(); }
There are only two event types, "arrive" and "done with service," so Main.EvntHandler() is very simple.
The first method called, though, DoArrival(), is somewhat complex:
public static void DoArrival() { float Tmp; JSSim.JSCurrEvnt.JSName = "done with service"; JSSim.JSCurrEvnt.JSServiceTime = JSSim.JSExpon(MeanServe); // try to serve, else add to machine queue if (!Machine.JSBusy) { Machine.JSStartServe(JSSim.JSCurrEvnt); } else Machine.JSAppendToFclQ(); // schedule the next arrival Tmp = JSSim.JSExpon(MeanArrive); Tmp += JSSim.JSSimTime; MachineJob TmpEltPtr = new MachineJob(); TmpEltPtr.JSEvntTime = Tmp; TmpEltPtr.ArrivalTime = Tmp; TmpEltPtr.JSInsertInSchedList(); }
What this code does is the following: The next event for this newly arrived job will be service by the machine, so we change its event type to "done with service". We check to see if the machine is busy, using JSFacil's JSBusy variable. If the machine isn't busy, we start service for this job, by calling the JSFacil method JSStartServe(). One the other hand, if the machine is busy, we call the JSFacil method JSAppendToFclQ() to add this job to the machine's queue. In addition, we must schedule the next arrival.
Consider the case in which this is the first time DoArrival() is called. DoArrival(), as seen above, will take the job which arrived and add it to the event list as a "done with service" event. DoArrival() will then form a new job, to be the next arrival, and add it to the event list as an "arrive" event. In other words, there will now be two events in the event list in this case. 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:
public static void DoDone() { // process the job which just finished TotJobs++; MachineJob TmpAE = (MachineJob) JSSim.JSCurrEvnt; TotWait += JSSim.JSSimTime - TmpAE.ArrivalTime; // bookkeeping, plus a check of machine's queue float TmpF = JSSim.JSExpon(MeanServe); Machine.JSDoneServe(); return; }
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 JSFacil.JSDoneServe().
Since it is part of the "innards" of JSim, JSDoneServe() is not something you would normally look at. But a brief look here will help you learn what JSim does. Remember, this is NOT what you write yourself when you develop a JSim application; it is already there in JSim, as a library function. But let's sneak a look at its code:
// does bookkeeping associated with finishing a job, and starting the // next job, if any; returns true or false, depending on whether a // new job was started boolean JSDoneServe() { JSElt OldFclQHd; JSBusy = false; JSCurrJob = null; if (JSNQ == 0) return false; OldFclQHd = JSFclQHd; if (JSFclQHd == JSFclQTl) JSFclQHd = JSFclQTl = null; else JSFclQHd = JSFclQHd.JSNext; JSNQ--; JSStartServe(OldFclQHd); return true; }
JSBusy is reset to false. If the number in the queue is 0, then we are done, as there is no other job to start; if not, we delete the head of the queue, and call JSFacil.JSStartServe() to start that job.
Again, 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).
As with any programs, JSim code should be debugged with the aid of a good debugging tool. See my debugging Web page, especially regarding the JSwat Java debugging tool.
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 also has a couple of debugging aids of its own (which should be used in conjunction with a debugging tool such as JSwat)::
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.
Required in Main. Initializes JSim.
Required in Main. This is the core of the simulator.
Inserts the given event into the event list.
Removes a specified event from the event list, if it has become invalid.
Used as a mechanism for a "switch" statement within Main.EvntHandler().
Appends a job to the queue for a given server.
Starts service for a given job at the given server.
Processes the completion of service at the given server, including starting a new job if any jobs had been queued for this server.
Generates a U(0,1) random variable.
Generates an exponential random variable.
For debugging. Prints out this work element/event..
For debugging. Prints out the entire event list.
For debugging. Prints out information on the given server.
A basic event element, e.g. one task for a machine, one frame on a communications link, etc.
A server, for which jobs queue..
Contains data and methods related to the general operation of JSim.
Current simulated time.
Head of the event list.
Current event.
The job currently being served by this facility.
For debugging, set from command line; true means debug.