Pydtn Simulator Documentation

Overview

This package provides the framework for a very simple simulator. The simulator is event-based, and comprises a set of abstract building blocks. These building blocks can be specialized almost arbitrarily in order to construct a full simulation environment for just about any task. Our design intent, however, is to provide a test harness for code that will eventually be glued into a more sophisticated simulator, or even a concrete implementation.

We divide the simulator along two directions. At the higher level, the simulator is divided between the executable harness and dynamically loadable modules. This makes the harness (which is this package) easier to write and maintain. It also provides for greater flexibility, since detailed functionality is pushed to modules, which can potentially be combined into increasingly complex models.

At the lower level, the simulator is also divided between mechanism and interaction. The mechanism portion of the simulator is written in C++, and all modules should similarly be written (at least partially) in C++. For control of the simulation, which includes both interactive control and batch scripting, a separate interpreted language is used. The interpreter is embedded within the harness executable, so that running the simulator also starts the interpreter. Code that is specific to a particular interpretive language is isolated into a single file, in order to simplify additional implementations in other languages.

When running the simulator, the first thing that must be done is to configure the various elements that will make up the network. At least one event should also be scheduled. When the simulator is told to run, it will process events until either there are no more remaining in its queue or an event triggers a breakpoint. Using breakpoints rather than terminating the simulation allows us to examine and potentially modify the system before deciding whether to exit or continue running from where we stopped.

Structure of the C++ Code

Basic Objects and Concepts

There are two basic building blocks in the simulator: Events and Entitys. An Event is anything that happens in the simulator. It might cause a state change, represent information moving through the network, or implement anything else that occurs at some specific time. Events are scheduled with the Clock, which is a static class. That is, all of Clock's data and methods are declared static, and no instances of this class can be created. Clock uses a Time structure to keep track of the time, which corresponds to the standard timeval struct. Event is an abstract class, so concrete subclasses must be defined in order to generate actual events. The harness provides one subclass: DataEvent. This class holds a Data element containing a byte string, which is useful for representing packets, bundles, filesystem data, and so on.

An Entity produces and consumes Events. Nearly every "thing" in a simulation will be an Entity, such as nodes, links, or data analyzers. A specific type of Entity will be able to handle certain types of Events, and a specific type of Event will generally only be able to take certain types of Entitys as destinations. The Entity base class only knows about DataEvents, since those are sufficiently generic that we can reasonably expect all Entitys to handle them (though a few might return without doing anything). One concrete Entity subclass is defined: Terminator. The only purpose of this class is to produce and consume Events that cause the simulation to stop. As noted above, these are effectively breakpoints, and the internal state of the simulator is otherwise left unchanged. Most Entitys will need to be entered into the Registry, so that they can be created from the interpreter. Terminator is an exception to this, and some other Entitys might also not be made visible to the interface.

Writing Modules

Modules are where we specify actual Entitys that perform interesting actions and the Events they employ. These should be linked into dynamic libaries that can then be loaded at run time by the interpreter. A module will have some bootstrapping routine that is called when it is loaded. This routine should call add_entity() for each concrete subclass of Entity that it defines and wishes to make available through the interpreter. Each concrete subclass of Entity must have a unique identifying string that is returned by its identifier() method. For example:
 class GeosynchronousSatellite : public Entity
 {
    public:
       // ...
       std::string identifier() const { return "geosynchronous satellite"; }
       // ...
 };
This identifier is then used in the interpreter to clone the appropriate dummy instance of the Entity that is stored in the Registry.
Note:
All registrations must be done through add_entity(), not by calling Registry::insert() directly. This is because Registry cannot know if there is any interpreter-specific configuration that must be done.
An Entity must also define all of its Event handlers, as well as its configure() and emit() methods. The last two take a vector of strings containing the actual arguments, and should be thought of as essentially the same as argc and argv. The Entity is responsible for ensuring that its arguments are correct in number and type. Entitys referenced within the interpreter are passed as string tokens that may be passed to resolve_symbol().
See also:
interpreter_hooks.h

Interfacing with the Interpreter

The Makefile distinguishes between two types of source files. Most are compiled and linked into any executable, but files with names like X_main.cc are taken to be interpreter glue, where X is the language of the interpreter. The interpreter hooks should be defined here. These glue files also include the main routine.

The general pattern for an interpreter interface is that modules are loaded, the network is configured, and some number of events are scheduled. The simulator is then run, and if not told to exit control is returned to the interpreter in interactive mode.

Python

The Python interface is defined in python_main.cc. In addition to starting the interpreter, this file also defines a module sim, which has the following methods:

The sim.Entity class has the following members and methods:

An example of a simple simulation might look as follows:

 import mysim
 node1 = sim.Entity("node")
 node2 = sim.Entity("node")
 link = sim.Entity("link")
 node1.config("link",link,node2)
 node2.config("link",link,node1)
 link.config("bandwidth",100)
 link.config("latency",30)
 d = "hello world"
 node1.emit([0,1],"data",d,link)
 sim.stopAt(sim.time() + [0,1000])
 sim.run()

We might run this code by executing

 python_main
and typing it in by hand. However, if it is stored in a file sim.py, we can also run it with
 python_main sim.py
If we want to exit immediately upon completion, we could modify sim.py as follows:
 import mysim
 import sys
 node1 = sim.Entity("node")
 node2 = sim.Entity("node")
 link = sim.Entity("link")
 node1.config("link",link,node2)
 node2.config("link",link,node1)
 link.config("bandwidth",100)
 link.config("latency",30)
 d = "hello world"
 node1.emit([0,1],"data",d,link)
 sim.stopAt(sim.time() + [0,1000])
 sim.run()
 sys.exit()
sys is, in fact, loaded automatically, and so is redundant to import here, but we leave it in for illustrative purposes.

python_main can take any number of Python script files on the command line. It can also take arguments of the form

 python_main -i <module>
which will import the specified <module> before executing any scripts. That means we could remove the two import statements above and instead run
 python_main -i mysim -i sys sim.py

In order to import a module, it must be located in Python's search path. python_main adds the current directory (".") to its path automatically, but it might be more convenient to add other directories to the path. There are two ways to do this. The first is to preface your scripts with

 sys.path.append('/path/to/your/modules')
The other is to use the python_main command-line option -L, which behaves like the C compiler flag in that it adds its argument to the search path for libraries (which is what modules are). The equivalent command-line version of the above would be:
 python_main -L /path/to/your/modules

python_main does not take any other command line arguments at present. In particular, it cannot pass any of the standard Python flags to the interpreter.

pydtn

This is a module for the simulator that wraps the code in libdtn.a. The Entitys visible from the interpreter are WrapNode and WrapLink. WrapNode, as the name suggests, provides a wrapper around the DTN::Node class. WrapLink provides a wrapper around DTN::Link indirectly through the subclass SimLink, which implements the actual link functionality that we need. AliasLink is another subclass of DTN::Link, and is used in routing tables.

Because the example module provides a detailed discussion of writing a module, we will focus here on using the wrapper.

A Simple Example

We first look at a simple example. Consider the following network:

inline_dotgraph_1.dot

Nodes are shown with their network addresses, and links with their prefix matches. We want to set up this network and send a DTN::Bundle from 01 to 11. This is done with the following script using the python interpreter.

# Copyright 2008 Michael Marsh, University of Maryland.
#
# This file is part of pydtn.
#
# pydtn is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pydtn is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pydtn.  If not, see <http:#www.gnu.org/licenses/>.
#
# The views and conclusions contained in the software and documentation
# are those of the authors and should not be interpreted as representing
# official policies, either expressed or implied, of the University
# of Maryland.
#
# pydtn extends and embeds the Python interpreter, which is
# Copyright 2001-2006 Python Software Foundation, All Rights Reserved,
# and is released under the PSF License Agreement.
#
# RANLUX random number generation uses the Boost library,
# Copyright 1994-2006 by various authors (details in individual files),
# which is released under the Boost Software License, Version 1.0.

import pydtn
import pydtn.namtrace
import pydtn.network
import pydtn.flowtrace
import pydtn.storetrace
import pydtn.storeprofile
import pydtn.sampleapp

pydtn.namtrace.setup("example.nam")
pydtn.flowtrace.setup("example.flow")
pydtn.storetrace.setup("example.stor")
pydtn.storeprofile.setup("example.storprof")
pydtn.config("bundle_lifetime",[0,9000]);
pydtn.stats( "stats.out", 1000 )
pydtn.network.algorithm("forwarding:routed:explicit") # this is the default

# Create three nodes.
node1 = pydtn.network.addNode("\0\1",capacity=4096,stable=10000)
node2 = pydtn.network.addNode("\1\0",capacity=4096,stable=10000)
node3 = pydtn.network.addNode("\1\1",capacity=4096)
node2.config("custody","spaceavail")
node2.config("resend",[0,5000])

# Create links 1 <--> 2 <--> 3
link12 = pydtn.network.addLink( node1, node2, latency=0.003, bandwidth=100 )
link21 = pydtn.network.addLink( node2, node1, latency=0.003, bandwidth=100 )
link23 = pydtn.network.addLink( node2, node3, latency=0.003, bandwidth=100 )
link32 = pydtn.network.addLink( node3, node2, latency=0.003, bandwidth=100 )

# Set up routing.
pydtn.network.route()

pydtn.network.applyToNodes(pydtn.storeprofile.collect)
pydtn.network.applyToNodes(pydtn.sampleapp.attach)

# Schedule a data event.
node1.emit([0,1],"data",node3,"hello world")
node1.emit([0,2],"data",node3,"hello world")
node1.emit([0,3],"data",node3,"hello world")

# Send a message from an application.
pydtn.sampleapp.send(node1,node3,"app data",[0,50])

for i in range(1000):
    node1.emit([0,100],"data",node3,"hello world")

for i in range(20):
    node1.emit([0,100000],"datafake",node3,10240)

def td( now ):
    t = now
    t[0] += 0
    t[1] += 100000
    return t

def gen():
    node1.emit(sim.time(),"datafake",node3,10)

tg = sim.Entity("trafficgen")
tg.config("time_dist",td)
tg.config("generator",gen)
tg.emit([0,5000])

sim.stopAt([0,600000])

# Start the simulation.
sim.run()

# Stop the interpreter.
sys.exit()

This script should be mostly self-explanatory. The empty string in

 link.config("prefix","")

Specifies that this link should be added to the routing table with a 0-length prefix. That is, if nothing else matches for a destination, this will.

When run, the following output is produced:

 0001 adding route for prefix of length 0
 received data at 00 01 
 sending data from 00 01 
 looking for a route from 0001 to 0101
 received data at 01 01 
 consuming data at 01 01 
    11 bytes
    68 65 6C 6C 6F 20 77 6F 72 6C 64 
 

You can match up the route configuration and the subsequent path taken by the DTN::Bundle through the network. Output is generated each time a DTN::Node's recv or send method is called, and again when the DTN::Bundle is finally "consumed" at its destination.

A More Complicated Example

The previous example is excessively simple, and demonstrates little of the system's functionality. A more interesting example is the following network.

inline_dotgraph_2.dot

Once again, we want to send a DTN::Bundle from 01 to 11, but this time there is another node, 10, between them. Note that 10 has two routes, one for addresses beginning with 0 and one for addresses beginning with 1.

The script that sets up this network and sends the data is:

# Copyright 2008 Michael Marsh, University of Maryland.
#
# This file is part of pydtn.
#
# pydtn is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pydtn is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pydtn.  If not, see <http:#www.gnu.org/licenses/>.
#
# The views and conclusions contained in the software and documentation
# are those of the authors and should not be interpreted as representing
# official policies, either expressed or implied, of the University
# of Maryland.
#
# pydtn extends and embeds the Python interpreter, which is
# Copyright 2001-2006 Python Software Foundation, All Rights Reserved,
# and is released under the PSF License Agreement.
#
# RANLUX random number generation uses the Boost library,
# Copyright 1994-2006 by various authors (details in individual files),
# which is released under the Boost Software License, Version 1.0.

import pydtn
import pydtn.network

pydtn.network.algorithm("forwarding:routed:explicit") # this is the default

# Create three nodes.
node1 = pydtn.network.addNode("\0\1")
node2 = pydtn.network.addNode("\1\0")
node3 = pydtn.network.addNode("\1\1")

# Create links 1 <--> 2 <--> 3
link12 = pydtn.network.addLink( node1, node2, latency=3000, bandwidth=100 )
link21 = pydtn.network.addLink( node2, node1, latency=3000, bandwidth=100 )
link23 = pydtn.network.addLink( node2, node3, latency=3000, bandwidth=100 )
link32 = pydtn.network.addLink( node3, node2, latency=3000, bandwidth=100 )

# Set up routing.
pydtn.network.route()


# Schedule a data event.
node1.emit([0,1],"data",node3,"hello world")


# Start the simulation.
sim.run()

# Stop the interpreter.
sys.exit()

Aside from the increased number of Entitys, there are two things to note in this example. The first is that we are now specifying non-empty prefixes for the links link21 and link23. In particular, 10 has no default route. The other thing to note is that 01 is now specifying a destination to which it has no direct link.

The output for this script is:

 0001 adding route for prefix of length 0
 0100 adding route for prefix of length 1
 0100 adding route for prefix of length 1
 0101 adding route for prefix of length 0
 received data at 00 01 
 sending data from 00 01 
 looking for a route from 0001 to 0101
 received data at 01 00 
 sending data from 01 00 
 looking for a route from 0100 to 0101
 received data at 01 01 
 consuming data at 01 01 
    11 bytes
    68 65 6C 6C 6F 20 77 6F 72 6C 64 
 

From this, we can see how the data has travelled through the network from 01 to 10 to 11.


Generated on Mon Mar 24 11:15:45 2008 for Pydtn Simulator by  doxygen 1.5.4