800 lines
31 KiB
ReStructuredText
800 lines
31 KiB
ReStructuredText
.. include:: replace.txt
|
|
|
|
Attributes
|
|
----------
|
|
|
|
In |ns3| 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
|
|
|ns3| are organized, documented, and modifiable by |ns3| users. The |ns3|
|
|
attribute system is also the underpinning of how traces and statistics are
|
|
gathered in the simulator.
|
|
|
|
Before delving into details of the attribute value system, it will help to
|
|
review some basic properties of class :cpp:class:`ns3::Object`.
|
|
|
|
Object Overview
|
|
***************
|
|
|
|
|ns3| 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 |ns3| objects inherit from the :cpp:class:`ns3::Object` base class. These
|
|
objects have some additional properties that we exploit for organizing the
|
|
system and improving the memory management of our objects:
|
|
|
|
* a "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, and the set of "attributes" of the
|
|
subclass
|
|
* a reference counting smart pointer implementation, for memory management.
|
|
|
|
|ns3| objects that use the attribute system derive from either
|
|
:cpp:class:`ns3::Object` or :cpp:class:`ns3::ObjectBase`. Most |ns3| objects we
|
|
will discuss derive from :cpp:class:`ns3::Object`, but a few that are outside
|
|
the smart pointer memory management framework derive from
|
|
:cpp:class:`ns3::ObjectBase`.
|
|
|
|
Let's review a couple of properties of these objects.
|
|
|
|
Smart pointers
|
|
**************
|
|
|
|
As introduced in the |ns3| tutorial, |ns3| objects are memory managed by a
|
|
`reference counting smart pointer implementation
|
|
<http://en.wikipedia.org/wiki/Smart_pointer>`_, class :cpp:class:`ns3::Ptr`.
|
|
|
|
Smart pointers are used extensively in the |ns3| 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.
|
|
|
|
CreateObject
|
|
++++++++++++
|
|
|
|
As we discussed above in :ref:`Memory management and class Ptr`, at the
|
|
lowest-level API, objects of type :cpp:class:`ns3::Object` are not instantiated
|
|
using ``operator new`` as usual but instead by a templated function called
|
|
:cpp:func:`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 :cpp:class:`ns3::Object` must be allocated on the heap
|
|
using CreateObject(). Those deriving from :cpp:class:`ns3::ObjectBase`, such as
|
|
|ns3| 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()s for you.
|
|
|
|
TypeId
|
|
++++++
|
|
|
|
|ns3| classes that derive from class ns3::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
|
|
|
|
Object Summary
|
|
++++++++++++++
|
|
|
|
Putting all of these concepts together, let's look at a specific
|
|
example: class :cpp:class:`ns3::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 an |ns3| ``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 declaration above is used in
|
|
conjunction with our object aggregation mechanisms to allow safe up- and
|
|
down-casting in inheritance trees during ``GetObject``.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
Attribute Overview
|
|
******************
|
|
|
|
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.
|
|
|
|
Functional overview
|
|
+++++++++++++++++++
|
|
|
|
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 |ns3| attribute system, these value definitions and accessor functions
|
|
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 with this
|
|
value:
|
|
|
|
* Binding the variable m_maxPackets to a string "MaxPackets"
|
|
* Providing a default value (100 packets)
|
|
* Providing some help text defining 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 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?
|
|
|
|
Default values and command-line arguments
|
|
+++++++++++++++++++++++++++++++++++++++++
|
|
|
|
Let's look at how a user script might access these values.
|
|
This is based on the script found at ``samples/main-attribute-value.cc``,
|
|
with some details stripped out.::
|
|
|
|
//
|
|
// 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
|
|
CommandLine cmd;
|
|
cmd.Parse (argc, argv);
|
|
|
|
The main thing to notice in the above are the two 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".
|
|
|
|
Now, we will create a few objects using the low-level API; here,
|
|
our newly created queues will not have a m_maxPackets initialized to
|
|
100 packets 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 (Node 0) and a single
|
|
PointToPointNetDevice (NetDevice 0) and added a DropTailQueue to it.
|
|
|
|
Now, we can manipulate the MaxPackets value of the already instantiated
|
|
DropTailQueue. Here are various ways to do that.
|
|
|
|
Pointer-based access
|
|
++++++++++++++++++++
|
|
|
|
We 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 a member::
|
|
|
|
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 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 done the same
|
|
using the Ptr<Queue> even though the attribute is a member of the subclass::
|
|
|
|
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");
|
|
|
|
Namespace-based access
|
|
++++++++++++++++++++++
|
|
|
|
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");
|
|
|
|
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
|
|
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-based access
|
|
++++++++++++++++++++++++++++++++
|
|
|
|
Another way to get at the attribute is to use the object name service facility.
|
|
Here, this attribute is found using a 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 namespaced path.::
|
|
|
|
Names::Add ("server", serverNode);
|
|
Names::Add ("server/eth0", serverDevice);
|
|
|
|
...
|
|
|
|
Config::Set ("/Names/server/eth0/TxQueue/MaxPackets", UintegerValue (25));
|
|
|
|
:ref:`Object names` for a fuller treatment of the |ns3| configuration namespace.
|
|
|
|
Setting through constructors helper classes
|
|
+++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
Arbitrary combinations of attributes can be set and fetched from
|
|
the helper and low-level APIs; either from the constructors themselves:::
|
|
|
|
Ptr<Object> p = CreateObject<MyNewObject> ("n1", v1, "n2", v2, ...);
|
|
|
|
or from the higher-level helper APIs, such as:::
|
|
|
|
mobility.SetPositionAllocator ("GridPositionAllocator",
|
|
"MinX", DoubleValue (-100.0),
|
|
"MinY", DoubleValue (-100.0),
|
|
"DeltaX", DoubleValue (5.0),
|
|
"DeltaY", DoubleValue (20.0),
|
|
"GridWidth", UintegerValue (20),
|
|
"LayoutType", StringValue ("RowFirst"));
|
|
|
|
Implementation details
|
|
++++++++++++++++++++++
|
|
|
|
Value classes
|
|
~~~~~~~~~~~~~
|
|
|
|
Readers will note the new FooValue classes which are subclasses of the
|
|
AttributeValue base class. These can be thought of as an intermediate class that
|
|
can be used to convert from raw types to the Values that are used by the
|
|
attribute system. Recall that this database is holding objects of many types
|
|
with a single generic type. Conversions to this type can either be done using an
|
|
intermediate class (IntegerValue, DoubleValue for "floating point") or via
|
|
strings. Direct implicit conversion of types to Value 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
|
|
|
|
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 :cpp:class:`ns3::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 :cpp:class:`ns3::ConfigStore`: call ``ObjectBase::ConstructSelf ()`` as
|
|
follows:::
|
|
|
|
ConfigStore::ConfigStore ()
|
|
{
|
|
ObjectBase::ConstructSelf (AttributeList ());
|
|
// continue on with constructor.
|
|
}
|
|
|
|
Extending attributes
|
|
********************
|
|
|
|
The |ns3| 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 this.
|
|
|
|
Adding an existing internal variable to the metadata system
|
|
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
Consider this variable in class 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 |ns3|,
|
|
the user could declare the following addition in the runtime metadata system (to
|
|
the TypeId declaration 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 the TcpSocket 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.
|
|
|
|
Adding a new TypeId
|
|
+++++++++++++++++++
|
|
|
|
Here, we discuss the impact on a user who wants to add a new class to |ns3|;
|
|
what additional things must be done to hook it into this system.
|
|
|
|
We've already introduced what a TypeId definition looks like:::
|
|
|
|
TypeId
|
|
RandomWalk2dMobilityModel::GetTypeId (void)
|
|
{
|
|
static TypeId tid = TypeId ("ns3::RandomWalk2dMobilityModel")
|
|
.SetParent<MobilityModel> ()
|
|
.SetGroupName ("Mobility")
|
|
.AddConstructor<RandomWalk2dMobilityModel> ()
|
|
.AddAttribute ("Bounds",
|
|
"Bounds of the area to cruise.",
|
|
RectangleValue (Rectangle (0.0, 0.0, 100.0, 100.0)),
|
|
MakeRectangleAccessor (&RandomWalk2dMobilityModel::m_bounds),
|
|
MakeRectangleChecker ())
|
|
.AddAttribute ("Time",
|
|
"Change current direction and speed after moving for this delay.",
|
|
TimeValue (Seconds (1.0)),
|
|
MakeTimeAccessor (&RandomWalk2dMobilityModel::m_modeTime),
|
|
MakeTimeChecker ())
|
|
// etc (more parameters).
|
|
;
|
|
return tid;
|
|
}
|
|
|
|
The declaration for this in the class declaration is one-line public member
|
|
method:::
|
|
|
|
public:
|
|
static TypeId GetTypeId (void);
|
|
|
|
Typical mistakes here involve:
|
|
|
|
* Not calling the SetParent method or calling it with the wrong type
|
|
* Not calling the AddConstructor method of 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
|
|
|
|
None of these mistakes can be detected by the |ns3| codebase so, users
|
|
are advised to check carefully multiple times that they got these right.
|
|
|
|
Adding new class type to the attribute system
|
|
*********************************************
|
|
|
|
From the perspective of the user who writes a new class in the system and wants
|
|
to hook it in to the attribute system, 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 class declaration for
|
|
Rectangle in the ``src/mobility/`` 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, and the modeler
|
|
must specify these operators and the string syntactical representation of an
|
|
instance of the new class.
|
|
|
|
ConfigStore
|
|
***********
|
|
|
|
**Feedback requested:** This is an experimental feature of |ns3|. It is found
|
|
in ``src/contrib`` and not in the main tree. If you like this feature and
|
|
would like to provide feedback on it, please email us.
|
|
|
|
Values for |ns3| attributes can be stored in an ASCII or XML text file and
|
|
loaded into a future simulation. This feature is known as the |ns3|
|
|
ConfigStore. The ConfigStore code is in ``src/contrib/``. It is not yet
|
|
main-tree code, because we are seeking some user feedback and experience with
|
|
this.
|
|
|
|
We can explore this system by using an example. Copy the ``csma-bridge.cc``
|
|
file to the scratch directory:::
|
|
|
|
cp examples/csma-bridge.cc scratch/
|
|
./waf
|
|
|
|
Let's edit it to add the ConfigStore feature. First, add an include statement to
|
|
include the contrib module, and then add these lines:::
|
|
|
|
#include "ns3/config-store-module.h"
|
|
...
|
|
int main (...)
|
|
{
|
|
// setup topology
|
|
|
|
// Invoke just before entering Simulator::Run ()
|
|
ConfigStore config;
|
|
config.ConfigureDefaults ();
|
|
config.ConfigureAttributes ();
|
|
|
|
Simulator::Run ();
|
|
}
|
|
|
|
There are three attributes that govern the behavior of the ConfigStore: "Mode",
|
|
"Filename", and "FileFormat". The Mode (default "None") configures whether
|
|
|ns3| 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 store its output data. The FileFormat
|
|
(default "RawText") governs whether the ConfigStore format is Xml or RawText
|
|
format.
|
|
|
|
So, using the above modified program, try executing the following waf command
|
|
and ::
|
|
|
|
./waf --command-template="%s --ns3::ConfigStore::Filename=csma-bridge-config.xml
|
|
--ns3::ConfigStore::Mode=Save --ns3::ConfigStore::FileFormat=Xml" --run scratch/csma-bridge
|
|
|
|
After running, you can open the csma-bridge-config.xml file and it will
|
|
display the configuration that was applied to your simulation; e.g.::
|
|
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<ns3>
|
|
<default name="ns3::V4Ping::Remote" value="102.102.102.102"/>
|
|
<default name="ns3::MsduStandardAggregator::MaxAmsduSize" value="7935"/>
|
|
<default name="ns3::EdcaTxopN::MinCw" value="31"/>
|
|
<default name="ns3::EdcaTxopN::MaxCw" value="1023"/>
|
|
<default name="ns3::EdcaTxopN::Aifsn" value="3"/>
|
|
<default name="ns3::StaWifiMac::ProbeRequestTimeout" value="50000000ns"/>
|
|
<default name="ns3::StaWifiMac::AssocRequestTimeout" value="500000000ns"/>
|
|
<default name="ns3::StaWifiMac::MaxMissedBeacons" value="10"/>
|
|
<default name="ns3::StaWifiMac::ActiveProbing" value="false"/>
|
|
...
|
|
|
|
This file can be archived with your simulation script and output data.
|
|
|
|
While it is possible to generate a sample config file and lightly edit it to
|
|
change a couple of values, there are cases where this process will not work
|
|
because the same value on the same object can appear multiple times in the same
|
|
automatically-generated configuration file under different configuration paths.
|
|
|
|
As such, the best way to use this class is to use it to generate an initial
|
|
configuration file, extract from that configuration file only the strictly
|
|
necessary elements, 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.
|
|
|
|
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". (Note-- to get this input xml file to begin with, it
|
|
is sometimes helpful to run the program to generate an output xml file first,
|
|
then hand-edit that file and re-input it for the next simulation run).::
|
|
|
|
#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, via command-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 ();
|
|
}
|
|
|
|
GTK-based ConfigStore
|
|
+++++++++++++++++++++
|
|
|
|
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 <http://www.nsnam.org/docs/ns-3-overview.pdf>`_
|
|
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
|
|
./waf configure --enable-examples --enable-tests step:::
|
|
|
|
---- Summary of optional NS-3 features:
|
|
Threading Primitives : enabled
|
|
Real Time Simulator : 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
|
|
* add the default values
|