Advanced network example #1
Description
This model extends the previous one by adding a new TCP sender and a new router to handle the acknowledgments sent by the TCP receiver. In addition to this extended network topology, this example also covers the following subjects:
- Definition of multiple data flows
- Logging to HDF5 files
- Parameter sweeping (in
py2pdevs
)
Being an advanced example, it is mostly oriented to
py2pdevs
users. Nevertheless, we will also explain how to experiment with it through the
PowerDEVS GUI.
Model creation in py2pdevs
As we did before, create a new file named
network_advanced_1.py
inside
build/lib
and paste the following code:
import sys
# Make py2pdevs package visible. An alternative is to export the
# environment variable PYTHONPATH appeding the absolute path to build/lib
sys.path.append('../../../../build/lib')
import py2pdevs.network as network
import py2pdevs.library.datanetworks_ext as library
from py2pdevs.network.distributions import Normal, Exponential
def network_advanced_1():
# Sweep through sender bandwidth.
experiment = 0
for bandwidth in [9800, 98000, 980000]:
run_model(experiment, bandwidth)
experiment += 1
def run_model(experiment, bandwidth):
model_name = 'network_advanced_1'
global_params = {"TCP.CWND_max":250, 'TCP.CWND_ssthresh': 64, 'TCP.RTT_initial': 0.2, "MSS":1024,
# 'debugLevel': 999999999, # log debug messages (huge files and slow)
'TCP_SND.logLevel': 1000, # log CWND
'queue.logLevel': 1000, # log queues
} #
model = network.new_model(model_name, params = global_params,
ExperimentNumber=experiment, # set the experiment number (eg: results are stored in different files)
bandwidth=bandwidth, # common link speeds in all network
propagationDelay=0.001)
# Create network objects.
sender1 = library.TcpSenderExt(model, 'Sender1', set_samplers = False)
sender2 = library.TcpSenderExt(model, 'Sender2', firstPort=1, set_samplers = False) # both send to the same receiver, so we need to set up different ports
router = library.RouterExt(model, 'Router1', bufferSize=2977000)
receiver = library.TcpReceiverExt(model, 'Receiver', n_sessions=2)
# Define the 2 data flows
flow1 = model.new_flow("Sender1_flow", src=sender1, dst=receiver, packet_size=Normal(3624, 800))
flow1.set_route((sender1, 0, 0), (router, 1, 0), (receiver, 0, 0)) # route is in the form : (node1, inport, outport), ...
flow2 = model.new_flow("Sender2_flow", src=sender2, dst=receiver, packet_size=Exponential(2000)) # use exponential packet sizes
flow2.set_route((sender2, 0, 0), (router, 2, 0), (receiver, 0, 0))
# Run model and finalize.
model.run()
print ("Simulation %d finished" % experiment)
if __name__ == '__main__':
network_advanced_1()
Network topology
We will first discuss the new network topology. Observe how network objects are created:
- We have two TCP senders, Sender1 and Sender2, which have respectively IP addresses
10.147.0.16
and 10.147.0.17
. If you recall from our previous basic model, no IP address was specified whatsoever. Although we can still simulate network models without strict IP addresses, it is quite useful to supply them if e.g. we wish to simulate an actual network.
- We also have two routers with different buffer sizes. Recall that we can override global parameters by providing them for each object creation, as it is happening here. Should we need routers with the same buffer size, it would be more convenient to declare a global
buffer_size
parameter when creating the root network model (as it happens e.g. with CWND_max
).
- We finally have a single TCP receiver with IP address
10.147.0.15
. This object will interact with both TCP senders through different TCP ports, as we will see below. The internal logic that allows this demultiplexation is hidden in the coupled DEVS model that represents the TCP receiver. In the case of py2pdevs
, the method NetworkModel::new_tcp_receiver
builds this coupled model through succesive calls to some atomic network models and their respective connections. We will delve more deeply into coupled model creation and modification in the final advanced network example.
Now take a look at the physical connections between them. When we call
NetworkModel::connect
to perform a physical connection between two network objects, we need to provide the
source and the
destination. Each of these is a pair of a network object and an interface through which the connection is established --in DEVS jargon, it is an
input port (for the source) or an
output port (for the destination). So, for example, we see that
Sender2 uses interface 0 to connect to interface 1 of
Router1, or that
Router2 reaches
Sender2 via interface 1.
Flow definition
Up to this point, we have the network physically assembled but still no data exchange is possible. In order to achieve this, we need to define the
packet flows to connect the objects at the transport layer level. In networking, a packet flow is determined by the source and destination IP addresses and ports and by the transport protocol in use (usually TCP or UDP). In our models, we will mostly follow this definition. Note how the first flow,
flow1
, is constructed:
- It receives a
src
parameter indicating the source object, Sender1, and its (TCP) port, 5555.
- The
dst
parameter sets the destination of the flow: TCP port 80 in Receiver.
- We also identify an additional parameter named
packet_size
. This configures how TCP packets sent by the sender are generated: their sizes follow a normal distribution with mean 3624 and standard deviation 800. A similar parameter can be used to configure the interpacket time. This is the period
parameter, which defaults to a uniform distribution if not supplied.
- After creating the flow, the packet route is configured. This is very important, as it defines the exact path the packets will traverse in order to reach their destination. In
py2pdevs
a route is simply a sequence of pairs indicating one after one the network objects traversed and their out interfaces. In the case of the first flow, we see that packets start at Sender1, go through interface 0 to arrive at Router1 and leave through interface 0 to arrive at the Receiver. Note that the route ends where it started: acknowledgments leave the Receiver through interface 0, reach Router2 and then go through to interface 0 to finally arrive at Sender1.
After
flow1
, a second flow to connect
Sender2 and
Receiver is also constructed. Take a moment to analyze it and make sure you fully understand it.
Logging configuration & HDF5 output
If we would run the simulation here, packets will flow as expected but we will see no output at all, which in the end makes the whole effort pointless. So, the final step is to configure how logging should work. In other words, we have to instruct
PowerDEVS which variables are important for us so that it redirects their values to a suitable output channel. Typically, the default output channel is
Scilab (used in the basic network example), but it has some considerable drawbacks. We will now focus on
HDF5 logging.
HDF5 (Hierarchical Data Format) is a standarized file format to store and organize large amounts of data. When HDF5 logging is enabled in
PowerDEVS, a single output file named after the root model will be produced as simulation output. The user can then access the variables stored in it through any standard HDF5 reader of their preference. As we will discuss shortly, the recommended way to access this information is through the
py2pdevs
built-in HDF5 reader.
Going now back to our code, we can see a call to the method
NetworkModel::log
. This receives a list of logging variables that
PowerDEVS should produce. If no
logger
is specified, the default logger is used (other options include the
sampler logger and the
discrete sampler logger). Multiple calls to
log
can indeed be used to log different sets of variables for different logger types.
The first variable in the list is the TCP congestion window size (
CWND
) of
Sender1. As a given TCP sender might have several concurrent sessions, it is important to specify to which one this window is associated. This is achieved by providing the
flow object as argument: in this case, we see
flow1
, and thus the congestion window linked to this flow will be logged. The final parameter is an
alias: a unique identification string which we will later use to collect the data from the output. If no alias is specified, the full variable name will be used. Thus, it is strongly recommended to use aliases, as full variable names are complex since they reflect the inner coupled model structure, usually hidden from end users.
Parameter sweeping
Before running the script, go all the way back to the main function
network_advanced_1
. Remarkably short, this function sweeps a single parameter (the bandwidth of the two TCP senders) through three different values and builds an independent model for each one of them. Here it is clearly seen how Python versatility and flexibility can be fully leveraged for making easier our simulation tasks!
Running the script
Now proceed to run the script. Execute the following command in a terminal:
-
python network_advanced_1.py -tf 10 -variable_logging_backend hdf5
Note that we set 10 seconds and the final simulation time, and that we explicitly tell
PowerDEVS to use HDF5 logging. Recall that the default output channel is Scilab, so it is important to add this command-line argument if we wish to produce HDF5 output. After running network_advanced_1.py there should be 3 .h5 files in the directory where you run the script.
Run the plot.py script to read and plot the simulation results. For example (you can do the same for the other .h5 files):
- python ../plot.py network_advanced_1_1.h5
After a while a chart with three plots should pop up. This corresponds to the first iteration of the parameter sweeping. There you can see the congestion windows of both senders and the buffer size of the router. Note that in our model script we instructed
PowerDEVS to log precisely those three variables. If you close the chart, the script will continue with the second iteration of the sweeping, and eventually a second chart will be shown (it will take a little bit longer, though, since there will be more packets exchanged). Finally, a third simulation will be run once the chart is closed. The last plot generated should look similar to the following:
Output parsing & plotting
To understand how these plots are generated, take a look at the
plot.py
script. We use
matplotlib, a Python plotting library, and our built-in HDF5 reader specifically tailored to
py2pdevs
network models. As you can see, it is very straightforward: the reader is initialized by calling
NetworkModel::get_h5_reader
and it abstracts away all the access to the underlying HDF5 output produced by
PowerDEVS. In order to read a given variable, you can use the method
read_variable
, which expects a variable alias and returns two collections: the
times and
values of the output, respectively. Another method, not used here, is
read_all_variables
which, given an object (e.g.,
sender1
), returns the data of every variable logged for that object. This is provided by a dictionary indexed by aliases.
Complete model
For reference, you can find the complete model script in
examples/network/advanced_1/py2pdevs/network_advanced_1.py
.
Model creation through PowerDEVS GUI
As we did for the basic network model, open
PowerDEVS and create a new empty model. Then follow these steps:
- Add two
TcpSender=s, two =Router=s and a single =TcpReceiver_5sessions
(recall thay are provided by the Data Networks (High-Level)
library).
- Name them Sender1, Sender2, Router1, Router2 and Receiver, respectively.
- Connect these models as explained above: Sender1 to Router1 (first input port), Sender2 to Router1 (second input port), Router1 (first output port) to Receiver, Receiver to Router2 (first input port), Router2 (first output port) to Sender1 and Router2 (second output port) to Sender2.
- Now proceed to configure each model. Double-click on the *Sender*s and use the following values:
-
bandwidth
: bandwidth
(string values like this are parsed from the input file)
-
propagation
: delay
-
flowName
: Flow1
-
ipSender
: 10.147.0.16
for Sender1 or 10.147.0.17
for Sender2
-
ipReceiver
: 10.147.0.15
-
portReceiver
: 0
for Sender1 or 1
for Sender2 (this is the TCP port the Receiver will use to connect to each sender; for implementation reasons we should use ports starting from 0 and not actual TCP port values as we did in py2pdevs
)
-
TCP.MSS
: MSS
-
TCP.CWND_max
: 250
-
TCP.CWND_ssthresh
: 64
-
TCP.RTT_initial
: 0.2
- Use the following for the *Router*s:
-
bufferSize
: Router.bufferSize
(replacing Router
with Router1
or Router2
accordingly)
-
bandwidth
: bandwidth
-
propagation
: delay
- And the following for Receiver:
-
TCP.MSS
: MSS
-
bandwidth
: bandwidth
-
propagation
: delay
-
ipSender
: you should leave this field empty (see below)
-
ipReceiver
: 10.147.0.15
- As we have multiple senders, we need to edit the inner models manually to set the IP addresses. So, right-click on the Receiver and select
Open coupled
.
- Right-click on TCP_RCV_0 and click on
Edit...
. Go to the Parameters
tab, click on ipSender
and put 10.147.0.16
in the Value
field.
- Do the same for TCP_RCV_1 but change the IP to
10.147.0.17
. Also, put 0
in portSender
(this is because Sender2 uses port 0).
Execution
Compile the model by clicking on the
Simulate
button in the toolbar. Wait for the simulation window to pop up and then open a terminal and go to the
output
directory inside the
PowerDEVS root directory. Once there, execute the following command:
-
./model -tf 10 -c ../examples/network/advanced_1/network_advanced_1.params -variable_logging_backend hdf5 -finalization_script ../examples/network/advanced_1/plot.py
After some seconds a plot like the third one obtained through
py2pdevs
should appear.
Notes:
- As you might have noticed, we skipped parameter sweeping. Doing this through the PowerDEVS GUI is rather cumbersome, so we think it is better to leave it aside. The recommended way of sweeping parameters is by using
py2pdevs
.
- You might have also noticed that there was no mention to flow definitions. This is because the two flows are already present in the parameter definition file (
examples/network/advanced_1/network_advanced_1.params
). We suggest to check it out in order to understand how to write flows in models created through the GUI. The theoretical explanation of network flows given above is also valid in this scenario.
- The finalization script used here is essentially equivalent to the one shown above. Nevertheless, as
py2pdevs
is not used, HDF5 data should be read manually with Python. If you open that file, you will see that full variable names are used to retrieve the data.