[Ns-developers] helper APIs, and planning for ns-3.1 release
Mathieu Lacage
mathieu.lacage at sophia.inria.fr
Thu Apr 3 14:05:44 PDT 2008
On Wed, 2008-04-02 at 15:00 -0700, craigdo at ee.washington.edu wrote:
> [ ... ]
>
> > Therefore, I'd like to suggest that we plan on the month of April for
> > the following activities:
> > - review of recently merged helper APIs and example scripts
>
> I have a few comments about the helper APIs. First, we've actually been
> through this exercise several times, and prototyped some fairly complex
> topologies using similar methods, so I'm not worried about them hanging
> together architecturally. My concerns are more related to usability and
> understandability, now. Especially so since we've decided that we want to
> make this API be easy to understand for new users of the simulator. I think
> that some small changes would go a long way to improving this API set.
>
> The original incarnations of the helper APIs used the verb Install as the
> primary action method. I think this conveys what is happening much better
> than Build. Consider how you would ask someone to put an ns-3 internet
> protocol stack on a computer in natural language. I know I'd ask them to
> *install* the protocol stack. If someone were to ask me to build the
> protocol stack on a computer, my first impression would be that they wanted
> me to go run a compiler. I think this is true for most helpers. If someone
> wanted me to place a bunch of applications on a computer, they'd tell me to
> *install* them. Build means something else. If someone wanted me to put a
> NIC on a computer, they'd tell me to *install* the NIC. Build the NIC means
> something else.
>
> I think that small changes from
>
> InternetStackHelper internet;
> internet.Build (c);
>
> to
>
> InternetStackHelper stack;
> stack.Install (c);
>
> including the variable name change, make it much clearer what is actually
> happening. Seeing Install next to stack means something to me; seeing Build
> next to internet doesn't.
yes, this makes sense.
>
> The address helper idioms can me improved also, IMO. Right now you'll find
>
> NodeContainer c;
> c.Create (4);
>
> NetDeviceContainer nd0 = csma.Build (c);
>
> InternetStackHelper internet;
> internet.Build (c);
>
> Ipv4AddressHelper ipv4;
> ipv4.SetBase ("10.1.1.0", "255.255.255.0");
> ipv4.Allocate (nd0);
>
> Yes, I wrote the Allocate method, and it makes sense from the perspective of
> what's happening down *inside* the routine, but it's not clear what's
> happening from above. In normal conversation, I don't speak of installing
> an IP address, I say Add an IP address, so I won't say blindly change the
> method to Install. [What happens in the Add method is that we are actually
> adding an Ipv4 interface and assigning the address to the interface, but the
> focus is on addresses and net devices, so we can probably ignore that
> detail]
>
> It's probably a typo, but the first line uses the variable nd0, which I
> would interpret as "net device 0." It's a container of net devices, so I'd
> call it "devices" or something like that. Using this, and changing the verb
> to add IP addresses to Add, I'd write something like,
>
> NodeContainer c;
> c.Create (4);
>
> NetDeviceContainer devices = csma.Install (c);
>
> InternetStackHelper stack;
> stack.Install (c);
>
> Ipv4AddressHelper ipAddress;
> ipAddress.SetBase ("10.1.1.0", "255.255.255.0");
> ipAddress.Add (devices);
>
> I find this much more intuitive. In natural language, we Create four nodes.
> We Install CSMA devices in those nodes and return a container of the devices
> we installed. We Install the internet stack in the nodes and we (loosely)
> Add ip addresses to the devices.
I am sure that this is really a matter of taste but 'Allocate' conveys
to me much better the purpose and meaning of what you call 'Add'.
>
> I think that is much clearer than, we Create four nodes. We Build CSMA
> devices in those nodes and return a container of the devices we built. We
> Build the internet stack in the nodes and we (loosely) Allocate ip addresses
> to the devices.
>
> Next, some of the application helpers use Attributes to do some things and
> regular methods to do others. For example,
>
> OnOffHelper onoff;
> onoff.SetUdpRemote (Ipv4Address ("10.1.1.2"), port);
> onoff.SetAppAttribute ("OnTime", ConstantVariable (1));
> onoff.SetAppAttribute ("OffTime", ConstantVariable (0));
>
> I'm not sure what rule determines which is which. Do I just need to
> memorize or lookup which one to use? Why do I need to do both kinds of
> call? Why do I need to call SetAppAttribute? Don't I already know it's an
> OnOff Application underneath? Isn't there one destination for the Attribute
> setter?
What I have tried to do is to give explicit setters to the attributes
which _must_ be set to ensure that the code will work: their default
values are meaningless and thus _absolutely_ need to be overriden. It is
the case here since creating an OnOffapplication without setting an
explicit ipv4 address and port as destination will just not produce
anything useful while all the other attributes are just extra noise
attributes which have working default values.
>
> Next, in order to get the application into a node, I have got to Build the
> onoff application.
>
> ApplicationContainer app = onoff.Build (c.Get (0));
> // Start the application
> app.Start (Seconds (1.0));
> app.Stop (Seconds (10.0));
>
> Building the application is counterintuitive. Again, I think we should
> Install the application in a node. That's what I would ask in conversation.
> Building an application means compiling it to me. I'm not sure about calls
> to Start and Stop the ApplicationContainer either. It seems more natural to
> Start and Stop the application represented by the helper instead of the
> container. Perhaps adding start and stop methods to the onoff helper and
> calling the methods something like StartAll or StopAll would make more
> sense. Perhaps just calling the container apps (plural) would help.
>
> On another note, the methods on some of the helpers are quite different when
> they do similar things. For example,
>
> OnOffHelper onoff;
> onoff.SetUdpRemote (Ipv4Address ("10.1.1.2"), port);
>
> PacketSinkHelper sink;
> sink.SetupUdp (Ipv4Address::GetAny (), port);
>
> Internally, they are both setting the protocol to ns3::Udp and setting an
> address, port pair.
>
> void
> PacketSinkHelper::SetupUdp (Ipv4Address ip, uint16_t port)
> {
> m_factory.Set ("Protocol", String ("ns3::Udp"));
> m_factory.Set ("Local", Address (InetSocketAddress (ip, port)));
> }
>
> void
> OnOffHelper::SetUdpRemote (Ipv4Address ip, uint16_t port)
> {
> m_factory.Set ("Protocol", String ("ns3::Udp"));
> m_factory.Set ("Remote", Address (InetSocketAddress (ip, port)));
> }
>
> Why do they have such different names? This seems to be just a useless fact
> I have to remember. Why not have them be the same or at least substantially
> similar?
>
> OnOffHelper onoff;
> onoff.SetUdpRemote (Ipv4Address ("10.1.1.2"), port);
>
> PacketSinkHelper sink;
> sink.SetUdpLocal (Ipv4Address::GetAny (), port);
>
> or even (although it's not quite as transparent),
>
> OnOffHelper onoff;
> onoff.SetUdp (Ipv4Address ("10.1.1.2"), port);
>
> PacketSinkHelper sink;
> sink.SetUdp (Ipv4Address::GetAny (), port);
I like the Local/Remote version much better than the SetUdp version.
> I think there could be another method added to the device helpers to "help"
> people who just have one kind of device they want to trace. For example
>
> CsmaHelper::EnableAscii ("csma-one-subnet.tr");
>
> instead of having to write out
>
> std::ofstream ascii;
> ascii.open ("csma-one-subnet.tr");
> CsmaHelper::EnableAscii (ascii);
>
> every time, even though you might not be interested in sharing the stream
> with other device types. This is a lot of stuff to type for the simple
> (common?) case.
ok.
>
> Finally, I noticed that the idiom,
>
> NodeContainer c;
> c.Create (3);
> NodeContainer c0 = NodeContainer (c.Get (0), c.Get (1));
>
> is fairly common. If you just read over this quickly, you may assume that
> there is a constructor in NodeContainer that takes two Ptr<Node>s and
> creates a new container out of them. That's not quite what is going on.
> There is a constructor that takes two things, but those things are
> NodeContainer references. What is happening here is an implicit conversion
> by constructor from Ptr<Node> to NodeContainer and then the union of the two
> NodeContainers is taken and the resulting container is assigned to c0.
>
> Now, this is pretty slick, and allows you to do things like,
>
> NodeContainer c;
> c.Create (3);
>
> NodeContainer d;
> d.Create (3);
>
> NodeContainer c0 = NodeContainer (c, d.Get (1));
>
> But it is not at all obvious how this works unless you know C++ fairly well.
> We did spend some time in the tutorial going over unnamed parameters and
> implicit conversion sequences when dealing with things like Ipv4Address. I
> think this may be another step out in left field when we're talking about
> "things" implicitly converted to "containers of things" and taking unions of
> containers when you think you're adding a couple of node ptrs.
>
> Anyway, it struck me that this is a relatively sophisticated construct to
> find living in what was advertized as a kind of simple introductory API for
> people who weren't yet ready to see Ptr<Blah> blah = Create<Yadda> ();
>
> It may merit some discussion in a tutorial or judicious use limited to more
> advanced examples. Perhaps a more transparently obvious addition would be
> methods that actually do take multiple Ptr<Node>. We already have
>
> NodeContainer c;
> c.Add (c.Get (0));
>
> Perhaps we could make this a little less hip, slick and cool and add new
> methods that actually take Ptr<Node> to parallel the ones that take
> NodeContainer &. I think this would be much more understandable for our new
> users trying to grok the system. If you're a hot C++ stud, you could
> understand and use the other container reference method when it's
> appropriate.
I think that while understanding the way this works is non-trivial, the
use ends up being trivial: just remember that wherever you see a
NodeContainer, you can pass a Ptr<Node> and, wherever you see a
NetDeviceContainer, you can pass a Ptr<NetDevice>. This is really just
like std::string which has a std::string (const char *) and which allows
you to write:
void Foo (std::string str);
Foo ("Foo");
Understanding how this works takes a fair bit of c++ sophistication but
pretty much every c++ user knows how to _use_ this API so, I think that
this is really just a matter of getting used to the idiom.
In the end, though, the whole point of this "sophistication" is to save
those who write helpers from writing quite a bit of extra overloaded
functions/methods to handle the two cases: a single pointer or a node
container so, if you don't care about this or if you think that it is ok
to change
NodeContainer n01 = NodeContainer (n.Get (0), n.Get (1));
to:
NodeContainer n01;
n01.Add (n.Get (0));
n01.Add (n.Get (1));
then, I guess I am fine with it.
(The latter became sufficiently painful to handle for me as I went
through a number of our example scripts which have a lot of point to
point links that I found the former much easier to read)
regards,
Mathieu
More information about the Ns-developers
mailing list