EnglishFrenchSpanish

OnWorks favicon

ns-3-manual - Online in the Cloud

Run ns-3-manual in OnWorks free hosting provider over Ubuntu Online, Fedora Online, Windows online emulator or MAC OS online emulator

This is the command ns-3-manual that can be run in the OnWorks free hosting provider using one of our multiple free online workstations such as Ubuntu Online, Fedora Online, Windows online emulator or MAC OS online emulator

PROGRAM:

NAME


ns-3-manual - ns-3 Manual

This is the ns-3 Manual. Primary documentation for the ns-3 project is available in five
forms:

· ns-3 Doxygen: Documentation of the public APIs of the simulator

· Tutorial, Manual (this document), and Model Library for the latest release and
development tree

· ns-3 wiki

CONTENTS


Organization
This chapter describes the overall ns-3 software organization and the corresponding
organization of this manual.

ns-3 is a discrete-event network simulator in which the simulation core and models are
implemented in C++. ns-3 is built as a library which may be statically or dynamically
linked to a C++ main program that defines the simulation topology and starts the
simulator. ns-3 also exports nearly all of its API to Python, allowing Python programs to
import an "ns3" module in much the same way as the ns-3 library is linked by executables
in C++.
[image] Software organization of ns-3.UNINDENT

The source code for ns-3 is mostly organized in the src directory and can be described
by the diagram in Software organization of ns-3. We will work our way from the bottom
up; in general, modules only have dependencies on modules beneath them in the figure.

We first describe the core of the simulator; those components that are common across all
protocol, hardware, and environmental models. The simulation core is implemented in
src/core. Packets are fundamental objects in a network simulator and are implemented in
src/network. These two simulation modules by themselves are intended to comprise a
generic simulation core that can be used by different kinds of networks, not just
Internet-based networks. The above modules of ns-3 are independent of specific network
and device models, which are covered in subsequent parts of this manual.

In addition to the above ns-3 core, we introduce, also in the initial portion of the
manual, two other modules that supplement the core C++-based API. ns-3 programs may
access all of the API directly or may make use of a so-called helper API that provides
convenient wrappers or encapsulation of low-level API calls. The fact that ns-3 programs
can be written to two APIs (or a combination thereof) is a fundamental aspect of the
simulator. We also describe how Python is supported in ns-3 before moving onto specific
models of relevance to network simulation.

The remainder of the manual is focused on documenting the models and supporting
capabilities. The next part focuses on two fundamental objects in ns-3: the Node and
NetDevice. Two special NetDevice types are designed to support network emulation use
cases, and emulation is described next. The following chapter is devoted to
Internet-related models, including the sockets API used by Internet applications. The
next chapter covers applications, and the following chapter describes additional support
for simulation, such as animators and statistics.

The project maintains a separate manual devoted to testing and validation of ns-3 code
(see the ns-3 Testing and Validation manual).

Random Variables
ns-3 contains a built-in pseudo-random number generator (PRNG). It is important for
serious users of the simulator to understand the functionality, configuration, and usage
of this PRNG, and to decide whether it is sufficient for his or her research use.

Quick Overview
ns-3 random numbers are provided via instances of ns3::RandomVariableStream.

· by default, ns-3 simulations use a fixed seed; if there is any randomness in the
simulation, each run of the program will yield identical results unless the seed and/or
run number is changed.

· in ns-3.3 and earlier, ns-3 simulations used a random seed by default; this marks a
change in policy starting with ns-3.4.

· in ns-3.14 and earlier, ns-3 simulations used a different wrapper class called
ns3::RandomVariable. As of ns-3.15, this class has been replaced by
ns3::RandomVariableStream; the underlying pseudo-random number generator has not
changed.

· to obtain randomness across multiple simulation runs, you must either set the seed
differently or set the run number differently. To set a seed, call
ns3::RngSeedManager::SetSeed() at the beginning of the program; to set a run number with
the same seed, call ns3::RngSeedManager::SetRun() at the beginning of the program; see
Creating random variables.

· each RandomVariableStream used in ns-3 has a virtual random number generator associated
with it; all random variables use either a fixed or random seed based on the use of the
global seed (previous bullet);

· if you intend to perform multiple runs of the same scenario, with different random
numbers, please be sure to read the section on how to perform independent replications:
Creating random variables.

Read further for more explanation about the random number facility for ns-3.

Background
Simulations use a lot of random numbers; one study found that most network simulations
spend as much as 50% of the CPU generating random numbers. Simulation users need to be
concerned with the quality of the (pseudo) random numbers and the independence between
different streams of random numbers.

Users need to be concerned with a few issues, such as:

· the seeding of the random number generator and whether a simulation outcome is
deterministic or not,

· how to acquire different streams of random numbers that are independent from one
another, and

· how long it takes for streams to cycle

We will introduce a few terms here: a RNG provides a long sequence of (pseudo) random
numbers. The length of this sequence is called the cycle length or period, after which
the RNG will repeat itself. This sequence can be partitioned into disjoint streams. A
stream of a RNG is a contiguous subset or block of the RNG sequence. For instance, if the
RNG period is of length N, and two streams are provided from this RNG, then the first
stream might use the first N/2 values and the second stream might produce the second N/2
values. An important property here is that the two streams are uncorrelated. Likewise,
each stream can be partitioned disjointedly to a number of uncorrelated substreams. The
underlying RNG hopefully produces a pseudo-random sequence of numbers with a very long
cycle length, and partitions this into streams and substreams in an efficient manner.

ns-3 uses the same underlying random number generator as does ns-2: the MRG32k3a
generator from Pierre L'Ecuyer. A detailed description can be found in
http://www.iro.umontreal.ca/~lecuyer/myftp/papers/streams00.pdf. The MRG32k3a generator
provides 1.8x10^{19} independent streams of random numbers, each of which consists of
2.3x10^{15} substreams. Each substream has a period (i.e., the number of random numbers
before overlap) of 7.6x10^{22}. The period of the entire generator is 3.1x10^{57}.

Class ns3::RandomVariableStream is the public interface to this underlying random number
generator. When users create new random variables (such as ns3::UniformRandomVariable,
ns3::ExponentialRandomVariable, etc.), they create an object that uses one of the
distinct, independent streams of the random number generator. Therefore, each object of
type ns3::RandomVariableStream has, conceptually, its own "virtual" RNG. Furthermore,
each ns3::RandomVariableStream can be configured to use one of the set of substreams drawn
from the main stream.

An alternate implementation would be to allow each RandomVariable to have its own
(differently seeded) RNG. However, we cannot guarantee as strongly that the different
sequences would be uncorrelated in such a case; hence, we prefer to use a single RNG and
streams and substreams from it.

Creating random variables
ns-3 supports a number of random variable objects from the base class
RandomVariableStream. These objects derive from ns3::Object and are handled by smart
pointers.

The correct way to create these objects is to use the templated CreateObject<> method,
such as:

Ptr<UniformRandomVariable> x = CreateObject<UniformRandomVariable> ();

then you can access values by calling methods on the object such as:

myRandomNo = x->GetInteger ();

If you try to instead do something like this:

myRandomNo = UniformRandomVariable().GetInteger ();

your program will encounter a segmentation fault, because the implementation relies on
some attribute construction that occurs only when CreateObject is called.

Much of the rest of this chapter now discusses the properties of the stream of
pseudo-random numbers generated from such objects, and how to control the seeding of such
objects.

Seeding and independent replications
ns-3 simulations can be configured to produce deterministic or random results. If the
ns-3 simulation is configured to use a fixed, deterministic seed with the same run number,
it should give the same output each time it is run.

By default, ns-3 simulations use a fixed seed and run number. These values are stored in
two ns3::GlobalValue instances: g_rngSeed and g_rngRun.

A typical use case is to run a simulation as a sequence of independent trials, so as to
compute statistics on a large number of independent runs. The user can either change the
global seed and rerun the simulation, or can advance the substream state of the RNG, which
is referred to as incrementing the run number.

A class ns3::RngSeedManager provides an API to control the seeding and run number
behavior. This seeding and substream state setting must be called before any random
variables are created; e.g:

RngSeedManager::SetSeed (3); // Changes seed from default of 1 to 3
RngSeedManager::SetRun (7); // Changes run number from default of 1 to 7
// Now, create random variables
Ptr<UniformRandomVariable> x = CreateObject<UniformRandomVariable> ();
Ptr<ExponentialRandomVariable> y = CreateObject<ExponentialRandomVarlable> ();
...

Which is better, setting a new seed or advancing the substream state? There is no
guarantee that the streams produced by two random seeds will not overlap. The only way to
guarantee that two streams do not overlap is to use the substream capability provided by
the RNG implementation. Therefore, use the substream capability to produce multiple
independent runs of the same simulation. In other words, the more statistically rigorous
way to configure multiple independent replications is to use a fixed seed and to advance
the run number. This implementation allows for a maximum of 2.3x10^{15} independent
replications using the substreams.

For ease of use, it is not necessary to control the seed and run number from within the
program; the user can set the NS_GLOBAL_VALUE environment variable as follows:

$ NS_GLOBAL_VALUE="RngRun=3" ./waf --run program-name

Another way to control this is by passing a command-line argument; since this is an ns-3
GlobalValue instance, it is equivalently done such as follows:

$ ./waf --command-template="%s --RngRun=3" --run program-name

or, if you are running programs directly outside of waf:

$ ./build/optimized/scratch/program-name --RngRun=3

The above command-line variants make it easy to run lots of different runs from a shell
script by just passing a different RngRun index.

Class RandomVariableStream
All random variables should derive from class RandomVariable. This base class provides a
few methods for globally configuring the behavior of the random number generator. Derived
classes provide API for drawing random variates from the particular distribution being
supported.

Each RandomVariableStream created in the simulation is given a generator that is a new
RNGStream from the underlying PRNG. Used in this manner, the L'Ecuyer implementation
allows for a maximum of 1.8x10^19 random variables. Each random variable in a single
replication can produce up to 7.6x10^22 random numbers before overlapping.

Base class public API
Below are excerpted a few public methods of class RandomVariableStream that access the
next value in the substream.

/**
* \brief Returns a random double from the underlying distribution
* \return A floating point random value
*/
double GetValue (void) const;

/**
* \brief Returns a random integer from the underlying distribution
* \return Integer cast of ::GetValue()
*/
uint32_t GetInteger (void) const;

We have already described the seeding configuration above. Different RandomVariable
subclasses may have additional API.

Types of RandomVariables
The following types of random variables are provided, and are documented in the ns-3
Doxygen or by reading src/core/model/random-variable-stream.h. Users can also create
their own custom random variables by deriving from class RandomVariableStream.

· class UniformRandomVariable

· class ConstantRandomVariable

· class SequentialRandomVariable

· class ExponentialRandomVariable

· class ParetoRandomVariable

· class WeibullRandomVariable

· class NormalRandomVariable

· class LogNormalRandomVariable

· class GammaRandomVariable

· class ErlangRandomVariable

· class TriangularRandomVariable

· class ZipfRandomVariable

· class ZetaRandomVariable

· class DeterministicRandomVariable

· class EmpiricalRandomVariable

Semantics of RandomVariableStream objects
RandomVariableStream objects derive from ns3::Object and are handled by smart pointers.

RandomVariableStream instances can also be used in ns-3 attributes, which means that
values can be set for them through the ns-3 attribute system. An example is in the
propagation models for WifiNetDevice:

TypeId
RandomPropagationDelayModel::GetTypeId (void)
{
static TypeId tid = TypeId ("ns3::RandomPropagationDelayModel")
.SetParent<PropagationDelayModel> ()
.AddConstructor<RandomPropagationDelayModel> ()
.AddAttribute ("Variable",
"The random variable which generates random delays (s).",
StringValue ("ns3::UniformRandomVariable"),
MakePointerAccessor (&RandomPropagationDelayModel::m_variable),
MakePointerChecker<RandomVariableStream> ())
;
return tid;
}

Here, the ns-3 user can change the default random variable for this delay model (which is
a UniformRandomVariable ranging from 0 to 1) through the attribute system.

Using other PRNG
There is presently no support for substituting a different underlying random number
generator (e.g., the GNU Scientific Library or the Akaroa package). Patches are welcome.

Setting the stream number
The underlying MRG32k3a generator provides 2^64 independent streams. In ns-3, these are
assigned sequentially starting from the first stream as new RandomVariableStream instances
make their first call to GetValue().

As a result of how these RandomVariableStream objects are assigned to underlying streams,
the assignment is sensitive to perturbations of the simulation configuration. The
consequence is that if any aspect of the simulation configuration is changed, the mapping
of RandomVariables to streams may (or may not) change.

As a concrete example, a user running a comparative study between routing protocols may
find that the act of changing one routing protocol for another will notice that the
underlying mobility pattern also changed.

Starting with ns-3.15, some control has been provided to users to allow users to
optionally fix the assignment of selected RandomVariableStream objects to underlying
streams. This is the Stream attribute, part of the base class RandomVariableStream.

By partitioning the existing sequence of streams from before:

<-------------------------------------------------------------------------->
stream 0 stream (2^64 - 1)

into two equal-sized sets:

<-------------------------------------------------------------------------->
^ ^^ ^
| || |
stream 0 stream (2^63 - 1) stream 2^63 stream (2^64 - 1)
<- automatically assigned -----------><- assigned by user ----------------->

The first 2^63 streams continue to be automatically assigned, while the last 2^63 are
given stream indices starting with zero up to 2^63-1.

The assignment of streams to a fixed stream number is optional; instances of
RandomVariableStream that do not have a stream value assigned will be assigned the next
one from the pool of automatic streams.

To fix a RandomVariableStream to a particular underlying stream, assign its Stream
attribute to a non-negative integer (the default value of -1 means that a value will be
automatically allocated).

Publishing your results
When you publish simulation results, a key piece of configuration information that you
should always state is how you used the the random number generator.

· what seeds you used,

· what RNG you used if not the default,

· how were independent runs performed,

· for large simulations, how did you check that you did not cycle.

It is incumbent on the researcher publishing results to include enough information to
allow others to reproduce his or her results. It is also incumbent on the researcher to
convince oneself that the random numbers used were statistically valid, and to state in
the paper why such confidence is assumed.

Summary
Let's review what things you should do when creating a simulation.

· Decide whether you are running with a fixed seed or random seed; a fixed seed is the
default,

· Decide how you are going to manage independent replications, if applicable,

· Convince yourself that you are not drawing more random values than the cycle length, if
you are running a very long simulation, and

· When you publish, follow the guidelines above about documenting your use of the random
number generator.

Hash Functions
ns-3 provides a generic interface to general purpose hash functions. In the simplest
usage, the hash function returns the 32-bit or 64-bit hash of a data buffer or string.
The default underlying hash function is murmur3, chosen because it has good hash function
properties and offers a 64-bit version. The venerable FNV1a hash is also available.

There is a straight-forward mechanism to add (or provide at run time) alternative hash
function implementations.

Basic Usage
The simplest way to get a hash value of a data buffer or string is just:

#include "ns3/hash.h"

using namespace ns3;

char * buffer = ...
size_t buffer_size = ...

uint32_t buffer_hash = Hash32 ( buffer, buffer_size);

std::string s;
uint32_t string_hash = Hash32 (s);

Equivalent functions are defined for 64-bit hash values.

Incremental Hashing
In some situations it's useful to compute the hash of multiple buffers, as if they had
been joined together. (For example, you might want the hash of a packet stream, but not
want to assemble a single buffer with the combined contents of all the packets.)

This is almost as straight-forward as the first example:

#include "ns3/hash.h"

using namespace ns3;

char * buffer;
size_t buffer_size;

Hasher hasher; // Use default hash function

for (<every buffer>)
{
buffer = get_next_buffer ();
hasher (buffer, buffer_size);
}
uint32_t combined_hash = hasher.GetHash32 ();

By default Hasher preserves internal state to enable incremental hashing. If you want to
reuse a Hasher object (for example because it's configured with a non-default hash
function), but don't want to add to the previously computed hash, you need to clear()
first:

hasher.clear ().GetHash32 (buffer, buffer_size);

This reinitializes the internal state before hashing the buffer.

Using an Alternative Hash Function
The default hash function is murmur3. FNV1a is also available. To specify the hash
function explicitly, use this contructor:

Hasher hasher = Hasher ( Create<Hash::Function::Fnv1a> () );

Adding New Hash Function Implementations
To add the hash function foo, follow the hash-murmur3.h/.cc pattern:

· Create a class declaration (.h) and definition (.cc) inheriting from
Hash::Implementation.

· include the declaration in hash.h (at the point where hash-murmur3.h is included.

· In your own code, instantiate a Hasher object via the constructor Hasher
(Ptr<Hash::Function::Foo> ())

If your hash function is a single function, e.g. hashf, you don't even need to create a
new class derived from HashImplementation:

Hasher hasher =
Hasher ( Create<Hash::Function::Hash32> (&hashf) );

For this to compile, your hashf has to match one of the function pointer signatures:

typedef uint32_t (*Hash32Function_ptr) (const char *, const size_t);
typedef uint64_t (*Hash64Function_ptr) (const char *, const size_t);

Sources for Hash Functions
Sources for other hash function implementations include:

· Peter Kankowski: http://www.strchr.com

· Arash Partow: http://www.partow.net/programming/hashfunctions/index.html

· SMHasher: http://code.google.com/p/smhasher/

· Sanmayce: http://www.sanmayce.com/Fastest_Hash/index.html

Events and Simulator
ns-3 is a discrete-event network simulator. Conceptually, the simulator keeps track of a
number of events that are scheduled to execute at a specified simulation time. The job of
the simulator is to execute the events in sequential time order. Once the completion of
an event occurs, the simulator will move to the next event (or will exit if there are no
more events in the event queue). If, for example, an event scheduled for simulation time
"100 seconds" is executed, and the next event is not scheduled until "200 seconds", the
simulator will immediately jump from 100 seconds to 200 seconds (of simulation time) to
execute the next event. This is what is meant by "discrete-event" simulator.

To make this all happen, the simulator needs a few things:

1. a simulator object that can access an event queue where events are stored and that can
manage the execution of events

2. a scheduler responsible for inserting and removing events from the queue

3. a way to represent simulation time

4. the events themselves

This chapter of the manual describes these fundamental objects (simulator, scheduler,
time, event) and how they are used.

Event
To be completed

Simulator
The Simulator class is the public entry point to access event scheduling facilities. Once
a couple of events have been scheduled to start the simulation, the user can start to
execute them by entering the simulator main loop (call Simulator::Run). Once the main loop
starts running, it will sequentially execute all scheduled events in order from oldest to
most recent until there are either no more events left in the event queue or
Simulator::Stop has been called.

To schedule events for execution by the simulator main loop, the Simulator class provides
the Simulator::Schedule* family of functions.

1. Handling event handlers with different signatures

These functions are declared and implemented as C++ templates to handle automatically the
wide variety of C++ event handler signatures used in the wild. For example, to schedule an
event to execute 10 seconds in the future, and invoke a C++ method or function with
specific arguments, you might write this:

void handler (int arg0, int arg1)
{
std::cout << "handler called with argument arg0=" << arg0 << " and
arg1=" << arg1 << std::endl;
}

Simulator::Schedule(Seconds(10), &handler, 10, 5);

Which will output:

handler called with argument arg0=10 and arg1=5

Of course, these C++ templates can also handle transparently member methods on C++
objects:

To be completed: member method example

Notes:

· the ns-3 Schedule methods recognize automatically functions and methods only if they
take less than 5 arguments. If you need them to support more arguments, please, file a
bug report.

· Readers familiar with the term 'fully-bound functors' will recognize the
Simulator::Schedule methods as a way to automatically construct such objects.

2. Common scheduling operations

The Simulator API was designed to make it really simple to schedule most events. It
provides three variants to do so (ordered from most commonly used to least commonly used):

· Schedule methods which allow you to schedule an event in the future by providing the
delay between the current simulation time and the expiration date of the target event.

· ScheduleNow methods which allow you to schedule an event for the current simulation
time: they will execute _after_ the current event is finished executing but _before_ the
simulation time is changed for the next event.

· ScheduleDestroy methods which allow you to hook in the shutdown process of the Simulator
to cleanup simulation resources: every 'destroy' event is executed when the user calls
the Simulator::Destroy method.

3. Maintaining the simulation context

There are two basic ways to schedule events, with and without context. What does this
mean?

Simulator::Schedule (Time const &time, MEM mem_ptr, OBJ obj);

vs.

Simulator::ScheduleWithContext (uint32_t context, Time const &time, MEM mem_ptr, OBJ obj);

Readers who invest time and effort in developing or using a non-trivial simulation model
will know the value of the ns-3 logging framework to debug simple and complex simulations
alike. One of the important features that is provided by this logging framework is the
automatic display of the network node id associated with the 'currently' running event.

The node id of the currently executing network node is in fact tracked by the Simulator
class. It can be accessed with the Simulator::GetContext method which returns the
'context' (a 32-bit integer) associated and stored in the currently-executing event. In
some rare cases, when an event is not associated with a specific network node, its
'context' is set to 0xffffffff.

To associate a context to each event, the Schedule, and ScheduleNow methods automatically
reuse the context of the currently-executing event as the context of the event scheduled
for execution later.

In some cases, most notably when simulating the transmission of a packet from a node to
another, this behavior is undesirable since the expected context of the reception event is
that of the receiving node, not the sending node. To avoid this problem, the Simulator
class provides a specific schedule method: ScheduleWithContext which allows one to provide
explicitly the node id of the receiving node associated with the receive event.

XXX: code example

In some very rare cases, developers might need to modify or understand how the context
(node id) of the first event is set to that of its associated node. This is accomplished
by the NodeList class: whenever a new node is created, the NodeList class uses
ScheduleWithContext to schedule a 'initialize' event for this node. The 'initialize' event
thus executes with a context set to that of the node id and can use the normal variety of
Schedule methods. It invokes the Node::Initialize method which propagates the 'initialize'
event by calling the DoInitialize method for each object associated with the node. The
DoInitialize method overridden in some of these objects (most notably in the Application
base class) will schedule some events (most notably Application::StartApplication) which
will in turn scheduling traffic generation events which will in turn schedule
network-level events.

Notes:

· Users need to be careful to propagate DoInitialize methods across objects by calling
Initialize explicitely on their member objects

· The context id associated with each ScheduleWithContext method has other uses beyond
logging: it is used by an experimental branch of ns-3 to perform parallel simulation on
multicore systems using multithreading.

The Simulator::* functions do not know what the context is: they merely make sure that
whatever context you specify with ScheduleWithContext is available when the corresponding
event executes with ::GetContext.

It is up to the models implemented on top of Simulator::* to interpret the context value.
In ns-3, the network models interpret the context as the node id of the node which
generated an event. This is why it is important to call ScheduleWithContext in
ns3::Channel subclasses because we are generating an event from node i to node j and we
want to make sure that the event which will run on node j has the right context.

Time
To be completed

Scheduler
To be completed

Callbacks
Some new users to ns-3 are unfamiliar with an extensively used programming idiom used
throughout the code: the ns-3 callback. This chapter provides some motivation on the
callback, guidance on how to use it, and details on its implementation.

Callbacks Motivation
Consider that you have two simulation models A and B, and you wish to have them pass
information between them during the simulation. One way that you can do that is that you
can make A and B each explicitly knowledgeable about the other, so that they can invoke
methods on each other:

class A {
public:
void ReceiveInput ( // parameters );
...
}

(in another source file:)

class B {
public:
void DoSomething (void);
...

private:
A* a_instance; // pointer to an A
}

void
B::DoSomething()
{
// Tell a_instance that something happened
a_instance->ReceiveInput ( // parameters);
...
}

This certainly works, but it has the drawback that it introduces a dependency on A and B
to know about the other at compile time (this makes it harder to have independent
compilation units in the simulator) and is not generalized; if in a later usage scenario,
B needs to talk to a completely different C object, the source code for B needs to be
changed to add a c_instance and so forth. It is easy to see that this is a brute force
mechanism of communication that can lead to programming cruft in the models.

This is not to say that objects should not know about one another if there is a hard
dependency between them, but that often the model can be made more flexible if its
interactions are less constrained at compile time.

This is not an abstract problem for network simulation research, but rather it has been a
source of problems in previous simulators, when researchers want to extend or modify the
system to do different things (as they are apt to do in research). Consider, for example,
a user who wants to add an IPsec security protocol sublayer between TCP and IP:

------------ -----------
| TCP | | TCP |
------------ -----------
| becomes -> |
----------- -----------
| IP | | IPsec |
----------- -----------
|
-----------
| IP |
-----------

If the simulator has made assumptions, and hard coded into the code, that IP always talks
to a transport protocol above, the user may be forced to hack the system to get the
desired interconnections. This is clearly not an optimal way to design a generic
simulator.

Callbacks Background
NOTE:
Readers familiar with programming callbacks may skip this tutorial section.

The basic mechanism that allows one to address the problem above is known as a callback.
The ultimate goal is to allow one piece of code to call a function (or method in C++)
without any specific inter-module dependency.

This ultimately means you need some kind of indirection -- you treat the address of the
called function as a variable. This variable is called a pointer-to-function variable.
The relationship between function and pointer-to-function pointer is really no different
that that of object and pointer-to-object.

In C the canonical example of a pointer-to-function is a
pointer-to-function-returning-integer (PFI). For a PFI taking one int parameter, this
could be declared like,:

int (*pfi)(int arg) = 0;

What you get from this is a variable named simply pfi that is initialized to the value 0.
If you want to initialize this pointer to something meaningful, you have to have a
function with a matching signature. In this case:

int MyFunction (int arg) {}

If you have this target, you can initialize the variable to point to your function like:

pfi = MyFunction;

You can then call MyFunction indirectly using the more suggestive form of the call:

int result = (*pfi) (1234);

This is suggestive since it looks like you are dereferencing the function pointer just
like you would dereference any pointer. Typically, however, people take advantage of the
fact that the compiler knows what is going on and will just use a shorter form:

int result = pfi (1234);

Notice that the function pointer obeys value semantics, so you can pass it around like any
other value. Typically, when you use an asynchronous interface you will pass some entity
like this to a function which will perform an action and call back to let you know it
completed. It calls back by following the indirection and executing the provided function.

In C++ you have the added complexity of objects. The analogy with the PFI above means you
have a pointer to a member function returning an int (PMI) instead of the pointer to
function returning an int (PFI).

The declaration of the variable providing the indirection looks only slightly different:

int (MyClass::*pmi) (int arg) = 0;

This declares a variable named pmi just as the previous example declared a variable named
pfi. Since the will be to call a method of an instance of a particular class, one must
declare that method in a class:

class MyClass {
public:
int MyMethod (int arg);
};

Given this class declaration, one would then initialize that variable like this:

pmi = &MyClass::MyMethod;

This assigns the address of the code implementing the method to the variable, completing
the indirection. In order to call a method, the code needs a this pointer. This, in turn,
means there must be an object of MyClass to refer to. A simplistic example of this is just
calling a method indirectly (think virtual function):

int (MyClass::*pmi) (int arg) = 0; // Declare a PMI
pmi = &MyClass::MyMethod; // Point at the implementation code

MyClass myClass; // Need an instance of the class
(myClass.*pmi) (1234); // Call the method with an object ptr

Just like in the C example, you can use this in an asynchronous call to another module
which will call back using a method and an object pointer. The straightforward extension
one might consider is to pass a pointer to the object and the PMI variable. The module
would just do:

(*objectPtr.*pmi) (1234);

to execute the callback on the desired object.

One might ask at this time, what's the point? The called module will have to understand
the concrete type of the calling object in order to properly make the callback. Why not
just accept this, pass the correctly typed object pointer and do object->Method(1234) in
the code instead of the callback? This is precisely the problem described above. What is
needed is a way to decouple the calling function from the called class completely. This
requirement led to the development of the Functor.

A functor is the outgrowth of something invented in the 1960s called a closure. It is
basically just a packaged-up function call, possibly with some state.

A functor has two parts, a specific part and a generic part, related through inheritance.
The calling code (the code that executes the callback) will execute a generic overloaded
operator () of a generic functor to cause the callback to be called. The called code (the
code that wants to be called back) will have to provide a specialized implementation of
the operator () that performs the class-specific work that caused the close-coupling
problem above.

With the specific functor and its overloaded operator () created, the called code then
gives the specialized code to the module that will execute the callback (the calling
code).

The calling code will take a generic functor as a parameter, so an implicit cast is done
in the function call to convert the specific functor to a generic functor. This means
that the calling module just needs to understand the generic functor type. It is decoupled
from the calling code completely.

The information one needs to make a specific functor is the object pointer and the
pointer-to-method address.

The essence of what needs to happen is that the system declares a generic part of the
functor:

template <typename T>
class Functor
{
public:
virtual int operator() (T arg) = 0;
};

The caller defines a specific part of the functor that really is just there to implement
the specific operator() method:

template <typename T, typename ARG>
class SpecificFunctor : public Functor<ARG>
{
public:
SpecificFunctor(T* p, int (T::*_pmi)(ARG arg))
{
m_p = p;
m_pmi = _pmi;
}

virtual int operator() (ARG arg)
{
(*m_p.*m_pmi)(arg);
}
private:
int (T::*m_pmi)(ARG arg);
T* m_p;
};

Here is an example of the usage:

class A
{
public:
A (int a0) : a (a0) {}
int Hello (int b0)
{
std::cout << "Hello from A, a = " << a << " b0 = " << b0 << std::endl;
}
int a;
};

int main()
{
A a(10);
SpecificFunctor<A, int> sf(&a, &A::Hello);
sf(5);
}

NOTE:
The previous code is not real ns-3 code. It is simplistic example code used only to
illustrate the concepts involved and to help you understand the system more. Do not
expect to find this code anywhere in the ns-3 tree.

Notice that there are two variables defined in the class above. The m_p variable is the
object pointer and m_pmi is the variable containing the address of the function to
execute.

Notice that when operator() is called, it in turn calls the method provided with the
object pointer using the C++ PMI syntax.

To use this, one could then declare some model code that takes a generic functor as a
parameter:

void LibraryFunction (Functor functor);

The code that will talk to the model would build a specific functor and pass it to
LibraryFunction:

MyClass myClass;
SpecificFunctor<MyClass, int> functor (&myclass, MyClass::MyMethod);

When LibraryFunction is done, it executes the callback using the operator() on the generic
functor it was passed, and in this particular case, provides the integer argument:

void
LibraryFunction (Functor functor)
{
// Execute the library function
functor(1234);
}

Notice that LibraryFunction is completely decoupled from the specific type of the client.
The connection is made through the Functor polymorphism.

The Callback API in ns-3 implements object-oriented callbacks using the functor mechanism.
This callback API, being based on C++ templates, is type-safe; that is, it performs static
type checks to enforce proper signature compatibility between callers and callees. It is
therefore more type-safe to use than traditional function pointers, but the syntax may
look imposing at first. This section is designed to walk you through the Callback system
so that you can be comfortable using it in ns-3.

Using the Callback API
The Callback API is fairly minimal, providing only two services:

1. callback type declaration: a way to declare a type of callback with a given signature,
and,

2. callback instantiation: a way to instantiate a template-generated forwarding callback
which can forward any calls to another C++ class member method or C++ function.

This is best observed via walking through an example, based on samples/main-callback.cc.

Using the Callback API with static functions
Consider a function:

static double
CbOne (double a, double b)
{
std::cout << "invoke cbOne a=" << a << ", b=" << b << std::endl;
return a;
}

Consider also the following main program snippet:

int main (int argc, char *argv[])
{
// return type: double
// first arg type: double
// second arg type: double
Callback<double, double, double> one;
}

This is an example of a C-style callback -- one which does not include or need a this
pointer. The function template Callback is essentially the declaration of the variable
containing the pointer-to-function. In the example above, we explicitly showed a pointer
to a function that returned an integer and took a single integer as a parameter, The
Callback template function is a generic version of that -- it is used to declare the type
of a callback.

NOTE:
Readers unfamiliar with C++ templates may consult
http://www.cplusplus.com/doc/tutorial/templates/.

The Callback template requires one mandatory argument (the return type of the function to
be assigned to this callback) and up to five optional arguments, which each specify the
type of the arguments (if your particular callback function has more than five arguments,
then this can be handled by extending the callback implementation).

So in the above example, we have a declared a callback named "one" that will eventually
hold a function pointer. The signature of the function that it will hold must return
double and must support two double arguments. If one tries to pass a function whose
signature does not match the declared callback, a compilation error will occur. Also, if
one tries to assign to a callback an incompatible one, compilation will succeed but a
run-time NS_FATAL_ERROR will be raised. The sample program
src/core/examples/main-callback.cc demonstrates both of these error cases at the end of
the main() program.

Now, we need to tie together this callback instance and the actual target function
(CbOne). Notice above that CbOne has the same function signature types as the callback--
this is important. We can pass in any such properly-typed function to this callback.
Let's look at this more closely:

static double CbOne (double a, double b) {}
^ ^ ^
| | |
| | |
Callback<double, double, double> one;

You can only bind a function to a callback if they have the matching signature. The first
template argument is the return type, and the additional template arguments are the types
of the arguments of the function signature.

Now, let's bind our callback "one" to the function that matches its signature:

// build callback instance which points to cbOne function
one = MakeCallback (&CbOne);

This call to MakeCallback is, in essence, creating one of the specialized functors
mentioned above. The variable declared using the Callback template function is going to
be playing the part of the generic functor. The assignment one = MakeCallback (&CbOne) is
the cast that converts the specialized functor known to the callee to a generic functor
known to the caller.

Then, later in the program, if the callback is needed, it can be used as follows:

NS_ASSERT (!one.IsNull ());

// invoke cbOne function through callback instance
double retOne;
retOne = one (10.0, 20.0);

The check for IsNull() ensures that the callback is not null -- that there is a function
to call behind this callback. Then, one() executes the generic operator() which is really
overloaded with a specific implementation of operator() and returns the same result as if
CbOne() had been called directly.

Using the Callback API with member functions
Generally, you will not be calling static functions but instead public member functions of
an object. In this case, an extra argument is needed to the MakeCallback function, to
tell the system on which object the function should be invoked. Consider this example,
also from main-callback.cc:

class MyCb {
public:
int CbTwo (double a) {
std::cout << "invoke cbTwo a=" << a << std::endl;
return -5;
}
};

int main ()
{
...
// return type: int
// first arg type: double
Callback<int, double> two;
MyCb cb;
// build callback instance which points to MyCb::cbTwo
two = MakeCallback (&MyCb::CbTwo, &cb);
...
}

Here, we pass an additional object pointer to the MakeCallback<> function. Recall from
the background section above that Operator() will use the pointer to member syntax when it
executes on an object:

virtual int operator() (ARG arg)
{
(*m_p.*m_pmi)(arg);
}

And so we needed to provide the two variables (m_p and m_pmi) when we made the specific
functor. The line:

two = MakeCallback (&MyCb::CbTwo, &cb);

does precisely that. In this case, when two () is invoked:

int result = two (1.0);

will result in a call tothe CbTwo member function (method) on the object pointed to by
&cb.

Building Null Callbacks
It is possible for callbacks to be null; hence it may be wise to check before using them.
There is a special construct for a null callback, which is preferable to simply passing
"0" as an argument; it is the MakeNullCallback<> construct:

two = MakeNullCallback<int, double> ();
NS_ASSERT (two.IsNull ());

Invoking a null callback is just like invoking a null function pointer: it will crash at
runtime.

Bound Callbacks
A very useful extension to the functor concept is that of a Bound Callback. Previously it
was mentioned that closures were originally function calls packaged up for later
execution. Notice that in all of the Callback descriptions above, there is no way to
package up any parameters for use later -- when the Callback is called via operator().
All of the parameters are provided by the calling function.

What if it is desired to allow the client function (the one that provides the callback) to
provide some of the parameters? Alexandrescu calls the process of allowing a client to
specify one of the parameters "binding". One of the parameters of operator() has been
bound (fixed) by the client.

Some of our pcap tracing code provides a nice example of this. There is a function that
needs to be called whenever a packet is received. This function calls an object that
actually writes the packet to disk in the pcap file format. The signature of one of these
functions will be:

static void DefaultSink (Ptr<PcapFileWrapper> file, Ptr<const Packet> p);

The static keyword means this is a static function which does not need a this pointer, so
it will be using C-style callbacks. We don't want the calling code to have to know about
anything but the Packet. What we want in the calling code is just a call that looks like:

m_promiscSnifferTrace (m_currentPkt);

What we want to do is to bind the Ptr<PcapFileWriter> file to the specific callback
implementation when it is created and arrange for the operator() of the Callback to
provide that parameter for free.

We provide the MakeBoundCallback template function for that purpose. It takes the same
parameters as the MakeCallback template function but also takes the parameters to be
bound. In the case of the example above:

MakeBoundCallback (&DefaultSink, file);

will create a specific callback implementation that knows to add in the extra bound
arguments. Conceptually, it extends the specific functor described above with one or more
bound arguments:

template <typename T, typename ARG, typename BOUND_ARG>
class SpecificFunctor : public Functor
{
public:
SpecificFunctor(T* p, int (T::*_pmi)(ARG arg), BOUND_ARG boundArg)
{
m_p = p;
m_pmi = pmi;
m_boundArg = boundArg;
}

virtual int operator() (ARG arg)
{
(*m_p.*m_pmi)(m_boundArg, arg);
}
private:
void (T::*m_pmi)(ARG arg);
T* m_p;
BOUND_ARG m_boundArg;
};

You can see that when the specific functor is created, the bound argument is saved in the
functor / callback object itself. When the operator() is invoked with the single
parameter, as in:

m_promiscSnifferTrace (m_currentPkt);

the implementation of operator() adds the bound parameter into the actual function call:

(*m_p.*m_pmi)(m_boundArg, arg);

It's possible to bind two or three arguments as well. Say we have a function with
signature:

static void NotifyEvent (Ptr<A> a, Ptr<B> b, MyEventType e);

One can create bound callback binding first two arguments like:

MakeBoundCallback (&NotifyEvent, a1, b1);

assuming a1 and b1 are objects of type A and B respectively. Similarly for three
arguments one would have function with a signature:

static void NotifyEvent (Ptr<A> a, Ptr<B> b, MyEventType e);

Binding three arguments in done with:

MakeBoundCallback (&NotifyEvent, a1, b1, c1);

again assuming a1, b1 and c1 are objects of type A, B and C respectively.

This kind of binding can be used for exchanging information between objects in simulation;
specifically, bound callbacks can be used as traced callbacks, which will be described in
the next section.

Traced Callbacks
Placeholder subsection

Callback locations in ns-3
Where are callbacks frequently used in ns-3? Here are some of the more visible ones to
typical users:

· Socket API

· Layer-2/Layer-3 API

· Tracing subsystem

· API between IP and routing subsystems

Implementation details
The code snippets above are simplistic and only designed to illustrate the mechanism
itself. The actual Callback code is quite complicated and very template-intense and a
deep understanding of the code is not required. If interested, expert users may find the
following useful.

The code was originally written based on the techniques described in
http://www.codeproject.com/cpp/TTLFunction.asp. It was subsequently rewritten to follow
the architecture outlined in Modern C++ Design, Generic Programming and Design Patterns
Applied, Alexandrescu, chapter 5, Generalized Functors.

This code uses:

· default template parameters to saves users from having to specify empty parameters when
the number of parameters is smaller than the maximum supported number

· the pimpl idiom: the Callback class is passed around by value and delegates the crux of
the work to its pimpl pointer.

· two pimpl implementations which derive from CallbackImpl FunctorCallbackImpl can be used
with any functor-type while MemPtrCallbackImpl can be used with pointers to member
functions.

· a reference list implementation to implement the Callback's value semantics.

This code most notably departs from the Alexandrescu implementation in that it does not
use type lists to specify and pass around the types of the callback arguments. Of course,
it also does not use copy-destruction semantics and relies on a reference list rather than
autoPtr to hold the pointer.

Object model
ns-3 is fundamentally a C++ object system. Objects can be declared and instantiated as
usual, per C++ rules. ns-3 also adds some features to traditional C++ objects, as
described below, to provide greater functionality and features. This manual chapter is
intended to introduce the reader to the ns-3 object model.

This section describes the C++ class design for ns-3 objects. In brief, several design
patterns in use include classic object-oriented design (polymorphic interfaces and
implementations), separation of interface and implementation, the non-virtual public
interface design pattern, an object aggregation facility, and reference counting for
memory management. Those familiar with component models such as COM or Bonobo will
recognize elements of the design in the ns-3 object aggregation model, although the ns-3
design is not strictly in accordance with either.

Object-oriented behavior
C++ objects, in general, provide common object-oriented capabilities (abstraction,
encapsulation, inheritance, and polymorphism) that are part of classic object-oriented
design. ns-3 objects make use of these properties; for instance:

class Address
{
public:
Address ();
Address (uint8_t type, const uint8_t *buffer, uint8_t len);
Address (const Address & address);
Address &operator = (const Address &address);
...
private:
uint8_t m_type;
uint8_t m_len;
...
};

Object base classes
There are three special base classes used in ns-3. Classes that inherit from these base
classes can instantiate objects with special properties. These base classes are:

· class Object

· class ObjectBase

· class SimpleRefCount

It is not required that ns-3 objects inherit from these class, but those that do get
special properties. Classes deriving from class Object get the following properties.

· the ns-3 type and attribute system (see Attributes)

· an object aggregation system

· a smart-pointer reference counting system (class Ptr)

Classes that derive from class ObjectBase get the first two properties above, but do not
get smart pointers. Classes that derive from class SimpleRefCount: get only the
smart-pointer reference counting system.

In practice, class Object is the variant of the three above that the ns-3 developer will
most commonly encounter.

Memory management and class Ptr
Memory management in a C++ program is a complex process, and is often done incorrectly or
inconsistently. We have settled on a reference counting design described as follows.

All objects using reference counting maintain an internal reference count to determine
when an object can safely delete itself. Each time that a pointer is obtained to an
interface, the object's reference count is incremented by calling Ref(). It is the
obligation of the user of the pointer to explicitly Unref() the pointer when done. When
the reference count falls to zero, the object is deleted.

· When the client code obtains a pointer from the object itself through object creation,
or via GetObject, it does not have to increment the reference count.

· When client code obtains a pointer from another source (e.g., copying a pointer) it must
call Ref() to increment the reference count.

· All users of the object pointer must call Unref() to release the reference.

The burden for calling Unref() is somewhat relieved by the use of the reference counting
smart pointer class described below.

Users using a low-level API who wish to explicitly allocate non-reference-counted objects
on the heap, using operator new, are responsible for deleting such objects.

Reference counting smart pointer (Ptr)
Calling Ref() and Unref() all the time would be cumbersome, so ns-3 provides a smart
pointer class Ptr similar to Boost::intrusive_ptr. This smart-pointer class assumes that
the underlying type provides a pair of Ref and Unref methods that are expected to
increment and decrement the internal refcount of the object instance.

This implementation allows you to manipulate the smart pointer as if it was a normal
pointer: you can compare it with zero, compare it against other pointers, assign zero to
it, etc.

It is possible to extract the raw pointer from this smart pointer with the GetPointer()
and PeekPointer() methods.

If you want to store a newed object into a smart pointer, we recommend you to use the
CreateObject template functions to create the object and store it in a smart pointer to
avoid memory leaks. These functions are really small convenience functions and their goal
is just to save you a small bit of typing.

CreateObject and Create
Objects in C++ may be statically, dynamically, or automatically created. This holds true
for ns-3 also, but some objects in the system have some additional frameworks available.
Specifically, reference counted objects are usually allocated using a templated Create or
CreateObject method, as follows.

For objects deriving from class Object:

Ptr<WifiNetDevice> device = CreateObject<WifiNetDevice> ();

Please do not create such objects using operator new; create them using CreateObject()
instead.

For objects deriving from class SimpleRefCount, or other objects that support usage of the
smart pointer class, a templated helper function is available and recommended to be used:

Ptr<B> b = Create<B> ();

This is simply a wrapper around operator new that correctly handles the reference counting
system.

In summary, use Create<B> if B is not an object but just uses reference counting (e.g.
Packet), and use CreateObject<B> if B derives from ns3::Object.

Aggregation
The ns-3 object aggregation system is motivated in strong part by a recognition that a
common use case for ns-2 has been the use of inheritance and polymorphism to extend
protocol models. For instance, specialized versions of TCP such as RenoTcpAgent derive
from (and override functions from) class TcpAgent.

However, two problems that have arisen in the ns-2 model are downcasts and "weak base
class." Downcasting refers to the procedure of using a base class pointer to an object and
querying it at run time to find out type information, used to explicitly cast the pointer
to a subclass pointer so that the subclass API can be used. Weak base class refers to the
problems that arise when a class cannot be effectively reused (derived from) because it
lacks necessary functionality, leading the developer to have to modify the base class and
causing proliferation of base class API calls, some of which may not be semantically
correct for all subclasses.

ns-3 is using a version of the query interface design pattern to avoid these problems.
This design is based on elements of the Component Object Model and GNOME Bonobo although
full binary-level compatibility of replaceable components is not supported and we have
tried to simplify the syntax and impact on model developers.

Examples
Aggregation example
Node is a good example of the use of aggregation in ns-3. Note that there are not derived
classes of Nodes in ns-3 such as class InternetNode. Instead, components (protocols) are
aggregated to a node. Let's look at how some Ipv4 protocols are added to a node.:

static void
AddIpv4Stack(Ptr<Node> node)
{
Ptr<Ipv4L3Protocol> ipv4 = CreateObject<Ipv4L3Protocol> ();
ipv4->SetNode (node);
node->AggregateObject (ipv4);
Ptr<Ipv4Impl> ipv4Impl = CreateObject<Ipv4Impl> ();
ipv4Impl->SetIpv4 (ipv4);
node->AggregateObject (ipv4Impl);
}

Note that the Ipv4 protocols are created using CreateObject(). Then, they are aggregated
to the node. In this manner, the Node base class does not need to be edited to allow users
with a base class Node pointer to access the Ipv4 interface; users may ask the node for a
pointer to its Ipv4 interface at runtime. How the user asks the node is described in the
next subsection.

Note that it is a programming error to aggregate more than one object of the same type to
an ns3::Object. So, for instance, aggregation is not an option for storing all of the
active sockets of a node.

GetObject example
GetObject is a type-safe way to achieve a safe downcasting and to allow interfaces to be
found on an object.

Consider a node pointer m_node that points to a Node object that has an implementation of
IPv4 previously aggregated to it. The client code wishes to configure a default route. To
do so, it must access an object within the node that has an interface to the IP forwarding
configuration. It performs the following:

Ptr<Ipv4> ipv4 = m_node->GetObject<Ipv4> ();

If the node in fact does not have an Ipv4 object aggregated to it, then the method will
return null. Therefore, it is good practice to check the return value from such a function
call. If successful, the user can now use the Ptr to the Ipv4 object that was previously
aggregated to the node.

Another example of how one might use aggregation is to add optional models to objects. For
instance, an existing Node object may have an "Energy Model" object aggregated to it at
run time (without modifying and recompiling the node class). An existing model (such as a
wireless net device) can then later "GetObject" for the energy model and act appropriately
if the interface has been either built in to the underlying Node object or aggregated to
it at run time. However, other nodes need not know anything about energy models.

We hope that this mode of programming will require much less need for developers to modify
the base classes.

Object factories
A common use case is to create lots of similarly configured objects. One can repeatedly
call CreateObject() but there is also a factory design pattern in use in the ns-3 system.
It is heavily used in the "helper" API.

Class ObjectFactory can be used to instantiate objects and to configure the attributes on
those objects:

void SetTypeId (TypeId tid);
void Set (std::string name, const AttributeValue &value);
Ptr<T> Create (void) const;

The first method allows one to use the ns-3 TypeId system to specify the type of objects
created. The second allows one to set attributes on the objects to be created, and the
third allows one to create the objects themselves.

For example:

ObjectFactory factory;
// Make this factory create objects of type FriisPropagationLossModel
factory.SetTypeId ("ns3::FriisPropagationLossModel")
// Make this factory object change a default value of an attribute, for
// subsequently created objects
factory.Set ("SystemLoss", DoubleValue (2.0));
// Create one such object
Ptr<Object> object = factory.Create ();
factory.Set ("SystemLoss", DoubleValue (3.0));
// Create another object with a different SystemLoss
Ptr<Object> object = factory.Create ();

Downcasting
A question that has arisen several times is, "If I have a base class pointer (Ptr) to an
object and I want the derived class pointer, should I downcast (via C++ dynamic cast) to
get the derived pointer, or should I use the object aggregation system to GetObject<> ()
to find a Ptr to the interface to the subclass API?"

The answer to this is that in many situations, both techniques will work. ns-3 provides a
templated function for making the syntax of Object dynamic casting much more user
friendly:

template <typename T1, typename T2>
Ptr<T1>
DynamicCast (Ptr<T2> const&p)
{
return Ptr<T1> (dynamic_cast<T1 *> (PeekPointer (p)));
}

DynamicCast works when the programmer has a base type pointer and is testing against a
subclass pointer. GetObject works when looking for different objects aggregated, but also
works with subclasses, in the same way as DynamicCast. If unsure, the programmer should
use GetObject, as it works in all cases. If the programmer knows the class hierarchy of
the object under consideration, it is more direct to just use DynamicCast.

Configuration and Attributes
In ns-3 simulations, there are two main aspects to configuration:

· The simulation topology and how objects are connected.

· The values used by the models instantiated in the topology.

This chapter focuses on the second item above: how the many values in use in ns-3 are
organized, documented, and modifiable by ns-3 users. The ns-3 attribute system is also the
underpinning of how traces and statistics are gathered in the simulator.

In the course of this chapter we will discuss the various ways to set or modify the values
used by ns-3 model objects. In increasing order of specificity, these are:

┌─────────────────────────────────┬──────────────────────────────────┐
│Method │ Scope │
├─────────────────────────────────┼──────────────────────────────────┤
│Default Attribute values set │ Affect all instances of the │
│when Attributes are defined in │ class. │
GetTypeId (). │ │
└─────────────────────────────────┴──────────────────────────────────┘

CommandLine │ Affect all future instances. │
Config::SetDefault() │ │
ConfigStore │ │
├─────────────────────────────────┼──────────────────────────────────┤
ObjectFactory │ Affects all instances created │
│ │ with the factory. │
├─────────────────────────────────┼──────────────────────────────────┤
XHelperSetAttribute () │ Affects all instances created by │
│ │ the helper. │
├─────────────────────────────────┼──────────────────────────────────┤
MyClass::SetX () │ Alters this particular instance. │
Object::SetAttribute () │ Generally this is the only form │
Config::Set() │ which can be scheduled to alter │
│ │ an instance once the simulation │
│ │ is running. │
└─────────────────────────────────┴──────────────────────────────────┘

By "specificity" we mean that methods in later rows in the table override the values set
by, and typically affect fewer instances than, earlier methods.

Before delving into details of the attribute value system, it will help to review some
basic properties of class Object.

Object Overview
ns-3 is fundamentally a C++ object-based system. By this we mean that new C++ classes
(types) can be declared, defined, and subclassed as usual.

Many ns-3 objects inherit from the Object base class. These objects have some additional
properties that we exploit for organizing the system and improving the memory management
of our objects:

· "Metadata" system that links the class name to a lot of meta-information about the
object, including:

· The base class of the subclass,

· The set of accessible constructors in the subclass,

· The set of "attributes" of the subclass,

· Whether each attribute can be set, or is read-only,

· The allowed range of values for each attribute.

· Reference counting smart pointer implementation, for memory management.

ns-3 objects that use the attribute system derive from either Object or ObjectBase. Most
ns-3 objects we will discuss derive from Object, but a few that are outside the smart
pointer memory management framework derive from ObjectBase.

Let's review a couple of properties of these objects.

Smart Pointers
As introduced in the ns-3 tutorial, ns-3 objects are memory managed by a reference
counting smart pointer implementation, class Ptr.

Smart pointers are used extensively in the ns-3 APIs, to avoid passing references to
heap-allocated objects that may cause memory leaks. For most basic usage (syntax), treat
a smart pointer like a regular pointer:

Ptr<WifiNetDevice> nd = ...;
nd->CallSomeFunction ();
// etc.

So how do you get a smart pointer to an object, as in the first line of this example?

CreateObject
As we discussed above in Memory-management-and-class-Ptr, at the lowest-level API, objects
of type Object are not instantiated using operator new as usual but instead by a templated
function called CreateObject ().

A typical way to create such an object is as follows:

Ptr<WifiNetDevice> nd = CreateObject<WifiNetDevice> ();

You can think of this as being functionally equivalent to:

WifiNetDevice* nd = new WifiNetDevice ();

Objects that derive from Object must be allocated on the heap using CreateObject (). Those
deriving from ObjectBase, such as ns-3 helper functions and packet headers and trailers,
can be allocated on the stack.

In some scripts, you may not see a lot of CreateObject () calls in the code; this is
because there are some helper objects in effect that are doing the CreateObject () calls
for you.

TypeId
ns-3 classes that derive from class Object can include a metadata class called TypeId that
records meta-information about the class, for use in the object aggregation and component
manager systems:

· A unique string identifying the class.

· The base class of the subclass, within the metadata system.

· The set of accessible constructors in the subclass.

· A list of publicly accessible properties ("attributes") of the class.

Object Summary
Putting all of these concepts together, let's look at a specific example: class Node.

The public header file node.h has a declaration that includes a static GetTypeId ()
function call:

class Node : public Object
{
public:
static TypeId GetTypeId (void);
...

This is defined in the node.cc file as follows:

TypeId
Node::GetTypeId (void)
{
static TypeId tid = TypeId ("ns3::Node")
.SetParent<Object> ()
.AddConstructor<Node> ()
.AddAttribute ("DeviceList",
"The list of devices associated to this Node.",
ObjectVectorValue (),
MakeObjectVectorAccessor (&Node::m_devices),
MakeObjectVectorChecker<NetDevice> ())
.AddAttribute ("ApplicationList",
"The list of applications associated to this Node.",
ObjectVectorValue (),
MakeObjectVectorAccessor (&Node::m_applications),
MakeObjectVectorChecker<Application> ())
.AddAttribute ("Id",
"The id (unique integer) of this Node.",
TypeId::ATTR_GET, // allow only getting it.
UintegerValue (0),
MakeUintegerAccessor (&Node::m_id),
MakeUintegerChecker<uint32_t> ())
;
return tid;
}

Consider the TypeId of the ns-3 Object class as an extended form of run time type
information (RTTI). The C++ language includes a simple kind of RTTI in order to support
dynamic_cast and typeid operators.

The SetParent<Object> () call in the definition above is used in conjunction with our
object aggregation mechanisms to allow safe up- and down-casting in inheritance trees
during GetObject (). It also enables subclasses to inherit the Attributes of their parent
class.

The AddConstructor<Node> () call is used in conjunction with our abstract object factory
mechanisms to allow us to construct C++ objects without forcing a user to know the
concrete class of the object she is building.

The three calls to AddAttribute () associate a given string with a strongly typed value in
the class. Notice that you must provide a help string which may be displayed, for example,
via command line processors. Each Attribute is associated with mechanisms for accessing
the underlying member variable in the object (for example, MakeUintegerAccessor () tells
the generic Attribute code how to get to the node ID above). There are also "Checker"
methods which are used to validate values against range limitations, such as maximum and
minimum allowed values.

When users want to create Nodes, they will usually call some form of CreateObject (),:

Ptr<Node> n = CreateObject<Node> ();

or more abstractly, using an object factory, you can create a Node object without even
knowing the concrete C++ type:

ObjectFactory factory;
const std::string typeId = "ns3::Node'';
factory.SetTypeId (typeId);
Ptr<Object> node = factory.Create <Object> ();

Both of these methods result in fully initialized attributes being available in the
resulting Object instances.

We next discuss how attributes (values associated with member variables or functions of
the class) are plumbed into the above TypeId.

Attributes
The goal of the attribute system is to organize the access of internal member objects of a
simulation. This goal arises because, typically in simulation, users will cut and
paste/modify existing simulation scripts, or will use higher-level simulation constructs,
but often will be interested in studying or tracing particular internal variables. For
instance, use cases such as:

· "I want to trace the packets on the wireless interface only on the first access point."

· "I want to trace the value of the TCP congestion window (every time it changes) on a
particular TCP socket."

· "I want a dump of all values that were used in my simulation."

Similarly, users may want fine-grained access to internal variables in the simulation, or
may want to broadly change the initial value used for a particular parameter in all
subsequently created objects. Finally, users may wish to know what variables are settable
and retrievable in a simulation configuration. This is not just for direct simulation
interaction on the command line; consider also a (future) graphical user interface that
would like to be able to provide a feature whereby a user might right-click on an node on
the canvas and see a hierarchical, organized list of parameters that are settable on the
node and its constituent member objects, and help text and default values for each
parameter.

Defining Attributes
We provide a way for users to access values deep in the system, without having to plumb
accessors (pointers) through the system and walk pointer chains to get to them. Consider a
class DropTailQueue that has a member variable that is an unsigned integer m_maxPackets;
this member variable controls the depth of the queue.

If we look at the declaration of DropTailQueue, we see the following:

class DropTailQueue : public Queue {
public:
static TypeId GetTypeId (void);
...

private:
std::queue<Ptr<Packet> > m_packets;
uint32_t m_maxPackets;
};

Let's consider things that a user may want to do with the value of m_maxPackets:

· Set a default value for the system, such that whenever a new DropTailQueue is created,
this member is initialized to that default.

· Set or get the value on an already instantiated queue.

The above things typically require providing Set () and Get () functions, and some type of
global default value.

In the ns-3 attribute system, these value definitions and accessor function registrations
are moved into the TypeId class; e.g.:

NS_OBJECT_ENSURE_REGISTERED (DropTailQueue);

TypeId
DropTailQueue::GetTypeId (void)
{
static TypeId tid = TypeId ("ns3::DropTailQueue")
.SetParent<Queue> ()
.AddConstructor<DropTailQueue> ()
.AddAttribute ("MaxPackets",
"The maximum number of packets accepted by this DropTailQueue.",
UintegerValue (100),
MakeUintegerAccessor (&DropTailQueue::m_maxPackets),
MakeUintegerChecker<uint32_t> ())
;

return tid;
}

The AddAttribute () method is performing a number of things for the m_maxPackets value:

· Binding the (usually private) member variable m_maxPackets to a public string
"MaxPackets".

· Providing a default value (100 packets).

· Providing some help text defining the meaning of the value.

· Providing a "Checker" (not used in this example) that can be used to set bounds on the
allowable range of values.

The key point is that now the value of this variable and its default value are accessible
in the attribute namespace, which is based on strings such as "MaxPackets" and TypeId name
strings. In the next section, we will provide an example script that shows how users may
manipulate these values.

Note that initialization of the attribute relies on the macro NS_OBJECT_ENSURE_REGISTERED
(DropTailQueue) being called; if you leave this out of your new class implementation, your
attributes will not be initialized correctly.

While we have described how to create attributes, we still haven't described how to access
and manage these values. For instance, there is no globals.h header file where these are
stored; attributes are stored with their classes. Questions that naturally arise are how
do users easily learn about all of the attributes of their models, and how does a user
access these attributes, or document their values as part of the record of their
simulation?

Detailed documentation of the actual attributes defined for a type, and a global list of
all defined attributes, are available in the API documentation. For the rest of this
document we are going to demonstrate the various ways of getting and setting attribute
values.

Setting Default Values
Config::SetDefault and CommandLine
Let's look at how a user script might access a specific attribute value. We're going to
use the src/point-to-point/examples/main-attribute-value.cc script for illustration, with
some details stripped out. The main function begins:

// This is a basic example of how to use the attribute system to
// set and get a value in the underlying system; namely, an unsigned
// integer of the maximum number of packets in a queue
//

int
main (int argc, char *argv[])
{

// By default, the MaxPackets attribute has a value of 100 packets
// (this default can be observed in the function DropTailQueue::GetTypeId)
//
// Here, we set it to 80 packets. We could use one of two value types:
// a string-based value or a Uinteger value
Config::SetDefault ("ns3::DropTailQueue::MaxPackets", StringValue ("80"));
// The below function call is redundant
Config::SetDefault ("ns3::DropTailQueue::MaxPackets", UintegerValue (80));

// Allow the user to override any of the defaults and the above
// SetDefaults () at run-time, via command-line arguments
// For example, via "--ns3::DropTailQueue::MaxPackets=80"
CommandLine cmd;
// This provides yet another way to set the value from the command line:
cmd.AddValue ("maxPackets", "ns3::DropTailQueue::MaxPackets");
cmd.Parse (argc, argv);

The main thing to notice in the above are the two equivalent calls to Config::SetDefault
(). This is how we set the default value for all subsequently instantiated
DropTailQueues. We illustrate that two types of Value classes, a StringValue and a
UintegerValue class, can be used to assign the value to the attribute named by
"ns3::DropTailQueue::MaxPackets".

It's also possible to manipulate Attributes using the CommandLine; we saw some examples
early in the Tutorial. In particular, it is straightforward to add a shorthand argument
name, such as --maxPackets, for an Attribute that is particular relevant for your model,
in this case "ns3::DropTailQueue::MaxPackets". This has the additional feature that the
help string for the Attribute will be printed as part of the usage message for the script.
For more information see the CommandLine API documentation.

Now, we will create a few objects using the low-level API. Our newly created queues will
not have m_maxPackets initialized to 100 packets, as defined in the
DropTailQueue::GetTypeId () function, but to 80 packets, because of what we did above with
default values.:

Ptr<Node> n0 = CreateObject<Node> ();

Ptr<PointToPointNetDevice> net0 = CreateObject<PointToPointNetDevice> ();
n0->AddDevice (net0);

Ptr<Queue> q = CreateObject<DropTailQueue> ();
net0->AddQueue(q);

At this point, we have created a single Node (n0) and a single PointToPointNetDevice
(net0), and added a DropTailQueue (q) to net0.

Constructors, Helpers and ObjectFactory
Arbitrary combinations of attributes can be set and fetched from the helper and low-level
APIs; either from the constructors themselves:

Ptr<GridPositionAllocator> p =
CreateObjectWithAttributes<GridPositionAllocator>
("MinX", DoubleValue (-100.0),
"MinY", DoubleValue (-100.0),
"DeltaX", DoubleValue (5.0),
"DeltaY", DoubleValue (20.0),
"GridWidth", UintegerValue (20),
"LayoutType", StringValue ("RowFirst"));

or from the higher-level helper APIs, such as:

mobility.SetPositionAllocator
("ns3::GridPositionAllocator",
"MinX", DoubleValue (-100.0),
"MinY", DoubleValue (-100.0),
"DeltaX", DoubleValue (5.0),
"DeltaY", DoubleValue (20.0),
"GridWidth", UintegerValue (20),
"LayoutType", StringValue ("RowFirst"));

We don't illustrate it here, but you can also configure an ObjectFactory with new values
for specific attributes. Instances created by the ObjectFactory will have those
attributes set during construction. This is very similar to using one of the helper APIs
for the class.

To review, there are several ways to set values for attributes for class instances to be
created in the future:

· Config::SetDefault ()

· CommandLine::AddValue ()

· CreateObjectWithAttributes<> ()

· Various helper APIs

But what if you've already created an instance, and you want to change the value of the
attribute? In this example, how can we manipulate the m_maxPackets value of the already
instantiated DropTailQueue? Here are various ways to do that.

Changing Values
SmartPointer
Assume that a smart pointer (Ptr) to a relevant network device is in hand; in the current
example, it is the net0 pointer.

One way to change the value is to access a pointer to the underlying queue and modify its
attribute.

First, we observe that we can get a pointer to the (base class) Queue via the
PointToPointNetDevice attributes, where it is called "TxQueue":

PointerValue tmp;
net0->GetAttribute ("TxQueue", tmp);
Ptr<Object> txQueue = tmp.GetObject ();

Using the GetObject () function, we can perform a safe downcast to a DropTailQueue, where
"MaxPackets" is an attribute:

Ptr<DropTailQueue> dtq = txQueue->GetObject <DropTailQueue> ();
NS_ASSERT (dtq != 0);

Next, we can get the value of an attribute on this queue. We have introduced wrapper
Value classes for the underlying data types, similar to Java wrappers around these types,
since the attribute system stores values serialized to strings, and not disparate types.
Here, the attribute value is assigned to a UintegerValue, and the Get () method on this
value produces the (unwrapped) uint32_t.:

UintegerValue limit;
dtq->GetAttribute ("MaxPackets", limit);
NS_LOG_INFO ("1. dtq limit: " << limit.Get () << " packets");

Note that the above downcast is not really needed; we could have gotten the attribute
value directly from txQueue, which is an Object:

txQueue->GetAttribute ("MaxPackets", limit);
NS_LOG_INFO ("2. txQueue limit: " << limit.Get () << " packets");

Now, let's set it to another value (60 packets):

txQueue->SetAttribute("MaxPackets", UintegerValue (60));
txQueue->GetAttribute ("MaxPackets", limit);
NS_LOG_INFO ("3. txQueue limit changed: " << limit.Get () << " packets");

Config Namespace Path
An alternative way to get at the attribute is to use the configuration namespace. Here,
this attribute resides on a known path in this namespace; this approach is useful if one
doesn't have access to the underlying pointers and would like to configure a specific
attribute with a single statement.:

Config::Set ("/NodeList/0/DeviceList/0/TxQueue/MaxPackets",
UintegerValue (25));
txQueue->GetAttribute ("MaxPackets", limit);
NS_LOG_INFO ("4. txQueue limit changed through namespace: "
<< limit.Get () << " packets");

The configuration path often has the form of ".../<container
name>/<index>/.../<attribute>/<attribute>" to refer to a specific instance by index of an
object in the container. In this case the first container is the list of all Nodes; the
second container is the list of all NetDevices on the chosen Node. Finally, the
configuration path usually ends with a succession of member attributes, in this case the
"MaxPackets" attribute of the "TxQueue" of the chosen NetDevice.

We could have also used wildcards to set this value for all nodes and all net devices
(which in this simple example has the same effect as the previous Config::Set ()):

Config::Set ("/NodeList/*/DeviceList/*/TxQueue/MaxPackets",
UintegerValue (15));
txQueue->GetAttribute ("MaxPackets", limit);
NS_LOG_INFO ("5. txQueue limit changed through wildcarded namespace: "
<< limit.Get () << " packets");

Object Name Service
Another way to get at the attribute is to use the object name service facility. The
object name service allows us to add items to the configuration namespace under the
"/Names/" path with a user-defined name string. This approach is useful if one doesn't
have access to the underlying pointers and it is difficult to determine the required
concrete configuration namespace path.

Names::Add ("server", n0);
Names::Add ("server/eth0", net0);

...

Config::Set ("/Names/server/eth0/TxQueue/MaxPackets", UintegerValue (25));

Here we've added the path elements "server" and "eth0" under the "/Names/" namespace, then
used the resulting configuration path to set the attribute.

See Object-names for a fuller treatment of the ns-3 configuration namespace.

Implementation Details
Value Classes
Readers will note the TypeValue classes which are subclasses of the AttributeValue base
class. These can be thought of as intermediate classes which are used to convert from raw
types to the AttributeValues that are used by the attribute system. Recall that this
database is holding objects of many types serialized to strings. Conversions to this type
can either be done using an intermediate class (such as IntegerValue, or DoubleValue for
floating point numbers) or via strings. Direct implicit conversion of types to
AttributeValue is not really practical. So in the above, users have a choice of using
strings or values:

p->Set ("cwnd", StringValue ("100")); // string-based setter
p->Set ("cwnd", IntegerValue (100)); // integer-based setter

The system provides some macros that help users declare and define new AttributeValue
subclasses for new types that they want to introduce into the attribute system:

· ATTRIBUTE_HELPER_HEADER

· ATTRIBUTE_HELPER_CPP

See the API documentation for these constructs for more information.

Initialization Order
Attributes in the system must not depend on the state of any other Attribute in this
system. This is because an ordering of Attribute initialization is not specified, nor
enforced, by the system. A specific example of this can be seen in automated configuration
programs such as ConfigStore. Although a given model may arrange it so that Attributes
are initialized in a particular order, another automatic configurator may decide
independently to change Attributes in, for example, alphabetic order.

Because of this non-specific ordering, no Attribute in the system may have any dependence
on any other Attribute. As a corollary, Attribute setters must never fail due to the state
of another Attribute. No Attribute setter may change (set) any other Attribute value as a
result of changing its value.

This is a very strong restriction and there are cases where Attributes must set
consistently to allow correct operation. To this end we do allow for consistency checking
when the attribute is used (cf. NS_ASSERT_MSG or NS_ABORT_MSG).

In general, the attribute code to assign values to the underlying class member variables
is executed after an object is constructed. But what if you need the values assigned
before the constructor body executes, because you need them in the logic of the
constructor? There is a way to do this, used for example in the class ConfigStore: call
ObjectBase::ConstructSelf () as follows:

ConfigStore::ConfigStore ()
{
ObjectBase::ConstructSelf (AttributeConstructionList ());
// continue on with constructor.
}

Beware that the object and all its derived classes must also implement a GetInstanceTypeId
() method. Otherwise the ObjectBase::ConstructSelf () will not be able to read the
attributes.

Adding Attributes
The ns-3 system will place a number of internal values under the attribute system, but
undoubtedly users will want to extend this to pick up ones we have missed, or to add their
own classes to the system.

There are three typical use cases:

· Making an existing class data member accessible as an Attribute, when it isn't already.

· Making a new class able to expose some data members as Attributes by giving it a TypeId.

· Creating an AttributeValue subclass for a new class so that it can be accessed as an
Attribute.

Existing Member Variable
Consider this variable in TcpSocket:

uint32_t m_cWnd; // Congestion window

Suppose that someone working with TCP wanted to get or set the value of that variable
using the metadata system. If it were not already provided by ns-3, the user could declare
the following addition in the runtime metadata system (to the GetTypeId() definition for
TcpSocket):

.AddAttribute ("Congestion window",
"Tcp congestion window (bytes)",
UintegerValue (1),
MakeUintegerAccessor (&TcpSocket::m_cWnd),
MakeUintegerChecker<uint16_t> ())

Now, the user with a pointer to a TcpSocket instance can perform operations such as
setting and getting the value, without having to add these functions explicitly.
Furthermore, access controls can be applied, such as allowing the parameter to be read and
not written, or bounds checking on the permissible values can be applied.

New Class TypeId
Here, we discuss the impact on a user who wants to add a new class to ns-3. What
additional things must be done to enable it to hold attributes?

Let's assume our new class, called ns3::MyMobility, is a type of mobility model. First,
the class should inherit from it's parent class, ns3::MobilityModel. In the my-mobility.h
header file:

namespace ns3 {

class MyClass : public MobilityModel
{

This requires we declare the GetTypeId () function. This is a one-line public function
declaration:

public:
/**
* Register this type.
* \return The object TypeId.
*/
static TypeId GetTypeId (void);

We've already introduced what a TypeId definition will look like in the my-mobility.cc
implementation file:

NS_OBJECT_ENSURE_REGISTERED (MyMobility);

TypeId
MyMobility::GetTypeId (void)
{
static TypeId tid = TypeId ("ns3::MyMobility")
.SetParent<MobilityModel> ()
.SetGroupName ("Mobility")
.AddConstructor<MyMobility> ()
.AddAttribute ("Bounds",
"Bounds of the area to cruise.",
RectangleValue (Rectangle (0.0, 0.0, 100.0, 100.0)),
MakeRectangleAccessor (&MyMobility::m_bounds),
MakeRectangleChecker ())
.AddAttribute ("Time",
"Change current direction and speed after moving for this delay.",
TimeValue (Seconds (1.0)),
MakeTimeAccessor (&MyMobility::m_modeTime),
MakeTimeChecker ())
// etc (more parameters).
;
return tid;
}

If we don't want to subclass from an existing class, in the header file we just inherit
from ns3::Object, and in the object file we set the parent class to ns3::Object with
.SetParent<Object> ().

Typical mistakes here involve:

· Not calling NS_OBJECT_ENSURE_REGISTERED ()

· Not calling the SetParent () method, or calling it with the wrong type.

· Not calling the AddConstructor () method, or calling it with the wrong type.

· Introducing a typographical error in the name of the TypeId in its constructor.

· Not using the fully-qualified C++ typename of the enclosing C++ class as the name of the
TypeId. Note that "ns3::" is required.

None of these mistakes can be detected by the ns-3 codebase, so users are advised to check
carefully multiple times that they got these right.

New AttributeValue Type
From the perspective of the user who writes a new class in the system and wants it to be
accessible as an attribute, there is mainly the matter of writing the conversions to/from
strings and attribute values. Most of this can be copy/pasted with macro-ized code. For
instance, consider a class declaration for Rectangle in the src/mobility/model directory:

Header File
/**
* \brief a 2d rectangle
*/
class Rectangle
{
...

double xMin;
double xMax;
double yMin;
double yMax;
};

One macro call and two operators, must be added below the class declaration in order to
turn a Rectangle into a value usable by the Attribute system:

std::ostream &operator << (std::ostream &os, const Rectangle &rectangle);
std::istream &operator >> (std::istream &is, Rectangle &rectangle);

ATTRIBUTE_HELPER_HEADER (Rectangle);

Implementation File
In the class definition (.cc file), the code looks like this:

ATTRIBUTE_HELPER_CPP (Rectangle);

std::ostream &
operator << (std::ostream &os, const Rectangle &rectangle)
{
os << rectangle.xMin << "|" << rectangle.xMax << "|" << rectangle.yMin << "|"
<< rectangle.yMax;
return os;
}
std::istream &
operator >> (std::istream &is, Rectangle &rectangle)
{
char c1, c2, c3;
is >> rectangle.xMin >> c1 >> rectangle.xMax >> c2 >> rectangle.yMin >> c3
>> rectangle.yMax;
if (c1 != '|' ||
c2 != '|' ||
c3 != '|')
{
is.setstate (std::ios_base::failbit);
}
return is;
}

These stream operators simply convert from a string representation of the Rectangle
("xMin|xMax|yMin|yMax") to the underlying Rectangle. The modeler must specify these
operators and the string syntactical representation of an instance of the new class.

ConfigStore
Values for ns-3 attributes can be stored in an ASCII or XML text file and loaded into a
future simulation run. This feature is known as the ns-3 ConfigStore. The ConfigStore is
a specialized database for attribute values and default values.

Although it is a separately maintained module in the src/config-store/ directory, we
document it here because of its sole dependency on ns-3 core module and attributes.

We can explore this system by using an example from
src/config-store/examples/config-store-save.cc.

First, all users of the ConfigStore must include the following statement:

#include "ns3/config-store-module.h"

Next, this program adds a sample object ConfigExample to show how the system is extended:

class ConfigExample : public Object
{
public:
static TypeId GetTypeId (void) {
static TypeId tid = TypeId ("ns3::A")
.SetParent<Object> ()
.AddAttribute ("TestInt16", "help text",
IntegerValue (-2),
MakeIntegerAccessor (&A::m_int16),
MakeIntegerChecker<int16_t> ())
;
return tid;
}
int16_t m_int16;
};

NS_OBJECT_ENSURE_REGISTERED (ConfigExample);

Next, we use the Config subsystem to override the defaults in a couple of ways:

Config::SetDefault ("ns3::ConfigExample::TestInt16", IntegerValue (-5));

Ptr<ConfigExample> a_obj = CreateObject<ConfigExample> ();
NS_ABORT_MSG_UNLESS (a_obj->m_int16 == -5,
"Cannot set ConfigExample's integer attribute via Config::SetDefault");

Ptr<ConfigExample> a2_obj = CreateObject<ConfigExample> ();
a2_obj->SetAttribute ("TestInt16", IntegerValue (-3));
IntegerValue iv;
a2_obj->GetAttribute ("TestInt16", iv);
NS_ABORT_MSG_UNLESS (iv.Get () == -3,
"Cannot set ConfigExample's integer attribute via SetAttribute");

The next statement is necessary to make sure that (one of) the objects created is rooted
in the configuration namespace as an object instance. This normally happens when you
aggregate objects to a ns3::Node or ns3::Channel instance, but here, since we are working
at the core level, we need to create a new root namespace object:

Config::RegisterRootNamespaceObject (a2_obj);

Writing
Next, we want to output the configuration store. The examples show how to do it in two
formats, XML and raw text. In practice, one should perform this step just before calling
Simulator::Run () to save the final configuration just before running the simulation.

There are three Attributes that govern the behavior of the ConfigStore: "Mode",
"Filename", and "FileFormat". The Mode (default "None") configures whether ns-3 should
load configuration from a previously saved file (specify "Mode=Load") or save it to a file
(specify "Mode=Save"). The Filename (default "") is where the ConfigStore should read or
write its data. The FileFormat (default "RawText") governs whether the ConfigStore format
is plain text or Xml ("FileFormat=Xml")

The example shows:

Config::SetDefault ("ns3::ConfigStore::Filename", StringValue ("output-attributes.xml"));
Config::SetDefault ("ns3::ConfigStore::FileFormat", StringValue ("Xml"));
Config::SetDefault ("ns3::ConfigStore::Mode", StringValue ("Save"));
ConfigStore outputConfig;
outputConfig.ConfigureDefaults ();
outputConfig.ConfigureAttributes ();

// Output config store to txt format
Config::SetDefault ("ns3::ConfigStore::Filename", StringValue ("output-attributes.txt"));
Config::SetDefault ("ns3::ConfigStore::FileFormat", StringValue ("RawText"));
Config::SetDefault ("ns3::ConfigStore::Mode", StringValue ("Save"));
ConfigStore outputConfig2;
outputConfig2.ConfigureDefaults ();
outputConfig2.ConfigureAttributes ();

Simulator::Run ();

Simulator::Destroy ();

Note the placement of these statements just prior to the Simulator::Run () statement.
This output logs all of the values in place just prior to starting the simulation (i.e.
after all of the configuration has taken place).

After running, you can open the output-attributes.txt file and see:

default ns3::RealtimeSimulatorImpl::SynchronizationMode "BestEffort"
default ns3::RealtimeSimulatorImpl::HardLimit "+100000000.0ns"
default ns3::PcapFileWrapper::CaptureSize "65535"
default ns3::PacketSocket::RcvBufSize "131072"
default ns3::ErrorModel::IsEnabled "true"
default ns3::RateErrorModel::ErrorUnit "EU_BYTE"
default ns3::RateErrorModel::ErrorRate "0"
default ns3::RateErrorModel::RanVar "Uniform:0:1"
default ns3::DropTailQueue::Mode "Packets"
default ns3::DropTailQueue::MaxPackets "100"
default ns3::DropTailQueue::MaxBytes "6553500"
default ns3::Application::StartTime "+0.0ns"
default ns3::Application::StopTime "+0.0ns"
default ns3::ConfigStore::Mode "Save"
default ns3::ConfigStore::Filename "output-attributes.txt"
default ns3::ConfigStore::FileFormat "RawText"
default ns3::ConfigExample::TestInt16 "-5"
global RngSeed "1"
global RngRun "1"
global SimulatorImplementationType "ns3::DefaultSimulatorImpl"
global SchedulerType "ns3::MapScheduler"
global ChecksumEnabled "false"
value /$ns3::ConfigExample/TestInt16 "-3"

In the above, all of the default values for attributes for the core module are shown.
Then, all the values for the ns-3 global values are recorded. Finally, the value of the
instance of ConfigExample that was rooted in the configuration namespace is shown. In a
real ns-3 program, many more models, attributes, and defaults would be shown.

An XML version also exists in output-attributes.xml:

<?xml version="1.0" encoding="UTF-8"?>
<ns3>
<default name="ns3::RealtimeSimulatorImpl::SynchronizationMode" value="BestEffort"/>
<default name="ns3::RealtimeSimulatorImpl::HardLimit" value="+100000000.0ns"/>
<default name="ns3::PcapFileWrapper::CaptureSize" value="65535"/>
<default name="ns3::PacketSocket::RcvBufSize" value="131072"/>
<default name="ns3::ErrorModel::IsEnabled" value="true"/>
<default name="ns3::RateErrorModel::ErrorUnit" value="EU_BYTE"/>
<default name="ns3::RateErrorModel::ErrorRate" value="0"/>
<default name="ns3::RateErrorModel::RanVar" value="Uniform:0:1"/>
<default name="ns3::DropTailQueue::Mode" value="Packets"/>
<default name="ns3::DropTailQueue::MaxPackets" value="100"/>
<default name="ns3::DropTailQueue::MaxBytes" value="6553500"/>
<default name="ns3::Application::StartTime" value="+0.0ns"/>
<default name="ns3::Application::StopTime" value="+0.0ns"/>
<default name="ns3::ConfigStore::Mode" value="Save"/>
<default name="ns3::ConfigStore::Filename" value="output-attributes.xml"/>
<default name="ns3::ConfigStore::FileFormat" value="Xml"/>
<default name="ns3::ConfigExample::TestInt16" value="-5"/>
<global name="RngSeed" value="1"/>
<global name="RngRun" value="1"/>
<global name="SimulatorImplementationType" value="ns3::DefaultSimulatorImpl"/>
<global name="SchedulerType" value="ns3::MapScheduler"/>
<global name="ChecksumEnabled" value="false"/>
<value path="/$ns3::ConfigExample/TestInt16" value="-3"/>
</ns3>

This file can be archived with your simulation script and output data.

Reading
Next, we discuss configuring simulations via a stored input configuration file. There are
a couple of key differences compared to writing the final simulation configuration.
First, we need to place statements such as these at the beginning of the program, before
simulation configuration statements are written (so the values are registered before being
used in object construction).

Config::SetDefault ("ns3::ConfigStore::Filename", StringValue ("input-defaults.xml"));
Config::SetDefault ("ns3::ConfigStore::Mode", StringValue ("Load"));
Config::SetDefault ("ns3::ConfigStore::FileFormat", StringValue ("Xml"));
ConfigStore inputConfig;
inputConfig.ConfigureDefaults ();

Next, note that loading of input configuration data is limited to Attribute default (i.e.
not instance) values, and global values. Attribute instance values are not supported
because at this stage of the simulation, before any objects are constructed, there are no
such object instances around. (Note, future enhancements to the config store may change
this behavior).

Second, while the output of ConfigStore state will list everything in the database, the
input file need only contain the specific values to be overridden. So, one way to use
this class for input file configuration is to generate an initial configuration using the
output ("Save") "Mode" described above, extract from that configuration file only the
elements one wishes to change, and move these minimal elements to a new configuration file
which can then safely be edited and loaded in a subsequent simulation run.

When the ConfigStore object is instantiated, its attributes "Filename", "Mode", and
"FileFormat" must be set, either via command-line or via program statements.

Reading/Writing Example
As a more complicated example, let's assume that we want to read in a configuration of
defaults from an input file named input-defaults.xml, and write out the resulting
attributes to a separate file called output-attributes.xml.:

#include "ns3/config-store-module.h"
...
int main (...)
{

Config::SetDefault ("ns3::ConfigStore::Filename", StringValue ("input-defaults.xml"));
Config::SetDefault ("ns3::ConfigStore::Mode", StringValue ("Load"));
Config::SetDefault ("ns3::ConfigStore::FileFormat", StringValue ("Xml"));
ConfigStore inputConfig;
inputConfig.ConfigureDefaults ();

//
// Allow the user to override any of the defaults and the above Bind () at
// run-time, viacommand-line arguments
//
CommandLine cmd;
cmd.Parse (argc, argv);

// setup topology
...

// Invoke just before entering Simulator::Run ()
Config::SetDefault ("ns3::ConfigStore::Filename", StringValue ("output-attributes.xml"));
Config::SetDefault ("ns3::ConfigStore::Mode", StringValue ("Save"));
ConfigStore outputConfig;
outputConfig.ConfigureAttributes ();
Simulator::Run ();
}

ConfigStore GUI
There is a GTK-based front end for the ConfigStore. This allows users to use a GUI to
access and change variables. Screenshots of this feature are available in the |ns3|
Overview presentation.

To use this feature, one must install libgtk and libgtk-dev; an example Ubuntu
installation command is:

$ sudo apt-get install libgtk2.0-0 libgtk2.0-dev

To check whether it is configured or not, check the output of the step:

$ ./waf configure --enable-examples --enable-tests

---- Summary of optional NS-3 features:
Python Bindings : enabled
Python API Scanning Support : enabled
NS-3 Click Integration : enabled
GtkConfigStore : not enabled (library 'gtk+-2.0 >= 2.12' not found)

In the above example, it was not enabled, so it cannot be used until a suitable version is
installed and:

$ ./waf configure --enable-examples --enable-tests
$ ./waf

is rerun.

Usage is almost the same as the non-GTK-based version, but there are no ConfigStore
attributes involved:

// Invoke just before entering Simulator::Run ()
GtkConfigStore config;
config.ConfigureDefaults ();
config.ConfigureAttributes ();

Now, when you run the script, a GUI should pop up, allowing you to open menus of
attributes on different nodes/objects, and then launch the simulation execution when you
are done.

Future work
There are a couple of possible improvements:

· Save a unique version number with date and time at start of file.

· Save rng initial seed somewhere.

· Make each RandomVariable serialize its own initial seed and re-read it later.

Object names
Placeholder chapter

Logging
The ns-3 logging facility can be used to monitor or debug the progress of simulation
programs. Logging output can be enabled by program statements in your main() program or
by the use of the NS_LOG environment variable.

Logging statements are not compiled into optimized builds of ns-3. To use logging, one
must build the (default) debug build of ns-3.

The project makes no guarantee about whether logging output will remain the same over
time. Users are cautioned against building simulation output frameworks on top of logging
code, as the output and the way the output is enabled may change over time.

Overview
ns-3 logging statements are typically used to log various program execution events, such
as the occurrence of simulation events or the use of a particular function.

For example, this code snippet is from Ipv4L3Protocol::IsDestinationAddress():

if (address == iaddr.GetBroadcast ())
{
NS_LOG_LOGIC ("For me (interface broadcast address)");
return true;
}

If logging has been enabled for the Ipv4L3Protocol component at a severity of LOGIC or
above (see below about log severity), the statement will be printed out; otherwise, it
will be suppressed.

Enabling Output
There are two ways that users typically control log output. The first is by setting the
NS_LOG environment variable; e.g.:

$ NS_LOG="*" ./waf --run first

will run the first tutorial program with all logging output. (The specifics of the NS_LOG
format will be discussed below.)

This can be made more granular by selecting individual components:

$ NS_LOG="Ipv4L3Protocol" ./waf --run first

The output can be further tailored with prefix options.

The second way to enable logging is to use explicit statements in your program, such as in
the first tutorial program:

int
main (int argc, char *argv[])
{
LogComponentEnable ("UdpEchoClientApplication", LOG_LEVEL_INFO);
LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO);
...

(The meaning of LOG_LEVEL_INFO, and other possible values, will be discussed below.)

NS_LOG Syntax
The NS_LOG environment variable contains a list of log components and options. Log
components are separated by `:' characters:

$ NS_LOG="<log-component>:<log-component>..."

Options for each log component are given as flags after each log component:

$ NS_LOG="<log-component>=<option>|<option>...:<log-component>..."

Options control the severity and level for that component, and whether optional
information should be included, such as the simulation time, simulation node, function
name, and the symbolic severity.

Log Components
Generally a log component refers to a single source code .cc file, and encompasses the
entire file.

Some helpers have special methods to enable the logging of all components in a module,
spanning different compilation units, but logically grouped together, such as the ns-3
wifi code:

WifiHelper wifiHelper;
wifiHelper.EnableLogComponents ();

The NS_LOG log component wildcard `*' will enable all components.

To see what log components are defined, any of these will work:

$ NS_LOG="print-list" ./waf --run ...

$ NS_LOG="foo" # a token not matching any log-component

The first form will print the name and enabled flags for all log components which are
linked in; try it with scratch-simulator. The second form prints all registered log
components, then exit with an error.

Severity and Level Options
Individual messages belong to a single "severity class," set by the macro creating the
message. In the example above, NS_LOG_LOGIC(..) creates the message in the LOG_LOGIC
severity class.

The following severity classes are defined as enum constants:

┌───────────────┬──────────────────────────────────┐
│Severity Class │ Meaning │
├───────────────┼──────────────────────────────────┤
LOG_NONE │ The default, no logging │
├───────────────┼──────────────────────────────────┤
LOG_ERROR │ Serious error messages only │
├───────────────┼──────────────────────────────────┤
LOG_WARN │ Warning messages │
├───────────────┼──────────────────────────────────┤
LOG_DEBUG │ For use in debugging │
├───────────────┼──────────────────────────────────┤
LOG_INFO │ Informational │
├───────────────┼──────────────────────────────────┤
LOG_FUNCTION │ Function tracing │
├───────────────┼──────────────────────────────────┤
LOG_LOGIC │ Control flow tracing within │
│ │ functions │
└───────────────┴──────────────────────────────────┘

Typically one wants to see messages at a given severity class and higher. This is done by
defining inclusive logging "levels":

┌───────────────────┬──────────────────────────────────┐
│Level │ Meaning │
├───────────────────┼──────────────────────────────────┤
LOG_LEVEL_ERROR │ Only LOG_ERROR severity class │
│ │ messages. │
├───────────────────┼──────────────────────────────────┤
LOG_LEVEL_WARNLOG_WARN and above. │
├───────────────────┼──────────────────────────────────┤
LOG_LEVEL_DEBUGLOG_DEBUG and above. │
├───────────────────┼──────────────────────────────────┤
LOG_LEVEL_INFOLOG_INFO and above. │
├───────────────────┼──────────────────────────────────┤
LOG_LEVEL_FUNCTIONLOG_FUNCTION and above. │
├───────────────────┼──────────────────────────────────┤
LOG_LEVEL_LOGICLOG_LOGIC and above. │
├───────────────────┼──────────────────────────────────┤
LOG_LEVEL_ALL │ All severity classes. │
├───────────────────┼──────────────────────────────────┤
LOG_ALL │ Synonym for LOG_LEVEL_ALL
└───────────────────┴──────────────────────────────────┘

The severity class and level options can be given in the NS_LOG environment variable by
these tokens:

┌─────────┬────────────────┐
│Class │ Level │
├─────────┼────────────────┤
errorlevel_error
├─────────┼────────────────┤
warnlevel_warn
├─────────┼────────────────┤
debuglevel_debug
├─────────┼────────────────┤
infolevel_info
├─────────┼────────────────┤
functionlevel_function
├─────────┼────────────────┤
logiclevel_logic
├─────────┼────────────────┤
│ │ level_all
│ │ all
│ │ *
└─────────┴────────────────┘

Using a severity class token enables log messages at that severity only. For example,
NS_LOG="*=warn" won't output messages with severity error. NS_LOG="*=level_debug" will
output messages at severity levels debug and above.

Severity classes and levels can be combined with the `|' operator:
NS_LOG="*=level_warn|logic" will output messages at severity levels error, warn and logic.

The NS_LOG severity level wildcard `*' and all are synonyms for level_all.

For log components merely mentioned in NS_LOG

$ NS_LOG="<log-component>:..."

the default severity is LOG_LEVEL_ALL.

Prefix Options
A number of prefixes can help identify where and when a message originated, and at what
severity.

The available prefix options (as enum constants) are

┌─────────────────┬──────────────────────────────────┐
│Prefix Symbol │ Meaning │
├─────────────────┼──────────────────────────────────┤
LOG_PREFIX_FUNC │ Prefix the name of the calling │
│ │ function. │
├─────────────────┼──────────────────────────────────┤
LOG_PREFIX_TIME │ Prefix the simulation time. │
├─────────────────┼──────────────────────────────────┤
LOG_PREFIX_NODE │ Prefix the node id. │
├─────────────────┼──────────────────────────────────┤
LOG_PREFIX_LEVEL │ Prefix the severity level. │
├─────────────────┼──────────────────────────────────┤
LOG_PREFIX_ALL │ Enable all prefixes. │
└─────────────────┴──────────────────────────────────┘

The prefix options are described briefly below.

The options can be given in the NS_LOG environment variable by these tokens:

┌─────────────┬───────────┐
│Token │ Alternate │
├─────────────┼───────────┤
prefix_funcfunc
├─────────────┼───────────┤
prefix_timetime
└─────────────┴───────────┘

prefix_nodenode
├─────────────┼───────────┤
prefix_levellevel
├─────────────┼───────────┤
prefix_allall
│ │ *
└─────────────┴───────────┘

For log components merely mentioned in NS_LOG

$ NS_LOG="<log-component>:..."

the default prefix options are LOG_PREFIX_ALL.

Severity Prefix
The severity class of a message can be included with the options prefix_level or level.
For example, this value of NS_LOG enables logging for all log components (`*') and all
severity classes (=all), and prefixes the message with the severity class (|prefix_level).

$ NS_LOG="*=all|prefix_level" ./waf --run scratch-simulator
Scratch Simulator
[ERROR] error message
[WARN] warn message
[DEBUG] debug message
[INFO] info message
[FUNCT] function message
[LOGIC] logic message

Time Prefix
The simulation time can be included with the options prefix_time or time. This prints the
simulation time in seconds.

Node Prefix
The simulation node id can be included with the options prefix_node or node.

Function Prefix
The name of the calling function can be included with the options prefix_func or func.

NS_LOG Wildcards
The log component wildcard `*' will enable all components. To enable all components at a
specific severity level use *=<severity>.

The severity level option wildcard `*' is a synonym for all. This must occur before any
`|' characters separating options. To enable all severity classes, use <log-component>=*,
or <log-component>=*|<options>.

The option wildcard `*' or token all enables all prefix options, but must occur after a
`|' character. To enable a specific severity class or level, and all prefixes, use
<log-component>=<severity>|*.

The combined option wildcard ** enables all severities and all prefixes; for example,
<log-component>=**.

The uber-wildcard *** enables all severities and all prefixes for all log components.
These are all equivalent:

$ NS_LOG="***" ... $ NS_LOG="*=all|*" ... $ NS_LOG="*=*|all" ...
$ NS_LOG="*=**" ... $ NS_LOG="*=level_all|*" ... $ NS_LOG="*=*|prefix_all" ...
$ NS_LOG="*=*|*" ...

Be advised: even the trivial scratch-simulator produces over 46K lines of output with
NS_LOG="***"!

How to add logging to your code
Adding logging to your code is very simple:

1. Invoke the NS_LOG_COMPONENT_DEFINE (...); macro inside of namespace ns3.
Create a unique string identifier (usually based on the name of the file and/or class
defined within the file) and register it with a macro call such as follows:

namespace ns3 {

NS_LOG_COMPONENT_DEFINE ("Ipv4L3Protocol");
...

This registers Ipv4L3Protocol as a log component.

(The macro was carefully written to permit inclusion either within or outside of
namespace ns3, and usage will vary across the codebase, but the original intent was to
register this outside of namespace ns3 at file global scope.)

2. Add logging statements (macro calls) to your functions and function bodies.

Logging Macros
The logging macros and associated severity levels are

┌───────────────┬────────────────────────┐
│Severity Class │ Macro │
├───────────────┼────────────────────────┤
LOG_NONE │ (none needed) │
├───────────────┼────────────────────────┤
LOG_ERRORNS_LOG_ERROR (...);
├───────────────┼────────────────────────┤
LOG_WARNNS_LOG_WARN (...);
├───────────────┼────────────────────────┤
LOG_DEBUGNS_LOG_DEBUG (...);
├───────────────┼────────────────────────┤
LOG_INFONS_LOG_INFO (...);
├───────────────┼────────────────────────┤
LOG_FUNCTIONNS_LOG_FUNCTION (...);
├───────────────┼────────────────────────┤
LOG_LOGICNS_LOG_LOGIC (...);
└───────────────┴────────────────────────┘

The macros function as output streamers, so anything you can send to std::cout, joined
by << operators, is allowed:

void MyClass::Check (int value, char * item)
{
NS_LOG_FUNCTION (this << arg << item);
if (arg > 10)
{
NS_LOG_ERROR ("encountered bad value " << value <<
" while checking " << name << "!");
}
...
}

Note that NS_LOG_FUNCTION automatically inserts a `,' (comma-space) separator between
each of its arguments. This simplifies logging of function arguments; just concatenate
them with << as in the example above.

Unconditional Logging
As a convenience, the NS_LOG_UNCOND (...); macro will always log its arguments, even if
the associated log-component is not enabled at any severity. This macro does not use any
of the prefix options. Note that logging is only enabled in debug builds; this macro
won't produce output in optimized builds.

Guidelines
· Start every class method with NS_LOG_FUNCTION (this << args...); This enables easy
function call tracing.

· Except: don't log operators or explicit copy constructors, since these will cause
infinite recursion and stack overflow.

· For methods without arguments use the same form: NS_LOG_FUNCTION (this);

· For static functions:

· With arguments use NS_LOG_FUNCTION (...); as normal.

· Without arguments use NS_LOG_FUNCTION_NOARGS ();

· Use NS_LOG_ERROR for serious error conditions that probably invalidate the simulation
execution.

· Use NS_LOG_WARN for unusual conditions that may be correctable. Please give some hints
as to the nature of the problem and how it might be corrected.

· NS_LOG_DEBUG is usually used in an ad hoc way to understand the execution of a model.

· Use NS_LOG_INFO for additional information about the execution, such as the size of a
data structure when adding/removing from it.

· Use NS_LOG_LOGIC to trace important logic branches within a function.

· Test that your logging changes do not break the code. Run some example programs with
all log components turned on (e.g. NS_LOG="***").

Tracing
The tracing subsystem is one of the most important mechanisms to understand in ns-3. In
most cases, ns-3 users will have a brilliant idea for some new and improved networking
feature. In order to verify that this idea works, the researcher will make changes to an
existing system and then run experiments to see how the new feature behaves by gathering
statistics that capture the behavior of the feature.

In other words, the whole point of running a simulation is to generate output for further
study. In ns-3, the subsystem that enables a researcher to do this is the tracing
subsystem.

Tracing Motivation
There are many ways to get information out of a program. The most straightforward way is
to just directly print the information to the standard output, as in,

#include <iostream>
...
int main ()
{
...
std::cout << "The value of x is " << x << std::endl;
...
}

This is workable in small environments, but as your simulations get more and more
complicated, you end up with more and more prints and the task of parsing and performing
computations on the output begins to get harder and harder.

Another thing to consider is that every time a new tidbit is needed, the software core
must be edited and another print introduced. There is no standardized way to control all
of this output, so the amount of output tends to grow without bounds. Eventually, the
bandwidth required for simply outputting this information begins to limit the running time
of the simulation. The output files grow to enormous sizes and parsing them becomes a
problem.

ns-3 provides a simple mechanism for logging and providing some control over output via
Log Components, but the level of control is not very fine grained at all. The logging
module is a relatively blunt instrument.

It is desirable to have a facility that allows one to reach into the core system and only
get the information required without having to change and recompile the core system. Even
better would be a system that notified the user when an item of interest changed or an
interesting event happened.

The ns-3 tracing system is designed to work along those lines and is well-integrated with
the Attribute and Config substems allowing for relatively simple use scenarios.

Overview
The tracing subsystem relies heavily on the ns-3 Callback and Attribute mechanisms. You
should read and understand the corresponding sections of the manual before attempting to
understand the tracing system.

The ns-3 tracing system is built on the concepts of independent tracing sources and
tracing sinks; along with a uniform mechanism for connecting sources to sinks.

Trace sources are entities that can signal events that happen in a simulation and provide
access to interesting underlying data. For example, a trace source could indicate when a
packet is received by a net device and provide access to the packet contents for
interested trace sinks. A trace source might also indicate when an interesting state
change happens in a model. For example, the congestion window of a TCP model is a prime
candidate for a trace source.

Trace sources are not useful by themselves; they must be connected to other pieces of code
that actually do something useful with the information provided by the source. The
entities that consume trace information are called trace sinks. Trace sources are
generators of events and trace sinks are consumers.

This explicit division allows for large numbers of trace sources to be scattered around
the system in places which model authors believe might be useful. Unless a user connects a
trace sink to one of these sources, nothing is output. This arrangement allows relatively
unsophisticated users to attach new types of sinks to existing tracing sources, without
requiring editing and recompiling the core or models of the simulator.

There can be zero or more consumers of trace events generated by a trace source. One can
think of a trace source as a kind of point-to-multipoint information link.

The "transport protocol" for this conceptual point-to-multipoint link is an ns-3 Callback.

Recall from the Callback Section that callback facility is a way to allow two modules in
the system to communicate via function calls while at the same time decoupling the calling
function from the called class completely. This is the same requirement as outlined above
for the tracing system.

Basically, a trace source is a callback to which multiple functions may be registered.
When a trace sink expresses interest in receiving trace events, it adds a callback to a
list of callbacks held by the trace source. When an interesting event happens, the trace
source invokes its operator() providing zero or more parameters. This tells the source to
go through its list of callbacks invoking each one in turn. In this way, the parameter(s)
are communicated to the trace sinks, which are just functions.

The Simplest Example
It will be useful to go walk a quick example just to reinforce what we've said.:

#include "ns3/object.h"
#include "ns3/uinteger.h"
#include "ns3/traced-value.h""
#include "ns3/trace-source-accessor.h"

#include <iostream>

using namespace ns3;

The first thing to do is include the required files. As mentioned above, the trace system
makes heavy use of the Object and Attribute systems. The first two includes bring in the
declarations for those systems. The file, traced-value.h brings in the required
declarations for tracing data that obeys value semantics.

In general, value semantics just means that you can pass the object around, not an
address. In order to use value semantics at all you have to have an object with an
associated copy constructor and assignment operator available. We extend the requirements
to talk about the set of operators that are pre-defined for plain-old-data (POD) types.
Operator=, operator++, operator--, operator+, operator==, etc.

What this all means is that you will be able to trace changes to an object made using
those operators.:

class MyObject : public Object
{
public:
static TypeId GetTypeId (void)
{
static TypeId tid = TypeId ("MyObject")
.SetParent (Object::GetTypeId ())
.AddConstructor<MyObject> ()
.AddTraceSource ("MyInteger",
"An integer value to trace.",
MakeTraceSourceAccessor (&MyObject::m_myInt))
;
return tid;
}

MyObject () {}
TracedValue<uint32_t> m_myInt;
};

Since the tracing system is integrated with Attributes, and Attributes work with Objects,
there must be an ns-3 Object for the trace source to live in. The two important lines of
code are the .AddTraceSource and the TracedValue declaration.

The .AddTraceSource provides the "hooks" used for connecting the trace source to the
outside world. The TracedValue declaration provides the infrastructure that overloads the
operators mentioned above and drives the callback process.:

void
IntTrace (Int oldValue, Int newValue)
{
std::cout << "Traced " << oldValue << " to " << newValue << std::endl;
}

This is the definition of the trace sink. It corresponds directly to a callback function.
This function will be called whenever one of the operators of the TracedValue is
executed.:

int
main (int argc, char *argv[])
{
Ptr<MyObject> myObject = CreateObject<MyObject> ();

myObject->TraceConnectWithoutContext ("MyInteger", MakeCallback(&IntTrace));

myObject->m_myInt = 1234;
}

In this snippet, the first thing that needs to be done is to create the object in which
the trace source lives.

The next step, the TraceConnectWithoutContext, forms the connection between the trace
source and the trace sink. Notice the MakeCallback template function. Recall from the
Callback section that this creates the specialized functor responsible for providing the
overloaded operator() used to "fire" the callback. The overloaded operators (++, --, etc.)
will use this operator() to actually invoke the callback. The TraceConnectWithoutContext,
takes a string parameter that provides the name of the Attribute assigned to the trace
source. Let's ignore the bit about context for now since it is not important yet.

Finally, the line,:

myObject->m_myInt = 1234;

should be interpreted as an invocation of operator= on the member variable m_myInt with
the integer 1234 passed as a parameter. It turns out that this operator is defined (by
TracedValue) to execute a callback that returns void and takes two integer values as
parameters -- an old value and a new value for the integer in question. That is exactly
the function signature for the callback function we provided -- IntTrace.

To summarize, a trace source is, in essence, a variable that holds a list of callbacks. A
trace sink is a function used as the target of a callback. The Attribute and object type
information systems are used to provide a way to connect trace sources to trace sinks. The
act of "hitting" a trace source is executing an operator on the trace source which fires
callbacks. This results in the trace sink callbacks registering interest in the source
being called with the parameters provided by the source.

Using the Config Subsystem to Connect to Trace Sources
The TraceConnectWithoutContext call shown above in the simple example is actually very
rarely used in the system. More typically, the Config subsystem is used to allow selecting
a trace source in the system using what is called a config path.

For example, one might find something that looks like the following in the system (taken
from examples/tcp-large-transfer.cc):

void CwndTracer (uint32_t oldval, uint32_t newval) {}

...

Config::ConnectWithoutContext (
"/NodeList/0/$ns3::TcpL4Protocol/SocketList/0/CongestionWindow",
MakeCallback (&CwndTracer));

This should look very familiar. It is the same thing as the previous example, except that
a static member function of class Config is being called instead of a method on Object;
and instead of an Attribute name, a path is being provided.

The first thing to do is to read the path backward. The last segment of the path must be
an Attribute of an Object. In fact, if you had a pointer to the Object that has the
"CongestionWindow" Attribute handy (call it theObject), you could write this just like the
previous example:

void CwndTracer (uint32_t oldval, uint32_t newval) {}

...

theObject->TraceConnectWithoutContext ("CongestionWindow", MakeCallback (&CwndTracer));

It turns out that the code for Config::ConnectWithoutContext does exactly that. This
function takes a path that represents a chain of Object pointers and follows them until it
gets to the end of the path and interprets the last segment as an Attribute on the last
object. Let's walk through what happens.

The leading "/" character in the path refers to a so-called namespace. One of the
predefined namespaces in the config system is "NodeList" which is a list of all of the
nodes in the simulation. Items in the list are referred to by indices into the list, so
"/NodeList/0" refers to the zeroth node in the list of nodes created by the simulation.
This node is actually a Ptr<Node> and so is a subclass of an ns3::Object.

As described in the Object-model section, ns-3 supports an object aggregation model. The
next path segment begins with the "$" character which indicates a GetObject call should be
made looking for the type that follows. When a node is initialized by an
InternetStackHelper a number of interfaces are aggregated to the node. One of these is the
TCP level four protocol. The runtime type of this protocol object is ns3::TcpL4Protocol''.
When the ``GetObject is executed, it returns a pointer to the object of this type.

The TcpL4Protocol class defines an Attribute called "SocketList" which is a list of
sockets. Each socket is actually an ns3::Object with its own Attributes. The items in
the list of sockets are referred to by index just as in the NodeList, so "SocketList/0"
refers to the zeroth socket in the list of sockets on the zeroth node in the NodeList --
the first node constructed in the simulation.

This socket, the type of which turns out to be an ns3::TcpSocketImpl defines an attribute
called "CongestionWindow" which is a TracedValue<uint32_t>. The
Config::ConnectWithoutContext now does a,:

object->TraceConnectWithoutContext ("CongestionWindow", MakeCallback (&CwndTracer));

using the object pointer from "SocketList/0" which makes the connection between the trace
source defined in the socket to the callback -- CwndTracer.

Now, whenever a change is made to the TracedValue<uint32_t> representing the congestion
window in the TCP socket, the registered callback will be executed and the function
CwndTracer will be called printing out the old and new values of the TCP congestion
window.

Using the Tracing API
There are three levels of interaction with the tracing system:

· Beginning user can easily control which objects are participating in tracing;

· Intermediate users can extend the tracing system to modify the output format generated
or use existing trace sources in different ways, without modifying the core of the
simulator;

· Advanced users can modify the simulator core to add new tracing sources and sinks.

Using Trace Helpers
The ns-3 trace helpers provide a rich environment for configuring and selecting different
trace events and writing them to files. In previous sections, primarily "Building
Topologies," we have seen several varieties of the trace helper methods designed for use
inside other (device) helpers.

Perhaps you will recall seeing some of these variations:

pointToPoint.EnablePcapAll ("second");
pointToPoint.EnablePcap ("second", p2pNodes.Get (0)->GetId (), 0);
csma.EnablePcap ("third", csmaDevices.Get (0), true);
pointToPoint.EnableAsciiAll (ascii.CreateFileStream ("myfirst.tr"));

What may not be obvious, though, is that there is a consistent model for all of the
trace-related methods found in the system. We will now take a little time and take a look
at the "big picture".

There are currently two primary use cases of the tracing helpers in ns-3: Device helpers
and protocol helpers. Device helpers look at the problem of specifying which traces should
be enabled through a node, device pair. For example, you may want to specify that pcap
tracing should be enabled on a particular device on a specific node. This follows from the
ns-3 device conceptual model, and also the conceptual models of the various device
helpers. Following naturally from this, the files created follow a
<prefix>-<node>-<device> naming convention.

Protocol helpers look at the problem of specifying which traces should be enabled through
a protocol and interface pair. This follows from the ns-3 protocol stack conceptual model,
and also the conceptual models of internet stack helpers. Naturally, the trace files
should follow a <prefix>-<protocol>-<interface> naming convention.

The trace helpers therefore fall naturally into a two-dimensional taxonomy. There are
subtleties that prevent all four classes from behaving identically, but we do strive to
make them all work as similarly as possible; and whenever possible there are analogs for
all methods in all classes.

┌────────────────┬──────┬───────┐
│ │ pcap │ ascii │
├────────────────┼──────┼───────┤
│Device Helper │ │ │
├────────────────┼──────┼───────┤
│Protocol Helper │ │ │
└────────────────┴──────┴───────┘

We use an approach called a mixin to add tracing functionality to our helper classes. A
mixin is a class that provides functionality to that is inherited by a subclass.
Inheriting from a mixin is not considered a form of specialization but is really a way to
collect functionality.

Let's take a quick look at all four of these cases and their respective mixins.

Pcap Tracing Device Helpers
The goal of these helpers is to make it easy to add a consistent pcap trace facility to an
ns-3 device. We want all of the various flavors of pcap tracing to work the same across
all devices, so the methods of these helpers are inherited by device helpers. Take a look
at src/network/helper/trace-helper.h if you want to follow the discussion while looking at
real code.

The class PcapHelperForDevice is a mixin provides the high level functionality for using
pcap tracing in an ns-3 device. Every device must implement a single virtual method
inherited from this class.:

virtual void EnablePcapInternal (std::string prefix, Ptr<NetDevice> nd, bool promiscuous) = 0;

The signature of this method reflects the device-centric view of the situation at this
level. All of the public methods inherited from class PcapUserHelperForDevice reduce to
calling this single device-dependent implementation method. For example, the lowest level
pcap method,:

void EnablePcap (std::string prefix, Ptr<NetDevice> nd, bool promiscuous = false, bool explicitFilename = false);

will call the device implementation of EnablePcapInternal directly. All other public pcap
tracing methods build on this implementation to provide additional user-level
functionality. What this means to the user is that all device helpers in the system will
have all of the pcap trace methods available; and these methods will all work in the same
way across devices if the device implements EnablePcapInternal correctly.

Pcap Tracing Device Helper Methods
void EnablePcap (std::string prefix, Ptr<NetDevice> nd,
bool promiscuous = false, bool explicitFilename = false);
void EnablePcap (std::string prefix, std::string ndName,
bool promiscuous = false, bool explicitFilename = false);
void EnablePcap (std::string prefix, NetDeviceContainer d,
bool promiscuous = false);
void EnablePcap (std::string prefix, NodeContainer n,
bool promiscuous = false);
void EnablePcap (std::string prefix, uint32_t nodeid, uint32_t deviceid,
bool promiscuous = false);
void EnablePcapAll (std::string prefix, bool promiscuous = false);

In each of the methods shown above, there is a default parameter called promiscuous that
defaults to false. This parameter indicates that the trace should not be gathered in
promiscuous mode. If you do want your traces to include all traffic seen by the device
(and if the device supports a promiscuous mode) simply add a true parameter to any of the
calls above. For example,:

Ptr<NetDevice> nd;
...
helper.EnablePcap ("prefix", nd, true);

will enable promiscuous mode captures on the NetDevice specified by nd.

The first two methods also include a default parameter called explicitFilename that will
be discussed below.

You are encouraged to peruse the Doxygen for class PcapHelperForDevice to find the details
of these methods; but to summarize ...

You can enable pcap tracing on a particular node/net-device pair by providing a
Ptr<NetDevice> to an EnablePcap method. The Ptr<Node> is implicit since the net device
must belong to exactly one Node. For example,:

Ptr<NetDevice> nd;
...
helper.EnablePcap ("prefix", nd);

You can enable pcap tracing on a particular node/net-device pair by providing a
std::string representing an object name service string to an EnablePcap method. The
Ptr<NetDevice> is looked up from the name string. Again, the <Node> is implicit since the
named net device must belong to exactly one Node. For example,:

Names::Add ("server" ...);
Names::Add ("server/eth0" ...);
...
helper.EnablePcap ("prefix", "server/ath0");

You can enable pcap tracing on a collection of node/net-device pairs by providing a
NetDeviceContainer. For each NetDevice in the container the type is checked. For each
device of the proper type (the same type as is managed by the device helper), tracing is
enabled. Again, the <Node> is implicit since the found net device must belong to exactly
one Node. For example,:

NetDeviceContainer d = ...;
...
helper.EnablePcap ("prefix", d);

You can enable pcap tracing on a collection of node/net-device pairs by providing a
NodeContainer. For each Node in the NodeContainer its attached NetDevices are iterated.
For each NetDevice attached to each node in the container, the type of that device is
checked. For each device of the proper type (the same type as is managed by the device
helper), tracing is enabled.:

NodeContainer n;
...
helper.EnablePcap ("prefix", n);

You can enable pcap tracing on the basis of node ID and device ID as well as with explicit
Ptr. Each Node in the system has an integer node ID and each device connected to a node
has an integer device ID.:

helper.EnablePcap ("prefix", 21, 1);

Finally, you can enable pcap tracing for all devices in the system, with the same type as
that managed by the device helper.:

helper.EnablePcapAll ("prefix");

Pcap Tracing Device Helper Filename Selection
Implicit in the method descriptions above is the construction of a complete filename by
the implementation method. By convention, pcap traces in the ns-3 system are of the form
<prefix>-<node id>-<device id>.pcap

As previously mentioned, every node in the system will have a system-assigned node id; and
every device will have an interface index (also called a device id) relative to its node.
By default, then, a pcap trace file created as a result of enabling tracing on the first
device of node 21 using the prefix "prefix" would be prefix-21-1.pcap.

You can always use the ns-3 object name service to make this more clear. For example, if
you use the object name service to assign the name "server" to node 21, the resulting pcap
trace file name will automatically become, prefix-server-1.pcap and if you also assign the
name "eth0" to the device, your pcap file name will automatically pick this up and be
called prefix-server-eth0.pcap.

Finally, two of the methods shown above,:

void EnablePcap (std::string prefix, Ptr<NetDevice> nd, bool promiscuous = false, bool explicitFilename = false);
void EnablePcap (std::string prefix, std::string ndName, bool promiscuous = false, bool explicitFilename = false);

have a default parameter called explicitFilename. When set to true, this parameter
disables the automatic filename completion mechanism and allows you to create an explicit
filename. This option is only available in the methods which enable pcap tracing on a
single device.

For example, in order to arrange for a device helper to create a single promiscuous pcap
capture file of a specific name (my-pcap-file.pcap) on a given device, one could:

Ptr<NetDevice> nd;
...
helper.EnablePcap ("my-pcap-file.pcap", nd, true, true);

The first true parameter enables promiscuous mode traces and the second tells the helper
to interpret the prefix parameter as a complete filename.

Ascii Tracing Device Helpers
The behavior of the ascii trace helper mixin is substantially similar to the pcap version.
Take a look at src/network/helper/trace-helper.h if you want to follow the discussion
while looking at real code.

The class AsciiTraceHelperForDevice adds the high level functionality for using ascii
tracing to a device helper class. As in the pcap case, every device must implement a
single virtual method inherited from the ascii trace mixin.:

virtual void EnableAsciiInternal (Ptr<OutputStreamWrapper> stream, std::string prefix, Ptr<NetDevice> nd) = 0;

The signature of this method reflects the device-centric view of the situation at this
level; and also the fact that the helper may be writing to a shared output stream. All of
the public ascii-trace-related methods inherited from class AsciiTraceHelperForDevice
reduce to calling this single device- dependent implementation method. For example, the
lowest level ascii trace methods,:

void EnableAscii (std::string prefix, Ptr<NetDevice> nd);
void EnableAscii (Ptr<OutputStreamWrapper> stream, Ptr<NetDevice> nd);

will call the device implementation of EnableAsciiInternal directly, providing either a
valid prefix or stream. All other public ascii tracing methods will build on these
low-level functions to provide additional user-level functionality. What this means to the
user is that all device helpers in the system will have all of the ascii trace methods
available; and these methods will all work in the same way across devices if the devices
implement EnablAsciiInternal correctly.

Ascii Tracing Device Helper Methods
void EnableAscii (std::string prefix, Ptr<NetDevice> nd);
void EnableAscii (Ptr<OutputStreamWrapper> stream, Ptr<NetDevice> nd);

void EnableAscii (std::string prefix, std::string ndName);
void EnableAscii (Ptr<OutputStreamWrapper> stream, std::string ndName);

void EnableAscii (std::string prefix, NetDeviceContainer d);
void EnableAscii (Ptr<OutputStreamWrapper> stream, NetDeviceContainer d);

void EnableAscii (std::string prefix, NodeContainer n);
void EnableAscii (Ptr<OutputStreamWrapper> stream, NodeContainer n);

void EnableAscii (std::string prefix, uint32_t nodeid, uint32_t deviceid);
void EnableAscii (Ptr<OutputStreamWrapper> stream, uint32_t nodeid, uint32_t deviceid);

void EnableAsciiAll (std::string prefix);
void EnableAsciiAll (Ptr<OutputStreamWrapper> stream);

You are encouraged to peruse the Doxygen for class TraceHelperForDevice to find the
details of these methods; but to summarize ...

There are twice as many methods available for ascii tracing as there were for pcap
tracing. This is because, in addition to the pcap-style model where traces from each
unique node/device pair are written to a unique file, we support a model in which trace
information for many node/device pairs is written to a common file. This means that the
<prefix>-<node>-<device> file name generation mechanism is replaced by a mechanism to
refer to a common file; and the number of API methods is doubled to allow all
combinations.

Just as in pcap tracing, you can enable ascii tracing on a particular node/net-device pair
by providing a Ptr<NetDevice> to an EnableAscii method. The Ptr<Node> is implicit since
the net device must belong to exactly one Node. For example,:

Ptr<NetDevice> nd;
...
helper.EnableAscii ("prefix", nd);

In this case, no trace contexts are written to the ascii trace file since they would be
redundant. The system will pick the file name to be created using the same rules as
described in the pcap section, except that the file will have the suffix ".tr" instead of
".pcap".

If you want to enable ascii tracing on more than one net device and have all traces sent
to a single file, you can do that as well by using an object to refer to a single file:

Ptr<NetDevice> nd1;
Ptr<NetDevice> nd2;
...
Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
...
helper.EnableAscii (stream, nd1);
helper.EnableAscii (stream, nd2);

In this case, trace contexts are written to the ascii trace file since they are required
to disambiguate traces from the two devices. Note that since the user is completely
specifying the file name, the string should include the ".tr" for consistency.

You can enable ascii tracing on a particular node/net-device pair by providing a
std::string representing an object name service string to an EnablePcap method. The
Ptr<NetDevice> is looked up from the name string. Again, the <Node> is implicit since the
named net device must belong to exactly one Node. For example,:

Names::Add ("client" ...);
Names::Add ("client/eth0" ...);
Names::Add ("server" ...);
Names::Add ("server/eth0" ...);
...
helper.EnableAscii ("prefix", "client/eth0");
helper.EnableAscii ("prefix", "server/eth0");

This would result in two files named prefix-client-eth0.tr and prefix-server-eth0.tr with
traces for each device in the respective trace file. Since all of the EnableAscii
functions are overloaded to take a stream wrapper, you can use that form as well:

Names::Add ("client" ...);
Names::Add ("client/eth0" ...);
Names::Add ("server" ...);
Names::Add ("server/eth0" ...);
...
Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
...
helper.EnableAscii (stream, "client/eth0");
helper.EnableAscii (stream, "server/eth0");

This would result in a single trace file called trace-file-name.tr that contains all of
the trace events for both devices. The events would be disambiguated by trace context
strings.

You can enable ascii tracing on a collection of node/net-device pairs by providing a
NetDeviceContainer. For each NetDevice in the container the type is checked. For each
device of the proper type (the same type as is managed by the device helper), tracing is
enabled. Again, the <Node> is implicit since the found net device must belong to exactly
one Node. For example,:

NetDeviceContainer d = ...;
...
helper.EnableAscii ("prefix", d);

This would result in a number of ascii trace files being created, each of which follows
the <prefix>-<node id>-<device id>.tr convention. Combining all of the traces into a
single file is accomplished similarly to the examples above:

NetDeviceContainer d = ...;
...
Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
...
helper.EnableAscii (stream, d);

You can enable ascii tracing on a collection of node/net-device pairs by providing a
NodeContainer. For each Node in the NodeContainer its attached NetDevices are iterated.
For each NetDevice attached to each node in the container, the type of that device is
checked. For each device of the proper type (the same type as is managed by the device
helper), tracing is enabled.:

NodeContainer n;
...
helper.EnableAscii ("prefix", n);

This would result in a number of ascii trace files being created, each of which follows
the <prefix>-<node id>-<device id>.tr convention. Combining all of the traces into a
single file is accomplished similarly to the examples above:

You can enable pcap tracing on the basis of node ID and device ID as well as with explicit
Ptr. Each Node in the system has an integer node ID and each device connected to a node
has an integer device ID.:

helper.EnableAscii ("prefix", 21, 1);

Of course, the traces can be combined into a single file as shown above.

Finally, you can enable pcap tracing for all devices in the system, with the same type as
that managed by the device helper.:

helper.EnableAsciiAll ("prefix");

This would result in a number of ascii trace files being created, one for every device in
the system of the type managed by the helper. All of these files will follow the
<prefix>-<node id>-<device id>.tr convention. Combining all of the traces into a single
file is accomplished similarly to the examples above.

Ascii Tracing Device Helper Filename Selection
Implicit in the prefix-style method descriptions above is the construction of the complete
filenames by the implementation method. By convention, ascii traces in the ns-3 system are
of the form <prefix>-<node id>-<device id>.tr.

As previously mentioned, every node in the system will have a system-assigned node id; and
every device will have an interface index (also called a device id) relative to its node.
By default, then, an ascii trace file created as a result of enabling tracing on the first
device of node 21, using the prefix "prefix", would be prefix-21-1.tr.

You can always use the ns-3 object name service to make this more clear. For example, if
you use the object name service to assign the name "server" to node 21, the resulting
ascii trace file name will automatically become, prefix-server-1.tr and if you also assign
the name "eth0" to the device, your ascii trace file name will automatically pick this up
and be called prefix-server-eth0.tr.

Pcap Tracing Protocol Helpers
The goal of these mixins is to make it easy to add a consistent pcap trace facility to
protocols. We want all of the various flavors of pcap tracing to work the same across all
protocols, so the methods of these helpers are inherited by stack helpers. Take a look at
src/network/helper/trace-helper.h if you want to follow the discussion while looking at
real code.

In this section we will be illustrating the methods as applied to the protocol Ipv4. To
specify traces in similar protocols, just substitute the appropriate type. For example,
use a Ptr<Ipv6> instead of a Ptr<Ipv4> and call EnablePcapIpv6 instead of EnablePcapIpv4.

The class PcapHelperForIpv4 provides the high level functionality for using pcap tracing
in the Ipv4 protocol. Each protocol helper enabling these methods must implement a single
virtual method inherited from this class. There will be a separate implementation for
Ipv6, for example, but the only difference will be in the method names and signatures.
Different method names are required to disambiguate class Ipv4 from Ipv6 which are both
derived from class Object, and methods that share the same signature.:

virtual void EnablePcapIpv4Internal (std::string prefix, Ptr<Ipv4> ipv4, uint32_t interface) = 0;

The signature of this method reflects the protocol and interface-centric view of the
situation at this level. All of the public methods inherited from class PcapHelperForIpv4
reduce to calling this single device-dependent implementation method. For example, the
lowest level pcap method,:

void EnablePcapIpv4 (std::string prefix, Ptr<Ipv4> ipv4, uint32_t interface);

will call the device implementation of EnablePcapIpv4Internal directly. All other public
pcap tracing methods build on this implementation to provide additional user-level
functionality. What this means to the user is that all protocol helpers in the system will
have all of the pcap trace methods available; and these methods will all work in the same
way across protocols if the helper implements EnablePcapIpv4Internal correctly.

Pcap Tracing Protocol Helper Methods
These methods are designed to be in one-to-one correspondence with the Node- and
NetDevice- centric versions of the device versions. Instead of Node and NetDevice pair
constraints, we use protocol and interface constraints.

Note that just like in the device version, there are six methods:

void EnablePcapIpv4 (std::string prefix, Ptr<Ipv4> ipv4, uint32_t interface);
void EnablePcapIpv4 (std::string prefix, std::string ipv4Name, uint32_t interface);
void EnablePcapIpv4 (std::string prefix, Ipv4InterfaceContainer c);
void EnablePcapIpv4 (std::string prefix, NodeContainer n);
void EnablePcapIpv4 (std::string prefix, uint32_t nodeid, uint32_t interface);
void EnablePcapIpv4All (std::string prefix);

You are encouraged to peruse the Doxygen for class PcapHelperForIpv4 to find the details
of these methods; but to summarize ...

You can enable pcap tracing on a particular protocol/interface pair by providing a
Ptr<Ipv4> and interface to an EnablePcap method. For example,:

Ptr<Ipv4> ipv4 = node->GetObject<Ipv4> ();
...
helper.EnablePcapIpv4 ("prefix", ipv4, 0);

You can enable pcap tracing on a particular node/net-device pair by providing a
std::string representing an object name service string to an EnablePcap method. The
Ptr<Ipv4> is looked up from the name string. For example,:

Names::Add ("serverIPv4" ...);
...
helper.EnablePcapIpv4 ("prefix", "serverIpv4", 1);

You can enable pcap tracing on a collection of protocol/interface pairs by providing an
Ipv4InterfaceContainer. For each Ipv4 / interface pair in the container the protocol type
is checked. For each protocol of the proper type (the same type as is managed by the
device helper), tracing is enabled for the corresponding interface. For example,:

NodeContainer nodes;
...
NetDeviceContainer devices = deviceHelper.Install (nodes);
...
Ipv4AddressHelper ipv4;
ipv4.SetBase ("10.1.1.0", "255.255.255.0");
Ipv4InterfaceContainer interfaces = ipv4.Assign (devices);
...
helper.EnablePcapIpv4 ("prefix", interfaces);

You can enable pcap tracing on a collection of protocol/interface pairs by providing a
NodeContainer. For each Node in the NodeContainer the appropriate protocol is found. For
each protocol, its interfaces are enumerated and tracing is enabled on the resulting
pairs. For example,:

NodeContainer n;
...
helper.EnablePcapIpv4 ("prefix", n);

You can enable pcap tracing on the basis of node ID and interface as well. In this case,
the node-id is translated to a Ptr<Node> and the appropriate protocol is looked up in the
node. The resulting protocol and interface are used to specify the resulting trace
source.:

helper.EnablePcapIpv4 ("prefix", 21, 1);

Finally, you can enable pcap tracing for all interfaces in the system, with associated
protocol being the same type as that managed by the device helper.:

helper.EnablePcapIpv4All ("prefix");

Pcap Tracing Protocol Helper Filename Selection
Implicit in all of the method descriptions above is the construction of the complete
filenames by the implementation method. By convention, pcap traces taken for devices in
the ns-3 system are of the form <prefix>-<node id>-<device id>.pcap. In the case of
protocol traces, there is a one-to-one correspondence between protocols and Nodes. This is
because protocol Objects are aggregated to Node Objects. Since there is no global protocol
id in the system, we use the corresponding node id in file naming. Therefore there is a
possibility for file name collisions in automatically chosen trace file names. For this
reason, the file name convention is changed for protocol traces.

As previously mentioned, every node in the system will have a system-assigned node id.
Since there is a one-to-one correspondence between protocol instances and node instances
we use the node id. Each interface has an interface id relative to its protocol. We use
the convention "<prefix>-n<node id>-i<interface id>.pcap" for trace file naming in
protocol helpers.

Therefore, by default, a pcap trace file created as a result of enabling tracing on
interface 1 of the Ipv4 protocol of node 21 using the prefix "prefix" would be
"prefix-n21-i1.pcap".

You can always use the ns-3 object name service to make this more clear. For example, if
you use the object name service to assign the name "serverIpv4" to the Ptr<Ipv4> on node
21, the resulting pcap trace file name will automatically become,
"prefix-nserverIpv4-i1.pcap".

Ascii Tracing Protocol Helpers
The behavior of the ascii trace helpers is substantially similar to the pcap case. Take a
look at src/network/helper/trace-helper.h if you want to follow the discussion while
looking at real code.

In this section we will be illustrating the methods as applied to the protocol Ipv4. To
specify traces in similar protocols, just substitute the appropriate type. For example,
use a Ptr<Ipv6> instead of a Ptr<Ipv4> and call EnableAsciiIpv6 instead of
EnableAsciiIpv4.

The class AsciiTraceHelperForIpv4 adds the high level functionality for using ascii
tracing to a protocol helper. Each protocol that enables these methods must implement a
single virtual method inherited from this class.:

virtual void EnableAsciiIpv4Internal (Ptr<OutputStreamWrapper> stream, std::string prefix,
Ptr<Ipv4> ipv4, uint32_t interface) = 0;

The signature of this method reflects the protocol- and interface-centric view of the
situation at this level; and also the fact that the helper may be writing to a shared
output stream. All of the public methods inherited from class
PcapAndAsciiTraceHelperForIpv4 reduce to calling this single device- dependent
implementation method. For example, the lowest level ascii trace methods,:

void EnableAsciiIpv4 (std::string prefix, Ptr<Ipv4> ipv4, uint32_t interface);
void EnableAsciiIpv4 (Ptr<OutputStreamWrapper> stream, Ptr<Ipv4> ipv4, uint32_t interface);

will call the device implementation of EnableAsciiIpv4Internal directly, providing either
the prefix or the stream. All other public ascii tracing methods will build on these
low-level functions to provide additional user-level functionality. What this means to the
user is that all device helpers in the system will have all of the ascii trace methods
available; and these methods will all work in the same way across protocols if the
protocols implement EnablAsciiIpv4Internal correctly.

Ascii Tracing Device Helper Methods
void EnableAsciiIpv4 (std::string prefix, Ptr<Ipv4> ipv4, uint32_t interface);
void EnableAsciiIpv4 (Ptr<OutputStreamWrapper> stream, Ptr<Ipv4> ipv4, uint32_t interface);

void EnableAsciiIpv4 (std::string prefix, std::string ipv4Name, uint32_t interface);
void EnableAsciiIpv4 (Ptr<OutputStreamWrapper> stream, std::string ipv4Name, uint32_t interface);

void EnableAsciiIpv4 (std::string prefix, Ipv4InterfaceContainer c);
void EnableAsciiIpv4 (Ptr<OutputStreamWrapper> stream, Ipv4InterfaceContainer c);

void EnableAsciiIpv4 (std::string prefix, NodeContainer n);
void EnableAsciiIpv4 (Ptr<OutputStreamWrapper> stream, NodeContainer n);

void EnableAsciiIpv4 (std::string prefix, uint32_t nodeid, uint32_t deviceid);
void EnableAsciiIpv4 (Ptr<OutputStreamWrapper> stream, uint32_t nodeid, uint32_t interface);

void EnableAsciiIpv4All (std::string prefix);
void EnableAsciiIpv4All (Ptr<OutputStreamWrapper> stream);

You are encouraged to peruse the Doxygen for class PcapAndAsciiHelperForIpv4 to find the
details of these methods; but to summarize ...

There are twice as many methods available for ascii tracing as there were for pcap
tracing. This is because, in addition to the pcap-style model where traces from each
unique protocol/interface pair are written to a unique file, we support a model in which
trace information for many protocol/interface pairs is written to a common file. This
means that the <prefix>-n<node id>-<interface> file name generation mechanism is replaced
by a mechanism to refer to a common file; and the number of API methods is doubled to
allow all combinations.

Just as in pcap tracing, you can enable ascii tracing on a particular protocol/interface
pair by providing a Ptr<Ipv4> and an interface to an EnableAscii method. For example,:

Ptr<Ipv4> ipv4;
...
helper.EnableAsciiIpv4 ("prefix", ipv4, 1);

In this case, no trace contexts are written to the ascii trace file since they would be
redundant. The system will pick the file name to be created using the same rules as
described in the pcap section, except that the file will have the suffix ".tr" instead of
".pcap".

If you want to enable ascii tracing on more than one interface and have all traces sent to
a single file, you can do that as well by using an object to refer to a single file. We
have already something similar to this in the "cwnd" example above:

Ptr<Ipv4> protocol1 = node1->GetObject<Ipv4> ();
Ptr<Ipv4> protocol2 = node2->GetObject<Ipv4> ();
...
Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
...
helper.EnableAsciiIpv4 (stream, protocol1, 1);
helper.EnableAsciiIpv4 (stream, protocol2, 1);

In this case, trace contexts are written to the ascii trace file since they are required
to disambiguate traces from the two interfaces. Note that since the user is completely
specifying the file name, the string should include the ".tr" for consistency.

You can enable ascii tracing on a particular protocol by providing a std::string
representing an object name service string to an EnablePcap method. The Ptr<Ipv4> is
looked up from the name string. The <Node> in the resulting filenames is implicit since
there is a one-to-one correspondence between protocol instances and nodes, For example,:

Names::Add ("node1Ipv4" ...);
Names::Add ("node2Ipv4" ...);
...
helper.EnableAsciiIpv4 ("prefix", "node1Ipv4", 1);
helper.EnableAsciiIpv4 ("prefix", "node2Ipv4", 1);

This would result in two files named "prefix-nnode1Ipv4-i1.tr" and
"prefix-nnode2Ipv4-i1.tr" with traces for each interface in the respective trace file.
Since all of the EnableAscii functions are overloaded to take a stream wrapper, you can
use that form as well:

Names::Add ("node1Ipv4" ...);
Names::Add ("node2Ipv4" ...);
...
Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
...
helper.EnableAsciiIpv4 (stream, "node1Ipv4", 1);
helper.EnableAsciiIpv4 (stream, "node2Ipv4", 1);

This would result in a single trace file called "trace-file-name.tr" that contains all of
the trace events for both interfaces. The events would be disambiguated by trace context
strings.

You can enable ascii tracing on a collection of protocol/interface pairs by providing an
Ipv4InterfaceContainer. For each protocol of the proper type (the same type as is managed
by the device helper), tracing is enabled for the corresponding interface. Again, the
<Node> is implicit since there is a one-to-one correspondence between each protocol and
its node. For example,:

NodeContainer nodes;
...
NetDeviceContainer devices = deviceHelper.Install (nodes);
...
Ipv4AddressHelper ipv4;
ipv4.SetBase ("10.1.1.0", "255.255.255.0");
Ipv4InterfaceContainer interfaces = ipv4.Assign (devices);
...
...
helper.EnableAsciiIpv4 ("prefix", interfaces);

This would result in a number of ascii trace files being created, each of which follows
the <prefix>-n<node id>-i<interface>.tr convention. Combining all of the traces into a
single file is accomplished similarly to the examples above:

NodeContainer nodes;
...
NetDeviceContainer devices = deviceHelper.Install (nodes);
...
Ipv4AddressHelper ipv4;
ipv4.SetBase ("10.1.1.0", "255.255.255.0");
Ipv4InterfaceContainer interfaces = ipv4.Assign (devices);
...
Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream ("trace-file-name.tr");
...
helper.EnableAsciiIpv4 (stream, interfaces);

You can enable ascii tracing on a collection of protocol/interface pairs by providing a
NodeContainer. For each Node in the NodeContainer the appropriate protocol is found. For
each protocol, its interfaces are enumerated and tracing is enabled on the resulting
pairs. For example,:

NodeContainer n;
...
helper.EnableAsciiIpv4 ("prefix", n);

This would result in a number of ascii trace files being created, each of which follows
the <prefix>-<node id>-<device id>.tr convention. Combining all of the traces into a
single file is accomplished similarly to the examples above:

You can enable pcap tracing on the basis of node ID and device ID as well. In this case,
the node-id is translated to a Ptr<Node> and the appropriate protocol is looked up in the
node. The resulting protocol and interface are used to specify the resulting trace
source.:

helper.EnableAsciiIpv4 ("prefix", 21, 1);

Of course, the traces can be combined into a single file as shown above.

Finally, you can enable ascii tracing for all interfaces in the system, with associated
protocol being the same type as that managed by the device helper.:

helper.EnableAsciiIpv4All ("prefix");

This would result in a number of ascii trace files being created, one for every interface
in the system related to a protocol of the type managed by the helper. All of these files
will follow the <prefix>-n<node id>-i<interface.tr convention. Combining all of the traces
into a single file is accomplished similarly to the examples above.

Ascii Tracing Device Helper Filename Selection
Implicit in the prefix-style method descriptions above is the construction of the complete
filenames by the implementation method. By convention, ascii traces in the ns-3 system are
of the form "<prefix>-<node id>-<device id>.tr."

As previously mentioned, every node in the system will have a system-assigned node id.
Since there is a one-to-one correspondence between protocols and nodes we use to node-id
to identify the protocol identity. Every interface on a given protocol will have an
interface index (also called simply an interface) relative to its protocol. By default,
then, an ascii trace file created as a result of enabling tracing on the first device of
node 21, using the prefix "prefix", would be "prefix-n21-i1.tr". Use the prefix to
disambiguate multiple protocols per node.

You can always use the ns-3 object name service to make this more clear. For example, if
you use the object name service to assign the name "serverIpv4" to the protocol on node
21, and also specify interface one, the resulting ascii trace file name will automatically
become, "prefix-nserverIpv4-1.tr".

Tracing implementation details
Data Collection
This chapter describes the ns-3 Data Collection Framework (DCF), which provides
capabilities to obtain data generated by models in the simulator, to perform on-line
reduction and data processing, and to marshal raw or transformed data into various output
formats.

The framework presently supports standalone ns-3 runs that don't rely on any external
program execution control. The objects provided by the DCF may be hooked to ns-3 trace
sources to enable data processing.

The source code for the classes lives in the directory src/stats.

This chapter is organized as follows. First, an overview of the architecture is
presented. Next, the helpers for these classes are presented; this initial treatment
should allow basic use of the data collection framework for many use cases. Users who
wish to produce output outside of the scope of the current helpers, or who wish to create
their own data collection objects, should read the remainder of the chapter, which goes
into detail about all of the basic DCF object types and provides low-level coding
examples.

Design
The DCF consists of three basic classes:

· Probe is a mechanism to instrument and control the output of simulation data that is
used to monitor interesting events. It produces output in the form of one or more ns-3
trace sources. Probe objects are hooked up to one or more trace sinks (called
Collectors), which process samples on-line and prepare them for output.

· Collector consumes the data generated by one or more Probe objects. It performs
transformations on the data, such as normalization, reduction, and the computation of
basic statistics. Collector objects do not produce data that is directly output by the
ns-3 run; instead, they output data downstream to another type of object, called
Aggregator, which performs that function. Typically, Collectors output their data in
the form of trace sources as well, allowing collectors to be chained in series.

· Aggregator is the end point of the data collected by a network of Probes and Collectors.
The main responsibility of the Aggregator is to marshal data and their corresponding
metadata, into different output formats such as plain text files, spreadsheet files, or
databases.

All three of these classes provide the capability to dynamically turn themselves on or off
throughout a simulation.

Any standalone ns-3 simulation run that uses the DCF will typically create at least one
instance of each of the three classes above.
[image] Data Collection Framework overview.UNINDENT

The overall flow of data processing is depicted in Data Collection Framework overview.
On the left side, a running ns-3 simulation is depicted. In the course of running the
simulation, data is made available by models through trace sources, or via other means.
The diagram depicts that probes can be connected to these trace sources to receive data
asynchronously, or probes can poll for data. Data is then passed to a collector object
that transforms the data. Finally, an aggregator can be connected to the outputs of the
collector, to generate plots, files, or databases.
[image] Data Collection Framework aggregation.UNINDENT

A variation on the above figure is provided in Data Collection Framework aggregation.
This second figure illustrates that the DCF objects may be chained together in a manner
that downstream objects take inputs from multiple upstream objects. The figure
conceptually shows that multiple probes may generate output that is fed into a single
collector; as an example, a collector that outputs a ratio of two counters would
typically acquire each counter data from separate probes. Multiple collectors can also
feed into a single aggregator, which (as its name implies) may collect a number of data
streams for inclusion into a single plot, file, or database.

Data Collection Helpers
The full flexibility of the data collection framework is provided by the interconnection
of probes, collectors, and aggregators. Performing all of these interconnections leads to
many configuration statements in user programs. For ease of use, some of the most common
operations can be combined and encapsulated in helper functions. In addition, some
statements involving ns-3 trace sources do not have Python bindings, due to limitations in
the bindings.

Data Collection Helpers Overview
In this section, we provide an overview of some helper classes that have been created to
ease the configuration of the data collection framework for some common use cases. The
helpers allow users to form common operations with only a few statements in their C++ or
Python programs. But, this ease of use comes at the cost of significantly less
flexibility than low-level configuration can provide, and the need to explicitly code
support for new Probe types into the helpers (to work around an issue described below).

The emphasis on the current helpers is to marshal data out of ns-3 trace sources into
gnuplot plots or text files, without a high degree of output customization or statistical
processing (initially). Also, the use is constrained to the available probe types in
ns-3. Later sections of this documentation will go into more detail about creating new
Probe types, as well as details about hooking together Probes, Collectors, and Aggregators
in custom arrangements.

To date, two Data Collection helpers have been implemented:

· GnuplotHelper

· FileHelper

GnuplotHelper
The GnuplotHelper is a helper class for producing output files used to make gnuplots. The
overall goal is to provide the ability for users to quickly make plots from data exported
in ns-3 trace sources. By default, a minimal amount of data transformation is performed;
the objective is to generate plots with as few (default) configuration statements as
possible.

GnuplotHelper Overview
The GnuplotHelper will create 3 different files at the end of the simulation:

· A space separated gnuplot data file

· A gnuplot control file

· A shell script to generate the gnuplot

There are two configuration statements that are needed to produce plots. The first
statement configures the plot (filename, title, legends, and output type, where the output
type defaults to PNG if unspecified):

void ConfigurePlot (const std::string &outputFileNameWithoutExtension,
const std::string &title,
const std::string &xLegend,
const std::string &yLegend,
const std::string &terminalType = ".png");

The second statement hooks the trace source of interest:

void PlotProbe (const std::string &typeId,
const std::string &path,
const std::string &probeTraceSource,
const std::string &title);

The arguments are as follows:

· typeId: The ns-3 TypeId of the Probe

· path: The path in the ns-3 configuration namespace to one or more trace sources

· probeTraceSource: Which output of the probe (itself a trace source) should be plotted

· title: The title to associate with the dataset(s) (in the gnuplot legend)

A variant on the PlotProbe above is to specify a fifth optional argument that controls
where in the plot the key (legend) is placed.

A fully worked example (from seventh.cc) is shown below:

// Create the gnuplot helper.
GnuplotHelper plotHelper;

// Configure the plot.
// Configure the plot. The first argument is the file name prefix
// for the output files generated. The second, third, and fourth
// arguments are, respectively, the plot title, x-axis, and y-axis labels
plotHelper.ConfigurePlot ("seventh-packet-byte-count",
"Packet Byte Count vs. Time",
"Time (Seconds)",
"Packet Byte Count",
"png");

// Specify the probe type, trace source path (in configuration namespace), and
// probe output trace source ("OutputBytes") to plot. The fourth argument
// specifies the name of the data series label on the plot. The last
// argument formats the plot by specifying where the key should be placed.
plotHelper.PlotProbe (probeType,
tracePath,
"OutputBytes",
"Packet Byte Count",
GnuplotAggregator::KEY_BELOW);

In this example, the probeType and tracePath are as follows (for IPv4):

probeType = "ns3::Ipv4PacketProbe";
tracePath = "/NodeList/*/$ns3::Ipv4L3Protocol/Tx";

The probeType is a key parameter for this helper to work. This TypeId must be registered
in the system, and the signature on the Probe's trace sink must match that of the trace
source it is being hooked to. Probe types are pre-defined for a number of data types
corresponding to ns-3 traced values, and for a few other trace source signatures such as
the 'Tx' trace source of ns3::Ipv4L3Protocol class.

Note that the trace source path specified may contain wildcards. In this case, multiple
datasets are plotted on one plot; one for each matched path.

The main output produced will be three files:

seventh-packet-byte-count.dat
seventh-packet-byte-count.plt
seventh-packet-byte-count.sh

At this point, users can either hand edit the .plt file for further customizations, or
just run it through gnuplot. Running sh seventh-packet-byte-count.sh simply runs the plot
through gnuplot, as shown below.
[image] 2-D Gnuplot Created by seventh.cc Example..UNINDENT

It can be seen that the key elements (legend, title, legend placement, xlabel, ylabel,
and path for the data) are all placed on the plot. Since there were two matches to the
configuration path provided, the two data series are shown:

· Packet Byte Count-0 corresponds to /NodeList/0/$ns3::Ipv4L3Protocol/Tx

· Packet Byte Count-1 corresponds to /NodeList/1/$ns3::Ipv4L3Protocol/Tx

GnuplotHelper ConfigurePlot
The GnuplotHelper's ConfigurePlot() function can be used to configure plots.

It has the following prototype:

void ConfigurePlot (const std::string &outputFileNameWithoutExtension,
const std::string &title,
const std::string &xLegend,
const std::string &yLegend,
const std::string &terminalType = ".png");

It has the following arguments:

┌───────────────────────────────┬──────────────────────────────────┐
│Argument │ Description │
├───────────────────────────────┼──────────────────────────────────┤
│outputFileNameWithoutExtension │ Name of gnuplot related files to │
│ │ write with no extension. │
├───────────────────────────────┼──────────────────────────────────┤
│title │ Plot title string to use for │
│ │ this plot. │
├───────────────────────────────┼──────────────────────────────────┤
│xLegend │ The legend for the x horizontal │
│ │ axis. │
├───────────────────────────────┼──────────────────────────────────┤
│yLegend │ The legend for the y vertical │
│ │ axis. │
└───────────────────────────────┴──────────────────────────────────┘

│terminalType │ Terminal type setting string for │
│ │ output. The default terminal │
│ │ type is "png". │
└───────────────────────────────┴──────────────────────────────────┘

The GnuplotHelper's ConfigurePlot() function configures plot related parameters for this
gnuplot helper so that it will create a space separated gnuplot data file named
outputFileNameWithoutExtension + ".dat", a gnuplot control file named
outputFileNameWithoutExtension + ".plt", and a shell script to generate the gnuplot named
outputFileNameWithoutExtension + ".sh".

An example of how to use this function can be seen in the seventh.cc code described above
where it was used as follows:

plotHelper.ConfigurePlot ("seventh-packet-byte-count",
"Packet Byte Count vs. Time",
"Time (Seconds)",
"Packet Byte Count",
"png");

GnuplotHelper PlotProbe
The GnuplotHelper's PlotProbe() function can be used to plot values generated by probes.

It has the following prototype:

void PlotProbe (const std::string &typeId,
const std::string &path,
const std::string &probeTraceSource,
const std::string &title,
enum GnuplotAggregator::KeyLocation keyLocation = GnuplotAggregator::KEY_INSIDE);

It has the following arguments:

┌─────────────────┬──────────────────────────────────┐
│Argument │ Description │
├─────────────────┼──────────────────────────────────┤
│typeId │ The type ID for the probe │
│ │ created by this helper. │
├─────────────────┼──────────────────────────────────┤
│path │ Config path to access the trace │
│ │ source. │
├─────────────────┼──────────────────────────────────┤
│probeTraceSource │ The probe trace source to │
│ │ access. │
├─────────────────┼──────────────────────────────────┤
│title │ The title to be associated to │
│ │ this dataset │
├─────────────────┼──────────────────────────────────┤
│keyLocation │ The location of the key in the │
│ │ plot. The default location is │
│ │ inside. │
└─────────────────┴──────────────────────────────────┘

The GnuplotHelper's PlotProbe() function plots a dataset generated by hooking the ns-3
trace source with a probe created by the helper, and then plotting the values from the
probeTraceSource. The dataset will have the provided title, and will consist of the
'newValue' at each timestamp.

If the config path has more than one match in the system because there is a wildcard, then
one dataset for each match will be plotted. The dataset titles will be suffixed with the
matched characters for each of the wildcards in the config path, separated by spaces. For
example, if the proposed dataset title is the string "bytes", and there are two wildcards
in the path, then dataset titles like "bytes-0 0" or "bytes-12 9" will be possible as
labels for the datasets that are plotted.

An example of how to use this function can be seen in the seventh.cc code described above
where it was used (with variable substitution) as follows:

plotHelper.PlotProbe ("ns3::Ipv4PacketProbe",
"/NodeList/*/$ns3::Ipv4L3Protocol/Tx",
"OutputBytes",
"Packet Byte Count",
GnuplotAggregator::KEY_BELOW);

Other Examples
Gnuplot Helper Example
A slightly simpler example than the seventh.cc example can be found in
src/stats/examples/gnuplot-helper-example.cc. The following 2-D gnuplot was created using
the example.
[image] 2-D Gnuplot Created by gnuplot-helper-example.cc Example..UNINDENT

In this example, there is an Emitter object that increments its counter according to a
Poisson process and then emits the counter's value as a trace source.

Ptr<Emitter> emitter = CreateObject<Emitter> ();
Names::Add ("/Names/Emitter", emitter);

Note that because there are no wildcards in the path used below, only 1 datastream was
drawn in the plot. This single datastream in the plot is simply labeled "Emitter Count",
with no extra suffixes like one would see if there were wildcards in the path.

// Create the gnuplot helper.
GnuplotHelper plotHelper;

// Configure the plot.
plotHelper.ConfigurePlot ("gnuplot-helper-example",
"Emitter Counts vs. Time",
"Time (Seconds)",
"Emitter Count",
"png");

// Plot the values generated by the probe. The path that we provide
// helps to disambiguate the source of the trace.
plotHelper.PlotProbe ("ns3::Uinteger32Probe",
"/Names/Emitter/Counter",
"Output",
"Emitter Count",
GnuplotAggregator::KEY_INSIDE);

FileHelper
The FileHelper is a helper class used to put data values into a file. The overall goal is
to provide the ability for users to quickly make formatted text files from data exported
in ns-3 trace sources. By default, a minimal amount of data transformation is performed;
the objective is to generate files with as few (default) configuration statements as
possible.

FileHelper Overview
The FileHelper will create 1 or more text files at the end of the simulation.

The FileHelper can create 4 different types of text files:

· Formatted

· Space separated (the default)

· Comma separated

· Tab separated

Formatted files use C-style format strings and the sprintf() function to print their
values in the file being written.

The following text file with 2 columns of formatted values named
seventh-packet-byte-count-0.txt was created using more new code that was added to the
original ns-3 Tutorial example's code. Only the first 10 lines of this file are shown
here for brevity.

Time (Seconds) = 1.000e+00 Packet Byte Count = 40
Time (Seconds) = 1.004e+00 Packet Byte Count = 40
Time (Seconds) = 1.004e+00 Packet Byte Count = 576
Time (Seconds) = 1.009e+00 Packet Byte Count = 576
Time (Seconds) = 1.009e+00 Packet Byte Count = 576
Time (Seconds) = 1.015e+00 Packet Byte Count = 512
Time (Seconds) = 1.017e+00 Packet Byte Count = 576
Time (Seconds) = 1.017e+00 Packet Byte Count = 544
Time (Seconds) = 1.025e+00 Packet Byte Count = 576
Time (Seconds) = 1.025e+00 Packet Byte Count = 544

...

The following different text file with 2 columns of formatted values named
seventh-packet-byte-count-1.txt was also created using the same new code that was added to
the original ns-3 Tutorial example's code. Only the first 10 lines of this file are shown
here for brevity.

Time (Seconds) = 1.002e+00 Packet Byte Count = 40
Time (Seconds) = 1.007e+00 Packet Byte Count = 40
Time (Seconds) = 1.013e+00 Packet Byte Count = 40
Time (Seconds) = 1.020e+00 Packet Byte Count = 40
Time (Seconds) = 1.028e+00 Packet Byte Count = 40
Time (Seconds) = 1.036e+00 Packet Byte Count = 40
Time (Seconds) = 1.045e+00 Packet Byte Count = 40
Time (Seconds) = 1.053e+00 Packet Byte Count = 40
Time (Seconds) = 1.061e+00 Packet Byte Count = 40
Time (Seconds) = 1.069e+00 Packet Byte Count = 40

...

The new code that was added to produce the two text files is below. More details about
this API will be covered in a later section.

Note that because there were 2 matches for the wildcard in the path, 2 separate text files
were created. The first text file, which is named "seventh-packet-byte-count-0.txt",
corresponds to the wildcard match with the "*" replaced with "0". The second text file,
which is named "seventh-packet-byte-count-1.txt", corresponds to the wildcard match with
the "*" replaced with "1". Also, note that the function call to WriteProbe() will give an
error message if there are no matches for a path that contains wildcards.

// Create the file helper.
FileHelper fileHelper;

// Configure the file to be written.
fileHelper.ConfigureFile ("seventh-packet-byte-count",
FileAggregator::FORMATTED);

// Set the labels for this formatted output file.
fileHelper.Set2dFormat ("Time (Seconds) = %.3e\tPacket Byte Count = %.0f");

// Write the values generated by the probe.
fileHelper.WriteProbe ("ns3::Ipv4PacketProbe",
"/NodeList/*/$ns3::Ipv4L3Protocol/Tx",
"OutputBytes");

FileHelper ConfigureFile
The FileHelper's ConfigureFile() function can be used to configure text files.

It has the following prototype:

void ConfigureFile (const std::string &outputFileNameWithoutExtension,
enum FileAggregator::FileType fileType = FileAggregator::SPACE_SEPARATED);

It has the following arguments:

┌───────────────────────────────┬──────────────────────────────────┐
│Argument │ Description │
├───────────────────────────────┼──────────────────────────────────┤
│outputFileNameWithoutExtension │ Name of output file to write │
│ │ with no extension. │
├───────────────────────────────┼──────────────────────────────────┤
│fileType │ Type of file to write. The │
│ │ default type of file is space │
│ │ separated. │
└───────────────────────────────┴──────────────────────────────────┘

The FileHelper's ConfigureFile() function configures text file related parameters for the
file helper so that it will create a file named outputFileNameWithoutExtension plus
possible extra information from wildcard matches plus ".txt" with values printed as
specified by fileType. The default file type is space-separated.

An example of how to use this function can be seen in the seventh.cc code described above
where it was used as follows:

fileHelper.ConfigureFile ("seventh-packet-byte-count",
FileAggregator::FORMATTED);

FileHelper WriteProbe
The FileHelper's WriteProbe() function can be used to write values generated by probes to
text files.

It has the following prototype:

void WriteProbe (const std::string &typeId,
const std::string &path,
const std::string &probeTraceSource);

It has the following arguments:

┌─────────────────┬──────────────────────────────────┐
│Argument │ Description │
├─────────────────┼──────────────────────────────────┤
│typeId │ The type ID for the probe to be │
│ │ created. │
├─────────────────┼──────────────────────────────────┤
│path │ Config path to access the trace │
│ │ source. │
├─────────────────┼──────────────────────────────────┤
│probeTraceSource │ The probe trace source to │
│ │ access. │
└─────────────────┴──────────────────────────────────┘

The FileHelper's WriteProbe() function creates output text files generated by hooking the
ns-3 trace source with a probe created by the helper, and then writing the values from the
probeTraceSource. The output file names will have the text stored in the member variable
m_outputFileNameWithoutExtension plus ".txt", and will consist of the 'newValue' at each
timestamp.

If the config path has more than one match in the system because there is a wildcard, then
one output file for each match will be created. The output file names will contain the
text in m_outputFileNameWithoutExtension plus the matched characters for each of the
wildcards in the config path, separated by dashes, plus ".txt". For example, if the value
in m_outputFileNameWithoutExtension is the string "packet-byte-count", and there are two
wildcards in the path, then output file names like "packet-byte-count-0-0.txt" or
"packet-byte-count-12-9.txt" will be possible as names for the files that will be created.

An example of how to use this function can be seen in the seventh.cc code described above
where it was used as follows:

fileHelper.WriteProbe ("ns3::Ipv4PacketProbe",
"/NodeList/*/$ns3::Ipv4L3Protocol/Tx",
"OutputBytes");

Other Examples
File Helper Example
A slightly simpler example than the seventh.cc example can be found in
src/stats/examples/file-helper-example.cc. This example only uses the FileHelper.

The following text file with 2 columns of formatted values named file-helper-example.txt
was created using the example. Only the first 10 lines of this file are shown here for
brevity.

Time (Seconds) = 0.203 Count = 1
Time (Seconds) = 0.702 Count = 2
Time (Seconds) = 1.404 Count = 3
Time (Seconds) = 2.368 Count = 4
Time (Seconds) = 3.364 Count = 5
Time (Seconds) = 3.579 Count = 6
Time (Seconds) = 5.873 Count = 7
Time (Seconds) = 6.410 Count = 8
Time (Seconds) = 6.472 Count = 9
...

In this example, there is an Emitter object that increments its counter according to a
Poisson process and then emits the counter's value as a trace source.

Ptr<Emitter> emitter = CreateObject<Emitter> ();
Names::Add ("/Names/Emitter", emitter);

Note that because there are no wildcards in the path used below, only 1 text file was
created. This single text file is simply named "file-helper-example.txt", with no extra
suffixes like you would see if there were wildcards in the path.

// Create the file helper.
FileHelper fileHelper;

// Configure the file to be written.
fileHelper.ConfigureFile ("file-helper-example",
FileAggregator::FORMATTED);

// Set the labels for this formatted output file.
fileHelper.Set2dFormat ("Time (Seconds) = %.3e\tCount = %.0f");

// Write the values generated by the probe. The path that we
// provide helps to disambiguate the source of the trace.
fileHelper.WriteProbe ("ns3::Uinteger32Probe",
"/Names/Emitter/Counter",
"Output");

Scope and Limitations
Currently, only these Probes have been implemented and connected to the GnuplotHelper and
to the FileHelper:

· BooleanProbe

· DoubleProbe

· Uinteger8Probe

· Uinteger16Probe

· Uinteger32Probe

· TimeProbe

· PacketProbe

· ApplicationPacketProbe

· Ipv4PacketProbe

These Probes, therefore, are the only TypeIds available to be used in PlotProbe() and
WriteProbe().

In the next few sections, we cover each of the fundamental object types (Probe, Collector,
and Aggregator) in more detail, and show how they can be connected together using
lower-level API.

Probes
This section details the functionalities provided by the Probe class to an ns-3
simulation, and gives examples on how to code them in a program. This section is meant for
users interested in developing simulations with the ns-3 tools and using the Data
Collection Framework, of which the Probe class is a part, to generate data output with
their simulation's results.

Probe Overview
A Probe object is supposed to be connected to a variable from the simulation whose values
throughout the experiment are relevant to the user. The Probe will record what were
values assumed by the variable throughout the simulation and pass such data to another
member of the Data Collection Framework. While it is out of this section's scope to
discuss what happens after the Probe produces its output, it is sufficient to say that, by
the end of the simulation, the user will have detailed information about what values were
stored inside the variable being probed during the simulation.

Typically, a Probe is connected to an ns-3 trace source. In this manner, whenever the
trace source exports a new value, the Probe consumes the value (and exports it downstream
to another object via its own trace source).

The Probe can be thought of as kind of a filter on trace sources. The main reasons for
possibly hooking to a Probe rather than directly to a trace source are as follows:

· Probes may be dynamically turned on and off during the simulation with calls to Enable()
and Disable(). For example, the outputting of data may be turned off during the
simulation warmup phase.

· Probes may perform operations on the data to extract values from more complicated
structures; for instance, outputting the packet size value from a received ns3::Packet.

· Probes register a name in the ns3::Config namespace (using Names::Add ()) so that other
objects may refer to them.

· Probes provide a static method that allows one to manipulate a Probe by name, such as
what is done in ns2measure [Cic06]

Stat::put ("my_metric", ID, sample);

The ns-3 equivalent of the above ns2measure code is, e.g.

DoubleProbe::SetValueByPath ("/path/to/probe", sample);

Creation
Note that a Probe base class object can not be created because it is an abstract base
class, i.e. it has pure virtual functions that have not been implemented. An object of
type DoubleProbe, which is a subclass of the Probe class, will be created here to show
what needs to be done.

One declares a DoubleProbe in dynamic memory by using the smart pointer class (Ptr<T>). To
create a DoubleProbe in dynamic memory with smart pointers, one just needs to call the
ns-3 method CreateObject():

Ptr<DoubleProbe> myprobe = CreateObject<DoubleProbe> ();

The declaration above creates DoubleProbes using the default values for its attributes.
There are four attributes in the DoubleProbe class; two in the base class object
DataCollectionObject, and two in the Probe base class:

· "Name" (DataCollectionObject), a StringValue

· "Enabled" (DataCollectionObject), a BooleanValue

· "Start" (Probe), a TimeValue

· "Stop" (Probe), a TimeValue

One can set such attributes at object creation by using the following method:

Ptr<DoubleProbe> myprobe = CreateObjectWithAttributes<DoubleProbe> (
"Name", StringValue ("myprobe"),
"Enabled", BooleanValue (false),
"Start", TimeValue (Seconds (100.0)),
"Stop", TimeValue (Seconds (1000.0)));

Start and Stop are Time variables which determine the interval of action of the Probe. The
Probe will only output data if the current time of the Simulation is inside of that
interval. The special time value of 0 seconds for Stop will disable this attribute (i.e.
keep the Probe on for the whole simulation). Enabled is a flag that turns the Probe on or
off, and must be set to true for the Probe to export data. The Name is the object's name
in the DCF framework.

Importing and exporting data
ns-3 trace sources are strongly typed, so the mechanisms for hooking Probes to a trace
source and for exporting data belong to its subclasses. For instance, the default
distribution of ns-3 provides a class DoubleProbe that is designed to hook to a trace
source exporting a double value. We'll next detail the operation of the DoubleProbe, and
then discuss how other Probe classes may be defined by the user.

DoubleProbe Overview
The DoubleProbe connects to a double-valued ns-3 trace source, and itself exports a
different double-valued ns-3 trace source.

The following code, drawn from src/stats/examples/double-probe-example.cc, shows the basic
operations of plumbing the DoubleProbe into a simulation, where it is probing a Counter
exported by an emitter object (class Emitter).

Ptr<Emitter> emitter = CreateObject<Emitter> ();
Names::Add ("/Names/Emitter", emitter);
...

Ptr<DoubleProbe> probe1 = CreateObject<DoubleProbe> ();

// Connect the probe to the emitter's Counter
bool connected = probe1->ConnectByObject ("Counter", emitter);

The following code is probing the same Counter exported by the same emitter object. This
DoubleProbe, however, is using a path in the configuration namespace to make the
connection. Note that the emitter registered itself in the configuration namespace after
it was created; otherwise, the ConnectByPath would not work.

Ptr<DoubleProbe> probe2 = CreateObject<DoubleProbe> ();

// Note, no return value is checked here
probe2->ConnectByPath ("/Names/Emitter/Counter");

The next DoubleProbe shown that is shown below will have its value set using its path in
the configuration namespace. Note that this time the DoubleProbe registered itself in the
configuration namespace after it was created.

Ptr<DoubleProbe> probe3 = CreateObject<DoubleProbe> ();
probe3->SetName ("StaticallyAccessedProbe");

// We must add it to the config database
Names::Add ("/Names/Probes", probe3->GetName (), probe3);

The emitter's Count() function is now able to set the value for this DoubleProbe as
follows:

void
Emitter::Count (void)
{
...
m_counter += 1.0;
DoubleProbe::SetValueByPath ("/Names/StaticallyAccessedProbe", m_counter);
...
}

The above example shows how the code calling the Probe does not have to have an explicit
reference to the Probe, but can direct the value setting through the Config namespace.
This is similar in functionality to the Stat::Put method introduced by ns2measure paper
[Cic06], and allows users to temporarily insert Probe statements like printf statements
within existing ns-3 models. Note that in order to be able to use the DoubleProbe in this
example like this, 2 things were necessary:

1. the stats module header file was included in the example .cc file

2. the example was made dependent on the stats module in its wscript file.

Analogous things need to be done in order to add other Probes in other places in the ns-3
code base.

The values for the DoubleProbe can also be set using the function DoubleProbe::SetValue(),
while the values for the DoubleProbe can be gotten using the function
DoubleProbe::GetValue().

The DoubleProbe exports double values in its "Output" trace source; a downstream object
can hook a trace sink (NotifyViaProbe) to this as follows:

connected = probe1->TraceConnect ("Output", probe1->GetName (), MakeCallback (&NotifyViaProbe));

Other probes
Besides the DoubleProbe, the following Probes are also available:

· Uinteger8Probe connects to an ns-3 trace source exporting an uint8_t.

· Uinteger16Probe connects to an ns-3 trace source exporting an uint16_t.

· Uinteger32Probe connects to an ns-3 trace source exporting an uint32_t.

· PacketProbe connects to an ns-3 trace source exporting a packet.

· ApplicationPacketProbe connects to an ns-3 trace source exporting a packet and a socket
address.

· Ipv4PacketProbe connects to an ns-3 trace source exporting a packet, an IPv4 object, and
an interface.

Creating new Probe types
To create a new Probe type, you need to perform the following steps:

· Be sure that your new Probe class is derived from the Probe base class.

· Be sure that the pure virtual functions that your new Probe class inherits from the
Probe base class are implemented.

· Find an existing Probe class that uses a trace source that is closest in type to the
type of trace source your Probe will be using.

· Copy that existing Probe class's header file (.h) and implementation file (.cc) to two
new files with names matching your new Probe.

· Replace the types, arguments, and variables in the copied files with the appropriate
type for your Probe.

· Make necessary modifications to make the code compile and to make it behave as you would
like.

Examples
Two examples will be discussed in detail here:

· Double Probe Example

· IPv4 Packet Plot Example

Double Probe Example
The double probe example has been discussed previously. The example program can be found
in src/stats/examples/double-probe-example.cc. To summarize what occurs in this program,
there is an emitter that exports a counter that increments according to a Poisson process.
In particular, two ways of emitting data are shown:

1. through a traced variable hooked to one Probe:

TracedValue<double> m_counter; // normally this would be integer type

2. through a counter whose value is posted to a second Probe, referenced by its name in
the Config system:

void
Emitter::Count (void)
{
NS_LOG_FUNCTION (this);
NS_LOG_DEBUG ("Counting at " << Simulator::Now ().GetSeconds ());
m_counter += 1.0;
DoubleProbe::SetValueByPath ("/Names/StaticallyAccessedProbe", m_counter);
Simulator::Schedule (Seconds (m_var->GetValue ()), &Emitter::Count, this);
}

Let's look at the Probe more carefully. Probes can receive their values in a multiple
ways:

1. by the Probe accessing the trace source directly and connecting a trace sink to it

2. by the Probe accessing the trace source through the config namespace and connecting a
trace sink to it

3. by the calling code explicitly calling the Probe's SetValue() method

4. by the calling code explicitly calling SetValueByPath
("/path/through/Config/namespace", ...)

The first two techniques are expected to be the most common. Also in the example, the
hooking of a normal callback function is shown, as is typically done in ns-3. This
callback function is not associated with a Probe object. We'll call this case 0) below.

// This is a function to test hooking a raw function to the trace source
void
NotifyViaTraceSource (std::string context, double oldVal, double newVal)
{
NS_LOG_DEBUG ("context: " << context << " old " << oldVal << " new " << newVal);
}

First, the emitter needs to be setup:

Ptr<Emitter> emitter = CreateObject<Emitter> ();
Names::Add ("/Names/Emitter", emitter);

// The Emitter object is not associated with an ns-3 node, so
// it won't get started automatically, so we need to do this ourselves
Simulator::Schedule (Seconds (0.0), &Emitter::Start, emitter);

The various DoubleProbes interact with the emitter in the example as shown below.

Case 0):

// The below shows typical functionality without a probe
// (connect a sink function to a trace source)
//
connected = emitter->TraceConnect ("Counter", "sample context", MakeCallback (&NotifyViaTraceSource));
NS_ASSERT_MSG (connected, "Trace source not connected");

case 1):

//
// Probe1 will be hooked directly to the Emitter trace source object
//

// probe1 will be hooked to the Emitter trace source
Ptr<DoubleProbe> probe1 = CreateObject<DoubleProbe> ();
// the probe's name can serve as its context in the tracing
probe1->SetName ("ObjectProbe");

// Connect the probe to the emitter's Counter
connected = probe1->ConnectByObject ("Counter", emitter);
NS_ASSERT_MSG (connected, "Trace source not connected to probe1");

case 2):

//
// Probe2 will be hooked to the Emitter trace source object by
// accessing it by path name in the Config database
//

// Create another similar probe; this will hook up via a Config path
Ptr<DoubleProbe> probe2 = CreateObject<DoubleProbe> ();
probe2->SetName ("PathProbe");

// Note, no return value is checked here
probe2->ConnectByPath ("/Names/Emitter/Counter");

case 4) (case 3 is not shown in this example):

//
// Probe3 will be called by the emitter directly through the
// static method SetValueByPath().
//
Ptr<DoubleProbe> probe3 = CreateObject<DoubleProbe> ();
probe3->SetName ("StaticallyAccessedProbe");
// We must add it to the config database
Names::Add ("/Names/Probes", probe3->GetName (), probe3);

And finally, the example shows how the probes can be hooked to generate output:

// The probe itself should generate output. The context that we provide
// to this probe (in this case, the probe name) will help to disambiguate
// the source of the trace
connected = probe3->TraceConnect ("Output",
"/Names/Probes/StaticallyAccessedProbe/Output",
MakeCallback (&NotifyViaProbe));
NS_ASSERT_MSG (connected, "Trace source not .. connected to probe3 Output");

The following callback is hooked to the Probe in this example for illustrative purposes;
normally, the Probe would be hooked to a Collector object.

// This is a function to test hooking it to the probe output
void
NotifyViaProbe (std::string context, double oldVal, double newVal)
{
NS_LOG_DEBUG ("context: " << context << " old " << oldVal << " new " << newVal);
}

IPv4 Packet Plot Example
The IPv4 packet plot example is based on the fifth.cc example from the ns-3 Tutorial. It
can be found in src/stats/examples/ipv4-packet-plot-example.cc.

node 0 node 1
+----------------+ +----------------+
| ns-3 TCP | | ns-3 TCP |
+----------------+ +----------------+
| 10.1.1.1 | | 10.1.1.2 |
+----------------+ +----------------+
| point-to-point | | point-to-point |
+----------------+ +----------------+
| |
+---------------------+

We'll just look at the Probe, as it illustrates that Probes may also unpack values from
structures (in this case, packets) and report those values as trace source outputs, rather
than just passing through the same type of data.

There are other aspects of this example that will be explained later in the documentation.
The two types of data that are exported are the packet itself (Output) and a count of the
number of bytes in the packet (OutputBytes).

TypeId
Ipv4PacketProbe::GetTypeId ()
{
static TypeId tid = TypeId ("ns3::Ipv4PacketProbe")
.SetParent<Probe> ()
.AddConstructor<Ipv4PacketProbe> ()
.AddTraceSource ( "Output",
"The packet plus its IPv4 object and interface that serve as the output for this probe",
MakeTraceSourceAccessor (&Ipv4PacketProbe::m_output))
.AddTraceSource ( "OutputBytes",
"The number of bytes in the packet",
MakeTraceSourceAccessor (&Ipv4PacketProbe::m_outputBytes))
;
return tid;
}

When the Probe's trace sink gets a packet, if the Probe is enabled, then it will output
the packet on its Output trace source, but it will also output the number of bytes on the
OutputBytes trace source.

void
Ipv4PacketProbe::TraceSink (Ptr<const Packet> packet, Ptr<Ipv4> ipv4, uint32_t interface)
{
NS_LOG_FUNCTION (this << packet << ipv4 << interface);
if (IsEnabled ())
{
m_packet = packet;
m_ipv4 = ipv4;
m_interface = interface;
m_output (packet, ipv4, interface);

uint32_t packetSizeNew = packet->GetSize ();
m_outputBytes (m_packetSizeOld, packetSizeNew);
m_packetSizeOld = packetSizeNew;
}
}

References
[Cic06]
Claudio Cicconetti, Enzo Mingozzi, Giovanni Stea, "An Integrated Framework for
Enabling Effective Data Collection and Statistical Analysis with ns2, Workshop on
ns-2 (WNS2), Pisa, Italy, October 2006.

Collectors
This section is a placeholder to detail the functionalities provided by the Collector
class to an ns-3 simulation, and gives examples on how to code them in a program.

Note: As of ns-3.18, Collectors are still under development and not yet provided as part
of the framework.

Aggregators
This section details the functionalities provided by the Aggregator class to an ns-3
simulation. This section is meant for users interested in developing simulations with the
ns-3 tools and using the Data Collection Framework, of which the Aggregator class is a
part, to generate data output with their simulation's results.

Aggregator Overview
An Aggregator object is supposed to be hooked to one or more trace sources in order to
receive input. Aggregators are the end point of the data collected by the network of
Probes and Collectors during the simulation. It is the Aggregator's job to take these
values and transform them into their final output format such as plain text files,
spreadsheet files, plots, or databases.

Typically, an aggregator is connected to one or more Collectors. In this manner, whenever
the Collectors' trace sources export new values, the Aggregator can process the value so
that it can be used in the final output format where the data values will reside after the
simulation.

Note the following about Aggregators:

· Aggregators may be dynamically turned on and off during the simulation with calls to
Enable() and Disable(). For example, the aggregating of data may be turned off during
the simulation warmup phase, which means those values won't be included in the final
output medium.

· Aggregators receive data from Collectors via callbacks. When a Collector is associated
to an aggregator, a call to TraceConnect is made to establish the Aggregator's trace
sink method as a callback.

To date, two Aggregators have been implemented:

· GnuplotAggregator

· FileAggregator

GnuplotAggregator
The GnuplotAggregator produces output files used to make gnuplots.

The GnuplotAggregator will create 3 different files at the end of the simulation:

· A space separated gnuplot data file

· A gnuplot control file

· A shell script to generate the gnuplot

Creation
An object of type GnuplotAggregator will be created here to show what needs to be done.

One declares a GnuplotAggregator in dynamic memory by using the smart pointer class
(Ptr<T>). To create a GnuplotAggregator in dynamic memory with smart pointers, one just
needs to call the ns-3 method CreateObject(). The following code from
src/stats/examples/gnuplot-aggregator-example.cc shows how to do this:

string fileNameWithoutExtension = "gnuplot-aggregator";

// Create an aggregator.
Ptr<GnuplotAggregator> aggregator =
CreateObject<GnuplotAggregator> (fileNameWithoutExtension);

The first argument for the constructor, fileNameWithoutExtension, is the name of the
gnuplot related files to write with no extension. This GnuplotAggregator will create a
space separated gnuplot data file named "gnuplot-aggregator.dat", a gnuplot control file
named "gnuplot-aggregator.plt", and a shell script to generate the gnuplot named +
"gnuplot-aggregator.sh".

The gnuplot that is created can have its key in 4 different locations:

· No key

· Key inside the plot (the default)

· Key above the plot

· Key below the plot

The following gnuplot key location enum values are allowed to specify the key's position:

enum KeyLocation {
NO_KEY,
KEY_INSIDE,
KEY_ABOVE,
KEY_BELOW
};

If it was desired to have the key below rather than the default position of inside, then
you could do the following.

aggregator->SetKeyLocation(GnuplotAggregator::KEY_BELOW);

Examples
One example will be discussed in detail here:

· Gnuplot Aggregator Example

Gnuplot Aggregator Example
An example that exercises the GnuplotAggregator can be found in
src/stats/examples/gnuplot-aggregator-example.cc.

The following 2-D gnuplot was created using the example.
[image] 2-D Gnuplot Created by gnuplot-aggregator-example.cc Example..UNINDENT

This code from the example shows how to construct the GnuplotAggregator as was discussed
above.

void Create2dPlot ()
{
using namespace std;

string fileNameWithoutExtension = "gnuplot-aggregator";
string plotTitle = "Gnuplot Aggregator Plot";
string plotXAxisHeading = "Time (seconds)";
string plotYAxisHeading = "Double Values";
string plotDatasetLabel = "Data Values";
string datasetContext = "Dataset/Context/String";

// Create an aggregator.
Ptr<GnuplotAggregator> aggregator =
CreateObject<GnuplotAggregator> (fileNameWithoutExtension);

Various GnuplotAggregator attributes are set including the 2-D dataset that will be
plotted.

// Set the aggregator's properties.
aggregator->SetTerminal ("png");
aggregator->SetTitle (plotTitle);
aggregator->SetLegend (plotXAxisHeading, plotYAxisHeading);

// Add a data set to the aggregator.
aggregator->Add2dDataset (datasetContext, plotDatasetLabel);

// aggregator must be turned on
aggregator->Enable ();

Next, the 2-D values are calculated, and each one is individually written to the
GnuplotAggregator using the Write2d() function.

double time;
double value;

// Create the 2-D dataset.
for (time = -5.0; time <= +5.0; time += 1.0)
{
// Calculate the 2-D curve
//
// 2
// value = time .
//
value = time * time;

// Add this point to the plot.
aggregator->Write2d (datasetContext, time, value);
}

// Disable logging of data for the aggregator.
aggregator->Disable ();
}

FileAggregator
The FileAggregator sends the values it receives to a file.

The FileAggregator can create 4 different types of files:

· Formatted

· Space separated (the default)

· Comma separated

· Tab separated

Formatted files use C-style format strings and the sprintf() function to print their
values in the file being written.

Creation
An object of type FileAggregator will be created here to show what needs to be done.

One declares a FileAggregator in dynamic memory by using the smart pointer class (Ptr<T>).
To create a FileAggregator in dynamic memory with smart pointers, one just needs to call
the ns-3 method CreateObject. The following code from
src/stats/examples/file-aggregator-example.cc shows how to do this:

string fileName = "file-aggregator-formatted-values.txt";

// Create an aggregator that will have formatted values.
Ptr<FileAggregator> aggregator =
CreateObject<FileAggregator> (fileName, FileAggregator::FORMATTED);

The first argument for the constructor, filename, is the name of the file to write; the
second argument, fileType, is type of file to write. This FileAggregator will create a
file named "file-aggregator-formatted-values.txt" with its values printed as specified by
fileType, i.e., formatted in this case.

The following file type enum values are allowed:

enum FileType {
FORMATTED,
SPACE_SEPARATED,
COMMA_SEPARATED,
TAB_SEPARATED
};

Examples
One example will be discussed in detail here:

· File Aggregator Example

File Aggregator Example
An example that exercises the FileAggregator can be found in
src/stats/examples/file-aggregator-example.cc.

The following text file with 2 columns of values separated by commas was created using the
example.

-5,25
-4,16
-3,9
-2,4
-1,1
0,0
1,1
2,4
3,9
4,16
5,25

This code from the example shows how to construct the FileAggregator as was discussed
above.

void CreateCommaSeparatedFile ()
{
using namespace std;

string fileName = "file-aggregator-comma-separated.txt";
string datasetContext = "Dataset/Context/String";

// Create an aggregator.
Ptr<FileAggregator> aggregator =
CreateObject<FileAggregator> (fileName, FileAggregator::COMMA_SEPARATED);

FileAggregator attributes are set.

// aggregator must be turned on
aggregator->Enable ();

Next, the 2-D values are calculated, and each one is individually written to the
FileAggregator using the Write2d() function.

double time;
double value;

// Create the 2-D dataset.
for (time = -5.0; time <= +5.0; time += 1.0)
{
// Calculate the 2-D curve
//
// 2
// value = time .
//
value = time * time;

// Add this point to the plot.
aggregator->Write2d (datasetContext, time, value);
}

// Disable logging of data for the aggregator.
aggregator->Disable ();
}

The following text file with 2 columns of formatted values was also created using the
example.

Time = -5.000e+00 Value = 25
Time = -4.000e+00 Value = 16
Time = -3.000e+00 Value = 9
Time = -2.000e+00 Value = 4
Time = -1.000e+00 Value = 1
Time = 0.000e+00 Value = 0
Time = 1.000e+00 Value = 1
Time = 2.000e+00 Value = 4
Time = 3.000e+00 Value = 9
Time = 4.000e+00 Value = 16
Time = 5.000e+00 Value = 25

This code from the example shows how to construct the FileAggregator as was discussed
above.

void CreateFormattedFile ()
{
using namespace std;

string fileName = "file-aggregator-formatted-values.txt";
string datasetContext = "Dataset/Context/String";

// Create an aggregator that will have formatted values.
Ptr<FileAggregator> aggregator =
CreateObject<FileAggregator> (fileName, FileAggregator::FORMATTED);

FileAggregator attributes are set, including the C-style format string to use.

// Set the format for the values.
aggregator->Set2dFormat ("Time = %.3e\tValue = %.0f");

// aggregator must be turned on
aggregator->Enable ();

Next, the 2-D values are calculated, and each one is individually written to the
FileAggregator using the Write2d() function.

double time;
double value;

// Create the 2-D dataset.
for (time = -5.0; time <= +5.0; time += 1.0)
{
// Calculate the 2-D curve
//
// 2
// value = time .
//
value = time * time;

// Add this point to the plot.
aggregator->Write2d (datasetContext, time, value);
}

// Disable logging of data for the aggregator.
aggregator->Disable ();
}

Adaptors
This section details the functionalities provided by the Adaptor class to an ns-3
simulation. This section is meant for users interested in developing simulations with the
ns-3 tools and using the Data Collection Framework, of which the Adaptor class is a part,
to generate data output with their simulation's results.

Note: the term 'adaptor' may also be spelled 'adapter'; we chose the spelling aligned
with the C++ standard.

Adaptor Overview
An Adaptor is used to make connections between different types of DCF objects.

To date, one Adaptor has been implemented:

· TimeSeriesAdaptor

Time Series Adaptor
The TimeSeriesAdaptor lets Probes connect directly to Aggregators without needing any
Collector in between.

Both of the implemented DCF helpers utilize TimeSeriesAdaptors in order to take probed
values of different types and output the current time plus the value with both converted
to doubles.

The role of the TimeSeriesAdaptor class is that of an adaptor, which takes raw-valued
probe data of different types and outputs a tuple of two double values. The first is a
timestamp, which may be set to different resolutions (e.g. Seconds, Milliseconds, etc.) in
the future but which is presently hardcoded to Seconds. The second is the conversion of a
non-double value to a double value (possibly with loss of precision).

Scope/Limitations
This section discusses the scope and limitations of the Data Collection Framework.

Currently, only these Probes have been implemented in DCF:

· BooleanProbe

· DoubleProbe

· Uinteger8Probe

· Uinteger16Probe

· Uinteger32Probe

· TimeProbe

· PacketProbe

· ApplicationPacketProbe

· Ipv4PacketProbe

Currently, no Collectors are available in the DCF, although a BasicStatsCollector is under
development.

Currently, only these Aggregators have been implemented in DCF:

· GnuplotAggregator

· FileAggregator

Currently, only this Adaptor has been implemented in DCF:

Time-Series Adaptor.

Future Work
This section discusses the future work to be done on the Data Collection Framework.

Here are some things that still need to be done:

· Hook up more trace sources in ns-3 code to get more values out of the simulator.

· Implement more types of Probes than there currently are.

· Implement more than just the single current 2-D Collector, BasicStatsCollector.

· Implement more Aggregators.

· Implement more than just Adaptors.

Statistical Framework
This chapter outlines work on simulation data collection and the statistical framework for
ns-3.

The source code for the statistical framework lives in the directory src/stats.

Goals
Primary objectives for this effort are the following:

· Provide functionality to record, calculate, and present data and statistics for analysis
of network simulations.

· Boost simulation performance by reducing the need to generate extensive trace logs in
order to collect data.

· Enable simulation control via online statistics, e.g. terminating simulations or
repeating trials.

Derived sub-goals and other target features include the following:

· Integration with the existing ns-3 tracing system as the basic instrumentation framework
of the internal simulation engine, e.g. network stacks, net devices, and channels.

· Enabling users to utilize the statistics framework without requiring use of the tracing
system.

· Helping users create, aggregate, and analyze data over multiple trials.

· Support for user created instrumentation, e.g. of application specific events and
measures.

· Low memory and CPU overhead when the package is not in use.

· Leveraging existing analysis and output tools as much as possible. The framework may
provide some basic statistics, but the focus is on collecting data and making it
accessible for manipulation in established tools.

· Eventual support for distributing independent replications is important but not included
in the first round of features.

Overview
The statistics framework includes the following features:

· The core framework and two basic data collectors: A counter, and a min/max/avg/total
observer.

· Extensions of those to easily work with times and packets.

· Plaintext output formatted for OMNet++.

· Database output using SQLite, a standalone, lightweight, high performance SQL engine.

· Mandatory and open ended metadata for describing and working with runs.

· An example based on the notional experiment of examining the properties of NS-3's
default ad hoc WiFi performance. It incorporates the following:

· Constructs of a two node ad hoc WiFi network, with the nodes a parameterized distance
apart.

· UDP traffic source and sink applications with slightly different behavior and
measurement hooks than the stock classes.

· Data collection from the NS-3 core via existing trace signals, in particular data on
frames transmitted and received by the WiFi MAC objects.

· Instrumentation of custom applications by connecting new trace signals to the stat
framework, as well as via direct updates. Information is recorded about total packets
sent and received, bytes transmitted, and end-to-end delay.

· An example of using packet tags to track end-to-end delay.

· A simple control script which runs a number of trials of the experiment at varying
distances and queries the resulting database to produce a graph using GNUPlot.

To-Do
High priority items include:

· Inclusion of online statistics code, e.g. for memory efficient confidence intervals.

· Provisions in the data collectors for terminating runs, i.e. when a threshold or
confidence is met.

· Data collectors for logging samples over time, and output to the various formats.

· Demonstrate writing simple cyclic event glue to regularly poll some value.

Each of those should prove straightforward to incorporate in the current framework.

Approach
The framework is based around the following core principles:

· One experiment trial is conducted by one instance of a simulation program, whether in
parallel or serially.

· A control script executes instances of the simulation, varying parameters as necessary.

· Data is collected and stored for plotting and analysis using external scripts and
existing tools.

· Measures within the ns-3 core are taken by connecting the stat framework to existing
trace signals.

· Trace signals or direct manipulation of the framework may be used to instrument custom
simulation code.

Those basic components of the framework and their interactions are depicted in the
following figure. [image]

Example
This section goes through the process of constructing an experiment in the framework and
producing data for analysis (graphs) from it, demonstrating the structure and API along
the way.

Question
''What is the (simulated) performance of ns-3's WiFi NetDevices (using the default
settings)? How far apart can wireless nodes be in a simulation before they cannot
communicate reliably?''

· Hypothesis: Based on knowledge of real life performance, the nodes should communicate
reasonably well to at least 100m apart. Communication beyond 200m shouldn't be
feasible.

Although not a very common question in simulation contexts, this is an important property
of which simulation developers should have a basic understanding. It is also a common
study done on live hardware.

Simulation Program
The first thing to do in implementing this experiment is developing the simulation
program. The code for this example can be found in examples/stats/wifi-example-sim.cc.
It does the following main steps.

· Declaring parameters and parsing the command line using ns3::CommandLine.

double distance = 50.0;
string format ("OMNet++");
string experiment ("wifi-distance-test");
string strategy ("wifi-default");
string runID;

CommandLine cmd;
cmd.AddValue("distance", "Distance apart to place nodes (in meters).", distance);
cmd.AddValue("format", "Format to use for data output.", format);
cmd.AddValue("experiment", "Identifier for experiment.", experiment);
cmd.AddValue("strategy", "Identifier for strategy.", strategy);
cmd.AddValue("run", "Identifier for run.", runID);
cmd.Parse (argc, argv);

· Creating nodes and network stacks using ns3::NodeContainer, ns3::WiFiHelper, and
ns3::InternetStackHelper.

NodeContainer nodes;
nodes.Create(2);

WifiHelper wifi;
wifi.SetMac("ns3::AdhocWifiMac");
wifi.SetPhy("ns3::WifiPhy");
NetDeviceContainer nodeDevices = wifi.Install(nodes);

InternetStackHelper internet;
internet.Install(nodes);
Ipv4AddressHelper ipAddrs;
ipAddrs.SetBase("192.168.0.0", "255.255.255.0");
ipAddrs.Assign(nodeDevices);

· Positioning the nodes using ns3::MobilityHelper. By default the nodes have static
mobility and won't move, but must be positioned the given distance apart. There are
several ways to do this; it is done here using ns3::ListPositionAllocator, which draws
positions from a given list.

MobilityHelper mobility;
Ptr<ListPositionAllocator> positionAlloc =
CreateObject<ListPositionAllocator>();
positionAlloc->Add(Vector(0.0, 0.0, 0.0));
positionAlloc->Add(Vector(0.0, distance, 0.0));
mobility.SetPositionAllocator(positionAlloc);
mobility.Install(nodes);

· Installing a traffic generator and a traffic sink. The stock Applications could be
used, but the example includes custom objects in src/test/test02-apps.(cc|h). These
have a simple behavior, generating a given number of packets spaced at a given interval.
As there is only one of each they are installed manually; for a larger set the
ns3::ApplicationHelper class could be used. The commented-out Config::Set line changes
the destination of the packets, set to broadcast by default in this example. Note that
in general WiFi may have different performance for broadcast and unicast frames due to
different rate control and MAC retransmission policies.

Ptr<Node> appSource = NodeList::GetNode(0);
Ptr<Sender> sender = CreateObject<Sender>();
appSource->AddApplication(sender);
sender->Start(Seconds(1));

Ptr<Node> appSink = NodeList::GetNode(1);
Ptr<Receiver> receiver = CreateObject<Receiver>();
appSink->AddApplication(receiver);
receiver->Start(Seconds(0));

// Config::Set("/NodeList/*/ApplicationList/*/$Sender/Destination",
// Ipv4AddressValue("192.168.0.2"));

· Configuring the data and statistics to be collected. The basic paradigm is that an
ns3::DataCollector object is created to hold information about this particular run, to
which observers and calculators are attached to actually generate data. Importantly,
run information includes labels for the ''experiment'', ''strategy'', ''input'', and
''run''. These are used to later identify and easily group data from multiple trials.

· The experiment is the study of which this trial is a member. Here it is on WiFi
performance and distance.

· The strategy is the code or parameters being examined in this trial. In this example
it is fixed, but an obvious extension would be to investigate different WiFi bit
rates, each of which would be a different strategy.

· The input is the particular problem given to this trial. Here it is simply the
distance between the two nodes.

· The runID is a unique identifier for this trial with which it's information is tagged
for identification in later analysis. If no run ID is given the example program makes
a (weak) run ID using the current time.

Those four pieces of metadata are required, but more may be desired. They may be added
to the record using the ns3::DataCollector::AddMetadata() method.

DataCollector data;
data.DescribeRun(experiment, strategy, input, runID);
data.AddMetadata("author", "tjkopena");

Actual observation and calculating is done by ns3::DataCalculator objects, of which
several different types exist. These are created by the simulation program, attached to
reporting or sampling code, and then registered with the ns3::DataCollector so they will
be queried later for their output. One easy observation mechanism is to use existing
trace sources, for example to instrument objects in the ns-3 core without changing their
code. Here a counter is attached directly to a trace signal in the WiFi MAC layer on
the target node.

Ptr<PacketCounterCalculator> totalRx = CreateObject<PacketCounterCalculator>();
totalRx->SetKey("wifi-rx-frames");
Config::Connect("/NodeList/1/DeviceList/*/$ns3::WifiNetDevice/Rx",
MakeCallback(&PacketCounterCalculator::FrameUpdate, totalRx));
data.AddDataCalculator(totalRx);

Calculators may also be manipulated directly. In this example, a counter is created and
passed to the traffic sink application to be updated when packets are received.

Ptr<CounterCalculator<> > appRx = CreateObject<CounterCalculator<> >();
appRx->SetKey("receiver-rx-packets");
receiver->SetCounter(appRx);
data.AddDataCalculator(appRx);

To increment the count, the sink's packet processing code then calls one of the
calculator's update methods.

m_calc->Update();

The program includes several other examples as well, using both the primitive
calculators such as ns3::CounterCalculator and those adapted for observing packets and
times. In src/test/test02-apps.(cc|h) it also creates a simple custom tag which it uses
to track end-to-end delay for generated packets, reporting results to a
ns3::TimeMinMaxAvgTotalCalculator data calculator.

· Running the simulation, which is very straightforward once constructed.

Simulator::Run();

· Generating either OMNet++ or SQLite output, depending on the command line arguments. To
do this a ns3::DataOutputInterface object is created and configured. The specific type
of this will determine the output format. This object is then given the
ns3::DataCollector object which it interrogates to produce the output.

Ptr<DataOutputInterface> output;
if (format == "OMNet++") {
NS_LOG_INFO("Creating OMNet++ formatted data output.");
output = CreateObject<OmnetDataOutput>();
} else {
# ifdef STAT_USE_DB
NS_LOG_INFO("Creating SQLite formatted data output.");
output = CreateObject<SqliteDataOutput>();
# endif
}

output->Output(data);

· Freeing any memory used by the simulation. This should come at the end of the main
function for the example.

Simulator::Destroy();

Logging
To see what the example program, applications, and stat framework are doing in detail, set
the NS_LOG variable appropriately. The following will provide copious output from all
three.

$ export NS_LOG=WiFiDistanceExperiment:WiFiDistanceApps

Note that this slows down the simulation extraordinarily.

Sample Output
Compiling and simply running the test program will append OMNet++ formatted output such as
the following to data.sca.

run run-1212239121

attr experiment "wifi-distance-test"
attr strategy "wifi-default"
attr input "50"
attr description ""

attr "author" "tjkopena"

scalar wifi-tx-frames count 30
scalar wifi-rx-frames count 30
scalar sender-tx-packets count 30
scalar receiver-rx-packets count 30
scalar tx-pkt-size count 30
scalar tx-pkt-size total 1920
scalar tx-pkt-size average 64
scalar tx-pkt-size max 64
scalar tx-pkt-size min 64
scalar delay count 30
scalar delay total 5884980ns
scalar delay average 196166ns
scalar delay max 196166ns
scalar delay min 196166ns

Control Script
In order to automate data collection at a variety of inputs (distances), a simple Bash
script is used to execute a series of simulations. It can be found at
examples/stats/wifi-example-db.sh. The script is meant to be run from the examples/stats/
directory.

The script runs through a set of distances, collecting the results into an SQLite
database. At each distance five trials are conducted to give a better picture of expected
performance. The entire experiment takes only a few dozen seconds to run on a low end
machine as there is no output during the simulation and little traffic is generated.

#!/bin/sh

DISTANCES="25 50 75 100 125 145 147 150 152 155 157 160 162 165 167 170 172 175 177 180"
TRIALS="1 2 3 4 5"

echo WiFi Experiment Example

if [ -e data.db ]
then
echo Kill data.db?
read ANS
if [ "$ANS" = "yes" -o "$ANS" = "y" ]
then
echo Deleting database
rm data.db
fi
fi

for trial in $TRIALS
do
for distance in $DISTANCES
do
echo Trial $trial, distance $distance
./bin/test02 --format=db --distance=$distance --run=run-$distance-$trial
done
done

Analysis and Conclusion
Once all trials have been conducted, the script executes a simple SQL query over the
database using the SQLite command line program. The query computes average packet loss in
each set of trials associated with each distance. It does not take into account different
strategies, but the information is present in the database to make some simple extensions
and do so. The collected data is then passed to GNUPlot for graphing.

CMD="select exp.input,avg(100-((rx.value*100)/tx.value)) \
from Singletons rx, Singletons tx, Experiments exp \
where rx.run = tx.run AND \
rx.run = exp.run AND \
rx.name='receiver-rx-packets' AND \
tx.name='sender-tx-packets' \
group by exp.input \
order by abs(exp.input) ASC;"

sqlite3 -noheader data.db "$CMD" > wifi-default.data
sed -i "s/|/ /" wifi-default.data
gnuplot wifi-example.gnuplot

The GNUPlot script found at examples/stats/wifi-example.gnuplot simply defines the output
format and some basic formatting for the graph.

set terminal postscript portrait enhanced lw 2 "Helvetica" 14

set size 1.0, 0.66

#-------------------------------------------------------
set out "wifi-default.eps"
#set title "Packet Loss Over Distance"
set xlabel "Distance (m) --- average of 5 trials per point"
set xrange [0:200]
set ylabel "% Packet Loss"
set yrange [0:110]

plot "wifi-default.data" with lines title "WiFi Defaults"

End Result
The resulting graph provides no evidence that the default WiFi model's performance is
necessarily unreasonable and lends some confidence to an at least token faithfulness to
reality. More importantly, this simple investigation has been carried all the way through
using the statistical framework. Success! [image]

RealTime
ns-3 has been designed for integration into testbed and virtual machine environments. To
integrate with real network stacks and emit/consume packets, a real-time scheduler is
needed to try to lock the simulation clock with the hardware clock. We describe here a
component of this: the RealTime scheduler.

The purpose of the realtime scheduler is to cause the progression of the simulation clock
to occur synchronously with respect to some external time base. Without the presence of
an external time base (wall clock), simulation time jumps instantly from one simulated
time to the next.

Behavior
When using a non-realtime scheduler (the default in ns-3), the simulator advances the
simulation time to the next scheduled event. During event execution, simulation time is
frozen. With the realtime scheduler, the behavior is similar from the perspective of
simulation models (i.e., simulation time is frozen during event execution), but between
events, the simulator will attempt to keep the simulation clock aligned with the machine
clock.

When an event is finished executing, and the scheduler moves to the next event, the
scheduler compares the next event execution time with the machine clock. If the next
event is scheduled for a future time, the simulator sleeps until that realtime is reached
and then executes the next event.

It may happen that, due to the processing inherent in the execution of simulation events,
that the simulator cannot keep up with realtime. In such a case, it is up to the user
configuration what to do. There are two ns-3 attributes that govern the behavior. The
first is ns3::RealTimeSimulatorImpl::SynchronizationMode. The two entries possible for
this attribute are BestEffort (the default) or HardLimit. In "BestEffort" mode, the
simulator will just try to catch up to realtime by executing events until it reaches a
point where the next event is in the (realtime) future, or else the simulation ends. In
BestEffort mode, then, it is possible for the simulation to consume more time than the
wall clock time. The other option "HardLimit" will cause the simulation to abort if the
tolerance threshold is exceeded. This attribute is ns3::RealTimeSimulatorImpl::HardLimit
and the default is 0.1 seconds.

A different mode of operation is one in which simulated time is not frozen during an event
execution. This mode of realtime simulation was implemented but removed from the ns-3 tree
because of questions of whether it would be useful. If users are interested in a realtime
simulator for which simulation time does not freeze during event execution (i.e., every
call to Simulator::Now() returns the current wall clock time, not the time at which the
event started executing), please contact the ns-developers mailing list.

Usage
The usage of the realtime simulator is straightforward, from a scripting perspective.
Users just need to set the attribute SimulatorImplementationType to the Realtime
simulator, such as follows:

GlobalValue::Bind ("SimulatorImplementationType",
StringValue ("ns3::RealtimeSimulatorImpl"));

There is a script in examples/realtime/realtime-udp-echo.cc that has an example of how to
configure the realtime behavior. Try:

$ ./waf --run realtime-udp-echo

Whether the simulator will work in a best effort or hard limit policy fashion is governed
by the attributes explained in the previous section.

Implementation
The implementation is contained in the following files:

· src/core/model/realtime-simulator-impl.{cc,h}

· src/core/model/wall-clock-synchronizer.{cc,h}

In order to create a realtime scheduler, to a first approximation you just want to cause
simulation time jumps to consume real time. We propose doing this using a combination of
sleep- and busy- waits. Sleep-waits cause the calling process (thread) to yield the
processor for some amount of time. Even though this specified amount of time can be passed
to nanosecond resolution, it is actually converted to an OS-specific granularity. In
Linux, the granularity is called a Jiffy. Typically this resolution is insufficient for
our needs (on the order of a ten milliseconds), so we round down and sleep for some
smaller number of Jiffies. The process is then awakened after the specified number of
Jiffies has passed. At this time, we have some residual time to wait. This time is
generally smaller than the minimum sleep time, so we busy-wait for the remainder of the
time. This means that the thread just sits in a for loop consuming cycles until the
desired time arrives. After the combination of sleep- and busy-waits, the elapsed realtime
(wall) clock should agree with the simulation time of the next event and the simulation
proceeds.

Helpers
The above chapters introduced you to various ns-3 programming concepts such as smart
pointers for reference-counted memory management, attributes, namespaces, callbacks, etc.
Users who work at this low-level API can interconnect ns-3 objects with fine granulariy.
However, a simulation program written entirely using the low-level API would be quite long
and tedious to code. For this reason, a separate so-called "helper API" has been overlaid
on the core ns-3 API. If you have read the ns-3 tutorial, you will already be familiar
with the helper API, since it is the API that new users are typically introduced to first.
In this chapter, we introduce the design philosophy of the helper API and contrast it to
the low-level API. If you become a heavy user of ns-3, you will likely move back and forth
between these APIs even in the same program.

The helper API has a few goals:

1. the rest of src/ has no dependencies on the helper API; anything that can be done with
the helper API can be coded also at the low-level API

2. Containers: Often simulations will need to do a number of identical actions to groups
of objects. The helper API makes heavy use of containers of similar objects to which
similar or identical operations can be performed.

3. The helper API is not generic; it does not strive to maximize code reuse. So,
programming constructs such as polymorphism and templates that achieve code reuse are
not as prevalent. For instance, there are separate CsmaNetDevice helpers and
PointToPointNetDevice helpers but they do not derive from a common NetDevice base
class.

4. The helper API typically works with stack-allocated (vs. heap-allocated) objects. For
some programs, ns-3 users may not need to worry about any low level Object Create or
Ptr handling; they can make do with containers of objects and stack-allocated helpers
that operate on them.

The helper API is really all about making ns-3 programs easier to write and read, without
taking away the power of the low-level interface. The rest of this chapter provides some
examples of the programming conventions of the helper API.

Making Plots using the Gnuplot Class
There are 2 common methods to make a plot using ns-3 and gnuplot (‐
http://www.gnuplot.info):

1. Create a gnuplot control file using ns-3's Gnuplot class.

2. Create a gnuplot data file using values generated by ns-3.

This section is about method 1, i.e. it is about how to make a plot using ns-3's Gnuplot
class. If you are interested in method 2, see the "A Real Example" subsection under the
"Tracing" section in the ns-3 Tutorial.

Creating Plots Using the Gnuplot Class
The following steps must be taken in order to create a plot using ns-3's Gnuplot class:

1. Modify your code so that is uses the Gnuplot class and its functions.

2. Run your code so that it creates a gnuplot control file.

3. Call gnuplot with the name of the gnuplot control file.

4. View the graphics file that was produced in your favorite graphics viewer.

See the code from the example plots that are discussed below for details on step 1.

An Example Program that Uses the Gnuplot Class
An example program that uses ns-3's Gnuplot class can be found here:

src/stats/examples/gnuplot-example.cc

In order to run this example, do the following:

$ ./waf shell
$ cd build/debug/src/stats/examples
$ ./gnuplot-example

This should produce the following gnuplot control files in the directory where the example
is located:

plot-2d.plt
plot-2d-with-error-bars.plt
plot-3d.plt

In order to process these gnuplot control files, do the following:

$ gnuplot plot-2d.plt
$ gnuplot plot-2d-with-error-bars.plt
$ gnuplot plot-3d.plt

This should produce the following graphics files in the directory where the example is
located:

plot-2d.png
plot-2d-with-error-bars.png
plot-3d.png

You can view these graphics files in your favorite graphics viewer. If you have gimp
installed on your machine, for example, you can do this:

$ gimp plot-2d.png
$ gimp plot-2d-with-error-bars.png
$ gimp plot-3d.png

An Example 2-Dimensional Plot
The following 2-Dimensional plot
[image]

was created using the following code from gnuplot-example.cc:

using namespace std;

string fileNameWithNoExtension = "plot-2d";
string graphicsFileName = fileNameWithNoExtension + ".png";
string plotFileName = fileNameWithNoExtension + ".plt";
string plotTitle = "2-D Plot";
string dataTitle = "2-D Data";

// Instantiate the plot and set its title.
Gnuplot plot (graphicsFileName);
plot.SetTitle (plotTitle);

// Make the graphics file, which the plot file will create when it
// is used with Gnuplot, be a PNG file.
plot.SetTerminal ("png");

// Set the labels for each axis.
plot.SetLegend ("X Values", "Y Values");

// Set the range for the x axis.
plot.AppendExtra ("set xrange [-6:+6]");

// Instantiate the dataset, set its title, and make the points be
// plotted along with connecting lines.
Gnuplot2dDataset dataset;
dataset.SetTitle (dataTitle);
dataset.SetStyle (Gnuplot2dDataset::LINES_POINTS);

double x;
double y;

// Create the 2-D dataset.
for (x = -5.0; x <= +5.0; x += 1.0)
{
// Calculate the 2-D curve
//
// 2
// y = x .
//
y = x * x;

// Add this point.
dataset.Add (x, y);
}

// Add the dataset to the plot.
plot.AddDataset (dataset);

// Open the plot file.
ofstream plotFile (plotFileName.c_str());

// Write the plot file.
plot.GenerateOutput (plotFile);

// Close the plot file.
plotFile.close ();

An Example 2-Dimensional Plot with Error Bars
The following 2-Dimensional plot with error bars in the x and y directions
[image]

was created using the following code from gnuplot-example.cc:

using namespace std;

string fileNameWithNoExtension = "plot-2d-with-error-bars";
string graphicsFileName = fileNameWithNoExtension + ".png";
string plotFileName = fileNameWithNoExtension + ".plt";
string plotTitle = "2-D Plot With Error Bars";
string dataTitle = "2-D Data With Error Bars";

// Instantiate the plot and set its title.
Gnuplot plot (graphicsFileName);
plot.SetTitle (plotTitle);

// Make the graphics file, which the plot file will create when it
// is used with Gnuplot, be a PNG file.
plot.SetTerminal ("png");

// Set the labels for each axis.
plot.SetLegend ("X Values", "Y Values");

// Set the range for the x axis.
plot.AppendExtra ("set xrange [-6:+6]");

// Instantiate the dataset, set its title, and make the points be
// plotted with no connecting lines.
Gnuplot2dDataset dataset;
dataset.SetTitle (dataTitle);
dataset.SetStyle (Gnuplot2dDataset::POINTS);

// Make the dataset have error bars in both the x and y directions.
dataset.SetErrorBars (Gnuplot2dDataset::XY);

double x;
double xErrorDelta;
double y;
double yErrorDelta;

// Create the 2-D dataset.
for (x = -5.0; x <= +5.0; x += 1.0)
{
// Calculate the 2-D curve
//
// 2
// y = x .
//
y = x * x;

// Make the uncertainty in the x direction be constant and make
// the uncertainty in the y direction be a constant fraction of
// y's value.
xErrorDelta = 0.25;
yErrorDelta = 0.1 * y;

// Add this point with uncertainties in both the x and y
// direction.
dataset.Add (x, y, xErrorDelta, yErrorDelta);
}

// Add the dataset to the plot.
plot.AddDataset (dataset);

// Open the plot file.
ofstream plotFile (plotFileName.c_str());

// Write the plot file.
plot.GenerateOutput (plotFile);

// Close the plot file.
plotFile.close ();

An Example 3-Dimensional Plot
The following 3-Dimensional plot
[image]

was created using the following code from gnuplot-example.cc:

using namespace std;

string fileNameWithNoExtension = "plot-3d";
string graphicsFileName = fileNameWithNoExtension + ".png";
string plotFileName = fileNameWithNoExtension + ".plt";
string plotTitle = "3-D Plot";
string dataTitle = "3-D Data";

// Instantiate the plot and set its title.
Gnuplot plot (graphicsFileName);
plot.SetTitle (plotTitle);

// Make the graphics file, which the plot file will create when it
// is used with Gnuplot, be a PNG file.
plot.SetTerminal ("png");

// Rotate the plot 30 degrees around the x axis and then rotate the
// plot 120 degrees around the new z axis.
plot.AppendExtra ("set view 30, 120, 1.0, 1.0");

// Make the zero for the z-axis be in the x-axis and y-axis plane.
plot.AppendExtra ("set ticslevel 0");

// Set the labels for each axis.
plot.AppendExtra ("set xlabel 'X Values'");
plot.AppendExtra ("set ylabel 'Y Values'");
plot.AppendExtra ("set zlabel 'Z Values'");

// Set the ranges for the x and y axis.
plot.AppendExtra ("set xrange [-5:+5]");
plot.AppendExtra ("set yrange [-5:+5]");

// Instantiate the dataset, set its title, and make the points be
// connected by lines.
Gnuplot3dDataset dataset;
dataset.SetTitle (dataTitle);
dataset.SetStyle ("with lines");

double x;
double y;
double z;

// Create the 3-D dataset.
for (x = -5.0; x <= +5.0; x += 1.0)
{
for (y = -5.0; y <= +5.0; y += 1.0)
{
// Calculate the 3-D surface
//
// 2 2
// z = x * y .
//
z = x * x * y * y;

// Add this point.
dataset.Add (x, y, z);
}

// The blank line is necessary at the end of each x value's data
// points for the 3-D surface grid to work.
dataset.AddEmptyLine ();
}

// Add the dataset to the plot.
plot.AddDataset (dataset);

// Open the plot file.
ofstream plotFile (plotFileName.c_str());

// Write the plot file.
plot.GenerateOutput (plotFile);

// Close the plot file.
plotFile.close ();

Using Python to Run ns-3
Python bindings allow the C++ code in ns-3 to be called from Python.

This chapter shows you how to create a Python script that can run ns-3 and also the
process of creating Python bindings for a C++ ns-3 module.

Introduction
The goal of Python bindings for ns-3 are two fold:

1. Allow the programmer to write complete simulation scripts in Python (‐
http://www.python.org);

2. Prototype new models (e.g. routing protocols).

For the time being, the primary focus of the bindings is the first goal, but the second
goal will eventually be supported as well. Python bindings for ns-3 are being developed
using a new tool called PyBindGen (http://code.google.com/p/pybindgen).

An Example Python Script that Runs ns-3
Here is some example code that is written in Python and that runs ns-3, which is written
in C++. This Python example can be found in examples/tutorial/first.py:

import ns.applications
import ns.core
import ns.internet
import ns.network
import ns.point_to_point

ns.core.LogComponentEnable("UdpEchoClientApplication", ns.core.LOG_LEVEL_INFO)
ns.core.LogComponentEnable("UdpEchoServerApplication", ns.core.LOG_LEVEL_INFO)

nodes = ns.network.NodeContainer()
nodes.Create(2)

pointToPoint = ns.point_to_point.PointToPointHelper()
pointToPoint.SetDeviceAttribute("DataRate", ns.core.StringValue("5Mbps"))
pointToPoint.SetChannelAttribute("Delay", ns.core.StringValue("2ms"))

devices = pointToPoint.Install(nodes)

stack = ns.internet.InternetStackHelper()
stack.Install(nodes)

address = ns.internet.Ipv4AddressHelper()
address.SetBase(ns.network.Ipv4Address("10.1.1.0"), ns.network.Ipv4Mask("255.255.255.0"))

interfaces = address.Assign (devices);

echoServer = ns.applications.UdpEchoServerHelper(9)

serverApps = echoServer.Install(nodes.Get(1))
serverApps.Start(ns.core.Seconds(1.0))
serverApps.Stop(ns.core.Seconds(10.0))

echoClient = ns.applications.UdpEchoClientHelper(interfaces.GetAddress(1), 9)
echoClient.SetAttribute("MaxPackets", ns.core.UintegerValue(1))
echoClient.SetAttribute("Interval", ns.core.TimeValue(ns.core.Seconds (1.0)))
echoClient.SetAttribute("PacketSize", ns.core.UintegerValue(1024))

clientApps = echoClient.Install(nodes.Get(0))
clientApps.Start(ns.core.Seconds(2.0))
clientApps.Stop(ns.core.Seconds(10.0))

ns.core.Simulator.Run()
ns.core.Simulator.Destroy()

Running Python Scripts
waf contains some options that automatically update the python path to find the ns3
module. To run example programs, there are two ways to use waf to take care of this. One
is to run a waf shell; e.g.:

$ ./waf --shell
$ python examples/wireless/mixed-wireless.py

and the other is to use the --pyrun option to waf:

$ ./waf --pyrun examples/wireless/mixed-wireless.py

To run a python script under the C debugger:

$ ./waf --shell
$ gdb --args python examples/wireless/mixed-wireless.py

To run your own Python script that calls ns-3 and that has this path,
/path/to/your/example/my-script.py, do the following:

$ ./waf --shell
$ python /path/to/your/example/my-script.py

Caveats
Python bindings for ns-3 are a work in progress, and some limitations are known by
developers. Some of these limitations (not all) are listed here.

Incomplete Coverage
First of all, keep in mind that not 100% of the API is supported in Python. Some of the
reasons are:

1. some of the APIs involve pointers, which require knowledge of what kind of memory
passing semantics (who owns what memory). Such knowledge is not part of the function
signatures, and is either documented or sometimes not even documented. Annotations are
needed to bind those functions;

2. Sometimes a unusual fundamental data type or C++ construct is used which is not yet
supported by PyBindGen;

3. GCC-XML does not report template based classes unless they are instantiated.

Most of the missing APIs can be wrapped, given enough time, patience, and expertise, and
will likely be wrapped if bug reports are submitted. However, don't file a bug report
saying "bindings are incomplete", because we do not have manpower to complete 100% of the
bindings.

Conversion Constructors
Conversion constructors are not fully supported yet by PyBindGen, and they always act as
explicit constructors when translating an API into Python. For example, in C++ you can do
this:

Ipv4AddressHelper ipAddrs;
ipAddrs.SetBase ("192.168.0.0", "255.255.255.0");
ipAddrs.Assign (backboneDevices);

In Python, for the time being you have to do:

ipAddrs = ns3.Ipv4AddressHelper()
ipAddrs.SetBase(ns3.Ipv4Address("192.168.0.0"), ns3.Ipv4Mask("255.255.255.0"))
ipAddrs.Assign(backboneDevices)

CommandLine
CommandLine::AddValue() works differently in Python than it does in ns-3. In Python, the
first parameter is a string that represents the command-line option name. When the option
is set, an attribute with the same name as the option name is set on the CommandLine()
object. Example:

NUM_NODES_SIDE_DEFAULT = 3

cmd = ns3.CommandLine()

cmd.NumNodesSide = None
cmd.AddValue("NumNodesSide", "Grid side number of nodes (total number of nodes will be this number squared)")

cmd.Parse(argv)

[...]

if cmd.NumNodesSide is None:
num_nodes_side = NUM_NODES_SIDE_DEFAULT
else:
num_nodes_side = int(cmd.NumNodesSide)

Tracing
Callback based tracing is not yet properly supported for Python, as new ns-3 API needs to
be provided for this to be supported.

Pcap file writing is supported via the normal API.

Ascii tracing is supported since ns-3.4 via the normal C++ API translated to Python.
However, ascii tracing requires the creation of an ostream object to pass into the ascii
tracing methods. In Python, the C++ std::ofstream has been minimally wrapped to allow
this. For example:

ascii = ns3.ofstream("wifi-ap.tr") # create the file
ns3.YansWifiPhyHelper.EnableAsciiAll(ascii)
ns3.Simulator.Run()
ns3.Simulator.Destroy()
ascii.close() # close the file

There is one caveat: you must not allow the file object to be garbage collected while ns-3
is still using it. That means that the 'ascii' variable above must not be allowed to go
out of scope or else the program will crash.

Cygwin limitation
Python bindings do not work on Cygwin. This is due to a gccxml bug.

You might get away with it by re-scanning API definitions from within the cygwin
environment (./waf --python-scan). However the most likely solution will probably have to
be that we disable python bindings in CygWin.

If you really care about Python bindings on Windows, try building with mingw and native
python instead. Or else, to build without python bindings, disable python bindings in the
configuration stage:

$ ./waf configure --disable-python

Working with Python Bindings
There are currently two kinds of Python bindings in ns-3:

1. Monolithic bindings contain API definitions for all of the modules and can be found in
a single directory, bindings/python.

2. Modular bindings contain API definitions for a single module and can be found in each
module's bindings directory.

Python Bindings Workflow
The process by which Python bindings are handled is the following:

1. Periodically a developer uses a GCC-XML (http://www.gccxml.org) based API scanning
script, which saves the scanned API definition as bindings/python/ns3_module_*.py files
or as Python files in each modules' bindings directory. These files are kept under
version control in the main ns-3 repository;

2. Other developers clone the repository and use the already scanned API definitions;

3. When configuring ns-3, pybindgen will be automatically downloaded if not already
installed. Released ns-3 tarballs will ship a copy of pybindgen.

If something goes wrong with compiling Python bindings and you just want to ignore them
and move on with C++, you can disable Python with:

$ ./waf --disable-python

Instructions for Handling New Files or Changed API's
So you have been changing existing ns-3 APIs and Python bindings no longer compile? Do
not despair, you can rescan the bindings to create new bindings that reflect the changes
to the ns-3 API.

Depending on if you are using monolithic or modular bindings, see the discussions below to
learn how to rescan your Python bindings.

Monolithic Python Bindings
Scanning the Monolithic Python Bindings
To scan the monolithic Python bindings do the following:

$ ./waf --python-scan

Organization of the Monolithic Python Bindings
The monolithic Python API definitions are organized as follows. For each ns-3 module
<name>, the file bindings/python/ns3_module_<name>.py describes its API. Each of those
files have 3 toplevel functions:

1. def register_types(module)(): this function takes care of registering new types (e.g.
C++ classes, enums) that are defined in tha module;

2. def register_methods(module)(): this function calls, for each class <name>, another
function register_methods_Ns3<name>(module). These latter functions add method
definitions for each class;

3. def register_functions(module)(): this function registers ns-3 functions that belong to
that module.

Modular Python Bindings
Overview
Since ns 3.11, the modular bindings are being added, in parallel to the old monolithic
bindings.

The new python bindings are generated into an 'ns' namespace, instead of 'ns3' for the old
bindings. Example:

from ns.network import Node
n1 = Node()

With modular Python bindings:

1. There is one separate Python extension module for each ns-3 module;

2. Scanning API definitions (apidefs) is done on a per ns- module basis;

3. Each module's apidefs files are stored in a 'bindings' subdirectory of the module
directory;

Scanning the Modular Python Bindings
To scan the modular Python bindings for the core module, for example, do the following:

$ ./waf --apiscan=core

To scan the modular Python bindings for all of the modules, do the following:

$ ./waf --apiscan=all

Creating a New Module
If you are adding a new module, Python bindings will continue to compile but will not
cover the new module.

To cover a new module, you have to create a bindings/python/ns3_module_<name>.py file,
similar to the what is described in the previous sections, and register it in the variable
LOCAL_MODULES() in bindings/python/ns3modulegen.py

Adding Modular Bindings To A Existing Module
To add support for modular bindings to an existing ns-3 module, simply add the following
line to its wscript build() function:

bld.ns3_python_bindings()

Organization of the Modular Python Bindings
The src/<module>/bindings directory may contain the following files, some of them
optional:

· callbacks_list.py: this is a scanned file, DO NOT TOUCH. Contains a list of
Callback<...> template instances found in the scanned headers;

· modulegen__gcc_LP64.py: this is a scanned file, DO NOT TOUCH. Scanned API definitions
for the GCC, LP64 architecture (64-bit)

· modulegen__gcc_ILP32.py: this is a scanned file, DO NOT TOUCH. Scanned API definitions
for the GCC, ILP32 architecture (32-bit)

· modulegen_customizations.py: you may optionally add this file in order to customize the
pybindgen code generation

· scan-header.h: you may optionally add this file to customize what header file is scanned
for the module. Basically this file is scanned instead of ns3/<module>-module.h.
Typically, the first statement is #include "ns3/<module>-module.h", plus some other
stuff to force template instantiations;

· module_helpers.cc: you may add additional files, such as this, to be linked to python
extension module, but they have to be registered in the wscript. Look at
src/core/wscript for an example of how to do so;

· <module>.py: if this file exists, it becomes the "frontend" python module for the ns3
module, and the extension module (.so file) becomes _<module>.so instead of <module>.so.
The <module>.py file has to import all symbols from the module _<module> (this is more
tricky than it sounds, see src/core/bindings/core.py for an example), and then can add
some additional pure-python definitions.

More Information for Developers
If you are a developer and need more information on ns-3's Python bindings, please see the
Python Bindings wiki page.

Tests
Overview
This document is concerned with the testing and validation of ns-3 software.

This document provides

· background about terminology and software testing (Chapter 2);

· a description of the ns-3 testing framework (Chapter 3);

· a guide to model developers or new model contributors for how to write tests (Chapter
4);

In brief, the first three chapters should be read by ns developers and contributors who
need to understand how to contribute test code and validated programs, and the remainder
of the document provides space for people to report on what aspects of selected models
have been validated.

Background
This chapter may be skipped by readers familiar with the basics of software testing.

Writing defect-free software is a difficult proposition. There are many dimensions to the
problem and there is much confusion regarding what is meant by different terms in
different contexts. We have found it worthwhile to spend a little time reviewing the
subject and defining some terms.

Software testing may be loosely defined as the process of executing a program with the
intent of finding errors. When one enters a discussion regarding software testing, it
quickly becomes apparent that there are many distinct mind-sets with which one can
approach the subject.

For example, one could break the process into broad functional categories like
''correctness testing,'' ''performance testing,'' ''robustness testing'' and ''security
testing.'' Another way to look at the problem is by life-cycle: ''requirements testing,''
''design testing,'' ''acceptance testing,'' and ''maintenance testing.'' Yet another view
is by the scope of the tested system. In this case one may speak of ''unit testing,''
''component testing,'' ''integration testing,'' and ''system testing.'' These terms are
also not standardized in any way, and so ''maintenance testing'' and ''regression
testing'' may be heard interchangeably. Additionally, these terms are often misused.

There are also a number of different philosophical approaches to software testing. For
example, some organizations advocate writing test programs before actually implementing
the desired software, yielding ''test-driven development.'' Some organizations advocate
testing from a customer perspective as soon as possible, following a parallel with the
agile development process: ''test early and test often.'' This is sometimes called
''agile testing.'' It seems that there is at least one approach to testing for every
development methodology.

The ns-3 project is not in the business of advocating for any one of these processes, but
the project as a whole has requirements that help inform the test process.

Like all major software products, ns-3 has a number of qualities that must be present for
the product to succeed. From a testing perspective, some of these qualities that must be
addressed are that ns-3 must be ''correct,'' ''robust,'' ''performant'' and
''maintainable.'' Ideally there should be metrics for each of these dimensions that are
checked by the tests to identify when the product fails to meet its expectations /
requirements.

Correctness
The essential purpose of testing is to determine that a piece of software behaves
''correctly.'' For ns-3 this means that if we simulate something, the simulation should
faithfully represent some physical entity or process to a specified accuracy and
precision.

It turns out that there are two perspectives from which one can view correctness.
Verifying that a particular model is implemented according to its specification is
generically called verification. The process of deciding that the model is correct for
its intended use is generically called validation.

Validation and Verification
A computer model is a mathematical or logical representation of something. It can
represent a vehicle, an elephant (see David Harel's talk about modeling an elephant at
SIMUTools 2009, or a networking card. Models can also represent processes such as global
warming, freeway traffic flow or a specification of a networking protocol. Models can be
completely faithful representations of a logical process specification, but they
necessarily can never completely simulate a physical object or process. In most cases, a
number of simplifications are made to the model to make simulation computationally
tractable.

Every model has a target system that it is attempting to simulate. The first step in
creating a simulation model is to identify this target system and the level of detail and
accuracy that the simulation is desired to reproduce. In the case of a logical process,
the target system may be identified as ''TCP as defined by RFC 793.'' In this case, it
will probably be desirable to create a model that completely and faithfully reproduces RFC
793. In the case of a physical process this will not be possible. If, for example, you
would like to simulate a wireless networking card, you may determine that you need, ''an
accurate MAC-level implementation of the 802.11 specification and [...] a not-so-slow
PHY-level model of the 802.11a specification.''

Once this is done, one can develop an abstract model of the target system. This is
typically an exercise in managing the tradeoffs between complexity, resource requirements
and accuracy. The process of developing an abstract model has been called model
qualification in the literature. In the case of a TCP protocol, this process results in a
design for a collection of objects, interactions and behaviors that will fully implement
RFC 793 in ns-3. In the case of the wireless card, this process results in a number of
tradeoffs to allow the physical layer to be simulated and the design of a network device
and channel for ns-3, along with the desired objects, interactions and behaviors.

This abstract model is then developed into an ns-3 model that implements the abstract
model as a computer program. The process of getting the implementation to agree with the
abstract model is called model verification in the literature.

The process so far is open loop. What remains is to make a determination that a given ns-3
model has some connection to some reality -- that a model is an accurate representation of
a real system, whether a logical process or a physical entity.

If one is going to use a simulation model to try and predict how some real system is going
to behave, there must be some reason to believe your results -- i.e., can one trust that
an inference made from the model translates into a correct prediction for the real system.
The process of getting the ns-3 model behavior to agree with the desired target system
behavior as defined by the model qualification process is called model validation in the
literature. In the case of a TCP implementation, you may want to compare the behavior of
your ns-3 TCP model to some reference implementation in order to validate your model. In
the case of a wireless physical layer simulation, you may want to compare the behavior of
your model to that of real hardware in a controlled setting,

The ns-3 testing environment provides tools to allow for both model validation and
testing, and encourages the publication of validation results.

Robustness
Robustness is the quality of being able to withstand stresses, or changes in environments,
inputs or calculations, etc. A system or design is ''robust'' if it can deal with such
changes with minimal loss of functionality.

This kind of testing is usually done with a particular focus. For example, the system as
a whole can be run on many different system configurations to demonstrate that it can
perform correctly in a large number of environments.

The system can be also be stressed by operating close to or beyond capacity by generating
or simulating resource exhaustion of various kinds. This genre of testing is called
''stress testing.''

The system and its components may be exposed to so-called ''clean tests'' that demonstrate
a positive result -- that is that the system operates correctly in response to a large
variation of expected configurations.

The system and its components may also be exposed to ''dirty tests'' which provide inputs
outside the expected range. For example, if a module expects a zero-terminated string
representation of an integer, a dirty test might provide an unterminated string of random
characters to verify that the system does not crash as a result of this unexpected input.
Unfortunately, detecting such ''dirty'' input and taking preventive measures to ensure the
system does not fail catastrophically can require a huge amount of development overhead.
In order to reduce development time, a decision was taken early on in the project to
minimize the amount of parameter validation and error handling in the ns-3 codebase. For
this reason, we do not spend much time on dirty testing -- it would just uncover the
results of the design decision we know we took.

We do want to demonstrate that ns-3 software does work across some set of conditions. We
borrow a couple of definitions to narrow this down a bit. The domain of applicability is
a set of prescribed conditions for which the model has been tested, compared against
reality to the extent possible, and judged suitable for use. The range of accuracy is an
agreement between the computerized model and reality within a domain of applicability.

The ns-3 testing environment provides tools to allow for setting up and running test
environments over multiple systems (buildbot) and provides classes to encourage clean
tests to verify the operation of the system over the expected ''domain of applicability''
and ''range of accuracy.''

Performant
Okay, ''performant'' isn't a real English word. It is, however, a very concise neologism
that is quite often used to describe what we want ns-3 to be: powerful and fast enough to
get the job done.

This is really about the broad subject of software performance testing. One of the key
things that is done is to compare two systems to find which performs better (cf
benchmarks). This is used to demonstrate that, for example, ns-3 can perform a basic kind
of simulation at least as fast as a competing tool, or can be used to identify parts of
the system that perform badly.

In the ns-3 test framework, we provide support for timing various kinds of tests.

Maintainability
A software product must be maintainable. This is, again, a very broad statement, but a
testing framework can help with the task. Once a model has been developed, validated and
verified, we can repeatedly execute the suite of tests for the entire system to ensure
that it remains valid and verified over its lifetime.

When a feature stops functioning as intended after some kind of change to the system is
integrated, it is called generically a regression. Originally the term regression
referred to a change that caused a previously fixed bug to reappear, but the term has
evolved to describe any kind of change that breaks existing functionality. There are many
kinds of regressions that may occur in practice.

A local regression is one in which a change affects the changed component directly. For
example, if a component is modified to allocate and free memory but stale pointers are
used, the component itself fails.

A remote regression is one in which a change to one component breaks functionality in
another component. This reflects violation of an implied but possibly unrecognized
contract between components.

An unmasked regression is one that creates a situation where a previously existing bug
that had no affect is suddenly exposed in the system. This may be as simple as exercising
a code path for the first time.

A performance regression is one that causes the performance requirements of the system to
be violated. For example, doing some work in a low level function that may be repeated
large numbers of times may suddenly render the system unusable from certain perspectives.

The ns-3 testing framework provides tools for automating the process used to validate and
verify the code in nightly test suites to help quickly identify possible regressions.

Testing framework
ns-3 consists of a simulation core engine, a set of models, example programs, and tests.
Over time, new contributors contribute models, tests, and examples. A Python test program
test.py serves as the test execution manager; test.py can run test code and examples to
look for regressions, can output the results into a number of forms, and can manage code
coverage analysis tools. On top of this, we layer Buildbots that are automated build
robots that perform robustness testing by running the test framework on different systems
and with different configuration options.

BuildBots
At the highest level of ns-3 testing are the buildbots (build robots). If you are
unfamiliar with this system look at http://djmitche.github.com/buildbot/docs/0.7.11/.
This is an open-source automated system that allows ns-3 to be rebuilt and tested each
time something has changed. By running the buildbots on a number of different systems we
can ensure that ns-3 builds and executes properly on all of its supported systems.

Users (and developers) typically will not interact with the buildbot system other than to
read its messages regarding test results. If a failure is detected in one of the
automated build and test jobs, the buildbot will send an email to the ns-developers
mailing list. This email will look something like

In the full details URL shown in the email, one can search for the keyword failed and
select the stdio link for the corresponding step to see the reason for the failure.

The buildbot will do its job quietly if there are no errors, and the system will undergo
build and test cycles every day to verify that all is well.

Test.py
The buildbots use a Python program, test.py, that is responsible for running all of the
tests and collecting the resulting reports into a human- readable form. This program is
also available for use by users and developers as well.

test.py is very flexible in allowing the user to specify the number and kind of tests to
run; and also the amount and kind of output to generate.

Before running test.py, make sure that ns3's examples and tests have been built by doing
the following

$ ./waf configure --enable-examples --enable-tests
$ ./waf

By default, test.py will run all available tests and report status back in a very concise
form. Running the command

$ ./test.py

will result in a number of PASS, FAIL, CRASH or SKIP indications followed by the kind of
test that was run and its display name.

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
'build' finished successfully (0.939s)
FAIL: TestSuite ns3-wifi-propagation-loss-models
PASS: TestSuite object-name-service
PASS: TestSuite pcap-file-object
PASS: TestSuite ns3-tcp-cwnd
...
PASS: TestSuite ns3-tcp-interoperability
PASS: Example csma-broadcast
PASS: Example csma-multicast

This mode is intended to be used by users who are interested in determining if their
distribution is working correctly, and by developers who are interested in determining if
changes they have made have caused any regressions.

There are a number of options available to control the behavior of test.py. if you run
test.py --help you should see a command summary like:

Usage: test.py [options]

Options:
-h, --help show this help message and exit
-b BUILDPATH, --buildpath=BUILDPATH
specify the path where ns-3 was built (defaults to the
build directory for the current variant)
-c KIND, --constrain=KIND
constrain the test-runner by kind of test
-e EXAMPLE, --example=EXAMPLE
specify a single example to run (no relative path is
needed)
-g, --grind run the test suites and examples using valgrind
-k, --kinds print the kinds of tests available
-l, --list print the list of known tests
-m, --multiple report multiple failures from test suites and test
cases
-n, --nowaf do not run waf before starting testing
-p PYEXAMPLE, --pyexample=PYEXAMPLE
specify a single python example to run (with relative
path)
-r, --retain retain all temporary files (which are normally
deleted)
-s TEST-SUITE, --suite=TEST-SUITE
specify a single test suite to run
-t TEXT-FILE, --text=TEXT-FILE
write detailed test results into TEXT-FILE.txt
-v, --verbose print progress and informational messages
-w HTML-FILE, --web=HTML-FILE, --html=HTML-FILE
write detailed test results into HTML-FILE.html
-x XML-FILE, --xml=XML-FILE
write detailed test results into XML-FILE.xml

If one specifies an optional output style, one can generate detailed descriptions of the
tests and status. Available styles are text and HTML. The buildbots will select the HTML
option to generate HTML test reports for the nightly builds using

$ ./test.py --html=nightly.html

In this case, an HTML file named ''nightly.html'' would be created with a pretty summary
of the testing done. A ''human readable'' format is available for users interested in the
details.

$ ./test.py --text=results.txt

In the example above, the test suite checking the ns-3 wireless device propagation loss
models failed. By default no further information is provided.

To further explore the failure, test.py allows a single test suite to be specified.
Running the command

$ ./test.py --suite=ns3-wifi-propagation-loss-models

or equivalently

$ ./test.py -s ns3-wifi-propagation-loss-models

results in that single test suite being run.

FAIL: TestSuite ns3-wifi-propagation-loss-models

To find detailed information regarding the failure, one must specify the kind of output
desired. For example, most people will probably be interested in a text file:

$ ./test.py --suite=ns3-wifi-propagation-loss-models --text=results.txt

This will result in that single test suite being run with the test status written to the
file ''results.txt''.

You should find something similar to the following in that file

FAIL: Test Suite ''ns3-wifi-propagation-loss-models'' (real 0.02 user 0.01 system 0.00)
PASS: Test Case "Check ... Friis ... model ..." (real 0.01 user 0.00 system 0.00)
FAIL: Test Case "Check ... Log Distance ... model" (real 0.01 user 0.01 system 0.00)
Details:
Message: Got unexpected SNR value
Condition: [long description of what actually failed]
Actual: 176.395
Limit: 176.407 +- 0.0005
File: ../src/test/ns3wifi/propagation-loss-models-test-suite.cc
Line: 360

Notice that the Test Suite is composed of two Test Cases. The first test case checked the
Friis propagation loss model and passed. The second test case failed checking the Log
Distance propagation model. In this case, an SNR of 176.395 was found, and the test
expected a value of 176.407 correct to three decimal places. The file which implemented
the failing test is listed as well as the line of code which triggered the failure.

If you desire, you could just as easily have written an HTML file using the --html option
as described above.

Typically a user will run all tests at least once after downloading ns-3 to ensure that
his or her environment has been built correctly and is generating correct results
according to the test suites. Developers will typically run the test suites before and
after making a change to ensure that they have not introduced a regression with their
changes. In this case, developers may not want to run all tests, but only a subset. For
example, the developer might only want to run the unit tests periodically while making
changes to a repository. In this case, test.py can be told to constrain the types of
tests being run to a particular class of tests. The following command will result in only
the unit tests being run:

$ ./test.py --constrain=unit

Similarly, the following command will result in only the example smoke tests being run:

$ ./test.py --constrain=unit

To see a quick list of the legal kinds of constraints, you can ask for them to be listed.
The following command

$ ./test.py --kinds

will result in the following list being displayed:

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
'build' finished successfully (0.939s)Waf: Entering directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
bvt: Build Verification Tests (to see if build completed successfully)
core: Run all TestSuite-based tests (exclude examples)
example: Examples (to see if example programs run successfully)
performance: Performance Tests (check to see if the system is as fast as expected)
system: System Tests (spans modules to check integration of modules)
unit: Unit Tests (within modules to check basic functionality)

Any of these kinds of test can be provided as a constraint using the --constraint option.

To see a quick list of all of the test suites available, you can ask for them to be
listed. The following command,

$ ./test.py --list

will result in a list of the test suite being displayed, similar to

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
'build' finished successfully (0.939s)
histogram
ns3-wifi-interference
ns3-tcp-cwnd
ns3-tcp-interoperability
sample
devices-mesh-flame
devices-mesh-dot11s
devices-mesh
...
object-name-service
callback
attributes
config
global-value
command-line
basic-random-number
object

Any of these listed suites can be selected to be run by itself using the --suite option as
shown above.

Similarly to test suites, one can run a single C++ example program using the --example
option. Note that the relative path for the example does not need to be included and that
the executables built for C++ examples do not have extensions. Entering

$ ./test.py --example=udp-echo

results in that single example being run.

PASS: Example examples/udp/udp-echo

You can specify the directory where ns-3 was built using the --buildpath option as
follows.

$ ./test.py --buildpath=/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build/debug --example=wifi-simple-adhoc

One can run a single Python example program using the --pyexample option. Note that the
relative path for the example must be included and that Python examples do need their
extensions. Entering

$ ./test.py --pyexample=examples/tutorial/first.py

results in that single example being run.

PASS: Example examples/tutorial/first.py

Because Python examples are not built, you do not need to specify the directory where ns-3
was built to run them.

Normally when example programs are executed, they write a large amount of trace file data.
This is normally saved to the base directory of the distribution (e.g.,
/home/user/ns-3-dev). When test.py runs an example, it really is completely unconcerned
with the trace files. It just wants to to determine if the example can be built and run
without error. Since this is the case, the trace files are written into a
/tmp/unchecked-traces directory. If you run the above example, you should be able to find
the associated udp-echo.tr and udp-echo-n-1.pcap files there.

The list of available examples is defined by the contents of the ''examples'' directory in
the distribution. If you select an example for execution using the --example option,
test.py will not make any attempt to decide if the example has been configured or not, it
will just try to run it and report the result of the attempt.

When test.py runs, by default it will first ensure that the system has been completely
built. This can be defeated by selecting the --nowaf option.

$ ./test.py --list --nowaf

will result in a list of the currently built test suites being displayed, similar to:

ns3-wifi-propagation-loss-models
ns3-tcp-cwnd
ns3-tcp-interoperability
pcap-file-object
object-name-service
random-number-generators

Note the absence of the Waf build messages.

test.py also supports running the test suites and examples under valgrind. Valgrind is a
flexible program for debugging and profiling Linux executables. By default, valgrind runs
a tool called memcheck, which performs a range of memory- checking functions, including
detecting accesses to uninitialised memory, misuse of allocated memory (double frees,
access after free, etc.) and detecting memory leaks. This can be selected by using the
--grind option.

$ ./test.py --grind

As it runs, test.py and the programs that it runs indirectly, generate large numbers of
temporary files. Usually, the content of these files is not interesting, however in some
cases it can be useful (for debugging purposes) to view these files. test.py provides a
--retain option which will cause these temporary files to be kept after the run is
completed. The files are saved in a directory named testpy-output under a subdirectory
named according to the current Coordinated Universal Time (also known as Greenwich Mean
Time).

$ ./test.py --retain

Finally, test.py provides a --verbose option which will print large amounts of information
about its progress. It is not expected that this will be terribly useful unless there is
an error. In this case, you can get access to the standard output and standard error
reported by running test suites and examples. Select verbose in the following way:

$ ./test.py --verbose

All of these options can be mixed and matched. For example, to run all of the ns-3 core
test suites under valgrind, in verbose mode, while generating an HTML output file, one
would do:

$ ./test.py --verbose --grind --constrain=core --html=results.html

TestTaxonomy
As mentioned above, tests are grouped into a number of broadly defined classifications to
allow users to selectively run tests to address the different kinds of testing that need
to be done.

· Build Verification Tests

· Unit Tests

· System Tests

· Examples

· Performance Tests

BuildVerificationTests
These are relatively simple tests that are built along with the distribution and are used
to make sure that the build is pretty much working. Our current unit tests live in the
source files of the code they test and are built into the ns-3 modules; and so fit the
description of BVTs. BVTs live in the same source code that is built into the ns-3 code.
Our current tests are examples of this kind of test.

Unit Tests
Unit tests are more involved tests that go into detail to make sure that a piece of code
works as advertised in isolation. There is really no reason for this kind of test to be
built into an ns-3 module. It turns out, for example, that the unit tests for the object
name service are about the same size as the object name service code itself. Unit tests
are tests that check a single bit of functionality that are not built into the ns-3 code,
but live in the same directory as the code it tests. It is possible that these tests
check integration of multiple implementation files in a module as well. The file
src/core/test/names-test-suite.cc is an example of this kind of test. The file
src/network/test/pcap-file-test-suite.cc is another example that uses a known good pcap
file as a test vector file. This file is stored locally in the src/network directory.

System Tests
System tests are those that involve more than one module in the system. We have lots of
this kind of test running in our current regression framework, but they are typically
overloaded examples. We provide a new place for this kind of test in the directory
src/test. The file src/test/ns3tcp/ns3-interop-test-suite.cc is an example of this kind
of test. It uses NSC TCP to test the ns-3 TCP implementation. Often there will be test
vectors required for this kind of test, and they are stored in the directory where the
test lives. For example, ns3tcp-interop-response-vectors.pcap is a file consisting of a
number of TCP headers that are used as the expected responses of the ns-3 TCP under test
to a stimulus generated by the NSC TCP which is used as a ''known good'' implementation.

Examples
The examples are tested by the framework to make sure they built and will run. Nothing is
checked, and currently the pcap files are just written off into /tmp to be discarded. If
the examples run (don't crash) they pass this smoke test.

Performance Tests
Performance tests are those which exercise a particular part of the system and determine
if the tests have executed to completion in a reasonable time.

Running Tests
Tests are typically run using the high level test.py program. To get a list of the
available command-line options, run test.py --help

The test program test.py will run both tests and those examples that have been added to
the list to check. The difference between tests and examples is as follows. Tests
generally check that specific simulation output or events conforms to expected behavior.
In contrast, the output of examples is not checked, and the test program merely checks the
exit status of the example program to make sure that it runs without error.

Briefly, to run all tests, first one must configure tests during configuration stage, and
also (optionally) examples if examples are to be checked:

$ ./waf --configure --enable-examples --enable-tests

Then, build ns-3, and after it is built, just run test.py. test.py -h will show a number
of configuration options that modify the behavior of test.py.

The program test.py invokes, for C++ tests and examples, a lower-level C++ program called
test-runner to actually run the tests. As discussed below, this test-runner can be a
helpful way to debug tests.

Debugging Tests
The debugging of the test programs is best performed running the low-level test-runner
program. The test-runner is the bridge from generic Python code to ns-3 code. It is
written in C++ and uses the automatic test discovery process in the ns-3 code to find and
allow execution of all of the various tests.

The main reason why test.py is not suitable for debugging is that it is not allowed for
logging to be turned on using the NS_LOG environmental variable when test.py runs. This
limitation does not apply to the test-runner executable. Hence, if you want to see logging
output from your tests, you have to run them using the test-runner directly.

In order to execute the test-runner, you run it like any other ns-3 executable -- using
waf. To get a list of available options, you can type:

$ ./waf --run "test-runner --help"

You should see something like the following

Waf: Entering directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
Waf: Leaving directory `/home/craigdo/repos/ns-3-allinone-test/ns-3-dev/build'
'build' finished successfully (0.353s)
--assert: Tell tests to segfault (like assert) if an error is detected
--basedir=dir: Set the base directory (where to find src) to ''dir''
--tempdir=dir: Set the temporary directory (where to find data files) to ''dir''
--constrain=test-type: Constrain checks to test suites of type ''test-type''
--help: Print this message
--kinds: List all of the available kinds of tests
--list: List all of the test suites (optionally constrained by test-type)
--out=file-name: Set the test status output file to ''file-name''
--suite=suite-name: Run the test suite named ''suite-name''
--verbose: Turn on messages in the run test suites

There are a number of things available to you which will be familiar to you if you have
looked at test.py. This should be expected since the test- runner is just an interface
between test.py and ns-3. You may notice that example-related commands are missing here.
That is because the examples are really not ns-3 tests. test.py runs them as if they were
to present a unified testing environment, but they are really completely different and not
to be found here.

The first new option that appears here, but not in test.py is the --assert option. This
option is useful when debugging a test case when running under a debugger like gdb. When
selected, this option tells the underlying test case to cause a segmentation violation if
an error is detected. This has the nice side-effect of causing program execution to stop
(break into the debugger) when an error is detected. If you are using gdb, you could use
this option something like,

$ ./waf shell
$ cd build/debug/utils
$ gdb test-runner
$ run --suite=global-value --assert

If an error is then found in the global-value test suite, a segfault would be generated
and the (source level) debugger would stop at the NS_TEST_ASSERT_MSG that detected the
error.

Another new option that appears here is the --basedir option. It turns out that some
tests may need to reference the source directory of the ns-3 distribution to find local
data, so a base directory is always required to run a test.

If you run a test from test.py, the Python program will provide the basedir option for
you. To run one of the tests directly from the test-runner using waf, you will need to
specify the test suite to run along with the base directory. So you could use the shell
and do:

$ ./waf --run "test-runner --basedir=`pwd` --suite=pcap-file-object"

Note the ''backward'' quotation marks on the pwd command.

If you are running the test suite out of a debugger, it can be quite painful to remember
and constantly type the absolute path of the distribution base directory. Because of
this, if you omit the basedir, the test-runner will try to figure one out for you. It
begins in the current working directory and walks up the directory tree looking for a
directory file with files named VERSION and LICENSE. If it finds one, it assumes that
must be the basedir and provides it for you.

Test output
Many test suites need to write temporary files (such as pcap files) in the process of
running the tests. The tests then need a temporary directory to write to. The Python
test utility (test.py) will provide a temporary file automatically, but if run stand-alone
this temporary directory must be provided. Just as in the basedir case, it can be
annoying to continually have to provide a --tempdir, so the test runner will figure one
out for you if you don't provide one. It first looks for environment variables named TMP
and TEMP and uses those. If neither TMP nor TEMP are defined it picks /tmp. The code
then tacks on an identifier indicating what created the directory (ns-3) then the time
(hh.mm.ss) followed by a large random number. The test runner creates a directory of that
name to be used as the temporary directory. Temporary files then go into a directory that
will be named something like

/tmp/ns-3.10.25.37.61537845

The time is provided as a hint so that you can relatively easily reconstruct what
directory was used if you need to go back and look at the files that were placed in that
directory.

Another class of output is test output like pcap traces that are generated to compare to
reference output. The test program will typically delete these after the test suites all
run. To disable the deletion of test output, run test.py with the "retain" option:

$ ./test.py -r

and test output can be found in the testpy-output/ directory.

Reporting of test failures
When you run a test suite using the test-runner it will run the test quietly by default.
The only indication that you will get that the test passed is the absence of a message
from waf saying that the program returned something other than a zero exit code. To get
some output from the test, you need to specify an output file to which the tests will
write their XML status using the --out option. You need to be careful interpreting the
results because the test suites will append results onto this file. Try,

$ ./waf --run "test-runner --basedir=`pwd` --suite=pcap-file-object --out=myfile.xml"

If you look at the file myfile.xml you should see something like,

<TestSuite>
<SuiteName>pcap-file-object</SuiteName>
<TestCase>
<CaseName>Check to see that PcapFile::Open with mode ''w'' works</CaseName>
<CaseResult>PASS</CaseResult>
<CaseTime>real 0.00 user 0.00 system 0.00</CaseTime>
</TestCase>
<TestCase>
<CaseName>Check to see that PcapFile::Open with mode ''r'' works</CaseName>
<CaseResult>PASS</CaseResult>
<CaseTime>real 0.00 user 0.00 system 0.00</CaseTime>
</TestCase>
<TestCase>
<CaseName>Check to see that PcapFile::Open with mode ''a'' works</CaseName>
<CaseResult>PASS</CaseResult>
<CaseTime>real 0.00 user 0.00 system 0.00</CaseTime>
</TestCase>
<TestCase>
<CaseName>Check to see that PcapFileHeader is managed correctly</CaseName>
<CaseResult>PASS</CaseResult>
<CaseTime>real 0.00 user 0.00 system 0.00</CaseTime>
</TestCase>
<TestCase>
<CaseName>Check to see that PcapRecordHeader is managed correctly</CaseName>
<CaseResult>PASS</CaseResult>
<CaseTime>real 0.00 user 0.00 system 0.00</CaseTime>
</TestCase>
<TestCase>
<CaseName>Check to see that PcapFile can read out a known good pcap file</CaseName>
<CaseResult>PASS</CaseResult>
<CaseTime>real 0.00 user 0.00 system 0.00</CaseTime>
</TestCase>
<SuiteResult>PASS</SuiteResult>
<SuiteTime>real 0.00 user 0.00 system 0.00</SuiteTime>
</TestSuite>

If you are familiar with XML this should be fairly self-explanatory. It is also not a
complete XML file since test suites are designed to have their output appended to a master
XML status file as described in the test.py section.

Debugging test suite failures
To debug test crashes, such as

CRASH: TestSuite ns3-wifi-interference

You can access the underlying test-runner program via gdb as follows, and then pass the
"--basedir=`pwd`" argument to run (you can also pass other arguments as needed, but
basedir is the minimum needed):

$ ./waf --command-template="gdb %s" --run "test-runner"
Waf: Entering directory `/home/tomh/hg/sep09/ns-3-allinone/ns-3-dev-678/build'
Waf: Leaving directory `/home/tomh/hg/sep09/ns-3-allinone/ns-3-dev-678/build'
'build' finished successfully (0.380s)
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
L cense GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu"...
(gdb) r --basedir=`pwd`
Starting program: <..>/build/debug/utils/test-runner --basedir=`pwd`
[Thread debugging using libthread_db enabled]
assert failed. file=../src/core/model/type-id.cc, line=138, cond="uid <= m_information.size () && uid != 0"
...

Here is another example of how to use valgrind to debug a memory problem such as:

VALGR: TestSuite devices-mesh-dot11s-regression

$ ./waf --command-template="valgrind %s --basedir=`pwd` --suite=devices-mesh-dot11s-regression" --run test-runner

Class TestRunner
The executables that run dedicated test programs use a TestRunner class. This class
provides for automatic test registration and listing, as well as a way to execute the
individual tests. Individual test suites use C++ global constructors to add themselves to
a collection of test suites managed by the test runner. The test runner is used to list
all of the available tests and to select a test to be run. This is a quite simple class
that provides three static methods to provide or Adding and Getting test suites to a
collection of tests. See the doxygen for class ns3::TestRunner for details.

Test Suite
All ns-3 tests are classified into Test Suites and Test Cases. A test suite is a
collection of test cases that completely exercise a given kind of functionality. As
described above, test suites can be classified as,

· Build Verification Tests

· Unit Tests

· System Tests

· Examples

· Performance Tests

This classification is exported from the TestSuite class. This class is quite simple,
existing only as a place to export this type and to accumulate test cases. From a user
perspective, in order to create a new TestSuite in the system one only has to define a new
class that inherits from class TestSuite and perform these two duties.

The following code will define a new class that can be run by test.py as a ''unit'' test
with the display name, my-test-suite-name.

class MySuite : public TestSuite
{
public:
MyTestSuite ();
};

MyTestSuite::MyTestSuite ()
: TestSuite ("my-test-suite-name", UNIT)
{
AddTestCase (new MyTestCase);
}

MyTestSuite myTestSuite;

The base class takes care of all of the registration and reporting required to be a good
citizen in the test framework.

Test Case
Individual tests are created using a TestCase class. Common models for the use of a test
case include "one test case per feature", and "one test case per method." Mixtures of
these models may be used.

In order to create a new test case in the system, all one has to do is to inherit from the
TestCase base class, override the constructor to give the test case a name and override
the DoRun method to run the test.

class MyTestCase : public TestCase
{
MyTestCase ();
virtual void DoRun (void);
};

MyTestCase::MyTestCase ()
: TestCase ("Check some bit of functionality")
{
}

void
MyTestCase::DoRun (void)
{
NS_TEST_ASSERT_MSG_EQ (true, true, "Some failure message");
}

Utilities
There are a number of utilities of various kinds that are also part of the testing
framework. Examples include a generalized pcap file useful for storing test vectors; a
generic container useful for transient storage of test vectors during test execution; and
tools for generating presentations based on validation and verification testing results.

These utilities are not documented here, but for example, please see how the TCP tests
found in src/test/ns3tcp/ use pcap files and reference output.

How to write tests
A primary goal of the ns-3 project is to help users to improve the validity and
credibility of their results. There are many elements to obtaining valid models and
simulations, and testing is a major component. If you contribute models or examples to
ns-3, you may be asked to contribute test code. Models that you contribute will be used
for many years by other people, who probably have no idea upon first glance whether the
model is correct. The test code that you write for your model will help to avoid future
regressions in the output and will aid future users in understanding the verification and
bounds of applicability of your models.

There are many ways to verify the correctness of a model's implementation. In this
section, we hope to cover some common cases that can be used as a guide to writing new
tests.

Sample TestSuite skeleton
When starting from scratch (i.e. not adding a TestCase to an existing TestSuite), these
things need to be decided up front:

· What the test suite will be called

· What type of test it will be (Build Verification Test, Unit Test, System Test, or
Performance Test)

· Where the test code will live (either in an existing ns-3 module or separately in
src/test/ directory). You will have to edit the wscript file in that directory to
compile your new code, if it is a new file.

A program called src/create-module.py is a good starting point. This program can be
invoked such as create-module.py router for a hypothetical new module called router. Once
you do this, you will see a router directory, and a test/router-test-suite.cc test suite.
This file can be a starting point for your initial test. This is a working test suite,
although the actual tests performed are trivial. Copy it over to your module's test
directory, and do a global substitution of "Router" in that file for something pertaining
to the model that you want to test. You can also edit things such as a more descriptive
test case name.

You also need to add a block into your wscript to get this test to compile:

module_test.source = [
'test/router-test-suite.cc',
]

Before you actually start making this do useful things, it may help to try to run the
skeleton. Make sure that ns-3 has been configured with the "--enable-tests" option.
Let's assume that your new test suite is called "router" such as here:

RouterTestSuite::RouterTestSuite ()
: TestSuite ("router", UNIT)

Try this command:

$ ./test.py -s router

Output such as below should be produced:

PASS: TestSuite router
1 of 1 tests passed (1 passed, 0 skipped, 0 failed, 0 crashed, 0 valgrind errors)

See src/lte/test/test-lte-antenna.cc for a worked example.

Test macros
There are a number of macros available for checking test program output with expected
output. These macros are defined in src/core/model/test.h.

The main set of macros that are used include the following:

NS_TEST_ASSERT_MSG_EQ(actual, limit, msg)
NS_TEST_ASSERT_MSG_NE(actual, limit, msg)
NS_TEST_ASSERT_MSG_LT(actual, limit, msg)
NS_TEST_ASSERT_MSG_GT(actual, limit, msg)
NS_TEST_ASSERT_MSG_EQ_TOL(actual, limit, tol, msg)

The first argument actual is the value under test, the second value limit is the expected
value (or the value to test against), and the last argument msg is the error message to
print out if the test fails.

The first four macros above test for equality, inequality, less than, or greater than,
respectively. The fifth macro above tests for equality, but within a certain tolerance.
This variant is useful when testing floating point numbers for equality against a limit,
where you want to avoid a test failure due to rounding errors.

Finally, there are variants of the above where the keyword ASSERT is replaced by EXPECT.
These variants are designed specially for use in methods (especially callbacks) returning
void. Reserve their use for callbacks that you use in your test programs; otherwise, use
the ASSERT variants.

How to add an example program to the test suite
One can "smoke test" that examples compile and run successfully to completion (without
memory leaks) using the examples-to-run.py script located in your module's test directory.
Briefly, by including an instance of this file in your test directory, you can cause the
test runner to execute the examples listed. It is usually best to make sure that you
select examples that have reasonably short run times so as to not bog down the tests. See
the example in src/lte/test/ directory.

Testing for boolean outcomes
Testing outcomes when randomness is involved
Testing output data against a known distribution
Providing non-trivial input vectors of data
Storing and referencing non-trivial output data
Presenting your output test data
Support
Creating a new ns-3 model
This chapter walks through the design process of an ns-3 model. In many research cases,
users will not be satisfied to merely adapt existing models, but may want to extend the
core of the simulator in a novel way. We will use the example of adding an ErrorModel to a
simple ns-3 link as a motivating example of how one might approach this problem and
proceed through a design and implementation.

NOTE:
Documentation

Here we focus on the process of creating new models and new modules, and some of the
design choices involved. For the sake of clarity, we defer discussion of the mechanics
of documenting models and source code to the Documentation chapter.

Design Approach
Consider how you want it to work; what should it do. Think about these things:

· functionality: What functionality should it have? What attributes or configuration is
exposed to the user?

· reusability: How much should others be able to reuse my design? Can I reuse code from
ns-2 to get started? How does a user integrate the model with the rest of another
simulation?

· dependencies: How can I reduce the introduction of outside dependencies on my new code
as much as possible (to make it more modular)? For instance, should I avoid any
dependence on IPv4 if I want it to also be used by IPv6? Should I avoid any dependency
on IP at all?

Do not be hesitant to contact the ns-3-users or ns-developers list if you have questions.
In particular, it is important to think about the public API of your new model and ask for
feedback. It also helps to let others know of your work in case you are interested in
collaborators.

Example: ErrorModel
An error model exists in ns-2. It allows packets to be passed to a stateful object that
determines, based on a random variable, whether the packet is corrupted. The caller can
then decide what to do with the packet (drop it, etc.).

The main API of the error model is a function to pass a packet to, and the return value of
this function is a boolean that tells the caller whether any corruption occurred. Note
that depending on the error model, the packet data buffer may or may not be corrupted.
Let's call this function "IsCorrupt()".

So far, in our design, we have:

class ErrorModel
{
public:
/**
* \returns true if the Packet is to be considered as errored/corrupted
* \param pkt Packet to apply error model to
*/
bool IsCorrupt (Ptr<Packet> pkt);
};

Note that we do not pass a const pointer, thereby allowing the function to modify the
packet if IsCorrupt() returns true. Not all error models will actually modify the packet;
whether or not the packet data buffer is corrupted should be documented.

We may also want specialized versions of this, such as in ns-2, so although it is not the
only design choice for polymorphism, we assume that we will subclass a base class
ErrorModel for specialized classes, such as RateErrorModel, ListErrorModel, etc, such as
is done in ns-2.

You may be thinking at this point, "Why not make IsCorrupt() a virtual method?". That is
one approach; the other is to make the public non-virtual function indirect through a
private virtual function (this in C++ is known as the non virtual interface idiom and is
adopted in the ns-3 ErrorModel class).

Next, should this device have any dependencies on IP or other protocols? We do not want
to create dependencies on Internet protocols (the error model should be applicable to
non-Internet protocols too), so we'll keep that in mind later.

Another consideration is how objects will include this error model. We envision putting
an explicit setter in certain NetDevice implementations, for example.:

/**
* Attach a receive ErrorModel to the PointToPointNetDevice.
*
* The PointToPointNetDevice may optionally include an ErrorModel in
* the packet receive chain.
*
* @see ErrorModel
* @param em Ptr to the ErrorModel.
*/
void PointToPointNetDevice::SetReceiveErrorModel(Ptr<ErrorModel> em);

Again, this is not the only choice we have (error models could be aggregated to lots of
other objects), but it satisfies our primary use case, which is to allow a user to force
errors on otherwise successful packet transmissions, at the NetDevice level.

After some thinking and looking at existing ns-2 code, here is a sample API of a base
class and first subclass that could be posted for initial review:

class ErrorModel
{
public:
ErrorModel ();
virtual ~ErrorModel ();
bool IsCorrupt (Ptr<Packet> pkt);
void Reset (void);
void Enable (void);
void Disable (void);
bool IsEnabled (void) const;
private:
virtual bool DoCorrupt (Ptr<Packet> pkt) = 0;
virtual void DoReset (void) = 0;
};

enum ErrorUnit
{
EU_BIT,
EU_BYTE,
EU_PKT
};

// Determine which packets are errored corresponding to an underlying
// random variable distribution, an error rate, and unit for the rate.
class RateErrorModel : public ErrorModel
{
public:
RateErrorModel ();
virtual ~RateErrorModel ();
enum ErrorUnit GetUnit (void) const;
void SetUnit (enum ErrorUnit error_unit);
double GetRate (void) const;
void SetRate (double rate);
void SetRandomVariable (const RandomVariable &ranvar);
private:
virtual bool DoCorrupt (Ptr<Packet> pkt);
virtual void DoReset (void);
};

Scaffolding
Let's say that you are ready to start implementing; you have a fairly clear picture of
what you want to build, and you may have solicited some initial review or suggestions from
the list. One way to approach the next step (implementation) is to create scaffolding and
fill in the details as the design matures.

This section walks through many of the steps you should consider to define scaffolding, or
a non-functional skeleton of what your model will eventually implement. It is usually good
practice to not wait to get these details integrated at the end, but instead to plumb a
skeleton of your model into the system early and then add functions later once the API and
integration seems about right.

Note that you will want to modify a few things in the below presentation for your model
since if you follow the error model verbatim, the code you produce will collide with the
existing error model. The below is just an outline of how ErrorModel was built that you
can adapt to other models.

Review the ns-3 Coding Style Document
At this point, you may want to pause and read the ns-3 coding style document, especially
if you are considering to contribute your code back to the project. The coding style
document is linked off the main project page: ns-3 coding style.

Decide Where in the Source Tree the Model Should Reside
All of the ns-3 model source code is in the directory src/. You will need to choose which
subdirectory it resides in. If it is new model code of some sort, it makes sense to put it
into the src/ directory somewhere, particularly for ease of integrating with the build
system.

In the case of the error model, it is very related to the packet class, so it makes sense
to implement this in the src/network/ module where ns-3 packets are implemented.

waf and wscript
ns-3 uses the Waf build system. You will want to integrate your new ns-3 uses the Waf
build system. You will want to integrate your new source files into this system. This
requires that you add your files to the wscript file found in each directory.

Let's start with empty files error-model.h and error-model.cc, and add this to
src/network/wscript. It is really just a matter of adding the .cc file to the rest of the
source files, and the .h file to the list of the header files.

Now, pop up to the top level directory and type "./test.py". You shouldn't have broken
anything by this operation.

Include Guards
Next, let's add some include guards in our header file.:

#ifndef ERROR_MODEL_H
#define ERROR_MODEL_H
...
#endif

namespace ns3
ns-3 uses the ns-3 namespace to isolate its symbols from other namespaces. Typically, a
user will next put an ns-3 namespace block in both the cc and h file.:

namespace ns3 {
...
}

At this point, we have some skeletal files in which we can start defining our new classes.
The header file looks like this:

#ifndef ERROR_MODEL_H
#define ERROR_MODEL_H

namespace ns3 {

} // namespace ns3
#endif

while the error-model.cc file simply looks like this:

#include "error-model.h"

namespace ns3 {

} // namespace ns3

These files should compile since they don't really have any contents. We're now ready to
start adding classes.

Initial Implementation
At this point, we're still working on some scaffolding, but we can begin to define our
classes, with the functionality to be added later.

Inherit from the Object Class?
This is an important design step; whether to use class Object as a base class for your new
classes.

As described in the chapter on the ns-3 Object-model, classes that inherit from class
Object get special properties:

· the ns-3 type and attribute system (see Attributes)

· an object aggregation system

· a smart-pointer reference counting system (class Ptr)

Classes that derive from class ObjectBase} get the first two properties above, but do not
get smart pointers. Classes that derive from class RefCountBase get only the smart-pointer
reference counting system.

In practice, class Object is the variant of the three above that the ns-3 developer will
most commonly encounter.

In our case, we want to make use of the attribute system, and we will be passing instances
of this object across the ns-3 public API, so class Object is appropriate for us.

Initial Classes
One way to proceed is to start by defining the bare minimum functions and see if they will
compile. Let's review what all is needed to implement when we derive from class Object.:

#ifndef ERROR_MODEL_H
#define ERROR_MODEL_H

#include "ns3/object.h"

namespace ns3 {

class ErrorModel : public Object
{
public:
static TypeId GetTypeId (void);

ErrorModel ();
virtual ~ErrorModel ();
};

class RateErrorModel : public ErrorModel
{
public:
static TypeId GetTypeId (void);

RateErrorModel ();
virtual ~RateErrorModel ();
};
#endif

A few things to note here. We need to include object.h. The convention in ns-3 is that if
the header file is co-located in the same directory, it may be included without any path
prefix. Therefore, if we were implementing ErrorModel in src/core/model directory, we
could have just said "#include "object.h"". But we are in src/network/model, so we must
include it as "#include "ns3/object.h"". Note also that this goes outside the namespace
declaration.

Second, each class must implement a static public member function called GetTypeId (void).

Third, it is a good idea to implement constructors and destructors rather than to let the
compiler generate them, and to make the destructor virtual. In C++, note also that copy
assignment operator and copy constructors are auto-generated if they are not defined, so
if you do not want those, you should implement those as private members. This aspect of
C++ is discussed in Scott Meyers' Effective C++ book. item 45.

Let's now look at some corresponding skeletal implementation code in the .cc file.:

#include "error-model.h"

namespace ns3 {

NS_OBJECT_ENSURE_REGISTERED (ErrorModel);

TypeId ErrorModel::GetTypeId (void)
{
static TypeId tid = TypeId ("ns3::ErrorModel")
.SetParent<Object> ()
;
return tid;
}

ErrorModel::ErrorModel ()
{
}

ErrorModel::~ErrorModel ()
{
}

NS_OBJECT_ENSURE_REGISTERED (RateErrorModel);

TypeId RateErrorModel::GetTypeId (void)
{
static TypeId tid = TypeId ("ns3::RateErrorModel")
.SetParent<ErrorModel> ()
.AddConstructor<RateErrorModel> ()
;
return tid;
}

RateErrorModel::RateErrorModel ()
{
}

RateErrorModel::~RateErrorModel ()
{
}

What is the GetTypeId (void) function? This function does a few things. It registers a
unique string into the TypeId system. It establishes the hierarchy of objects in the
attribute system (via SetParent). It also declares that certain objects can be created via
the object creation framework (AddConstructor).

The macro NS_OBJECT_ENSURE_REGISTERED (classname) is needed also once for every class that
defines a new GetTypeId method, and it does the actual registration of the class into the
system. The Object-model chapter discusses this in more detail.

Including External Files
Logging Support
Here, write a bit about adding |ns3| logging macros. Note that LOG_COMPONENT_DEFINE is
done outside the namespace ns3

Constructor, Empty Function Prototypes
Key Variables (Default Values, Attributes)
Test Program 1
Object Framework
Adding a Sample Script
At this point, one may want to try to take the basic scaffolding defined above and add it
into the system. Performing this step now allows one to use a simpler model when plumbing
into the system and may also reveal whether any design or API modifications need to be
made. Once this is done, we will return to building out the functionality of the
ErrorModels themselves.

Add Basic Support in the Class
/* point-to-point-net-device.h */
class ErrorModel;

/**
* Error model for receive packet events
*/
Ptr<ErrorModel> m_receiveErrorModel;

Add Accessor
void
PointToPointNetDevice::SetReceiveErrorModel (Ptr<ErrorModel> em)
{
NS_LOG_FUNCTION (this << em);
m_receiveErrorModel = em;
}

.AddAttribute ("ReceiveErrorModel",
"The receiver error model used to simulate packet loss",
PointerValue (),
MakePointerAccessor (&PointToPointNetDevice::m_receiveErrorModel),
MakePointerChecker<ErrorModel> ())

Plumb Into the System
void PointToPointNetDevice::Receive (Ptr<Packet> packet)
{
NS_LOG_FUNCTION (this << packet);
uint16_t protocol = 0;

if (m_receiveErrorModel && m_receiveErrorModel->IsCorrupt (packet) )
{
//
// If we have an error model and it indicates that it is time to lose a
// corrupted packet, don't forward this packet up, let it go.
//
m_dropTrace (packet);
}
else
{
//
// Hit the receive trace hook, strip off the point-to-point protocol header
// and forward this packet up the protocol stack.
//
m_rxTrace (packet);
ProcessHeader(packet, protocol);
m_rxCallback (this, packet, protocol, GetRemote ());
if (!m_promiscCallback.IsNull ())
{ m_promiscCallback (this, packet, protocol, GetRemote (),
GetAddress (), NetDevice::PACKET_HOST);
}
}
}

Create Null Functional Script
/* simple-error-model.cc */

// Error model
// We want to add an error model to node 3's NetDevice
// We can obtain a handle to the NetDevice via the channel and node
// pointers
Ptr<PointToPointNetDevice> nd3 = PointToPointTopology::GetNetDevice
(n3, channel2);
Ptr<ErrorModel> em = Create<ErrorModel> ();
nd3->SetReceiveErrorModel (em);

bool
ErrorModel::DoCorrupt (Packet& p)
{
NS_LOG_FUNCTION;
NS_LOG_UNCOND("Corrupt!");
return false;
}

At this point, we can run the program with our trivial ErrorModel plumbed into the receive
path of the PointToPointNetDevice. It prints out the string "Corrupt!" for each packet
received at node n3. Next, we return to the error model to add in a subclass that performs
more interesting error modeling.

Add a Subclass
The trivial base class ErrorModel does not do anything interesting, but it provides a
useful base class interface (Corrupt () and Reset ()), forwarded to virtual functions that
can be subclassed. Let's next consider what we call a BasicErrorModel which is based on
the ns-2 ErrorModel class (in ns-2/queue/errmodel.{cc,h}).

What properties do we want this to have, from a user interface perspective? We would like
for the user to be able to trivially swap out the type of ErrorModel used in the
NetDevice. We would also like the capability to set configurable parameters.

Here are a few simple requirements we will consider:

· Ability to set the random variable that governs the losses (default is UniformVariable)

· Ability to set the unit (bit, byte, packet, time) of granularity over which errors are
applied.

· Ability to set the rate of errors (e.g. 10^-3) corresponding to the above unit of
granularity.

· Ability to enable/disable (default is enabled)

How to Subclass
We declare BasicErrorModel to be a subclass of ErrorModel as follows,:

class BasicErrorModel : public ErrorModel
{
public:
static TypeId GetTypeId (void);
...
private:
// Implement base class pure virtual functions
virtual bool DoCorrupt (Ptr<Packet> p);
virtual bool DoReset (void);
...
}

and configure the subclass GetTypeId function by setting a unique TypeId string and
setting the Parent to ErrorModel:

TypeId RateErrorModel::GetTypeId (void)
{
static TypeId tid = TypeId ("ns3::RateErrorModel")
.SetParent<ErrorModel> ()
.AddConstructor<RateErrorModel> ()
...

Build Core Functions and Unit Tests
Assert Macros
Writing Unit Tests
Adding a New Module to ns-3
When you have created a group of related classes, examples, and tests, they can be
combined together into an ns-3 module so that they can be used with existing ns-3 modules
and by other researchers.

This chapter walks you through the steps necessary to add a new module to ns-3.

Step 0 - Module Layout
All modules can be found in the src directory. Each module can be found in a directory
that has the same name as the module. For example, the spectrum module can be found here:
src/spectrum. We'll be quoting from the spectrum module for illustration.

A prototypical module has the following directory structure and required files:

src/
module-name/
bindings/
doc/
examples/
wscript
helper/
model/
test/
examples-to-run.py
wscript

Not all directories will be present in each module.

Step 1 - Create a Module Skeleton
A python program is provided in the source directory that will create a skeleton for a new
module. For the purposes of this discussion we will assume that your new module is called
new-module. From the src directory, do the following to create the new module:

$ ./create-module.py new-module

Next, cd into new-module; you will find this directory layout:

$ cd new-module
$ ls
doc examples helper model test wscript

In more detail, the create-module.py script will create the directories as well as initial
skeleton wscript, .h, .cc and .rst files. The complete module with skeleton files looks
like this:

src/
new-module/
doc/
new-module.rst
examples/
new-module-example.cc
wscript
helper/
new-module-helper.cc
new-module-helper.h
model/
new-module.cc
new-module.h
test/
new-module-test-suite.cc
wscript

(If required the bindings/ directory listed in Step-0 will be created automatically during
the build.)

We next walk through how to customize this module. Informing waf about the files which
make up your module is done by editing the two wscript files. We will walk through the
main steps in this chapter.

All ns-3 modules depend on the core module and usually on other modules. This dependency
is specified in the wscript file (at the top level of the module, not the separate wscript
file in the examples directory!). In the skeleton wscript the call that will declare your
new module to waf will look like this (before editing):

def build(bld):
module = bld.create_ns3_module('new-module', ['core'])

Let's assume that new-module depends on the internet, mobility, and aodv modules. After
editing it the wscript file should look like:

def build(bld):
module = bld.create_ns3_module('new-module', ['internet', 'mobility', 'aodv'])

Note that only first level module dependencies should be listed, which is why we removed
core; the internet module in turn depends on core.

Your module will most likely have model source files. Initial skeletons (which will
compile successfully) are created in model/new-module.cc and model/new-module.h.

If your module will have helper source files, then they will go into the helper/
directory; again, initial skeletons are created in that directory.

Finally, it is good practice to write tests and examples. These will almost certainly be
required for new modules to be accepted into the official ns-3 source tree. A skeleton
test suite and test case is created in the test/ directory. The skeleton test suite will
contain the below constructor, which declares a new unit test named new-module, with a
single test case consisting of the class NewModuleTestCase1:

NewModuleTestSuite::NewModuleTestSuite ()
: TestSuite ("new-module", UNIT)
{
AddTestCase (new NewModuleTestCase1);
}

Step 3 - Declare Source Files
The public header and source code files for your new module should be specified in the
wscript file by modifying it with your text editor.

As an example, after declaring the spectrum module, the src/spectrum/wscript specifies the
source code files with the following list:

def build(bld):

module = bld.create_ns3_module('spectrum', ['internet', 'propagation', 'antenna', 'applications'])

module.source = [
'model/spectrum-model.cc',
'model/spectrum-value.cc',
.
.
.
'model/microwave-oven-spectrum-value-helper.cc',
'helper/spectrum-helper.cc',
'helper/adhoc-aloha-noack-ideal-phy-helper.cc',
'helper/waveform-generator-helper.cc',
'helper/spectrum-analyzer-helper.cc',
]

The objects resulting from compiling these sources will be assembled into a link library,
which will be linked to any programs relying on this module.

But how do such programs learn the public API of our new module? Read on!

Step 4 - Declare Public Header Files
The header files defining the public API of your model and helpers also should be
specified in the wscript file.

Continuing with the spectrum model illustration, the public header files are specified
with the following stanza. (Note that the argument to the bld function tells waf to
install this module's headers with the other ns-3 headers):

headers = bld(features='ns3header')

headers.module = 'spectrum'

headers.source = [
'model/spectrum-model.h',
'model/spectrum-value.h',
.
.
.
'model/microwave-oven-spectrum-value-helper.h',
'helper/spectrum-helper.h',
'helper/adhoc-aloha-noack-ideal-phy-helper.h',
'helper/waveform-generator-helper.h',
'helper/spectrum-analyzer-helper.h',
]

Headers made public in this way will be accessible to users of your model with include
statements like

#include "ns3/spectrum-model.h"

Headers used strictly internally in your implementation should not be included here. They
are still accessible to your implemenation by include statements like

#include "my-module-implementation.h"

Step 5 - Declare Tests
If your new module has tests, then they must be specified in your wscript file by
modifying it with your text editor.

The spectrum model tests are specified with the following stanza:

module_test = bld.create_ns3_module_test_library('spectrum')

module_test.source = [
'test/spectrum-interference-test.cc',
'test/spectrum-value-test.cc',
]

See Tests for more information on how to write test cases.

Step 6 - Declare Examples
If your new module has examples, then they must be specified in your examples/wscript
file. (The skeleton top-level wscript will recursively include examples/wscript only if
the examples were enabled at configure time.)

The spectrum model defines it's first example in src/spectrum/examples/wscript with

def build(bld):
obj = bld.create_ns3_program('adhoc-aloha-ideal-phy',
['spectrum', 'mobility'])
obj.source = 'adhoc-aloha-ideal-phy.cc'

Note that the second argument to the function create_ns3_program() is the list of modules
that the program being created depends on; again, don't forget to include new-module in
the list. It's best practice to list only the direct module dependencies, and let waf
deduce the full dependency tree.

Occasionally, for clarity, you may want to split the implementation for your example among
several source files. In this case, just include those files as additional explicit
sources of the example:

obj = bld.create_ns3_program('new-module-example', [new-module])
obj.source = ['new-module-example.cc', 'new-module-example-part.cc']

Python examples are specified using the following function call. Note that the second
argument for the function register_ns3_script() is the list of modules that the Python
example depends on:

bld.register_ns3_script('new-module-example.py', ['new-module'])

Step 7 - Examples Run as Tests
In addition to running explicit test code, the test framework can also be instrumented to
run full example programs to try to catch regressions in the examples. However, not all
examples are suitable for regression tests. The file test/examples-to-run.py controls the
invocation of the examples when the test framework runs.

The spectrum model examples run by test.py are specified in
src/spectrum/test/examples-to-run.py using the following two lists of C++ and Python
examples:

# A list of C++ examples to run in order to ensure that they remain
# buildable and runnable over time. Each tuple in the list contains
#
# (example_name, do_run, do_valgrind_run).
#
# See test.py for more information.
cpp_examples = [
("adhoc-aloha-ideal-phy", "True", "True"),
("adhoc-aloha-ideal-phy-with-microwave-oven", "True", "True"),
("adhoc-aloha-ideal-phy-matrix-propagation-loss-model", "True", "True"),
]

# A list of Python examples to run in order to ensure that they remain
# runnable over time. Each tuple in the list contains
#
# (example_name, do_run).
#
# See test.py for more information.
python_examples = [
("sample-simulator.py", "True"),
]

As indicated in the comment, each entry in the C++ list of examples to run contains the
tuple (example_name, do_run, do_valgrind_run), where

· example_name is the executable to be run,

· do_run is a condition under which to run the example, and

· do_valgrind_run is a condition under which to run the example under valgrind. (This
is needed because NSC causes illegal instruction crashes with some tests when they
are run under valgrind.)

Note that the two conditions are Python statements that can depend on waf configuration
variables. For example,

("tcp-nsc-lfn", "NSC_ENABLED == True", "NSC_ENABLED == False"),

Each entry in the Python list of examples to run contains the tuple (example_name,
do_run), where, as for the C++ examples,

· example_name is the Python script to be run, and

· do_run is a condition under which to run the example.

Again, the condition is a Python statement that can depend on waf configuration variables.
For example,

("realtime-udp-echo.py", "ENABLE_REAL_TIME == False"),

Step 8 - Configure and Build
You can now configure, build and test your module as normal. You must reconfigure the
project as a first step so that waf caches the new information in your wscript files, or
else your new module will not be included in the build.

$ ./waf configure --enable-examples --enable-tests
$ ./waf build
$ ./test.py

Look for your new module's test suite (and example programs, if your module has any
enabled) in the test output.

Step 9 - Python Bindings
Adding Python bindings to your module is optional, and the step is commented out by
default in the create-module.py script.

# bld.ns3_python_bindings()

If you want to include Python bindings (needed only if you want to write Python ns-3
programs instead of C++ ns-3 programs), you should uncomment the above and install the
Python API scanning system (covered elsewhere in this manual) and scan your module to
generate new bindings.

Creating Documentation
ns-3 supplies two kinds of documentation: expository "user-guide"-style chapters, and
source code API documentation.

The "user-guide" chapters are written by hand in reStructuredText format (.rst), which is
processed by the Python documentation system Sphinx to generate web pages and pdf files.
The API documentation is generated from the source code itself, using Doxygen, to generate
cross-linked web pages. Both of these are important: the Sphinx chapters explain the why
and overview of using a model; the API documentation explains the how details.

This chapter gives a quick overview of these tools, emphasizing preferred usage and
customizations for ns-3.

To build all the standard documentation:

$ ./waf docs

For more specialized options, read on.

Documenting with Sphinx
We use Sphinx to generate expository chapters describing the design and usage of each
module. Right now you are reading the Documentation Chapter. The Show Source link in the
sidebar will show you the reStructuredText source for this chapter.

Adding New Chapters
Adding a new chapter takes three steps (described in more detail below):

1. Choose Where? the documentation file(s) will live.

2. Link from an existing page to the new documentation.

3. Add the new file to the Makefile.

Where?
Documentation for a specific module, foo, should normally go in src/foo/doc/. For example
src/foo/doc/foo.rst would be the top-level document for the module. The
src/create-module.py script will create this file for you.

Some models require several .rst files, and figures; these should all go in the
src/foo/doc/ directory. The docs are actually build by a Sphinx Makefile. For especially
involved documentation, it may be helpful to have a local Makefile in the src/foo/doc/
directory to simplify building the documentation for this module (Antenna is an example).
Setting this up is not particularly hard, but is beyond the scope of this chapter.

In some cases, documentation spans multiple models; the Network chapter is an example. In
these cases adding the .rst files directly to doc/models/source/ might be appropriate.

Link
Sphinx has to know where your new chapter should appear. In most cases, a new model
chapter should appear the in Models book. To add your chapter there, edit
doc/models/source/index.rst

.. toctree::
:maxdepth: 1

organization
animation
antenna
aodv
applications
...

Add the name of your document (without the .rst extension) to this list. Please keep the
Model chapters in alphabetical order, to ease visual scanning for specific chapters.

Makefile
You also have to add your document to the appropriate Makefile, so make knows to check it
for updates. The Models book Makefile is doc/models/Makefile, the Manual book Makefile is
doc/manual/Makefile.

# list all model library .rst files that need to be copied to $SOURCETEMP
SOURCES = \
source/conf.py \
source/_static \
source/index.rst \
source/replace.txt \
source/organization.rst \
...
$(SRC)/antenna/doc/source/antenna.rst \
...

You add your .rst files to the SOURCES variable. To add figures, read the comments in the
Makefile to see which variable should contain your image files. Again, please keep these
in alphabetical order.

Building Sphinx Docs
Building the Sphinx documentation is pretty simple. To build all the Sphinx
documentation:

$ ./waf sphinx

To build just the Models documentation:

$ make -C doc/models

To see the generated documentation point your browser at doc/models/build/html.

As you can see, Sphinx uses Make to guide the process. The default target builds all
enabled output forms, which in ns-3 are the multi-page html, single-page singlehtml, and
pdf (latex). To build just the multi-page html, you add the html target:

$ make -C doc/models html

This can be helpful to reduce the build time (and the size of the build chatter) as you
are writing your chapter.

Before committing your documentation to the repo, please check that it builds without
errors or warnings. The build process generates lots of output (mostly normal chatter
from LaTeX), which can make it difficult to see if there are any Sphinx warnings or
errors. To find important warnings and errors build just the html version, then search
the build log for warning or error.

ns-3 Specifics
The Sphinx documentation and tutorial are pretty good. We won't duplicate the basics
here, instead focusing on preferred usage for ns-3.

· Start documents with these two lines:

.. include:: replace.txt
.. highlight:: cpp

The first line enables some simple replacements. For example, typing |ns3| renders as
ns-3. The second sets the default source code highlighting language explicitly for the
file, since the parser guess isn't always accurate. (It's also possible to set the
language explicitly for a single code block, see below.)

· Sections:

Sphinx is pretty liberal about marking section headings. By convention, we prefer this
hierarchy:

.. heading hierarchy:
------------- Chapter
************* Section (#.#)
============= Subsection (#.#.#)
############# Sub-subsection

· Syntax Highlighting:

To use the default syntax highlighter, simply start a sourcecode block:

┌──────────────────────────────────────────────┬─────────────────────────────────┐
│Sphinx Source │ Rendered Output │
├──────────────────────────────────────────────┼─────────────────────────────────┤
│ │ The Frobnitz is accessed by: │
│ The ``Frobnitz`` is accessed by:: │ │
│ │ Foo::Frobnitz frob; │
│ Foo::Frobnitz frob; │ frob.Set (...); │
│ frob.Set (...); │ │
└──────────────────────────────────────────────┴─────────────────────────────────┘

To use a specific syntax highlighter, for example, bash shell commands:

┌─────────────────────────────────┬──────────────────┐
│Sphinx Source │ Rendered Output │
├─────────────────────────────────┼──────────────────┤
│ │ │
│ .. sourcecode:: bash │ $ ls │
│ │ │
│ $ ls │ │
└─────────────────────────────────┴──────────────────┘

· Shorthand Notations:

These shorthands are defined:

┌────────────────────────┬─────────────────┐
│Sphinx Source │ Rendered Output │
├────────────────────────┼─────────────────┤
│ │ ns-3
│ |ns3| │ │
├────────────────────────┼─────────────────┤
│ │ ns-2
│ |ns2| │ │
├────────────────────────┼─────────────────┤
│ │ │
│ |check| │ │
├────────────────────────┼─────────────────┤
│ │ RFC 6282
│ :rfc:`6282` │ │
└────────────────────────┴─────────────────┘

Documenting with Doxygen
We use Doxygen to generate browsable API documentation. Doxygen provides a number of
useful features:

· Summary table of all class members.

· Graphs of inheritance and collaboration for all classes.

· Links to the source code imlementing each function.

· Links to every place a member is used.

· Links to every object used in implementing a function.

· Grouping of related classes, such as all the classes related to a specific protocol.

In addition, we use the TypeId system to add to the documentation for every class

· The Config paths by which such objects can be reached.

· Documentation for any Attributes, including Attributes defined in parent classes.

· Documentation for any Trace sources defined by the class.

Doxygen operates by scaning the source code, looking for specially marked comments. It
also creates a cross reference, indicating where each file, class, method, and variable is
used.

Preferred Style
The preferred style for Doxygen comments is the JavaDoc style:

/**
* Brief description of this class or method.
* Adjacent lines become a single paragraph.
*
* Longer description, with lots of details.
*
* Blank lines separate paragraphs.
*
* Explain what the class or method does, using what algorithm.
* Explain the units of arguments and return values.
*
* \note Note any limitations or gotchas.
*
* (For functions with arguments or return valued:)
* \param foo Brief noun phrase describing this argument.
* \param bar Note Sentence case, and terminating period.
* \return Brief noun phrase describing the value.
*
* \internal
*
* You can also discuss internal implementation details.
* Understanding this material shouldn't be necessary to using
* the class or method.
*/
class Example

In this style the Doxygen comment block begins with two `*' characters: /**, and precedes
the item being documented.

For items needing only a brief description, either of these short forms is appropriate:

/** Destructor implementation. */
void DoDispose ();

int m_count; //!< Count of ...

Note the special form of the end of line comment, //!<, indicating that it refers to the
preceding item.

Some items to note:

· Use sentence case, including the initial capital.

· Use punctuation, especially `.'s at the end of sentences or phrases.

· The \brief tag is not needed; the first sentence will be used as the brief
description.

Every class, method, typedef, member variable, function argument and return value should
be documented in all source code files which form the formal API and implementation for
ns-3, such as src/<module>/model/*, src/<module>/helper/* and src/<module>/utils/*.
Documentation for items in src/<module>/test/* and src/<module>/examples/* is preferred,
but not required.

Useful Features
· Inherited members will automatically inherit docs from the parent, (but can be replaced
by local documentation).

1. Document the base class.

2. In the sub class mark inherited functions with an ordinary comment:

// Inherited methods
virtual void FooBar (void);
virtual int BarFoo (double baz);

Note that the signatures have to match exactly, so include the formal argument (void)

This doesn't work for static functions; see GetTypeId, below, for an example.

Building Doxygen Docs
Building the Doxygen documentation is pretty simple:

$ ./waf doxygen

This builds using the default configuration, which generates documentation sections for
all items, even if they do not have explicit comment documentation blocks. This has the
effect of suppressing warnings for undocumented items, but makes sure everything appears
in the generated output.

When writing documentation, it's often more useful to see which items are generating
warnings, typically about missing documentation. To see the full warnings list, use the
doc/doxygen.warnings.report.sh script:

$ doc/doxygen.warnings.report.sh
Waf: Entering directory `build'
...
Waf: Leaving directory `build'
'build' finished successfully (3m24.094s)

Rebuilding doxygen docs with full errors...Done.

Report of Doxygen warnings
----------------------------------------

(All counts are lower bounds.)

Warnings by module/directory:

Count Directory
----- ----------------------------------
3844 src/lte/model
1718 src/wimax/model
1423 src/core/model
....
138 additional undocumented parameters.
----------------------------------------
15765 total warnings
126 directories with warnings

Warnings by file (alphabetical)

Count File
----- ----------------------------------
17 doc/introspected-doxygen.h
15 examples/routing/manet-routing-compare.cc
26 examples/stats/wifi-example-apps.h
....
----------------------------------------
967 files with warnings

Warnings by file (numerical)

Count File
----- ----------------------------------
374 src/lte/model/lte-asn1-header.h
280 src/lte/model/lte-rrc-sap.h
262 src/lte/model/lte-rrc-header.h
....
----------------------------------------
967 files with warnings

Doxygen Warnings Summary
----------------------------------------
126 directories
967 files
15765 warnings

The script modifies the configuration to show all warnings, and to shorten the run time.
As you can see, at this writing we have a lot of undocumented items. The report
summarizes warnings by module src/*/*, and by file, in alphabetically and numerical order.

The script has a few options to pare things down and make this more manageable. For help,
use the -h option. Having run it once to do the Doxygen build and generate the full
warnings log, you can reprocess the log file with various "filters," without having to do
the full Doxygen build by, again using the -s option. You can exclude warnings from
*/examples/* files (-e option), and/or */test/* files (-t).

Perhaps the most useful option when writing documentation comments is -m <module>, which
will limit the report to just files matching src/<module>/*, and follow the report with
the actual warning lines. Combine with -et and you can focus on the warnings that are
most urgent in a single module:

$ doc/doxygen.warnings.report.sh -m mesh/helper
...
Doxygen Warnings Summary
----------------------------------------
1 directories
3 files
149 warnings

Filtered Warnings
========================================
src/mesh/helper/dot11s/dot11s-installer.h:72: warning: Member m_root (variable) of class ns3::Dot11sStack is not documented.
src/mesh/helper/dot11s/dot11s-installer.h:35: warning: return type of member ns3::Dot11sStack::GetTypeId is not documented
src/mesh/helper/dot11s/dot11s-installer.h:56: warning: return type of member ns3::Dot11sStack::InstallStack is not documented
src/mesh/helper/flame/lfame-installer.h:40: warning: Member GetTypeId() (function) of class ns3::FlameStack is not documented.
src/mesh/helper/flame/flame-installer.h:60: warning: return type of member ns3::FlameStack::InstallStack is not documented
src/mesh/helper/mesh-helper.h:213: warning: Member m_nInterfaces (variable) of class ns3::MeshHelper is not documented.
src/mesh/helper/mesh-helper.h:214: warning: Member m_spreadChannelPolicy (variable) of class ns3::MeshHelper is not documented.
src/mesh/helper/mesh-helper.h:215: warning: Member m_stack (variable) of class ns3::MeshHelper is not documented.
src/mesh/helper/mesh-helper.h:216: warning: Member m_stackFactory (variable) of class ns3::MeshHelper is not documented.
src/mesh/helper/mesh-helper.h:209: warning: parameters of member ns3::MeshHelper::CreateInterface are not (all) documented
src/mesh/helper/mesh-helper.h:119: warning: parameters of member ns3::MeshHelper::SetStandard are not (all) documented

Now it's just a matter of understanding the code, and writing some docs!

ns-3 Specifics
As for Sphinx, the Doxygen docs and reference are pretty good. We won't duplicate the
basics here, instead focusing on preferred usage for ns-3.

· Use Doxygen Modules to group related items.

In the main header for a module, create a Doxgyen group:

/**
* \defgroup foo Foo protocol.
*/

Mark each associated class as belonging to the group:

/**
* \ingroup foo
*
* Foo packet type.
*/
class Foo

· Did you know typedefs can have formal arguments? This enables documentation of function
pointer signatures:

/**
* Bar callback function signature.
*
* \param ale The size of a pint of ale, in Imperial ounces.
*/
typedef void (* BarCallback)(const int ale);

· Copy the Attribute help strings from the GetTypeId method to use as the brief
descriptions of associated members.

· \bugid{298} will create a link to bug 298 in our Bugzilla.

· \pname{foo} in a description will format foo as a \param foo parameter, making it clear
that you are referring to an actual argument.

· \RFC{301} will create a link to RFC 301.

· \internal should be used only to set off a discussion of implementation details, not to
mark private functions (they are already marked, as private!)

· Don't create classes with trivial names, such as class A, even in test suites. These
cause all instances of the class name literal `A' to be rendered as links.

As noted above, static functions don't inherit the documentation of the same functions in
the parent class. ns-3 uses a few static functions ubiquitously; the suggested
documentation block for these cases is:

· Default constructor/destructor:

MyClass (); //!< Default constructor
~MyClass (); //!< Destructor

· Dummy destructor and DoDispose:

/** Dummy destructor, see DoDispose. */
~MyClass ();

/** Destructor implementation */
virtual void DoDispose ();

· GetTypeId:

/**
* Register this type.
* \return The object TypeId.
*/
static TypeId GetTypeId (void);

Enabling Subsets of ns-3 Modules
As with most software projects, ns-3 is ever growing larger in terms of number of modules,
lines of code, and memory footprint. Users, however, may only use a few of those modules
at a time. For this reason, users may want to explicitly enable only the subset of the
possible ns-3 modules that they actually need for their research.

This chapter discusses how to enable only the ns-3 modules that you are intersted in
using.

How to enable a subset of ns-3's modules
If shared libraries are being built, then enabling a module will cause at least one
library to be built:

libns3-modulename.so

If the module has a test library and test libraries are being built, then

libns3-modulename-test.so

will be built, too. Other modules that the module depends on and their test libraries
will also be built.

By default, all modules are built in ns-3. There are two ways to enable a subset of these
modules:

1. Using waf's --enable-modules option

2. Using the ns-3 configuration file

Enable modules using waf's --enable-modules option
To enable only the core module with example and tests, for example, try these commands:

$ ./waf clean
$ ./waf configure --enable-examples --enable-tests --enable-modules=core
$ ./waf build
$ cd build/debug/
$ ls

and the following libraries should be present:

bindings libns3-core.so ns3 scratch utils
examples libns3-core-test.so samples src

Note the ./waf clean step is done here only to make it more obvious which module libraries
were built. You don't have to do ./waf clean in order to enable subsets of modules.

Running test.py will cause only those tests that depend on module core to be run:

24 of 24 tests passed (24 passed, 0 skipped, 0 failed, 0 crashed, 0 valgrind errors)

Repeat the above steps for the "network" module instead of the "core" module, and the
following will be built, since network depends on core:

bindings libns3-core.so libns3-network.so ns3 scratch utils
examples libns3-core-test.so libns3-network-test.so samples src

Running test.py will cause those tests that depend on only the core and network modules to
be run:

31 of 31 tests passed (31 passed, 0 skipped, 0 failed, 0 crashed, 0 valgrind errors)

Enable modules using the ns-3 configuration file
A configuration file, .ns3rc, has been added to ns-3 that allows users to specify which
modules are to be included in the build.

When enabling a subset of ns-3 modules, the precedence rules are as follows:

1. the --enable-modules configure string overrides any .ns3rc file

2. the .ns3rc file in the top level ns-3 directory is next consulted, if present

3. the system searches for ~/.ns3rc if the above two are unspecified

If none of the above limits the modules to be built, all modules that waf knows about will
be built.

The maintained version of the .ns3rc file in the ns-3 source code repository resides in
the utils directory. The reason for this is if it were in the top-level directory of the
repository, it would be prone to accidental checkins from maintainers that enable the
modules they want to use. Therefore, users need to manually copy the .ns3rc from the
utils directory to their preferred place (top level directory or their home directory) to
enable persistent modular build configuration.

Assuming that you are in the top level ns-3 directory, you can get a copy of the .ns3rc
file that is in the utils directory as follows:

$ cp utils/.ns3rc .

The .ns3rc file should now be in your top level ns-3 directory, and it contains the
following:

#! /usr/bin/env python

# A list of the modules that will be enabled when ns-3 is run.
# Modules that depend on the listed modules will be enabled also.
#
# All modules can be enabled by choosing 'all_modules'.
modules_enabled = ['all_modules']

# Set this equal to true if you want examples to be run.
examples_enabled = False

# Set this equal to true if you want tests to be run.
tests_enabled = False

Use your favorite editor to modify the .ns3rc file to only enable the core module with
examples and tests like this:

#! /usr/bin/env python

# A list of the modules that will be enabled when ns-3 is run.
# Modules that depend on the listed modules will be enabled also.
#
# All modules can be enabled by choosing 'all_modules'.
modules_enabled = ['core']

# Set this equal to true if you want examples to be run.
examples_enabled = True

# Set this equal to true if you want tests to be run.
tests_enabled = True

Only the core module will be enabled now if you try these commands:

$ ./waf clean
$ ./waf configure
$ ./waf build
$ cd build/debug/
$ ls

and the following libraries should be present:

bindings libns3-core.so ns3 scratch utils
examples libns3-core-test.so samples src

Note the ./waf clean step is done here only to make it more obvious which module libraries
were built. You don't have to do ./waf clean in order to enable subsets of modules.

Running test.py will cause only those tests that depend on module core to be run:

24 of 24 tests passed (24 passed, 0 skipped, 0 failed, 0 crashed, 0 valgrind errors)

Repeat the above steps for the "network" module instead of the "core" module, and the
following will be built, since network depends on core:

bindings libns3-core.so libns3-network.so ns3 scratch utils
examples libns3-core-test.so libns3-network-test.so samples src

Running test.py will cause those tests that depend on only the core and network modules to
be run:

31 of 31 tests passed (31 passed, 0 skipped, 0 failed, 0 crashed, 0 valgrind errors)

Enabling/disabling ns-3 Tests and Examples
The ns-3 distribution includes many examples and tests that are used to validate the ns-3
system. Users, however, may not always want these examples and tests to be run for their
installation of ns-3.

This chapter discusses how to build ns-3 with or without its examples and tests.

How to enable/disable examples and tests in ns-3
There are 3 ways to enable/disable examples and tests in ns-3:

1. Using build.py when ns-3 is built for the first time

2. Using waf once ns-3 has been built

3. Using the ns-3 configuration file once ns-3 has been built

Enable/disable examples and tests using build.py
You can use build.py to enable/disable examples and tests when ns-3 is built for the first
time.

By default, examples and tests are not built in ns-3.

From the ns-3-allinone directory, you can build ns-3 without any examples or tests simply
by doing:

$ ./build.py

Running test.py in the top level ns-3 directory now will cause no examples or tests to be
run:

0 of 0 tests passed (0 passed, 0 skipped, 0 failed, 0 crashed, 0 valgrind errors)

If you would like build ns-3 with examples and tests, then do the following from the
ns-3-allinone directory:

$ ./build.py --enable-examples --enable-tests

Running test.py in the top level ns-3 directory will cause all of the examples and tests
to be run:

170 of 170 tests passed (170 passed, 0 skipped, 0 failed, 0 crashed, 0 valgrind errors)

Enable/disable examples and tests using waf
You can use waf to enable/disable examples and tests once ns-3 has been built.

By default, examples and tests are not built in ns-3.

From the top level ns-3 directory, you can build ns-3 without any examples or tests simply
by doing:

$ ./waf configure
$ ./waf build

Running test.py now will cause no examples or tests to be run:

0 of 0 tests passed (0 passed, 0 skipped, 0 failed, 0 crashed, 0 valgrind errors)

If you would like build ns-3 with examples and tests, then do the following from the top
level ns-3 directory:

$ ./waf configure --enable-examples --enable-tests
$ ./waf build

Running test.py will cause all of the examples and tests to be run:

170 of 170 tests passed (170 passed, 0 skipped, 0 failed, 0 crashed, 0 valgrind errors)

Enable/disable examples and tests using the ns-3 configuration file
A configuration file, .ns3rc, has been added to ns-3 that allows users to specify whether
examples and tests should be built or not. You can use this file to enable/disable
examples and tests once ns-3 has been built.

When enabling disabling examples and tests, the precedence rules are as follows:

1. the --enable-examples/--disable-examples configure strings override any .ns3rc file

2. the --enable-tests/--disable-tests configure strings override any .ns3rc file

3. the .ns3rc file in the top level ns-3 directory is next consulted, if present

4. the system searches for ~/.ns3rc if the .ns3rc file was not found in the previous step

If none of the above exists, then examples and tests will not be built.

The maintained version of the .ns3rc file in the ns-3 source code repository resides in
the utils directory. The reason for this is if it were in the top-level directory of the
repository, it would be prone to accidental checkins from maintainers that enable the
modules they want to use. Therefore, users need to manually copy the .ns3rc from the
utils directory to their preferred place (top level directory or their home directory) to
enable persistent enabling of examples and tests.

Assuming that you are in the top level ns-3 directory, you can get a copy of the .ns3rc
file that is in the utils directory as follows:

$ cp utils/.ns3rc .

The .ns3rc file should now be in your top level ns-3 directory, and it contains the
following:

#! /usr/bin/env python

# A list of the modules that will be enabled when ns-3 is run.
# Modules that depend on the listed modules will be enabled also.
#
# All modules can be enabled by choosing 'all_modules'.
modules_enabled = ['all_modules']

# Set this equal to true if you want examples to be run.
examples_enabled = False

# Set this equal to true if you want tests to be run.
tests_enabled = False

From the top level ns-3 directory, you can build ns-3 without any examples or tests simply
by doing:

$ ./waf configure
$ ./waf build

Running test.py now will cause no examples or tests to be run:

0 of 0 tests passed (0 passed, 0 skipped, 0 failed, 0 crashed, 0 valgrind errors)

If you would like build ns-3 with examples and tests, use your favorite editor to change
the values in the .ns3rc file for examples_enabled and tests_enabled file to be True:

#! /usr/bin/env python

# A list of the modules that will be enabled when ns-3 is run.
# Modules that depend on the listed modules will be enabled also.
#
# All modules can be enabled by choosing 'all_modules'.
modules_enabled = ['all_modules']

# Set this equal to true if you want examples to be run.
examples_enabled = True

# Set this equal to true if you want tests to be run.
tests_enabled = True

From the top level ns-3 directory, you can build ns-3 with examples and tests simply by
doing:

$ ./waf configure
$ ./waf build

Running test.py will cause all of the examples and tests to be run:

170 of 170 tests passed (170 passed, 0 skipped, 0 failed, 0 crashed, 0 valgrind errors)

Troubleshooting
This chapter posts some information about possibly common errors in building or running
ns-3 programs.

Please note that the wiki (http://www.nsnam.org/wiki/Troubleshooting) may have contributed
items.

Build errors
Run-time errors
Sometimes, errors can occur with a program after a successful build. These are run-time
errors, and can commonly occur when memory is corrupted or pointer values are unexpectedly
null.

Here is an example of what might occur:

$ ./waf --run tcp-point-to-point
Entering directory '/home/tomh/ns-3-nsc/build'
Compilation finished successfully
Command ['/home/tomh/ns-3-nsc/build/debug/examples/tcp-point-to-point'] exited with code -11

The error message says that the program terminated unsuccessfully, but it is not clear
from this information what might be wrong. To examine more closely, try running it under
the gdb debugger:

$ ./waf --run tcp-point-to-point --command-template="gdb %s"
Entering directory '/home/tomh/ns-3-nsc/build'
Compilation finished successfully
GNU gdb Red Hat Linux (6.3.0.0-1.134.fc5rh)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db
library "/lib/libthread_db.so.1".

(gdb) run
Starting program: /home/tomh/ns-3-nsc/build/debug/examples/tcp-point-to-point
Reading symbols from shared object read from target memory...done.
Loaded system supplied DSO at 0xf5c000

Program received signal SIGSEGV, Segmentation fault.
0x0804aa12 in main (argc=1, argv=0xbfdfefa4)
at ../examples/tcp-point-to-point.cc:136
136 Ptr<Socket> localSocket = socketFactory->CreateSocket ();
(gdb) p localSocket
$1 = {m_ptr = 0x3c5d65}
(gdb) p socketFactory
$2 = {m_ptr = 0x0}
(gdb) quit
The program is running. Exit anyway? (y or n) y

Note first the way the program was invoked-- pass the command to run as an argument to the
command template "gdb %s".

This tells us that there was an attempt to dereference a null pointer socketFactory.

Let's look around line 136 of tcp-point-to-point, as gdb suggests:

Ptr<SocketFactory> socketFactory = n2->GetObject<SocketFactory> (Tcp::iid);
Ptr<Socket> localSocket = socketFactory->CreateSocket ();
localSocket->Bind ();

The culprit here is that the return value of GetObject is not being checked and may be
null.

Sometimes you may need to use the valgrind memory checker for more subtle errors. Again,
you invoke the use of valgrind similarly:

$ ./waf --run tcp-point-to-point --command-template="valgrind %s"

SOURCE


This document is written in reStructuredText for Sphinx and is maintained in the
doc/manual directory of ns-3's source code.

Use ns-3-manual online using onworks.net services


Free Servers & Workstations

Download Windows & Linux apps

Linux commands

Ad