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: network_advanced_1.png

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.
Edit | Attach | Watch | Print version | History: r6 < r5 < r4 < r3 < r2 | Backlinks | Raw View | WYSIWYG | More topic actions
Topic revision: r6 - 2020-05-02 - MatiasAlejandroBonaventura
 
    • Cern Search Icon Cern Search
    • TWiki Search Icon TWiki Search
    • Google Search Icon Google Search

    Main All webs login

This site is powered by the TWiki collaboration platform Powered by PerlCopyright &© 2008-2024 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
or Ideas, requests, problems regarding TWiki? use Discourse or Send feedback