Support dataplane subnet mask & latency histogram 93/71193/3
authorLuc Provoost <luc.provoost@intel.com>
Wed, 30 Sep 2020 14:57:30 +0000 (16:57 +0200)
committerXavier Simonart <xavier.simonart@intel.com>
Mon, 19 Oct 2020 08:50:26 +0000 (08:50 +0000)
There is a new parameter in the config_file: dataplane_subnet_mask. This
parameter is added to the dataplane IP addresses and is needed by the
Linux stack to deal with some protocols (e.g. ARP). If not present, the
default value of 24 will be used. In the end, this will make sure that
IP addresses in the rapid.env file will have this mask, e.g. /24
If you use other tools than createrapid.py to create the rapid.env file,
make sure to add the mask.
This commit also introduces a change when exiting: we only copy the
prox.log files from the different prox instances in case we expect a new
prox.log file to be created in these instances.
When using a valid format.yaml file, the tool will now also send the
latency distribution data (histogram) to the URL.
The tool is now also checking the value of bucket_size_exp: it needs to
be 11 or more.

Change-Id: I633cdc64ef687fdb6625be1e7482a5a371f83e93
Signed-off-by: Luc Provoost <luc.provoost@intel.com>
16 files changed:
VNFs/DPPD-PROX/helper-scripts/rapid/config_file
VNFs/DPPD-PROX/helper-scripts/rapid/createrapid.py
VNFs/DPPD-PROX/helper-scripts/rapid/format.yaml
VNFs/DPPD-PROX/helper-scripts/rapid/gen.cfg
VNFs/DPPD-PROX/helper-scripts/rapid/gen_gw.cfg
VNFs/DPPD-PROX/helper-scripts/rapid/helper.lua
VNFs/DPPD-PROX/helper-scripts/rapid/impair.cfg
VNFs/DPPD-PROX/helper-scripts/rapid/rapid_flowsizetest.py
VNFs/DPPD-PROX/helper-scripts/rapid/rapid_generator_machine.py
VNFs/DPPD-PROX/helper-scripts/rapid/rapid_impairtest.py
VNFs/DPPD-PROX/helper-scripts/rapid/rapid_machine.py
VNFs/DPPD-PROX/helper-scripts/rapid/rapid_parser.py
VNFs/DPPD-PROX/helper-scripts/rapid/rapid_test.py
VNFs/DPPD-PROX/helper-scripts/rapid/runrapid.py
VNFs/DPPD-PROX/helper-scripts/rapid/stackdeployment.py
VNFs/DPPD-PROX/helper-scripts/rapid/swap.cfg

index f31ed25..0cd5c70 100644 (file)
@@ -5,4 +5,5 @@ heat_template= openstack-rapid.yaml
 heat_param = params_rapid.yaml
 keypair_name = rapid_key
 user = centos
+dataplane_subnet_mask = 24
 ;push_gateway = http://192.168.36.61:9091/metrics/job/
index e488f35..1658cff 100755 (executable)
@@ -32,6 +32,8 @@ class RapidStackManager(object):
         options = config.options(section)
         for option in options:
             rapid_stack_params[option] = config.get(section, option)
+        if 'dataplane_subnet_mask' not in rapid_stack_params.keys():
+            rapid_stack_params['dataplane_subnet_mask'] = 24
         return (rapid_stack_params)
 
     @staticmethod
@@ -42,9 +44,10 @@ class RapidStackManager(object):
         heat_param = rapid_stack_params['heat_param']
         keypair_name = rapid_stack_params['keypair_name']
         user = rapid_stack_params['user']
+        dataplane_subnet_mask = rapid_stack_params['dataplane_subnet_mask']
         deployment = StackDeployment(cloud_name)
         deployment.deploy(stack_name, keypair_name, heat_template, heat_param)
-        deployment.generate_env_file(user)
+        deployment.generate_env_file(user, dataplane_subnet_mask)
 
 def main():
     rapid_stack_params = {}
index 1ac48ab..f422077 100644 (file)
@@ -45,6 +45,9 @@ rapid_flowsizetest:
       AvgLatency: AvgLatency
       PCTLatency: PCTLatency
       MaxLatency: MaxLatency
+      Distribution:
+        bucket_size: bucket_size
+        buckets: buckets
     Absolute Packet Count:
       PacketsSent: PacketsSent
       PacketsReceived: PacketsReceived
index 717915d..1827395 100644 (file)
@@ -28,7 +28,7 @@ rx desc=2048
 tx desc=2048
 vlan=yes
 vdev=gen_tap
-local ipv4=${local_ip1}/24
+local ipv4=${local_ip1}
 
 
 [variables]
index f53b41d..fc3b6a6 100644 (file)
@@ -28,7 +28,7 @@ rx desc=2048
 tx desc=2048
 vlan=yes
 vdev=gen_tap
-local ipv4=${local_ip1}/24
+local ipv4=${local_ip1}
 
 [variables]
 $mbs=8
index 237c385..1b4c657 100644 (file)
@@ -21,6 +21,22 @@ function convertIPToHex(ip)
     return "IP ADDRESS ERROR"
   end
 
+  local chunks = {ip:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)(\/%d+)$")}
+  if #chunks == 5 then
+    for i,v in ipairs(chunks) do
+      if i < 5 then
+        if tonumber(v) > 255 then
+          print ("IPV4 ADDRESS ERROR: ", ip)
+          return "IPV4 ADDRESS ERROR"
+        end
+        address_chunks[#address_chunks + 1] = string.format ("%02x", v)
+      end
+    end
+    result = table.concat(address_chunks, " ")
+    print ("Hex IPV4: ", result)
+    return result
+  end
+
   local chunks = {ip:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")}
   if #chunks == 4 then
     for i,v in ipairs(chunks) do
index cb3f349..95d6516 100644 (file)
@@ -29,7 +29,7 @@ rx desc=2048
 tx desc=2048
 vlan=yes
 vdev=impair_tap
-local ipv4=${local_ip1}/24
+local ipv4=${local_ip1}
 
 
 [defaults]
index ca3b5a2..f547b3c 100644 (file)
@@ -138,7 +138,7 @@ class FlowSizeTest(RapidTest):
                     self.set_background_speed(self.background_machines, speed)
                     self.start_background_traffic(self.background_machines)
                     # Get statistics now that the generation is stable and initial ARP messages are dealt with
-                    pps_req_tx,pps_tx,pps_sut_tx,pps_rx,lat_avg,lat_perc , lat_perc_max, lat_max, abs_tx,abs_rx,abs_dropped, abs_tx_fail, drop_rate, lat_min, lat_used, r, actual_duration, avg_bg_rate = self.run_iteration(float(self.test['runtime']),flow_number,size,speed)
+                    pps_req_tx,pps_tx,pps_sut_tx,pps_rx,lat_avg,lat_perc , lat_perc_max, lat_max, abs_tx,abs_rx,abs_dropped, abs_tx_fail, drop_rate, lat_min, lat_used, r, actual_duration, avg_bg_rate, bucket_size, buckets = self.run_iteration(float(self.test['runtime']),flow_number,size,speed)
                     self.stop_background_traffic(self.background_machines)
                     if r > 1:
                         retry_warning = bcolors.WARNING + ' {:1} retries needed'.format(r) +  bcolors.ENDC
@@ -160,6 +160,7 @@ class FlowSizeTest(RapidTest):
                         endlat_perc = lat_perc
                         endlat_perc_max = lat_perc_max
                         endlat_max = lat_max
+                        endbuckets = buckets
                         endabs_dropped = abs_dropped
                         enddrop_rate = drop_rate
                         endabs_tx = abs_tx
@@ -193,6 +194,7 @@ class FlowSizeTest(RapidTest):
                         endlat_perc = lat_perc
                         endlat_perc_max = lat_perc_max
                         endlat_max = lat_max
+                        endbuckets = buckets
                         endabs_dropped = None
                         enddrop_rate = drop_rate
                         endabs_tx = abs_tx
@@ -267,7 +269,9 @@ class FlowSizeTest(RapidTest):
                                 'MaxLatency': endlat_max,
                                 'PacketsSent': endabs_tx,
                                 'PacketsReceived': endabs_rx,
-                                'PacketsLost': abs_dropped}
+                                'PacketsLost': abs_dropped,
+                                'bucket_size': bucket_size,
+                                'buckets': endbuckets}
                         self.post_data('rapid_flowsizetest', variables)
                 else:
                     RapidLog.info('|{:>7}'.format(str(flow_number))+" | Speed 0 or close to 0")
index dee40d9..293720d 100644 (file)
@@ -49,7 +49,7 @@ class RapidGeneratorMachine(RapidMachine):
     """
     Class to deal with a generator PROX instance (VM, bare metal, container)
     """
-    def __init__(self, key, user, vim, rundir, machine_params, ipv6):
+    def __init__(self, key, user, vim, rundir, machine_params, configonly, ipv6):
         mac_address_size = 6
         ethertype_size = 2
         FCS_size = 4
@@ -73,7 +73,7 @@ class RapidGeneratorMachine(RapidMachine):
         self.udp_dest_port_offset = udp_header_start_offset + 2
         self.udp_length_offset = udp_header_start_offset + 4
         self.ipv6 = ipv6
-        super().__init__(key, user, vim, rundir, machine_params)
+        super().__init__(key, user, vim, rundir, machine_params, configonly)
 
     def get_cores(self):
         return (self.machine_params['gencores'] +
@@ -103,10 +103,10 @@ class RapidGeneratorMachine(RapidMachine):
             appendix = appendix + 'heartbeat="60"\n'
         super().generate_lua(vim, appendix)
 
-    def start_prox(self, configonly=False):
+    def start_prox(self):
         # Start the generator with the -e option so that the cores don't
         # start automatically
-        super().start_prox(configonly, '-e')
+        super().start_prox('-e')
 
     def set_generator_speed(self, speed):
         # The assumption is that we only use task 0 for generating
index 74d624d..8b7f876 100644 (file)
@@ -57,7 +57,7 @@ class ImpairTest(RapidTest):
             sys.stdout.flush()
             time.sleep(1)
             # Get statistics now that the generation is stable and NO ARP messages any more
-            pps_req_tx,pps_tx,pps_sut_tx,pps_rx,lat_avg, lat_perc, lat_perc_max, lat_max, abs_tx, abs_rx, abs_dropped, abs_tx_fail, drop_rate, lat_min, lat_used, r, actual_duration, _ = self.run_iteration(float(self.test['runtime']),flow_number,size,speed)
+            pps_req_tx,pps_tx,pps_sut_tx,pps_rx,lat_avg, lat_perc, lat_perc_max, lat_max, abs_tx, abs_rx, abs_dropped, abs_tx_fail, drop_rate, lat_min, lat_used, r, actual_duration, _,bucket_size, buckets = self.run_iteration(float(self.test['runtime']),flow_number,size,speed)
             # Drop rate is expressed in percentage. lat_used is a ratio (0 to 1). The sum of these 2 should be 100%.
             # If the sum is lower than 95, it means that more than 5% of the latency measurements where dropped for accuracy reasons.
             if (drop_rate + lat_used * 100) < 95:
@@ -78,7 +78,9 @@ class ImpairTest(RapidTest):
                     'PCTLatency': lat_perc,
                     'MaxLatency': lat_max,
                     'PacketsLost': abs_dropped,
-                    'DropRate': drop_rate}
+                    'DropRate': drop_rate,
+                    'bucket_size': bucket_size,
+                    'buckets': buckets}
             self.post_data('rapid_impairtest', variables)
         self.gen_machine.stop_latency_cores()
         return (True)
index 4a0bcb8..8466c85 100644 (file)
@@ -24,7 +24,7 @@ class RapidMachine(object):
     """
     Class to deal with a PROX instance (VM, bare metal, container)
     """
-    def __init__(self, key, user, vim, rundir, machine_params):
+    def __init__(self, key, user, vim, rundir, machine_params, configonly):
         self.name = machine_params['name']
         self.ip = machine_params['admin_ip']
         self.key = key
@@ -32,6 +32,7 @@ class RapidMachine(object):
         self.rundir = rundir
         self.dp_ports = []
         self.dpdk_port_index = []
+        self.configonly = configonly
         index = 1
         while True:
             ip_key = 'dp_ip{}'.format(index)
@@ -48,13 +49,13 @@ class RapidMachine(object):
         self.vim = vim
 
     def __del__(self):
-        if self.machine_params['prox_socket']:
+        if ((not self.configonly) and self.machine_params['prox_socket']):
             self._client.scp_get('/prox.log', './{}.prox.log'.format(self.name))
 
     def get_cores(self):
         return (self.machine_params['cores'])
 
-    def devbind(self, configonly):
+    def devbind(self):
         # Script to bind the right network interface to the poll mode driver
         for index, dp_port in enumerate(self.dp_ports, start = 1):
             DevBindFileName = self.rundir + '/devbind-{}-port{}.sh'.format(self.ip, index)
@@ -62,7 +63,7 @@ class RapidMachine(object):
             cmd =  'sed -i \'s/MACADDRESS/' + dp_port['mac'] + '/\' ' + DevBindFileName 
             result = self._client.run_cmd(cmd)
             RapidLog.debug('devbind.sh MAC updated for port {} on {} {}'.format(index, self.name, result))
-            if ((not configonly) and self.machine_params['prox_launch_exit']):
+            if ((not self.configonly) and self.machine_params['prox_launch_exit']):
                 result = self._client.run_cmd(DevBindFileName)
                 RapidLog.debug('devbind.sh running for port {} on {} {}'.format(index, self.name, result))
 
@@ -95,15 +96,15 @@ class RapidMachine(object):
         self._client.scp_put(self.LuaFileName, self.rundir + '/parameters.lua')
         self._client.scp_put('helper.lua', self.rundir + '/helper.lua')
 
-    def start_prox(self, configonly=False, autostart=''):
+    def start_prox(self, autostart=''):
         if self.machine_params['prox_socket']:
             self._client = prox_ctrl(self.ip, self.key, self.user)
             self._client.connect()
             if self.vim in ['OpenStack']:
-                self.devbind(configonly)
+                self.devbind()
             self.generate_lua(self.vim)
             self._client.scp_put(self.machine_params['config_file'], '{}/{}'.format(self.rundir, self.machine_params['config_file']))
-            if ((not configonly) and self.machine_params['prox_launch_exit']):
+            if ((not self.configonly) and self.machine_params['prox_launch_exit']):
                 cmd = 'sudo {}/prox {} -t -o cli -f {}/{}'.format(self.rundir, autostart, self.rundir, self.machine_params['config_file'])
                 RapidLog.debug("Starting PROX on {}: {}".format(self.name, cmd))
                 result = self._client.run_cmd(cmd, 'PROX Testing on {}'.format(self.name))
@@ -111,7 +112,7 @@ class RapidMachine(object):
                 RapidLog.debug("Finished PROX on {}: {}".format(self.name, cmd))
 
     def close_prox(self):
-        if self.machine_params['prox_socket'] and self.machine_params['prox_launch_exit']:
+        if (not self.configonly) and self.machine_params['prox_socket'] and self.machine_params['prox_launch_exit']:
             self.socket.quit()
 
     def connect_prox(self):
index af59570..992d2d0 100644 (file)
@@ -106,6 +106,8 @@ class RapidConfigParser(object):
                         section, option))
                 elif option in ['bucket_size_exp']:
                     machine[option] = int(testconfig.get(section, option))
+                    if machine[option] < 11:
+                        RapidLog.exception("Minimum Value for bucket_size_exp is 11")
                 else:
                     machine[option] = testconfig.get(section, option)
                 for key in ['prox_socket','prox_launch_exit']:
index cdacb80..2babd34 100644 (file)
@@ -285,8 +285,9 @@ class RapidTest(object):
                         if sample_count > lat_samples * LAT_PERCENTILE:
                             break
                     percentile_max = (sample_percentile == len(buckets))
-                    sample_percentile = sample_percentile *  float(2 ** BUCKET_SIZE_EXP) / (old_div(float(lat_hz),float(10**6)))
-                    buckets_total = [buckets_total[i] + buckets[i] for i in range(len(buckets_total))] 
+                    bucket_size = float(2 ** BUCKET_SIZE_EXP) / (old_div(float(lat_hz),float(10**6)))
+                    sample_percentile = sample_percentile *  bucket_size
+                    buckets_total = [buckets_total[i] + buckets[i] for i in range(len(buckets_total))]
                     t2_lat_tsc = t3_lat_tsc
                     lat_avail = True
                 t3_rx, t3_non_dp_rx, t3_tx, t3_non_dp_tx, t3_drop, t3_tx_fail, t3_tsc, tsc_hz = self.gen_machine.core_stats()
@@ -351,7 +352,10 @@ class RapidTest(object):
                                 'MaxLatency': lat_max_sample,
                                 'PacketsSent': delta_dp_tx,
                                 'PacketsReceived': delta_dp_rx,
-                                'PacketsLost': tot_dp_drop}
+                                'PacketsLost': tot_dp_drop,
+                                'bucket_size': bucket_size,
+                                'buckets': buckets}
+
                         self.post_data('rapid_flowsizetest', variables)
             end_bg_gen_stats = []
             for bg_gen_machine in self.background_machines:
@@ -393,7 +397,7 @@ class RapidTest(object):
                     if sample_count > lat_samples * LAT_PERCENTILE:
                         break
                 percentile_max = (percentile == len(buckets))
-                percentile = percentile *  float(2 ** BUCKET_SIZE_EXP) / (old_div(float(lat_hz),float(10**6)))
+                percentile = percentile *  bucket_size
                 lat_max = lat_max_sample
                 lat_avg = lat_avg_sample
                 delta_rx = t4_rx - t2_rx
@@ -414,12 +418,13 @@ class RapidTest(object):
                 break ## Not really needed since the while loop will stop when evaluating the value of r
             else:
                 sample_count = 0
+                buckets = buckets_total
                 for percentile, bucket in enumerate(buckets_total,start=1):
                     sample_count += bucket
                     if sample_count > tot_lat_samples * LAT_PERCENTILE:
                         break
                 percentile_max = (percentile == len(buckets_total))
-                percentile = percentile *  float(2 ** BUCKET_SIZE_EXP) / (old_div(float(lat_hz),float(10**6)))
+                percentile = percentile *  bucket_size
                 pps_req_tx = (tot_tx + tot_drop - tot_rx)/tot_core_measurement_duration/1000000.0 # tot_drop is all packets dropped by all tasks. This includes packets dropped at the generator task + packets dropped by the nop task. In steady state, this equals to the number of packets received by this VM
                 pps_tx = tot_tx/tot_core_measurement_duration/1000000.0 # tot_tx is all generated packets actually accepted by the interface
                 pps_rx = tot_rx/tot_core_measurement_duration/1000000.0 # tot_rx is all packets received by the nop task = all packets received in the gen VM
@@ -433,4 +438,4 @@ class RapidTest(object):
                 drop_rate = 100.0*tot_dp_drop/dp_tx
                 if ((drop_rate < self.test['drop_rate_threshold']) or (tot_dp_drop == self.test['drop_rate_threshold'] ==0) or (tot_dp_drop > self.test['maxz'])):
                     break
-        return(pps_req_tx,pps_tx,pps_sut_tx,pps_rx,lat_avg,percentile,percentile_max,lat_max,dp_tx,dp_rx,tot_dp_drop,(t4_tx_fail - t1_tx_fail),drop_rate,lat_min,used_avg,r,tot_core_measurement_duration,avg_bg_rate)
+        return(pps_req_tx,pps_tx,pps_sut_tx,pps_rx,lat_avg,percentile,percentile_max,lat_max,dp_tx,dp_rx,tot_dp_drop,(t4_tx_fail - t1_tx_fail),drop_rate,lat_min,used_avg,r,tot_core_measurement_duration,avg_bg_rate,bucket_size,buckets)
index 8e879a0..f5b85a3 100755 (executable)
@@ -57,12 +57,13 @@ class RapidTestManager(object):
         background_machines = []
         sut_machine = gen_machine = None
         self.machines = []
+        configonly = test_params['configonly']
         for machine_params in test_params['machines']:
             if 'gencores' in machine_params.keys():
                 machine = RapidGeneratorMachine(test_params['key'],
                         test_params['user'], test_params['vim_type'],
                         test_params['rundir'], machine_params,
-                        test_params['ipv6'])
+                        configonly, test_params['ipv6'])
                 if machine_params['monitor']:
                     if monitor_gen:
                         RapidLog.exception("Can only monitor 1 generator")
@@ -75,7 +76,7 @@ class RapidTestManager(object):
             else:
                 machine = RapidMachine(test_params['key'], test_params['user'],
                         test_params['vim_type'], test_params['rundir'],
-                        machine_params)
+                        machine_params, configonly)
                 if machine_params['monitor']:
                     if monitor_sut:
                         RapidLog.exception("Can only monitor 1 sut")
@@ -86,8 +87,8 @@ class RapidTestManager(object):
                             sut_machine = machine
             self.machines.append(machine)
         prox_executor = concurrent.futures.ThreadPoolExecutor(max_workers=len(self.machines))
-        self.future_to_prox = {prox_executor.submit(machine.start_prox,test_params['configonly']): machine for machine in self.machines}
-        if test_params['configonly']:
+        self.future_to_prox = {prox_executor.submit(machine.start_prox): machine for machine in self.machines}
+        if configonly:
             concurrent.futures.wait(self.future_to_prox,return_when=ALL_COMPLETED)
             sys.exit()
         with concurrent.futures.ThreadPoolExecutor(max_workers=len(self.machines)) as executor:
index 2e9c6cc..925fd8b 100755 (executable)
@@ -75,7 +75,7 @@ class StackDeployment(object):
                     for name in server_group_output:
                         self.names.append(name)
 
-    def print_paramDict(self, user):
+    def print_paramDict(self, user, dataplane_subnet_mask):
         if not(len(self.dp_ips) == len(self.dp_macs) == len(self.mngmt_ips)):
             sys.exit()
         _ENV_FILE_DIR = os.path.dirname(os.path.realpath(__file__))
@@ -90,9 +90,11 @@ class StackDeployment(object):
                 env_file.write('admin_ip = {}\n'.format(str(self.mngmt_ips[count])))
                 if type(self.dp_ips[count]) == list:
                     for i, dp_ip in enumerate(self.dp_ips[count], start = 1):
-                        env_file.write('dp_ip{} = {}\n'.format(i, str(dp_ip)))
+                        env_file.write('dp_ip{} = {}/{}\n'.format(i, str(dp_ip),
+                            dataplane_subnet_mask))
                 else:
-                    env_file.write('dp_ip1 = {}\n'.format(str(self.dp_ips[count])))
+                    env_file.write('dp_ip1 = {}/{}\n'.format(str(self.dp_ips[count]),
+                        dataplane_subnet_mask))
                 if type(self.dp_macs[count]) == list:
                     for i, dp_mac in enumerate(self.dp_macs[count], start = 1):
                         env_file.write('dp_mac{} = {}\n'.format(i, str(dp_mac)))
@@ -156,6 +158,6 @@ class StackDeployment(object):
                 self.create_key()
             self.stack = self.create_stack(stack_name, heat_template, heat_param)
 
-    def generate_env_file(self, user = 'centos'):
+    def generate_env_file(self, user = 'centos', dataplane_subnet_mask = '24'):
         self.generate_paramDict()
-        self.print_paramDict(user)
+        self.print_paramDict(user, dataplane_subnet_mask)
index 4463f83..0cca80c 100644 (file)
@@ -27,7 +27,7 @@ name=if0
 mac=hardware
 vlan=yes
 vdev=swap_tap
-local ipv4=${local_ip1}/24
+local ipv4=${local_ip1}
 
 [defaults]
 mempool size=8K