[Ns-developers] finalizing the ns-3 object model (configuration)
craigdo@ee.washington.edu
craigdo at ee.washington.edu
Mon Jan 14 20:22:49 PST 2008
Hi all,
This is my $.02 on the topology and configuration sections of our
"finalizing the ns-3 object model" discussion.
I've been talking for some time about how hard it is to come up to speed on
ns-3. I've been commenting how sometimes it seems that everything in ns-3
is done in some unusual or unexpected way. I've used lots of terms to
describe what we do in some cases and how to make things easier to deal
with. Well, some of you may not be surprised that I'm going to begin my
comments on topology and configuration by introducing another new term (okay
two terms):
The Principle of Least Astonishment -- also known as the Rule of Least
Surprise. This was well put IMO by Raymond,
"[ ... ] to design usable interfaces, it's best when possible not to design
an entire new interface model. Novelty is a barrier to entry; it puts a
learning burden on the user, so minimize it. Instead, think carefully about
the experience and knowledge of your user base. Try to find functional
similarities between your program and programs they are likely to already
know about. Then mimic the relevant parts of the existing interfaces.
"The Rule of Least Surprise should not be interpreted as a call for
mechanical conservatism in design. Novelty raises the cost of a user's first
few interactions with an interface, but poor design will make the interface
needlessly painful forever. As in other sorts of design, rules are not a
substitute for good taste and engineering judgment. Consider your tradeoffs
carefully - and consider them from the user's point of view. The bias
implied by the Rule of Least Surprise is a good one to hold consciously,
mainly because interface designers (like other programmers) have an
unconscious tendency to be too clever for the user's good."
I'll also add a mantra that I spout quite often: Make simple things simple
and complex things possible. I've also run across this phrased as, "make
common things easy and rare things possible."
So, I'm going to come at this in a relatively abstract way and focus on:
novelty is a barrier to entry; interface designers have an unconscious
tendency to be too clever for the user's good; poor design will make the
interface needlessly painful forever; make common things easy and rare
things possible.
So, my first comment is, that when I saw:
Parameters params;
params.Set ("PointToPoint::ChannelDataRate", "5000000");
params.Set ("PointToPoint::ChannelDelay", "2");
Ptr<PointToPointChannel> channel0 =
PointToPointTopology::AddPointToPointLink ( n0, n2, params);
I was astonished. This code snippet seems to violate pretty much every
usability guideline for interface design I've mentioned. It's completely
new and unique. It makes a simple and common thing like passing 5000000 to
a function painful. This pain will last forever. I can see this kind of
stuff appearing all over our codebase. It does make rare things possible,
though, and strikes me as way to clever. It may work and it may enable some
really hairy reconfiguration down at the low levels of the code, but I'll
have a really, really hard time signing off on something like this.
I think it may be time to take a step back and have a look at the issues.
Tom identifies the following as important (most have been requirements for a
year):
1. write general topology or scenario code and allow it to be run
populated with different types of objects, such as swapping out
TCP variants
2. expose existing parameter configurations
3. allow users to create new default variables for new classes
4. hook into command line arguments
5. support scope control
It seems to me that there are two distinct things we've been talking about.
There is a configuration subsystem and there is a replaceable
component/topology subsystem.
If you do break these two major classifications of functionality out, I
think you can make a case for a simpler and easier to understand system.
Configuration subsystems are common and known by lots of people. Windows
people know the registry. Apple devotees know the System Configuration
Framework. Unix hackers understand .rc files. Thoroughly modern Linux
major-generals can deal with GConf. There are lots of well-known and
understood ways to configure complex systems. If you believe in the
principle of least surprise, we should configure ns-3 with something that
looks, at least conceptually, like everything else that configures systems.
I was thinking about this last Friday and it occurred to me that the
namespace in our tracing system gets us almost there. I have methods like
the following scribbled on my whiteboard:
Set ("/nodes/*/tcp/reno/ssthresh", "16000");
Set ("/nodes/1/tcp/rajno/cwnd". "4000");
I like this because it uses some of the same conceptual model that is
required to use our tracing subsystem. Perhaps tracing will be uncommon and
unusual, but that's for a good reason; and once you get past that barrier,
this will not be hard. If you actually work with the tracing
implementation, you could hook into the tracing callback mechanism to have
objects notified of run-time changes to their configuration parameters
(Mathieu has expressed interest in this).
I can also see this as an *easy* way to get information out of the bowels of
the system during or after simulations:
str = Get ("/nodes/1/tcpcraig/randomCounter");
NS_LOG_INFO ("the random counter ended up as " << str);
This is, I think, quite nice from an API point of view. An immediate issue
that comes to mind is that the objects in question must exist in order to be
configured in this way. So there needs to be some kind of global
configuration namespace:
Set ("/global/tcp/reno/ssthresh", "16000");
This could be written by scripts before they started creating objects or
given by command line. It could conceivably be done by reading an xml file
that has lines that look like:
<set key = "/global/tcp/reno/ssthresh", value = "16000"/>
This is a natural division for scoping of configuration information; and
notifications could be used to enforce finer granularity scoping.
One more issue is how to add new configuration variables and get them
documented. This seems to me to be similar to how we get new tracing
variables. The difference is in the global case -- perhaps static variables
-- needs to be investigated. Perhaps there is one documentation solution
for a unified tracing/configuration subsystem.
This approach would also work for the simple case Tom mentions where you
would want to substitute different kinds of "components" with identical
(ideally default) constructors. In this case you would need to describe
some kind of name to the system (in the global namespace) that could be
resolved into a typeid and constructed in the usual way.
Now, maybe this is naive, and maybe I'm on crack, but something along these
lines does seem to address the requirements and provide an interface that is
not too surprising, is relatively easily understood by a user, and makes the
simple and commonly done things relatively easy to do.
What it does not address is the (possibly/perhaps probably?) rare, hard to
do cases of generic topology. My hope is that this could be best addressed
with a rarely used, hard to understand API, leaving the basic stuff alone
and easy to deal with.
I think Mathieu's comments about data-flow versus control-flow are good.
I've been pondering this control-flow problem off and on for some time.
Hmmm. Inversion-of-control-flow?
Anyway, that's enough for this lengthy email. Comments?
-- Craig
More information about the Ns-developers
mailing list