[Ns-developers] helper APIs, and planning for ns-3.1 release
craigdo@ee.washington.edu
craigdo at ee.washington.edu
Wed Apr 2 15:00:28 PDT 2008
[ ... ]
> 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.
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 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?
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 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.
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.
-- Craig
[ ... ]
More information about the Ns-developers
mailing list