[Dev-sig] Simulated fun (with Objects!)
Emerson, Tom
Tom.Emerson at wbconsultant.com
Wed May 17 16:17:29 PDT 2006
I've just completed an interesting "task" here at work that I thought
I'd share -- simulating our system's processing capability to determine
when it all goes kablooey...
[short answer: it won't, unless we force the issue...]
However, in finding this out, I applied some techniques gleaned from
studying the "simscript" language that I've always wanted to try out.
For those of you who don't know, Simscript is/was a pseudo-language,
more accurately a pre-processor for Fortran that would take the details
in "source" form and create a fortran program which would actually "run"
the simulation. At least, that was the case in 1962 when it was
invented...
[http://cgibin.erols.com/ziring/cgi-bin/cep/cep.pl?_key=Simscript]
It looks like there is a new version which may be it's own stand-alone
language/processor altogether [http://www.simscript.com/] (looks like
there are some free downloads available, though I'd imagine "windows
only" -- hey, wait -- looks like this is cross-platform!
[http://www.simscript.com/products/simscript_platforms.cfm] WOOOHOO!!!
DIRECT LINUX SUPPORT!) Hmmm also: it looks like it is now a front-end
to C++ -- no matter, once you understand the concepts, you could write
this natively anyway, which is exactly what I did...
So, how does this all work, and what did I do you ask? Well...
The "core" of this is an "event queue", which is basically a sorted
collection or list. "events" are added with a timestamp and sorted
according to that timestamp. Processing occurs by evaluating the item
at the head of the queue and taking whatever action is appropriate,
including scheduling further events, and repeating until the queue is
empty. "time" within the simulation will always be the timestamp at the
head of the list, so if there is a two-hour or two-millisecond gap
between events it doesn't matter -- the simulation will simply skip
ahead accordingly.
The way I implemented this was to create two objects [which might have
been one object, but I'm working with VB in which "inheritance" doesn't
mean what you think...] A "simevent" object and an "event" object. The
"simevent" object consists of a timestamp and (a pointer to) an "event"
object. Simobjects are placed in the event queue and sorted by the
event time.
"event" objects basically have two methods: schedule and fire. The
"schedule" event takes a timestamp and an optional bit of data (in the
form of a string) and inserts itself into the global "event queue" as
follows:
Public Sub Schedule(Optional t As Date = "00:00:00", Optional xdata
As String = "")
Dim x As simEvent
Set x = EQ.Add(Me, t)
EventData.Add xdata, x.EventID
End Sub
The "eventdata" structure is a collection (sparse array) to store the
string for later retrieval. "EQ" is the global event queue, and
"adding" an item to the queue creates a simevent object and returns the
ID, which is guaranteed unique.
The second method, fire, is equally short:
Public Sub Fire(id As String)
Dim s As String
s = EventData(id)
RaiseEvent Activate(s)
End Sub
The "raiseevent" command causes the (generic) event object to tickle the
(specific) instance of the event object to perform whatever action is
necessary. This is "really cool" for a couple of reasons, most notably
the "engine" that drives the simulation is extremely short as well:
[defined within the code of the "event queue" object, aka EQ]
Public Sub RunEvents()
Dim EV As simEvent
While mCol.Count > 0
Set EV = NextEvent()
EV.thisevent.Fire EV.EventID
Wend
End Sub
The "nextevent" function removes the first event from the queue and
returns (a pointer to) it. Then, without knowing any details of what
this event is to do, it simply "fires" the contained "event" object
[above] and that, in turn, raises the "activate" event of the specific
instance. [whew!]
Now, ALL of that was just "background" information, but with that in
mind, actually /writing/ a simulation becomes trivial -- in the "main
line" code:
Dim WithEvents Generator As EventObj
Dim WithEvents SubmitJob As EventObj
Dim WithEvents JMSWakeup As EventObj
Dim WithEvents ReportJobs As EventObj ...
These statements create specific INSTANCES of the event object, [named
"eventobj" because "event" itself is a keyword...] and the keyword
"withevents" tells the VB interpreter/compiler that there will be a
dedicated code block for each of the "events" the object can "raise" [in
this case, "activate"] that will be different from all other instances
of the object. To actually "run" the simulation, you would have
something like this:
Private Sub Command1_Click()
Generator.Schedule TimeSerial(8, 0, 0)
JMSWakeup.schedule EQ.walltime
ReportJobs.schedule EQ.walltime + timeserial(0,15,0)
End Sub
Remember, "EQ" is the global event queue, and the "runevents" function
is shown above. Here I've told the "generator" object to schedule
itself for 8:00am (which, in turn, sets EQ.walltime, or "current
simulation time", to 8:00 am since it is the only thing in the
scheduling queue) I've also added a "wakeup" event, which is actually
what I'm trying to simulate [ironically, a job scheduler...] and a
"report on activity" event for 15 minutes after it starts. Then I tell
the event queue to "run all scheduled events". This in turn calls the
generator's "activate" code when the queue "fires" the event. The
Generator_activate code then looks like this:
Public Sub Generator_Activate(xdata as string)
dim sTime as date 'includes the "time" component of a date...
sTime = EQ.walltime
while sTime < cutoff
sTime = sTime + <some random amount>
Submitjob.Schedule sTime, <some random jobtype>
wend
end sub
Submitjob, in turn, exercises the "xdata" optional field and supplies
some specific-to-this-instance details about the job being submitted.
Later on, when this isntance "fires", the <random jobtype> value will be
passed as a string, and the code can then act accordingly. Note that
while "stime" is ever increasing (until the cutoff, or end-of-simulation
time), I could have written this as follows:
for I = 1 to <events To Simulate>
submitjob.schedule <some random time>, <some random jobtype>
next
The "event queue" object itself takes care of ensuring these are in
chronological order. In this case, "submitjob" simply adds 1 to a
counter of jobtypes awating processing (i.e., submitted.) "JMSWakeup",
the thing I'm trying to analyze, reviews the list of outstanding
jobtypes and schedules a "job completion" event based on the type of job
and how many "other" jobs are "running" [there is a limit to the number
of concurrant jobs...] The job completion event updates a counter which
is reported by the (periodic) reportjobs event. (periodic in that
reportjobs re-schedules itself if the simulation isn't "over") By
varying how often "JMSWakeup" fires, I can balance waiting times for
different classes of jobs against the overhead of JMS itself.
For validation of the simulation as a whole, instead of scheduling a
"generator" event object, I read the actual submission times of jobs
processed on our system from the system's log file -- when I'm done, I
compared the "reports" of backlogged jobs with what I "saw" on the live
system -- since they matched, I know I've got the simulation running in
step with reality...
"And there you have it..." -- it's actually pretty simple, but it looks
like I can do some amazing stuff with it...
More information about the Dev-sig
mailing list