/************************************************************************ * @ 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; }