Smalltalk80LanguageImplementation:Chapter 23

From 흡혈양파의 번역工房
Jump to: navigation, search
Chapter 23 Statistics Gathering in Event-Driven Simulations

Statistics Gathering in Event-Driven Simulations

A framework for specifying event-driven simulations was presented in the previous chapter. This framework did not include methods for gathering statistics about a simulation as it is running. Statistics might consist of the amount of time each simulation object spends in the simulation (and, therefore, how well the model supports carrying out the kinds of tasks specified by the objects); information about the length of the queues such as the maximum, minimum and average lengths and amount of time spent in each queue; and information about the utilization of the simulation's resources.


There are a number of ways to add statistics gathering to class Simulation or class SimulationObject. In this chapter, we provide four examples of statistics gathering: duration statistics, throughput histograms, event tallying, and event monitoring. Each of these examples involves creating a subclass of Simulation or SimulationObject in order to provide a dictionary in which data can be stored or a file into which data can be written, and, in that subclass, modifying the appropriate methods in order to store the appropriate data.


Duration Statistics

For the first example of statistics gathering, we create a general subclass of Simulation that simply stores the time an object enters and exits the simulation. The times are kept in a special record whose fields are the time at which the object entered the simulation (entranceTime) and the length of time the object spent in the simulation (duration). Records are described as follows.

class name SimulationObjectRecord
superclass Object
instance variable names entranceTime
duration
instance methods
accessing
    entrance: currentTime
        entranceTime  currentTime
    exit: currentTime
        duration  entranceTime - currentTime
    entrance
        entranceTime
    exit
        entranceTime + duration
    duration
        duration
printing
    printOn: aStream
        entranceTime printOn: aStream.
        aStream tab.
        duration printOn: aStream


An example subclass of Simulation which uses SimulationObjectRecords for gathering statistics is StatisticsWithSimulation. It is defined as follows.

class name StatisticsWithSimulation
superclass Simulation
instance variable names statistics
instance methods
initialization
    initialize
        super initialize.
        statistics  Dictionary new

simulation scheduling
    enter: anObject
        statistics at: anObject
            put: (SimulationObjectRecord new entrance: currentTime)
    exit: anObject
        (statistics at: anObject) exit: currentTime

statistics
    printStatisticsOn: aStream
        | stat |
        aStream cr.
        aStream nextPutAll: 'Object'.
        aStream tab.
        aStream nextPutAll: 'Entrance Time'.
        aStream tab.
        aStream nextPutAll: 'Duration'.
        aStream cr.
        "Sort with respect to the time the object entered the simulation. Because the keys as well as the values are needed, it is necessary to first obtain the set of associations and then sort them."
        stat  SortedCollection
            sortBlock: [ :i :j | i value entrance < = j value entrance].
        statistics associationsDo: [ :each | stat add: each].
        stat do:
            [ :anAssociation |
                aStream cr.
                anAssociation key printOn: aStream.
                aStream tab.
                anAssociation value printOn: aStream
                "The value is a SimulationObjectRecord which prints the entrance time and duration"]


Suppose we created NothingAtAll as a subclass of StatisticsWithSimulation. In this example, NothingAtAll schedules two simulation objects: one that does nothing (a DoNothing), arriving according to a uniform distribution from 3 to 5 units of time starting at 0; and one that looks around for 5 units of simulated time (a Visitor), arriving according to a uniform distribution from 5 to 10 units of time starting at 1. Whenever one of these objects enters the simulation, an entry is put in the statistics dictionary. The key is the object itself; the equals (=) test is the default (= =), so each object is a unique key. The value is an instance of SimulationObjectRecord with entrance data initialized. When the object exits, the record associated with it is retrieved and a message is sent to it in order to set the exit data.

class name DoNothing
superclass SimulationObject
instance methods
no new methods
class name Visitor
superclass SimulationObject
instance methods
simulation control
    tasks
        self holdFor: 5
class name NothingAtAll
superclass StatisticsWithSimulation
instance methods
initialization
    defineArrivalSchedule
        self scheduleArrivalOf: DoNothing
            accordingTo: (Uniform from: 3 to: 5).
        self scheduleArrivalOf: Visitor
            accordingTo: (Uniform from: 5 to: 10)
            startingAt: 1


Whenever we halt the simulation, we can send the message printStatisticsOn: aFile (where aFile is a kind of FileStream). The result might look like:

Object Entrance Time Duration
a DoNothing 0.0 0.0
a Visitor 1.0 5.0
a DoNothing 4.58728 0.0
a Visitor 6.71938 5.0
a DoNothing 9.3493 0.0
a DoNothing 13.9047 0.0
a Visitor 16.7068 5.0
a DoNothing 17.1963 0.0
a DoNothing 21.7292 0.0
a Visitor 23.2563 5.0
a DoNothing 25.6805 0.0
a DoNothing 29.3202 0.0
a Visitor 32.1147 5.0
a DoNothing 32.686 0.0
a DoNothing 36.698 0.0
a DoNothing 41.1135 0.0
a Visitor 41.1614 5.0
a DoNothing 44.3258 0.0
a Visitor 48.4145 5.0
a DoNothing 48.492 0.0
a DoNothing 51.7833 0.0
a Visitor 53.5166 5.0
a DoNothing 56.4262 0.0
a DoNothing 60.5357 0.0
a Visitor 63.4532 5.0
a DoNothing 64.8572 0.0
a DoNothing 68.7634 0.0
a Visitor 68.921 5.0
a DoNothing 72.4788 0.0
a DoNothing 75.8567 0.0


Throughput Histograms

A common statistic gathered in simulations is the throughput of objects, that is, how many objects pass through the simulation in some amount of time; this is proportional to how much time each object spends in the simulation. Gathering such statistics involves keeping track of the number of objects that spend time within predetermined intervals of time. Reporting the results involves displaying the number of objects, the minimum time, maximum time, and the number of objects whose times fall within each specified interval. Such statistics are especially useful in simulations involving resources in order to determine whether or not there are sufficient resources to handle requests-if objects have to wait a long time to get a resource, then they must spend more time in the simulation.


In order to support the gathering of throughput statistics, we provide class Histogram. Histograms maintain a tally of values within prespecified ranges. For example, we might tally the number of times various values fall between 1 and 5, 5 and 10, 10 and 20, 20 and 25, 25 and 30, 30 and 35, 35 and 40, and 40 and 45. That is, we divide the interval 5 to 45 into bins of size 5 and keep a running count of the number of times a value is stored into each bin.


Class Histogram is created by specifying the lower bound, the upper bound, and the bin size. To obtain a Histogram for the above example, evaluate

Histogram from: 5 to: 45 by: 5


Besides data on the bins, a Histogram keeps track of minimum, maximum, and total values entered. An entry might not fit within the bounds of the interval; an additional variable is used to store all such entries (extraEntries). The bins are stored as elements of an array; the array size equals the number of bins (that is, upper bound lower bound // bin size). The message store: aValue is used to put values in the Histogram. The index of the array element to be incremented is 1 + (aValue - lower bound // bins size). Most of the methods shown support printing the collected information.

class name Histogram
superclass Object
instance variable names tallyArray
lowerBound upperBound
step
minValue maxValue
totalValues
extraEntries
class methods
class initialization
    from: lowerNum to: upperNum by: step
        self new newLower: lowerNum upper: upperNum by: step
instance methods
accessing
    contains: aValue
        lowerBound < = aValue and: [aValue < upperBound]
    store: aValue
        | index |
        minValue isNil
            ifTrue: [minValue  maxValue  aValue]
            ifFalse: [minValue  minValue min: aValue.
                maxValue  maxValue max: aValue].
        totalValues  totalValues + aValue.
        (self contains: aValue)
            ifTrue: [index  (aValue - lowerBound // step) + 1.
                tallyArray at: index put: (tallyArray at: index) + 1]
            ifFalse: [extraEntries  extraEntries + 1]

printing
    printStatisticsOn: aStream
        | totalObjs pos |
        self firstHeader: aStream.
        aStream cr; tab.
        totalObjs  extraEntries.
        "count the number of entries the throughput records know"
        tallyArray do: [ :each | totalObjs  totalObjs + each].
        totalObjs printOn: aStream.
        aStream tab.
        minValue printOn: aStream.
        aStream tab.
        maxValue printOn: aStream.
        aStream tab.
        (totalValues / totalObjs) asFloat printOn: aStream.
        aStream cr.
        self secondHeader: aStream.
        aStream cr.
        pos  lowerBound.
        tallyArray do:
            [ :entry |
                pos printOn: aStream.
                aStream nextPut: $-.
                (pos  pos + step) printOn: aStream.
                aStream tab.
                entry printOn: aStream.
                aStream tab.
                (entry / totalObjs) asFloat printOn: aStream.
                aStream tab.
                aStream nextPut: $|
                "print the X's"
                entry rounded timesRepeat: [aStream nextPut: $X].
                aStream cr]
    firstHeader: aStream
        aStream cr; tab.
        aStream nextPutAll: 'Number of'.
        aStream tab.
        aStream nextPutAll: 'Minimum'.
        aStream tab.
        aStream nextPutAll: 'Maximum'.
        aStream tab.
        aStream nextPutAll: 'Average'.
        aStream cr; tab.
        aStream nextPutAll: 'Objects'.
        aStream tab.
        aStream nextPutAll: 'Value'.
        aStream tab.
        aStream nextPutAll: 'Value'.
        aStream tab.
        aStream nextPutAll: 'Value'
    secondHeader: aStream
        aStream cr; tab.
        aStream nextPutAll: 'Number of'.
        aStream cr.
        aStream nextPutAll: 'Entry'.
        aStream tab.
        aStream nextPutAll: 'Objects'.
        aStream tab.
        aStream nextPutAll: 'Frequency'.

private
    newLower: lowerNum upper: upperNum by: stepAmount
        tallyArray  Array new: (upperNum - lowerNum // stepAmount).
        tallyArray atAllPut: 0.
        lowerBound  lowerNum.
        upperBound  upperNum.
        step  stepAmount.
        minValue  maxValue  nil.
        totalValues  0.
        extraEntries  0


A simulation of visitors to a museum serves as an example of the use of a Histogram. Museum is like NothingAtAll in that Visitors arrive and look around. The Visitors take a varying amount of time to look around, depending on their interest in the museum artifacts. We assume that this time is normally distributed with a mean of 20 and a standard deviation of 5. Visitors come to the museum throughout the day, one every 5 to 10 units of simulated time.

class name Museum
superclass Simulation
instance variable names statistics
instance methods
initialization
    initialize
        super initialize.
        statistics  Histogram from: 5 to: 45 by: 5
    defineArrivalSchedule
        self scheduleArrivalOf: Visitor
            accordingTo: (Uniform from: 5 to: 10)

scheduling
    exit: aSimulationObject
        super exit: aSimulationObject.
        statistics store: currentTime - aSimulationObject entryTime
    printStatisticsOn: aStream
        statistics printStatisticsOn: aStream


In order for class Museum to update the statistics, Visitors must keep track of when they entered the museum and be able to respond to an inquiry as to their time of entry.

class name Visitor
superclass SimulationObject
instance variable names entryTime
instance methods
initialization
    initialize
        super initialize.
        entryTime  ActiveSimulation time

accessing
    entryTime
        entryTime

simulation control
    tasks
        self holdFor: (Normal mean: 20 deviation: 5) next


To create and run the simulation, we evaluate

aSimulation  Museum new startUp.
[aSimulation time < 50] whileTrue: [aSimulation proceed]


When the Museum was created, a Histogram for statistics was created. Each time a Visitor left the Museum, data was stored in the Histogram. The data consists of the duration of the visit.


After running the simulation until time 50, we ask the Museum for a report.

aSimulation printStatisticsOn: (Disk file: 'museum.report')


The method associated with printStatisticsOn: sends the same message, printStatisticsOn:, to the Histogram, which prints the following information on the file museum.report.

Number of Objects Minimum Value Maximum Value Average Value
64 10.0202 31.2791 20.152
Entry Number of Objects Frequency
5-10 0 0
10-15 14 0.21875 XXXXXXXXXXXXXX
15-20 16 0.25 XXXXXXXXXXXXXXXX
20-25 20 0.3125 XXXXXXXXXXXXXXXXXXXX
25-30 13 0.203125 XXXXXXXXXXXXX
30-35 1 0.015625 X
35-40 0 0
40-45 0 0


Tallying Events

As another example of tallying the events in a simulation, we present a commonly-used example of a simulation of Traffic. In this simulation, we tally the number of cars that enter an intersection, distinguishing those that drive straight through the intersection from those that turn left and those that turn right. By observation, we note that twice as many cars go straight as turn right or left, but twice as many turn left as right. A new car arrives at the intersection according to a uniform distribution every 0.5 to 2 units of time (self scheduleArrivalOf: Car accordingTo: (Uniform from: 0.5 to: 2)). We will run the simulation until simulated time exceeds 100.

class name Traffic
superclass Simulation
instance variable names statistics
instance methods
initialization
    initialize
        super initialize.
        statistics  Dictionary new: 3.
        statistics at: #straight put: 0.
        statistics at: #right put: 0.
        statistics at: #left put: 0
    defineArrivalSchedule
        self scheduleArrivalOf: Car accordingTo: (Uniform from: 0.5 to: 2).
        self schedule: [self finishUp] at: 100

statistics
    update: key
        statistics at: key put: (statistics at: key) + 1
    printStatisticsOn: aStream
        aStream cr.
        aStream nextPutAll: 'Car Direction Tally'
        statistics associationsDo:
            [ :assoc |
                aStream cr.
                assoc key printOn: aStream.
                aStream tab.
                assoc value printOn: aStream]


Note that in the method associated with defineArrivalSchedule, the action self finishUp is scheduled to occur at simulated time 100. This event will terminate the simulation as desired.

class name Car
superclass SimulationObject
instance methods
simulation control
    tasks
        "Sample, without replacement, the direction through the intersection that the car will travel."
        | sample |
        sample  SampleSpace data:
            #(left left right straight straight straight straight straight straight)
        ActiveSimulation update: sample next


SampleSpace was a class we introduced in Chapter 21. Cars are scheduled to enter the simulation with the sole task of picking a direction to tell the simulation. After running the simulation, we ask the Traffic simulation to report the tallies by sending it the printStatisticsOn: message. A possible outcome of evaluating

aSimulation  Traffic new startUp.
[aSimulation proceed] whileTrue.
aSimulation printStatisticsOn: (Disk file: 'traffic.data')


is the following information written on the file traffic.data.

Car Direction Tally
straight 57
right 8
left 15


Event Monitoring

Another possible technique for gathering data from a simulation is to note the occurrence of each (major) event, including the entering and exiting of simulation objects. This is accomplished by creating a subclass of SimulationObject that we call EventMonitor. In this example, a class variable refers to a file onto which notations about their events can be stored. Each message that represents an event to be monitored must be overridden in the subclass to include the instructions for storing information on the file. The method in the superclass is still executed by distributing the message to the pseudo-variable super.


Basically, the notations consist of the time and identification of the receiver (a kind of SimulationObject), and an annotation such as "enters" or "requests" or "releases."

class name EventMonitor
superclass SimulationObject
class variable names DataFile
class methods
class initialization
    file: aFile
        DataFile  aFile
instance methods
scheduling
    startUp
        self timeStamp.
        DataFile nextPutAll: 'enters'.
        super startUp
    finishUp
        super finishUp.
        self timeStamp.
        DataFile nextPutAll: 'exits'

task language
    holdFor: aTimeDelay
        self timeStamp.
        DataFile nextPutAll: 'holds for'.
        aTimeDelay printOn: DataFile.
        super holdFor: aTimeDelay
    acquire: amount ofResource: resourceName
        | aStaticResource |
        "Store fact that resource is being requested."
        self timeStamp.
        DataFile nextPutAll: 'requests'.
        amount printOn: DataFile.
        DataFile nextPutAll: 'of', resourceName.
        "Now try to get the resource."
        aStaticResource  super acquire: amount
            ofResource: resourceName.
        "Returns here when resource is obtained; store the fact."
        self timeStamp.
        DataFile nextPutAll: 'obtained'.
        amount printOn: DataFile.
        DataFile nextPutAll: 'of ', resourceName.
        aStaticResource
    acquire: amount ofResource: resourceName withPriority: priorityNumber
        | aStaticResource |
        "Store fact that resource is being requested"
        self timeStamp.
        DataFile nextPutAll: 'requests'.
        amount printOn: DataFile.
        DataFile nextPutAll: 'at priority'.
        priorityNumber printOn: DataFile.
        DataFile nextPutAll: 'of', resourceName.
        "Now try to get the resource."
        aStaticResource 
            super acquire amount
                ofResource: resourceName
                withPriority: priorityNumber.
        "Returns here when resource is obtained; store the fact."
        self timeStamp.
        DataFile nextPutAll: "obtained'.
        amount printOn: DataFile.
        DataFile nextPutAll: 'of', resourceName.
        ↑aStaticResource
    produce: amount ofResource: resourceName
        self timeStamp.
        DataFile nextPutAll: 'produces'
        amount printOn: DataFile.
        DataFile nextPutAll: 'of', resourceName.
        super produce amount ofResource resourceName
    release: aStaticResource
        self timeStamp.
        DataFile nextPutAll: 'releases'
        aStaticResource amount printOn: DataFile. 
        DataFile nextPutAll: 'of', aStaticResource name. 
        super release: aStaticResource
    acquireResource: resourceName
        | anEvent |
        "Store fact that resource is being requested"
        self timeStamp.
        DataFile nextPutAll: 'wants to serve for'.
        DataFile nextPutAll: resourceName.
        "Now try to get the resource."
        anEvent ← super acquireResource resourceName.
        "Returns here when resource is obtained store the fact." 
        self timeStamp.
        DataFile nextPutAll: 'can serve"
        anEvent condition printOn: DataFile.
        anEvent
    produceResource: resourceName
        self timeStamp.
        DataFile nextPutAll: 'wants to get service as'
        DataFile nextPutAll: resourceName.
        super produce amount ofResource resourceName
    resume: anEvent
        self timeStamp.
        DataFile nextPutAll: 'resumes'.
        anEvent condition printOn: DataFile.
        super resume: anEvent

private
    timeStamp
        DataFile cr.
        ActiveSimulation time printOn: DataFile.
        DataFile tab.
        self printOn: DataFile.


We can monitor the events of the NothingAtAll simulation consisting of arrivals of Visitors and default simulations (DoNothings). Except for creating Visitor and DoNothing as subclassses of EventMonitor rather than of SimulationObject, the class definitions are the same.

class name DoNothing
superclass EventMonitor
instance methods
no new methods
class name Visitor
superclass EventMonitor
instance methods
simulation control
    tasks
        self holdFor: (Uniform from: 4.0 to: 10.0) next


NothingAtAll is redefined so that the default simulation is an EventMonitor.

class name NothingAtAll
superclass Simulation
instance methods
initialization
    defineArrivalSchedule
        self scheduleArrivalOf: DoNothing
            accordingTo: (Uniform from: 1 to: 5).
        self scheduleArrivalOf: Visitor
            accordingTo: (Uniform from: 4 to: 8)
            startingAt: 3


After executing

Visitor file: (Disk file: 'NothingAtAll.events').
    "This informs DoNothing too"
aSimulation  NothingAtAll new startUp.
[aSimulation time < 25] whileTrue: [aSimulation proceed]


the file 'NothingAtAll.events' contains the following information

0.0 a DoNothing enters
0.0 a DoNothing exits
3.0 a Visitor enters
3.0 a Visitor holds for 7.5885
4.32703 a DoNothing enters
4.32703 a DoNothing exits
7.74896 a Visitor enters
7.74896 a Visitor holds for 4.14163
8.20233 a DoNothing enters
8.20233 a DoNothing exits
10.5885 a Visitor exits
11.8906 a Visitor exits
12.5153 a DoNothing enters
12.5153 a DoNothing exits
14.2642 a Visitor enters
14.2642 a Visitor holds for 4.51334
16.6951 a DoNothing enters
16.6951 a DoNothing exits
18.7776 a Visitor exits
19.8544 a Visitor enters
19.8544 a Visitor holds for 5.10907
20.5342 a DoNothing enters
20.5342 a DoNothing exits
23.464 a DoNothing enters
23.464 a DoNothing exits
24.9635 a Visitor exits


Distinctively labeling each arriving SimulationObject might improve the ability to follow the sequence of events. The goal is to have a trace for an execution of the NothingAtAll simulation look like the following.

0.0 DoNothing 1 enters
0.0 DoNothing 1 exits
3.0 Visitor 1 enters
3.0 Visitor 1 holds for 7.5885
4.32703 DoNothing 2 enters
4.32703 DoNothing 2 exits
7.74896 Visitor 2 enters
7.74896 Visitor 2 holds for 4.14163
8.20233 DoNothing 3 enters
8.20233 DoNothing 3 exits
10.5885 Visitor 1 exits
11.8906 Visitor 2 exits
12.5153 DoNothing 4 enters
12.5153 DoNothing 4 exits
14.2642 Visitor 3 enters
14.2642 Visitor 3 holds for 4.51334
16.6951 DoNothing 5 enters
16.6951 DoNothing 5 exits
18.7776 Visitor 3 exits
19.8544 Visitor 4 enters
19.8544 Visitor 4 holds for 5.10907
20.5342 DoNothing 6 enters
20.5342 DoNothing 6 exits
23.464 DoNothing 7 enters
23.464 DoNothing 7 exits
24.9635 Visitor 4 exits


Each subclass of EventMonitor must create its own sequence of labels. EventMonitor sets up a label framework in which the subclass can implement a way of distinguishing its instances. EventMonitor itself provides a model that the subclasses can duplicate; in this way, a default simulation using instances of EventMonitor can be used to produce the trace shown above. In addition to the scheduling, task language and private, messages shown in the earlier implementation of EventMonitor, the class description has the following messages.

class name EventMonitor
superclass SimulationObject
instance variable names label
class variable names DataFile
Counter
class methods
class initialization
    file: aFile
        DataFile  aFile.
        Counter  0
instance methods
initialization
    initialize
        super initialize.
        self setLabel

accessing
    setLabel
        Counter  Counter + 1.
        label  Counter printString
    label
        label

printing
    printOn: aStream
        self class name printOn: aStream.
        aStream space.
        aStream nextPutAll: self label


Visitor, as a subclass of EventMonitor, has to have an independent class variable to act as the counter of its instances. The class description of Visitor is now

class name Visitor
superclass EventMonitor
class variable names MyCounter
class methods
class initialization
    file: aFile
        super file: aFile.
        MyCounter  0
instance methods
accessing
    setLabel
        MyCounter  MyCounter + 1.
        label  MyCounter printString

simulation control
    tasks
        self holdFor: (Uniform from: 4.0 to: 10.0) next


MyCounter is set to 0 when the instance is tom initialize; the method is found in class EventMonitor. Printing retrieves the label that Visitor redefines with respect to MyCounter. We let DoNothing use the class variable, Counter, of its superclass. Then the desired trace will be produced using these definitions.


Notes