Configuring Simulations¶
The Configuration File¶
Configuration and input data for the simulation are in
a configuration file usually called omnetpp.ini.
An Example¶
For a start, let us see a simple omnetpp.ini file which
can be used to run the Fifo example simulation.
[General]
network = FifoNet
sim-time-limit = 100h
cpu-time-limit = 300s
#debug-on-errors = true
#record-eventlog = true
[Config Fifo1]
description = "low job arrival rate"
**.gen.sendIaTime = exponential(0.2s)
**.gen.msgLength = 100b
**.fifo.bitsPerSec = 1000bps
[Config Fifo2]
description = "high job arrival rate"
**.gen.sendIaTime = exponential(0.01s)
**.gen.msgLength = 10b
**.fifo.bitsPerSec = 1000bps
The file is grouped into sections named [General], [Config Fifo1]
and [Config Fifo2], each containing several entries.
File Syntax¶
An OMNeT++ configuration file is a line-oriented text file. The encoding is primarily ASCII, but non-ASCII characters are permitted in comments and string literals. This allows for using encodings that are a superset of ASCII, for example ISO 8859-1 and UTF-8. There is no limit on the file size or on the line length.
Comments may be placed at the end of any line after a hash mark, “#”. Comments extend to the end of the line and are ignored during processing. Blank lines are also allowed and are ignored.
Long lines can be broken into multiple lines in two ways: using the traditional trailing backslash notation also found in C/C++, or alternatively, by indenting the continuation lines.
When using the former method, the rule is that if the last character of a line is “\”, it will be joined with the next line after removing the backslash and the newline. (Potential leading whitespace on the second line is preserved.) Note that this allows breaking the line even in the middle of a name, number or string constant.
When using the latter method, a line can be broken between any two tokens by inserting a newline and indenting the next line. An indented line is interpreted as a continuation of the previous line. The first line and indented lines that follow it are then parsed as a single multi-line unit. Consequently, this method does not allow breaking a line in the middle of a word or inside string constants.
The two ways of breaking lines can be freely combined.
There are three types of lines: section heading lines, key-value lines, and directive lines:
Section heading lines contain a section name enclosed in square brackets.
Key-value lines have the <key>=<value> syntax; spaces are allowed (but not required) on both sides of the equal sign. If a line contains more than one equal sign, the leftmost one is taken as the key-value separator.
Currently there is only one kind of directive line, include. An include line starts with the
includeword, followed by the name of the file to be included.
Key-value lines may not occur above the first section heading line (except in included files, see later).
Keys may be further classified based on syntax alone:
Keys that do not contain dots represent global or per-run configuration options.
If a key contains a dot, its last component (substring after the last dot) is considered. If the last component contains a hyphen or is equal to
typename, the key represents a per-object configuration option.Otherwise, the key represents a parameter assignment. Thus, parameter assignment keys contain a dot and no hyphen after the last dot.
An example:
# This is a comment line
[General] # section heading
network = Foo # configuration option
debug-on-errors = false # another configuration option
**.app*.typename = "HttpClient" # per-object configuration option
**.app*.generationInterval = 3s # parameter value
**.app*.requestURL = "http://www.example.com/this-is-a-very-very-very-very\
-very-long-url?q=123456789" # a two-line parameter value
File Inclusion¶
OMNeT++ supports including an ini file in another,
via the include keyword. This feature allows one to partition a large ini
file into logical units, fixed and varying parts, etc.
An example:
# omnetpp.ini
# ...
include params1.ini
include params2.ini
include ../common/config.ini
# ...
One can also include files from other directories. If the included ini file further includes other files, their path names will be understood as relative to the location of the file which contains the reference, rather than relative to the current working directory of the simulation.
This rule also applies to other file names occurring in ini files (such as
the load-libs, output-vector-file,
output-scalar-file, etc. options, and xmldoc() module
parameter values.)
In included files, it is allowed to have key-value lines without first having a section heading line. File inclusion is conceptually handled as text substitution, except that a section heading in an included file will not change the current section of the main file. The following example illustrates the rules:
# incl.ini
foo1 = 1 # no preceding section heading: these lines will go into
foo2 = 2 # whichever section the file is included into
[Config Bar]
bar = 3 # this will always go into [Config Bar]
# omnetpp.ini
[General]
include incl.ini # adds foo1/foo2 to [General], and defines [Config Bar] w/ bar
baz1 = 4 # include files don't change the current section, so these
baz2 = 4 # lines still belong to [General]
Note
The concept of file inclusion implies that include files may not make sense on their own. Thus, when an included ini file is opened in the ini editor in the IDE, file contents may be flagged with errors and warnings. These errors/warnings disappear when the file is viewed as part of its main file.
Sections¶
An ini file may contain a [General] section, and several
[<configname>] or [Config <configname>] sections. The use of the
Config prefix is optional, i.e. [Foo] and [Config Foo] are
equivalent.
The order of the sections is not significant.
The [General] Section¶
The most commonly used options of the [General] section are the
following.
The
networkoption selects the model to be set up and run.The length of the simulation can be set with the
sim-time-limitand thecpu-time-limitoptions (the usual time units such as ms, s, m, h, etc. can be used).
Note that the NED files loaded by the simulation may contain several
networks, and any of them may be specified in the network
option.
Named Configurations¶
Named configurations are in sections of the form [Config <configname>] or
[<configname>] (the Config word is optional), where <configname>
is by convention a camel-case string that starts with a capital letter:
Config1, WirelessPing, OverloadedFifo, etc. For example,
omnetpp.ini for an Aloha simulation might have the following skeleton:
[General]
# ...
[Config PureAloha]
# ...
[Config SlottedAloha1]
# ...
[Config SlottedAloha2]
# ...
Some configuration options (such as user interface selection) are only
accepted in the [General] section, but most of them can go into Config
sections as well.
When a simulation is run, one needs to select one of the configurations to be activated. In Cmdenv, this is done with the -c command-line option:
$ aloha -c PureAloha
The simulation will then use the contents of the [Config PureAloha]
section to set up the simulation. (Qtenv, of course, lets the user choose
the configuration from a dialog.)
Section Inheritance¶
When the PureAloha configuration is activated, the contents of
the [General] section will also be taken into account: if some
configuration option or parameter value is not found in [Config PureAloha],
then the search will continue in the [General] section. In
other words, lookups in [Config PureAloha] will fall back to [General].
The [General] section itself is optional; when it is absent, it is
treated like an empty [General] section.
All named configurations fall back to [General] by default. However, for
each configuration it is possible to specify the fallback section or a list of
fallback sections explicitly, using the extends key. Consider the
following ini file skeleton:
[General]
# ...
[Config SlottedAlohaBase]
# ...
[Config LowTrafficSettings]
# ...
[Config HighTrafficSettings]
# ...
[Config SlottedAloha1]
extends = SlottedAlohaBase, LowTrafficSettings
# ...
[Config SlottedAloha2]
extends = SlottedAlohaBase, HighTrafficSettings
# ...
[Config SlottedAloha2a]
extends = SlottedAloha2
# ...
[Config SlottedAloha2b]
extends = SlottedAloha2
# ...
When SlottedAloha2b is activated, lookups will consider sections in the
following order (this is also called the section fallback chain):
SlottedAloha2b, SlottedAloha2, SlottedAlohaBase,
HighTrafficSettings, General.
The effect is the same as if the contents of the sections
SlottedAloha2b, SlottedAloha2, SlottedAlohaBase, HighTrafficSettings and
General were copied together into one section, one after another,
[Config SlottedAloha2b] being at the top, and [General]
at the bottom. Lookups always start at the top, and stop at the first
matching entry.
The order of the sections in the fallback chain is computed using the C3 linearization algorithm ((Barrett et al. 1996)):
The fallback chain of a configuration A is
if
Adoes not have anextendskey thenA,Generalotherwise the merge of the configurations enumerated in the
extendskey, and all of their fallback section chains. The merge is monotonic: if some configurationXprecedes configurationYin one of the input chains, it will precede it in the output chain too.
The section fallback chain can be printed by the -X option of the command line of the simulation program:
$ aloha -X SlottedAloha2b
OMNeT++ Discrete Event Simulation
...
Config SlottedAloha2b
Config SlottedAloha2
Config SlottedAlohaBase
Config HighTrafficSettings
General
The section fallback concept is similar to multiple inheritance in object-oriented languages, and the benefits are similar too; one can factor out the common parts of several configurations into a “base” configuration, and additionally, one can reuse existing configurations without copying, by using them as a base. In practice, one will often have “abstract” configurations too (in the C++/Java sense), which assign only a subset of parameters and leave the others open, to be assigned in derived configurations.
When experimenting with a lot of different parameter settings for a simulation model, file inclusion and section inheritance can make it much easier to manage ini files.
Assigning Module Parameters¶
Simulations receive 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 of 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
(hierarchical names). This name consists of the dot-separated list of
the module names (from the top-level module down to the module containing
the parameter), plus the parameter name (see section Hierarchical Name).
An example omnetpp.ini which sets the numHosts parameter of
the top-level module and the transactionsPerSecond parameter of the
server module:
[General]
Network.numHosts = 15
Network.server.transactionsPerSecond = 100
Typename pattern assignments are also accepted:
[General]
Network.host[*].app.typename = "PingApp"
Using Wildcard Patterns¶
Models can have a large number of parameters to be configured, and it would
be tedious to set them one-by-one in omnetpp.ini. OMNeT++ supports
wildcard patterns which allow setting several model parameters
at once. The same pattern syntax is used for per-object configuration options;
for example <object-path-pattern>.record-scalar, or <module-path-pattern>.rng-<N>.
The pattern syntax is a variation on Unix glob-style
patterns. The most apparent differences from globbing rules are the
distinction between * and **, and that character ranges should
be written with curly braces instead of square brackets, i.e.,
any-letter is expressed as {a-zA-Z} and not as
[a-zA-Z], because square brackets are 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 characters (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, inclusive; both limits are optional[38..150]: index range: any number in square brackets in the range 38..150, inclusive; both limits are optionalbackslash (\) : takes away the special meaning of the subsequent character
Precedence¶
The order of entries is very important with wildcards. When a key matches several wildcard patterns, the first matching occurrence is used. This means that one needs to list specific settings first, and more general ones later. Catch-all settings should come last.
An example ini file:
[General]
*.host[0].waitTime = 5ms # specifics come first
*.host[3].waitTime = 6ms
*.host[*].waitTime = 10ms # catch-all comes last
Asterisk vs Double Asterisk¶
The * wildcard is for matching a single module or parameter name in the
path name, while ** can be used to match several components in the path.
For example, **.queue*.bufSize matches the bufSize parameter of any module
whose name begins with queue in the model, while *.queue*.bufSize
or net.queue*.bufSize selects only queues immediately on the network level.
Also note that **.queue**.bufSize would match net.queue1.foo.bar.bufSize
as well!
Sets, Negated Sets¶
Sets and negated sets can contain several character ranges and also
enumerations of characters. For example, {_a-zA-Z0-9} matches any letter
or digit, plus the underscore; {xyzc-f} matches any of the characters
x, y, z, c, d, e, f.
To include ’-’ in the set, put it in a position where it cannot be
interpreted as a character range, for example: {a-z-} or {-a-z}.
To include ’}’ in the set, it must be the first character: {}a-z},
or as a negated set: {^}a-z}.
A backslash is always taken as a literal backslash (and not as an escape
character) within set definitions.
Numeric Ranges and Index Ranges¶
Only nonnegative integers can be matched. The start or the end of the range
(or both) can be omitted: {10..}, {..99} or {..}
are valid numeric ranges (the last one matches any number).
The specification must use exactly two dots.
Caveat: *{17..19} will match a17, 117 and 963217 as well,
because the * can also match digits!
An example of numeric ranges:
[General]
*.*.queue[3..5].bufSize = 10
*.*.queue[12..].bufSize = 18
*.*.queue[*].bufSize = 6 # this will only affect queues 0,1,2 and 6..11
Using the Default Values¶
It is also possible to utilize the default values specified in the NED files.
The <parameter-fullpath>=default setting assigns
the default value to a parameter if it has one.
The <parameter-fullpath>=ask setting will try to get the parameter
value interactively from the user.
If a parameter was not set but has a default value, that value will be
assigned. This is like having a **=default line at the
bottom of the [General] section.
If a parameter was not set and has no default value, that will either cause an error or will be interactively prompted for, depending on the particular user interface.
Note
In Cmdenv, one must explicitly enable interactive mode with the --cmdenv-interactive=true option, otherwise the simulation program will stop with an error in the setup phase.
More precisely, parameter resolution takes place as follows:
If the parameter is assigned in NED, it cannot be overridden in the configuration. The value is applied and the process finishes.
If the first match is a value line (matches <parameter-fullpath>
=<value>), the value is applied and the process finishes.If the first match is a <parameter-fullpath>
=defaultline, the default value is applied and the process finishes.If the first match is a <parameter-fullpath>
=askline, the parameter will be asked from the user interactively (UI dependent).If there was no match and the parameter has a default value, it is applied and the process finishes.
Otherwise, the parameter is declared unassigned, and handled accordingly by the user interface. It may be reported as an error, or may be asked from the user interactively.
Entries That Have No Effect¶
An entry that does not match any parameter does not cause an error by itself, so a mistake in an entry may go unnoticed, and the simulation runs with a different value than intended. Common causes are:
a typo in the parameter name or in the module path;
a parameter or submodule that has been renamed, moved or removed, for example in a new version of a model library;
a module type, selected for example with a
typenameentry, that does not have the parameter;an earlier entry that matches the same parameter and therefore takes precedence, or a value assigned in NED, which cannot be overridden.
Therefore, the simulation reports the entries that have had no effect in the run, that is, that no parameter or other setting has been read from. By default, they are reported as a warning at the end of the run, together with their locations:
<!> Warning: 2 configuration entries have had no effect in this run (see the
unused-entry-check option):
**.app[*].sendIntv = 2s -- omnetpp.ini:42
**.host.mac.address = "auto" -- omnetpp.ini:57
The unused-entry-check option controls what happens with them:
off, warning (the default) or error. With error,
the run fails. The unused-entry-check-time option controls when
the check is done: at the end of the run (the default), after the
finish() methods have been called, or after network
initialization, before the simulation starts. Checking early reports
mistakes before a possibly long simulation, but it also reports entries
meant for modules that are created later during the simulation.
The check covers parameter assignments and the typename,
display-string, display-name and rng-N options. It does
not cover result recording options such as vector-recording, because
those are read only when results are recorded, or not at all if a result is
not produced. An entry is not reported if the same key also occurs earlier
in the configuration, as that is the normal way of overriding an entry,
for example in a section that extends [General]. The check is skipped
in parallel simulation.
Some entries have no effect in a run by design, and are reported nevertheless:
entries in a shared section such as
[General]that are meant for another configuration’s network. Move them into the configuration that uses them;entries in a parameter study that apply only to some runs, for example to only one of the module types that an iteration variable selects with
typename;entries that only serve to define an iteration variable, for example
**.dummy = ${x=1,2,3};entries for optional parts of a model that are not present in the current build.
In such cases, consider the entries one by one, and if needed, set
unused-entry-check to off for the configurations
concerned.
The check finds entries that do not reach any parameter, but it does not
tell whether the model actually reads the parameters that the entries set.
That is the other half of the chain from configuration entry to model
code, and the print-unused-parameters option covers it: at the
end of the run, it prints the parameters of each NED type that no instance
of the type has read. Whether a parameter is read often depends on the
configuration, for example on which features of the model are enabled, so
this list is a report for model developers rather than a warning.
Parameter Studies¶
It is quite common in simulation studies that the simulation model is run several times with different parameter settings, and the results are analyzed in relation to the input parameters. OMNeT++ 3.x had no direct support for batch runs, and users had to resort to writing shell (or Python, Ruby, etc.) scripts that iterated over the required parameter space, to generate a (partial) ini file and run the simulation program in each iteration.
OMNeT++ 4.x largely automates this process and eliminates the need for writing batch execution scripts. It is the ini file where the user can specify iterations over various parameter settings. Here is an example:
[Config AlohaStudy]
*.numHosts = ${1, 2, 5, 10..50 step 10}
**.host[*].generationInterval = exponential(${0.2, 0.4, 0.6}s)
This parameter study expands to 8*3 = 24 simulation runs, where the number of hosts iterates over the numbers 1, 2, 5, 10, 20, 30, 40, 50, and for each host count three simulation runs will be conducted, with the generation interval being exponential(0.2), exponential(0.4), and exponential(0.6).
How can it be used? First of all, running the simulation program with the -q numruns option will print how many simulation runs a given configuration expands to.
$ ./aloha -c AlohaStudy -q numruns
OMNeT++ Discrete Event Simulation
...
Config: AlohaStudy
Number of runs: 24
When -q runs is used instead, the program will print the list
of runs, with the values of the iteration variables for each run.
(Use -q rundetails to get even more info.)
Note that the parameter study actually maps to nested loops, with the last
${...} becoming the innermost loop. The iteration variables are
just named $0 and $1 – we’ll see that it is possible to give
meaningful names to them. Please ignore the $repetition=0 part in
the printout for now.
$ ./aloha -c AlohaStudy -q runs
OMNeT++ Discrete Event Simulation
...
Config: AlohaStudy
Number of runs: 24
Run 0: $0=1, $1=0.2, $repetition=0
Run 1: $0=1, $1=0.4, $repetition=0
Run 2: $0=1, $1=0.6, $repetition=0
Run 3: $0=2, $1=0.2, $repetition=0
Run 4: $0=2, $1=0.4, $repetition=0
Run 5: $0=2, $1=0.6, $repetition=0
Run 6: $0=5, $1=0.2, $repetition=0
Run 7: $0=5, $1=0.4, $repetition=0
...
Run 19: $0=40, $1=0.4, $repetition=0
Run 20: $0=40, $1=0.6, $repetition=0
Run 21: $0=50, $1=0.2, $repetition=0
Run 22: $0=50, $1=0.4, $repetition=0
Run 23: $0=50, $1=0.6, $repetition=0
Any of these runs can be executed by passing the -r <runnumber> option to Cmdenv. So, the task is now to run the simulation program 24 times, with -r running from 0 through 23:
$ ./aloha -u Cmdenv -c AlohaStudy -r 0
$ ./aloha -u Cmdenv -c AlohaStudy -r 1
$ ./aloha -u Cmdenv -c AlohaStudy -r 2
...
$ ./aloha -u Cmdenv -c AlohaStudy -r 23
This batch can be executed either from the OMNeT++ IDE (where you are prompted to pick an executable and an ini file, choose the configuration from a list, and just click Run), or using a little command-line batch execution tool (opp_runall) supplied with OMNeT++.
Actually, it is also possible to make Cmdenv execute all runs in one go, by simply omitting the -r option.
$ ./aloha -u Cmdenv -c AlohaStudy
OMNeT++ Discrete Event Simulation
Preparing for running configuration AlohaStudy, run #0...
...
Preparing for running configuration AlohaStudy, run #1...
...
...
Preparing for running configuration AlohaStudy, run #23...
However, this approach is not recommended, because it is more susceptible to C++ programming errors in the model. (For example, if any of the runs crashes, the whole batch stops – which may not be what the user wants.)
Iterations¶
Let us return to the example ini file in the previous section:
[Config AlohaStudy]
*.numHosts = ${1, 2, 5, 10..50 step 10}
**.host[*].generationInterval = exponential( ${0.2, 0.4, 0.6}s )
The ${...} syntax specifies an iteration. It is sort of a macro: at
each run, the whole ${...} string is textually replaced with the
current iteration value. The values to iterate over do not need to be
numbers (although the “a..b” and “a..b step c” forms
only work on numbers), and the substitution takes place even inside
string constants. So, the following examples are all valid (note that
textual substitution is used):
*.param = 1 + ${1e-6, 1/3, sin(0.5)}
==> *.param = 1 + 1e-6
*.param = 1 + 1/3
*.param = 1 + sin(0.5)
*.greeting = "We will simulate ${1,2,5} host(s)."
==> *.greeting = "We will simulate 1 host(s)."
*.greeting = "We will simulate 2 host(s)."
*.greeting = "We will simulate 5 host(s)."
To write a literal ${..} inside a string constant, quote
the left brace with a backslash: $\{..}.
Note
Inside ${..}, the values are separated with commas. However,
not every comma is taken as a value separator because the parser
tries to be smart about what is meant. Commas inside (nested) parentheses,
brackets or curly braces are ignored so that ${uniform(0,3)} is
parsed as one value and not as uniform(0 plus 3). Commas, curly
braces and other characters inside double-quoted string literals are
also ignored, so ${"Hello, world"} yields a single "Hello, world"
string and not "Hello plus world". It is assumed that string literals
use backslash as an escape character, like in C/C++ and NED.
To include a literal comma or close-brace inside a value, one needs
to escape it with a backslash: ${foo\,bar\}baz}
will parse as a single value, foo,bar}baz. Backslashes themselves must be doubled.
As the above examples illustrate, the parser removes one level of backslashes,
except inside string literals where they are left intact.
Named Iteration Variables¶
One can assign names to iteration variables, which has the advantage
that meaningful names will be displayed in the Cmdenv output instead
of $0 and $1, and also lets one reference iteration
variables at other places in the ini file. The syntax is
${<varname>=<iteration>}, and variables can be referred to simply as
${<varname>}:
[Config Aloha]
*.numHosts = ${N=1, 2, 5, 10..50 step 10}
**.host[*].generationInterval = exponential( ${mean=0.2, 0.4, 0.6}s )
**.greeting = "There are ${N} hosts"
The scope of the variable name is the section that defines it, plus
sections based on that section (via extends).
Referencing Other Iteration Variables¶
Iterations may refer to other iteration variables, using the dollar syntax
($var) or the dollar-brace syntax (${var}).
This feature makes it possible to have loops where the inner iteration range depends on the outer one. An example:
**.foo = ${i=1..10} # outer loop
**.bar = ${j=1..$i} # inner loop depends on $i
When needed, the default top-down nesting order of iteration loops is modified (loops are reordered) to ensure that expressions only refer to more outer loop variables, but not to inner ones. When this is not possible, an error is generated with the “circular dependency” message.
For instance, in the following example the loops will be nested in k - i - j order, k being the outermost and j the innermost loop:
**.foo = ${i=0..$k} # must be inner to $k
**.bar = ${j=$i..$k} # must be inner to both $i and $k
**.baz = ${k=1..10} # may be the outermost loop
And the next example will stop with an error because there is no “good” ordering:
**.foo = ${i=0..$j}
**.bar = ${j=0..$k}
**.baz = ${k=0..$i} # --> error: circular references
Variables are substituted textually, and the result is normally
not evaluated as an arithmetic expression. The result of the
substitution is only evaluated where needed, namely in the three arguments
of iteration ranges (from, to, step), and in the
value of the constraint configuration option.
To illustrate textual substitution, consider the following contorted example:
**.foo = ${i=1..3, 1s+, -}001s
Here, the foo NED parameter will receive the following values in subsequent runs:
1001s, 2001s, 3001s, 1s+001s, -001s.
Caution
Due to textual substitution, variables in arithmetic expressions should be protected with parentheses – just like in C/C++ function-style macros. Consider the following example:
**.foo = ${i=10}
**.bar = ${j=$i+5}
**.baz = ${k=2*$j} # bogus! $j should be written as ($j)
constraint = $i+50 < 2*$j # ditto: should use ($i) and ($j)
Here, the baz parameter will receive the string 2*10+5 after
the substitutions and hence evaluate to \(25\) instead of the correct
\(2*(10+5)=30\); the constraint expression is similarly wrong. Mind the parens!
Substitution also works inside string constants within iterations (${..}).
**.foo = "${i=Jo}" # -> Jo
**.bar = ${"Hi $i", "Hi ${i}hn"} # -> Hi Jo /John
However, outside iterations the plain dollar syntax is not understood, only the dollar-brace syntax is:
**.foo = "${i=Day}"
**.baz = "Good $i" # -> remains "Good $i"
**.baz = "Good ${i}" # -> becomes "Good Day"
The text substitution model was chosen for greater flexibility as well as the ability to produce more consistent semantics. The advantages outweigh the inconvenience of having to parenthesize variable references in arithmetic expressions.
Parallel Iteration¶
The body of an iteration may end in an exclamation mark followed by the name of another iteration variable. This syntax denotes a parallel iteration. A parallel iteration does not define a loop of its own, but rather, the sequence is advanced in lockstep with the variable after the “!”. In other words, the “!” syntax chooses the kth value from the iteration, where k is the position (iteration count) of the iteration variable after the “!”.
An example:
**.plan = ${plan= "A", "B", "C", "D"}
**.numHosts = ${hosts= 10, 20, 50, 100 ! plan}
**.load = ${load= 0.2, 0.3, 0.3, 0.4 ! plan}
In the above example, the only loop is defined by the first line, the plan
variable. The other two iterations, hosts and load just follow it;
for the first value of plan the first values of hosts and load
are selected, and so on.
Predefined Variables, Run ID¶
There are a number of predefined variables: ${configname} and
${runnumber} with the obvious meanings; ${network} is the
name of the network that is simulated; ${processid} and
${datetime} expand to the OS process id of the simulation and the
time it was started; and there are some more: ${runid},
${iterationvars} and ${repetition}.
${runid} holds the run ID. When a simulation is run, a
run ID is assigned that uniquely identifies that instance of running the
simulation: every subsequent run of the same simulation will produce a different
run ID. The run ID is generated as the concatenation of several variables like
${configname}, ${runnumber}, ${datetime} and
${processid}. This yields an identifier that is unique “enough”
for all practical purposes, yet it is meaningful for humans. The run ID is
recorded into result files written during the simulation, and can be used
to match vectors and scalars written by the same simulation run.
Constraint Expression¶
In cases when not all combinations of the iteration variables make sense
or need to be simulated, it is possible to specify an additional
constraint expression. This expression is interpreted as a conditional
(an “if” statement) within the innermost
loop, and it must evaluate to true for
the variable combination to generate a run. The expression should be
given with the constraint configuration option. An example:
*.numNodes = ${n=10..100 step 10}
**.numNeighbors = ${m=2..10 step 2}
constraint = ($m) <= sqrt($n) # note: parens needed due to textual substitution
The expression syntax supports most C language operators including boolean,
conditional and binary shift operations, and most <math.h> functions;
data types are boolean, double and string. The expression must evaluate to
a boolean.
Note
Remember that variables are substituted textually into the expression, so they must be protected with parentheses to preserve evaluation order.
Repeating Runs with Different Seeds¶
It is directly supported to perform several runs with the same
parameters but different random number seeds. There are two
configuration options related to this: repeat and seed-set.
The first one simply specifies how many times a run needs to be repeated. For
example,
repeat = 10
causes every combination of iteration variables to be repeated 10 times,
and the ${repetition} predefined variable holds the loop counter.
Indeed, repeat=10 is equivalent to adding ${repetition=0..9}
to the ini file. The ${repetition} loop always becomes the innermost loop.
The seed-set configuration key affects seed selection. Every
simulation uses one or more random number generators (as configured by
the num-rngs key), for which the simulation kernel can automatically
generate seeds. The first simulation run may use one set of seeds (seed
set 0), the second run may use a second set (seed set 1), and so on.
Each set contains as many seeds as there are RNGs configured. All
automatic seeds generate random number sequences that are far apart in
the RNG’s cycle, so they will never overlap during
simulations.
Note
Mersenne Twister, the default RNG of OMNeT++ has a cycle length of \(2^{19937}\), which is more than enough for any conceivable purpose.
The seed-set key tells the simulation kernel which seed set to use.
It can be set to a concrete number (such as seed-set=0), but it
usually does not make sense as it would cause every simulation to run
with exactly the same seeds. It is more practical to set it to either
${runnumber} or to ${repetition}. The default setting is
${runnumber}:
seed-set = ${runnumber} # this is the default
This causes every simulation run to execute with a unique seed set. The second option is:
seed-set = ${repetition}
where all $repetition=0 runs will use the same seeds (seed set 0), all
$repetition=1 runs use another seed set, $repetition=2 a third seed
set, etc.
To perform runs with manually selected seed sets, one needs to define an
iteration for the seed-set key:
seed-set = ${5,6,8..11}
In this case, the repeat key should be left out, as seed-set
already defines an iteration and there is no need for an extra loop.
It is of course also possible to manually specify individual seeds for simulations. The parallel iteration feature is very convenient here:
repeat = 4
seed-1-mt = ${53542, 45732, 47853, 33434 ! repetition}
seed-2-mt = ${75335, 35463, 24674, 56673 ! repetition}
seed-3-mt = ${34542, 67563, 96433, 23567 ! repetition}
The meaning of the above is this: in the first repetition, the first column of seeds is chosen, for the second repetition, the second column, etc. The “!” syntax chooses the kth value from the iteration, where k is the position (iteration count) of the iteration variable after the “!”. Thus, the above example is equivalent to the following:
# no repeat= line!
seed-1-mt = ${seed1 = 53542, 45732, 47853, 33434}
seed-2-mt = ${ 75335, 35463, 24674, 56673 ! seed1}
seed-3-mt = ${ 34542, 67563, 96433, 23567 ! seed1}
That is, the iterators of seed-2-mt and seed-3-mt are advanced
in lockstep with the seed1 iteration.
Experiment-Measurement-Replication¶
We have introduced three concepts that are useful for organizing simulation results generated by batch executions or several batches of executions.
During a simulation study, a user prepares several experiments. The purpose of an experiment is to find out the answer to questions like “how does the number of nodes affect response times in the network?” For an experiment, several measurements are performed on the simulation model, and each measurement runs the simulation model with a different set of parameters. To eliminate the bias introduced by the particular random number stream used for the simulation, several replications of every measurement are run with different random number seeds, and the results are averaged.
OMNeT++ result analysis tools can take advantage of the experiment, measurement and replication labels recorded into result files, and display simulation runs and recorded results accordingly on the user interface.
These labels can be explicitly specified in the ini file using the
experiment-label, measurement-label and replication-label
config options. If they are missing, the default is the following:
experiment-label = "${configname}"
measurement-label = "${iterationvars}"
replication-label = "#${repetition},seed-set=<seedset>"
That is, the default experiment label is the configuration name; the measurement label is concatenated from the iteration variables; and the replication label contains the repeat loop variable and seed-set. Thus, for our first example the experiment-measurement-replication tree would look like this:
"PureAloha"--experiment
$N=1,$mean=0.2 -- measurement
#0, seed-set=0 -- replication
#1, seed-set=1
#2, seed-set=2
#3, seed-set=3
#4, seed-set=4
$N=1,$mean=0.4
#0, seed-set=5
#1, seed-set=6
...
#4, seed-set=9
$N=1,$mean=0.6
#0, seed-set=10
#1, seed-set=11
...
#4, seed-set=14
$N=2,$mean=0.2
...
$N=2,$mean=0.4
...
...
The experiment-measurement-replication labels should be enough to reproduce the same simulation results, given of course that the ini files and the model (NED files and C++ code) haven’t changed.
Every instance of running the simulation gets a unique run ID. We can illustrate this by listing the corresponding run IDs under each repetition in the tree. For example:
"PureAloha"
$N=1,$mean=0.2
#0, seed-set=0
PureAloha-0-20070704-11:38:21-3241
PureAloha-0-20070704-11:53:47-3884
PureAloha-0-20070704-16:50:44-4612
#1, seed-set=1
PureAloha-1-20070704-16:50:55-4613
#2, seed-set=2
PureAloha-2-20070704-11:55:23-3892
PureAloha-2-20070704-16:51:17-4615
...
The tree shows that (“PureAloha”, “$N=1,$mean=0.2”, “#0, seed-set=0”) was run three times. The results produced by these three executions should be identical, unless, for example, some parameter was modified in the ini file, or a bug got fixed in the C++ code.
The default way of generating the experiment/measurement/replication labels is useful and sufficient for the majority of simulation studies. However, it can be customized if needed. For example, here is a way to join two configurations into one experiment:
[Config PureAloha_Part1]
experiment-label = "PureAloha"
# ...
[Config PureAloha_Part2]
experiment-label = "PureAloha"
# ...
Measurement and replication labels can be customized in a similar way,
making use of named iteration variables, ${repetition},
${runnumber} and other predefined variables. One possible benefit is
to customize the generated measurement and replication labels. For
example:
[Config PureAloha_Part1]
measurement = "${N} hosts, exponential(${mean}) packet generation interval"
One should be careful with the above technique though, because if some iteration variables are left out of the measurement labels, runs with all values of those variables will be grouped together to the same replications.
Configuring the Random Number Generators¶
The random number architecture of OMNeT++ was already outlined
in section Random Number Generators. Here
we’ll cover the configuration of RNGs in omnetpp.ini.
Number of RNGs¶
The num-rngs configuration option sets the number of
random number generator instances (i.e., random number streams)
available for the simulation model (see Random Number Generators).
Referencing an RNG number greater than or equal to this number
(from a simple module or NED file) will cause a runtime error.
RNG Choice¶
The rng-class configuration option 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 \(2^{31}-2\)), and "cAkaroaRNG" (Akaroa’s random number generator,
see section Akaroa Support: Multiple Replications in Parallel).
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
stream configuration – even for simulation models that 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 # where N and M are numeric, M < num-rngs
This maps module-local RNG N to physical RNG M. The following
example maps all gen module’s default (N=0) RNG to physical RNG 1,
and all noisychannel module’s default (N=0) RNG to physical RNG 2.
[General]
num-rngs = 3
**.gen[*].rng-0 = 1
**.noisychannel[*].rng-0 = 2
The value also allows expressions, including those containing index,
parentIndex, and ancestorIndex(level). This allows things
like assigning a separate RNG to each element of a module vector.
This mapping allows variance reduction techniques to be applied to OMNeT++ models, without any model change or recompilation.
Automatic Seed Selection¶
Automatic seed selection is used for an RNG if one does not 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 and the RNG number. For the same 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 separated in the RNG’s sequence so that the generated sequences don’t overlap.
The run number can be specified either in omnetpp.ini (e.g. via the
cmdenv-runs-to-execute option) 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 don’t overlap is easy,
due to the extremely long sequence of the RNG.
The RNG is initialized from the 32-bit seed value \(seed = runNumber*numRngs + rngNumber\).
(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 (\(2^{31}-1\), about 2 billion).
For this RNG, OMNeT++ uses a table of 256 pre-generated seeds, equally spaced
in the RNG’s sequence. Index into the table is calculated with the
\(runNumber*numRngs + rngNumber\) formula. Care should be taken that
one doesn’t 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.
Manual Seed Configuration¶
In some cases, one may want to manually configure seed values. The motivation for doing so may be the use of variance reduction techniques, or the intention to reuse the same seeds for several simulation runs.
To manually set seeds for the Mersenne Twister RNG, use the seed-k-mt
option, where k is the RNG index. An example:
[General]
num-rngs = 3
seed-0-mt = 12
seed-1-mt = 9
seed-2-mt = 7
For the now-obsolete cLCG32 RNG, the name of the corresponding option is
seed-k-lcg32.
Logging¶
The OMNeT++ logging infrastructure provides a few configuration options that affect what is written to the log output. It supports configuring multiple filters: global compile-time, global runtime, and per-component runtime log level filters. For a log statement to actually produce output, it must pass each filter simultaneously. In addition, one can also specify a log prefix format string which determines the context information that is written before each log line. In the following sections, we look at how to configure logging.
Compile-Time Filtering¶
The COMPILETIME_LOGLEVEL macro determines which log statements are compiled into the executable. Any log statement which uses a log level below the specified compile-time log level is omitted. In other words, no matter how the runtime log levels are configured, such log statements are not even executed. This is mainly useful to avoid the performance penalty paid for log statements which are not needed.
#define COMPILETIME_LOGLEVEL LOGLEVEL_INFO
EV_INFO << "Packet received successfully" << endl;
EV_DEBUG << "CRC check successful" << endl;
In the above example, the output of the second log statement is omitted:
[INFO] Packet received successfully
If simulation performance is critical, and if there are lots of log statements in the code, it might be useful to omit all log statements from the executable. This can be very simply achieved by putting the following macro into effect for the compilation of all source files.
#define COMPILETIME_LOGLEVEL LOGLEVEL_OFF
On the other hand, if there’s some hard-to-track-down issue, it might be useful to just do the opposite. Compiling with the lowest log level ensures that the log output contains as much information as possible.
#define COMPILETIME_LOGLEVEL LOGLEVEL_TRACE
By default, the COMPILETIME_LOGLEVEL macro is set to LOGLEVEL_TRACE if the code is compiled in debug mode (NDEBUG is not set). However, it is set to LOGLEVEL_DETAIL if the code is compiled in release mode (NDEBUG is set).
In fact, the COMPILETIME_LOG_PREDICATE macro is the most generic compile time predicate that determines which log statements are compiled into the executable. Mostly, there’s no need to redefine this macro, but it can be useful sometimes. For example, one can do compile-time filtering for log categories by redefining this macro. By default, the COMPILETIME_LOG_PREDICATE macro is defined as follows:
#define COMPILETIME_LOG_PREDICATE(object, loglevel, category) \
(loglevel >= COMPILETIME_LOGLEVEL)
Runtime Filtering¶
The cLog::logLevel variable restricts during runtime which log statements produce output. By default, the global runtime log level doesn’t filter logging, it is set to LOGLEVEL_TRACE. Although due to its global nature it’s not really modular, nevertheless it’s still allowed to change the value of this variable. It is mainly used in interactive user interfaces to implement efficient global filtering, but it may also be useful for various debugging purposes.
In addition to the global variable, there’s also a per-component runtime log level which only restricts the output of a particular component of the simulation. By default, the runtime log level of all components is set to LOGLEVEL_TRACE. Programmatically, these log levels can be retrieved with cComponent::getLogLevel() and changed with cComponent::setLogLevel().
In general, any log statement which uses a log level below the specified global runtime log level, or below the specified per-component runtime log level, is omitted. If the log statement appears in a module source, then the module’s per-component runtime log level is checked. In any other C++ code, the context module’s per-component runtime log level is checked.
In fact, the cLog::noncomponentLogPredicate and the cLog::componentLogPredicate are the most generic runtime predicates that determine which log statements are executed. Mostly, there’s no need to redefine these predicates, but it can be useful sometimes. For example, one can do runtime filtering for log categories by redefining them. To cite a real example, the cLog::componentLogPredicate function contains the following runtime checks:
return statementLogLevel >= cLog::loglevel &&
statementLogLevel >= sourceComponent->getLogLevel() &&
getEnvir()->isLoggingEnabled(); // for express mode
Log Prefix Format¶
The log prefix format is a string which determines the log prefix that is written
before each log line. The format string contains constant parts interleaved with
special format directives. The latter always start with the % character
followed by another character that identifies the format directive. Constant
parts are simply written to the output, while format directives are substituted
at runtime with the corresponding data that is captured by the log statement.
The following is the list of predefined log prefix format directives. They are organized into groups based on what kind of information they provide.
Log statement related format directives:
%llog level name%clog category
Current simulation state related format directives:
%ecurrent event number%tcurrent simulation time%gcurrent fingerprint if fingerprint verification is enabled in the configuration, otherwise empty%vcurrent message or event name%acurrent message or event class name%nmodule name of current event%mmodule path of current event%omodule class name of current event%ssimple NED type name of module of current event%qfully qualified NED type name of module of current event%Ncontext component name%Mcontext component path%Ocontext component class name%Scontext component NED type simple name%Qcontext component NED type fully qualified name
Simulation run related format directives:
%Gconfig name%Rrun number%Xnetwork module class name%Ynetwork module NED type simple name%Znetwork module NED type fully qualified name
C++ source related (where the log statement is) format directives:
%psource object pointer%bsource object name%dsource object path%zsource class name%usource function name%xsource component NED type simple name%ysource component NED type fully qualified%fsource file name%isource line number
Operating system related format directives:
%wuser time in seconds%Whuman readable wall time%Hhost name%Iprocess id
Compound field format directives:
%Eevent object (class name, name)%Umodule of current event (NED type, full path)%Ccontext component (NED type, full path)%Kcontext component, if different from current module (NED type, full path)%Jsource component or object (NED type or class, full path or pointer)%Lsource component or object, if different from context component (NED type or class, full path or pointer)
Padding format directives:
%[0-9]+add spaces until specified column%|adaptive tabstop: add padding until longest prefix seen so far%>function call depth times 2-space indentation (see Enter_Method, Enter_Method_Silent)%<remove preceding whitespace characters
Conditional format directives:
%?ignore the following constant part if the preceding directive didn’t print anything (useful for separators)
Escaping the % character:
%%one % character
Configuring Logging in Cmdenv¶
In Cmdenv, logging can be configured using omnetpp.ini configuration
options. The configured settings remain in effect during the whole simulation run
unless overridden programmatically.
cmdenv-output-fileredirects standard output to a filecmdenv-log-prefixdetermines the log prefix of each line<object-full-path>.cmdenv-log-levelrestricts output on a per-component basis
By default, the log is written to the standard output but it can be redirected to
a file. The output can be completely disabled from omnetpp.ini, so
that it doesn’t slow down simulation when it is not needed. The per-component
runtime log level option must match the full path of the targeted component. The
supported values for this configuration option are the following:
offcompletely disables log outputfatalomits log output belowLOGLEVEL_FATALerroromits log output belowLOGLEVEL_ERRORwarnomits log output belowLOGLEVEL_WARNinfoomits log output belowLOGLEVEL_INFOdetailomits log output belowLOGLEVEL_DETAILdebugomits log output belowLOGLEVEL_DEBUGtracecompletely enables log output
By default, the log prefix format is set to "[%l]\t". The
default setting is intentionally quite simple to avoid cluttered standard output,
it produces similar log output:
[INFO] Packet received successfully
[DEBUG] CRC check successful
The log messages are aligned vertically because there’s a TAB character
in the format string. Setting the log prefix format to an empty string disables
writing a log prefix altogether. Finally, here is a more detailed format string:
"[%l]\t%C for %E: %|", it produces similar output:
[INFO] (IPv4)host.ip for (ICMPMessage)ping0: Pending (IPv4Datagram)ping0
[INFO] (ARP)host.arp for (ICMPMessage)ping0: Starting ARP resolution
[DEBUG] (ARP)host.arp for (ICMPMessage)ping0: Sending (ARPPacket)arpREQ
[INFO] (Mac)host.wlan.mac for (ARPPacket)arpREQ: Enqueing (ARPPacket)arpREQ
In express mode, for performance reasons, log output is disabled during the whole
simulation. However, during the simulation finish stage, logging is automatically
re-enabled to allow writing statistical and other results to the log. One can
completely disable all logging by adding the following configuration option at the
beginning of omnetpp.ini:
[General]
**.cmdenv-log-level = off
Finally, the following is a more complex example that sets the per-component runtime log levels for all PHY components to LOGLEVEL_WARN, except for all MAC modules where it is set to LOGLEVEL_DEBUG, and for all other modules it is set LOGLEVEL_OFF.
[General]
**.phy.cmdenv-log-level = warn
**.mac.cmdenv-log-level = debug
**.cmdenv-log-level = off
Configuring Logging in Qtenv¶
The graphical user interface Qtenv provides its own configuration
dialog where the user can configure logging. This dialog offers setting the
global runtime log level and the log prefix format string. The per-component
runtime log levels can be set from the context menu of components. As in Cmdenv,
it’s also possible to set the log levels to off, effectively disabling
logging globally or for specific components only.
In contrast to Cmdenv, setting the runtime log levels is possible even if the simulation is already running. This feature allows continuous control over the level of detail of what is written to the log output. For obvious reasons, changing the log levels has no effect back in time, so already written log content in the log windows will not change.
By default, the log prefix format is set to "%l %C: ", it produces
similar log output:
INFO Network.server.wlan[0].mac: Packet received successfully
DEBUG Network.server.wlan[0].mac: CRC check successful
Barrett, Kim, Bob Cassels, Paul Haahr, David A. Moon, Keith Playford, and P. Tucker Withington. 1996. “A Monotonic Superclass Linearization for Dylan.” Proceedings of the 11th ACM SIGPLAN Conference on Object-Oriented Programming, Systems, Languages, and Applications (New York, NY, USA), OOPSLA ’96, 69–82. https://doi.org/http://doi.acm.org/10.1145/236337.236343.