Merge "Tools: Improve Stability."
[vswitchperf.git] / tools / pkt_gen / testcenter / testcenter-rfc2544-rest.py
index 8da8ed1..8089ef4 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright 2016 Spirent Communications.
+# Copyright 2016-2017 Spirent Communications.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -11,7 +11,9 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
-
+#
+# Invalid name of file, must be used '_' instead '-'
+# pylint: disable=invalid-name
 '''
 @author Spirent Communications
 
@@ -20,12 +22,26 @@ TestCenter REST APIs. This test supports Python 3.4
 
 '''
 import argparse
+import collections
 import logging
 import os
-from conf import settings
+import sqlite3
+import time
+
+_LOGGER = logging.getLogger(__name__)
+
+GENOME_PKTSIZE_ENCODING = {"a": 64, "b": 128, "c": 256, "d": 512,
+                           "e": 1024, "f": 1280, "g": 1518, "h": 2112}
 
 
-logger = logging.getLogger(__name__)
+def genome2weights(sequence):
+    """ Convert genome sequence to packetsize weights"""
+    weights = collections.defaultdict(int)
+    for char in GENOME_PKTSIZE_ENCODING:
+        charcount = sequence.count(char)
+        if charcount:
+            weights[GENOME_PKTSIZE_ENCODING[char]] = charcount
+    return weights
 
 
 def create_dir(path):
@@ -33,21 +49,72 @@ def create_dir(path):
     if not os.path.exists(path):
         try:
             os.makedirs(path)
-        except OSError as e:
-            logger.error("Failed to create directory %s: %s", path, str(e))
+        except OSError as ex:
+            _LOGGER.error("Failed to create directory %s: %s", path, str(ex))
             raise
 
 
+def write_histogram_to_csv(results_path, csv_results_file_prefix,
+                           counts, ranges):
+    """ Write the results of the query to the CSV """
+    filec = os.path.join(results_path, csv_results_file_prefix + ".csv")
+    with open(filec, "wb") as result_file:
+        for key in counts:
+            result_file.write(str(key) + "\n")
+            result_file.write(str(ranges) + "\n")
+            result_file.write(str(counts[key]) + "\n")
+
+
 def write_query_results_to_csv(results_path, csv_results_file_prefix,
                                query_results):
     """ Write the results of the query to the CSV """
     create_dir(results_path)
     filec = os.path.join(results_path, csv_results_file_prefix + ".csv")
-    with open(filec, "wb") as f:
-        f.write(query_results["Columns"].replace(" ", ",") + "\n")
+    with open(filec, "wb") as result_file:
+        result_file.write(query_results["Columns"].replace(" ", ",") + "\n")
         for row in (query_results["Output"].replace("} {", ",").
                     replace("{", "").replace("}", "").split(",")):
-            f.write(row.replace(" ", ",") + "\n")
+            result_file.write(row.replace(" ", ",") + "\n")
+
+
+def write_headers(results_path, file_name, rx_tx):
+    """ Write headers for the live-results files """
+    filec = os.path.join(results_path, file_name + rx_tx)
+    with open(filec, "a") as result_file:
+        if 'rx' in rx_tx:
+            result_file.write('Time,RxPrt,DrpFrCnt,SeqRnLen,AvgLat,' +
+                              'DrpFrRate,FrCnt,FrRate,MaxLat,MinLat,' +
+                              'OctCnt,OctRate\n')
+        else:
+            result_file.write('Time,StrId,BlkId,FrCnt,FrRate,ERxFrCnt,' +
+                              'OctCnt,OctRate,bitCnt,bitRate\n')
+
+
+def write_rx_live_results_to_file(results_path, file_name, results):
+    """ Write live results from the rx-ports"""
+    filec = os.path.join(results_path, file_name + ".rx")
+    with open(filec, "a") as result_file:
+        result_file.write('{0},{3},{1},{2},{4},{5},{6},{7},{8},{9},{10},{11}\n'
+                          .format(time.time(), results['DroppedFrameCount'],
+                                  results['SeqRunLength'], results['RxPort'],
+                                  results['AvgLatency'],
+                                  results['DroppedFrameRate'],
+                                  results['FrameCount'], results['FrameRate'],
+                                  results['MaxLatency'], results['MinLatency'],
+                                  results['OctetCount'], results['OctetRate']))
+
+
+def write_tx_live_results_to_file(results_path, file_name, results):
+    """ Write live results from the tx-ports"""
+    filec = os.path.join(results_path, file_name + ".tx")
+    with open(filec, "a") as result_file:
+        result_file.write('{0},{1},{9},{2},{3},{4},{5},{6},{7},{8}\n'
+                          .format(time.time(), results['StreamId'],
+                                  results['FrameCount'], results['FrameRate'],
+                                  results['ExpectedRxFrameCount'],
+                                  results['OctetCount'], results['OctetRate'],
+                                  results['BitCount'], results['BitRate'],
+                                  results['BlockId']))
 
 
 def positive_int(value):
@@ -68,6 +135,7 @@ def percent_float(value):
     return pvalue
 
 
+# pylint: disable=too-many-branches, too-many-statements, too-many-locals
 def main():
     """ Read the arguments, Invoke Test and Return the results"""
     parser = argparse.ArgumentParser()
@@ -142,9 +210,14 @@ def main():
                                 dest="test_user_name")
     optional_named.add_argument("--results_dir",
                                 required=False,
-                                default=settings.getValue("TRAFFICGEN_STC_RESULTS_DIR"),
+                                default="./Results",
                                 help="The directory to copy results to",
                                 dest="results_dir")
+    optional_named.add_argument("--vsperf_results_dir",
+                                required=False,
+                                default="./Results",
+                                help="The directory to copy results to",
+                                dest="vsperf_results_dir")
     optional_named.add_argument("--csv_results_file_prefix",
                                 required=False,
                                 default="Rfc2544Tput",
@@ -268,6 +341,27 @@ def main():
                                       "the first emulated device interface"
                                       "on the first west port"),
                                 dest="west_intf_gateway_addr")
+    optional_named.add_argument("--latency_histogram",
+                                required=False,
+                                action="store_true",
+                                help="latency histogram is required in output?",
+                                dest="latency_histogram")
+    optional_named.add_argument("--imix",
+                                required=False,
+                                default="",
+                                help=("IMIX specification as genome"
+                                      "Encoding - RFC 6985"),
+                                dest="imix")
+    optional_named.add_argument("--live_results",
+                                required=False,
+                                action="store_true",
+                                help="Live Results required?",
+                                dest="live_results")
+    optional_named.add_argument("--logfile",
+                                required=False,
+                                default="./traffic_gen.log",
+                                help="Log file to log live results",
+                                dest="logfile")
     parser.add_argument("-v",
                         "--verbose",
                         required=False,
@@ -278,12 +372,12 @@ def main():
     args = parser.parse_args()
 
     if args.verbose:
-        logger.debug("Creating results directory")
+        _LOGGER.debug("Creating results directory")
     create_dir(args.results_dir)
 
     session_name = args.test_session_name
     user_name = args.test_user_name
-
+    # pylint: disable=import-error
     try:
         # Load Spirent REST Library
         from stcrestclient import stchttp
@@ -291,8 +385,8 @@ def main():
         stc = stchttp.StcHttp(args.lab_server_addr)
         session_id = stc.new_session(user_name, session_name)
         stc.join_session(session_id)
-    except RuntimeError as e:
-        logger.error(e)
+    except RuntimeError as err:
+        _LOGGER.error(err)
         raise
 
     # Get STC system info.
@@ -305,43 +399,48 @@ def main():
 
     # Retrieve and display the server information
     if args.verbose:
-        logger.debug("SpirentTestCenter system version: %s",
-                     stc.get("system1", "version"))
+        _LOGGER.debug("SpirentTestCenter system version: %s",
+                      stc.get("system1", "version"))
 
+    # pylint: disable=too-many-nested-blocks
     try:
         device_list = []
         port_list = []
         if args.verbose:
-            logger.debug("Bring up license server")
+            _LOGGER.debug("Bring up license server")
         license_mgr = stc.get("system1", "children-licenseservermanager")
         if args.verbose:
-            logger.debug("license_mgr = %s", license_mgr)
+            _LOGGER.debug("license_mgr = %s", license_mgr)
         stc.create("LicenseServer", under=license_mgr, attributes={
-                   "server": args.license_server_addr})
+            "server": args.license_server_addr})
 
         # Create the root project object
         if args.verbose:
-            logger.debug("Creating project ...")
+            _LOGGER.debug("Creating project ...")
         project = stc.get("System1", "children-Project")
 
+        # Configure the Result view
+        resultopts = stc.get('project1', 'children-resultoptions')
+        stc.config(resultopts, {'ResultViewMode': 'BASIC'})
+
         # Configure any custom traffic parameters
         if args.traffic_custom == "cont":
             if args.verbose:
-                logger.debug("Configure Continuous Traffic")
+                _LOGGER.debug("Configure Continuous Traffic")
             stc.create("ContinuousTestConfig", under=project)
 
         # Create ports
         if args.verbose:
-            logger.debug("Creating ports ...")
+            _LOGGER.debug("Creating ports ...")
         east_chassis_port = stc.create('port', project)
         if args.verbose:
-            logger.debug("Configuring TX port ...")
+            _LOGGER.debug("Configuring TX port ...")
         stc.config(east_chassis_port, {'location': tx_port_loc})
         port_list.append(east_chassis_port)
 
         west_chassis_port = stc.create('port', project)
         if args.verbose:
-            logger.debug("Configuring RX port ...")
+            _LOGGER.debug("Configuring RX port ...")
         stc.config(west_chassis_port, {'location': rx_port_loc})
         port_list.append(west_chassis_port)
 
@@ -352,7 +451,9 @@ def main():
                                                         east_chassis_port})
         # Create the DeviceGenEthIIIfParams object
         stc.create("DeviceGenEthIIIfParams",
-                   under=east_device_gen_params)
+                   under=east_device_gen_params,
+                   attributes={'UseDefaultPhyMac': True})
+
         # Configuring Ipv4 interfaces
         stc.create("DeviceGenIpv4IfParams",
                    under=east_device_gen_params,
@@ -373,7 +474,9 @@ def main():
                                                         west_chassis_port})
         # Create the DeviceGenEthIIIfParams object
         stc.create("DeviceGenEthIIIfParams",
-                   under=west_device_gen_params)
+                   under=west_device_gen_params,
+                   attributes={'UseDefaultPhyMac': True})
+
         # Configuring Ipv4 interfaces
         stc.create("DeviceGenIpv4IfParams",
                    under=west_device_gen_params,
@@ -387,12 +490,51 @@ def main():
         # Append to the device list
         device_list.append(device_gen_config['ReturnList'])
         if args.verbose:
-            logger.debug(device_list)
+            _LOGGER.debug(device_list)
+
+        # Configure Histogram
+        if args.latency_histogram:
+            # Generic Configuration
+            histResOptions = stc.get("project1", 'children-ResultOptions')
+            stc.config(histResOptions, {'ResultViewMode': 'HISTOGRAM'})
+            # East Port Configuration
+            histAnaEast = stc.get(east_chassis_port, 'children-Analyzer')
+            histAnaEastConfig = stc.get(histAnaEast, 'children-AnalyzerConfig')
+            stc.config(histAnaEastConfig, {'HistogramMode': 'LATENCY'})
+            eLatHist = stc.get(histAnaEastConfig, 'children-LatencyHistogram')
+            stc.config(eLatHist, {'ConfigMode': 'CONFIG_LIMIT_MODE',
+                                  'BucketSizeUnit': 'ten_nanoseconds',
+                                  'Active': 'TRUE',
+                                  'DistributionMode': 'CENTERED_MODE'})
+            # West Port Configuration
+            histAnaWest = stc.get(west_chassis_port, 'children-Analyzer')
+            histAnaWestConfig = stc.get(histAnaWest, 'children-AnalyzerConfig')
+            stc.config(histAnaWestConfig, {'HistogramMode': 'LATENCY'})
+            wLatHist = stc.get(histAnaWestConfig, 'children-LatencyHistogram')
+            stc.config(wLatHist, {'ConfigMode': 'CONFIG_LIMIT_MODE',
+                                  'BucketSizeUnit': 'ten_nanoseconds',
+                                  'Active': 'TRUE',
+                                  'DistributionMode': 'CENTERED_MODE'})
+            gBucketSizeList = stc.get(wLatHist, 'BucketSizeList')
+            # gLimitSizeList  = stc.get(wLatHist, 'LimitList')
+
+        # IMIX configuration
+        fld = None
+        if args.imix:
+            args.frame_size_list = []
+            weights = genome2weights(args.imix)
+            fld = stc.create('FrameLengthDistribution', under=project)
+            def_slots = stc.get(fld, "children-framelengthdistributionslot")
+            stc.perform("Delete", params={"ConfigList": def_slots})
+            for fsize in weights:
+                stc.create('framelengthdistributionslot', under=fld,
+                           attributes={'FixedFrameLength': fsize,
+                                       'Weight': weights[fsize]})
 
         # Create the RFC 2544 'metric test
         if args.metric == "throughput":
             if args.verbose:
-                logger.debug("Set up the RFC2544 throughput test...")
+                _LOGGER.debug("Set up the RFC2544 throughput test...")
             stc.perform("Rfc2544SetupThroughputTestCommand",
                         params={"AcceptableFrameLoss":
                                 args.acceptable_frame_loss_pct,
@@ -406,7 +548,8 @@ def main():
                                 "RateUpperLimit": args.rate_upper_limit_pct,
                                 "Resolution": args.resolution_pct,
                                 "SearchMode": args.search_mode,
-                                "TrafficPattern": args.traffic_pattern})
+                                "TrafficPattern": args.traffic_pattern,
+                                "FrameSizeDistributionList": fld})
         elif args.metric == "backtoback":
             stc.perform("Rfc2544SetupBackToBackTestCommand",
                         params={"AcceptableFrameLoss":
@@ -463,26 +606,135 @@ def main():
             "system1.project", "children-port"), "autoConnect": "TRUE"})
         # Apply configuration.
         if args.verbose:
-            logger.debug("Apply configuration...")
+            _LOGGER.debug("Apply configuration...")
         stc.apply()
 
+        # Register for the results
+        hResDataRx = stc.create('ResultDataSet', under='project1')
+        strmBlockList = stc.get('project1', 'children-streamblock')
+        stc.create('ResultQuery', under=hResDataRx, attributes={
+            'ResultRootList': strmBlockList,
+            'ConfigClassId': 'StreamBlock',
+            'ResultClassId': 'RxStreamSummaryResults',
+            'PropertyIdArray': "RxStreamSummaryResults.RxPort \
+                                RxStreamSummaryResults.AvgLatency \
+                                RxStreamSummaryResults.BitCount \
+                                RxStreamSummaryResults.BitRate \
+                                RxStreamSummaryResults.DroppedFrameCount\
+                                RxStreamSummaryResults.DroppedFrameRate \
+                                RxStreamSummaryResults.FrameCount \
+                                RxStreamSummaryResults.FrameRate \
+                                RxStreamSummaryResults.MaxLatency \
+                                RxStreamSummaryResults.MinLatency \
+                                RxStreamSummaryResults.OctetCount \
+                                RxStreamSummaryResults.OctetRate \
+                                RxStreamSummaryResults.SeqRunLength"})
+        hResDataTx = stc.create('ResultDataSet', under='project1')
+        strmBlockList = stc.get('project1', 'children-streamblock')
+        stc.create('ResultQuery', under=hResDataTx, attributes={
+            'ResultRootList': strmBlockList,
+            'ConfigClassId': 'StreamBlock',
+            'ResultClassId': 'TxStreamResults',
+            'PropertyIdArray': "TxStreamResults.BlockId \
+                                TxStreamResults.BitCount \
+                                TxStreamResults.BitRate \
+                                TxStreamResults.FrameCount \
+                                TxStreamResults.FrameRate \
+                                TxStreamResults.OctetCount \
+                                TxStreamResults.OctetRate"})
+        stc.perform('ResultDataSetSubscribe', params={'ResultDataSet': hResDataRx})
+        stc.perform('ResultDataSetSubscribe', params={'ResultDataSet': hResDataTx})
+        time.sleep(3)
+        stc.perform('RefreshResultView', params={'ResultDataSet': hResDataTx})
+        hndListRx = stc.get(hResDataRx, 'ResultHandleList')
+        hndListTx = stc.get(hResDataTx, 'ResultHandleList')
+
         if args.verbose:
-            logger.debug("Starting the sequencer...")
+            _LOGGER.debug("Starting the sequencer...")
         stc.perform("SequencerStart")
 
-        # Wait for sequencer to finish
-        logger.info(
-            "Starting test... Please wait for the test to complete...")
-        stc.wait_until_complete()
-        logger.info("The test has completed... Saving results...")
+        sequencer = stc.get("system1", "children-sequencer")
+        state = stc.get(sequencer, 'State')
+
+        # If Live-results are required, we don't wait for the test to complete
+        if args.live_results:
+            write_headers(args.vsperf_results_dir, args.logfile, '.rx')
+            write_headers(args.vsperf_results_dir, args.logfile, '.tx')
+            while state != 'IDLE':
+                state = stc.get(sequencer, 'State')
+                hndListTx = stc.get(hResDataTx, 'ResultHandleList')
+                if hndListTx:
+                    handles = hndListTx.split(' ')
+                    for handle in handles:
+                        tx_values = stc.get(handle)
+                        write_tx_live_results_to_file(args.vsperf_results_dir,
+                                                      args.logfile,
+                                                      tx_values)
+                if hndListRx:
+                    handles = hndListRx.split(' ')
+                    for handle in handles:
+                        rx_values = stc.get(handle)
+                        write_rx_live_results_to_file(args.vsperf_results_dir,
+                                                      args.logfile,
+                                                      rx_values)
+                time.sleep(1)
+        # Live results not needed, so just wait!
+        else:
+            # Wait for sequencer to finish
+            _LOGGER.info(
+                "Starting test... Please wait for the test to complete...")
+            stc.wait_until_complete()
+
+        _LOGGER.info("The test has completed... Saving results...")
 
         # Determine what the results database filename is...
         lab_server_resultsdb = stc.get(
             "system1.project.TestResultSetting", "CurrentResultFileName")
 
+        if not lab_server_resultsdb or 'Results' not in lab_server_resultsdb:
+            _LOGGER.info("Failed to find results.")
+            stc.end_session()
+            return
+
         if args.verbose:
-            logger.debug("The lab server results database is %s",
-                         lab_server_resultsdb)
+            _LOGGER.debug("The lab server results database is %s",
+                          lab_server_resultsdb)
+
+        # Create Latency Histogram CSV file()
+        if args.latency_histogram:
+            hist_dict_counts = {}
+            for file_url in stc.files():
+                if '-FrameSize-' in file_url:
+                    stc.download(file_url)
+                    filename = file_url.split('/')[-1]
+                    if os.path.exists(os.getcwd() + '/' + filename):
+                        conn = sqlite3.connect(os.getcwd() + '/' + filename)
+                        # cursor = conn.execute(
+                        #    'select * from RxEotStreamResults')
+                        # names = [desc[0] for desc in cursor.description]
+                        counts = conn.execute("SELECT \
+                                              HistBin1Count, HistBin2Count,\
+                                              HistBin3Count, HistBin4Count,\
+                                              HistBin5Count, HistBin6Count,\
+                                              HistBin7Count, HistBin8Count,\
+                                              HistBin9Count, HistBin10Count,\
+                                              HistBin11Count, HistBin12Count,\
+                                              HistBin13Count, HistBin14Count, \
+                                              HistBin15Count, HistBin16Count \
+                                              from RxEotStreamResults")
+                        strs = filename.split('-')
+                        key = strs[strs.index('FrameSize')+1]
+                        if key in hist_dict_counts:
+                            hist_dict_counts[key] = [a+b for a, b in
+                                                     zip(counts.fetchone(),
+                                                         hist_dict_counts[key])]
+                        else:
+                            hist_dict_counts[key] = counts.fetchone()
+                        conn.close()
+
+            write_histogram_to_csv(args.vsperf_results_dir, 'Histogram',
+                                   hist_dict_counts,
+                                   gBucketSizeList)
 
         stc.perform("CSSynchronizeFiles",
                     params={"DefaultDownloadDir": args.results_dir})
@@ -492,10 +744,10 @@ def main():
 
         if not os.path.exists(resultsdb):
             resultsdb = lab_server_resultsdb
-            logger.info("Failed to create the local summary DB File, using"
-                        " the remote DB file instead.")
+            _LOGGER.info("Failed to create the local summary DB File, using"
+                         " the remote DB file instead.")
         else:
-            logger.info(
+            _LOGGER.info(
                 "The local summary DB file has been saved to %s", resultsdb)
 
         # The returns the "RFC2544ThroughputTestResultDetailedSummaryView"
@@ -551,26 +803,27 @@ def main():
                                 ("RFC2544FrameLossTestResultDetailed"
                                  "SummaryView")}))
         if args.verbose:
-            logger.debug("resultsdict[\"Columns\"]: %s",
-                         resultsdict["Columns"])
-            logger.debug("resultsdict[\"Output\"]: %s", resultsdict["Output"])
-            logger.debug("Result paths: %s",
-                         stc.perform("GetTestResultSettingPaths"))
+            _LOGGER.debug("resultsdict[\"Columns\"]: %s",
+                          resultsdict["Columns"])
+            _LOGGER.debug("resultsdict[\"Output\"]: %s", resultsdict["Output"])
+            _LOGGER.debug("Result paths: %s",
+                          stc.perform("GetTestResultSettingPaths"))
 
             # Write results to csv
-            logger.debug("Writing CSV file to results directory %s",
-                         args.results_dir)
+            _LOGGER.debug("Writing CSV file to results directory %s",
+                          args.results_dir)
         write_query_results_to_csv(
             args.results_dir, args.csv_results_file_prefix, resultsdict)
 
     except RuntimeError as e:
-        logger.error(e)
+        stc.end_session()
+        _LOGGER.error(e)
 
     if args.verbose:
-        logger.debug("Destroy session on lab server")
+        _LOGGER.debug("Destroy session on lab server")
     stc.end_session()
 
-    logger.info("Test complete!")
+    _LOGGER.info("Test complete!")
 
 if __name__ == "__main__":
     main()