Omnet
Omnet
Omnet
User Manual
by Andrs Varga
OMNeT++ Manual
Document History
Date
2005/10
2005/03
2004/12
2003/06
Author
AV
AV
AV
AV
2003/06
2003/04-06
AV
AV
2003/03
2003/02
2003/01
AV
AV
AV
Summer 2002
Ulrich
Kaage
AV
2002/03/18
Change
updated for the OMNeT++ 3.2 release
updated for the OMNeT++ 3.1 release
updated for the OMNeT++ 3.0 release
Mentioned Grace and ROOT in section "Visualizing...". Added
section "Using STL in message classes".
OMNeT++ 2.3 released
"Design of OMNeT++" chapter revised, extended, and renamed to "Customization and Embedding". Added "Interpreting Cmdenv output" section to the "Running the Simulation"
chapter. Added section about Akaroa in "Running the Simulation" chapter. Expanded section about writing shell scripts
to control the simulation. Added background info about RNGs
and warning about old RNG in "Class Library" chapter; revised/extended "Deriving new classes" section in same chapter. Bibliography converted to Bibtex, expanded and cleaned
up; citations added to text. "Parallel Simulation" chapter:
contents removed until new PDES implementation gets released. Revised and reorganized NED chapter. Section about
message sending/receiving and other simple module related
functions moved to chapter "Simple Modules"; cMessage treatment from "Simulation Library" merged with message subclassing chapter into new chapter "Messages". Deprecated
cPacket. Removed sections "Simulation techniques" and "Coding conventions", and their useful fragments were incorporated elsewhere. Added/created sections about message transmission modeling, and using global variables. Added sections explaining how to implement broadcasts and retransmissions. Revised section about dynamic module creation.
Deprecated putaside-queue, receiveNew(), receiveOn(). Added
section "Object ownership management"; removed section on
"Using shared objects".
OMNeT++ 2.3b2 released
OMNeT++ 2.3b1 released
Added chapter about message subclassing; revised chapter
about running the simulation and incorporated new Cmdenv
options; added new distributions and clarified many details in
NED expr. handling section
Converted from Word to LaTeX
Documented new ini file options about Envir plugins
iii
OMNeT++ Manual
2002/01/24
2001/10/23
AV
AV
iv
Contents
Contents
1 Introduction
1.3 Credits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2 Overview
2.1.5 Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
. . . . . . . . . . . . . . . . . . . . . . . . . . 19
37
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.4.2 activity() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4.4.3 initialize() and finish() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4.4.4 handleParameterChange()N ew! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.4.5 Reusing module code via subclassing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
4.5 Finite State Machines in OMNeT++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
4.6 Sending and receiving messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
4.6.1 Sending messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.6.2 Broadcasts and retransmissions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.6.3 Delayed sending . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.6.4 Direct message sending . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.6.5 Receiving messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
4.6.6 The wait() function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
4.6.7 Modeling events using self-messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
4.6.8 Stopping the simulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
4.7 Accessing module parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.7.1 Emulating parameter arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
4.8 Accessing gates and connections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
4.8.1 Gate objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
4.8.2 Connection parameters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
4.8.3 Transmission state . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
4.8.4 Connectivity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
4.9 Walking the module hierarchy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
4.10 Direct method calls between modules
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
81
103
143
149
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
177
187
193
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
. . . . . . . . . . . . . . . . . . . . . . . . . . . 198
201
211
219
xii
223
Index
226
xiii
xiv
Chapter 1
Introduction
1.1
What is OMNeT++?
OMNeT++ is an object-oriented modular discrete event network simulator. The simulator can be used for:
traffic modeling of telecommunication networks
protocol modeling
modeling queueing networks
modeling multiprocessors and other distributed hardware systems
validating hardware architectures
evaluating performance aspects of complex software systems
. . . modeling any other system where the discrete event approach is suitable.
An OMNeT++ model consists of hierarchically nested modules. The depth of module nesting is not limited,
which allows the user to reflect the logical structure of the actual system in the model structure. Modules
communicate through message passing. Messages can contain arbitrarily complex data structures. Modules can send messages either directly to their destination or along a predefined path, through gates and
connections.
Modules can have their own parameters. Parameters can be used to customize module behaviour and to
parameterize the models topology.
Modules at the lowest level of the module hierarchy encapsulate behaviour. These modules are termed
simple modules, and they are programmed in C++ using the simulation library.
OMNeT++ simulations can feature varying user interfaces for different purposes: debugging, demonstration and batch execution. Advanced user interfaces make the inside of the model visible to the user, allow
control over simulation execution and to intervene by changing variables/objects inside the model. This is
very useful in the development/debugging phase of the simulation project. User interfaces also facilitate
demonstration of how a model works.
The simulator as well as user interfaces and tools are portable: they are known to work on Windows and
on several Unix flavours, using various C++ compilers.
OMNeT++ also supports parallel distributed simulation. OMNeT++ can use several mechanisms for communication between partitions of a parallel distributed simulation, for example MPI or named pipes. The
parallel simulation algorithm can easily be extended or new ones plugged in. Models do not need any
special instrumentation to be run in parallel it is just a matter of configuration. OMNeT++ can even
1
1.2
1.3
Credits
Chapter 2
Overview
2.1
Modeling concepts
OMNeT++ provides efficient tools for the user to describe the structure of the actual system. Some of the
main features are:
hierarchically nested modules
modules are instances of module types
modules communicate with messages through channels
flexible module parameters
topology description language
2.1.1
Hierarchical modules
An OMNeT++ model consists of hierarchically nested modules, which communicate by passing messages
to each another. OMNeT++ models are often referred to as networks. The top level module is the system
module. The system module contains submodules, which can also contain submodules themselves (Fig.
2.1). The depth of module nesting is not limited; this allows the user to reflect the logical structure of the
actual system in the model structure.
Model structure is described in OMNeT++s NED language.
2.1.2
Module types
Both simple and compound modules are instances of module types. While describing the model, the user
defines module types; instances of these module types serve as components for more complex module
types. Finally, the user creates the system module as an instance of a previously defined module type; all
modules of the network are instantiated as submodules and sub-submodules of the system module.
When a module type is used as a building block, there is no distinction whether it is a simple or a compound module. This allows the user to split a simple module into several simple modules embedded into
a compound module, or vica versa, aggregate the functionality of a compound module into a single simple
module, without affecting existing users of the module type.
Module types can be stored in files separately from the place of their actual usage. This means that the
user can group existing module types and create component libraries. This feature will be discussed later,
in Chapter 8.
2.1.3
Modules communicate by exchanging messages. In an actual simulation, messages can represent frames
or packets in a computer network, jobs or customers in a queuing network or other types of mobile entities.
Messages can contain arbitrarily complex data structures. Simple modules can send messages either
directly to their destination or along a predefined path, through gates and connections.
The local simulation time of a module advances when the module receives a message. The message can
arrive from another module or from the same module (self-messages are used to implement timers).
Gates are the input and output interfaces of modules; messages are sent out through output gates and
arrive through input gates.
Each connection (also called link) is created within a single level of the module hierarchy: within a compound module, one can connect the corresponding gates of two submodules, or a gate of one submodule
and a gate of the compound module (Fig. 2.2).
2.1.4
Connections can be assigned three parameters, which facilitate the modeling of communication networks,
but can be useful in other models too: propagation delay, bit error rate and data rate, all three being
optional. One can specify link parameters individually for each connection, or define link types and use
them throughout the whole model.
Propagation delay is the amount of time the arrival of the message is delayed by when it travels through
the channel.
6
2.1.5
Parameters
Modules can have parameters. Parameters can be assigned either in the NED files or the configuration
file omnetpp.ini.
Parameters may be used to customize simple module behaviour, and for parameterizing the model topology.
Parameters can take string, numeric or boolean values, or can contain XML data trees. Numeric values
include expressions using other parameters and calling C functions, random variables from different
distributions, and values input interactively by the user.
Numeric-valued parameters can be used to construct topologies in a flexible way. Within a compound
module, parameters can define the number of submodules, number of gates, and the way the internal
connections are made.
2.1.6
The user defines the structure of the model in NED language descriptions (Network Description).The
NED language will be discussed in detail in Chapter 3.
2.2
The simple modules of a model contain algorithms as C++ functions. The full flexibility and power of the
programming language can be used, supported by the OMNeT++ simulation class library. The simulation
programmer can choose between event-driven and process-style description, and can freely use objectoriented concepts (inheritance, polymorphism etc) and design patterns to extend the functionality of the
simulator.
Simulation objects (messages, modules, queues etc.) are represented by C++ classes. They have been
designed to work together efficiently, creating a powerful simulation programming framework. The following classes are part of the simulation class library:
modules, gates, connections etc.
parameters
messages
container classes (e.g. queue, array)
data collection classes
statistic and distribution estimation classes (histograms, P 2 algorithm for calculating quantiles etc.)
transient detection and result accuracy detection classes
7
2.3
2.3.1
Using OMNeT++
Building and running simulations
This section provides insight into working with OMNeT++ in practice: Issues such as model files, compiling and running simulations are discussed.
An OMNeT++ model consists of the following parts:
NED language topology description(s) (.ned files) which describe the module structure with parameters, gates etc. NED files can be written using any text editor or the GNED graphical editor.
Message definitions (.msg files). You can define various message types and add data fields to them.
OMNeT++ will translate message definitions into full-fledged C++ classes.
Simple modules sources. They are C++ files, with .h/.cc suffix.
The simulation system provides the following components:
Simulation kernel. This contains the code that manages the simulation and the simulation class
library. It is written in C++, compiled and put together to form a library (a file with .a or .lib
extension)
User interfaces. OMNeT++ user interfaces are used in simulation execution, to facilitate debugging,
demonstration, or batch execution of simulations. There are several user interfaces, written in C++,
compiled and put together into libraries (.a or .lib files).
Simulation programs are built from the above components. First, .msg files are translated into C++ code
using the opp_msgc. program. Then all C++ sources are compiled, and linked with the simulation kernel
and a user interface library to form a simulation executable. NED files can either be also translated
into C++ (using nedtool) and linked in, or loaded dynamically in their original text forms when the
simulation program starts.
Running the simulation and analyzing the results
The simulation executable is a standalone program, thus it can be run on other machines without OMNeT++ or the model files being present. When the program is started, it reads a configuration file (usually
called omnetpp.ini). This file contains settings that control how the simulation is executed, values for
model parameters, etc. The configuration file can also prescribe several simulation runs; in the simplest
case, they will be executed by the simulation program one after another.
The output of the simulation is written into data files: output vector files, output scalar files , and possibly
the users own output files. OMNeT++ provides a GUI tool named Plove to view and plot the contents of
output vector files. It is not expected that someone will process the result files using OMNeT++ alone:
output files are text files in a format which can be read into math packages like Matlab or Octave, or
imported into spreadsheets like OpenOffice Calc, Gnumeric or MS Excel (some preprocessing using sed,
awk or perl might be required, this will be discussed later). All these external programs provide rich
functionality for statistical analysis and visualization, and it is outside the scope of OMNeT++ to duplicate
their efforts. This manual briefly describes some data plotting programs and how to use them with OMNeT++.
8
2.3.2
If you installed the source distribution, the omnetpp directory on your system should contain the following
subdirectories. (If you installed a precompiled distribution, some of the directories may be missing, or
there might be additional directories, e.g. containing software bundled with OMNeT++.)
The simulation system itself:
omnetpp/
OMNeT++ root directory
bin/
OMNeT++ executables (GNED, nedtool, etc.)
include/
header files for simulation models
lib/
library files
bitmaps/
icons that can be used in network graphics
doc/
manual (PDF), readme, license, etc.
manual/
manual in HTML
tictoc-tutorial/ introduction into using OMNeT++
api/
API reference in HTML
nedxml-api/ API reference for the NEDXML library
src/
sources of the documentation
src/
OMNeT++ sources
nedc/
nedtool, message compiler
sim/
simulation kernel
parsim/
files for distributed execution
netbuilder/files for dynamically reading NED files
envir/
common code for user interfaces
9
You may also find additional directories like msvc/, which contain integration components for Microsoft
Visual C++, etc.
10
Chapter 3
NED overview
The topology of a model is specified using the NED language. The NED language facilitates the modular
description of a network. This means that a network description may consist of a number of component
descriptions (channels, simple/compound module types). The channels, simple modules and compound
modules of one network description can be reused in another network description.
Files containing network descriptions generally have a .ned suffix. NED files can be loaded dynamically
into simulation programs, or translated into C++ by the NED compiler and linked into the simulation
executable.
The EBNF description of the language can be found in Appendix A.
3.1.1
A NED description can contain the following components, in arbitrary number or order:
import directives
channel definitions
simple and compound module definitions
network definitions
3.1.2
Reserved words
The writer of the network description has to take care that no reserved words are used for names. The
reserved words of the NED language are:
import channel endchannel simple endsimple module endmodule error delay datarate
const parameters gates submodules connections gatesizes if for do endfor network endnetwork nocheck ref ancestor true false like input numeric string bool char xml xmldoc
3.1.3
Identifiers
Identifiers are the names of modules, channels, networks, submodules, parameters, gates, channel attributes and functions.
11
3.1.4
Case sensitivity
The network description and all identifiers in it are case sensitive. For example, TCP and Tcp are two
different names.
3.1.5
Comments
Comments can be placed anywhere in the NED file, with the usual C++ syntax: comments begin with a
double slash //, and last until the end of the line. Comments are ignored by the NED compiler.
NED comments can be used for documentation generation, much like JavaDoc or Doxygen. This feature
is described in Chapter 11.
3.2
The import directive is used to import declarations from another network description file. After importing a network description, one can use the components (channels, simple/compound module types) defined
in it.
When a file is imported, only the declaration information is used. Also, importing a .ned file does not
cause that file to be compiled with the NED compiler when the parent file is NED compiled, i.e., one must
compile and link all network description files not only the top-level ones.
You can specify the name of the files with or without the .ned extension. You can also include a path in
the filenames, or better, use the NED compilers -I <path> command-line option to name the directories
where the imported files reside.
Example:
import "ethernet";
3.3
// imports ethernet.ned
Channel definitions
A channel definition specifies a connection type of given characteristics. The channel name can be used
later in the NED description to create connections with these parameters.
The syntax:
channel ChannelName
//...
endchannel
Three attributes can be assigned values in the body of the channel declaration, all of them are optional:
delay, error and datarate. delay is the propagation delay in (simulated) seconds; error is the
12
3.4
Simple modules are the basic building blocks for other (compound) modules. Simple module types are
identified by names. By convention, module names begin with upper-case letters.
A simple module is defined by declaring its parameters and gates.
Simple modules are declared with the following syntax:
simple SimpleModuleName
parameters:
//...
gates:
//...
endsimple
3.4.1
Parameters are variables that belong to a module. Simple module parameters can be queried and used
by simple module algorithms. For example, a module called TrafficGen may have a parameter called
numOfMessages that determines how many messages it should generate.
Parameters are identified by names. By convention, parameter names begin with lower-case letters.
Parameters are declared by listing their names in the parameters: section of a module description.
The parameter type can optionally be specified as numeric, numeric const (or simply const), bool,
string, or xml. If the parameter type is omitted, numeric is assumed.
Example:
simple TrafficGen
parameters:
interarrivalTime,
numOfMessages : const,
address : string;
gates: //...
endsimple
Parameters are assigned from NED (when the module is used as a building block of a larger compound
module) or from the config file omnetpp.ini. omnetpp.ini is described in Chapter 8.
13
3.4.2
Gates are the connection points of modules. The starting and ending points of the connections between
modules are gates. OMNeT++ supports simplex (one-directional) connections, so there are input and
output gates. Messages are sent through output gates and received through input gates.
Gates are identified by their names. By convention, gate names begin with lower-case letters.
Gate vectors are supported: a gate vector contains a number of single gates.
Gates are declared by listing their names in the gates: section of a module description. An empty
bracket pair [] denotes a gate vector. Elements of the vector are numbered from zero.
Examples:
simple NetworkInterface
parameters: //...
gates:
in: fromPort, fromHigherLayer;
out: toPort, toHigherLayer;
endsimple
simple RoutingUnit
parameters: //...
gates:
14
3.5
Compound modules are modules composed of one or more submodules. Any module type (simple or compound module) can be used as a submodule. Like simple modules, compound modules can also have gates
and parameters, and they can be used wherever simple modules can be used.
It is useful to think about compound modules as cardboard boxes that help you organize your simulation
model and bring structure into it. No active behaviour is associated with compound modules they are
simply for grouping modules into larger components that can can be used either as a model (see section
3.6) or as a building block for other compound modules.
By convention, module type names (and so compound module type names, too) begin with upper-case
letters.
Submodules may use parameters of the compound module. They may be connected with each other and/or
with the compound module itself.
A compound module definition looks similar to a simple module definition: it has gates and parameters
sections. There are two additional sections, submodules and connections.
The syntax for compound modules is the following:
module CompoundModule
parameters:
//...
gates:
//...
submodules:
//...
connections:
//...
endmodule
All sections (parameters, gates, submodules, connections) are optional.
3.5.1
Parameters and gates for compound modules are declared and work in the same way as with simple
modules, described in sections 3.4.1 and 3.4.2.
Typically, compound module parameters are passed to submodules and used for initializing their parameters.
Parameters can also be used in defining the internal structure of the compound module: the number of
submodules and sizes of gate vectors can be defined with the help of parameters, and parameters can also
be used in defining the connections inside the compound module. As a practical example, you can create
a Router compound module with a variable number of ports, specified in a numOfPorts parameter.
Parameters affecting the internal structure should always be declared const, so that accessing them
always yields the same value. Otherwise, if the parameter was assigned a random value, one could get
15
3.5.2
Submodules
Submodules are defined in the submodules: section of a compound module declaration. Submodules are
identified by names. By convention, submodule names begin with lower-case letters.
Submodules are instances of a module type, either simple or compound there is no distinction. The
module type must be known to the NED compiler, that is, it must have appeared earlier in the same NED
file or have been imported from another NED file.
It is possible to define vectors of submodules, and the size of the vector may come from a parameter value.
When defining submodules, you can assign values to their parameters, and if the corresponding module
type has gate vectors, you have to specify their sizes.
Example:
module CompoundModule
//...
submodules:
submodule1: ModuleType1
parameters:
//...
gatesizes:
//...
submodule2: ModuleType2
parameters:
//...
gatesizes:
//...
endmodule
Module vectors
It is possible to create an array of submodules (a module vector). This is done with an expression between
brackets right behind the module type name. The expression can refer to module parameters. A zero
value as module count is also allowed.
Example:
module CompoundModule
parameters:
16
3.5.3
Sometimes it is convenient to make the name of a submodule type a parameter, so that one can easily
plug in any module there.
For example, assume the purpose of your simulation study is to compare different routing algorithms.
Suppose you programmed the needed routing algorithms as simple modules: DistVecRoutingNode,
AntNetRouting1Node, AntNetRouting2Node, etc. You have also created the network topology as a
compound module called RoutingTestNetwork, which will serve as a testbed for your routing algorithms. Currently, RoutingTestNetwork has DistVecRoutingNode hardcoded (all submodules are of
this type), but you want to be able to switch to other routing algorithms easily.
NED gives you the possibility to add a string-valued parameter, say routingNodeType to the RoutingTestNetwork compound module. Then you can tell NED that types of the submodules inside RoutingTestNetwork are not of any fixed module type, but contained in the routingNodeType parameter.
That is all now you are free to assign any of the "DistVecRoutingNode", "AntNetRouting1Node"
or "AntNetRouting2Node" string constants to this parameter (you can do that in NED, in the config
file (omnetpp.ini), or even enter it interactively), and your network will use the routing algorithm you
chose.
If you specify a wrong value, say "FooBarRoutingNode" when you have no FooBarRoutingNode module implemented, youll get a runtime error at the beginning of the simulation: module type definition not
found.
Inside the RoutingTestNetwork module you assign parameter values and connect the gates of the routing modules. To provide some degree of type safety, NED wants to make sure you didnt misspell parameter or gate names and you used them correctly. To be able to do such checks, NED requires some help from
you: you have to name an existing module type (say RoutingNode) and promise NED that all modules
youre going you specify in the routingNodeType parameter will have (at least) the same parameters
and gates as the RoutingNode module. 1
All the above is achieved via the like keyword. The syntax is the following:
module RoutingTestNetwork
parameters:
routingNodeType: string; // should hold the name
// of an existing module type
gates: //...
submodules:
node1: routingNodeType like RoutingNode;
node2: routingNodeType like RoutingNode;
//...
connections nocheck:
node1.out0 --> node2.in0;
1 If you like, the above solution somewhat similar to polymorphism in object-oriented languages RoutingNode is like a base
class, DistVecRoutingNode and AntNetRouting1Node are like derived classes, and the routingNodeType parameter is like
a pointer to a base class which may be downcast to specific types.
17
3.5.4
If the module type used as submodule has parameters, you can assign values to them in the parameters
section of the submodule declaration. As a value you can use a constant (such as 42 or "www.foo.org"),
various parameters (most commonly, parameters of the compound module), or write an arbitrary expression containing the above.
It is not mandatory to mention and assign all parameters. Unassigned parameters can get their values at
runtime: either from the configuration file (omnetpp.ini), or if the value isnt there either, the simulator
will prompt you to enter it interactively. Indeed, for flexibility reasons it is often very useful not to
hardcode parameter values in the NED file, but to leave them to omnetpp.ini where they can be
changed more easily.
Example:
module CompoundModule
parameters:
param1: numeric,
param2: numeric,
useParam1: bool;
submodules:
submodule1: Node
parameters:
p1 = 10,
p2 = param1+param2,
p3 = useParam1==true ? param1 : param2;
//...
endmodule
The expression syntax is very similar to C. Expressions may contain constants (literals) and parameters of
the compound module being defined. Parameters can be passed by value or by reference. The latter means
that the expression is evaluated at runtime each time its value is accessed (e.g. from simple module code),
opening up interesting possibilities for the modeler. You can also refer to parameters of the already defined
submodules, with the syntax submodule.parametername (or submodule[index].parametername).
Expressions are described in detail in section 3.7.
The input keyword
When a parameter does not receive a value inside NED files or in the configuration file (omnetpp.ini),
the user will be prompted to enter its value at the beginning of the simulation. If you plan to make use of
interactive prompting, you can specify a prompt text and a default value.
The syntax is the following:
18
3.5.5
The sizes of gate vectors are defined with the gatesizes keyword. Gate vector sizes can be given as
constants, parameters or expressions.
An example:
simple Node
gates:
in: inputs[];
out: outputs[];
endsimple
module CompoundModule
parameters:
numPorts: const;
submodules:
node1: Node
gatesizes:
inputs[2], outputs[2];
node2: Node
gatesizes:
inputs[numPorts], outputs[numPorts];
//...
endmodule
gatesizes is not mandatory. If you omit gatesizes for a gate vector, it will be created with zero size.
One reason for omitting gatesizes is that youll want to use the gate++ (extend gate vector with a new
gate) notation later in the connections section.
3.5.6
Multiple parameters and gatesizes sections can exist in a submodule definition and each of them can
be tagged with conditions.
Example:
module Chain
parameters: count: const;
submodules:
node : Node [count]
parameters:
position = "middle";
parameters if index==0:
position = "beginning";
19
3.5.7
Connections
The compound module definition specifies how the gates of the compound module and its immediate submodules are connected.
You can connect two submodules or a submodule with its enclosing compound module. (For completeness,
you can also connect two gates of the compound module on the inside, but this is rarely needed). This
means that NED does not permit connections that span multiple levels of hierarchy this restriction
enforces compound modules to be self-contained, and thus promotes reusability. Gate directions must
also be observed, that is, you cannot connect two output gates or two input gates.
Only one-to-one connections are supported, so a particular gate may only be used occur in one connection.
One-to-many and many-to-one connections can be achieved using simple modules that duplicate messages
or merge message flows. The rationale is that wherever such fan-in or fan-out occurs in a model, it is
usually associated with some processing anyway that makes it necessary to use simple modules.
Connections are specified in the connections: section of a compound module definition. It lists the
connections, separated by semicolons.
Example:
module CompoundModule
parameters: //...
gates: //...
submodules: //...
connections:
node1.output --> node2.input;
node1.input <-- node2.output;
//...
endmodule
The source gate can be an output gate of a submodule or an input gate of the compound module, and the
destination gate can be an input gate of a submodule or an output gate of the compound module. The
arrow can point either left-to-right or right-to-left.
The gate++ notation allows you to extend a gate vector with new gates, without having to declare the
vector size in advance with gatesizes. This feature is very convenient for connecting nodes of a network:
simple Node
gates:
in: in[];
out: out[];
20
3.6
Network definitions
Module module declarations (compound and simple module declarations) just define module types. To
actually get a simulation model that can be run, you need to write a network definition.
A network definition declares a simulation model as an instance of a previously defined module type.
Youll typically want to use a compound module type here, although it is also possible to program a model
as a self-contained simple module and instantiate it as a network.
There can be several network definitions in your NED file or NED files. The simulation program that
uses those NED files will be able to run any of them; you typically select the desired one in the config file
(omnetpp.ini).
The syntax of a network definition is similar to that of a submodule declaration:
network wirelessLAN: WirelessLAN
parameters:
numUsers=10,
httpTraffic=true,
ftpTraffic=true,
distanceFromHub=truncnormal(100,60);
endnetwork
Here, WirelessLAN is the name of previously defined compound module type, which presumably contains
further compound modules of types WirelessHost, WirelessHub, etc.
Naturally, only module types without gates can be used in network definitions.
Just as in submodules, you do not need to assign values to all parameters. Unassigned parameters can
get their values from the config file (omnetpp.ini) or will be interactively prompted for.
3.7
Expressions
In the NED language there are a number of places where expressions are expected.
Expressions have a C-style syntax. They are built with the usual math operators; they can use parameters
taken by value or by reference; call C functions; contain random and input values etc.
When an expression is used for a parameter value, it is evaluated each time the parameter value is
accessed (unless the parameter is declared const, see 3.4.1). This means that a simple module querying
a non-const parameter during simulation may get different values every time (e.g. if the value involves
a random variable, or it contains other parameters taken by reference). Other expressions (including
const parameter values) are evaluated only once.
XML-type parameters can be used to conveniently access external XML files or parts of them. XML-type
parameters can be assigned with the xmldoc() operator, also described in this section.
23
3.7.1
Constants
3.7.2
Meaning
nanoseconds
microseconds
milliseconds
seconds
minutes (60s)
hours (3600s)
days (86400s)
Referencing parameters
Expressions can use the parameters of the enclosing compound module (the one being defined) and of submodules defined earlier in NED file. The syntax for the latter is submod.param or submod[index].param.
There are two keywords that you can use with a parameter name: ancestor and ref. The first one
(ancestor param) means that if compound module doesnt have such a parameter, further modules up
in the module hierarchy will be searched for the parameter. ancestor is considered bad practice because
it violates the encapsulation principle and can only be checked at runtime. It is provided for the rare case
when it is really needed.
ref param takes the parameter by reference, meaning that runtime changes to the parameter will propagate to all modules which take that parameter by reference. Like ancestor, ref should also be used
very sparingly. One possible use is tuning a model at runtime, in search for an optimum: one defines a parameter at the highest level of the model, and lets other modules take it by reference then if you change
the parameter value at runtime (manually or from a simple module), it will affect the whole model. In
another setup, reference parameters may be used to propagate status values to neighbouring modules.
3.7.3
Operators
The operators supported in NED are similar to C/C++ operators, with the following differences:
24
3.7.4
Meaning
unary minus, negation, bitwise complement
power-of
multiply, divide, modulus
add, subtract
bitwise shift
bitwise and, or, xor
equal
not equal
greater, greater or equal
less, less or equal
logical operators and, or, xor
the C/C++ inline if
A useful operator is sizeof(), which gives the size of a vector gate. The index operator gives the index
of the current submodule in its module vector.
The following example describes a router with several ports and one routing unit. We assume that gate
vectors in[] and out[] have the same size.
module Router
gates:
in: in[];
out: out[];
submodules:
port: PPPInterface[sizeof(in)]; // one PPP for each input gate
parameters: interfaceId = 1+index; // 1,2,3...
routing: RoutingUnit;
gatesizes:
in[sizeof(in)]; // one gate pair for each port
out[sizeof(in)];
connections:
for i = 0..sizeof(in)-1 do
in[i] --> port[i].in;
out[i] <-- port[i].out;
2 In case you are worried about long values being not accurately represented in doubles, this is not the case. IEEE-754 doubles
have 52 bit mantissas, and integer numbers in that range are represented without rounding errors.
25
3.7.5
The xmldoc() operator can be used to assign XML-type parameters, that is, point them to XML files or
to specific elements inside XML files.
xmldoc() has two flavours: one accepts a file name, the second accepts a file name plus an XPath-like
expression which selects an element inside the XML file. Examples:
xmlparam = xmldoc("someconfig.xml");
xmlparam = xmldoc("someconfig.xml", "/config/profile[@id=2]");
OMNeT++ supports a subset of the XPath 1.0 specification; details are documented below.
From the C++ code youd access the XML element like this:
cXMLElement *rootelement = par("xmlparam").xmlValue();
The cXMLElement class provides a DOM-like access to the XML document. You can then navigate the
document tree, extract the information you need, and store it in variables or your internal data structure.
cXMLElement is documented in Chapter 6.
You can also read XML parameters from omnetpp.ini:
[Parameters]
**.interface[*].config = xmldoc("conf.xml")
or
[Parameters]
**.interface[*].config = xmldoc("all-in-one.xml", "/config/interfaces/interface[2]")
3.7.6
xmldoc() with two arguments accepts a path expression to select an element within the document. The
expression syntax is similar to XPath.
If the expression matches several elements, the first element (in preorder depth-first traversal) will be
selected. (This is unlike XPath, which selects all matching nodes.)
The expression syntax is the following:
An expression consists of path components (or "steps") separated by "/" or "//".
A path component can be an element tag name, "*", "." or "..".
"/" means child element (just as e.g. in /usr/bin/gcc); "//" means an element any levels under
the current element.
".", ".." and "*" mean current element, parent element, and an element with any tag name, respectively.
26
Element tag names and "*" can have an optional predicate in the form "[position]" or "[@attribute=val
Positions start from zero.
Predicate of the form "[@attribute=$param]" are also accepted, where $param can be one of:
$MODULE_FULLPATH, $MODULE_FULLNAME, $MODULE_NAME, $MODULE_INDEX, $MODULE_ID, $PARENTMODULE
$PARENTMODULE_FULLNAME, $PARENTMODULE_NAME, $PARENTMODULE_INDEX, $PARENTMODULE_ID,
$GRANDPARENTMODULE_FULLPATH, $GRANDPARENTMODULE_FULLNAME, $GRANDPARENTMODULE_NAME,
$GRANDPARENTMODULE_INDEX, $GRANDPARENTMODULE_ID.N ew!
Examples:
/foo the root element which must be called <foo>
/foo/bar first <bar> child of the <foo> root element
//bar first <bar> anywhere (depth-first search!)
/*/bar first <bar> child of the root element which may have any tag name
/*/*/bar first <bar> child two levels below the root element
/*/foo[0] first <foo> child of the root element
/*/foo[1] second <foo> child of the root element
/*/foo[@color=green] first <foo> child which has attribute "color" with value "green"
//bar[1] a <bar> element anywhere which is the second <bar> among its siblings
//*[@color=yellow] any element anywhere which has attribute "color" with value "yellow"
//*[@color=yellow]/foo/bar first <bar> child of first <foo> child of a "yellow-colored"
element anywhere
Path support allows you put all your XML configuration into a single XML document, when you would
otherwise end up with lots of small XML files. For example, consider the following sample.xml:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<traffic-profile id="low">
...
</traffic-profile>
<traffic-profile id="medium">
...
</traffic-profile>
<traffic-profile id="high">
...
</traffic-profile>
</root>
3.7.7
Functions
3.7.8
Random values
Expressions may contain random variates from different distributions. Such parameters, unless declared
as const, return different values each time they are evaluated.
If the parameter is declared as const, it is only evaluated once at the beginning of the simulation, and
subsequent queries on the parameter will always return the same value.
Random variate functions use one of the random number generators (RNGs) provided by OMNeT++. By
default this is generator 0, but you can specify which one is to be used.
OMNeT++ has the following predefined distributions:
Function
Description
Continuous distributions
uniform(a, b, rng=0)
uniform distribution in the range [a,b)
exponential(mean, rng=0)
exponential distribution with the given mean
normal(mean, stddev, rng=0)
normal distribution with the given mean and
standard deviation
truncnormal(mean, stddev,
normal distribution truncated to nonnegative
rng=0)
values
gamma_d(alpha, beta, rng=0)
gamma distribution with parameters alpha>0,
beta>0
beta(alpha1, alpha2, rng=0)
beta distribution with parameters alpha1>0,
alpha2>0
erlang_k(k, mean, rng=0)
Erlang distribution with k>0 phases and the
given mean
chi_square(k, rng=0)
chi-square distribution with k>0 degrees of
freedom
student_t(i, rng=0)
student-t distribution with i>0 degrees of freedom
cauchy(a, b, rng=0)
Cauchy distribution with parameters a,b
where b>0
triang(a, b, c, rng=0)
triangular distribution with parameters
a<=b<=c, a!=c
lognormal(m, s, rng=0)
lognormal distribution with mean m and variance s>0
weibull(a, b, rng=0)
Weibull distribution with parameters a>0, b>0
pareto_shifted(a, b, c, rng=0)
generalized Pareto distribution with parameters a, b and shift c
Discrete distributions
intuniform(a, b, rng=0)
uniform integer from a..b
28
bernoulli(p, rng=0)
binomial(n, p, rng=0)
geometric(p, rng=0)
negbinomial(n, p, rng=0)
poisson(lambda, rng=0)
If you do not specify the optional rng argument, the functions will use random number generator 0.
Examples:
intuniform(0,10)/10
exponential(5)
2+truncnormal(5,3)
The above distributions are implemented with C functions, and you can easily add new ones (see section
3.7.9). Your distributions will be treated in the same way as the built-in ones.
3.7.9
To use user-defined functions, one has to code the function in C++. The C++ function must take 0, 1, 2,
3, or 4 arguments of type double and return a double. The function must be registered in one of the C++
files with the Define_Function() macro.
An example function (the following code must appear in one of the C++ sources):
#include <omnetpp.h>
double average(double a, double b)
{
return (a+b)/2;
}
Define_Function(average, 2);
The number 2 means that the average() function has 2 arguments. After this, the average() function
can be used in NED files:
module Compound
parameter: a,b;
submodules:
proc: Processor
parameters: av = average(a,b);
endmodule
If your function takes parameters that are int or long or some other type which is not double, you can
create wrapper function that takes all doubles and does the conversion. In this case you have to register
the wrapper function with the Define_Function2() macro which allows a function to be registered with
a name different from the name of the function that implements it. You can do the same if the return
value differs from double.
29
3.8
With the help of conditional parameter and gatesize blocks and conditional connections, one can create
complex topologies.
3.8.1
Examples
Example 1: Router
The following example contains a router module with the number of ports taken as parameter. The
compound module is built using three module types: Application, RoutingModule, DataLink. We assume
that their definition is in a separate NED file which we will import.
import "modules";
module Router
parameters:
rteProcessingDelay, rteBuffersize,
numOfPorts: const;
gates:
in: inputPorts[];
out: outputPorts[];
submodules:
localUser: Application;
routing: RoutingUnit
parameters:
processingDelay = rteProcessingDelay,
buffersize = rteBuffersize;
gatesizes:
input[numOfPorts+1],
output[numOfPorts+1];
portIf: PPPNetworkInterface[numOfPorts]
parameters:
retryCount = 5,
windowSize = 2;
connections:
for i=0..numOfPorts-1 do
routing.output[i] --> portIf[i].fromHigherLayer;
30
3.8.2
Several approaches can be used when you want to create complex topologies which have a regular structure; three of them are described below.
Subgraph of a Full Graph
This pattern takes a subset of the connections of a full graph. A condition is used to carve out the
necessary interconnection from the full graph:
for i=0..N-1, j=0..N-1 do
node[i].out[...] --> node[j].in[...] if condition(i,j);
endfor;
32
3.8.3
Topology templates
Overview
Topology templates are nothing more than compound modules where one or more submodule types are
left as parameters (using the like phrase of the NED language). You can write such modules which
implement mesh, hypercube, butterfly, perfect shuffle or other topologies, and you can use them wherever
needed in you simulations. With topology templates, you can reuse interconnection structure.
An example: hypercube
The concept is demonstrated on a network with hypercube interconnection. When building an N-dimension
hypercube, we can exploit the fact that each node is connected to N others which differ from it only in one
bit of the binary representations of the node indices (see Fig. 3.2).
33
The hypercube topology template is the following (it can be placed into a separate file, e.g hypercube.ned):
simple Node
gates:
out: out[];
in: in[];
endsimple
module Hypercube
parameters:
dim, nodetype;
submodules:
node: nodetype[2^dim] like Node
gatesizes:
out[dim], in[dim];
connections:
for i=0..2^dim-1, j=0..dim-1 do
node[i].out[j] --> node[i # 2^j].in[j]; // # is bitwise XOR
endfor;
endmodule
When you create an actual hypercube, you substitute the name of an existing module type (e.g. "Hypercube_PE") for the nodetype parameter. The module type implements the algorithm the user wants
to simulate and it must have the same gates that the Node type has. The topology template code can be
used through importing the file:
import "hypercube.ned";
simple Hypercube_PE
gates: out: out[]; in: in[];
endsimple
network hypercube: Hypercube
parameters:
dim = 4,
nodetype = "Hypercube_PE";
endnetwork
If you put the nodetype parameter to the ini file, you can use the same simulation model to test e.g. several routing algorithms in a hypercube, each algorithm implemented with a different simple module type
you just have to supply different values to nodetype, such as "WormholeRoutingNode", "DeflectionRoutingNode", etc.
34
3.9
Large networks
There are situations when using hand-written NED files to describe network topology is inconvenient,
for example when the topology information comes from an external source like a network management
program.
In such case, you have two possibilities:
1. generating NED files from data files
2. building the network from C++ code
The two solutions have different advantages and disadvantages. The first is more useful in the model
development phase, while the second one is better for writing larger scale, more productized simulation
programs. In the next sections we examine both methods.
3.9.1
Text processing programs like awk or perl are excellent tools to read in textual data files and generate
NED files from them. Perl also has extensions to access SQL databases, so it can also be used if the
network topology is stored in a database.
The advantage is that the necessary awk or perl program can be written in a relatively short time,
and it is inexpensive to maintain afterwards: if the structure of the data files change, the NED-creating
program can be easily modified. The resulting NED files can either be translated by nedtool into C++
and compiled in, or loaded dynamically.
3.9.2
Another alternative is to write C++ code which becomes part of the simulation executable. The code would
read the topology data from data files or a database, and build the network directly, using dynamic module
creation (to be described later, in section 4.11). The code which you need to write would be similar to the
*_n.cc files output by nedtool.
Since writing such code is more complex than letting perl generate NED files, this method is recommended
when the simulation program has to be somewhat more productized, for example when OMNeT++ and
the simulation model is embedded into a larger program, e.g. a network design tool.
3.10
To increase interoperability, NED files (and also message definition files) have an XML representation.
Any NED file can be converted to XML, and any XML file which corresponds to the NED DTD can be
converted to NED. 3
XML is well suited for machine processing. For example, stylesheet transformations (XSLT) can be used
to extract information from NED files, or the other way round, create NED files from external info present
in XML form. One practical application of XML is the opp_neddoc documentation generation tool which
is described in Chapter 11.
The nedtool program (which also translates NED to C++ code) can be used to convert between NED and
XML.
Converting a NED file to XML:
3 DTD stands for Document Type Descriptor, and it defines a "grammar" for XML files. More info can be found on the W3C web
site, www.w3.org.
35
36
Chapter 4
Simple Modules
Simple modules are the active components in the model. Simple modules are programmed in C++, using
the OMNeT++ class library. The following sections contain a short introduction to discrete event simulation in general, explain how its concepts are implemented in OMNeT++, and give an overview and
practical advice on how to design and code simple modules.
4.1
Simulation concepts
This section contains a very brief introduction into how Discrete Event Simulation (DES) works, in order
to introduce terms well use when explaining OMNeT++ concepts and implementation.
4.1.1
A Discrete Event System is a system where state changes (events) happen at discrete instances in time,
and events take zero time to happen. It is assumed that nothing (i.e. nothing interesting) happens
between two consecutive events, that is, no state change takes place in the system between the events (in
contrast to continuous systems where state changes are continuous). Those systems that can be viewed as
Discrete Event Systems can be modeled using Discrete Event Simulation. (Other systems can be modelled
e.g. with continuous simulation models.)
For example, computer networks are usually viewed as discrete event systems. Some of the events are:
start of a packet transmission
end of a packet transmission
expiry of a retransmission timeout
This implies that between two events such as start of a packet transmission and end of a packet transmission, nothing interesting happens. That is, the packets state remains being transmitted. Note that
the definition of interesting events and states always depends on the intent and purposes of the person
doing the modeling. If we were interested in the transmission of individual bits, we would have included
something like start of bit transmission and end of bit transmission among our events.
The time when events occur is often called event timestamp ; with OMNeT++ well say arrival time (because in the class library, the word timestamp is reserved for a user-settable attribute in the event
class). Time within the model is often called simulation time, model time or virtual time as opposed to
real time or CPU time which refer to how long the simulation program has been running and how much
CPU time it has consumed.
37
4.1.2
Discrete event simulation maintains the set of future events in a data structure often called FES (Future Event Set) or FEL (Future Event List). Such simulators usually work according to the following
pseudocode:
initialize -- this includes building the model and
inserting initial events to FES
while (FES not empty and simulation not yet complete)
{
retrieve first event from FES
t:= timestamp of this event
process event
(processing may insert new events in FES or delete existing ones)
}
finish simulation (write statistical results, etc.)
The first, initialization step usually builds the data structures representing the simulation model, calls
any user-defined initialization code, and inserts initial events into the FES to ensure that the simulation
can start. Initialization strategy can differ considerably from one simulator to another.
The subsequent loop consumes events from the FES and processes them. Events are processed in strict
timestamp order in order to maintain causality, that is, to ensure that no event may have an effect on
earlier events.
Processing an event involves calls to user-supplied code. For example, using the computer network simulation example, processing a timeout expired event may consist of re-sending a copy of the network
packet, updating the retry count, scheduling another timeout event, and so on. The user code may also
remove events from the FES, for example when canceling timeouts.
The simulation stops when there are no events left (this happens rarely in practice), or when it isnt
necessary for the simulation to run further because the model time or the CPU time has reached a given
limit, or because the statistics have reached the desired accuracy. At this time, before the program exits,
the user will typically want to record statistics into output files.
4.1.3
In OMNeT++, events occur inside simple modules. Simple modules encapsulate C++ code that generates
events and reacts to events, in other words, implements the behaviour of the model.
The user creates simple module types by subclassing the cSimpleModule class, which is part of the OMNeT++ class library. cSimpleModule, just as cCompoundModule, is derived from a common base class,
cModule.
cSimpleModule, although packed with simulation-related functionality, doesnt do anything useful by
itself you have to redefine some virtual member functions to make it do useful work.
These member functions are the following:
void initialize()
void handleMessage(cMessage *msg)
void activity()
void finish()
38
4.1.4
Events in OMNeT++
OMNeT++ uses messages to represent events. Each event is represented by an instance of the cMessage
class or one its subclasses; there is no separate event class. Messages are sent from one module to
another this means that the place where the event will occur is the messages destination module, and
the model time when the event occurs is the arrival time of the message. Events like timeout expired
are implemented by the module sending a message to itself.
Simulation time in OMNeT++ is stored in the C++ type simtime_t, which is a typedef for double.
Events are consumed from the FES in arrival time order, to maintain causality. More precisely, given two
messages, the following rules apply:
1. the message with earlier arrival time is executed first. If arrival times are equal,
2. the one with smaller priority value is executed first. If priorities are the same,
3. the one scheduled or sent earlier is executed first.
Priority is a user-assigned integer attribute of messages.
Storing simulation time in doubles may sometimes cause inconveniences. Due to finite machine precision,
two doubles calculated in two different ways do not always compare equal even if they mathematically
should be. For example, addition is not an associative operation when it comes to floating point calculations: (x + y) + z! = x + (y + z)! (See [Gol91]). This means that it is generally not a good idea to rely on
arrival times of two events being the same unless they are calculated in exactly the same way.
One may suggest introducing a small simtime_precision parameter in the simulation kernel that would
force t1 and t2 to be regarded equal if they are very close (if they differ less than simtime_precision).
This approach, however, would be more likely to cause confusion than actually cure the problem.
4.1.5
FES implementation
The implementation of the FES is a crucial factor in the performance of a discrete event simulator. In OMNeT++, the FES is implemented with binary heap, the most widely used data structure for this purpose.
Heap is also the best algorithm we know, although exotic data structures like skiplist may perform better
than heap in some cases. In case youre interested, the FES implementation is in the cMessageHeap
class, but as a simulation programmer you wont ever need to care about that.
39
4.2
4.2.1
Connections can be assigned three parameters, which facilitate the modeling of communication networks,
but can be useful for other models too:
propagation delay (sec)
bit error rate (errors/bit)
data rate (bits/sec)
Each of these parameters is optional. One can specify link parameters individually for each connection,
or define link types (also called channel types) once and use them throughout the whole model.
The propagation delay is the amount of time the arrival of the message is delayed by when it travels
through the channel. Propagation delay is specified in seconds.
The bit error rate has influence on the transmission of messages through the channel. The bit error rate
(ber) is the probability that a bit is incorrectly transmitted. Thus, the probability that a message of n bits
length is transferred without bit errors is:
Pnobiterror = (1 ber)l ength
The message has an error flag which is set in case of transmission errors.
The data rate is specified in bits/second, and it is used for transmission delay calculation. The sending
time of the message normally corresponds to the transmission of the first bit, and the arrival time of the
message corresponds to the reception of the last bit (Fig. 4.1).
4.2.2
If a data rate is specified for a connection, a message will have a certain nonzero transmission time,
depending on the length of the connection. This implies that a message that is passsing through an
output gate, reserves the gate for a given period (it is being transmitted).
While a message is under transmission, other messages have to wait until the transmission is completed.
You can still send messages while the gate is busy, but the beginning of the modeled message transmission
will be delayed, just as if the gate had an internal queue for the messages waiting to be transmitted.
The OMNeT++ class library provides functions to check whether a certain output gate is transmitting or
to learn when it finishes transmission.
If the connection with a data rate is not directly connected to the simple modules output gate but is the
second one in the route, you have to check the second gates busy condition.
Implementation of message sending
Message sending is implemented like this: the arrival time and the bit error flag of a message are calculated immediately after the send() (or a similar) function is invoked. That is, if the message travels
through several links before it reaches its destination, it is not scheduled individually for each link, but
rather, every calculation is done once, within the send() call. This implementation was chosen because
of its run-time efficiency.
In the actual implementation of queuing the messages at busy gates and modeling the transmission delay,
41
4.3
4.3.1
As mentioned before 4.1.3, a simple module is nothing more than a C++ class which has to be subclassed
from cSimpleModule, with one or more virtual member functions redefined to define its behavior.
The class has to be registered with OMNeT++ via the Define_Module() macro. The Define_Module()
line should always be put into .cc or .cpp files and not header file (.h), because the compiler generates
code from it. 1
The following HelloModule is about the simplest simple module one could write. (We could have left
out the initialize() method as well to make it even smaller, but how would it say Hello then?) Note
cSimpleModule as base class, and the Define_Module() line.
1 For completeness, there is also a Define_Module_Like() macro, but its use is discouraged and might even be removed in
future OMNeT++ releases.
42
4.3.2
Constructor
Simple modules are never instantiated by the user directly, but rather by the simulation kernel. This
implies that one cannot write arbitrary constructors: the signature must be what is expected by the
simulation kernel. Luckily, this contract is very simple: the constructor must be public, and must take no
arguments:
public:
HelloModule();
4.3.3
The initialize() and finish() methods will be discussed in a later section in detail, but because
their apparent similarity to the constructor and the destructor is prone to cause some confusion, well
briefly cover them here.
The constructor gets called when the module is created, as part of the model setup process. At that time,
everything is just being built, so there isnt a lot things one can do from the constructor. In contrast,
initialize() gets called just before the simulation starts executing, when everything else has been set
up already.
finish() is for recording statistics, and it only gets called when the simulation has terminated normally.
It does not get called when the simulations stops with an error message. The destructor always gets called
at the end, no matter how the simulation stopped, but at that time it is fair to assume that the simulation
model has been halfway demolished already.
Based on the above, the following conventions exist for these four methods:
Constructor conventions:
Set pointer members of the module class to NULL; postpone all other initialization tasks to initialize().
initialize() conventions:
Perform all initialization tasks: read module parameters, initialize class variables, allocate dynamic data
structures with new; also allocate and initialize self-messages (timers) if needed.
finish() conventions:
Record statistics. Do not delete anything or cancel timers all cleanup must be done in the destructor.
destructor conventions:
Delete everything which was allocated by new and is still held by the module class. With self-messages
(timers), use the cancelAndDelete(msg) function! It is almost always wrong to just delete a selfmessage from the destructor, because it might be in the scheduled events list. The cancelAndDelete(msg)
function checks for that first, and cancels the message before deletion if necessary.
4.3.4
OMNeT++ versions earlier than 3.2 expected a different module class constructor, with the following
signature:
MyModule(const char *name, cModule *parentModule, size_t stack=<stacksize>);
For convenience, a macro named Module_Class_Members() was also provided, which expanded to a
default (i.e. do-nothing) constructor implementation.
In OMNeT++ 3.2, the Module_Class_Members() macro has been retained but expands to the new constructor definition. Thus a module which uses Module_Class_Members() does not need to be changed
44
4.3.5
OMNeT++ versions before the 3.2 release had a feature which often was, informally and also somewhat
incorrectly, called "garbage collection" (GC). The purpose of this feature was to mitigate the need for
writing destructors, and often constructors as well by providing automatic cleanup at the end of the
simulation. (It did not do anything during simulation, as the name might suggest.)
OMNeT++ (all versions) keep track of user-allocated simulation objects (typically: messages) and their
ownerships. What the "garbage collection" feature did was that during the cleanup of the model, after each
module destructor finished, it checked whether there were simulations objects left that were apparently
owned by that module but not deallocated by the destructor and if it found such objects, it invoked
delete on them.
It worked out nicely in 90 percent of cases, but occasionally it resulted in spurious crashes which were
hard to debug for users not familiar with OMNeT++ internals or lacking advanced C++ skills. 2
Starting from OMNeT++ 3.2, this cleanup-time GC mechanism has been disabled by default (perform-gc=
configuration option, see 8.2.6), and it generally not recommended to turn it back on. It does not do any
harm to run any simulation model without GC (apart from the memory leak).
It is expected that existing modules will be updated for OMNeT++ 3.2 sooner or later, by adding proper
constructors and destructors. To catalyse this process, OMNeT++ dumps the list of unreleased objects at
the end of the simulation. This dump can also be turned off in the configuration (print-undisposed=
configuration option, see 8.2.6).
4.3.6
An example
The following code is a bit longer but actually useful simple module implementation. It demonstrates
several of the above concepts, plus some others which will be explained in later sections:
1. constructor, initialize and destructor conventions
2. using messages for timers
3. accessing module parameters
4. recording statistics at the end of the simulation
5. documenting the programmers assumptions using ASSERT()
2 These crashes occurred due to lack of information available to the GC mechanism, e.g. C++ provides no way to detect from the
pointer whether an object is part of an array, or is inside a struct or class. The solution was to use pointers: pointer array, pointer
as class member, etc.
45
void FFGenerator::finish()
{
recordScalar("packets sent", numSent);
}
FFGenerator::~FFGenerator()
{
cancelAndDelete(sendMessageEvent);
}
It also needs a NED declaration to be able to use it in NED files:
// file: FFGenerator.ned
simple FFGenerator
parameters:
sendIaTime: numeric;
gates:
out: out;
endsimple
4.3.7
If possible, avoid using global variables, including static class members. They are prone to cause several
problems. First, they are not reset to their initial values (to zero) when you rebuild the simulation in
Tkenv, or start another run in Cmdenv. This may produce surprising results. Second, they prevent you
from running your simulation in parallel. When using parallel simulation, each partition of your model
(may) run in a separate process, having its own copy of the global variables. This is usually not what you
want.
The solution is to encapsulate the variables into simple modules as private or protected data members,
and expose them via public methods. Other modules can then call these public methods to get or set the
values. Calling methods of other modules will be discussed in section 4.10. Examples of such modules
are the Blackboard in the Mobility Framework, and InterfaceTable and RoutingTable in the INET
Framework.
4.4
This section discusses cSimpleModules four previously mentioned member functions, intended to be
redefined by the user: initialize(), handleMessage(), activity() and finish(), plus a fifth, less
frequently used one, handleParameterChange.
4.4.1
handleMessage()
Other simulators
Many simulation packages use a similar approach, often topped with something like a state machine
(FSM) which hides the underlying function calls. Such systems are:
OPNETT M which uses FSMs designed using a graphical editor;
NetSim++ clones OPNETs approach;
SMURPH (University of Alberta) defines a (somewhat eclectic) language to describe FSMs, and uses
a precompiler to turn it into C++ code;
Ptolemy (UC Berkeley) uses a similar method.
OMNeT++s FSM support is described in the next section.
4.4.2
activity()
Process-style description
With activity(), you can code the simple module much like you would code an operating system process
or a thread. You can wait for an incoming message (event) at any point of the code, you can suspend
the execution for some time (model time!), etc. When the activity() function exits, the module is
terminated. (The simulation can continue if there are other modules which can run.)
The most important functions you can use in activity() are (they will be discussed in detail later):
receive() to receive messages (events)
wait() to suspend execution for some time (model time)
send() family of functions to send messages to other modules
scheduleAt() to schedule an event (the module sends a message to itself )
cancelEvent() to delete an event scheduled with scheduleAt()
end() to finish execution of this module (same as exiting the activity() function)
The activity() function normally contains an infinite loop, with at least a wait() or receive() call
in its body.
52
Starter messages
Modules written with activity() need starter messages to boot. These starter messages are inserted
into the FES automatically by OMNeT++ at the beginning of the simulation, even before the initialize() functions are called.
4.4.3
Purpose
initialize() to provide place for any user setup code
finish() to provide place where the user can record statistics after the simulation has completed
Multi-stage initialization
In simulation models, when one-stage initialization provided by initialize() is not sufficient, one can
use multi-stage initialization. Modules have two functions which can be redefined by the user:
void initialize(int stage);
int numInitStages() const;
At the beginning of the simulation, initialize(0) is called for all modules, then initialize(1),
initialize(2), etc. You can think of it like initialization takes place in several waves. For each
module, numInitStages() must be redefined to return the number of init stages required, e.g. for a twostage init, numInitStages() should return 2, and initialize(int stage) must be implemented to
handle the stage=0 and stage=1 cases. 3
The callInitialize() function performs the full multi-stage initialization for that module and all its
submodules.
If you do not redefine the multi-stage initialization functions, the default behavior is single-stage initialization: the default numInitStages() returns 1, and the default initialize(int stage) simply
calls initialize().
End-of-Simulation event
The task of finish() is solved in several simulators by introducing a special end-of-simulation event.
This is not a very good practice because the simulation programmer has to code the models (often represented as FSMs) so that they can always properly respond to end-of-simulation events, in whichever state
they are. This often makes program code unnecessarily complicated.
This can also be witnessed in the design of the PARSEC simulation language (UCLA). Its predecessor
Maisie used end-of-simulation events, but as documented in the PARSEC manual this has led to
awkward programming in many cases, so for PARSEC end-of-simulation events were dropped in favour
of finish() (called finalize() in PARSEC).
4.4.4
handleParameterChange()N ew!
The handleParameterChange() method was added in OMNeT++ 3.2, and it gets called by the simulation kernel when a module parameter changes. The method signature is the following:
void handleParameterChange(const char *parname);
3 Note const in the numInitStages() declaration. If you forget it, by C++ rules you create a different function instead of
redefining the existing one in the base class, thus the existing one will remain in effect and return 1.
58
4.4.5
It is often needed to have several variants of a simple module. A good design strategy is to create a simple
module class with the common functionality, then subclass from it to create the specific simple module
types.
An example:
class ModifiedTransportProtocol : public TransportProtocol
{
protected:
virtual void recalculateTimeout();
};
Define_Module(ModifiedTransportProtocol);
void ModifiedTransportProtocol::recalculateTimeout()
{
//...
}
4.5
Overview
Finite State Machines (FSMs) can make life with handleMessage() easier. OMNeT++ provides a class
and a set of macros to build FSMs. OMNeT++s FSMs work very much like OPNETs or SDLs.
59
To enable the above output, you have to #define FSM_DEBUG before including omnetpp.h.
#define FSM_DEBUG
// enables debug output from FSMs
#include <omnetpp.h>
The actual logging is done via the FSM_Print() macro. It is currently defined as follows, but you can
change the output format by undefining FSM_Print() after including omnetpp.ini and providing a new
definition instead.
#define FSM_Print(fsm,exiting)
(ev << "FSM " << (fsm).name()
<< ((exiting) ? ": leaving state " : ": entering state ")
<< (fsm).stateName() << endl)
Implementation
The FSM_Switch() is a macro. It expands to a switch() statement embedded in a for() loop which
repeats until the FSM reaches a steady state. (The actual code is rather scary, but if youre dying to see
it, it is in cfsm.h.)
Infinite loops are avoided by counting state transitions: if an FSM goes through 64 transitions without
reaching a steady state, the simulation will terminate with an error message.
An example
Let us write another bursty generator. It will have two states, SLEEP and ACTIVE. In the SLEEP state,
the module does nothing. In the ACTIVE state, it sends messages with a given inter-arrival time. The
code was taken from the Fifo2 sample simulation.
#define FSM_DEBUG
#include <omnetpp.h>
61
4.6
On an abstract level, an OMNeT++ simulation model is a set of simple modules that communicate with
each other via message passing. The essence of simple modules is that they create, send, receive, store,
modify, schedule and destroy messages everything else is supposed to facilitate this task, and collect
statistics about what was going on.
63
4.6.1
Sending messages
Once created, a message object can be sent through an output gate using one of the following functions:
send(cMessage *msg, const char *gateName, int index=0);
send(cMessage *msg, int gateId);
send(cMessage *msg, cGate *gate);
In the first function, the argument gateName is the name of the gate the message has to be sent through.
If this gate is a vector gate, index determines though which particular output gate this has to be done;
otherwise, the index argument is not needed.
The second and third functions use the gate Id and the pointer to the gate object. They are faster than
the first one because they dont have to search through the gate array.
Examples:
send(msg, "outGate");
send(msg, "outGates", i); // send via outGates[i]
The following code example creates and sends messages every 5 simulated seconds:
int outGateId = findGate("outGate");
while(true)
{
send(new cMessage("packet"), outGateId);
wait(5);
}
Modeling packet transmissions
If youre sending messages over a link that has (nonzero) data rate, it is modeled as described earlier in
this manual, in section 4.2.
If you want to have full control over the transmission process, youll probably need the isBusy() and
transmissionFinishes() member functions of cGate. They are described in section 4.8.3.
4.6.2
When you implement broadcasts or retransmissions, two frequently occurring tasks in protocol simulation, you might feel tempted to use the same message in multiple send() operations. Do not do it
you cannot send the same message object multiple times. The solution in such cases is duplicating the
message.
64
4.6.3
Delayed sending
It is often needed to model a delay (processing time, etc.) immediately followed by message sending. In
OMNeT++, it is possible to implement it like this:
wait( someDelay );
send( msg, "outgate" );
If the module needs to react to messages that arrive during the delay, wait() cannot be used and the
timer mechanism described in Section 4.6.7, Self-messages, would need to be employed.
There is also a more straightforward method than those mentioned above: delayed sending. Delayed
sending can be achieved by using one of these functions:
sendDelayed(cMessage *msg, double delay, const char *gateName, int index);
sendDelayed(cMessage *msg, double delay, int gateId);
sendDelayed(cMessage *msg, double delay, cGate *gate);
The arguments are the same as for send(), except for the extra delay parameter. The effect of the
function is the same as if the module had kept the message for the delay interval and sent it afterwards.
That is, the sending time of the message will be the current simulation time (time at the sendDelayed()
call) plus the delay. The delay value must be non-negative.
Example:
sendDelayed(msg, 0.005, "outGate");
4.6.4
66
4.6.5
Receiving messages
With activity() only! The message receiving functions can only be used in the activity() function,
handleMessage() gets received messages in its argument list.
Messages are received using the receive() function. receive() is a member of cSimpleModule.
cMessage *msg = receive();
The receive() function accepts an optional timeout parameter. (This is a delta, not an absolute simulation time.) If an appropriate message doesnt arrive within the timeout period, the function returns a
NULL pointer. 5
simtime_t timeout = 3.0;
cMessage *msg = receive( timeout );
if (msg==NULL)
{
...
// handle timeout
}
else
{
... // process message
}
4.6.6
With activity() only! The wait() functions implementation contains a receive() call which cannot
be used in handleMessage().
The wait() function suspends the execution of the module for a given amount of simulation time (a
delta).
wait( delay );
In other simulation software, wait() is often called hold. Internally, the wait() function is implemented
by a scheduleAt() followed by a receive(). The wait() function is very convenient in modules that
do not need to be prepared for arriving messages, for example message generators. An example:
for(;;)
{
// wait for a (potentially random amount of) time, specified
// in the interArrivalTime module parameter
wait( par("interArrivalTime") );
// generate and send message
...
}
It is a runtime error if a message arrives during the wait interval. If you expect messages to arrive during
the wait period, you can use the waitAndEnqueue() function. It takes a pointer to a queue object (of
class cQueue, described in chapter 6) in addition to the wait interval. Messages that arrive during the
wait interval will be accumulated in the queue, so you can process them after the waitAndEnqueue()
call returned.
5 Putaside-queue and the functions receiveOn(), receiveNew(), and receiveNewOn() were deprecated in OMNeT++ 2.3 and
removed in OMNeT++ 3.0.
67
4.6.7
In most simulation models it is necessary to implement timers, or schedule events that occur at some
point in the future. For example, when a packet is sent by a communications protocol model, it has to
schedule an event that would occur when a timeout expires, because it will have to resent the packet then.
As another example, suppose you want to write a model of a server which processes jobs from a queue.
Whenever it begins processing a job, the server model will want to schedule an event to occur when the
job finishes processing, so that it can begin processing the next job.
In OMNeT++ you solve such tasks by letting the simple module send a message to itself; the message
would be delivered to the simple module at a later point of time. Messages used this way are called
self-messages. Self-messages are used to model events which occur within the module.
Scheduling an event
The module can send a message to itself using the scheduleAt() function. scheduleAt() accepts an
absolute simulation time, usually calculated as simTime()+delta:
scheduleAt(absoluteTime, msg);
scheduleAt(simtime()+delta, msg);
Self-messages are delivered to the module in the same way as other messages (via the usual receive calls
or handleMessage()); the module may call the isSelfMessage() member of any received message to
determine if it is a self-message.
As an example, heres how you could implement your own wait() function in an activity() simple
module, if the simulation kernel didnt provide it already:
cMessage *msg = new cMessage();
scheduleAt(simtime()+waitTime, msg);
cMessage *recvd = receive();
if (recvd!=msg)
// hmm, some other event occurred meanwhile: error!
...
You can determine if a message is currently in the FES by calling its isScheduled() member:
if (msg->isScheduled())
// currently scheduled
else
// not scheduled
Re-scheduling an event
If you want to reschedule an event which is currently scheduled to a different simulation time, first you
have to cancel it using cancelEvent().
68
4.6.8
Normal termination
You can finish the simulation with the endSimulation() function:
endSimulation();
Typically you dont need endSimulation() because you can specify simulation time and CPU time limits
in the ini file (see later).
Stopping on errors
If you want your simulation to stop if it detects an error condition, you can call the error() member
function of cModule. It is used like printf():
if (windowSize<1)
error("Invalid window size %d; must be >=1", windowSize);
Do not include a newline (\n) or punctuation (period or exclamation mark) in the error text, as it will
be added by OMNeT++.
69
4.7
Module parameters can be accessed by calling the par() member function of cModule:
cPar& delayPar = par("delay");
The cPar class is a general value-storing object. It supports type casts to numeric types, so parameter
values can be read like this:
int numTasks = par("numTasks");
double processingDelay = par("processingDelay");
If the parameter is a random variable or its value can change during execution, it is best to store a
reference to it and re-read the value each time it is needed:
cPar& waitTime = par("waitTime");
for(;;)
{
//...
wait( (simtime_t)waitTime );
}
If the wait_time parameter was given a random value (e.g. exponential(1.0)) in the NED source or
the ini file, the above code results in a different delay each time.
Parameter values can also be changed from the program, during execution. If the parameter was taken by
reference (with a ref modifier in the NED file), other modules will also see the change. Thus, parameters
taken by reference can be used as a means of module communication.
An example:
par("waitTime") = 0.12;
Or:
cPar& waitTime = par("waitTime");
waitTime = 0.12;
The cPar class is discussed in more detail in section 6.6.
4.7.1
As of version 3.2, OMNeT++ does not support parameter arrays, but in practice they can be emulated
using string parameters. One can assign the parameter a string which contains all values in a textual
form (for example, "0 1.234 3.95 5.467"), then parse this string in the simple module.
The cStringTokenizer class can be quite helpful for this purpose. The constructor accepts a string,
which it regards as a sequence of tokens (words) separated by delimiter characters (by default, spaces).
Then, calling the nextToken() method several times will return the tokens one by one. After the last
token, it returns NULL.
For example, you can parse a string containing a sequence of integers into a vector using the following
code:
70
4.8
4.8.1
Module gates are cGate objects. Gate objects know whether, and to which gate they are connected. They
can also be queried on the parameters of the link (delay, data rate, etc.)
The gate() member function of cModule returns a pointer to a cGate object, and an overloaded form of
the function lets you access elements of a vector gate:
cGate *outgate = gate("out");
cGate *outvec5gate = gate("outvec",5);
For gate vectors, the first form returns the first gate in the vector (at index 0).
The isVector() member function can be used to determine if a gate belongs to a gate vector or not.
Given a gate pointer, you can use the size() and index() member functions of cGate to determine the
size of the gate vector and the index of the gate within the vector:
int size2 = outvec5gate->size(); // --> size of outvec[]
int index = outvec5gate->index(); // --> 5 (it is gate 5 in the vector)
Instead of gate->size(), you can also call the gateSize() method of cModule, which does the same:
int size2 = gateSize("out");
For non-vector gates, size() returns 1 and index() returns 0.
Zero-size gate vectors are represented with a placeholder gate whose size() method returns zero and
cannot be connected.
The type() member function returns a character, I for input gates and O for output gates:
char type = outgate->type() // --> O
71
dir
input
output
name[index]
fromApp
toApp
empty
input
in[0]
input
in[1]
input
in[2]
output
status
The array may have empty slots. Gate vectors are guaranteed to occupy contiguous IDs, thus it is legal to
calculate the ID of gate[k] as gate("gate",0).id()+k.
Message sending and receiving functions accept both gate names and gate IDs; the functions using gate
IDs are a bit faster. Gate IDs do not change during execution, so it is often worth retrieving them in
advance and using them instead of gate names.
You can also obtain gate IDs with the findGate() member of cModule:
int id1 = findGate("out");
int id2 = findGate("outvect",5);
4.8.2
Connection parameters
Connection attributes (propagation delay, transmission data rate, bit error rate) are represented by the
channel object, which is available via the source gate of the connection.
cChannel *chan = outgate->channel();
cChannel is a small base class. All interesting attributes are part of its subclass cBasicChannel, so you
have to cast the pointer before getting to the delay, error and data rate values.
cBasicChannel *chan = check_and_cast<cBasicChannel *>(outgate->channel());
double d = chan->delay();
double e = chan->error();
double r = chan->datarate();
You can also change the channel attributes with the corresponding setXXX() functions. Note, however,
that (as it was explained in section 4.2) changes will not affect messages already sent, even if they have
not begun transmission yet.
4.8.3
Transmission state
The isBusy() member function returns whether the gate is currently transmitting, and if so, the transmissionFinishes() member function returns the simulation time when the gate is going to finish trans72
4.8.4
Connectivity
The isConnected() member function returns whether the gate is connected. If the gate is an output
gate, the gate to which it is connected is obtained by the toGate() member function. For input gates, the
function is fromGate().
cGate *gate = gate("somegate");
if (gate->isConnected())
{
cGate *othergate = (gate->type()==O) ?
gate->toGate() : gate->fromGate();
ev << "gate is connected to: " << othergate->fullPath() << endl;
}
else
{
ev << "gate not connected" << endl;
}
An alternative to isConnected() is to check the return value of toGate() or fromGate(). The following code is fully equivalent to the one above:
cGate *gate = gate("somegate");
cGate *othergate = (gate->type()==O) ?
gate->toGate() : gate->fromGate();
if (othergate)
ev << "gate is connected to: " << othergate->fullPath() << endl;
else
ev << "gate not connected" << endl;
73
4.9
Module vectors
If a module is part of a module vector, the index() and size() member functions can be used to query
its index and the vector size:
ev << "This is module [" << module->index() <<
"] in a vector of size [" << module->size() << "].\n";
Module IDs
Each module in the network has a unique ID that is returned by the id() member function. The module
ID is used internally by the simulation kernel to identify modules.
int myModuleId = id();
If you know the module ID, you can ask the simulation object (a global variable) to get back the module
pointer:
int id = 100;
cModule *mod = simulation.module( id );
Module IDs are guaranteed to be unique, even when modules are created and destroyed dynamically.
That is, an ID which once belonged to a module which was deleted is never issued to another module
later.
Walking up and down the module hierarchy
The surrounding compound module can be accessed by the parentModule() member function:
cModule *parent = parentModule();
For example, the parameters of the parent module are accessed like this:
double timeout = parentModule()->par( "timeout" );
74
4.10
In some simulation models, there might be modules which are too tightly coupled for message-based
communication to be efficient. In such cases, the solution might be calling one simple modules public
C++ methods from another module.
Simple modules are C++ classes, so normal C++ method calls will work. Two issues need to be mentioned,
however:
how to get a pointer to the object representing the module;
how to let the simulation kernel know that a method call across modules is taking place.
Typically, the called module is in the same compound module as the caller, so the parentModule() and
submodule() methods of cModule can be used to get a cModule* pointer to the called module. (Further
ways to obtain the pointer are described in the section 4.9.) The cModule* pointer then has to be cast to
the actual C++ class of the module, so that its methods become visible.
This makes the following code:
cModule *calleeModule = parentModule()->submodule("callee");
Callee *callee = check_and_cast<Callee *>(calleeModule);
callee->doSomething();
The check_and_cast<>() template function on the second line is part of OMNeT++. It does a standard
C++ dynamic_cast, and checks the result: if it is NULL, check_and_cast raises an OMNeT++ error.
Using check_and_cast saves you from writing error checking code: if calleeModule from the first line
is NULL because the submodule named "callee" was not found, or if that module is actually not of type
Callee, an error gets thrown from check_and_cast.
The second issue is how to let the simulation kernel know that a method call across modules is taking
place. Why is this necessary in the first place? First, the simulation kernel always has to know which
modules code is currently executing, in order to several internal mechanisms to work correctly. (One such
mechanism is ownership handling.) Second, the Tkenv simulation GUI can animate method calls, but to
be able to do that, it has to know about them.
The solution is to add the Enter_Method() or Enter_Method_Silent() macro at the top of the methods that may be invoked from other modules. These calls perform context switching, and, in case of
Enter_Method(), notify the simulation GUI so that animation of the method call can take place. Enter_Method_Silent() does not animate the call. Enter_Method() expects a printf()-like argument
list the resulting string will be displayed during animation.
void Callee::doSomething()
{
Enter_Method("doSomething()");
...
}
4.11
4.11.1
In some situations you need to dynamically create and maybe destroy modules. For example, when simulating a mobile network, you may create a new module whenever a new user enters the simulated area,
and dispose of them when they leave the area.
76
4.11.2
Overview
To understand how dynamic module creation works, you have to know a bit about how normally OMNeT++ instantiates modules. Each module type (class) has a corresponding factory object of the class
cModuleType. This object is created under the hood by the Define_Module() macro, and it has a
factory function which can instantiate the module class (this function basically only consists of a return
new module-class(...) statement).
The cModuleType object can be looked up by its name string (which is the same as the module class
name). Once you have its pointer, it is possible to call its factory method and create an instance of the
corresponding module class without having to include the C++ header file containing modules class
declaration into your source file.
The cModuleType object also knows what gates and parameters the given module type has to have. (This
info comes from compiled NED code.)
Simple modules can be created in one step. For a compound module, the situation is more complicated,
because its internal structure (submodules, connections) may depend on parameter values and gate vector
sizes. Thus, for compound modules it is generally required to first create the module itself, second, set
parameter values and gate vector sizes, and then call the method that creates its submodules and internal
connections.
As you know already, simple modules with activity() need a starter message. For statically created
modules, this message is created automatically by OMNeT++, but for dynamically created modules, you
have to do this explicitly by calling the appropriate functions.
Calling initialize() has to take place after insertion of the starter messages, because the initializing
code may insert new messages into the FES, and these messages should be processed after the starter
message.
4.11.3
Creating modules
4.11.4
Deleting modules
4.11.5
When you delete a module during simulation, its finish() function is not called automatically (deleteModule() doesnt do it.) How the module was created doesnt play any role here: finish() gets called
for all modules at the end of the simulation. If a module doesnt live that long, finish() is not invoked,
but you can still manually invoke it.
You can use the callFinish() function to arrange finish() to be called. It is usually not a good idea to
invoke finish() directly. If youre deleting a compound module, callFinish() will recursively invoke
finish() for all submodules, and if youre deleting a simple module from another module, callFinish() will do the context switch for the duration of the call. 6
Example:
mod->callFinish();
mod->deleteModule();
4.11.6
Creating connections
Connections can be created using cGates connectTo() method. 7 connectTo() should be invoked on
the source gate of the connection, and expects the destination gate pointer as an argument:
srcGate->connectTo(destGate);
The source and destination words correspond to the direction of the arrow in NED files.
As an example, we create two modules and connect them in both directions:
cModuleType *moduleType = findModuleType("TicToc");
cModule *a = modtype->createScheduleInit("a",this);
cModule *b = modtype->createScheduleInit("b",this);
a->gate("out")->connectTo(b->gate("in"));
b->gate("out")->connectTo(a->gate("in"));
connectTo() also accepts a channel object as an additional, optional argument. Channels are subclassed
from cChannel. Almost always youll want use an instance of cBasicChannel as channel this is the
one that supports delay, bit error rate and data rate. The channel object will be owned by the source gate
of the connection, and you cannot reuse the same channel object with several connections.
cBasicChannel has setDelay(), setError() and setDatarate() methods to set up the channel
attributes.
An example that sets up a channel with a delay:
cBasicChannel *channel = new cBasicChannel("channel");
channel->setDelay(0.001);
a->gate("out")->connectTo(b->gate("in"), channel); // a,b are modules
6 The
finish() function is even made protected in cSimpleModule, in order to discourage its invocation from other modules.
earlier connect() global functions that accepted two gates have been deprecated, and may be removed from further OMNeT++ releases.
7 The
79
4.11.7
Removing connections
The disconnect() method of cGate can be used to remove connections. This method has to be invoked
on the source side of the connection. It also destroys the channel object associated with the connection, if
one has been set.
srcGate->disconnect();
80
Chapter 5
Messages
5.1
5.1.1
cMessage is a central class in OMNeT++. Objects of cMessage and subclasses may model a number of
things: events; messages; packets, frames, cells, bits or signals travelling in a network; entities travelling
in a system and so on.
Attributes
A cMessage object has number of attributes. Some are used by the simulation kernel, others are provided
just for the convenience of the simulation programmer. A more-or-less complete list:
The name attribute is a string (const char *), which can be freely used by the simulation programmer. The message name appears in many places in Tkenv (for example, in animations), and it
is generally very useful to choose a descriptive name. This attribute is inherited from cObject (see
section 6.1.1).
The message kind attribute is supposed to carry some message type information. Zero and positive
values can be freely used for any purpose. Negative values are reserved for use by the OMNeT++
simulation library.
The length attribute (understood in bits) is used to compute transmission delay when the message
travels through a connection that has an assigned data rate.
The bit error flag attribute is set to true by the simulation kernel with a probability of 1(1ber)length
when the message is sent through a connection that has an assigned bit error rate (ber).
The priority attribute is used by the simulation kernel to order messages in the message queue
(FES) that have the same arrival time values.
The time stamp attribute is not used by the simulation kernel; you can use it for purposes such as
noting the time when the message was enqueued or re-sent.
Other attributes and data members make simulation programming easier, they will be discussed
later: parameter list, encapsulated message, control info and context pointer.
A number of read-only attributes store information about the messages (last) sending/scheduling:
source/destination module and gate, sending (scheduling) and arrival time. They are mostly used
by the simulation kernel while the message is in the FES, but the information is still in the message
object when a module receives the message.
81
=
=
=
=
=
=
msg->kind();
msg->priority();
msg->length();
msg->byteLength();
msg->hasBitError();
msg->timestamp();
byteLength() also reads the length field as length(), but the result gets divided by 8 and rounded up.
Duplicating messages
It is often necessary to duplicate a message (for example, sending one and keeping a copy). This can be
done in the same way as for any other OMNeT++ object:
cMessage *copy = (cMessage *) msg->dup();
82
5.1.2
Self-messages
5.1.3
Modelling packets
senderModuleId();
senderGateId();
arrivalModuleId();
arrivalGateId();
The following methods are just convenience functions which build on the ones above.
cModule *senderModule();
cGate *senderGate();
cGate *arrivalGate();
And there are further convenience functions to tell whether the message arrived on a specific gate given
with id or name+index.
bool arrivedOn(int id);
bool arrivedOn(const char *gname, int gindex=0);
The following methods return message creation time and the last sending and arrival times.
simtime_t creationTime()
simtime_t sendingTime();
simtime_t arrivalTime();
Control info
One of the main application areas of OMNeT++ is the simulation of telecommunication networks. Here,
protocol layers are usually implemented as modules which exchange packets. Packets themselves are
represented by messages subclassed from cMessage.
However, communication between protocol layers requires sending additional information to be attached
to packets. For example, a TCP implementation sending down a TCP packet to IP will want to specify
the destination IP address and possibly other parameters. When IP passes up a packet to TCP after
decapsulation from the IP header, itll want to let TCP know at least the source IP address.
This additional information is represented by control info objects in OMNeT++. Control info objects have
to be subclassed from cPolymorphic (a small footprint base class with no data members), and attached
to the messages representing packets. cMessage has the following methods for this purpose:
void setControlInfo(cPolymorphic *controlInfo);
cPolymorphic *controlInfo();
cPolymorphic *removeControlInfo();
When a "command" is associated with the message sending (such as TCP OPEN, SEND, CLOSE, etc),
the message kind field (kind(), setKind() methods of cMessage) should carry the command code.
When the command doesnt involve a data packet (e.g. TCP CLOSE command), a dummy packet (empty
cMessage) can be sent.
Identifying the protocol
In OMNeT++ protocol models, the protocol type is usually represented in the message subclass. For
example, instances of class IPv6Datagram represent IPv6 datagrams and EthernetFrame represents
Ethernet frames) and/or in the message kind value. The PDU type is usually represented as a field inside
the message class.
The C++ dynamic_cast operator can be used to determine if a message object is of a specific protocol.
84
5.1.4
Encapsulation
Encapsulating packets
It is often necessary to encapsulate a message into another when youre modeling layered protocols of
computer networks. Although you can encapsulate messages by adding them to the parameter list, theres
a better way.
The encapsulate() function encapsulates a message into another one. The length of the message will
grow by the length of the encapsulated message. An exception: when the encapsulating (outer) message
has zero length, OMNeT++ assumes it is not a real packet but some out-of-band signal, so its length is left
at zero.
cMessage *userdata = new cMessage("userdata");
userdata->setByteLength(2048); // 2K
cMessage *tcpseg = new cMessage("tcp");
tcpseg->setByteLength(24);
tcpseg->encapsulate(userdata);
ev << tcpseg->byteLength() << endl; // --> 2048+24 = 2072
A message can only hold one encapsulated message at a time. The second encapsulate() call will result
in an error. It is also an error if the message to be encapsulated isnt owned by the module.
You can get back the encapsulated message by decapsulate():
cMessage *userdata = tcpseg->decapsulate();
decapsulate() will decrease the length of the message accordingly, except if it was zero. If the length
would become negative, an error occurs.
The encapsulatedMsg() function returns a pointer to the encapsulated message, or NULL if no message
was encapsulated.
Reference countingN ew!
Since the 3.2 release, OMNeT++ implements reference counting of encapsulated messages, meaning that
if you dup() a message that contains an encapsulated message, then the encapsulated message will not
be duplicated, only a reference count incremented. Duplication of the encapsulated message is deferred
until decapsulate() actually gets called. If the outer message gets deleted without its decapsulate()
method ever being called, then the reference count of the encapsulated message simply gets decremented.
The encapsulated message is deleted when its reference count reaches zero.
Reference counting can significantly improve performance, especially in LAN and wireless scenarios. For
example, in the simulation of a broadcast LAN or WLAN, the IP, TCP and higher layer packets wont get
duplicated (and then discarded without being used) if the MAC address doesnt match in the first place.
The reference counting mechanism works transparently. However, there is one implication: one must
not change anything in a message that is encapsulated into another! That is, encapsulatedMsg() should be viewed as if it returned a pointer to a read-only object (it returns a const pointer
85
5.1.5
If you want to add parameters or objects to a message, the preferred way to do that is via message
definitions, described in chapter 5.2.
Attaching objects
The cMessage class has an internal cArray object which can carry objects. Only objects that are derived from cObject (most OMNeT++ classes are so) can be attached. The addObject(), getObject(),
hasObject(), removeObject() methods use the object name as the key to the array. An example:
cLongHistogram *pklenDistr = new cLongHistogram("pklenDistr");
msg->addObject( pklenDistr );
...
if (msg->hasObject("pklenDistr"))
{
cLongHistogram *pklenDistr =
(cLongHistogram *) msg->getObject("pklenDistr");
...
}
86
5.2
5.2.1
Message definitions
Introduction
In practice, youll need to add various fields to cMessage to make it useful. For example, if youre modelling packets in communication networks, you need to have a way to store protocol header fields in
message objects. Since the simulation library is written in C++, the natural way of extending cMessage
is via subclassing it. However, because for each field you need to write at least three things (a private
data member, a getter and a setter method), and the resulting class has to integrate with the simulation
framework, writing the necessary C++ code can be a tedious and time-consuming task.
OMNeT++ offers a more convenient way called message definitions. Message definitions provide a very
compact syntax to describe message contents. C++ code is automatically generated from message definitions, saving you a lot of typing.
A common source of complaint about code generators in general is lost flexibility: if you have a different
idea how the generated code should look like, theres little you can do about it. In OMNeT++, however,
theres nothing to worry about: you can customize the generated class to any extent you like. Even if you
87
5.2.2
Declaring enums
An enum {..} generates a normal C++ enum, plus creates an object which stores text representations of
the constants. The latter makes it possible to display symbolic names in Tkenv. An example:
enum ProtocolTypes
{
IP = 1;
TCP = 2;
};
Enum values need to be unique.
5.2.3
Message declarations
Basic use
You can describe messages with the following syntax:
89
The set...ArraySize() method internally allocates a new array. Existing values in the array will be
preserved (copied over to the new array.)
The default array size is zero. This means that you need to call the set...ArraySize() before you can
start filling array elements.
String members
You can declare string-valued fields with the following syntax:
message FooPacket
{
fields:
string hostName;
};
The generated getter and setter methods will return and accept const char* pointers:
virtual const char *getHostName() const;
virtual void setHostName(const char *hostName);
The generated object will have its own copy of the string.
NOTE: a string member is different from a character array, which is treated as an array of any other type.
For example,
message FooPacket
{
fields:
char chars[10];
};
will generate the following methods:
virtual char getChars(unsigned k);
virtual void setChars(unsigned k, char a);
5.2.4
Inheritance, composition
So far we have discussed how to add fields of primitive types (int, double, char, ...) to cMessage. This
might be sufficient for simple models, but if you have more complex models, youll probably need to:
set up a hierarchy of message (packet) classes, that is, not only subclass from cMessage but also
from your own message classes;
92
Pointers
Not supported yet.
5.2.5
5.2.6
5.2.7
You may want to use STL vector or stack classes in your message classes. This is possible using abstract
fields. After all, vector and stack are representations of a sequence same abstraction as dynamic-size
vectors. That is, you can declare the field as abstract T fld[], and provide an underlying implementation using vector<T>. You can also add methods to the message class that invoke push_back(),
push(), pop(), etc. on the underlying STL object.
See the following message declaration:
struct Item
{
fields:
int a;
double b;
}
message STLMessage
{
properties:
customize=true;
fields:
abstract Item foo[]; // will use vector<Item>
abstract Item bar[]; // will use stack<Item>
}
If you compile the above, in the generated code youll only find a couple of abstract methods for foo and
bar, no data members or anything concrete. You can implement everything as you like. You can write the
following C++ file then to implement foo and bar with std::vector and std::stack:
#include <vector>
#include <stack>
#include "stlmessage_m.h"
public:
STLMessage(const char *name=NULL, int kind=0) : STLMessage_Base(name,kind) {}
STLMessage(const STLMessage& other) : STLMessage_Base(other.name()) {operator=(other
STLMessage& operator=(const STLMessage& other) {
if (&other==this) return *this;
STLMessage_Base::operator=(other);
foo = other.foo;
bar = other.bar;
return *this;
}
virtual cPolymorphic *dup() {return new STLMessage(*this);}
// foo methods
virtual void setFooArraySize(unsigned int size) {}
virtual unsigned int getFooArraySize() const {return foo.size();}
virtual Item& getFoo(unsigned int k) {return foo[k];}
virtual void setFoo(unsigned int k, const Item& afoo) {foo[k]=afoo;}
virtual void addToFoo(const Item& afoo) {foo.push_back(afoo);}
// bar methods
virtual void setBarArraySize(unsigned int size) {}
virtual unsigned int getBarArraySize() const {return bar.size();}
virtual Item& getBar(unsigned int k) {throw new cRuntimeException("sorry");}
virtual void setBar(unsigned int k, const Item& bar) {throw new cRuntimeException("s
virtual void barPush(const Item& abar) {bar.push(abar);}
virtual void barPop() {bar.pop();}
virtual Item& barTop() {return bar.top();}
};
Register_Class(STLMessage);
Some additional notes:
1. setFooArraySize(), setBarArraySize() are redundant.
2. getBar(int k) cannot be implemented in a straightforward way (std::stack does not support
accessing elements by index). It could still be implemented in a less efficient way using STL iterators, and efficiency does not seem to be major problem because only Tkenv is going to invoke this
function.
3. setBar(int k, const Item&) could not be implemented, but this is not particularly a problem.
The exception will materialize in a Tkenv error dialog when you try to change the field value.
You may regret that the STL vector/stack are not directly exposed. Well you could expose them (by
adding a vector<Item>& getFoo() {return foo;} method to the class) but this is probably not a
good idea. STL itself was purposefully designed with a low-level approach, to provide nuts and bolts for
C++ programming, and STL is better used in other classes for internal representation of data.
5.2.8
Summary
Generated code
double field;
double getField();
void setField(double d);
string type
string field;
fixed-size arrays
double field[4];
100
dynamic arrays
double field[];
customized class
class Foo {
properties:
customize=true;
abstract fields
abstract double field;
double getField() = 0;
void setField(double d) = 0;
Example simulations
Several of the example simulations (Token Ring, Dyna, Hypercube) use message definitions. For example,
in Dyna youll find this:
dynapacket.msg defines DynaPacket and DynaDataPacket;
dynapacket_m.h and dynapacket_m.cc are produced by the message subclassing compiler from
it, and they contain the generated DynaPacket and DynaDataPacket C++ classes (plus code for
Tkenv inspectors);
other model files (client.cc, server.cc, ...) use the generated message classes
5.2.9
In addition to the message class and its implementation, the message compiler also generates reflection
code which makes it possible to inspect message contents in Tkenv. To illustrate why this is necessary,
suppose you manually subclass cMessage to get a new message class. You could write the following: 1
class RadioMsg : public cMessage
{
public:
int freq;
double power;
...
};
Now it is possible to use RadioMsg in your simple modules:
1 Note that the code is only for illustration. In real code, freq and power should be private members, and getter/setter methods
should exist to access them. Also, the above class definition misses several member functions (constructor, assignment operator,
etc.) that should be written.
101
102
Chapter 6
6.1
6.1.1
Classes in the OMNeT++ simulation library are derived from cObject. Functionality and conventions
that come from cObject:
103
6.1.2
Member functions that set and query object attributes follow consistent naming. The setter member
function has the form setFoo(...) and its getter counterpart is named foo(). (The get verb found in
Java and some other libraries is omitted for brevity.) For example, the length attribute of the cMessage
class can be set and read like this:
msg->setLength(1024);
length = msg->length();
6.1.3
className()
For each class, the className() member function returns the class name as a string:
const char *classname = msg->className(); // returns "cMessage"
6.1.4
Name attribute
An object can be assigned a name (a character string). The name string is the first argument to the
constructor of every class, and it defaults to NULL (no name string). An example:
cMessage *timeoutMsg = new cMessage("timeout");
You can also set the name after the object has been created:
timeoutMsg->setName("timeout");
You can get a pointer to the internally stored copy of the name string like this:
const char *name = timeoutMsg->name(); // --> "timeout"
For convenience and efficiency reasons, the empty string "" and NULL are treated as equivalent by library
objects. That is, "" is stored as NULL but returned as "". If you create a message object with either NULL
or "" as name string, it will be stored as NULL and name() will return a pointer to a static "".
cMessage *msg = new cMessage(NULL, <additional args>);
const char *str = msg->name(); // --> returns ""
104
6.1.5
Objects have two more member functions which return strings based on object names: fullName() and
fullPath(). For gates and modules which are part of gate or module vectors, fullName() returns the
name with the index in brackets. That is, for a module node[3] in the submodule vector node[10]
name() returns "node", and fullName() returns "node[3]". For other objects, fullName() is the
same as name().
fullPath() returns fullName(), prepended with the parent or owner objects fullPath() and separated by a dot. That is, if the node[3] module above is in the compound module "net.subnet1", its
fullPath() method will return "net.subnet1.node[3]".
ev << this->name();
// --> "node"
ev << this->fullName(); // --> "node[3]"
ev << this->fullPath(); // --> "net.subnet1.node[3]"
className(), fullName() and fullPath() are extensively used on the graphical runtime environment Tkenv, and also appear in error messages.
name() and fullName() return const char * pointers, and fullPath() returns std::string. This
makes no difference with ev, but when fullPath() is used as a "%s" argument to sprintf() you
have to write fullPath().c_str().
char buf[100];
sprintf("msg is %80s", msg->fullPath().c_str()); // note c_str()
6.1.6
The dup() member function creates an exact copy of the object, duplicating contained objects also if
necessary. This is especially useful in the case of message objects. dup() returns a pointer of type
cObject*, so it needs to be cast to the proper type:
cMessage *copyMsg = (cMessage *) msg->dup();
dup() works by calling the copy constructor, which in turn relies on the assignment operator between
objects. operator=() can be used to copy contents of an object into another object of the same type. This
is a deep copy: object contained in the object will also be duplicated if necessary. operator=() does not
copy the name string this task is done by the copy constructor.
6.1.7
Iterators
There are several container classes in the library (cQueue, cArray etc.) For many of them, there is a
corresponding iterator class that you can use to loop through the objects stored in the container.
For example:
cQueue queue;
//..
for (cQueue::Iterator queueIter(queue); !queueIter.end(); queueIter++)
{
cObject *containedObject = queueIter();
}
105
6.1.8
Error handling
When library objects detect an error condition, they throw a C++ exception. This exception is then caught
by the simulation environment which pops up an error dialog or displays the error message.
At times it can be useful to be able stop the simulation at the place of the error (just before the exception is thrown) and use a C++ debugger to look at the stack trace and examine variables. Enabling the
debug-on-errors ini file entry lets you do that check it in section 8.2.6 .
If you detect an error condition in your code, you can stop the simulation with an error message using
the opp_error() function. opp_error()s argument list works like printf(): the first argument is a
format string which can contain "%s", "%d" etc, filled in using subsequent arguments.
An example:
if (msg->controlInfo()==NULL)
opp_error("message (%s)%s has no control info attached",
msg->className(), msg->name());
6.2
The logging feature will be used extensively in the code examples, we introduce it here.
The ev object represents the user interface of the simulation. You can send debugging output to ev with
the C++-style output operators:
ev << "packet received, sequence number is " << seqNum << endl;
ev << "queue full, discarding packet\n";
An alternative solution is ev.printf():
ev.printf("packet received, sequence number is %d\n", seqNum);
The exact way messages are displayed to the user depends on the user interface. In the command-line
user interface (Cmdenv), it is simply dumped to the standard output. (This output can also be disabled
from omnetpp.ini so that it doesnt slow down simulation when it is not needed.) In Tkenv, the runtime
GUI, you can open a text output window for every module. It is not recommended that you use printf()
or cout to print messages ev output can be controlled much better from omnetpp.ini and it is more
convenient to view, using Tkenv.
One can save CPU cycles by making logging statements conditional on whether the output actually gets
displayed or recorded anywhere. The ev.disabled() call returns true when ev output is disabled,
such as in Tkenv or Cmdenv express mode. Thus, one can write code like this:
if (!ev.disabled())
ev << "Packet " << msg->name() << " received\n";
A more sophisticated implementation of the same idea is to define an EV macro which can be used in
logging statements instead of ev. The definition:
#define EV
ev.disabled()?std::cout:ev
6.3
Simulation time is represented by the type simtime_t which is a typedef to double. OMNeT++ provides
utility functions, which convert simtime_t to a printable string ("3s 130ms 230us") and vica versa.
The simtimeToStr() function converts a simtime_t (passed in the first argument) to textual form. The
result is placed into the char array pointed to by the second argument. If the second argument is omitted
or it is NULL, simtimeToStr() will place the result into a static buffer which is overwritten with each
call. An example:
char buf[32];
ev.printf("t1=%s, t2=%s\n", simtimeToStr(t1), simTimeToStr(t2,buf));
The simtimeToStrShort() is similar to simtimeToStr(), but its output is more concise.
The strToSimtime() function parses a time specification passed in a string, and returns a simtime_t.
If the string cannot be entirely interpreted, -1 is returned.
simtime_t t = strToSimtime("30s 152ms");
Another variant, strToSimtime0() can be used if the time string is a substring in a larger string.
Instead of taking a char*, it takes a reference to char* (char*&) as the first argument. The function
sets the pointer to the first character that could not be interpreted as part of the time string, and returns
the value. It never returns -1; if nothing at the beginning of the string looked like simulation time, it
returns 0.
const char *s = "30s 152ms and something extra";
simtime_t t = strToSimtime0(s); // now s points to "and something extra"
6.4
Random numbers in simulation are never random. Rather, they are produced using deteministic algorithms. Algorithms take a seed value and perform some deterministic calculations on them to produce
a random number and the next seed. Such algorithms and their implementations are called random
number generators or RNGs, or sometimes pseudo random number generators or PRNGs to highlight
their deterministic nature. 1
Starting from the same seed, RNGs always produce the same sequence of random numbers. This is a
useful property and of great importance, because it makes simulation runs repeatable.
RNGs produce uniformly distributed integers in some range, usually between 0 or 1 and 232 or so. Mathematical transformations are used to produce random variates from them that correspond to specific
distributions.
6.4.1
Mersenne Twister
By default, OMNeT++ uses the Mersenne Twister RNG (MT) by M. Matsumoto and T. Nishimura [MN98].
MT has a period of 219937 1, and 623-dimensional equidistribution property is assured. MT is also very
fast: as fast or faster than ANSI Cs rand().
1 There are real random numbers as well, see e.g. http://www.random.org/, http://www.comscire.com, or the Linux /dev/random
device. For non-random numbers, try www.noentropy.net.
107
6.4.2
Simulation programs may consume random numbers from several streams, that is, from several independent RNG instances. For example, if a network simulation uses random numbers for generating packets
and for simulating bit errors in the transmission, it might be a good idea to use different random streams
for both. Since the seeds for each stream can be configured independently, this arrangement would allow
you to perform several simulation runs with the same traffic but with bit errors occurring in different
places. A simulation technique called variance reduction is also related to the use of different random
number streams.
It is also important that different streams and also different simulation runs use non-overlapping series of random numbers. Overlap in the generated random number sequences can introduce unwanted
correlation in your results.
The number of random number streams as well as seeds for the individual streams can be configured in
omnetpp.ini (section 8.6). For the "minimal standard RNG", the seedtool program can be used for
selecting good seeds (section 8.6.6).
In OMNeT++, streams are identified with RNG numbers. The RNG numbers used in simple modules may
be arbitrarily mapped to the actual random number streams (actual RNG instances) from omnetpp.ini
(section 8.6). The mapping allows for great flexibility in RNG usage and random number streams configuration even for simulation models which were not written with RNG awareness.
6.4.3
The intrand(n) function generates random integers in the range [0, n 1], and dblrand() generates a
random double on [0, 1). These functions simply wrap the underlying RNG objects. Examples:
int dice = 1 + intrand(6); // result of intrand(6) is in the range 0..5
double p = dblrand();
// dblrand() produces numbers in [0,1)
They also have a counterparts that use generator k:
108
6.4.4
Random variates
The following functions are based on dblrand() and return random variables of different distributions:
Random variate functions use one of the random number generators (RNGs) provided by OMNeT++. By
default this is generator 0, but you can specify which one to be used.
OMNeT++ has the following predefined distributions:
Function
Description
Continuous distributions
uniform(a, b, rng=0)
uniform distribution in the range [a,b)
exponential(mean, rng=0)
exponential distribution with the given mean
normal(mean, stddev, rng=0)
normal distribution with the given mean and
standard deviation
truncnormal(mean, stddev,
normal distribution truncated to nonnegative
rng=0)
values
gamma_d(alpha, beta, rng=0)
gamma distribution with parameters alpha>0,
beta>0
beta(alpha1, alpha2, rng=0)
beta distribution with parameters alpha1>0,
alpha2>0
erlang_k(k, mean, rng=0)
Erlang distribution with k>0 phases and the
given mean
chi_square(k, rng=0)
chi-square distribution with k>0 degrees of
freedom
student_t(i, rng=0)
student-t distribution with i>0 degrees of freedom
cauchy(a, b, rng=0)
Cauchy distribution with parameters a,b
where b>0
triang(a, b, c, rng=0)
triangular distribution with parameters
a<=b<=c, a!=c
lognormal(m, s, rng=0)
lognormal distribution with mean m and variance s>0
weibull(a, b, rng=0)
Weibull distribution with parameters a>0, b>0
pareto_shifted(a, b, c, rng=0)
generalized Pareto distribution with parameters a, b and shift c
Discrete distributions
intuniform(a, b, rng=0)
uniform integer from a..b
bernoulli(p, rng=0)
result of a Bernoulli trial with probability
0<=p<=1 (1 with probability p and 0 with probability (1-p))
109
binomial(n, p, rng=0)
geometric(p, rng=0)
negbinomial(n, p, rng=0)
poisson(lambda, rng=0)
They are the same functions that can be used in NED files. intuniform() generates integers including both the lower and upper limit, so for example the outcome of tossing a coin could be written as
intuniform(1,2). truncnormal() is the normal distribution truncated to nonnegative values; its implementation generates a number with normal distribution and if the result is negative, it keeps generating
other numbers until the outcome is nonnegative.
If the above distributions do not suffice, you can write your own functions. If you register your functions
with the Register_Function() macro, you can use them in NED files and ini files too.
6.4.5
You can also specify your distribution as a histogram. The cLongHistogram, cDoubleHistogram,
cVarHistogram, cKSplit or cPSquare classes are there to generate random numbers from equidistant-cell or equiprobable-cell histograms. This feature is documented later, with the statistical classes.
6.5
Container classes
6.5.1
Basic usage
cQueue is a container class that acts as a queue. cQueue can hold objects of type derived from cObject
(almost all classes from the OMNeT++ library), such as cMessage, cPar, etc. Internally, cQueue uses a
double-linked list to store the elements.
A queue object has a head and a tail. Normally, new elements are inserted at its head and elements are
removed at its tail.
The basic cQueue member functions dealing with insertion and removal are insert() and pop(). They
are used like this:
cQueue queue("my-queue");
110
6.5.2
Basic usage
cArray is a container class that holds objects derived from cObject. cArray stores the pointers of the
objects inserted instead of making copies. cArray works as an array, but it grows automatically when
it gets full. Internally, cArray is implemented with an array of pointers; when the array fills up, it is
reallocated.
cArray objects are used in OMNeT++ to store parameters attached to messages, and internally, for storing
module parameters and gates.
Creating an array:
cArray array("array");
Adding an object at the first free index:
cPar *p = new cPar("par");
int index = array.add( p );
Adding an object at a given index (if the index is occupied, youll get an error message):
cPar *p = new cPar("par");
int index = array.addAt(5,p);
Finding an object in the array:
int index = array.find(p);
Getting a pointer to an object at a given index:
cPar *p = (cPar *) array[index];
You can also search the array or get a pointer to an object by the objects name:
int index = array.find("par");
Par *p = (cPar *) array["par"];
You can remove an object from the array by calling remove() with the object name, the index position or
the object pointer:
array.remove("par");
array.remove(index);
array.remove( p );
The remove() function doesnt deallocate the object, but it returns the object pointer. If you also want to
deallocate it, you can write:
delete array.remove( index );
112
6.6
Module parameters (as discussed in section 4.7) are represented as cPar objects. The module parameter
name is the cPar objects name, and the object can store any parameter type supported by the NED
language, that is, numeric (long or double), bool, string and XML config file reference. 2
Module parameters are accessed via cModules par() method:
cPar& par(const char *parameterName);
6.6.1
113
6.6.2
There are many ways to set a cPars value. One is the set...Value() member functions:
cPar& foo = par("foo");
foo.setLongValue(12);
foo.setDoubleValue(2.7371);
foo.setStringValue("one two three");
There are also overloaded assignment operators for C++ primitive types, const char *, and cXMLElement
*.
cPar
pp =
pp =
pp =
pp("pp");
12;
2.7371;
"one two three";
The cPar object makes its own copy of the string, so the original one does not need to be preserved. Short
strings (less than 20 chars) are handled more efficiently because they are stored in the objects memory
space (and are not dynamically allocated).
cPar can also store other types which yield numeric results such as function with constant args; they will
be mentioned in the next section.
For numeric and string types, an input flag can be set. In this case, when the objects value is first used,
the parameter value will be searched for in the configuration (ini) file; if it is not found there, the user
will be offered to enter the value interactively.
Examples:
cPar foo("foo");
foo.setPrompt("Enter foo value:");
foo.setInput(true);
// make it an input parameter
double d = (double)foo; // the user will be prompted HERE
Further set..() functions to assign other storage types, e.g. double function with constant args (MathFuncNoArgs, MathFunc1Args, etc), reverse Polish expression, compiled expressions based on cDoubleExpression, random distribution based on a cStatistics random() method, pointer to cObject, etc.
are listed in the next section; however, they are rarely useful for programming simulation models.
6.6.3
cPar supports the basic data types (long, double, bool, string, XML) via several storage types. Storage
types are internally identified by type characters. The type character is returned by the type() method.
Example:
cPar par = 10L;
char typechar = par.type(); // returns storage type L
The all cPar data types and are summarized in the table below. The isNumeric() function tells whether
the object stores a data types which allows the doubleValue() method to be called.
Type Storage Member functions
char type
Description
114
string
setStringValue(
const char *);
const char *
stringValue();
op const char *();
op=(const char *);
boolean setBoolValue(bool);
bool boolValue();
op bool();
op=(bool);
long
setLongValue(long);
int
long longValue();
op long();
op=(long);
double
setDoubleValue(double);
double doubleValue();
op double();
op=(double);
function setDoubleValue(
MathFunc,
[double],
[double],
[double]);
double doubleValue();
op double();
expr.
compiled setDoubleValue(
expr.
cDoubleExpression *expr);
double doubleValue();
op double();
distrib. setDoubleValue(
cStatistic*);
double doubleValue();
op double();
XML
setDoubleValue(
cPar::ExprElem*,int);
double doubleValue();
op double();
setXMLValue(
cXMLElement *node);
cXMLElement *xmlValue();
op cXMLElement*();
115
6.7
6.7.1
void*
pointer
setPointerValue(void*);
void *pointerValue();
op void *();
op=(void *);
object
pointer
indirect
value
setObjectValue(cObject*);
cObject *objectValue();
op cObject *();
op=(cObject *);
setRedirection(cPar*);
bool isRedirected();
cPar *redirection();
cancelRedirection();
The cTopology class was designed primarily to support routing in telecommunication or multiprocessor
networks.
A cTopology object stores an abstract representation of the network in graph form:
each cTopology node corresponds to a module (simple or compound), and
each cTopology edge corresponds to a link or series of connecting links.
You can specify which modules (either simple or compound) you want to include in the graph. The graph
will include all connections among the selected modules. In the graph, all nodes are at the same level,
theres no submodule nesting. Connections which span across compound module boundaries are also
represented as one graph edge. Graph edges are directed, just as module gates are.
If youre writing a router or switch model, the cTopology graph can help you determine what nodes
are available through which gate and also to find optimal routes. The cTopology object can calculate
shortest paths between nodes for you.
The mapping between the graph (nodes, edges) and network model (modules, gates, connections) is preserved: you can easily find the corresponding module for a cTopology node and vica versa.
6.7.2
Basic usage
You can extract the network topology into a cTopology object by a single function call. You have several
ways to select which modules you want to include in the topology:
by module type
by a parameters presence and its value
with a user-supplied boolean function
116
6.7.3
Shortest paths
The real power of cTopology is in finding shortest paths in the network to support optimal routing.
cTopology finds shortest paths from all nodes to a target node. The algorithm is computationally inexpensive. In the simplest case, all edges are assumed to have the same weight.
A real-life example when we have the target module pointer, finding the shortest path looks like this:
cModule *targetmodulep =...;
cTopology::Node *targetnode = topo.nodeFor( targetmodulep );
topo.unweightedSingleShortestPathsTo( targetnode );
This performs the Dijkstra algorithm and stores the result in the cTopology object. The result can then
be extracted using cTopology and cTopology::Node methods. Naturally, each call to unweightedSingleShortestPathsTo() overwrites the results of the previous call.
Walking along the path from our module to the target node:
cTopology::Node *node = topo.nodeFor( this );
if (node == NULL)
{
ev < "We (" << fullPath() << ") are not included in the topology.\n";
}
else if (node->paths()==0)
118
6.8
6.8.1
There are several statistic and result collection classes: cStdDev, cWeightedStdDev, LongHistogram,
cDoubleHistogram, cVarHistogram, cPSquare and cKSplit. They are all derived from the abstract
base class cStatistic.
cStdDev keeps number of samples, mean, standard deviation, minimum and maximum value etc.
cWeightedStdDev is similar to cStdDev, but accepts weighted observations. cWeightedStdDev
can be used for example to calculate time average. It is the only weighted statistics class.
cLongHistogram and cDoubleHistogram are descendants of cStdDev and also keep an approximation of the distribution of the observations using equidistant (equal-sized) cell histograms.
cVarHistogram implements a histogram where cells do not need to be the same size. You can
manually add the cell (bin) boundaries, or alternatively, automatically have a partitioning created
where each bin has the same number of observations (or as close to that as possible).
cPSquare is a class that uses the P 2 algorithm described in [JC85]. The algorithm calculates quantiles without storing the observations; one can also think of it as a histogram with equiprobable
cells.
cKSplit uses a novel, experimental method, based on an adaptive histogram-like algorithm.
Basic usage
One can insert an observation into a statistic object with the collect() function or the += operator (they
are equivalent). cStdDev has the following methods for getting statistics out of the object: samples(),
min(), max(), mean(), stddev(), variance(), sum(), sqrSum() with the obvious meanings. An
example usage for cStdDev:
cStdDev stat("stat");
for (int i=0; i<10; i++)
stat.collect( normal(0,1) );
long numSamples = stat.samples();
double smallest = stat.min(),
largest = stat.max();
double mean = stat.mean(),
standardDeviation = stat.stddev(),
variance = stat.variance();
6.8.2
Distribution estimation
After the cells have been set up, collection can go on.
The transformed() function returns true when the cells have already been set up. You can force range
estimation and setting up the cells by calling the transform() function.
The observations that fall outside the histogram range will be counted as underflows and overflows. The
number of underflows and overflows are returned by the underflowCell() and overflowCell() member functions.
6.8.3
Purpose
The k-split algorithm is an on-line distribution estimation method. It was designed for on-line result
collection in simulation programs. The method was proposed by Varga and Fakhamzadeh in 1997. The
123
Figure 6.5: Illustration of the k-split algorithm, k = 2. The numbers in boxes represent the observation
count values
For density estimation, the total number of observations that fell into each cell of the partition has to be
determined. For this purpose, mother observations in each internal node of the tree must be distributed
among its child cells and propagated up to the leaves.
Let n...,i be the (mother) observation count for a cell, s...,i be the total observation count in a cell n...,i plus
the observation counts in all its sub-, sub-sub-, etc. cells), and m...,i the mother observations propagated
to the cell. We are interested in the n
...,i = n...,i +m...,i estimated amount of observations in the tree nodes,
especially in the leaves. In other words, if we have n
...,i estimated observation amount in a cell, how to
divide it to obtain m...,i,1 , m...,i,2 m...,i,k that can be propagated to child cells. Naturally, m...,i,1 + m...,i,2 +
+ m...,i,k = n
...,i .
Two natural distribution methods are even distribution (when m...,i,1 = m...,i,2 = = m...,i,k ) and proportional distribution (when m...,i,1 : m...,i,2 : : m...,i,k = s...,i,1 : s...,i,2 : : s...,i,k ). Even distribution is
optimal when the s...,i,j values are very small, and proportional distribution is good when the s...,i,j values
are large compared to m...,i,j . In practice, a linear combination of them seems appropriate, where = 0
means even and = 1 means proportional distribution:
m,i,j = (1 )
n,i /k +
n,i s...,i,j /s,i where [0, 1]
Note that while n...,i are integers, m...,i and thus n
...,i are typically real numbers. The histogram estimate
calculated from k-split is not exact, because the frequency counts calculated in the above manner contain
a degree of estimation themselves. This introduces a certain cell division error; the parameter should
be selected so that it minimizes that error. It has been shown that the cell division error can be reduced
to a more-than-acceptable small value.
124
Figure 6.6: Density estimation from the k-split cell tree. We assume = 0, i.e. we distribute mother
observations evenly.
Strictly speaking, the k-split algorithm is semi-online, because its needs some observations to set up the
initial histogram range. Because of the range extension and cell split capabilities, the algorithm is not
very sensitive to the choice of the initial range, so very few observations are sufficient for range estimation
(say Npre = 10). Thus we can regard k-split as an on-line method.
K-split can also be used in semi-online mode, when the algorithm is only used to create an optimal partition from a larger number of Npre observations. When the partition has been created, the observation
counts are cleared and the Npre observations are fed into k-split once again. This way all mother (nonleaf) observation counts will be zero and the cell division error is eliminated. It has been shown that the
partition created by k-split can be better than both the equi-distant and the equal-frequency partition.
OMNeT++ contains an experimental implementation of the k-split algorithm, the cKSplit class. Research on k-split is still under way.
The cKSplit class
The cKSplit class is an implementation of the k-split method. Member functions:
void setCritFunc(KSplitCritFunc _critfunc, double *_critdata);
void setDivFunc(KSplitDivFunc \_divfunc, double *\_divdata);
void rangeExtension( bool enabled );
int treeDepth();
int treeDepth(sGrid& grid);
double realCellValue(sGrid& grid, int cell);
void printGrids();
sGrid& grid(int k);
sGrid& rootGrid();
struct sGrid
{
int parent;
int reldepth;
long total;
int mother;
int cells[K];
};
//
//
//
//
//
125
6.8.4
In many simulations, only the steady state performance (i.e. the performance after the system has reached
a stable state) is of interest. The initial part of the simulation is called the transient period. After the
model has entered steady state, simulation must proceed until enough statistical data has been collected
to compute result with the required accuracy.
Detection of the end of the transient period and a certain result accuracy is supported by OMNeT++.
The user can attach transient detection and result accuracy objects to a result object (cStatistics
descendants). The transient detection and result accuracy objects will do the specific algorithms on the
data fed into the result object and tell if the transient period is over or the result accuracy has been
reached.
The base classes for classes implementing specific transient detection and result accuracy detection algorithms are:
cTransientDetection: base class for transient detection
cAccuracyDetection: base class for result accuracy detection
Basic usage
Attaching detection objects to a cStatistic and getting pointers to the attached objects:
addTransientDetection(cTransientDetection *object);
addAccuracyDetection(cAccuracyDetection *object);
cTransientDetection *transientDetectionObject();
cAccuracyDetection *accuracyDetectionObject();
Detecting the end of the period:
polling the detect() function of the object
installing a post-detect function
Transient detection
Currently one transient detection algorithm is implemented, i.e. theres one class derived from cTransientDetection. The cTDExpandingWindows class uses the sliding window approach with two windows, and checks the difference of the two averages to see if the transient period is over.
void setParameters(int reps=3,
int minw=4,
double wind=1.3,
double acc=0.3);
Accuracy detection
Currently one accuracy detection algorithm is implemented, i.e. theres one class derived from cAccuracyDetection. The algorithm implemented in the cADByStddev class is: divide the standard deviation
by the square of the number of values and check if this is small enough.
void setParameters(double acc=0.1, int reps=3);
126
6.9
6.9.1
Objects of type cOutVector are responsible for writing time series data (referred to as output vectors) to
a file. The record() method is used to output a value (or a value pair) with a timestamp. The object
name will serve as the name of the output vector.
The vector name can be passed in the constructor,
but in the usual arrangement youd make the cOutVector a member of the module class and set the
name in initialize(). Youd record values from handleMessage() or from a function called from
handleMessage().
The following example is a Sink module which records the lifetime of every message that arrives to it.
There is also a recordWithTimestamp() methodN ew! , to make it possible to record values into output
vectors with a timestamp other than simTime(). Increasing timestamp order is still enforced though.
All cOutVector objects write to a single output vector file named omnetpp.vec by default. You can
configure output vectors from omnetpp.ini: you can disable writing to the file, or limit it to a certain
simulation time interval for recording (section 8.5).
The format and processing of output vector files is described in section 10.1.
If the output vector object is disabled or the simulation time is outside the specified interval, record()
doesnt write anything to the output file. However, if you have a Tkenv inspector window open for the
output vector object, the values will be displayed there, regardless of the state of the output vector object.
127
6.9.2
Output scalars
While output vectors are to record time series data and thus they typically record a large volume of data
during a simulation run, output scalars are supposed to record a single value per simulation run. You can
use output scalars
to record summary data at the end of the simulation run
to do several runs with different parameter settings/random seed and determine the dependence of
some measures on the parameter settings. For example, multiple runs and output scalars are the
way to produce Throughput vs. Offered Load plots.
Output scalars are recorded with the recordScalar() method of cSimpleModule, and youll usually
want to insert this code into the finish() function. An example:
void Transmitter::finish()
{
double avgThroughput = totalBits / simTime();
recordScalar("Average throughput", avgThroughput);
}
You can record whole statistics objects by calling their recordScalar() methods, declared as part of
cStatistic. In the following example we create a Sink module which calculates the mean, standard
deviation, minimum and maximum values of a variable, and records them at the end of the simulation.
class Sink : public cSimpleModule
{
protected:
cStdDev eedStats;
virtual void initialize();
virtual void handleMessage(cMessage *msg);
virtual void finish();
};
Define_Module(Sink);
void Sink::initialize()
{
eedStats.setName("End-to-End Delay");
}
void Sink::handleMessage(cMessage *msg)
{
simtime_t eed = simTime() - msg->creationTime();
eedStats.collect(eed);
delete msg;
}
void Sink::finish()
{
recordScalar("Simulation duration", simTime());
eedStats.recordScalar();
}
128
6.9.3
PrecisionN ew!
Output scalar and output vector files are text files, and floating point values (doubles) are recorded
into it using fprintf()s "%g" format. The number of significant digits can be configured using the
output-scalar-precision= and output-vector-precision= configuration entries (see 8.2.6). The
default precision is 12 digits. The following has to be considered when changing the default value:
IEEE-754 doubles are 64-bit numbers. The mantissa is 52 bits, which is roughly equivalent to 16 decimal
places (52*log(2)/log(10)). However, due to rounding errors, usually only 12..14 digits are correct, and the
rest is pretty much random garbage which should be ignored. However, when you convert the decimal
representation back into an IEEE-754 double (as in Plove and Scalars), an additional small error will
occurs because 0.1, 0.01, etc cannot be accurately represented in binary. This conversion error is usually
smaller than the one that the double variable already had before recording into the file, however if it is
important you can eliminate it by setting >16 digits precision for the file (but again, be aware that the last
digits are garbage). The practical upper limit is 17 digits, setting it higher doesnt make any difference in
fprintf()s output.
Errors coming from converting to/from decimal representation can be eliminated by choosing an output vector/output scalar manager class which stores doubles in their native binary form. The appropriate configuration entries are outputvectormanager-class= and outputvectormanager-class=;
see 8.2.6. For example, cMySQLOutputScalarManager and cMySQLOutputScalarManager provided in
samples/database fulfill this requirement.
However, before worrying too much about rounding and conversion errors, it is worth considering what is
the real accuracy of your results. Some things to consider:
in real life, it is very hard to measure quantities (weight, distance, even time) with more than a
few digits of precision. What precision are your input data? For example, if you approximate interarrival time as exponential(0.153) when the mean is really 0.152601... and the distribution is not
even exactly exponential, you are already starting out with a bigger error than rounding can cause.
the simulation model is itself an approximation of real life. How much error do the (known and
unknown) simplifications cause in the results?
6.10
6.10.1
Basic watches
It would be nice, but variables of type int, long, double do not show up by default in Tkenv; neither
do STL classes (std::string, std::vector, etc.) or your own structs and classes. This is because the
simulation kernel, being a library, knows nothing about types and variables in your source code.
OMNeT++ provides WATCH() and set of other macros to come to your rescue, and make variable to be
inspectable in Tkenv and to be output into the snapshot file. WATCH() macros are usually placed into
initialize() (to watch instance variables) or to the top of the activity() function (to watch its local
variables), the point being that they should only be executed once.
long packetsSent;
double idleTime;
129
WATCH(packetsSent);
WATCH(idleTime);
Of course, members of classes and structs can also be watched:
WATCH(config.maxRetries);
When you open an inspector for the simple module in Tkenv and click the Objects/Watches tab in it, youll
see your watched variables and their values there. Tkenv also lets you change the value of a watched
variable.
The WATCH() macro can be used with any type that has a stream output operator (operator) definedN ew! .
By default, this includes all primitive types and std::string, but since you can write operator for
your classes/structs and basically any type, WATCH() can be used with anything. The only limitation
is that since the output should more or less fit on single line, the amount of information that can be
conveniently displayed is limited.
An example stream output operator:
std::ostream& operator<<(std::ostream& os, const ClientInfo& cli)
{
os << "addr=" << cli.clientAddr << " port=" << cli.clientPort; // no endl!
return os;
}
And the WATCH() line:
WATCH(currentClientInfo);
6.10.2
Watches for primitive types and std::string allow for changing the value from the GUI as well, but
for other types you need to explicitly add support for that. What you need to do is define a stream input
operator (operator) and use the WATCH_RW() macro instead of WATCH().
The stream input operator:
std::ostream& operator>>(std::istream& is, ClientInfo& cli)
{
// read a line from "is" and parse its contents into "cli"
return is;
}
And the WATCH_RW() line:
WATCH_RW(currentClientInfo);
6.10.3
WATCH() and WATCH_RW() are basic watches: they allow one line of (unstructured) text to be displayed.
However, if you have a data structure generated from message definitions (see Chapter 5), then one can
do better. The message compiler automatically generates meta-information describing individual fields of
the class or struct, which makes it possible to display the contents on field level.
130
6.10.4
The standard C++ container classes (vector, map, set, etc) also have structured watches, available via
the following macros:
6.10.5
Snapshots
The snapshot() function outputs textual information about all or selected objects of the simulation
(including the objects created in module functions by the user) into the snapshot file.
bool snapshot(cObject *obj = &simulation, const char *label = NULL);
The function can be called from module functions, like this:
snapshot();
// dump the whole network
snapshot(this); // dump this simple module and all its objects
snapshot(&simulation.msgQueue); // dump future events
131
begin
3 (L)
10000 (L)
truncnormal(0.005,0.003) (F)
0.01 (D)
4000000 (L)
1e-06 (D)
[...]
(cQueue) token.comp[0].mac.local-objects.send-queue begin
0-->1
(cMessage) Tarr=0.0158105774 ( 15ms) Src=#4
0-->2
(cMessage) Tarr=0.0163553310 ( 16ms) Src=#4
0-->1
(cMessage) Tarr=0.0205628236 ( 20ms) Src=#4
0-->2
(cMessage) Tarr=0.0242203591 ( 24ms) Src=#4
0-->2
(cMessage) Tarr=0.0300994268 ( 30ms) Src=#4
Dest=#3
Dest=#3
Dest=#3
Dest=#3
Dest=#3
132
(cMessage)
(cMessage)
(cMessage)
(cMessage)
(cMessage)
(cMessage)
Tarr=0.0364005251
Tarr=0.0370745702
Tarr=0.0387984129
Tarr=0.0457462493
Tarr=0.0487308918
Tarr=0.0514466766
(
(
(
(
(
(
36ms)
37ms)
38ms)
45ms)
48ms)
51ms)
Src=#4
Src=#4
Src=#4
Src=#4
Src=#4
Src=#4
Dest=#3
Dest=#3
Dest=#3
Dest=#3
Dest=#3
Dest=#3
6.10.6
Breakpoints
With activity() only! In those user interfaces which support debugging, breakpoints stop execution and
the state of the simulation can be examined.
You can set a breakpoint inserting a breakpoint() call into the source:
for(;;)
{
cMessage *msg = receive();
breakpoint("before-processing");
breakpoint("before-send");
send( reply_msg, "out" );
//..
}
In user interfaces that do not support debugging, breakpoint() calls are simply ignored.
6.10.7
It is important to choose the correct stack size for modules. If the stack is too large, it unnecessarily
consumes memory; if it is too small, stack violation occurs.
From the Feb99 release, OMNeT++ contains a mechanism that detects stack overflows. It checks the
intactness of a predefined byte pattern (0xdeadbeef) at the stack boundary, and reports stack violation
if it was overwritten. The mechanism usually works fine, but occasionally it can be fooled by large and
133
6.11
6.11.1
cObject or not?
If you plan to implement a completely new class (as opposed to subclassing something already present
in OMNeT++), you have to ask yourself whether you want the new class to be based on cObject or not.
Note that we are not saying you should always subclass from cObject. Both solutions have advantages
and disadvantages, which you have to consider individually for each class.
cObject already carries (or provides a framework for) significant functionality that is either relevant to
your particular purpose or not. Subclassing cObject generally means you have more code to write (as
you have to redefine certain virtual functions and adhere to conventions) and your class will be a bit more
heavy-weight. However, if you need to store your objects in OMNeT++ objects like cQueue, or youll want
to store OMNeT++ classes in your object, then you must subclass from cObject. 5
The most significant features cObject has is the name string (which has to be stored somewhere, so it
has its overhead) and ownership management (see section 6.12) which also has the advantages but also
some costs.
As a general rule, small struct-like classes like IPAddress, MACAddress, RoutingTableEntry, TCPConnectionDescriptor, etc. are better not sublassed from cObject. If your class has at least one
virtual member function, consider subclassing from cPolymorphic, which does not impose any extra
cost because it doesnt have data members at all, only virtual functions.
6.11.2
Most classes in the simulation class library are descendants of cObject. If you want to derive a new class
from cObject or a cObject descendant, you must redefine some member functions so that objects of the
new type can fully co-operate with other parts of the simulation system. A more or less complete list of
these functions is presented here. You do not need to worry about the length of the list: most functions
are not absolutely necessary to implement. For example, you do not need to redefine forEachChild()
unless your class is a container class.
The following methods must be implemented:
Constructor. At least two constructors should be provided: one that takes the object name string
4 The
5 For
134
6.11.3
Class registration
You should also use the Register_Class() macro to register the new class. It is used by the createOne() factory function, which can create any object given the class name as a string. createOne() is
used by the Envir library to implement omnetpp.ini options such as rng-class="..." or schedulerclass="...". (see Chapter 13)
For example, an omnetpp.ini entry such as
rng-class="cMersenneTwister"
would result in something like the following code to be executed for creating the RNG objects:
cRNG *rng = check_and_cast<cRNG*>(createOne("cMersenneTwister"));
But for that to work, we needed to have the following line somewhere in the code:
135
6.11.4
Details
Well go through the details using an example. We create a new class NewClass, redefine all above mentioned cObject member functions, and explain the conventions, rules and tips associated with them. To
demonstrate as much as possible, the class will contain an int data member, dynamically allocated noncObject data (an array of doubles), an OMNeT++ object as data member (a cQueue), and a dynamically
allocated OMNeT++ object (a cMessage).
The class declaration is the following. It contains the declarations of all methods discussed in the previous
section.
//
// file: NewClass.h
//
#include <omnetpp.h>
class NewClass : public cObject
{
protected:
int data;
double *array;
cQueue queue;
cMessage *msg;
...
public:
NewClass(const char *name=NULL, int d=0);
NewClass(const NewClass& other);
virtual ~NewClass();
virtual cPolymorphic *dup() const;
NewClass& operator=(const NewClass& other);
virtual void forEachChild(cVisitor *v);
virtual std::string info();
};
Well discuss the implementation method by method. Heres the top of the .cc file:
//
// file:
//
#include
#include
#include
#include
NewClass.cc
<stdio.h>
<string.h>
<iostream.h>
"newclass.h"
Register_Class( NewClass );
137
6.12
6.12.1
OMNeT++ has a built-in ownership management mechanism which is used for sanity checks, and as part
of the infrastructure supporting Tkenv inspectors.
Container classes like cQueue own the objects inserted into them. But this is not limited to objects
inserted into a container: every cObject-based object has an owner all the time. From the users point
of view, ownership is managed transparently. For example, when you create a new cMessage, it will be
owned by the simple module. When you send it, it will first be handed over to (i.e. change owership to) the
FES, and, upon arrival, to the destination simple module. When you encapsulate the message in another
one, the encapsulating message will become the owner. When you decapsulate it again, the currently
active simple module becomes the owner.
The owner() method, defined in cObject, returns the owner of the object:
cObject *o = msg->owner();
ev << "Owner of " << msg->name() << " is: " <<
<< "(" << o->className() << ") " << o->fullPath() << endl;
The other direction, enumerating the objects owned can be implemented with the forEachChild()
method by it looping through all contained objects and checking the owner of each object.
Why do we need this?
The traditional concept of object ownership is associated with the right to delete objects. In addition to
that, keeping track of the owner and the list of objects owned also serves other purposes in OMNeT++:
enables methods like fullPath() to be implemented.
prevents certain types of programming errors, namely, those associated with wrong ownership handling.
enables Tkenv to display the list of simulation objects present within a simple module. This is
extremely useful for finding memory leaks caused by forgetting to delete messages that are no longer
needed.
Some examples of programming errors that can be caught by the ownership facility:
attempts to send a message while it is still in a queue, encapsulated in another message, etc.
attempts to send/schedule a message while it is still owned by the simulation kernel (i.e. scheduled
as a future event)
attempts to send the very same message object to multiple destinations at the same time (ie. to all
connected modules)
For example, the send() and scheduleAt() functions check that the message being sent/scheduled
must is owned by the module. If it is not, then it signals a programming error: the message is probably
owned by another module (already sent earlier?), or currently scheduled, or inside a queue, a message or
some other object in either case, the module does not have any authority over it. When you get the error
message ("not owner of object"), you need to carefully examine the error message: which object has
the ownership of the message, whys that, and then probably youll need to fix the logic somewhere in your
program.
139
6.12.2
Managing ownership
Ownership is managed transparently for the user, but this mechanism has to be supported by the participating classes themselves. It will be useful to look inside cQueue and cArray, because they might give
you a hint what behavior you need to implement when you want to use non-OMNeT++ container classes
to store messages or other cObject-based objects.
Insertion
cArray and cQueue have internal data structures (array and linked list) to store the objects which are
inserted into them. However, they do not necessarily own all of these objects. (Whether they own an object
or not can be determined from that objects owner() pointer.)
The default behaviour of cQueue and cArray is to take ownership of the objects inserted. This behavior
can be changed via the takeOwnership flag.
Heres what the insert operation of cQueue (or cArray) does:
insert the object into the internal array/list data structure
if the takeOwnership flag is true, take ownership of the object, otherwise just leave it with its original
owner
The corresponding source code:
void cQueue::insert(cObject *obj)
{
// insert into queue data structure
...
// take ownership if needed
if (takeOwnership())
take(obj);
}
Removal
Heres what the remove family of operations in cQueue (or cArray) does:
remove the object from the internal array/list data structure
if the object is actually owned by this cQueue/cArray, release ownership of the object, otherwise
just leave it with its current owner
After the object was removed from a cQueue/cArray, you may further use it, or if it is not needed any
more, you can delete it.
The release ownership phrase requires further explanation. When you remove an object from a queue
or array, the ownership is expected to be transferred to the simple modules local objects list. This is
140
6 Actual
code in src/sim is structured somewhat differently, but the meaning is the same.
141
142
Chapter 7
Overview
As it was already mentioned, an OMNeT++ model physically consists of the following parts:
NED language topology description(s). These are files with the .ned suffix.
Message definitions, in files with .msg suffix.
Simple modules implementations and other C++ code, in .cc files (or .cpp, on Windows)
To build an executable simulation program, you first need to translate the NED files and the message
files into C++, using the NED compiler (nedtool) and the message compiler (opp_msgc). After this step,
the process is the same as building any C/C++ program from source: all C++ sources need to be compiled
into object files (.o files on Unix/Linux, and .obj on Windows), and all object files need to be linked with
the necessary libraries to get an executable.
File names for libraries differ for Unix/Linux and for Windows, and also different for static and shared
libraries. Let us suppose you have a library called Tkenv. On a Unix/Linux system, the file name for the
static library would be something like libtkenv.a (or libtkenv.a.<version>), and the shared library
would be called libtkenv.so (or libtkenv.so.<version>). The Windows version of the static library
would be tkenv.lib, and the DLL (which is the Windows equivalent of shared libraries) would be a file
named tkenv.dll.
Youll need to link with the following libraries:
The simulation kernel and class library, called sim_std (file libsim_std.a, sim_std.lib, etc).
User interfaces. The common part of all user interfaces is the envir library (file libenvir.a, etc),
and the specific user interfaces are tkenv and cmdenv (libtkenv.a, libcmdenv.a, etc). You have
to link with envir, plus either tkenv or cmdenv.
Luckily, you do not have to worry about the above details, because automatic tools like opp_makemake
will take care of the hard part for you.
The following figure gives an overview of the process of building and running simulation programs.
This section discusses how to use the simulation system on the following platforms:
Unix with gcc (also Windows with Cygwin or MinGW)
MSVC 6.0 on Windows
143
7.2
This section applies to using OMNeT++ on Linux, Solaris, FreeBSD and other Unix derivatives, and also
more or less to Cygwin and MinGW on Windows.
Here in the manual we can give you a rough overview only. The doc/ directory of your OMNeT++ installation contains Readme.<platform> files that provide up-to-date, more detailed and more precise instructions.
7.2.1
Installation
The installation process depends on what distribution you take (source, precompiled RPM, etc.) and it
may change from release to release, so it is better to refer to the readme files. If you compile from source,
you can expect the usual GNU procedure: ./configure followed by make.
7.2.2
The opp_makemake script can automatically generate the Makefile for your simulation program, based
on the source files in the current directory. (It can also handle large models which are spread across
several directories; this is covered later in this section.)
opp_makemake has several options, with the -h option it displays a summary.
144
depend
clean
makefiles
makefile-ins
Action
The default target is to build the simulation executable
Adds (or refreshes) dependencies in the Makefile
Deletes all files that were produced by the make
process
Regenerates the Makefile using opp_makemake
(this is useful if e.g. after upgrading OMNeT++, if
opp_makemake has changed)
Similar to make makefiles, but it regenerates
the Makefile.in instead
If you already had a Makefile in that directory, opp_makemake will refuse to overwrite it. You can force
overwriting the old Makefile with the -f option:
% opp_makemake -f
If you have problems, check the path definitions (locations of include files and libraries etc.) in the configure script and correct them if necessary. Then re-run configure for the changes to take effect.
You can specify the user interface (Cmdenv/Tkenv) with the -u option (with no -u, Tkenv is the default):
% opp_makemake -u Tkenv
Or:
% opp_makemake -u Cmdenv
The name of the output file is set with the -o option (the default is the name of the directory):
% opp_makemake -o fddi-net
If some of your source files are generated from other files (for example, you use generated NED files),
write your make rules into a file called makefrag. When you run opp_makemake, it will automatically
insert makefrag into the resulting makefile. With the -i option, you can also name other files to be
included into Makefile.
If you want better portability for your models, you can generate Makefile.in instead of Makefile with
opp_makemakes -m option. You can then use autoconf-like configure scripts to generate the Makefile.
145
7.2.3
Multi-directory models
In the case of a large project, your source files may be spread across several directories. You have to
decide whether you want to use static linking, shared or run-time loaded (shared) libraries. Here we
discuss static linking.
In every subdirectory which contains source files, (say app/ and routing/), run
opp_makemake -n
The -n option means no linking is necessary, only compiling has to be done.
In non-leaf directories, run
opp_makemake -r -n
The -r option enables recursive make: when you build the simulation, make will descend into the subdirectories and runs make in them too. By default, -r decends into all subdirectories; the -X directory option
can be used to make it ignore certain subdirectories.
You may need to use the -I option if you include files from other directories. The -I option is for both C++
and NED files. In our example, you could run
opp_makemake -n -I../routing
in the app/ directory, and vice versa.
To build an executable, the -w option can be used; it causes a simulation executable to be built using all
object files from the include (-I) directories:
opp_makemake -w -I../routing -I../app
You can affect build order by adding dependencies among subdirectories into the makefrag (makefrag.vc)
file.
For a complex example of using opp_makemake, check the Makefiles of the INET Framework, or rather,
the makemake script (and makemake.bat file) which contain the commands to generate the makefiles.
7.2.4
Default linking uses the shared libraries. One reason you would want static linking is that debugging
the OMNeT++ class library is more trouble with shared libraries. Another reason might be that you want
to run the executable on another machine without having to worry about setting the LD_LIBRARY_PATH
variable (which should contain the name of the directory where the OMNeT++ shared libraries are).
If you want static linking, find the
build_shared_libs=yes
line in the configure.user script and change it to
build_shared_libs=no
Then you have to re-run the configure script and rebuild everything:
./configure
make clean
make
146
7.3
This is only a rough overview. Up-to-date, more detailed and more precise instructions can be found in
the doc/ directory of your OMNeT++ installation, in the file Readme.MSVC.
7.3.1
Installation
It is easiest to start with the binary, installer version. It contains all necessary software except MSVC,
and you can get a working system up and running very fast.
Later youll probably want to download and build the source distribution too. Reasons for that might be to
compile the libraries with different flags, to debug into them, or to recompile with support for additional
packages (e.g. Akaroa, MPI). Compilation should be painless (it takes a single nmake -f Makefile.vc
command) after you get the different component directories right in configuser.vc. Additional software
needed for the compilation is also described in doc/.
7.3.2
OMNeT++ has an automatic MSVC makefile creator named opp_nmakemake which is probably the easier
way to go. Its usage is very similar to the similarly named tool for Unix.
If you run opp_nmakemake in a directory of model sources, it collects all the names of all source files in
the directory, and creates a makefile from them. The resulting makefile is called Makefile.vc.
To use opp_nmakemake, open a command window (Start menu -> Run... > type cmd), then cd to the
directory of your model and type:
opp_nmakemake
opp_nmakemake has several command-line options, mostly the same as the Unix version.
Then you can build the program by typing:
nmake -f Makefile.vc
The most common problem is that nmake (which is is part of MSVC) cannot be found because it is not
in the path. You can fix this by running vcvars32.bat, which can be found in the MSVC bin directory
(usually C:\Program Files\Microsoft Visual Studio\VC98\Bin).
7.3.3
You can also use the MSVC IDE for development. It is best to start by copying one of the sample simulations.
If you want to use compiled NED files (as opposed to dynamic NED loading, described in section 8.3),
you need to add NED files to the project, with Custom Build Step commands to invoke the NED compiler (nedtool) on them. You also need to add the _n.cc files generated by nedtool to the project.
There is an AddNEDFileToProject macro which performs exactly this task: adding a NED file and the
corresponding _n.cc file, and configuring the Custom Build Step.
Some caveats (please read doc/Readme.MSVC for more!):
how to get the graphical environment. By default, the sample simulations link with Cmdenv if
you rebuild them from the IDE. To change to Tkenv, choose Build|Set active configuration from the
menu, select Debug-Tkenv or Release-Tkenv, then re-link the executable.
147
148
Chapter 8
User interfaces
OMNeT++ simulations can be run under different user interfaces. Currenly, two user interfaces are supported:
Tkenv: Tcl/Tk-based graphical, windowing user interface
Cmdenv: command-line user interface for batch execution
You would typically test and debug your simulation under Tkenv, then run actual simulation experiments
from the command line or shell script, using Cmdenv. Tkenv is also better suited for educational or
demonstration purposes.
Both Tkenv and Cmdenv are provided in the form of a library, and you choose between them by linking
one or the other into your simulation executable. (Creating the executable was described in chapter 7).
Both user interfaces are supported on Unix and Windows platforms.
Common functionality in Tkenv and Cmdenv has been collected and placed into the Envir library, which
can be thought of as the common base class for the two user interfaces.
The user interface is separated from the simulation kernel, and the two parts interact through a welldefined interface. This also means that, if needed, you can write your own user interface or embed an
OMNeT++ simulation into your application without any change to models or the simulation library.
Configuration and input data for the simulation are described in a configuration file usually called omnetpp.ini. Some entries in this file apply to Tkenv or Cmdenv only, other settings are in effect regardless
of the user interface. Both user interfaces accept command-line arguments, too.
The following sections explain omnetpp.ini and the common part of the user interfaces, describe Cmdenv and Tkenv in detail, then go on to specific problems.
8.2
8.2.1
For a start, let us see a simple omnetpp.ini file which can be used to run the Fifo1 sample simulation
under Cmdenv.
149
ev/sec=0
ev/sec=0
ev/sec=60168.5
ev/sec=59808.6
ev/sec=59772.9
ev/sec=60168.5
ev/sec=58754.4
ev/sec=59066.7
ev/sec=59453
ev/sec=58719.9
150
8.2.2
OMNeT++ can execute several simulation runs automatically one after another. If multiple runs are
selected, option settings and parameter values can be given either individually for each run, or together
for all runs, depending in which section the option or parameter appears.
8.2.3
File syntax
The ini file is a text file consisting of entries grouped into different sections. The order of the sections
doesnt matter. Also, if you have two sections with the same name (e.g. [General] occurs twice in the
file), they will be merged.
Lines that start with "#" or ";" are comments, and will be ignored during processing.
Long lines can be broken up using the backslash notation: if the last character of a line is "\", it will be
merged with the next line.
The size of the ini file (the number of sections and entries) is not limited. Currently there is a 1024character limit on the line length, which cannot be increased by breaking up the line using backslashes.
This limit might be lifted in future releases.
Example:
[General]
# this is a comment
foo="this is a single value \
for the foo parameter"
[General] # duplicate sections are merged
bar="belongs to the same section as foo"
8.2.4
File inclusion
OMNeT++ supports including an ini file in another, via the include keyword. This feature allows you to
partition large ini files into logical units, fixed and varying part etc.
An example:
# omnetpp.ini
...
include parameters.ini
include per-run-pars.ini
...
You can also include files from other directories. If the included ini file further includes others, their path
names will be understood as relative to the location of the file which contains the reference, rather than
151
8.2.5
Sections
[OutVectors]
8.2.6
Description
Contains general settings that apply to all simulation runs
and all user interfaces. For details, see section 8.2.6.
Contains per-run settings. These sections may contain any
entries that are accepted in other sections.
Contains Cmdenv-specific settings. For details, see section
8.7.2
Contains Tkenv-specific settings. For details, see section 8.8.2
Contains values for module parameters that did not get a
value (or got input value) inside the NED files. For details,
see section 8.4
Configures recording of output vectors. You can specify filtering by vector names and by simulation time (start/stop recording). For details, see section 8.5
The most important options of the [General] section are the following.
The network option selects the model to be set up and run.
The length of the simulation can be set with the sim-time-limit and the cpu-time-limit options (the usual time units such as ms, s, m, h, etc. can be used).
The output file names can be set with the following options: output-vector-file, outputscalar-file and snapshot-file.
The full list of supported options follows. Almost every one these options can also be put into the [Run
n] sections. Per-run settings have priority over globally set ones.
Name and default value
Description
[General]
ini-warnings = yes
When enabled, OMNeT++ lists the names of
ini file entries for which the default values
were used. This can at times be useful for debugging ini files.
preload-ned-files =
List of NED files to be loaded dynamically (see
8.3).
network =
The name of the network to be simulated.
snapshot-file = omnetpp.sna
Name of the snapshot file. The result of each
snapshot() call will be appended to this file.
output-vector-file = omnetpp.vec
Name of output vector file.
output-scalar-file = omnetpp.sca
Name of output scalar file.
pause-in-sendmsg = no
Only makes sense with step-by-step execution.
If enabled, OMNeT++ will split send() calls to
two steps.
152
sim-time-limit =
cpu-time-limit =
num-rngs = 1
rng-class = "cMersenneTwister"
seed-N-mt =, seed-N-lcg32 =
total-stack-kb =
debug-on-errors = false
load-libs =
perform-gc = false
print-undisposed = true
output-scalar-precision = 12
output-vector-precision = 12
fname-append-host = false
parallel-simulation = false
scheduler-class =
cSequentialScheduler
configuration-class =
cInifile
outputvectormanager-class =
cFileOutputVectorManager
outputscalarmanager-class =
cFileOutputScalarManager
snapshotmanager-class =
cFileSnapshotManager
8.3
Prior to OMNeT++ 3.0, NED files had to be translated into C++ by the NED compiler, compiled and
linked into the simulation program. From OMNeT++ 3.0 up, one can use dynamic NED loading, which
means that a simulation program can load NED files at runtime when it starts compiling NED files into
the simulation program is no longer necessary. This results in more flexibility, and can also save model
development time.
The key is the preload-ned-files= configuration option in the [General] section of omnetpp.ini.
This option should list the names of the NED files to be loaded when the simulation program starts.
Example:
[General]
154
-name *.ned
Moreover, the list file can also contain wildcards, and references to other list files:
transport/tcp/*.ned
transport/udp/*.ned
@moreprotocols.lst
Files given with relative paths are relative to the location of the list file (and not to the current working
directory). That is, the transport directory and moreprotocols.lst in the example above are expected
to be in the same directory as nedfiles.lst, whatever the current working directory is.
It is important to note, that the loaded NED files may contain any number of modules, channel and
any number of networks as well. It does not matter whether you use all or just some of them in the
simulations. You will be able to select any of the networks that occur in the loaded NED files using the
network= omnetpp.ini entry, and as long as every module, channel etc for it has been loaded, network
setup will be successful.
8.4
Simulations get input via module parameters, which can be assigned a value in NED files or in omnetpp.ini
in this order. Since parameters assigned in NED files cannot be overridden in omnetpp.ini, one can think
about them as being hardcoded. In contrast, it is easier and more flexible to maintain module parameter
settings in omnetpp.ini.
In omnetpp.ini, module parameters are referred to by their full paths or hiearchical names. This name
consists of the dot-separated list of the module names (from the top-level module down to the module
containg the parameter), plus the parameter name (see section 6.1.5).
An example omnetpp.ini which sets the numHosts parameter of the toplevel module and the transactionsPerS
parameter of the server module:
[Parameters]
net.numHosts = 15
net.server.transactionsPerSecond = 100
155
8.4.1
Values for module parameters can be placed into the [Parameters] or the [Run 1], [Run 2] etc. sections of the ini file. The run-specific settings take precedence over the overall settings.
Though runs are identified by numbers, you can assign them short descriptive labels, which will get
displayed e.g. in the Tkenv run selection dialog. Just place a description="some text" line under
the [Run x] heading.
An example omnetpp.ini (everything after # is a comment):
[Parameters]
net.numHosts = 15
net.server.transactionsPerSecond = 100
[Run 1]
description="general settings"
# uses settings from the [Parameters] section
[Run 2]
description="higher transaction rate"
net.server.transactionsPerSecond = 150 # overrides the value in [Parameters]
# net.numHosts comes from the [Parameters] section
[Run 3]
description="more hosts and higher transaction rate"
# override both setting in [Parameters]
net.numHosts = 20
net.server.transactionsPerSecond = 150
8.4.2
Models can have a large number of parameters to be configured, and it would be tedious to set them oneby-one in omnetpp.ini. OMNeT++ supports wildcards patterns which allow for setting several model
parameters at once.
The notation is a variation on the usual glob-style patterns. The most apperent differences to the usual
rules are the distinction between * and **, and that character ranges should be written with curly braces
instead of square brackets (that is, any-letter is {a-zA-Z} not [a-zA-Z], because square brackets are
already reserved for the notation of module vector indices).
Pattern syntax:
? : matches any character except dot (.)
* : matches zero or more characters except dot (.)
** : matches zero or more character (any character)
{a-f} : set: matches a character in the range a-f
{^a-f}: negated set: matches a character NOT in the range a-f
{38..150} : numeric range: any number (i.e. sequence of digits) in the range 38..150 (e.g. 99)
[38..150] : index range: any number in square brackets in the range 38..150 (e.g. [99])
backslash (\) : takes away the special meaning of the subsequent character
156
8.4.3
It is also possible to utilize the default values specifified with input(default-value) in the NED files. The
<parameter-name>.use-default=yes setting assigns the default value to the parameter, or 0, false or
empty string if there was no default value in the NED file.
The following example sets ttl (time-to-live) of hostAs ip module to 5, while all other nodes in the
network will get the default specified with input() in the NED files.
[Parameters]
**.hostA.ip.ttl = 5
**.ip.ttl.use-default = yes
To make use of all defaults in NED files, youd add the following to omnetpp.ini:
[Parameters]
**.use-default = yes
8.5
As a simulation program is evolving, it is becoming capable of collecting more and more statistics. The
size of output vector files can easily reach a magnitude of several ten or hundred megabytes, but very
often, only some of the recorded statistics are interesting to the analyst.
In OMNeT++, you can control how cOutVector objects record data to disk. You can turn output vectors
on/off or you can assign a result collection interval. Output vector configuration is given in the [OutVectors] section of the ini file, or in the [Run 1], [Run 2] etc sections individually for each run. By
default, all output vectors are turned on.
Output vectors can be configured with the following syntax:
module-pathname.objectname.enabled = yes/no
module-pathname.objectname.interval = start..stop
module-pathname.objectname.interval = ..stop
module-pathname.objectname.interval = start..
The object name is the string passed to cOutVector in its constructor or with the setName() member
function.
cOutVector eed("End-to-End Delay");
Start and stop values can be any time specification accepted in NED and config files (e.g. 10h 30m 45.2s).
As with parameter names, wildcards are allowed in the object names and module path names.
An example:
#
# omnetpp.ini
158
8.6
The random number architecture of OMNeT++ was already outlined in section 6.4. Here well cover the
configuration of RNGs in omnetpp.ini.
8.6.1
Number of RNGs
The num-rngs= configuration entry sets the number of random number generator instances (i.e. random
number streams) available for the simulation model (see 6.4). Referencing an RNG number greater or
equal to this number (from a simple module or NED file) will cause a runtime error.
8.6.2
RNG choice
The rng-class= configuration entry sets the random number generator class to be used. It defaults to
"cMersenneTwister", the Mersenne Twister RNG. Other available classes are "cLCG32" (the "legacy"
RNG of OMNeT++ 2.3 and earlier versions, with a cycle length of 231 2), and "cAkaroaRNG" (Akaroas
random number generator, see section 8.10).
8.6.3
RNG mapping
The RNG numbers used in simple modules may be arbitrarily mapped to the actual random number
streams (actual RNG instances) from omnetpp.ini. The mapping allows for great flexibility in RNG
usage and random number streams configuration even for simulation models which were not written
with RNG awareness.
RNG mapping may be specified in omnetpp.ini. The syntax of configuration entries is the following.
[General]
<modulepath>.rng-N=M
This maps module-local RNG N to physical RNG M. The following example maps all gen modules default
(N=0) RNG to physical RNG 1, and all noisychannel modules default (N=0) RNG to physical RNG 2.
[General]
num-rngs = 3
**.gen[*].rng-0 = 1
**.noisychannel[*].rng-0 = 2
This mapping allows variance reduction techniques to be applied to OMNeT++ models, without any model
change or recompilation.
159
8.6.4
Automatic seed selection gets used for an RNG if you dont explicitly specify seeds in omnetpp.ini. Automatic and manual seed selection can co-exist: for a particular simulation, some RNGs can be configured
manually, and some automatically.
The automatic seed selection mechanism uses two inputs: the run number (i.e. the number in the [Run
1], [Run 2], etc. section names), and the RNG number. For the same the run number and RNG number,
OMNeT++ always selects the same seed value for any simulation model. If the run number or the RNG
number is different, OMNeT++ does its best to choose different seeds which are also sufficiently apart in
the RNGs sequence so that the generated sequences dont overlap.
The run number can be specified either in in omnetpp.ini (e.g. via the [Cmdenv]/runs-to-execute=
entry) or on the command line:
./mysim -r 1
./mysim -r 2
./mysim -r 3
For the cMersenneTwister random number generator, selecting seeds so that the generated sequences
dont overlap is easy, due to the extremely long sequence of the RNG. The RNG is initialized from the
32-bit seed value seed = runN umber numRngs + rngN umber. (This implies that simulation runs participating in the study should have the same number of RNGs set). 1
For the cLCG32 random number generator, the situation is more difficult, because the range of this RNG
is rather short (231 1, about 2 billion). For this RNG, OMNeT++ uses a table of 256 pre-generated seeds,
equally spaced in the RNGs sequence. Index into the table is calculated with the runN umber numRngs+
rngN umber formula. Care should be taken that one doesnt exceed 256 with the index, or it will wrap and
the same seeds will be used again. It is best not to use the cLCG32 at all cMersenneTwister is superior
in every respect.
8.6.5
In some cases you may want manually configure seed values. Reasons for doing that may be that you
want to use variance reduction techniques, or you may want to use the same seeds for several simulation
runs.
For the cLCG32 RNG, OMNeT++ provides a standalone program to generate seed values (seedtool is
discussed in section 8.6.6), and you can specify those seeds explicitly in the ini file.
The following ini file explicitly initializes two of the random number generators, and uses different seed
values for each run:
[General]
rng-class=cLCG32
num-rngs = 2
[Run 1]
seed-0-lcg32 = 1768507984
seed-1-lcg32 = 33648008
[Run 2]
seed-0-lcg32 = 1082809519
seed-1-lcg32 = 703931312
...
1 While (to our knowledge) no one has proven that the seeds 0,1,2,... are well apart in the sequence, this is probably true, due to
the extremely long sequence of MT. The author would however be interested in papers published about seed selection for MT.
160
8.6.6
The seedtool program can be used for selecting seeds for the cLCG32 RNG. When started without
command-line arguments, the program prints out the following help:
seedtool - part of OMNeT++/OMNEST, (C) 1992-2004 Andras Varga
See the license for distribution terms and warranty disclaimer.
Generates seeds for the LCG32 random number generator. This RNG has a
period length of 2^31-2, which makes about 2,147 million random numbers.
Note that Mersenne Twister is also available in OMNeT++, which has a
practically infinite period length (2^19937).
Usage:
seedtool
seedtool
seedtool
seedtool
seedtool
seedtool
seedtool
i
s
d
g
g
t
p
seed
index
seed1 seed2
seed0 dist
seed0 dist n
The last two options, p and t were used internally to generate a hash table of pre-computed seeds that
greatly speeds up the tool. For practical use, the g option is the most important. Suppose you have 4
simulation runs that need two independent random number generators each and you want to start their
seeds at least 10,000,000 values apart. The first seed value can be simply 1. You would type the following
command:
C:\OMNETPP\UTILS> seedtool g 1 10000000 8
The program outputs 8 numbers that can be used as random number seeds:
1768507984
33648008
1082809519
703931312
1856610745
784675296
426676692
1100642647
161
8.7
The command line user interface is a small, portable and fast user interface that compiles and runs on all
platforms. Cmdenv is designed primarily for batch execution.
Cmdenv uses simply executes some or all simulation runs that are described in the configuration file. If
one run stops with an error message, subsequent ones will still be executed. The runs to be executed can
be passed via command-line argument or in the ini file.
8.7.1
Command-line switches
A simulation program built with Cmdenv accepts the following command line switches:
-h
-f <fileName>
-l <fileName>
-r <runs>
The program prints a short help message and the networks contained in
the executable, then exits.
Specify the name of the configuration file. The default is omnetpp.ini.
Multiple -f switches can be given; this allows you to partition your configuration file. For example, one file can contain your general settings,
another one most of the module parameters, another one the module parameters you change often.
Load a shared object (.so file on Unix). Multiple -l switches are accepted.
Your .so files may contain module code etc. By dynamically loading all
simple module code and compiled network description (_n.o files on Unix)
you can even eliminate the need to re-link the simulation program after
each change in a source file. (Shared objects can be created with gcc
-shared...)
It specifies which runs should be executed (e.g. -r 2,4,6-8). This option
overrides the runs-to-execute= option in the [Cmdenv] section of the
ini file (see later).
8.7.2
Cmdenv can be executed in two modes, selected by the express-mode ini file entry:
Normal (non-express) mode is for debugging: detailed information will be written to the standard
output (event banners, module output, etc).
Express mode can be used for long simulation runs: only periodical status update is displayed about
the progress of the simulation.
The full list of ini file options recognized by Cmdenv:
Entry and default value
[Cmdenv]
runs-to-execute =
Description
Specifies which simulation runs should be executed. It accepts a comma-separated list of
run numbers or run number ranges, e.g. 1,34,7-9. If the value is missing, Cmdenv executes all runs that have ini file sections; if no
runs are specified in the ini file, Cmdenv does
one run. The -r command line option overrides
this ini file setting.
Selects normal (debug/trace) or express
mode.
In normal mode only: printing module ev output on/off
In normal mode only: printing event banners
on/off
In normal mode only: print a line about each
message sending (by send(),scheduleAt(),
etc) and delivery on the standard output
Call fflush(stdout) after each event banner or status update; affects both express and
normal mode. Turning on autoflush can be
useful with printf-style debugging for tracking
down program crashes.
163
status-frequency=<integer>
fault: 50000)
(de-
performance-display=yes/no
fault: yes)
(de-
extra-stack-kb = 8
8.7.3
When the simulation is running in express mode with detailed performance display enabled, Cmdenv
periodically outputs a three-line status info about the progress of the simulation. The output looks like
this:
...
T=123.74354 ( 2m 3s)
Elapsed: 0m 12s
** Event #250000
Speed:
ev/sec=19731.6
simsec/sec=9.80713
ev/simsec=2011.97
Messages: created: 55532
present: 6553
in FES: 8
T=148.55496 ( 2m 28s)
Elapsed: 0m 15s
** Event #300000
Speed:
ev/sec=19584.8
simsec/sec=9.64698
ev/simsec=2030.15
Messages: created: 66605
present: 7815
in FES: 7
...
The first line of the status display (beginning with **) contains:
how many events have been processed so far
the current simulation time (T), and
the elapsed time (wall clock time) since the beginning of the simulation run.
The second line displays info about simulation performance:
ev/sec indicates performance: how many events are processed in one real-time second. On one
hand it depends on your hardware (faster CPUs process more events per second), and on the other
hand it depends on the complexity (amount of calculations) associated with processing one event.
For example, protocol simulations tend to require more processing per event than e.g. queueing
networks, thus the latter produce higher ev/sec values. In any case, this value is independent of the
size (number of modules) in your model.
simsec/sec shows relative speed of the simulation, that is, how fast the simulation is progressing
compared to real time, how many simulated seconds can be done in one real second. This value virtuall depends on everything: on the hardware, on the size of the simulation model, on the complexity
of events, and the average simulation time between events as well.
164
8.8
Features
Tkenv is a portable graphical windowing user interface. Tkenv supports interactive execution of the
simulation, tracing and debugging. Tkenv is recommended in the development stage of a simulation or
for presentation and educational purposes, since it allows one to get a detailed picture of the state of
simulation at any point of execution and to follow what happens inside the network. The most important
feaures are:
message flow animation
graphical display of statistics (histograms etc.) and output vectors during simulation execution
separate window for each modules text output
scheduled messages can be watched in a window as simulation progresses
event-by-event, normal and fast execution
labeled breakpoints
inspector windows to examine and alter objects and variables in the model
simulation can be restarted
snapshots (detailed report about the model: objects, variables etc.)
165
8.8.1
Command-line switches
A simulation program built with Tkenv accepts the following command line switches:
-h
-f <fileName>
-l <fileName>
-r <run-number>
8.8.2
The program prints a short help message and the networks contained in
the executable, then exits.
Specify the name of the configuration file. The default is omnetpp.ini.
Multiple -f switches can be given; this allows you to partition your configuration file. For example, one file can contain your general settings,
another one most of the module parameters, another one the module parameters you change often.
Load a shared object (.so file on Unix). Multiple -l switches are accepted.
Your .so files may contain module code etc. By dynamically loading all
simple module code and compiled network description (_n.o files on Unix)
you can even eliminate the need to re-link the simulation program after
each change in a source file. (Shared objects can be created with gcc shared...)
It has the same effect as (but takes priority over) the [Tkenv]/defaultrun= ini file entry.
Tkenv accepts the following settings in the [Tkenv] section of the ini file.
Entry and default value
extra-stack-kb = 48
default-run = 1
Description
[Tkenv]
Specifies the extra amount of stack (in kilobytes) that is reserved for each activity()
simple module when the simulation is run under Tkenv. This value is significantly higher
than the similar one for Cmdenv handling
GUI events requires a large amount of stack
space.
Specifies which run Tkenv should set up automatically after startup. If theres no defaultrun= entry or the value is 0, Tkenv will ask
which run to set up.
The following configuration entries are of marginal usefulness, because corresponding settings are also
accessible in the Simulation options dialog in the Tkenv GUI, and the GUI settings take precedence.
Tkenv stores the settings in the .tkenvrc file in the current directory the ini file settings are only used
if there is no .tkenvrc file.
Entry and default value
Description
166
use-mainwindow = yes
print-banners = yes
breakpoints-enabled = yes
update-freq-fast = 10
update-freq-express = 500
animation-delay = 0.3s
animation-enabled = yes
animation-msgnames = yes
animation-msgcolors = yes
animation-speed = 1.0
methodcalls-delay =
show-layouting = true
8.8.3
[Tkenv]
Enables/disables writing ev output to the
Tkenv main window.
Enables/disables printing banners for each
event.
Specifies whether the simulation should be
stopped at each breakpoint() call in the
simple modules.
Number of events executed between two display updates when in Fast execution mode.
Number of events executed between two display updates when in Express execution mode.
Delay between steps when you slow-execute
the simulation.
Enables/disables message flow animation.
Enables/disables displaying message names
during message flow animation.
Enables/disables using different colors for
each message kind during message flow animation.
Specifies the speed of message flow animation.
Sets delay after method call animation.
Show layouting process of network graphics.
8.8.4
In Memoriam. . .
There used to be other windowing user interfaces which have been removed from the distribution:
TVEnv. A Turbo Vision-based user interface, the first interactive UI for OMNeT++. Turbo Vision
was an excellent character-graphical windowing environment, originally shipped with Borland C++
3.1.
XEnv. A GUI written in pure X/Motif. It was an experiment, written before I stumbled into Tcl/Tk
and discovered its immense productivity in GUI building. XEnv never got too far because it was
really very-very slow to program in Motif. . .
8.9
Once your model works reliably, youll usually want to run several simulations. You may want to run the
model with various parameter settings, or you may want (should want?) to run the same model with the
same parameter settings but with different random number generator seeds, to achieve statistically more
reliable results.
168
8.9.1
In simple cases, you may define all simulation runs needed in the [Run 1], [Run 2], etc. sections of
omnetpp.ini, and invoke your simulation with the -r flag each time. The -f flag lets you use a file name
different from omnetpp.ini.
The following script executes a simulation named wireless several times, with parameters for the different runs given in the runs.ini file.
#! /bin/sh
./wireless
./wireless
./wireless
./wireless
...
./wireless
-f
-f
-f
-f
runs.ini
runs.ini
runs.ini
runs.ini
-r
-r
-r
-r
1
2
3
4
-f runs.ini -r 10
To run the above script, type it in a text file called e.g. run, give it x (executable) permission using chmod,
then you can execute it by typing ./run:
% chmod +x run
% ./run
You can simplify the above script by using a for loop. In the example below, the variable i iterates through
the values of list given after the in keyword. It is very practical, since you can leave out or add runs, or
change the order of runs by simply editing the list to demonstrate this, we skip run 6, and include run
15 instead.
#! /bin/sh
for i in 3 2 1 4 5 7 15 8 9 10; do
./wireless -f runs.ini -r $i
done
If you have many runs, you can use a C-style loop:
#! /bin/sh
for ((i=1; $i<50; i++)); do
./wireless -f runs.ini -r $i
done
8.9.2
It may not be practical to hand-write descriptions of all runs in an ini file, especially if there are many
parameter settings to try, or you want to try all possible combinations of two or more parameters. The
solution might be to generate only a small fraction of the ini file with the variable parameters, and use it
via ini file inclusion. For example, you might write your omnetpp.ini like this:
169
8.9.3
The same kind of control script can be used if you want to execute several runs with different random seeds. The following code does 500 runs with independent seeds. (omnetpp.ini should include
parameters.ini.)
The seeds are 10 million numbers apart in the sequence (seedtool parameter), so one run should not
use more random numbers than this, otherwise there will be overlaps in the sequences and the runs will
not be independent.
#! /bin/sh
seedtool g 1 10000000 500 > seeds.txt
for seed in cat seeds.txt; do
(
echo "[General]"
echo "random-seed = ${seed}"
echo "output-vector-file = xcube-${seed}.vec"
) > parameters.ini
./xcube
done
8.10
8.10.1
Introduction
Typical simulations are Monte-Carlo simulations: they use (pseudo-)random numbers to drive the simulation model. For the simulation to produce statistically reliable results, one has to carefully consider the
following:
When is the initial transient over, when can we start collecting data? We usually do not want to
include the initial transient when the simulation is still warming up.
When can we stop the simulation? We want to wait long enough so that the statistics we are collecting can stabilize, can reach the required sample size to be statistically trustable.
Neither questions are trivial to answer. One might just suggest to wait very long or long enough.
However, this is neither simple (how do you know what is long enough?) nor practical (even with
todays high speed processors simulations of modest complexity can take hours, and one may not afford
multiplying runtimes by, say, 10, just to be safe.) If you need further convincing, please read [PJL02]
and be horrified.
A possible solution is to look at the statistics while the simulation is running, and decide at runtime
when enough data have been collected for the results to have reached the required accuracy. One possible
criterion is given by the confidence level, more precisely, by its width relative to the mean. But ex ante
it is unknown how many observations have to be collected to achieve this level it must be determined
runtime.
8.10.2
What is Akaroa
Akaroa [EPM99] addresses the above problem. According to its authors, Akaroa (Akaroa2) is a fully
automated simulation tool designed for running distributed stochastic simulations in MRIP scenario in
a cluster computing environment.
MRIP stands for Multiple Replications in Parallel. In MRIP, the computers of the cluster run independent
replications of the whole simulation process (i.e. with the same parameters but different seed for the
171
8.10.3
Akaroa
Before the simulation can be run in parallel under Akaroa, you have to start up the system:
Start akmaster running in the background on some host.
On each host where you want to run a simulation engine, start akslave in the background.
Each akslave establishes a connection with the akmaster.
Then you use akrun to start a simulation. akrun waits for the simulation to complete, and writes a report
of the results to the standard output. The basic usage of the akrun command is:
akrun -n num_hosts command [argument..]
where command is the name of the simulation you want to start. Parameters for Akaroa are read from the
file named Akaroa in the working directory. Collected data from the processes are sent to the akmaster
process, and when the required precision has been reached, akmaster tells the simulation processes to
terminate. The results are written to the standard output.
The above description is not detailed enough help you set up and successfully use Akaroa for that you
need to read the Akaroa manual.
Configuring OMNeT++ for Akaroa
First of all, you have to compile OMNeT++ with Akaroa support enabled.
The OMNeT++ simulation must be configured in omnetpp.ini so that it passes the observations to
Akaroa. The simulation model itself does not need to be changed it continues to write the observations into output vectors (cOutVector objects, see chapter 6). You can place some of the output vectors
under Akaroa control.
You need to add the following to omnetpp.ini:
[General]
rng-class="cAkaroaRNG"
outputvectormanager-class="cAkOutputVectorManager"
172
8.11
Typical issues
8.11.1
Stack problems
more details on the plugin mechanism these settings make use of, see section 13.5.3.
173
required
/lib/security/pam_limits.so
hard
stack
65536
A more drastic solution is to recompile the kernel with a larger stack limit.
Edit
/usr/src/linux/include/linux/sched.h and increase _STK_LIM from (8*1024*1024) to
(64*1024*1024).
Finally, it youre tight with memory, you can switch to Cmdenv. Tkenv increases the stack size of each
module by about 32K so that user interface code that is called from a simple modules context can be
safely executed. Cmdenv does not need that much extra stack.
Eventually...
Once you get to the point where you have to adjust the total stack size to get your program running,
you should probably consider transforming (some of) your activity() simple modules to handleMessage(). activity() does not scale well for large simulations.
8.11.2
The most common problems in C++ are associated with memory allocation (usage of new and delete):
memory leaks, that is, forgetting to delete objects or memory blocks no longer used;
crashes, usually due to referring to an already deleted object or memory block, or trying to delete
one for a second time;
heap corruption (enventually leading to crash) due to overrunning allocated blocks, i.e. writing past
the end of an allocated array.
By far the most common ways leaking memory in simulation programs is by not deleting messages (cMessage objects or subclasses). Both Tkenv and Cmdenv are able to display the number of messages currently in the simulation, see e.g. section 8.7.3. If you find that the number of messages is steadily
increasing, you need to find where the message objects are. You can do so by selecting Inspect|From list
of all objects... from the Tkenv menu, and reviewing the list in the dialog that pops up. (If the model is
large, it may take a while for the dialog to appear.)
If the number of messages is stable, it is still possible youre leaking other cObject-based objects. You
can also find them using Tkenvs Inspect|From list of all objects... function.
If youre leaking non-cObject-based objects or just memory blocks (structs, int/double/struct arrays, etc, allocated by new), you cannot find them via Tkenv. Youll probably need a specialized memory
debugging tool like the ones described below.
Memory debugging tools
If you suspect that you may have memory allocation problems (crashes associated with double-deletion or
accessing already deleted block, or memory leaks), you can use specialized tools to track them down.
By far the most efficient, most robust and most versatile tool is Valgrind, originally developed for debugging KDE.
175
8.11.3
What can you do if the simulation executes much slower than you expect? The best advice that can be
given here is that you should use a good profiler to find out how much time is spent in each part of the
program. Do not make the mistake of omitting this step, thinking that you know "which part is slow"!
Even for experienced programmers, profiling session is all too often full of surprises. It often turns out that
lots of CPU time is spent in completely innocent-looking statements, while the big and complex algorithm
doesnt take nearly as much time as expected. Dont assume anything profile before you optimize! 3
A really impressive profiler on Linux is the Valgrind-based callgrind, and its visualizer KCachegrind.
Unfortunately it wont be ported to Windows anytime soon. On Windows, youre out of luck commercial
products may help, or, port your simulation to Linux. The latter goes usually much smoother than one
would expect.
3 And
176
Chapter 9
Display strings
Display string syntax
Display strings specify the arrangement and appearance of modules in graphical user interfaces (currently only Tkenv): they control how the objects (compound modules, their submodules and connections)
are displayed. Display strings occur in NED descriptions display: phrases.
The display string format is a semicolon-separated list of tags. Each tag consists of a key (usually one
letter), an equal sign and a comma-separated list of parameters, like:
"p=100,100;b=60,10,rect;o=blue,black,2"
Parameters may be omitted also at the end and also inside the parameter list, like:
"p=100,100;b=,,rect;o=blue,black"
Module/submodule parameters can be included with the $name notation:
"p=$xpos,$ypos;b=rect,60,10;o=$fillcolor,black,2"
Objects that may have display strings are:
submodules display string may contain position, arrangement (for module vectors), icon, icon color,
auxiliary icon, status text, communication range (as circle or filled circle), etc.
connections display string can specify positioning, arrow color, arrow thickness
compound modules display string can specify background color, border color, border thickness
messages display string can specify icon, icon color, etc.
The following NED sample shows where to place display strings in the code.
module ClientServer
submodules:
pc: Host;
display: "p=66,55;i=comp"; // position and icon
177
9.1.2
The following table lists the tags used in submodule display strings:
Tag
p=xpos,ypos
p=xpos,ypos,row,deltax
p=xpos,ypos,column,deltay
p=xpos,ypos,matrix,
row,deltax,deltay
itemsper-
p=xpos,ypos,ring,width,height
Meaning
Place submodule at (xpos,ypos) pixel position,
with the origin being the top-left corner of the
enclosing module.
Defaults: an appropriate automatic layout is
where submodules do not overlap.
If applied to a submodule vector, ring or row layout is selected automatically.
Used for module vectors. Arranges submodules
in a row starting at (xpos,ypos), keeping deltax
distances.
Defaults: deltax is chosen so that submodules do
not overlap.
row may be abbreviated as r.
Used for module vectors. Arranges submodules
in a column starting at (xpos,ypos), keeping
deltay distances.
Defaults: deltay is chosen so that submodules do
not overlap.
column may be abbreviated as col or c.
Used for module vectors. Arranges submodules
in a matrix starting at (xpos,ypos), at most
itemsperrow submodules in a row, keeping deltax
and deltay distances.
Defaults: itemsperrow=5, deltax,deltay are
chosen so that submodules do not overlap.
matrix may be abbreviated as m.
Used for module vectors. Arranges submodules
in an ellipse, with the top-left corner of the
ellipses bounding box at (xpos,ypos), with the
width and height.
Defaults: width,height are chosen so that
submodules do not overlap.
ring may be abbreviated as ri.
178
p=xpos,ypos,exact,deltax,deltay
b=width,height,rect
b=width,height,oval
o=fillcolor,outlinecolor,borderwidth
i=iconname,color,percentage
is=size
i2=iconname,color,percentage
r=radius,fillcolor,color,width
q=queue-object-name
t=text,pos,color
tt=tooltip-text
179
9.1.3
Compound module display strings specify the background. They can contain the following tags:
Tag
p=xpos,ypos
b=width,height,rect
b=width,height,oval
o=fillcolor,outlinecolor,borderwidth
tt=tooltip-text
9.1.4
Meaning
Place enclosing module at (xpos,ypos) pixel position, with (0,0) being the top-left corner of the
window.
Display enclosing module as a rectangle with the
given height and width.
Defaults: width, height are chosen automatically
Display enclosing module as an ellipse with the
given height and width.
Defaults: width, height are chosen automatically
Specifies options for the rectangle or oval. For
color notation, see section 9.2.
Defaults: fillcolor=#8080ff (a lightblue), outlinecolor=black, borderwidth=2
Displays the given text in a tooltip when the user
moves the mouse over the module name in the
top-left corner.
Meaning
Drawing mode. Specifies the exact placement of
the connection arrow. The arguments can be abbreviated as a,e,w,n,s.
o=color,width
180
t=text,color
tt=tooltip-text
Examples:
"m=a;o=blue,3"
9.1.5
Message objects do not store a display string by default, but you can redefine the cMessages displayString() method and make it return one.
const char *CustomPacket::displayString() const
{
return "i=msg/packet_vs";
}
This display string affects how messages are shown during animation. By default, they are displayed as
a small filled circle, in one of 8 basic colors (the color is determined as message kind modulo 8), and with
the message class and/or name displayed under it The latter is configurable in the Tkenv Options dialog,
and message kind dependent coloring can also be turned off there.
The following tags can be used in message display strings:
Tag
b=width,height,oval
b=width,height,rect
o=fillcolor,outlinecolor,borderwidth
i=iconname,color,percentage
tt=tooltip-text
Meaning
Ellipse with the given height and width.
Defaults: width=10, height=10
Rectangle with the given height and width.
Defaults: width=10, height=10
Specifies options for the rectangle or oval. For
color notation, see section 9.2.
Defaults: fillcolor=red, outlinecolor=black, borderwidth=1
Use the named icon. It can be colorized, and
percentage specifies the amount of colorization.
If color name is "kind", a message kind
dependent colors is used (like default behaviour).
Defaults: iconname: no default if no icon name
is present, a small red solid circle will be used;
color: no coloring; percentage: 30%
Displays the given text in a tooltip when the user
moves the mouse over the message icon.
Examples:
181
9.2
9.2.1
Colors
Color names
Any valid Tk color specification is accepted: English color names (blue, lightgray, wheat) or #rgb, #rrggbb
format (where r,g,b are hex digits).
It is also possible to specify colors in HSB (hue-saturation-brightness) as @hhssbb (with h, s, b being hex
digits). HSB makes it easier to scale colors e.g. from white to bright red.
You can produce a transparent background by specifying a hyphen ("-") as color.
9.2.2
Icon colorization
The "i=" display string tag allows for colorization of icons. It accepts a target color and a percentage as
the degree of colorization. Percentage has no effect if the target color is missing. Brightness of icon is also
affected to keep the original brightness, specify a color with about 50#008000 mid-green).
Examples:
"i=device/server,gold" creates a gold server icon
"i=misc/globe,#808080,100" makes the icon grayscale
"i=block/queue,white,100" yields a "burnt-in" black-and-white icon
Colorization works with both submodule and message icons.
9.3
9.3.1
The icons
The bitmap path
In the current OMNeT++ version, module icons are GIF files. The icons shipped with OMNeT++ are in
the bitmaps/ subdirectory. Both the GNED editor and Tkenv need the exact location of this directory to
load the icons.
Icons are loaded from all directories in the bitmap path, a semicolon-separated list of directories. The default bitmap path is compiled into GNED and Tkenv with the value "omnetpp-dir/bitmaps;./bitmaps"
which will work fine as long as you dont move the directory, and youll also be able to load more icons
from the bitmaps/ subdirectory of the current directory. As people usually run simulation models from
the models directory, this practically means that custom icons placed in the bitmaps/ subdirectory of
the models directory are automatically loaded.
The compiled-in bitmap path can be overridden with the OMNETPP_BITMAP_PATH environment variable.
The way of setting environment variables is system specific: in Unix, if youre using the bash shell, adding
a line
export OMNETPP_BITMAP_PATH="/home/you/bitmaps;./bitmaps"
182
9.3.2
Categorized icons
Since OMNeT++ 3.0, icons are organized into several categories, represented by folders. These categories
include:
block/ - icons for subcomponents (queues, protocols, etc).
device/ - network devices: servers, hosts, routers, etc.
abstract/ - symbolic icons for various devices
misc/ - node, subnet, cloud, building, town, city, etc.
msg/ - icons that can be used for messages
Old (pre-3.0) icons are in the old/ folder.
Tkenv and GNED now load icons from subdirectories of all directories of the bitmap path, and these icons
can be referenced from display strings by naming the subdirectory (subdirectories) as well: "subdir/icon",
"subdir/subdir2/icon", etc.
For compatibility, if the display string contains a icon without a category (i.e. subdirectory) name, OMNeT++ tries it as "old/icon" as well.
9.3.3
Icon size
Icons come in various sizes: normal, large, small, very small. Sizes are encoded into the icon names suffix:
_l, _s, _vs. In display strings, one can either use the suffix ("i=device/router_l"), or the "is" (icon
size) display string tag ("i=device/router;is=l").
9.4
Layouting
OMNeT++ implements an automatic layouting feature, using a variation of the SpringEmbedder algorithm. Modules which have not been assigned explicit positions via the "p=" tag will be automatically
placed by the algorithm.
SpringEmbedder is a graph layouting algorithm based on a physical model. Graph nodes (modules) repent
each other like electric charges of the same sign, and connections are sort of springs which try to contract
and pull the nodes theyre attached to. There is also friction built in, in order to prevent oscillation of the
nodes. The layouting algorithm simulates this physical system until it reaches equilibrium (or times out).
The physical rules above have been slightly tweaked to get better results.
The algorithm doesnt move any module which has fixed coordinates. Predefined row, matrix, ring or other
arrangements (defined via the 3rd and further args of the "p=" tag) will be preserved you can think
183
9.5
The GNED editor allows you to design compound modules graphically. GNED works directly with NED
files it doesnt have any internal file format. You can load any of your existing NED files, edit the
compound modules in it graphically and then save the file back. Other components in the NED file
(simple modules, channels, networks etc.) will survive the operation. GNED puts all graphics-related
data into display strings.
GNED works by parsing your NED file into an internal data structure, and regenerating the NED text
when you save the file. One consequence of this is that indentation will be canonized. Comments in the
original NED are preserved the parser associates them with the NED elements they belong to, so comments wont be messed up even if you edit the graphical representation extensively by removing/adding
submodules, gates, parameters, connections, etc.
GNED is a fully two-way visual tool. While editing the graphics, you can always switch to NED source
view, edit in there and switch back to graphics. Your changes in the NED source will be immediately
backparsed to graphics; in fact, the graphics will be totally reconstructed from the NED source and the
display strings in it.
9.5.1
In graphics view, there are two editing modes: draw and select/mode. The mouse bindings are the following:
Mouse
Effect
In draw mode:
Drag out a rectangle in empty area:
create new submodule
Drag from one submodule to another:
create new connection
Click in empty area:
switch to select/move mode
In select/move mode:
Click submodule/connection:
select it
Ctrl-click submodule/conn.:
add to selection
184
clear selection
move selected objects
move it
move that end
resize module
select enclosed submodules/connections
delete selected objects
Both editing modes:
Right-click on module/submodule/connec- popup menu
tion:
Double-click on submodule:
go into submodule
Click name label
edit name
Drag&drop module type from the tree view create a submodule of that type
to the canvas
9.6
9.6.1
Enhancing animation
Changing display strings at runtime
Often it is useful to manipulate the display string at runtime. Changing colors, icon, or text may convey
status change, and changing a modules position is useful when simulating mobile networks.
Display strings are stored in cDisplayString objects inside modules and gates. cDisplayString also
lets you manipulate the string.
To get a pointer to the cDisplayString object, you can call the modules displayString() method:
cDisplayString *dispStr = displayString();
cDisplayString *bgDispStr = parentModule()->backgroundDisplayString();
cDisplayString *gateDispStr = gate("out")->displayString();
As far as cDisplayString is concerned, a display string (e.g. "p=100,125;i=cloud") is a string that
consist of several tags separated by semicolons, and each tag has a name and after an equal sign, zero or
more arguments separated by commas.
The class facilitates tasks such as finding out what tags a display string has, adding new tags, adding
arguments to existing tags, removing tags or replacing arguments. The internal storage method allows
very fast operation; it will generally be faster than direct string manipulation. The class doesnt try to
interpret the display string in any way, nor does it know the meaning of the different tags; it merely
parses the string as data elements separated by semicolons, equal signs and commas.
An example:
dispStr->parse("a=1,2;p=alpha,,3");
dispStr->insertTag("x");
dispStr->setTagArg("x",0,"joe");
dispStr->setTagArg("x",2,"jim");
dispStr->setTagArg("p",0,"beta");
ev << dispStr->getString(); // result: "x=joe,,jim;a=1,2;p=beta,,3"
185
9.6.2
Bubbles
Modules can let the user know about important events (such as a node going down or coming up) by
displaying a bubble with a short message ("Going down", "Coming up", etc.) This is done by the bubble()
method of cModule. The method takes the string to be displayed as a const char * pointer.
An example:
bubble("Going down!");
If the module contains a lot of code that modifies the display string or displays bubbles, it is recommended
to make these calls conditional on ev.isGUI(). The ev.isGUI() call returns false when the simulation
is run under Cmdenv, so one can make the code skip potentially expensive display string manipulation.
if (ev.isGUI())
bubble("Going down!");
186
Chapter 10
Output vectors
Output vectors are time series data: values with timestamps. You can use output vectors to record end-toend delays or round trip times of packets, queue lengths, queueing times, link utilization, the number of
dropped packets, etc. anything that is useful to get a full picture of what happened in the model during
the simulation run.
Output vectors are recorded from simple modules, by cOutVector objects (see section 6.9.1). Since output
vectors usually record a large amount of data, in omnetpp.ini you can disable vectors or specify a
simulation time interval for recording (see section 8.5).
All cOutVector objects write to the same, common file. The following sections describe the format of the
file, and how to process it.
10.1.1
Plove features
Typically, youll get output vector files as a result of a simulation. Data written to cOutVector objects
from simple modules are written to output vector files. You can use Plove to look into the output vector
files and plot vectors from them.
Plove is a handy tool for plotting OMNeT++ output vectors. Line type (lines, dots etc) for each vector
can be set as well as the most frequent drawing options like axis bounds, scaling, titles and labels. You
can save the graphs to files (as Encapsulated Postscript or raster formats such as GIF) with a click. On
Windows, you can also copy the graph to the clipboard in a vector format (Windows metafile) and paste it
into other applications. 1
Filtering the results before plotting is possible. Filters can do averaging, truncation of extreme values,
smoothing, they can do density estimation by calculating histograms etc. Some filters are built in, and
you can create new filters by parameterizing and aggregating existing ones. You can apply several filters
to a vector.
On startup, Plove automatically reads the .ploverc file in your home directory. The file contains general
application settings, including the custom filters you created.
1 Note: prior to OMNeT++ 3.0, Plove has been a front-end to gnuplot. This older version of Plove is no longer supported, but it is
still available in the OMNeT++ source distribution.
187
10.1.2
An output vector file contains several series of data produced during simulation. The file is textual, and
it looks like this:
mysim.vec:
vector 1
1 12.895
1 14.126
vector 2
2 16.960
1 23.086
2 24.026
There two types of lines: vector declaration lines (beginning with the word vector), and data lines. A
vector declaration line introduces a new output vector, and its columns are: vector Id, module of creation,
name of cOutVector object, and multiplicity (usually 1). Actual data recorded in this vector are on data
lines which begin with the vector Id. Further columns on data lines are the simulation time and the
recorded value.
10.1.3
In case you have a large number of repeated experiments, youll probably want to automate processing
of the output vector files. OMNeT++ lets you use any tool you see fit for this purpose, because the output
vector files are text files and their format is simple enough to be processed by common tools such as perl,
awk, octave, etc.
Extracting vectors from the file
You can use the Unix grep tool to extract a particular vector from the file. As the first step, you must find
out the Id of the vector. You can find the appropriate vector line with a text editor or you can use grep for
this purpose:
% grep "queue length" vector.vec
Or, you can get the list of all vectors in the file by typing:
188
"subnet[4].srvr"
"queue length"
Pick the vector Id, which is 6 in this case, and grep the file for the vectors data lines:
grep ^6 vector.vec > vector6.vec
Now, vector6.vec contains the appropriate vector. The only potential problem is that the vector Id is
there at the beginning of each line and this may be hard to digest for some programs that you use for postprocessing and/or visualization. This problem is eliminated by the OMNeT++ splitvec utility (written
in awk), to be discussed in the next section.
Using splitvec
The splitvec script (part of OMNeT++) automates the process described in the previous section: it
breaks the vector file into several files which contain one vector each. The command
% splitvec mysim.vec
would create the files mysim1.vec, mysim2.vec etc. with contents similar to the following:
mysim1.vec:
# vector 1 "subnet[4].term[12]"
12.895 2355.66666666
14.126 4577.66664666
23.086 2355.66666666
"response time"
mysim2.vec:
# vector 2 "subnet[4].srvr" "queue length"
16.960 2.00000000000.63663666
24.026 8.00000000000.44766536
As you can see, the vector Id column has been stripped from the files. The resulting files can be directly
loaded e.g. into spreadsheets or other programs (10.3).
10.2
Scalar statistics
Output vectors capture the transient behaviour of the simulation run. However, to compare model behaviour under various parameter settings, output scalars are more useful.
10.2.1
Scalar results are recorded with recordScalar() calls, usually from the finish() methods of modules,
with code like this:
void EtherMAC::finish()
{
189
numFramesSent);
numFramesReceivedOK);
numBytesSent);
numBytesReceivedOK);
numCollisions);
numFramesSent/t);
numFramesReceivedOK/t);
8*numBytesSent/t);
8*numBytesReceivedOK/t);
}
The corresponding output scalar file (by default, omnetpp.sca) will look like this:
run 1 "lan"
scalar "lan.hostA.mac"
scalar "lan.hostA.mac"
scalar "lan.hostA.mac"
scalar "lan.hostA.mac"
scalar "lan.hostA.mac"
scalar "lan.hostA.mac"
scalar "lan.hostA.mac"
scalar "lan.hostA.mac"
scalar "lan.hostA.mac"
scalar "lan.hostA.mac"
scalar "lan.hostA.mac"
scalar "lan.hostA.mac"
scalar "lan.hostB.mac"
scalar "lan.hostB.mac"
scalar "lan.hostB.mac"
scalar "lan.hostB.mac"
[...]
scalar "lan.hostC.mac"
scalar "lan.hostC.mac"
scalar "lan.hostC.mac"
scalar "lan.hostC.mac"
[...]
"simulated time"
"rx channel idle (%)"
"rx channel utilization (%)"
"rx channel collision (%)"
"frames sent"
"frames rcvd"
"bytes sent"
"bytes rcvd"
"frames/sec sent"
"frames/sec rcvd"
"bits/sec sent"
"bits/sec rcvd"
"simulated time"
"rx channel idle (%)"
"rx channel utilization (%)"
"rx channel collision (%)"
120.249243
97.5916992
2.40820676
0.011312
99
3088
64869
3529448
0.823290006
25.6799953
4315.63632
234808.83
120.249243
97.5916992
2.40820676
0.011312
"simulated time"
"rx channel idle (%)"
"rx channel utilization (%)"
"rx channel collision (%)"
120.249243
97.5916992
2.40820676
0.011312
run 2 "lan"
scalar "lan.hostA.mac" "simulated time"
[...]
235.678665
Every recordScalar() call generates one "scalar" line in the file. (If you record statistics objects (cStatictic subclasses such as cStdDev) via their recordScalar() methods, theyll generate several lines:
mean, standard deviation, etc.) In addition, several simulation runs can record their results into a single
file this facilitates comparing them, creating x-y plots (offered load vs throughput-type diagrams), etc.
190
10.2.2
The Scalars program can be used to visualize the contents of the omnetpp.sca file. It can draw bar
charts, x-y plots (e.g. throughput vs offered load), or export data via the clipboard for more detailed
analysis into spreadsheets or other programs.
You can open a scalar file either from the Scalars programs menu or by specifying it as a command-line
argument to Scalars.
The program displays the data in a table with columns showing the file name, run number, module name
where it was recorded, and the value. Therere usually too many rows to get an overview, so you can filter
by choosing from (or editing) the three combo boxes at the top. (The filters also accept *, ** wildcards.)
You could actually load further scalar files into the window, and thus analyse them together.
You can copy the selected rows to the clipboard by Edit|Copy or the corresponding toolbar button, and
paste them e.g. into OpenOffice Calc, MS Excel or Gnumeric.
The bar chart toolbar button creates well a bar chart in a new window. You can customize the chart
by right-clicking on it and choosing from the context menu. It can also be exported to EPS, GIF, or as
metafile via the Windows clipbard (the latter is not available on Unix of course).
10.3
Output vector files (or files produced by splitvec) and output scalar files can be analysed and/or plotted
by a number of applications in addition to Plove and Scalars. These programs can produce output in
various forms (on the screen, as PostScript, in various image formats, etc.)
One straightforward solution is to import or paste them into spreadsheet programs such as OpenOffice
Calc, Microsoft Excel or GNOME Gnumeric. These programs have good charting and statistical features,
but the number of rows is usually limited to about 32,000..64,000. One useful functionality spreadsheets
offer for analysing scalar files is known as PivotTable in Excel, and as DataPilot in in OpenOffice. The
easiest way to import scalar files into them is via copy/paste from Scalars.
Alternatively, one can use numerical packages such as Octave, Matlab or the statistics package R. In
addition to their support for statistical computations, they can also create various plots.
There are also open-source programs directly for plotting, Gnuplot still being the most commonly used
one. Other, potentially more powerful ones include Grace, ROOT and PlotMTV.
10.3.1
Grace
Grace (also known as xmgrace, a successor of ACE/gr or Xmgr) is a GPL-ed powerful data visualization
program with a WYSIWIG point-and-click graphical user interface. It was developed for Unix, but there
is a Windows version, too.
You load the appropriate file by selecting it in a dialog box. The icon bar and menu commands can be used
to customize the graph.
As of June 2003, Grace 1.5.12 can export graphics to (E)PS, PDF, MIF, SVG, PNM, JPEG and PNG
formats. It has many useful features like built-in statistics and analysis functions (e.g. correlation,
histogram), fitting, splines, etc., and it also sports its own built-in programming language.
10.3.2
ROOT
ROOT is a powerful object-oriented data analysis framework, with strong support for plotting and graphics in general. ROOT was developed at CERN, and is distributed under a BSD-like license.
191
10.3.3
Gnuplot
Gnuplot has an interactive command interface. To plot the data in mysim1.vec and mysim4.vec (produced by splitvec) plotted in the same graph, you can type:
plot "mysim1.vec" with lines, "mysim4.vec" with lines
To adjust the y range, you would type:
set yrange [0:1.2]
replot
Several commands are available to adjust ranges, plotting style, labels, scaling etc. Gnuplot can also plot
3D graphs. Gnuplot is available for Windows and other platforms. On Windows, you can copy the resulting graph to the clipboard from the Gnuplot windows system menu, then insert it into the application
you are working with.
192
Chapter 11
Overview
OMNeT++ provides a tool which can generate HTML documentation from NED files and message definitions. Like Javadoc and Doxygen, opp_neddoc makes use of source code comments. opp_neddocgenerated documentation lists simple and compound modules, and presents their details including description, gates, parameters, unassigned submodule parameters and syntax-highlighted source code. The
documentation also includes clickable network diagrams (exported via the GNED graphical editor) and
module usage diagrams as well as inheritance diagrams for messages.
opp_neddoc works well with Doxygen, which means that it can hyperlink simple modules and message
classes to their C++ implementation classes in the Doxygen documentation. If you also generate the C++
documentation with some Doxygen features turned on (such as inline-sources and referenced-by-relation,
combined with extract-all, extract-private and extract-static), the result is an easily browsable and very
informative presentation of the source code. Of course, one still has to write documentation comments in
the code.
11.2
11.2.1
Documentation comments
Documentation is embedded in normal comments. All // comments that are in the right place (from the
documentation tools point of view) will be included in the generated documentation. 1
Example:
//
// An ad-hoc traffic generator to test the Ethernet models.
//
simple Gen
parameters:
destAddress: string, // destination MAC address
protocolId: numeric, // value for SSAP/DSAP in Ethernet frame
waitMean: numeric;
// mean for exponential interarrival times
gates:
1 In contrast, Javadoc and Doxygen use special comments (those beginning with /
**, ///, //< or a similar marker) to distinguish
documentation from normal comments in the source code. In OMNeT++ theres no need for that: NED and the message syntax is
so compact that practically all comments one would want to write in them can serve documentation purposes. Still, there is a way
to write comments that dont make it into the documentation by starting them with //#.
193
// to Ethernet LLC
You can also place comments above parameters and gates. This is useful if they need long explanations.
Example:
//
// Deletes packets and optionally keeps statistics.
//
simple Sink
parameters:
// You can turn statistics generation on and off. This is
// a very long comment because it has to be described what
// statistics are collected (or not).
statistics: bool;
gates:
in: in;
endsimple
If you want a comment line not to appear in the documentation, begin it with //#. Those lines will be
ignored by the documentation generation, and can be used to comment out unused NED code or to make
private comments like FIXME or TBD.
//
// An ad-hoc traffic generator to test the Ethernet models.
//# FIXME above description needs to be refined
//
simple Gen
parameters:
destAddress: string, // destination MAC address
protocolId: numeric, // value for SSAP/DSAP in Ethernet frame
//# burstiness: numeric; -- not yet supported
waitMean: numeric;
// mean for exponential interarrival times
gates:
out: out;
// to Ethernet LLC
endsimple
11.2.2
If you write longer descriptions, youll need text formatting capabilities. Text formatting works like in
Javadoc or Doxygen you can break up the text into paragraphs and create bulleted/numbered lists
without special commands, and use HTML for more fancy formatting.
Paragraphs are separated by empty lines, like in LaTeX or Doxygen. Lines beginning with - will be
turned into bulleted lists, and lines beginning with -# into numbered lists.
Example:
//
//
//
//
//
//
11.2.3
Special tags
OMNeT++_neddoc understands the following tags and will render them accordingly: @author, @date,
@todo, @bug, @see, @since, @warning, @version. An example usage:
//
// @author Jack Foo
// @date 2005-02-11
//
11.2.4
Common HTML tags are understood as formatting commands. The most useful of these tags are: <i>..</i>
(italic), <b>..</b> (bold), <tt>..</tt> (typewriter font), <sub>..</sub> (subscript), <sup>..</sup>
(superscript), <br> (line break), <h3> (heading), <pre>..</pre> (preformatted text) and <a href=..>..</a>
(link), as well as a few other tags used for table creation (see below). For example, <i>Hello</i> will be
rendered as Hello (using an italic font).
The complete list of HTML tags interpreted by opp_neddoc are: <a>, <b>, <body>, <br>, <center>,
<caption>, <code>, <dd>, <dfn>, <dl>, <dt>, <em>, <form>, <font>, <hr>, <h1>, <h2>, <h3>, <i>,
<input>, <img>, <li>, <meta>, <multicol>, <ol>, <p>, <small>, <span>, <strong>, <sub>, <sup>,
<table>, <td>, <th>, <tr>, <tt>, <kbd>, <ul>, <var>.
Any tags not in the above list will not be interpreted as formatting commands but will be printed verbatim for example, <what>bar</what> will be rendered literally as <what>bar</what> (unlike HTML
where unknown tags are simply ignored, i.e. HTML would display bar).
If you insert links to external pages (web sites), its useful to add the target="_blank" attribute to
ensure pages come up in a new browser window and not just in the current frame which looks awkward. (Alternatively, you can use the target="_top" attribute which replaces all frames in the current
browser).
Examples:
//
// For more info on Ethernet and other LAN standards, see the
// <a href="http://www.ieee802.org/" target="_blank">IEEE 802
// Committees site</a>.
//
You can also use the <a href=..> tag to create links within the page:
//
//
//
//
//
//
195
<pre>
// my preferred way of indentation in C/C++ is this:
<b>for</b> (<b>int</b> i=0; i<10; i++)
{
printf(<i>"%d\n"</i>, i);
}
</pre>
will be rendered as
// my preferred way of indentation in C/C++ is this:
for (int i=0; i<10; i++)
{
printf("%d\n", i);
}
HTML is also the way to create tables. The example below
//
// <table border="1">
//
<tr> <th>#</th>
//
<tr> <td>1</td>
//
<tr> <td>2</td>
//
<tr> <td>3</td>
// </table>
//
<th>number</th>
<td>one</td>
<td>two</td>
<td>three</td>
</tr>
</tr>
</tr>
</tr>
11.2.5
number
one
two
three
Sometimes may need to off interpreting HTML tags (<i>, <b>, etc.) as formatting instructions, and
rather you want them to appear as literal <i>, <b> texts in the documentation. You can achieve this via
surrounding the text with the <nohtml>...</nohtml> tag. For example,
// Use the <nohtml><i></nohtml> tag (like <tt><nohtml><i>this</i></nohtml><tt>)
// to write in <i>italic</i>.
will be rendered as Use the <i> tag (like <i>this</i>) to write in italic.
<nohtml>...</nohtml> will also prevent opp_neddoc from hyperlinking words that are accidentally the
same as an existing module or message name. Prefixing the word with a backslash will achieve the same.
That is, either of the following will do:
196
11.2.6
You have to put the comments where nedtool will find them. This is a) above the documented item, or b)
after the documented item, on the same line.
If you put it above, make sure theres no blank line left between the comment and the documented item.
Blank lines detach the comment from the documented item.
Example:
// This is wrong! Because of the blank line, this comment is not
// associated with the following simple module!
simple Gen
parameters:
...
endsimple
Do not try to comment groups of parameters together. The result will be awkward.
11.2.7
The title page is the one that appears in the main frame after opening the documentation in the browser.
By default it contains a boilerplate text with the generic title OMNeT++ Model Documentation. You
probably want to customize that, and at least change the title to the name of the documented simulation
model.
You can supply your own version of the title page adding a @titlepage directive to a file-level comment
(a comment that appears at the top of a NED file, but is separated from the first import, channel,
module, etc. definition by at least one blank line). In theory you can place your title page definition into
any NED or MSG file, but it is probably a good idea to create a separate index.ned file for it.
The lines you write after the @titlepage line up to the next @page line (see later) or the end of the
comment will be used as the title page. You probably want to begin with a title because the documentation
tool doesnt add one (it lets you have full control over the page contents). You can use the <h1>..</h1>
HTML tag to define a title.
Example:
//
//
//
//
//
//
//
@titlepage
<h1>Ethernet Model Documentation</h1>
This documents the Ethernet model created by David Wu and refined by Andras
Varga at CTIE, Monash University, Melbourne, Australia.
11.2.8
You can add new pages to the documentation in a similar way as customizing the title page. The directive
to be used is @page, and it can appear in any file-level comment (see above).
197
You can create links to the generated pages using standard HTML, using the <a href="...">...</a>
tag. All HTML files are placed in a single directory, so you dont have to worry about specifying directories.
Example:
//
// @titlepage
// ...
// The structure of the model is described <a href="structure.html">here</a>.
//
11.2.9
You may want to create pages outside the documentation tool (e.g. using a HTML editor) and include them
in the documentation. This is possible, all you have to do is declare such pages with the @externalpage
directive in any of the NED files, and they will be added to the page index. The pages can then be linked
to from other pages using the HTML <a href="...">...</a> tag.
The @externalpage directive is similar in syntax @page:
// @externalpage filename.html, Title of the Page
The documentation tool does not check if the page exists or not. It is your responsibility to copy them
manually into the directory of the generated documentation and then to make sure the hyperlinks work.
11.3
Invoking opp_neddoc
11.3.1
Multiple projects
The generated tags.xml can be used to generate other documentation that refers to pages in this documentation via HTML links.
11.4
*.ned and *.msg files are collected (e.g. via the find command if you used the -a option on Unix) and
processed with nedtool. nedtool parses them and outputs the resulting syntax tree in XML a single
large XML file which contains all files.
The *.ned files are processed with the -c (export-diagrams-and-exit) option of gned. This causes gned
to export diagrams for the compound modules in Postscript. Postscript files are then converted to GIFs
using convert (part of the ImageMagick package). gned also exports an images.xml file which describes
which image was generated from which compound module, and also contains additional info (coordinates
of submodule rectangles and icons in the image) for creating clickable image maps.
199
200
Chapter 12
OMNeT++ supports parallel execution of large simulations. The following paragraphs provide a brief
picture of the problems and methods of parallel discrete event simulation (PDES). Interested readers are
strongly encouraged to look into the literature.
For parallel execution, the model is to be partitioned into several LPs (logical processes) that will be
simulated independently on different hosts or processors. Each LP will have its own local Future Event
Set, thus they will maintain their own local simulation times. The main issue with parallel simulations
is keeping LPs synchronized in order to avoid violating the causality of events. Without synchronization,
a message sent by one LP could arrive in another LP when the simulation time in the receiving LP has
already passed the timestamp (arrival time) of the message. This would break causality of events in the
receiving LP.
There are two broad categories of parallel simulation algorithms that differ in the way they handle causality problems outlined above:
1. Conservative algorithms prevents incausalities from happening. The Null Message Algorithm
exploits knowledge of the time when LPs send messages to other LPs, and uses null messages
to propagate this information to other LPs. If an LP knows it wont receive any messages from
other LPs until t + t simulation time, it may advance until t + t without the need for external
synchronization. Conservative simulation tends to converge to sequential simulation (slowed down
by communication between LPs) if theres not enough parallelism in the model, or parallelism is not
exploited by sending a sufficient number of null messages.
2. Optimistic synchronization allows incausalities to occur, but detects and repairs them. Repairing involves rollbacks to a previous state, sending out anti-messages to cancel messages sent out
during the period that is being rolled back, etc. Optimistic synchronization is extremely difficult to
implement, because it requires periodic state saving and the ability to restore previous states. In
any case, implementing optimistic synchronization in OMNeT++ would require in addition to a
more complicated simulation kernel writing significantly more complex simple module code from
the user. Optimistic synchronization may be slow in cases of excessive rollbacks.
12.2
OMNeT++ currently supports conservative synchronization via the classic Chandy-Misra-Bryant (or null
message) algorithm [CM79]. To assess how efficiently a simulation can be parallelized with this algorithm, well need the following variables:
201
12.3
12.3.1
Overview
This chapter presents the parallel simulation architecture of OMNeT++. The design allows simulation
models to be run in parallel without code modification it only requires configuration. The implementation relies on the approach of placeholder modules and proxy gates to instantiate the model on different
LPs the placeholder approach allows simulation techniques such as topology discovery and direct message sending to work unmodified with PDES. The architecture is modular and extensible, so it can serve
as a framework for research on parallel simulation.
The OMNeT++ design places a big emphasis on separation of models from experiments. The main rationale
is that usually a large number of simulation experiments need to be done on a single model before a
1 Notations:
202
12.3.2
We will use the Parallel CQN example simulation for demonstrating the PDES capabilities of OMNeT++.
The model consists of N tandem queues where each tandem consists of a switch and k single-server queues
203
12.3.3
When setting up a model partitioned to several LPs, OMNeT++ uses placeholder modules and proxy gates.
In the local LP, placeholders represent sibling submodules that are instantiated on other LPs. With
placeholder modules, every module has all of its siblings present in the local LP either as placeholder
or as the real thing. Proxy gates take care of forwarding messages to the LP where the module is
instantiated (see Figure 12.5).
The main advantage of using placeholders is that algorithms such as topology discovery embedded in the
model can be used with PDES unmodified. Also, modules can use direct message sending to any sibling
205
12.3.4
Configuration
Description
206
parallel-simulation
default: false
<true/false>
parsim-debug
=
<true/false>
default: true
parsim-mpicommunicationsmpibuffer
=
<bytes>
default: 256K * (numPartitions-1) +
16K
parsim-namedpipecommunicationsprefix
=
<string>
default: "omnetpp" or "comm/"
parsim-filecommunicationsprefix
=
<string>
default: "comm/"
parsim-filecommunicationspreserve-read
=
<true/false>
default: false
parsim-filecommunicationsread-prefix
=
<string>
default: "comm/read/"
parsim-nullmessageprotocol-lookaheadclass
=
<class
name
string>
default: "cLinkDelayLookahead"
parsim-nullmessageprotocollaziness
=
<0..1>
default: 0.5
parsim-idealsimulationprotocoltablesize
=
default: 100,000
<int>
(see below)
The above 3 options control the cFileCommunications class. By default, it deletes files
that were read. By enabling the "preserveread" setting, you can make it move read files
to another directory instead ("comm/read/" by
default). BEWARE: for mysterious reasons, it
appears that there cannot be more than about
19800 files in a directory. When that point is
reached, an exception is thrown somewhere inside the standard C library, which materializes itself in OMNeT++ as an "Error: (null)"
message... Strangely, this can be reproduced
in both Linux and Windows.
Selects the lookahead class for the Null Message Algorithm; the class must be subclassed
from cNMPLookahead.
Controls how often the Null Message Algorithm should send out null messages; the
value is understood in proportion to the lookahead, e.g. 0.5 means every lookahead/2 simsec.
Size of chunks (in table entries) in which the
external events file (recorded by cISPEventLogger) should be loaded. (one entry is 8
bytes, so 100,000 corresponds to 800K allocated memory)
When you are using cross-mounted home directories (the simulations directory is on a disk mounted on
all nodes of the cluster), a useful configuration setting is
207
12.3.5
Design of PDES support in OMNeT++ follows a layered approach, with a modular and extensible architecture. The overall architecture is depicted in Figure 12.7.
2 Unfortunately, support for state saving/restoration needs to be individually and manually added to each class in the simulation,
including user-programmed simple modules.
209
210
Chapter 13
Architecture
OMNeT++ has a modular architecture. The following diagram shows the high-level architecture of OMNeT++ simulations:
of dynamic (shared) libraries is also possible, but for simplicity well use the word linking here.
211
13.2
Embedding OMNeT++
This section discusses the issues of embedding the simulation kernel or a simulation model into a larger
application.
What youll absolutely need for a simulation to run is the Sim library. You probably do not want to keep
the appearance of the simulation program, so you do not want Cmdenv and Tkenv. You may or may not
want to keep Envir. You can keep Envir if its philosophy and the infrastructure it provides (omnetpp.ini,
212
13.3
There is little to say about Sim here, since chapters 4 and 6, and part of chapter 5 are all about this
topic. Classes covered in those chapters are documented in more detail in the API Reference generated by
Doxygen. What we can do here is elaborating on some internals that have not been covered in the general
chapters.
The source code for the simulation kernel and class library reside in the src/sim/ subdirectory.
13.3.1
The global simulation object is an instance of cSimulation. It stores the model, and encapsulates
much of the functionality of setting up and running a simulation model.
simulation has two basic roles:
it stores modules of the executing model
it holds the future event set (FES) object
13.3.2
13.4
All model components (simple module definitions and their C++ implementations, compound module
types, channels, networks, message types, etc.) that you compile and link into a simulation program
213
Available modules:
FDDI_MAC
FDDI_MAC4Ring
...
Available channels:
...
End run of OMNeT++
Information on components are kept on registration lists. There are macros for registering components
(that is, for adding them to the registeration lists): Define_Module(), Define_Module_Like(), Define_Network(), Define_Function(), Register_Class(), and a few others. For components defined in NED files, the macro calls are generated by the NED compiler; in other cases you have to write
them in your C++ source.
Let us see the module registrations as an example. The
Define_Module(FIFO);
macro expands to the following code:
static cModule *FIFO__create(const char *name, cModule *parentmod)
{
return new FIFO(name, parentmod);
}
EXECUTE_ON_STARTUP( FIFO__mod,
modtypes.instance()->add(
new cModuleType("FIFO","FIFO",(ModuleCreateFunc)FIFO__create)
);
)
When the simulation program starts up, a new cModuleType object will be added to the modtypes object,
which holds the list of available module types. The cModuleType object will act as a factory: when its
create() method is called it will produce a new module object of class FIFO via the above static function
FIFO__create.
The cModuleType object also stores the name of the corresponding NED module declaration. This makes
it possible to add the gates and parameters declared in NED to the module when it is created.
214
Macro/
Objects on list
Define_Network()
cNetworkType
modtypes
Define_Module(),
Define_Module_Like(),
cModuleType
channeltypesDefine_Channel()
classes
cChannelType
Register_Class()
cClassRegister
functions
Define_Function()
cFunctionType
13.5
Function
List of available networks. Every cNetworkType object is a factory for a specific network type. That is, a cNetworkType object has methods for setting up a specific network. Define_Network() macros occur in
the code generated by the NED compiler.
List of available module types. Every cModuleType object is a factory for a specific module type. Usually, Define_Module() macros
for compound modules occur in the code generated by the NED compiler; for simple modules, the Define_Module() lines are added
by the user.
List of channel types. Every cChannelType
object acts as a factory for a channel type, a
class derived from cChannel.
List of available classes of which one can
create an instance. Every cClassRegister object is a factory for objects of a specific class. The list is used by the createOne() function: it can create an object of any class, given the class name as
a string. (E.g. the statement ptr = createOne("cArray") creates a cArray object.) To enable a class to work with createOne(), one has to register it using the
Register_Class(classname) macro
List of functions taking doubles and returning a double (see type MathFuncNoArg...MathFunc3Args). A cFunctionType object holds a pointer to the function
and knows how many arguments it takes.
The source code for the user interface of OMNeT++ resides in the src/envir/ directory (common part)
and in the src/cmdenv/, src/tkenv/ directories.
The classes in the user interface are not derived from cObject, they are completely separated from the
simulation kernel.
13.5.1
The main() function of OMNeT++ simply sets up the user interface and runs it. Actual simulation is
done in cEnvir::run() (see later).
215
13.5.2
The cEnvir class has only one instance, a global object called ev:
cEnvir ev;
cEnvir basically a facade, its member functions contain little code. cEnvir maintains a pointer to a
dynamically allocated simulation application object (derived from TOmnetApp, see later) which does all
actual work.
cEnvir member functions perform the following groups of tasks:
I/O for module activities; the actual implementation is different for each user interface (e.g. stdin/stdout
for Cmdenv, windowing in Tkenv)
cEnvir provides methods for the simulation kernel to access configuration information (for example,
module parameter settings)
cEnvir also provides methods that are called by simulation kernel to notify the user interface of
certain events (an object was deleted; a module was created or deleted; a message was sent or
delivered, etc.)
13.5.3
Customizing Envir
Certain aspects of Envir can be customized via plugin interfaces. The following plugin interfaces are
supported:
cRNG. Interface for the random number generator.
cScheduler. The scheduler class. This plugin interface allows for implementing real-time, hardwarein-the-loop, distributed and distributed parallel simulation.
cConfiguration. It defines a class from which all configuration will be obtained. In other words,
it option lets you replace omnetpp.ini with some other implementation, e.g. database input.
cOutputScalarManager. It handles recording the scalar output data, output via the cModule::recordScalar()
family of functions. The default output scalar manager is cFileOutputScalarManager, defined
in the Envir library.
cOutputVectorManager. It handles recording the output for cOutVector objects. The default
output vector manager is cFileOutputVectorManager, defined in the Envir library.
cSnapshotManager. It provides an output stream to which snapshots are written (see section
6.10.5). The default snapshot manager is cFileSnapshotManager, defined in the Envir library.
The classes (cRNG, cScheduler, etc.) are documented in the API Reference.
To actually implement and select a plugin for use:
1. Subclass the given interface class (e.g. for a custom RNG, cRNG) to create your own version.
2. Register the class by putting the Register_Class(MyRNGClass) line into the C++ source.
3. Compile and link your interface class into the OMNeT++ simulation executable. IMPORTANT: make
sure the executable actually contains the code of your class! Over-optimizing linkers (esp. on Unix)
tend to leave out code to which there seem to be no external reference.
216
13.5.4
The base class for simulation application is TOmnetApp. Specific user interfaces such as TCmdenv, TOmnetTkApp are derived from TOmnetApp.
TOmnetApps member functions are almost all virtual.
Some of them implement the cEnvir functions (described in the previous section)
Others implement the common part of all user interfaces (for example: reading options from the
configuration files; making the options effective within the simulation kernel)
The run() function is pure virtual (it is different for each user interface).
TOmnetApps data members:
a pointer to the object holding configuration file contents (type cInifile);
217
218
Appendix A
meaning
0 or 1 time a
a
1 or more times a, separated by commas
1 or more times a, separated by spaces
a or b
the character a
keyword
identifier
networkdescription ::=
{ definition... }
definition
::=
include
| channeldefinition
| simpledefinition
| moduledefinition
| networkdefinition
include ::=
include { fileName ,,, } ;
channeldefinition ::=
219
:
:
:
:
:
const [ numeric ]
string
bool
char
anytype
gateblock ::=
gates:
[ in: { gate ,,, } ; ]
[ out: { gate ,,, } ; ]
gate ::=
gatename [ [] ]
submodblock ::=
submodules: { submodule... }
submodule ::=
{ submodulename : moduletype [ vector ]
[ substparamblock... ]
[ gatesizeblock... ] }
| { submodulename : parametername [ vector ] like moduletype
[ substparamblock... ]
[ gatesizeblock... ] }
substparamblock
::=
parameters [ if expression ]:
220
gate ::=
[ modulename [vector]. ] gatename [vector]
networkdefinition ::=
network networkname : moduletype
[ substparamblock ]
endnetwork
vector ::=
[ expression ]
parexpression ::=
expression | otherconstvalue
expression
::=
expression +
| expression | expression *
| expression /
| expression %
| expression ^
expression
expression
expression
expression
expression
expression
221
expression == expression
expression != expression
expression < expression
expression <= expression
expression > expression
expression >= expression
expression ? expression : expression
expression and expression
expression or expression
not expression
( expression )
functionname ( [ expression ,,, ] )
- expression
numconstvalue
inputvalue
[ ancestor ] [ ref ] parametername
sizeof ( gatename )
index
numconstvalue ::=
integerconstant | realconstant | timeconstant
otherconstvalue ::=
characterconstant
| "stringconstant"
| true
| false
inputvalue ::=
input ( default , "prompt-string" )
default ::=
expression | otherconstvalue
222
References
[BT00]
R. L. Bagrodia and M. Takai. Performance Evaluation of Conservative Algorithms in Parallel Simulation Languages. 11(4):395414, 2000.
[CM79]
M. Chandy and J. Misra. Distributed Simulation: A Case Study in Design and Verification
of Distributed Programs. IEEE Transactions on Software Engineering, (5):440452, 1979.
[EHW02]
K. Entacher, B. Hechenleitner, and S. Wegenkittl. A Simple OMNeT++ Queuing Experiment Using Parallel Streams. PARALLEL NUMERICS02 - Theory and Applications,
pages 89105, 2002. Editors: R. Trobec, P. Zinterhof, M. Vajtersic and A. Uhl.
[EPM99]
[For94]
[Gol91]
David Goldberg. What Every Computer Scientist Should Know About Floating-Point Arithmetic. ACM Computing Surveys, 23(1):548, 1991.
[Hel98]
P. Hellekalek. Dont Trust Parallel Monte Carlo. ACM SIGSIM Simulation Digest,
28(1):8289, jul 1998. Authors page is a great source of information, see http://random.
mat.sbg.ac.at/.
[HPvdL95]
Jan Heijmans, Alex Paalvast, and Robert van der Leij. Network Simulation Using the JAR
Compiler for the OMNeT++ Simulation System. Technical report, Technical University of
Budapest, Dept. of Telecommunications, 1995.
[Jai91]
Raj Jain. The Art of Computer Systems Performance Analysis. Wiley, New York, 1991.
[JC85]
Raj Jain and Imrich Chlamtac. The P 2 Algorithm for Dynamic Calculation of Quantiles
and Histograms without Storing Observations. Communications of the ACM, 28(10):1076
1085, 1985.
[Kof95]
Stig Kofoed. Portable Multitasking in C++. Dr. Dobbs Journal, November 1995. Download
source from http://www.ddj.com/ftp/1995/1995.11/mtask.zip/.
[LAM]
[Len94]
Gbor Lencse. Graphical Network Editor for OMNeT++. Masters thesis, Technical University of Budapest, 1994. In Hungarian.
[LSCK02]
P. LEcuyer, R. Simard, E. J. Chen, and W. D. Kelton. An Objected-Oriented RandomNumber Package with Many Long Streams and Substreams. Operations Research,
50(6):10731075, 2002.
Source code can be downloaded from http://www.iro.
umontreal.ca/~lecuyer/papers.html.
223
[MvMvdW95] Andr Maurits, George van Montfort, and Gerard van de Weerd. OMNeT++ Extensions
and Examples. Technical report, Technical University of Budapest, Dept. of Telecommunications, 1995.
[OF00]
Hong Ong and Paul A. Farrell. Performance Comparison of LAM/MPI, MPICH and MVICH
on a Linux Cluster Connected by a Gigabit Ethernet Network. In Proceedings of the 4th
Annual Linux Showcase & Conference, Atlanta, October 10-14, 2000. The USENIX Association, 2000.
[PFS86]
[PJL02]
K. Pawlikowski, H. Jeong, and J. Lee. On Credibility of Simulation Studies of Telecommunication Networks. IEEE Communications Magazine, pages 132139, jan 2002.
[Pon91]
Gyrgy Pongor. OMNET: An Object-Oriented Network Simulator. Technical report, Technical University of Budapest, Dept. of Telecommunications, 1991.
[Pon92]
[Pon93]
[Qua]
[SVE03]
Y. Ahmet Sekercio
glu,
Andrs Varga, and Gregory K. Egan. Parallel Simulation Made
Easy with OMNeT++. In Proceedings of the European Simulation Symposium (ESS 2003),
26-29 Oct, 2003, Delft, The Netherlands. International Society for Computer Simulation,
2003.
[Var92]
[Var94]
Andrs Varga. Portable User Interface for the OMNeT++ Simulation System. Masters
thesis, Technical University of Budapest, 1994. In Hungarian.
[Var98a]
Andrs Varga. K-split On-Line Density Estimation for Simulation Result Collection. In
Proceedings of the European Simulation Symposium (ESS98), Nottingham, UK, October
26-28. International Society for Computer Simulation, 1998.
[Var98b]
[Var99]
Andrs Varga. Using the OMNeT++ Discrete Event Simulation System in Education.
IEEE Transactions on Education, 42(4):372, November 1999. (on CD-ROM issue; journal
contains abstract).
[Vas96]
Andrs Varga and Babak Fakhamzadeh. The K-Split Algorithm for the PDF Approximation of Multi-Dimensional Empirical Distributions without Storing Observations. In Proceedings of the 9th European Simulation Symposium (ESS97), Passau, Germany, October
19-22, 1997, pages 9498. International Society for Computer Simulation, 1997.
[VP97]
Andrs Varga and Gyrgy Pongor. Flexible Topology Description Language for Simulation
Programs. In Proceedings of the 9th European Simulation Symposium (ESS97), Passau,
Germany, October 19-22, 1997, pages 225229, 1997.
[VSE03]
glu,
and Gregory K. Egan. A practical efficiency criterion
for the null message algorithm. In Proceedings of the European Simulation Symposium
(ESS 2003), 26-29 Oct, 2003, Delft, The Netherlands. International Society for Computer
Simulation, 2003.
[Wel95]
225
Index
./fifo1, 150
#include, 95
OMNeT++_neddoc, 195
byteLength(), 82
cAccuracyDetection, 126
cADByStddev, 126
callFinish(), 79
abstract, 98
callInitialize(), 58, 77
accuracy detection, 126
activity(), 38, 39, 43, 44, 4749, 5256, 67, 68, 77, cancelAndDelete(msg), 44
cancelEvent(), 48, 52, 68, 69, 83
129, 164, 166, 174, 175
cancelRedirection(), 116
addBinBound(), 123
cArray, 86, 87, 103, 105, 112, 113, 140, 141
addObject(), 86
cauchy(a, b, rng=0), 28, 109
addPar(), 87
cBasicChannel, 72, 79
Akaroa, 171
cChannel, 72, 79, 215
animation-delay, 167
cChannelType, 215
animation-enabled, 167
cClassRegister, 215
animation-msgcolors, 167
cCompoundModule, 38
animation-msgnames, 167
cConfiguration, 154, 216, 217
animation-speed, 167
cCoroutine, 213
arrival time, 37, 39, 41, 42
cDensityEstBase, 120
arrivalGate(), 84
cdf(), 122
arrivalGateId(), 84
cDisplayString, 185
arrivalModuleId(), 84
cDoubleExpression, 114, 115
arrivalTime(), 83, 84
cDoubleHistogram, 103, 110, 120
arrivedOn, 84
cell(), 122
asVector(), 71
cellPDF(), 122
autoflush, 163
cells, 81
average(), 29
cells(), 122
awk, 8, 35, 189
cEnvir, 103, 213, 216, 217
basepoint(), 122
cEnvir::run(), 215
bernoulli(p, rng=0), 29, 109
cFileCommunications, 207
beta(alpha1, alpha2, rng=0), 28, 109
cFileOutputScalarManager, 154, 216
binary heap, 39
cFileOutputVectorManager, 154, 216
binary tree, 31
cFileSnapshotManager, 154, 216
binomial(n, p, rng=0), 29, 110
cFSM, 60
bit error, 41
cFunctionType, 215
bool, 90
cGate, 64, 71, 75, 79, 80
bool(), 115
chain, 31
boolValue(), 115
channel, 11, 12, 21
breakpoint, 133
datarate, 12, 40, 79
breakpoint(), 133, 167
definition, 12
breakpointHit(), 218
definitions, 11
breakpoints-enabled, 167
delay, 12, 40, 79
bubble(), 186
error, 12, 40, 79
buildInside(), 77, 78
name, 21
226
cQueue::Iterator, 111
create(), 77, 78
CreateFiber(), 213
createOne(), 135, 136, 215
creationTime(), 83, 84
cRNG, 108, 109, 153, 216
cScheduler, 154, 216
cSequentialScheduler, 154
cSimpleModule, 38, 42, 43, 47, 67, 79, 128, 213
cSimulation, 75, 212, 213
cSnapshotManager, 154, 216
cStatictic, 190
cStatistic, 114, 115, 120, 126, 128
cStdDev, 103, 120, 122, 190
cStringTokenizer, 70, 71
cSubModIterator, 75
cTDExpandingWindows, 126
cTopology, 103, 116119
cTopology::Link, 117, 118
cTopology::LinkIn, 118
cTopology::LinkOut, 117119
cTopology::Node, 117, 118
cTransientDetection, 126
customization, 211
cVarHistogram, 103, 110, 120, 123
cWeightedStdDev, 120
cWeightedStddev, 103
cXMLElement, 26, 115
cXMLElement*(), 115
data rate, 41
data rate change, 73
dblrand(), 108, 109
debug-on-errors, 153
debugging, 146
decapsulate(), 85
default-run, 166
defaultOwner(), 141
Define_Channel(), 215
Define_Function(), 29, 214, 215
Define_Function2(), 29
Define_Module(), 42, 77, 214, 215
Define_Module_Like(), 42, 214, 215
Define_Network(), 214, 215
delayed sending, 66
deleteModule(), 79
density estimation, 187
destinationGate(), 74
detect(), 126
Dijkstra algorithm, 118
disable(), 119
disconnect(), 80
discrete event simulation, 37
display strings, 177
227
FES, 38, 39, 42, 54, 55, 57, 68, 69, 77, 81, 139, 164,
165, 209, 213
fflush(stdout), 163
fifo1, 150
fifo1.vec, 150, 151
fifonet1, 150
filtering results, 187
finalize(), 58
find, 155
findGate(), 72
findPar(), 87
findSubmodule(), 75
finish(), 38, 39, 44, 47, 49, 55, 57, 58, 79, 128, 134,
151, 173
finite state machine, 52, 59
fname-append-host, 153
FooPacket, 90, 96, 97
FooPacket_Base, 96, 97
for(), 61
forEachChild(), 134, 135, 138, 139
frames, 81
freq, 101, 102
fromGate(), 73, 75
FSM, 52, 59, 61
nested, 60
FSM_DEBUG, 61
FSM_Goto(), 60
FSM_Print(), 61
FSM_Switch(), 60, 61
fullName(), 105
fullPath(), 105
functions
user-defined, 29
future events, 38
gamma_d(alpha, beta, rng=0), 28, 109
gate, 6, 1315, 42, 71
busy condition, 41, 73
conditional, 19
destination, 74
id, 72
vector, 14, 25, 71
size, 19
vector index, 71
vector size, 71
gate(), 71
gateSize(), 71
gdb, 166
geometric(p, rng=0), 29, 110
get, 90
getObject(), 86
global variables, 47
gned
mouse bindings, 184
228
LD_LIBRARY_PATH, 146
length(), 82, 111
link, 6
handleMessage(), 38, 39, 43, 4753, 59, 60, 67, 68, link delay, 42
127, 175
load-libs, 153
handleParameterChange, 47
loadFromFile(), 123
handleParameterChange(), 58
localGate(), 118
hasMoreTokens(), 71
localGateId(), 118
hasObject(), 86
lognormal(m, s, rng=0), 28, 109
hasPar(), 87
long, 90
head(), 111
long(), 115
histogram, 187
LongHistogram, 120
equal-sized, 120
longjmp(), 213
equiprobable-cells, 120
longValue(), 115
range estimation, 121
main(), 211, 215
id(), 72, 74
make, 145
import directives, 11
Makefile, 145
index, vi, 25
dependencies, 145
index(), 71, 74
math operators, 23
info(), 138
max(), 120
ini file, 114, 162
mean(), 120
file inclusion, 151
message, 39, 81, 93
wildcards, 156
attaching non-object types, 87
ini-warnings, 152
attaching objects, 86
initial events, 38
cancelling, 69
initialization, 58
data members, 82
multi-stage, 58
duplication, 82
initialize(), 38, 39, 44, 4750, 52, 53, 5558, 77, 78,
encapsulation, 85
127, 129
error flag, 82
initialize(int stage), 58
exchanging, 6
inLinks(), 118
kind, 82
input, 150, 152
priority, 39, 82
input flag, 114
time stamp, 82
insert(), 110, 111
length,
82
insertAfter(), 111
message definitions, 143
insertBefore(), 111
message-trace, 163
int, 9092, 97
method calls
intrand(), 109
between modules, 76
intrand(n), 108
methodcalls-delay, 167
intuniform(), 110
min(), 120
intuniform(a, b, rng=0), 28, 109
model
IPAddress, 94, 95
time, 37
isBusy(), 64, 72
module
isConnected(), 73
accessing parameters, 70
isConstant(), 113
array, 16
isNumeric(), 114
as parameter, 17
isRedirected(), 116
communication, 70
isScheduled(), 68, 83
compound, 5, 6, 11, 30, 220
isSelfMessage(), 68, 83
definition, 15
isVector(), 71
deletion, 79
items(), 113
parameters, 15
iter(), 75
patterns, 32
constructor, 48, 57
k, 91
229
keywords, 11
bool, 13
connections, 20
const, 13
display, 177
for, 21, 22
gates, 14
gatesizes, 19
if, 22
import, 12
include, 12
like, 17, 18, 33
nocheck, 22, 23, 32
numeric, 13
ref, 70
string, 13
submodules, 16
xml, 13
language, 2, 11, 219
nested for statements, 22
network definition, 23
parameters, 19
sizeof(), 25
nedc, 36
nedtool, 35, 36, 88, 147
negbinomial(n, p, rng=0), 29, 110
netPack(), 135
netUnpack(), 135
network, 152
definitions, 11
description, 11
list of, 215
new cArray(*this), 141
nextToken(), 70
nodeFor(), 118
nodes(), 118
noncobject, 95
normal(), 103
normal(mean, stddev, rng=0), 28, 109
num-rngs, 153
numeric constants, 24
numInitStages(), 58
object
copy, 105
duplication, 105
objectDeleted(), 218
objectValue(), 116
omnetpp.ini, 8, 108, 149, 151, 154, 162, 166
omnetpp.sca, 190, 191
omnetpp.sna, 132
OMNETPP_BITMAP_PATH, 168
OMNETPP_TKENV_DIR, 168
operator=(), 86, 90, 97, 105, 141
230
233