The SimStream simulation software for video streaming is a project in progress and the software on this page is subject to change. There are no guarantees for this software and you can use it on your own risk. SimStream is free software and is licensed under the terms of GNU General Public License.
- Source code documentation
- Download the open source code as ZIP (305 KB) or TAR (207 KB) archive (version 2010.12.14)
- The Basics
- Simulator Core
- Networking Library
- Streaming Library
- Simulation of Large-Scale P2P Streaming
In this chapter you will learn the basics of the SimStream simulation software:
- What is SimStream and what are the main features?
- What is the software architecture and the basic components?
- How can you use the basic components to create a simple simulator?
The streaming simulator, or SimStream for short, is a packet-level time-discrete network simulator, designed to be efficient for simulating large scale streaming applications. A particular requirement of these applications is that they need to simulate a large number of hosts and packets leading to extensive simulation times for many scenarios.
Compared with existing network simulators, SimStream reduces the complexity and computing times with the following key aspects:
- Uses native executable code written in C++
- Uses index-based addressing making packet routing and forwarding a constant-time operation
- Uses delegate-based communication between simulator components
SimStream consists of the following main components:
- The core
- A set of add-on libraries
- A set of implemented simulators
The figure I.a illustrates the main components and sub-components.
Figure I.a. SimStream architecture and simulators currently implemented
The simulator core
The simulator core is the central part of the simulator that can be used to create any type of time-discrete simulations, not necessarily computer network simulations. The core uses an event scheduling mechanism to serialize the execution of tasks according to their temporal order within the simulation. The core provides a set of core services, interfaces and libraries.
- The core services includes the simulator run-time that executes the simulation tasks. In addition, the run-time manages the simulation virtual time and schedules new tasks in the form of simulation events and simulation calls stored in an events and calls list.
- The core interfaces provide the foundation to build a simulator. They contain an interface for simulation events and an interface for the simulation model.
- The core libraries provide two classes of essential elements. The first, contains several entities such as delegates, calls and events, that allow flexible cross-object communication within the simulation. The second, provides timers.
The simulation libraries
The simulation libraries bring an extensive set of functionality that is required to implement network simulations for multimedia streaming. Although the libraries are not at all essential when developing a fully customized simulation from scratch, they provide common used elements such as basic implementation of several network protocols, network entities such as hosts, routers and links, streaming client and server application layers, streaming encoders and decoders, etc. In addition, commonly-used routines such as data collection, output formatting, cross-platform random number generators and shuffling are also available.
The libraries provide evolving functionality, and typically contain any set of objects and functions that is general enough to be used in more than one simulator.
The simulators is a list of customized simulator implementation that are part of the current version of SimStream. Each of this simulators uses the core and various elements from the simulation libraries to implement its desired objectives. The following tables illustrates the current simulators and their purpose.
|P2P Streaming with Single ALM-tree||Simulation of a multiple channel IPTV service with hybrid IP-multicast and P2P streaming. The P2P overlay uses a single ALM tree with push-based forwarding for each channel.|
|P2P Streaming with Multiple ALM-trees||Simulation of a multiple channel IPTV service with hybrid IP-multicast and P2P streaming. The P2P overlay uses more than one ALM tree with push-based forwarding for each channel.|
|P2P Streaming with Mesh||Simulation of a multiple channel IPTV service with hybrid IP-multicast and P2P streaming. The P2P overlay uses a mesh structure for each channel with segment-level pull-based forwarding. Segment scheduling is done according to the DoNET/Coolstreaming algorithm.|
|Unicast Connection Measurement||Simplified simulation of a multiple channel IPTV service for the purpose of measuring the hop-based distance between hosts.|
|Multicast Connection Measurement||Simplified simulation of a multiple channel IPTV service for the purpose of measuring the size of the IP multicast tree.|
|TFRC Flow Throughput Measurement||Simulation of TFRC flow congestion control.|
This chapter describes the core of the SimStream simulator:
- The run-time and the scheduling of simulation of events, calls
- The role of the simulation model
- Simulation objects and cross-communication between them
- Times and their usage
The run-time is the central part of the simulator. It manages the execution flow of the simulation tasks in the form of simulation events and simulation calls. In addition, it keeps track of the simulation virtual time, and provides the methods for the scheduling and cancellation of events and calls. The run-time is implemented by the following object class.
|CSim||The simulator run-time.|
Because all other objects within a simulation cannot call the run-time directly, for purposes such as scheduling events and obtaining the current virtual time, the run-time provides an abstract interface called the simulator handler that can be used for such purpose. At startup, the run-time passes a reference to this handler to the current simulation model, which then can pass it on to all other objects that need to call functions of the run-time. The simulator handler is implemented by the following object class.
|CSimHandler||The handler to the simulator run-time.|
The main function of the run-time is to accept the scheduling of simulation tasks from the simulation model or one of its sub-components, and execute them. The simulator tasks come in two flavors:
- Simulation events, and;
- Simulation calls.
Both the simulation events and simulation calls are objects that contain information about tasks to execute at any step during the simulation. In the following, we shall describe both, emphasizing the similarities and differences between them.
The simulation events are typically the most common used type of task objects to control the flow of the simulation. By far, the majority of tasks from a simulation consists of events.
All events have a temporal variable that indicates to the run-time when they should be executed on the virtual time scale, and some information about what the execution of the event should do. This information can be anything: data, code or both. The run-time executes the events by passing them to the simulation model, which in turn can use the custom information within the event to perform a specific action. To increase the performance of the simulation, the majority of events contain both the code and data to be executed. In this manner, the simulation model executes an event simply by calling its piece of code, rather than by trying to interpret the data from the event. The figure II.a illustrates the structure of a simulation event.
Figure II.a. The structure of a simulation event
The generic interface for a simulation event is implemented by the following object class.
|CSimEvent||The generic interface for a simulation event.|
A simulation call is a light-weight version of a simulation event. Unlike events, calls do not have a temporal relationship between them, and all current calls are executed at the end of the current event. Simulation calls are object calls, and their sole purpose is to execute tasks that belong to the current event but cannot be executed as part thereof. All scheduled calls are synchronous with the event after which they are executing sharing the same virtual time, and they are executed directly from the run-time.
The simulator run-time execution flow consists in executing scheduled events and calls. Simulation events are always executed first, each event followed by simulation calls, if available. If the code executed during an event schedules more than one call, all scheduled calls are executed after the event with the same virtual time. The figure II.b illustrates the execution flow of the simulator run-time.
Figure II.b. Run-time execution flow
The event list is an ordered list maintained by the simulator run-time that stores simulation events. The events are ordered according to their execution time in a B-tree structure that allows insertion, access and removal of events in a logarithmic amortized time, O(log n). Therefore, the simulator event list performance depends on the number of events with different execution times that is stored in the event list at any given time. Events that share the same execution time are stored according to a FIFO rule, and executed in the same order.
The simulator events list and calls list are implemented by the following object classes.
|CSimEventList||The simulator event list.|
|CSimCalls||The simulator calls list.|
The simulation model is the implementation of an interface that instructs the run-time how to execute the simulation. The SimStream simulator allows the implementation of any number of simulation models for any time of event-based time-discrete simulations. All simulation models must implement the CSimModel interface.
The simulation model provides the following set of parameters to the run-time:
- The maximum simulation time
- A list with initial events (each event is allowed to have an arbitrary virtual execution time)
Upon startup, the run-time provides the simulator handler to the model via an initialization function, allowing the model to obtain the virtual time and to schedule events and calls During simulation, the run-time executes the events by passing them to the model via an execution function. The model understands the structure of each event and based on the data and code the event carries, executes the appropriate task. Upon completion of the simulation, the run-time calls a finalization function enabling the simulation model to execute post-simulation tasks such as writing simulation results to files. For more information, see the run-time.
The figure II.c summarizes the interconnection between the simulator run-time and the simulation model.
Figure II.c. Information flow between the run-time and the simulation model. The arrows indicate the direction of the information flow and counter-indicate the direction of the function call.
The abstract interface for a simulation model is implemented by the following object class.
|CSimModel||The abstract interface for a simulation model.|
SimStream is a simulator implemented in C++. For this reason, all components are implemented as C++ classes, and all variables (with the exception of the fundamental data types such as int, char or double) or objects or pointers to objects. This approach decreases the gap between the real-life objects that must be simulated and their actual implementation.
To this end, all simulated real-life objects such as hosts, routers, packets, software (protocol layers, clients, servers, video players, etc.) are defined as C++ classes. Similar to the real-life objects they are trying to mimic, many of these classes require the ability to interact with each other on a reciprocal bases. An example of such interaction is represented by an upper protocol layer needing to interact with a lower protocol layer, while the lower protocol layer needs to interact to the same upper protocol layer. In addition, this level of interaction should be accomplished while the two classes are relatively separated from one another and they communicate only through a set of well defined interfaces.
However, the C++ programming language has not been designed with such a degree of inter-object communication in mind. One possible solution is to use inheritance where the base class defines the ground functionality while the derived classes implement the most complex features. A first drawback of this approach is that many different classes will be intricate linked to each other, defeating the initial purpose of separating the functionality in different objects in the first place. Then, the derived classes will be limited to the functions available in the base class, or the base class may become unnecessarily complex to accommodate the requirements from all derived classes.
The figure II.d is an example of the required level of inter-object communication. The figure illustrates the common protocol stack implemented in SimStream hosts and routers. Each set of common functions is implemented in a separate object class. This approach makes different components reusable (e.g. the blue components are used by both hosts and routers, while the orange components are only used by routers), while avoiding to add complexity to objects that do not require it (e.g. unlike in practice, IP unicast routing tables for incoming packets are not implemented by hosts, keeping the simulator optimized).
This degree of interdependence, which can be far more complex when adding new protocols, makes it very difficult to use C++ class inheritance. In addition, the inheritance must follow the dependency between classes resulting sometimes in a counter-intuitive implementation. For instance, the figure II.e illustrates a possible dependency diagram for the components listed in figure II.d, in which upper layer protocols such as IGMP and PIM-SM must be base classes, while IP forwarding must be derived classes.
Apart from class inheritance, there may be several other solutions to pass object parameters between classes. One is to use a global dispatcher with parameters as generic pointers (void*). The disadvantage of this approach is that class objects must register with the dispatcher, increasing the execution complexity, and the developer must keep track of the parameters data type, increasing the development effort.
To avoid these disadvantages, the SimStream simulator core provides a set of constructs that enables type-safe fast inter-object communication. These allow different classes to call each others' member functions directly, as in the following scenario.
The types of constructs for inter-object communication are:
In actuality, these constructs are C++ template classes. They use pointers to member functions to enable generic calls from one class to another, without any requirement on the class dependency.
A delegate is a template class that enables single function calls, that is a delegate wraps one object and one function. Any object that obtains an instance of the delegate, can use it to call the one function for the one object contained by the delegate.
A delegate has two components:
- A generic part (in actuality the base class of the delegate), that is independent of the type of the contained object.
- An explicit part (in actuality the derived class of the delegate), that is explicitly defined for a certain type of the contained object.
In addition, a delegate is explicitly defined for a certain function prototype (i.e. list of parameters and return type). An explicit delegate can be casted to a generic delegate of the same function prototype, but not the other way around.
Currently, the simulator core provides delegates for function prototypes with up to five parameters. The explicit version of these delegates are implemented by the following object classes.
|Delegate0||Explicit delegate with zero parameters.|
|Delegate1||Explicit delegate with one parameter.|
|Delegate2||Explicit delegate with two parameters.|
|Delegate3||Explicit delegate with three parameters.|
|Delegate4||Explicit delegate with four parameters.|
|Delegate5||Explicit delegate with five parameters.|
The generic version of these delegates are implemented by the following object classes.
|IDelegate0||Generic delegate with zero parameters.|
|IDelegate1||Generic delegate with one parameter.|
|IDelegate2||Generic delegate with two parameters.|
|IDelegate3||Generic delegate with three parameters.|
|IDelegate4||Generic delegate with four parameters.|
|IDelegate5||Generic delegate with five parameters.|
Last updated: December 29, 2010