[Prev] [Next] [TOC] [Chapters]

10 Configuring Simulations

10.1 The Configuration File

Configuration and input data for the simulation are in a configuration file usually called omnetpp.ini.

10.1.1 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 one containing several entries.

10.1.2 File Syntax

An OMNeT++ configuration file is an ASCII text file, 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 ignored.

The file is line oriented, and consists of section heading lines, key-value lines, and directive lines:

  1. Section heading lines contain a section name enclosed in square brackets.
  2. 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.
  3. Currently there is only one kind of directive line, include. An include line starts with the include word, 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:

  1. Keys that do not contain dots represent global or per-run configuration options.
  2. 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.
  3. Otherwise, the key represents a parameter assignment. Thus, parameter assignment keys contain a dot, and no hyphen after the last dot.

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.

An example:

# This is a comment line
[General]                       # section heading
network = Foo                   # configuration option
debug-on-errors = false         # another configuration option

**.vector-recording = false     # per-object configuration option
**.app*.typename = "HttpClient" # per-object configuration option

**.app*.interval = 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

10.1.3 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 part, 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 others, 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 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 to 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]

10.2 Sections

An ini file may contain a [General] section and several [Config <configname>] sections. The order of the sections doesn't matter.

10.2.1 The [General] Section

The most commonly used options of the [General] section are the following.

Note that the NED files loaded by the simulation may contain several networks, and any of them may be specified in the network option.

10.2.2 Named Configurations

Named configurations are sections of the form [Config <configname>], 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. (Tkenv, of course, lets the user choose the configuration from a dialog.)

10.2.3 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 fall-back 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 ([Barrett1996]):

The fallback chain of a configuration A is

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 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.

10.3 Assigning Module Parameters

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 (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 [7.1.2.2]).

An example omnetpp.ini which sets the numHosts parameter of the toplevel 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"

10.3.1 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 for 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 to globbing 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 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:

10.3.1.1 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

10.3.1.2 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 network level. Also note that **.queue**.bufSize would match net.queue1.foo.bar.bufSize as well!

10.3.1.3 Sets, Negated Sets

Sets and negated sets can contain several character ranges and also enumeration 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 at a position where it cannot be interpreted as 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.

10.3.1.4 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 for 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

10.3.2 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.

More precisely, parameter resolution takes place as follows:

  1. If the parameter is assigned in NED, it cannot be overridden in the configuration. The value is applied and the process finishes.
  2. If the first match is a value line (matches <parameter-fullpath>=<value>), the value is applied and the process finishes.
  3. If the first match is a <parameter-fullpath>=default line, the default value is applied and the process finishes.
  4. If the first match is a <parameter-fullpath>=ask line, the parameter will be asked from the user interactively (UI dependent).
  5. If there was no match and the parameter has a default value, it is applied and the process finishes.
  6. 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.

10.4 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 done, with the generation interval being exponential(0.2), exponential(0.4), and exponential(0.6).

How does it work? First of all, Cmdenv with the -x option will print how many simulation runs a given section expands to. (One should of course use Cmdenv for batch runs, not a GUI one like Tkenv or Qtenv.)

$ aloha -u Cmdenv -x AlohaStudy

OMNeT++ Discrete Event Simulation
...
Config: AlohaStudy
Number of runs: 24

When the -g option is also added, the program also prints the values of the iteration variables for each run. (Use -G for 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 -u Cmdenv -x AlohaStudy -g
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 get 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.)

10.4.1 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: $\{..}.

10.4.2 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).

10.4.2.1 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 receiving the following values in subsequent runs: 1001s, 2001s, 3001s, 1s+001s, -001s.

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"

10.4.3 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 host and load are selected, and so on.

10.4.4 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.

10.4.5 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.

10.4.6 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.

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.

10.4.7 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.

10.5 Configuring the Random Number Generators

The random number architecture of OMNeT++ was already outlined in section [7.3]. Here we'll cover the configuration of RNGs in omnetpp.ini.

10.5.1 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 [7.3]). Referencing an RNG number greater or equal to this number (from a simple module or NED file) will cause a runtime error.

10.5.2 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 231-2), and "cAkaroaRNG" (Akaroa's random number generator, see section [11.21]).

10.5.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  # where N,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.

10.5.4 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 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).

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 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.

10.5.5 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.

10.6 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 simulatenously. 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 how to configure logging.

10.6.1 Compile-Time Filtering

The COMPILETIME_LOGLEVEL macro determines which log statements are compiled into the executable. Any log statment 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)

10.6.2 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 are set to LOGLEVEL_TRACE. Programmatically, these log levels can be retrieved with cComponent::getLogLevel() and changed with cComponent::setLogLevel().

In general, any log statment 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 determines 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

10.6.3 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:

Current simulation state related format directives:

Simulation run related format directives:

C++ source related (where the log statement is) format directives:

Operating system related format directives:

Compound field format directives:

Padding format directives:

Conditional format directives:

Escaping the % character:

10.6.4 Configuring 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 programatically.

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:

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 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

10.6.5 Configuring Tkenv and Qtenv

The graphical user interfaces, Tkenv and Qtenv, provide their own configuration dialogs where the user can configure logging. These dialogs offer 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



[Prev] [Next] [TOC] [Chapters]