/************************************************************************
* @
M/M/1 Simulation written in COST @
*
* @ M/M/1 Simulation
@
* To help users understand how COST facilitates the development of
* simulation models, we will describe in detail the process of building an
* M/M/1 system.
* In an M/M/1 system, packets(or customers) arrive according
* to a Poisson distribution and compete for the service in an FIFO
* (First-In-First-Out) manner. The service time is also drawn from a
* Poisson distribution. In practice, M/M/1 system is useful because many
* complex systems can be abstracted as composition of simple M/M/1 systems.
* Theoretically, M/M/1 system has an accurate mathematical solution
* with respect to the mean arrival rate and the mean service rate. As a
* result, it is well suited for validating simulation results.
*
* An M/M/1 system built using COST is composed of four components, namely,
* Source, FIFO, Server and Sink. Packets are created by Source, queued
* in FIFO, served by Server, and dispatched from Sink.
*
* @
@
*
* @ Using COST
@
* To use COST, simply include the header file @cost.h@. The other file,
* @deque@, is required by the FIFO component we will describe momentarily.
************************************************************************/
#include
#include "cost.h"
using namespace cost;
/************************************************************************
* @ New Data Type
@
* A new data type, @Packet@, is created to represent the packets
* that flow through the M/M/1 system. We are interested in measuring the
* delay of packets, which is equal to the sum of the time spent in the
* FIFO queue plus the service time. For this purpose, Packet contains
* two fields, @arrival_time@ and @departure_time@, which
* record the arrival time and the departure time, respectively. In
* addition, to identify each packet, another field seq_number is
* declared to hold the sequence number (these fields are not all used
* in the sample simulation, but they may be essential in other cases).
************************************************************************/
struct Packet
{
int seq_number;
double arrival_time,departure_time;
};
/************************************************************************
* @ Source
@
* Now let us build our first component class @Source@, derived
* from @TypeII@. @TypeII@ is the class of base component in COST.
* All components in COST must be derived from @TypeII@. And the reason
* it is called TypeII is that in our simulation component classification
* they are Time-Aware, meaning they are aware of the existence of
* simulated time but have no control over it.
*
* The @Source@ component creates packets randomly at an average rate
* specified by the parameter @interval@. It has an outport with
* parameter type @Packet@ and return type @void@ (nothing to return),
* and a timer for scheduling the event to deliver the next packet.
*
* Notice the format of declaring outports and timers. Basically,
* inports and outports need two template parameters, one is the
* @parameter@ @type@ which is the type of data that the ports will
* pass on, the other is the @return@ @type@ that specifies the type
* of returned value.
*
* The @Create()@ function. is the event handler for the
* timer. When the timer is activated (i.e., the specified time has been
* reached by the simulation engine), the @Create()@ function is
* called.
*
* In COST, every event handler takes two arguments. The first is
* the content of the event. Its type must be the same to the type of
* the inport or the timer that the event handler was bound to.
* The second argument is an integer, which is useful for arrays of
* inports or timers. For now, it suffices to know that the second
* argument is always zero and we simply ignore it.
*
* An event handler must return a value whose type is the same as
* the return type of the binding inport or timer.
************************************************************************/
class Source : public TypeII
{
public:
double interval;
Outport< void, Packet > out;
Timer timer;
public:
void Setup(CorsaContainer*,const char*);
void Start();
private:
void Create(trigger_t&, int index);
Packet m_packet; // for temporarily holding the outgoing packet
int m_seq_number; // each packet has an unique sequence number
};
/************************************************************************
* All COST components must provide a @Setup()@ function in which
* the @Setup()@ function of every port and timer must be called.
* The @Setup()@ function of the base class @TypeII@ must
* be invoked last.
*
* The function call of the @Setup()@ function of inports, outports
* and times do several things at one time. First, we must provide
* a pointer to the component to which the object belongs. Second,
* for inports and timers, an event handler must be passed which will
* be bound to the object. Last thing is to name the object.
*
* The @Setup()@ function of a @TypeII@ component is slightly different.
* The first argument is a pointer to a predefined class, @CorsaContainer@,
* which is actually the class of component container. Both @TypeII@
* and @CostSimEng@ (the simulation engine we will be using, described
* below) are derived from @CorsaContainer@.
************************************************************************/
void Source::Setup(CorsaContainer*c, const char* name)
{
out.Setup(this,"out"); // naming the outport
timer.Setup(this,&Source::Create,"timer"); // bind to the event handler
TypeII::Setup(c,name); // must be called last
}
/************************************************************************
* The @Start()@ function, invoked the moment the simulation just
* gets started, i.e., at the simulation time zero, is where a component
* can perform initialization of variables and schedule initial events
* by calling the @Set()@ method of the desired timer.
* @Exponential()@ is an function declared in the @TypeII@
* class to generate a Poisson distribution.
************************************************************************/
void Source::Start()
{
m_seq_number=0;
timer.Set(Exponential(interval));
}
/************************************************************************
* As said before, the @Create()@ function is bound to the timer,
* so it is invoked every time the timer becomes activated. Its tasks
* include scheduling the next timer event whose timestamp is the time
* the next packet is to be generated, and delivering the current packet
* to the outport using the @Write()@ function.
*
* @SimTime()@, a member function declared in the @TypeII@ class, returns
* the current simulated time.
************************************************************************/
void Source::Create(trigger_t&, int)
{
timer.Set(SimTime()+Exponential(interval) );
m_packet.seq_number=m_seq_number++;
m_packet.arrival_time = SimTime();
out.Write(m_packet);
return;
}
/************************************************************************
* @FIFO Queue
@
* The @FIFO@ component is declared as a template class with a template
* parameter specifying the type of packets that the queue can hold. When
* instantiated with different packet types, this component can be
* reused to handle packets of any type.
*
* This component contains an inport and outport, to receive
* and sent packets, both with @DATATYPE@, the template parameter,
* as the parameter type, and @void@ as the return type.
* Another port, named @next@, is an inport
* indicating that it is time to send out the next packet. Its parameter
* type is @trigger_t@, a predefined empty class.
************************************************************************/
template < class DATATYPE >
class FIFO : public TypeII
{
public:
void Setup(CorsaContainer*, const char*);
void Start();
unsigned int queue_length; // packets dropped when queue is full
Inport < void, DATATYPE > in;
Inport < void, trigger_t > next;
Outport < void, DATATYPE > out;
private:
void Arrive(DATATYPE& packet, int index);
void Next(trigger_t&, int index);
bool m_busy; // if the connected server is busy
std::deque m_queue; // store packets here
};
/************************************************************************
* The @Setup()@ function setups up ports and timers one by one.
* We bind the @Arrive@ function to the @in@ inport,
* the @Next@ function to the @next@ inport.
************************************************************************/
template < class DATATYPE >
void FIFO :: Setup(CorsaContainer* c, const char * name)
{
in.Setup(this,&FIFO::Arrive,"in");
out.Setup(this,"out");
next.Setup(this,&FIFO::Next,"next");
TypeII::Setup(c,name);
}
/************************************************************************
* It makes sense to assume the connected server is available for
* immediate service, so we need to initialize the status here.
************************************************************************/
template < class DATATYPE >
void FIFO :: Start()
{
m_busy=false;
}
/************************************************************************
* The @Arrive()@ function is called when a packet arrives.
* Packets are passed by reference to avoid variable copying overhead.
* @m_busy@ remember the status of the connected server. If the
* server is free, simply forward the packet to the outport and update
* @m_busy@ accordingly. Otherwise, append the packet to the tail of
* the internal queue, if the queue is not full.
************************************************************************/
template < class DATATYPE >
void FIFO::Arrive(DATATYPE& packet, int)
{
if (!m_busy)
{ // server is free, we can pass it through
out.Write(packet);
m_busy=true;
} else
if (m_queue.size()
void FIFO :: Next(trigger_t&, int)
{
if (m_queue.size()>0)
{
out.Write(m_queue.front());
m_queue.pop_front();
} else
{
m_busy=false;
}
return;
}
/************************************************************************
* @Server
@
* Similarly, @Sever@ is a template class. It simulates the
* service for each coming packet. Ports are similar to those of
* the @FIFO@ component, except that the @next@ port is an
* outport. It is to be connected with the @next@ port of the
* @FIFO@ component. The timer is used to schedule the event representing
* the completion of the service.
************************************************************************/
template
class Server : public TypeII
{
public:
double service_time;
Inport < void, DATATYPE > in;
Outport < void, DATATYPE > out;
Outport < void, trigger_t > next;
Timer timer;
void Setup(CorsaContainer*c, const char*);
private:
void Arrive(DATATYPE& packet, int);
void Depart(trigger_t&, int);
DATATYPE m_packet;
};
/************************************************************************
* Here comes the Setup() function.
************************************************************************/
template
void Server::Setup(CorsaContainer*c, const char* name)
{
in.Setup(this,&Server::Arrive,"in");
out.Setup(this,"out");
next.Setup(this,"next");
timer.Setup(this,&Server::Depart,"timer");
TypeII::Setup(c,name);
}
/************************************************************************
* When a packet comes, schedule the service completion event.
************************************************************************/
template
void Server :: Arrive(DATATYPE& packet,int)
{
m_packet=packet;
timer.Set(SimTime()+Exponential(service_time));
return;
}
/************************************************************************
* When it is time for the packet to depart (the service completion event arrives),
* write it to the outport @out@, and at the same time send a trigger signal to the
* outport @next@.
************************************************************************/
template
void Server :: Depart(trigger_t&, int)
{
out.Write(m_packet);
trigger_t t;
next.Write(t);
return;
}
/************************************************************************
* @Sink
@
* In the @Sink@ component, we collect the time that each packet spent in
* the @FIFO@ queue and the server. It only has one inport and no timer.
* What is new here is the @Stop()@ function, which
* is called when the simulation reaches the preset ending time.
************************************************************************/
class Sink : public TypeII
{
public:
Inport< void, Packet > in;
void Start()
{
m_total=0.0;
m_number=0;
}
void Setup(CorsaContainer*c, const char* name)
{
in.Setup(this,&Sink::Arrive,"in");
TypeII::Setup(c,name);
}
void Stop()
{
printf("Average packet delay at %s is: %f (%d packets) \n",
GetName(), m_total/m_number,m_number);
}
private:
double m_total;
int m_number;
Packet m_packet;
void Arrive(Packet& packet, int )
{
m_packet=packet;
m_packet.departure_time=SimTime();
m_total+=m_packet.departure_time-m_packet.arrival_time;
m_number++;
return;
}
};
/************************************************************************
* @Constructing the Simulation
@
* The simulation is derived from the @CostSimEng@ class. Components are
* instantiated as private members, while simulation parameters as
* public members.
************************************************************************/
class MM1 : public CostSimEng
{
public:
void Setup(const char*);
double interval;
int queue_length;
double service_time;
private:
Source source;
FIFO fifo;
Server server;
Sink sink;
};
/************************************************************************
* The simulation has a @Setup()@ function too. It first maps component
* parameters to corresponding system parameters (for instance, assign
* the value of the system parameter @interval@ to the component parameter
* @source.interval@), and then invokes
* the @Setup()@ function of every component. Afterwards, it connects
* pairs of inport and outports. Finally, the @Setup()@ function of
* the the base class is invoked. The order of these three steps,
* parameterization, setting up components, interconnection, and then
* setting up the system, must be strictly followed.
************************************************************************/
void MM1::Setup(const char*name)
{
source.interval=interval;
fifo.queue_length=queue_length;
server.service_time=service_time;
source.Setup(this,"source");
fifo.Setup(this,"fifo");
server.Setup(this,"server");
sink.Setup(this,"sink");
Connect(source.out,fifo.in);
Connect(fifo.out,server.in);
Connect(server.next,fifo.next);
Connect(server.out,sink.in);
CostSimEng::Setup(name);
}
/************************************************************************
* @Running the Simulation
@
* To run the M/M/1 simulation, first we need to create an M/M/1
* simulation object. Several default system parameters must be determined.
* @StopTime@ denote the ending time of the simulation.
* @Seed@ is the initial seed of the random number generator used
* by the simulation.
* @InfoLevel@ determines the detail level of output information.
* @@
* @- @ 0 : Only errors will be shown.
* @
- @ 1 : Error and warning will be shown.
* @
- @ 2 : Normal.
* @
- @ 3 : Verbose.
* @
@
*
* To run the simulation, type in:
*
* mm1-cost [stop time] [random seed] [info level]
*
************************************************************************/
int main(int argc, char* argv[])
{
MM1 mm1;
mm1.interval=1;
mm1.queue_length=100;
mm1.service_time=0.5;
mm1.StopTime=1000000.0;
mm1.Seed=10;
mm1.InfoLevel=2;
if (argc>=2)
mm1.StopTime=atof(argv[1]);
if (argc>=3)
mm1.Seed=atoi(argv[2]);
if (argc>=4)
mm1.InfoLevel=atoi(argv[3]);
mm1.Setup("mm1"); // must be called first
mm1.Run(); // run the simulation
return 0;
}