1 @node ns-3 Attributes |
|
2 @chapter ns-3 Attributes |
|
3 @anchor{chap:Attributes} |
|
4 |
|
5 In ns-3 simulations, there are two main aspects to configuration: |
|
6 @itemize @bullet |
|
7 @item the simulation topology and how objects are connected |
|
8 @item the values used by the models instantiated in the topology |
|
9 @end itemize |
|
10 |
|
11 This chapter focuses on the second item above: how the many values |
|
12 in use in ns-3 are organized, documented, and modifiable by ns-3 users. |
|
13 The ns-3 attribute system is also the underpinning of how traces |
|
14 and statistics are gathered in the simulator. |
|
15 |
|
16 Before delving into details of the attribute value system, |
|
17 it will help to review some basic properties of @code{class ns3::Object}. |
|
18 |
|
19 @node Object Overview |
|
20 @section Object Overview |
|
21 |
|
22 ns-3 is fundamentally a C++ object-based system. By this we mean that |
|
23 new C++ classes (types) can be declared, defined, and subclassed |
|
24 as usual. |
|
25 |
|
26 Many ns-3 objects inherit from the @code{ns3::Object} base class. These |
|
27 objects have some additional properties that we exploit for |
|
28 organizing the system and improving the memory management |
|
29 of our objects: |
|
30 |
|
31 @itemize @bullet |
|
32 @item a "metadata" system that links the class name to a lot of |
|
33 meta-information about the object, including the base class of the subclass, |
|
34 the set of accessible constructors in the subclass, and the set of |
|
35 "attributes" of the subclass |
|
36 @item a reference counting smart pointer implementation, for memory |
|
37 management. |
|
38 @end itemize |
|
39 |
|
40 ns-3 objects that use the attribute system derive from either |
|
41 @code{ns3::Object} or @code{ns3::ObjectBase}. Most ns-3 objects |
|
42 we will discuss derive from @code{ns3::Object}, but a few that |
|
43 are outside the smart pointer memory management framework derive |
|
44 from @code{ns3::ObjectBase}. |
|
45 |
|
46 Let's review a couple of properties of these objects. |
|
47 |
|
48 @node Smart pointers |
|
49 @subsection Smart pointers |
|
50 |
|
51 As introduced above in @ref{Smart Pointers 101}, ns-3 objects |
|
52 are memory managed by a |
|
53 @uref{http://en.wikipedia.org/wiki/Smart_pointer,,reference counting smart pointer implementation}, @code{class ns3::Ptr}. |
|
54 |
|
55 Smart pointers are used extensively in the ns-3 APIs, to avoid passing |
|
56 references to heap-allocated objects that may cause memory leaks. |
|
57 For most basic usage (syntax), treat a smart pointer like a regular pointer: |
|
58 @verbatim |
|
59 Ptr<WifiNetDevice> nd = ...; |
|
60 nd->CallSomeFunction (); |
|
61 // etc. |
|
62 @end verbatim |
|
63 |
|
64 @node CreateObject |
|
65 @subsection CreateObject |
|
66 |
|
67 As we discussed above in @ref{Object Creation}, |
|
68 at the lowest-level API, objects of type @code{ns3::Object} are |
|
69 not instantiated using @code{operator new} as usual but instead by |
|
70 a templated function called @code{CreateObject()}. |
|
71 |
|
72 A typical way to create such an object is as follows: |
|
73 @verbatim |
|
74 Ptr<WifiNetDevice> nd = CreateObject<WifiNetDevice> (); |
|
75 @end verbatim |
|
76 |
|
77 You can think of this as being functionally equivalent to: |
|
78 @verbatim |
|
79 WifiNetDevice* nd = new WifiNetDevice (); |
|
80 @end verbatim |
|
81 |
|
82 Objects that derive from @code{ns3::Object} must be allocated |
|
83 on the heap using CreateObject(). Those deriving from |
|
84 @code{ns3::ObjectBase}, such as ns-3 helper functions and packet |
|
85 headers and trailers, can be allocated on the stack. |
|
86 |
|
87 In some scripts, you may not see a lot of CreateObject() calls |
|
88 in the code; |
|
89 this is because there are some helper objects in effect that |
|
90 are doing the CreateObject()s for you. |
|
91 |
|
92 @node TypeId |
|
93 @subsection TypeId |
|
94 |
|
95 ns-3 classes that derive from class ns3::Object can include |
|
96 a metadata class called @code{TypeId} that records meta-information |
|
97 about the class, for use in the object aggregation and component |
|
98 manager systems: |
|
99 @itemize @bullet |
|
100 @item a unique string identifying the class |
|
101 @item the base class of the subclass, within the metadata system |
|
102 @item the set of accessible constructors in the subclass |
|
103 @end itemize |
|
104 |
|
105 @node Object Summary |
|
106 @subsection Object Summary |
|
107 |
|
108 Putting all of these concepts together, let's look at a specific |
|
109 example: @code{class ns3::Node}. |
|
110 |
|
111 The public header file node.h has a declaration that includes |
|
112 a static GetTypeId function call: |
|
113 @verbatim |
|
114 class Node : public Object |
|
115 { |
|
116 public: |
|
117 static TypeId GetTypeId (void); |
|
118 ... |
|
119 @end verbatim |
|
120 |
|
121 This is defined in the node.cc file as follows: |
|
122 @verbatim |
|
123 TypeId |
|
124 Node::GetTypeId (void) |
|
125 { |
|
126 static TypeId tid = TypeId ("ns3::Node") |
|
127 .SetParent<Object> () |
|
128 return tid; |
|
129 } |
|
130 @end verbatim |
|
131 Finally, when users want to create Nodes, they call: |
|
132 @verbatim |
|
133 Ptr<Node> n = CreateObject<Node> n; |
|
134 @end verbatim |
|
135 |
|
136 We next discuss how attributes (values associated with member variables |
|
137 or functions of the class) are plumbed into the above TypeId. |
|
138 |
|
139 @node Attribute Overview |
|
140 @section Attribute Overview |
|
141 |
|
142 The goal of the attribute system is to organize the access of |
|
143 internal member objects of a simulation. This goal arises because, |
|
144 typically in simulation, users will cut and paste/modify existing |
|
145 simulation scripts, or will use higher-level simulation constructs, |
|
146 but often will be interested in studying or tracing particular |
|
147 internal variables. For instance, use cases such as: |
|
148 @itemize @bullet |
|
149 @item "I want to trace the packets on the wireless interface only on |
|
150 the first access point" |
|
151 @item "I want to trace the value of the TCP congestion window (every |
|
152 time it changes) on a particular TCP socket" |
|
153 @item "I want a dump of all values that were used in my simulation." |
|
154 @end itemize |
|
155 |
|
156 Similarly, users may want fine-grained access to internal |
|
157 variables in the simulation, or may want to broadly change the |
|
158 initial value used for a particular parameter in all subsequently |
|
159 created objects. Finally, users may wish to know what variables |
|
160 are settable and retrievable in a simulation configuration. This |
|
161 is not just for direct simulation interaction on the command line; |
|
162 consider also a (future) graphical user interface |
|
163 that would like to be able to provide a feature whereby a user |
|
164 might right-click on an node on the canvas and see a hierarchical, |
|
165 organized list of parameters that are settable on the node and its |
|
166 constituent member objects, and help text and default values for |
|
167 each parameter. |
|
168 |
|
169 @node Functional overview |
|
170 @subsection Functional overview |
|
171 |
|
172 We provide a way for users to access values deep in the system, without |
|
173 having to plumb accessors (pointers) through the system and walk |
|
174 pointer chains to get to them. Consider a class DropTailQueue that |
|
175 has a member variable that is an unsigned integer @code{m_maxPackets}; |
|
176 this member variable controls the depth of the queue. |
|
177 |
|
178 If we look at the declaration of DropTailQueue, we see the following: |
|
179 @verbatim |
|
180 class DropTailQueue : public Queue { |
|
181 public: |
|
182 static TypeId GetTypeId (void); |
|
183 ... |
|
184 |
|
185 private: |
|
186 std::queue<Ptr<Packet> > m_packets; |
|
187 uint32_t m_maxPackets; |
|
188 }; |
|
189 @end verbatim |
|
190 |
|
191 Let's consider things that a user may want to do with the value of |
|
192 m_maxPackets: |
|
193 |
|
194 @itemize @bullet |
|
195 @item Set a default value for the system, such that whenever a new |
|
196 DropTailQueue is created, this member is initialized to that default. |
|
197 @item Set or get the value on an already instantiated queue. |
|
198 @end itemize |
|
199 |
|
200 The above things typically require providing Set() and Get() functions, |
|
201 and some type of global default value. |
|
202 |
|
203 In the ns-3 attribute system, these value definitions and accessor |
|
204 functions are moved into the TypeId class; e.g.: |
|
205 @verbatim |
|
206 TypeId DropTailQueue::GetTypeId (void) |
|
207 { |
|
208 static TypeId tid = TypeId ("ns3::DropTailQueue") |
|
209 .SetParent<Queue> () |
|
210 .AddConstructor<DropTailQueue> () |
|
211 .AddAttribute ("MaxPackets", "The maximum number of packets accepted by this DropTailQueue.", |
|
212 Uinteger (100), |
|
213 MakeUintegerAccessor (&DropTailQueue::m_maxPackets), |
|
214 MakeUintegerChecker<uint32_t> ()) |
|
215 ; |
|
216 |
|
217 return tid; |
|
218 } |
|
219 @end verbatim |
|
220 |
|
221 The AddAttribute() method is performing a number of things with this |
|
222 value: |
|
223 @itemize @bullet |
|
224 @item Binding the variable m_maxPackets to a string "MaxPackets" |
|
225 @item Providing a default value (100 packets) |
|
226 @item Providing some help text defining the value |
|
227 @item Providing a "checker" (not used in this example) that can be used to set |
|
228 bounds on the allowable range of values |
|
229 @end itemize |
|
230 |
|
231 The key point is that now the value of this variable and its default |
|
232 value are accessible in the attribute namespace, which is based on |
|
233 strings such as "MaxPackets" and TypeId strings. In the next |
|
234 section, we will provide an example script that shows how users |
|
235 may manipulate these values. |
|
236 |
|
237 @node Basic usage |
|
238 @subsection Basic usage |
|
239 |
|
240 Let's look at how a user script might access these values. |
|
241 This is based on the script found at @code{samples/main-attribute-value.cc}, |
|
242 with some details stripped out. |
|
243 @verbatim |
|
244 // |
|
245 // This is a basic example of how to use the attribute system to |
|
246 // set and get a value in the underlying system; namely, an unsigned |
|
247 // integer of the maximum number of packets in a queue |
|
248 // |
|
249 |
|
250 int |
|
251 main (int argc, char *argv[]) |
|
252 { |
|
253 |
|
254 // By default, the MaxPackets attribute has a value of 100 packets |
|
255 // (this default can be observed in the function DropTailQueue::GetTypeId) |
|
256 // |
|
257 // Here, we set it to 80 packets. We could use one of two value types: |
|
258 // a string-based value or a Uinteger value |
|
259 Config::SetDefault ("ns3::DropTailQueue::MaxPackets", String ("80")); |
|
260 // The below function call is redundant |
|
261 Config::SetDefault ("ns3::DropTailQueue::MaxPackets", Uinteger(80)); |
|
262 |
|
263 // Allow the user to override any of the defaults and the above |
|
264 // SetDefaults() at run-time, via command-line arguments |
|
265 CommandLine cmd; |
|
266 cmd.Parse (argc, argv); |
|
267 @end verbatim |
|
268 |
|
269 The main thing to notice in the above are the two calls to |
|
270 @code{Config::SetDefault}. This is how we set the default value |
|
271 for all subsequently instantiated DropTailQueues. We illustrate |
|
272 that two types of Value classes, a String and a Uinteger class, |
|
273 can be used to assign the value to the attribute named by |
|
274 "ns3::DropTailQueue::MaxPackets". |
|
275 |
|
276 Now, we will create a few objects using the low-level API; here, |
|
277 our newly created queues will not have a m_maxPackets initialized to |
|
278 100 packets but to 80 packets, because of what we did above with |
|
279 default values. |
|
280 @verbatim |
|
281 Ptr<Node> n0 = CreateObject<Node> (); |
|
282 |
|
283 Ptr<PointToPointNetDevice> net0 = CreateObject<PointToPointNetDevice> (); |
|
284 n0->AddDevice (net0); |
|
285 |
|
286 Ptr<Queue> q = CreateObject<DropTailQueue> (); |
|
287 net0->AddQueue(q); |
|
288 @end verbatim |
|
289 |
|
290 At this point, we have created a single node (Node 0) and a |
|
291 single PointToPointNetDevice (NetDevice 0) and added a |
|
292 DropTailQueue to it. |
|
293 |
|
294 Now, we can manipulate the MaxPackets value of the already |
|
295 instantiated DropTailQueue. Here are various ways to do that. |
|
296 |
|
297 @subsubsection Pointer-based access |
|
298 |
|
299 We assume that a smart pointer (Ptr) to a relevant network device is |
|
300 in hand; here, it is the net0 pointer. |
|
301 |
|
302 One way to change the value is to access a pointer to the |
|
303 underlying queue and modify its attribute. |
|
304 |
|
305 First, we observe that we can get a pointer to the (base class) |
|
306 queue via the PointToPointNetDevice attributes, where it is called |
|
307 TxQueue |
|
308 @verbatim |
|
309 Ptr<Queue> txQueue = net0->GetAttribute ("TxQueue"); |
|
310 @end verbatim |
|
311 |
|
312 Using the GetObject function, we can perform a safe downcast |
|
313 to a DropTailQueue, where MaxPackets is a member |
|
314 @verbatim |
|
315 Ptr<DropTailQueue> dtq = txQueue->GetObject <DropTailQueue> (); |
|
316 NS_ASSERT (dtq); |
|
317 @end verbatim |
|
318 |
|
319 Next, we can get the value of an attribute on this queue |
|
320 We have introduced wrapper "Value" classes for the underlying |
|
321 data types, similar to Java wrappers around these types, since |
|
322 the attribute system stores values and not disparate types. |
|
323 Here, the attribute value is assigned to a Uinteger, and |
|
324 the Get() method on this value produces the (unwrapped) uint32_t. |
|
325 @verbatim |
|
326 Uinteger limit = dtq->GetAttribute ("MaxPackets"); |
|
327 NS_LOG_INFO ("1. dtq limit: " << limit.Get () << " packets"); |
|
328 @end verbatim |
|
329 |
|
330 Note that the above downcast is not really needed; we could have |
|
331 done the same using the Ptr<Queue> even though the attribute |
|
332 is a member of the subclass |
|
333 @verbatim |
|
334 limit = txQueue->GetAttribute ("MaxPackets"); |
|
335 NS_LOG_INFO ("2. txQueue limit: " << limit.Get () << " packets"); |
|
336 @end verbatim |
|
337 |
|
338 Now, let's set it to another value (60 packets) |
|
339 @verbatim |
|
340 txQueue->SetAttribute("MaxPackets", Uinteger (60)); |
|
341 limit = txQueue->GetAttribute ("MaxPackets"); |
|
342 NS_LOG_INFO ("3. txQueue limit changed: " << limit.Get () << " packets"); |
|
343 @end verbatim |
|
344 |
|
345 @subsubsection Namespace-based access |
|
346 |
|
347 An alternative way to get at the attribute is to use the configuration |
|
348 namespace. Here, this attribute resides on a known path in this |
|
349 namespace; this approach is useful if one doesn't have access to |
|
350 the underlying pointers and would like to configure a specific |
|
351 attribute with a single statement. |
|
352 @verbatim |
|
353 Config::Set ("/NodeList/0/DeviceList/0/TxQueue/MaxPackets", Uinteger (25)); |
|
354 limit = txQueue->GetAttribute ("MaxPackets"); |
|
355 NS_LOG_INFO ("4. txQueue limit changed through namespace: " << |
|
356 limit.Get () << " packets"); |
|
357 @end verbatim |
|
358 |
|
359 We could have also used wildcards to set this value for all nodes |
|
360 and all net devices (which in this simple example has the same |
|
361 effect as the previous Set()) |
|
362 @verbatim |
|
363 Config::Set ("/NodeList/*/DeviceList/*/TxQueue/MaxPackets", Uinteger (15)); |
|
364 limit = txQueue->GetAttribute ("MaxPackets"); |
|
365 NS_LOG_INFO ("5. txQueue limit changed through wildcarded namespace: " << |
|
366 limit.Get () << " packets"); |
|
367 @end verbatim |
|
368 |
|
369 @node Setting through constructors and helper classes |
|
370 @subsection Setting through constructors helper classes |
|
371 |
|
372 Arbitrary combinations of attributes can be set and fetched from |
|
373 the helper and low-level APIs; either from the constructors themselves: |
|
374 @verbatim |
|
375 Ptr<Object> p = CreateObject<MyNewObject> ("n1", v1, "n2", v2, ...); |
|
376 @end verbatim |
|
377 or from the higher-level helper APIs, such as: |
|
378 @verbatim |
|
379 mobility.SetPositionAllocator ("GridPositionAllocator", |
|
380 "MinX", FpValue (-100.0), |
|
381 "MinY", FpValue (-100.0), |
|
382 "DeltaX", FpValue (5.0), |
|
383 "DeltaY", FpValue (20.0), |
|
384 "GridWidth", UintValue (20), |
|
385 "LayoutType", "RowFirst"); |
|
386 @end verbatim |
|
387 |
|
388 @node Value classes |
|
389 @subsection Value classes |
|
390 Readers will note the new Value classes. These can be thought of as |
|
391 an intermediate class that can be used to convert from raw types to the |
|
392 Values that are used by the system. Recall that this database is holding |
|
393 objects of many types with a single generic type. Conversions to this |
|
394 type can either be done using an intermediate class (IntValue, FpValue for |
|
395 "floating point") or via strings. Direct implicit conversion of types |
|
396 to Value is not really practical. So in the above, users have a choice |
|
397 of using strings or values: |
|
398 @verbatim |
|
399 p->Set ("cwnd", "100"); // string-based setter |
|
400 p->Set ("cwnd", IntValue(100)); // value-based setter |
|
401 @end verbatim |
|
402 |
|
403 The system provides some macros that help users declare and define |
|
404 new Value subclasses for new types that they want to introduce into |
|
405 the attribute system. |
|
406 |
|
407 @node Extending attributes |
|
408 @section Extending attributes |
|
409 |
|
410 The ns-3 system will place a number of internal values under the |
|
411 attribute system, but undoubtedly users will want to extend this |
|
412 to pick up ones we have missed, or to add their own classes to this. |
|
413 |
|
414 @subsection Adding an existing internal variable to the metadata system |
|
415 |
|
416 // XXX revise me |
|
417 |
|
418 Consider this variable in class TcpSocket: |
|
419 @verbatim |
|
420 uint32_t m_cWnd; // Congestion window |
|
421 @end verbatim |
|
422 |
|
423 Suppose that someone working with Tcp wanted to get or set the |
|
424 value of that variable using the metadata system. If it were not |
|
425 already provided by ns-3, the user could declare the following addition |
|
426 in the metadata system (to the TypeId declaration for TcpSocket): |
|
427 @verbatim |
|
428 .AddParameter ("Congestion window", |
|
429 "Tcp congestion window (bytes)", |
|
430 MakeUIntParamSpec (&TcpSocket::m_cWnd, 1)); |
|
431 |
|
432 @end verbatim |
|
433 |
|
434 Now, the user with a pointer to the TcpSocket can perform operations |
|
435 such as setting and getting the value, without having to add these |
|
436 functions explicitly. Furthermore, access controls can be applied, such |
|
437 as allowing the parameter to be read and not written, or bounds |
|
438 checking on the permissible values can be applied. |
|
439 |
|
440 @subsection Adding a new TypeId |
|
441 |
|
442 Here, we discuss the impact on a user who wants to add a new class to |
|
443 ns-3; what additional things must be done to hook it into this system. |
|
444 |
|
445 We've already introduced what a TypeId definition looks like: |
|
446 @verbatim |
|
447 TypeId |
|
448 RandomWalk2dMobilityModel::GetTypeId (void) |
|
449 { |
|
450 static TypeId tid = TypeId ("RandomWalkMobilityModel") |
|
451 .SetParent<MobilityModel> () |
|
452 .SetGroupName ("Mobility") |
|
453 .AddConstructor<RandomWalk2dMobilityModel> () |
|
454 // followed by a number of Parameters |
|
455 .AddParameter ("bounds", |
|
456 "Bounds of the area to cruise.", |
|
457 MakeRectangleParamSpec (&RandomWalk2dMobilityModel::m_bounds, Rectangle (0.0, 0.0, 100.0, 100.0))) |
|
458 .AddParameter ("time", |
|
459 "Change current direction and speed after moving for this delay.", |
|
460 MakeTimeParamSpec (&RandomWalk2dMobilityModel::m_modeTime, |
|
461 Seconds (1.0))) |
|
462 |
|
463 // etc (more parameters). |
|
464 @end verbatim |
|
465 |
|
466 The declaration for this in the class declaration is one-line public |
|
467 member method: |
|
468 @verbatim |
|
469 public: |
|
470 static TypeId GetTypeId (void); |
|
471 @end verbatim |
|
472 |
|
473 @section Adding new class type to the Value system |
|
474 |
|
475 From the perspective of the user who writes a new class in the system and |
|
476 wants to hook it in to the attribute system, there is mainly the matter |
|
477 of writing |
|
478 the conversions to/from strings and Values. Most of this can be |
|
479 copy/pasted with macro-ized code. For instance, consider class |
|
480 Rectangle in the @code{src/mobility/} directory: |
|
481 |
|
482 One line is added to the class declaration: |
|
483 @verbatim |
|
484 /** |
|
485 * \brief a 2d rectangle |
|
486 */ |
|
487 class Rectangle |
|
488 { |
|
489 ... |
|
490 |
|
491 VALUE_HELPER_HEADER_1 (Rectangle); |
|
492 }; |
|
493 @end verbatim |
|
494 |
|
495 One templatized declaration, and two operators, are added below the |
|
496 class declaration: |
|
497 |
|
498 @verbatim |
|
499 std::ostream &operator << (std::ostream &os, const Rectangle &rectangle); |
|
500 std::istream &operator >> (std::istream &is, Rectangle &rectangle); |
|
501 |
|
502 VALUE_HELPER_HEADER_2 (Rectangle); |
|
503 @end verbatim |
|
504 |
|
505 In the class definition, the code looks like this: |
|
506 |
|
507 @verbatim |
|
508 VALUE_HELPER_CPP (Rectangle); |
|
509 |
|
510 std::ostream & |
|
511 operator << (std::ostream &os, const Rectangle &rectangle) |
|
512 { |
|
513 os << rectangle.xMin << "|" << rectangle.xMax << "|" << rectangle.yMin << "|" << rectangle.yMax; |
|
514 return os; |
|
515 } |
|
516 std::istream & |
|
517 operator >> (std::istream &is, Rectangle &rectangle) |
|
518 { |
|
519 char c1, c2, c3; |
|
520 is >> rectangle.xMin >> c1 >> rectangle.xMax >> c2 >> rectangle.yMin >> c3 >> rectangle.yMax; |
|
521 if (c1 != '|' || |
|
522 c2 != '|' || |
|
523 c3 != '|') |
|
524 { |
|
525 is.setstate (std::ios_base::failbit); |
|
526 } |
|
527 return is; |
|
528 } |
|
529 @end verbatim |
|
530 |
|
531 These stream operators simply convert from a string representation of the |
|
532 Rectangle ("xMin|xMax|yMin|yMax") to the underlying Rectangle, and the |
|
533 modeler must specify these operators and the string syntactical representation |
|
534 of an instance of the new class. |
|
535 |
|