== Exercise 2: Measuring Performance of a !MobilityFirst Router == [[TOC(Tutorials/oMF*, depth=2)]] === !Design/Setup === ==== Objective ==== In this exercise, we will try to drive synthetic traffic through the router and measure key performance characteristics such as throughput and forwarding latency. Since !MobilityFirst presents a hop-by-hop block data transport, we can vary the unit size of the data block and observe it's impact on the performance. We will also try to visualize the performance results using OMF's result service by installing an OML-enabled monitor on the routers. ==== Pre-requisites ==== * Experimenters are expected to have basic networking knowledge and familiarity with Linux OS and some of its tools (command line tools, ssh, etc.). * An ORBIT user account. * Some familiarity with the !MobilityFirst terminology. ==== Deploy the Network ==== This tutorial assumes that a 4 nodes topology has been already established in one of the Orbit sandboxes or the grid: [[Image(Tutorials/oMF:MFTurorialNetwork.png)]] If not coming from [wiki:Tutorials/oMF/tut1 exercise 1] follow these instructions on how to setup the topology. Running exercise 1 at least once before moving to exercise 2 is advised to understand the steps and software components involved. [[CollapsibleStart(4 nodes topology setup)]] First of all, log in into the grid console using SSH: {{{ #!sh ssh username@console.grid.orbit-lab.org }}} For simplicity, open 3 different consoles on your laptop and access the grid's console with all of them; you will need them in the continuation of the exercise. From the console we will start loading the !Mobilityfirst image into the four nodes that have been assigned to you: {{{ #!sh omf load -i 'mf-release-latest.ndz' -t system:topo:mf-groupX }}} ''system:topo:mf-groupX'' represents the topology of 4 nodes that has been assigned to you're group and ''mf-groupX'' has to be replaced by the group id assigned to you. For example, ''mf-group1'' will load the image on nodes 'node20-20,node20-19,node19-19,node19-20' If at the end of the execution, the final output of your console looks similar to: {{{ #!sh INFO exp: ----------------------------- INFO exp: Imaging Process Done INFO exp: 4 nodes successfully imaged - Topology saved in '/tmp/pxe_slice-2014-10-15t02.10.16.594-04.00-topo-success.rb' INFO exp: ----------------------------- INFO EXPERIMENT_DONE: Event triggered. Starting the associated tasks. INFO NodeHandler: INFO NodeHandler: Shutting down experiment, please wait... INFO NodeHandler: INFO NodeHandler: Shutdown flag is set - Turning Off the resources INFO run: Experiment pxe_slice-2014-10-15t02.10.16.594-04.00 finished after 1:50 }}} your nodes have been imaged correctly. ==== Deploy Network ==== Software and experiment control in the ORBIT testbed can be automated greatly using the OMF framework. An OMF control script is written in Ruby and allows the experimenter to specify the set of nodes, their network configuration, to specify software components and arguments, and to control their execution on one or more nodes. We will use an OMF script to bring up 4 ORBIT nodes to host our topology, with the corresponding software components. We will first introduce the main details of the scripts that will be run and then we will step to the execution process itself. ===== Software Component Specification ===== The following snippet shows the specification of the !MobilityFirst components along with the required arguments. A typical application will have at least a brief description, a path for the associated binary to execute and a list of properties that correspond to the parameters that will be passed once starting the executable. {{{ #!ruby #Application definition of a MobilityFirst access router defApplication('MF-Router', 'router') {|app| app.shortDescription = "Click-based MobilityFirst Access Router" app.path = "/usr/local/src/mobilityfirst/eval/orbit/tutorial/scripts/ARWrapper.sh" # click options app.defProperty('num_threads', 'number of threads', "-t",{:type => :integer, :mandatory => true, :default => 4, :order => 1}) app.defProperty('ctrl_port', 'port for Click control socket', "-c",{:type => :integer, :order => 2}) # click config file app.defProperty('config_file', 'Click configuration file', "-C",{:type => :string,:mandatory=> true}) # keyword parameters used in click config file app.defProperty('my_GUID', 'router GUID', "-m",{:type => :string, :mandatory => true}) app.defProperty('topo_file', 'path to topology file', "-f",{:type => :string, :mandatory => true}) app.defProperty('core_dev', 'core network interface', "-d",{:type => :string,:mandatory => true}) app.defProperty('GNRS_server_ip', 'IP of local GNRS server', "-s",{:type => :string,:mandatory => true}) app.defProperty('GNRS_server_port', 'Port of GNRS server', "-p",{:type => :string,:mandatory => true}) app.defProperty('GNRS_listen_ip', 'IP to listen for GNRS response', "-i",{:type => :string,:default => "0.0.0.0"}) app.defProperty('GNRS_listen_port', 'port to listen for GNRS response', "-P",{:type => :string,:default => "10001"}) app.defProperty('edge_dev', 'edge network interface', "-D",{:type => :string,:mandatory => true}) app.defProperty('edge_dev_ip', 'IP assigned to edge interface', "-I",{:type => :string,:mandatory => true}) } #Application definition of a GNRS server defApplication('MF-GNRS', 'gnrs') {|app| app.shortDescription = "GNRS Server" app.path = "/usr/local/src/mobilityfirst/eval/orbit/tutorial/scripts/GNRSWrapper.sh" app.defProperty('log4j_config_file', 'log 4j configuration file', "-d",{:type => :string, :order => 1}) app.defProperty('jar_file', 'server jar file with all dependencies', "-j" ,{:type => :string, :mandatory=> true, :default => "/usr/local/src/mobilityfirst/gnrs/jserver/target/gnrs-server-1.0.0-SNAPSHOT-jar-with-dependencies.jar", :order => 2}) app.defProperty('config_file', 'server configuration file', "-c",{:type => :string, :mandatory=> true, :order => 3}) } #Application definition of the client network protocol stack defApplication('MF-HostStack', 'hoststack') {|app| app.shortDescription = "MF host network stack" app.path = "/usr/local/bin/mfstack" app.defProperty('log_level', 'log level', nil,{:type => :string, :mandatory => true, :order => 1, :default => "-e"}) # default is 'error' app.defProperty('config_file', 'stack configuration file', nil,{:type => :string, :mandatory => true, :order => 2}) } }}} A few considerations on the defined applications: * As seen above, the router is configured with both 'core' (''core_dev'') and 'edge' (''edge_dev'') interfaces. Different router configurations are available depending on the required functionality. In this case we use what we call a !MobilityFirst Access Router, which has the particularity of having the core interfaces connected towards the core of the network, while the edge interface enables hosts to connect and access the !MobilityFirst network. * For this basic setup, the GNRS has been configured to be running as a single server instance, but in a larger experiment, it is designed to be a distributed system deployed at different locations. * Most of the client settings are located in a configuration file pre-loaded on the ORBIT image in the folder ''/usr/local/src/mobilityfirst/eval/orbit/conf/''. ===== Setting up the Software Node Groups ===== The following snippet shows how the node groups for the routers are setup in the OMF control scripts. Node groups allow experimenters to use single statements to set configuration (e.g. network interfaces) and execute commands across all nodes belonging to the group. {{{ #!ruby #Create router groups for i in 1..num_routers #Create a topology with a single router in it defTopology("topo:router_#{i}") { |t| aNode = routersTopo.getNodeByIndex(i-1) t.addNode(aNode) info aNode, " assigned role of router with GUID: #{i}" } #Through the group definition we set up the applications to run defGroup("router_#{i}", "topo:router_#{i}") {|node| node.addApplication('MF-Router') {|app| app.setProperty('num_threads', router_threads) app.setProperty('config_file', click_conf) app.setProperty('my_GUID', router_guid[i-1]) app.setProperty('topo_file', rtr_topo_file) app.setProperty('core_dev', core_dev) app.setProperty('GNRS_server_ip', GNRS_server_ip) app.setProperty('GNRS_server_port', GNRS_server_port) app.setProperty('GNRS_listen_ip', "192.168.100.#{i}") app.setProperty('GNRS_listen_port', GNRS_listen_port) app.setProperty('edge_dev', edge_dev) app.setProperty('edge_dev_ip', router_ether_if_ip[i-1]) } #If it is the first router add the GNRS if i == 1 aNode = routersTopo.getNodeByIndex(i-1) info aNode, " will also host the GNRS server" node.addApplication('MF-GNRS') {|app| app.setProperty('log4j_config_file', GNRS_log_file) app.setProperty('jar_file', GNRS_jar_file) app.setProperty('config_file', GNRS_conf_file) } end #Setup the node interfaces #The first ethernet interface is used as the core interface node.net.e0.ip = "192.168.100.#{i}" node.net.e0.netmask = '255.255.255.0' #The first wireless interface is used to give access to clients node.net.w0.mode = "adhoc" node.net.w0.type = 'g' node.net.w0.channel = "11" node.net.w0.essid = "SSID_group_#{i}" node.net.w0.ip = "192.168.#{i}.1" } end #Create host groups for i in 1..num_hosts #Create a topology with a single router in it defTopology("topo:host_#{i}") { |t| aNode = hostsTopo.getNodeByIndex(i-1) t.addNode(aNode) info aNode, " assigned role of client with GUID: #{100 + i}" } #Through the group definition we set up the applications to run defGroup("host_#{i}", "topo:host_#{i}") {|node| node.addApplication('MF-HostStack') {|app| app.setProperty('config_file', hoststack_conf_file[i-1]) app.setProperty('log_level', log_level) } #The first wifi interface is used to connect to the Access Router node.net.w0.mode = "adhoc" node.net.w0.type = 'g' node.net.w0.channel = "11" node.net.w0.essid = "SSID_group_#{i}" node.net.w0.ip = "192.168.#{i}.2" } end }}} As it can be seen above, once defining applications that each group will execute, the application properties are set accordingly. While we do not want to enter the details of each parameter, it is important to notice how by simple use of counters, the different nodes can be assigned different values. Moreover, resources such node interfaces and their corresponding IP addresses have to be set up in this phase of the experiment. As we discussed earlier the router is configured with both edge and core interfaces. An ethernet interface is used to connect to 2 core routers, while a wireless interface is used to provide access for the clients. [[CollapsibleEnd]] ===== Setting up the 'OML enabled Monitor on Routers Application' ===== At this point, the network topology described and initialized in Exercise 1 is supposed to be ready and functional. In order to produce synthetic traffic, we will use mfping to send packets between the end hosts. In order to perform more advanced network measurements, other applications are also available, such as a modified version of the commonly used application ''iperf''. As per the goal of the exercise, we will use an OML-enabled statistics monitor for !MobilityFirst routers in order to collect usage statistics on the nodes. The key extensions over exercise 1's script are briefly discussed below. The following snippet from the script shows the code added to set up the OML enabled Monitor on Routers Application and its arguments: {{{ #!ruby defApplication("mf_click_monitor", "mf_click_monitor") do |app| app.shortDescription = "OML enabld statistics monitor for MobilityFirst Routers" app.path = "/usr/local/bin/mf_click_mon" app.defProperty('ctrl_port', 'Port for Click control socket', nil,{:type => :string, :mandatory => true, :order => 1}) app.defProperty('self-id', 'OML ID', nil,{:type => :string, :mandatory => true, :order => 2}) app.defProperty('oml-config-file', 'OML configuration file', "--oml-config",{:type => :string,:mandatory=> true}) app.defProperty('oml-domain', 'OML domain name', "--oml-domain",{:type => :string,:mandatory=> true}) end self_id = "MonitorID" oml_config_file = "/usr/local/src/mobilityfirst/eval/orbit/tutorial/conf/click-oml-config.xml" oml_domain = "#{Experiment.ID}" defGroup("router_monitors", "router_universe") {|node| node.addApplication('mf_click_monitor') {|app| app.setProperty('ctrl_port', router_control_port) app.setProperty('self-id', self_id) app.setProperty('oml-config-file', oml_config_file) app.setProperty('oml-domain', oml_domain) } } }}} As seen above, the OML enabled monitor will work with the !MobilityFirst router and it will enable us to track and visualize the forwarding performance of the MFRs in real time. In order to report statistics to the ORBIT's OML server, the monitor periodically queries the router through a socket control port (''ctrl_port'' in our script). Using the conveniency of OML we can define different characteristics of the stream of measurements going to the OML server. This is done using an XML configuration file; in our case the following configuration file has been preloaded on the nodes: {{{ #!xml }}} After running the experiment which should be able to retrieve statistics on a per sample base divided among different groups, based on the analyzed layer (i.e. network layer, link layer, physical layer). === Execute === ===== Running the Benchmark Application ===== To generate the traffic that will be reported by the routers, we will use the same ''mfping'' application as in the previous exercise. First of all, you will need to start the experiment via an OMF script. Download the script to the orbit console copying and pasting the following command in your terminal: {{{ #!sh wget www.winlab.rutgers.edu/~bronzino/downloads/orbit/exercise2.rb }}} Once the file has been downloaded, execute it with the following command: {{{ #!sh omf exec exercise2.rb }}} Once your experiment is showing you the following line: {{{ #!sh INFO exp: Request from Experiment Script: Wait for 10000s.... }}} you can proceed running the ping application. If coming from exercise 1, you will only need to repeat the same procedure. For conveniency, the detailed steps on how to run the application follow. [[CollapsibleStart(Instructions on how to run the mfping application)]] You can now proceed: using the previously opened consoles log in into the two host nodes (GUIDs 101 and 102) that will be used to run the simple 'mfping' application. In order to access a running Orbit node ssh into it as follow: {{{ #!sh ssh root@nodex-y }}} Where ''x-y'' has to be replaced by the actual numbers identifying the node. Once logged in, run on the node with GUID 102 the mfping 'server' component properly specifying the application GUIDs: {{{ #!sh mfping -s -m 102 -o 101 }}} where "-s" specifies that the host is running as server, "-m" specifies the source guid and "-o" the destination one To run the mfping 'client' start on the client with GUID 101 the command: {{{ #!sh mfping -c -m 101 -o 102 -n 10 }}} Where "-c" specifies the client is running and "-n" specifies the number of pings between the two nodes. If successfully executed, the client will display some information similar to the following snippet {{{ #!sh root@node1-1:~# mfping -c -m 101 -o 102 -n 10 64 bytes received: seq_n=0, time=25.1470 msec 64 bytes received: seq_n=1, time=23.7070 msec 64 bytes received: seq_n=2, time=20.0559 msec 64 bytes received: seq_n=3, time=24.0371 msec 64 bytes received: seq_n=4, time=23.1831 msec 64 bytes received: seq_n=5, time=20.3069 msec 64 bytes received: seq_n=6, time=24.1379 msec 64 bytes received: seq_n=7, time=19.6230 msec 64 bytes received: seq_n=8, time=20.3931 msec 64 bytes received: seq_n=9, time=20.2239 msec }}} [[CollapsibleEnd]] ===== Visualizing the Performance Data ===== '''Method 1:''' the OMF framework supports a result service that allows experimenters to query data stored using the OML measurement framework. The query is performed over the web and requires that you know the ''experiment ID'' associated with your experiment - this is obtained from the output following the execution of the control script. It should look something like this : {{{ #!sh Experiment ID: default_slice-2014-10-15t02.12.19.869-04.00 }}} The result service supports either dumping of the entire database or a SQL based querying option to selectively retrieve required measurement data. The below HTTP request shows an example query to retrieve the reported statistics from the OML enabled monitor for !MobilityFirst Routers. In order to see the results the following web page should be requested using any browser. The following URL should be typed in the browser replacing 'experimentID' with the ID associated with your experiment: {{{ #!sh http://oml.orbit-lab.org:5054/result/dumpDatabase?expID=experimentID }}} In this case the hostname is "oml.orbit-lab.org" and the port number is "5054". '''Method 2:''' Alternatively, the performance data may be visualized using a tool called ''omf-web'', an OMF web-based visualization service. It also works in concert with the result service referenced in Method 1, and makes available a variety of graph widgets to visualize live-experiment data logged using OML. Documentation on the installation and usage of omf-web can be found on the [https://github.com/mytestbed/omf_web omf-web github site]. === Finish === Once the application has successfully completed its task, follow these steps to complete the experiments: * Kill the ''mfping'' server using Ctrl-C on the corresponding node. * On the grid's console running the experiment script, interrupt the experiment using the Ctrl-C key combination. This will stop all the applications and will conclude the experiment. ==== References ==== For more information regarding the !MobilityFirst project, visit the project page: http://mobilityfirst.winlab.rutgers.edu/ For more information regarding the prototype design and updated status, visit the wiki page: https://mobilityfirst.orbit-lab.org/