1ed129680e3b403e221856985b968a11469e3f53
[vswitchperf.git] / tools / pkt_gen / testcenter / testcenter-rfc2544-rest.py
1 # Copyright 2016-2017 Spirent Communications.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #   http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 #
15 # Invalid name of file, must be used '_' instead '-'
16 # pylint: disable=invalid-name
17 '''
18 @author Spirent Communications
19
20 This test automates the RFC2544 tests using the Spirent
21 TestCenter REST APIs. This test supports Python 3.4
22
23 '''
24 import argparse
25 import logging
26 import os
27 import sqlite3
28
29 _LOGGER = logging.getLogger(__name__)
30
31
32 def create_dir(path):
33     """Create the directory as specified in path """
34     if not os.path.exists(path):
35         try:
36             os.makedirs(path)
37         except OSError as ex:
38             _LOGGER.error("Failed to create directory %s: %s", path, str(ex))
39             raise
40
41
42 def write_histogram_to_csv(results_path, csv_results_file_prefix,
43                            counts, ranges):
44     """ Write the results of the query to the CSV """
45     filec = os.path.join(results_path, csv_results_file_prefix + ".csv")
46     with open(filec, "wb") as result_file:
47         for key in counts:
48             result_file.write(str(key) + "\n")
49             result_file.write(str(ranges) + "\n")
50             result_file.write(str(counts[key]) + "\n")
51
52
53 def write_query_results_to_csv(results_path, csv_results_file_prefix,
54                                query_results):
55     """ Write the results of the query to the CSV """
56     create_dir(results_path)
57     filec = os.path.join(results_path, csv_results_file_prefix + ".csv")
58     with open(filec, "wb") as result_file:
59         result_file.write(query_results["Columns"].replace(" ", ",") + "\n")
60         for row in (query_results["Output"].replace("} {", ",").
61                     replace("{", "").replace("}", "").split(",")):
62             result_file.write(row.replace(" ", ",") + "\n")
63
64
65 def positive_int(value):
66     """ Positive Integer type for Arguments """
67     ivalue = int(value)
68     if ivalue <= 0:
69         raise argparse.ArgumentTypeError(
70             "%s is an invalid positive int value" % value)
71     return ivalue
72
73
74 def percent_float(value):
75     """ Floating type for Arguments """
76     pvalue = float(value)
77     if pvalue < 0.0 or pvalue > 100.0:
78         raise argparse.ArgumentTypeError(
79             "%s not in range [0.0, 100.0]" % pvalue)
80     return pvalue
81
82
83 # pylint: disable=too-many-branches, too-many-statements, too-many-locals
84 def main():
85     """ Read the arguments, Invoke Test and Return the results"""
86     parser = argparse.ArgumentParser()
87     # Required parameters
88     required_named = parser.add_argument_group("required named arguments")
89     required_named.add_argument("--lab_server_addr",
90                                 required=True,
91                                 help=("The IP address of the"
92                                       "Spirent Lab Server"),
93                                 dest="lab_server_addr")
94     required_named.add_argument("--license_server_addr",
95                                 required=True,
96                                 help=("The IP address of the Spirent"
97                                       "License Server"),
98                                 dest="license_server_addr")
99     required_named.add_argument("--east_chassis_addr",
100                                 required=True,
101                                 help=("The TestCenter chassis IP address to"
102                                       "use for the east test port"),
103                                 dest="east_chassis_addr")
104     required_named.add_argument("--east_slot_num",
105                                 type=positive_int,
106                                 required=True,
107                                 help=("The TestCenter slot number to"
108                                       "use for the east test port"),
109                                 dest="east_slot_num")
110     required_named.add_argument("--east_port_num",
111                                 type=positive_int,
112                                 required=True,
113                                 help=("The TestCenter port number to use"
114                                       "for the east test port"),
115                                 dest="east_port_num")
116     required_named.add_argument("--west_chassis_addr",
117                                 required=True,
118                                 help=("The TestCenter chassis IP address"
119                                       "to use for the west test port"),
120                                 dest="west_chassis_addr")
121     required_named.add_argument("--west_slot_num",
122                                 type=positive_int,
123                                 required=True,
124                                 help=("The TestCenter slot number to use"
125                                       "for the west test port"),
126                                 dest="west_slot_num")
127     required_named.add_argument("--west_port_num",
128                                 type=positive_int,
129                                 required=True,
130                                 help=("The TestCenter port number to"
131                                       "use for the west test port"),
132                                 dest="west_port_num")
133     # Optional parameters
134     optional_named = parser.add_argument_group("optional named arguments")
135     optional_named.add_argument("--metric",
136                                 required=False,
137                                 help=("One among - throughput, latency,\
138                                       backtoback and frameloss"),
139                                 choices=["throughput", "latency",
140                                          "backtoback", "frameloss"],
141                                 default="throughput",
142                                 dest="metric")
143     optional_named.add_argument("--test_session_name",
144                                 required=False,
145                                 default="RFC2544 East-West Throughput",
146                                 help=("The friendly name to identify"
147                                       "the Spirent Lab Server test session"),
148                                 dest="test_session_name")
149
150     optional_named.add_argument("--test_user_name",
151                                 required=False,
152                                 default="RFC2544 East-West User",
153                                 help=("The friendly name to identify the"
154                                       "Spirent Lab Server test user"),
155                                 dest="test_user_name")
156     optional_named.add_argument("--results_dir",
157                                 required=False,
158                                 default="./Results",
159                                 help="The directory to copy results to",
160                                 dest="results_dir")
161     optional_named.add_argument("--vsperf_results_dir",
162                                 required=False,
163                                 default="./Results",
164                                 help="The directory to copy results to",
165                                 dest="vsperf_results_dir")
166     optional_named.add_argument("--csv_results_file_prefix",
167                                 required=False,
168                                 default="Rfc2544Tput",
169                                 help="The prefix for the CSV results files",
170                                 dest="csv_results_file_prefix")
171     optional_named.add_argument("--num_trials",
172                                 type=positive_int,
173                                 required=False,
174                                 default=1,
175                                 help=("The number of trials to execute during"
176                                       "the test"),
177                                 dest="num_trials")
178     optional_named.add_argument("--trial_duration_sec",
179                                 type=positive_int,
180                                 required=False,
181                                 default=60,
182                                 help=("The duration of each trial executed"
183                                       "during the test"),
184                                 dest="trial_duration_sec")
185     optional_named.add_argument("--traffic_pattern",
186                                 required=False,
187                                 choices=["BACKBONE", "MESH", "PAIR"],
188                                 default="PAIR",
189                                 help="The traffic pattern between endpoints",
190                                 dest="traffic_pattern")
191     optional_named.add_argument("--traffic_custom",
192                                 required=False,
193                                 default=None,
194                                 help="The traffic pattern between endpoints",
195                                 dest="traffic_custom")
196     optional_named.add_argument("--search_mode",
197                                 required=False,
198                                 choices=["COMBO", "STEP", "BINARY"],
199                                 default="BINARY",
200                                 help=("The search mode used to find the"
201                                       "throughput rate"),
202                                 dest="search_mode")
203     optional_named.add_argument("--learning_mode",
204                                 required=False,
205                                 choices=["AUTO", "L2_LEARNING",
206                                          "L3_LEARNING", "NONE"],
207                                 default="AUTO",
208                                 help=("The learning mode used during the test,"
209                                       "default is 'NONE'"),
210                                 dest="learning_mode")
211     optional_named.add_argument("--rate_lower_limit_pct",
212                                 type=percent_float,
213                                 required=False,
214                                 default=1.0,
215                                 help=("The minimum percent line rate that"
216                                       "will be used during the test"),
217                                 dest="rate_lower_limit_pct")
218     optional_named.add_argument("--rate_upper_limit_pct",
219                                 type=percent_float,
220                                 required=False,
221                                 default=99.0,
222                                 help=("The maximum percent line rate that"
223                                       "will be used during the test"),
224                                 dest="rate_upper_limit_pct")
225     optional_named.add_argument("--rate_initial_pct",
226                                 type=percent_float,
227                                 required=False,
228                                 default=99.0,
229                                 help=("If Search Mode is BINARY, the percent"
230                                       "line rate that will be used at the"
231                                       "start of the test"),
232                                 dest="rate_initial_pct")
233     optional_named.add_argument("--rate_step_pct",
234                                 type=percent_float,
235                                 required=False,
236                                 default=10.0,
237                                 help=("If SearchMode is STEP, the percent"
238                                       "load increase per step"),
239                                 dest="rate_step_pct")
240     optional_named.add_argument("--resolution_pct",
241                                 type=percent_float,
242                                 required=False,
243                                 default=1.0,
244                                 help=("The minimum percentage of load"
245                                       "adjustment between iterations"),
246                                 dest="resolution_pct")
247     optional_named.add_argument("--frame_size_list",
248                                 type=lambda s: [int(item)
249                                                 for item in s.split(',')],
250                                 required=False,
251                                 default=[256],
252                                 help="A comma-delimited list of frame sizes",
253                                 dest="frame_size_list")
254     optional_named.add_argument("--acceptable_frame_loss_pct",
255                                 type=percent_float,
256                                 required=False,
257                                 default=0.0,
258                                 help=("The maximum acceptable frame loss"
259                                       "percent in any iteration"),
260                                 dest="acceptable_frame_loss_pct")
261     optional_named.add_argument("--east_intf_addr",
262                                 required=False,
263                                 default="192.85.1.3",
264                                 help=("The address to assign to the first"
265                                       "emulated device interface on the first"
266                                       "east port"),
267                                 dest="east_intf_addr")
268     optional_named.add_argument("--east_intf_gateway_addr",
269                                 required=False,
270                                 default="192.85.1.53",
271                                 help=("The gateway address to assign to the"
272                                       "first emulated device interface on the"
273                                       "first east port"),
274                                 dest="east_intf_gateway_addr")
275     optional_named.add_argument("--west_intf_addr",
276                                 required=False,
277                                 default="192.85.1.53",
278                                 help=("The address to assign to the first"
279                                       "emulated device interface on the"
280                                       "first west port"),
281                                 dest="west_intf_addr")
282     optional_named.add_argument("--west_intf_gateway_addr",
283                                 required=False,
284                                 default="192.85.1.53",
285                                 help=("The gateway address to assign to"
286                                       "the first emulated device interface"
287                                       "on the first west port"),
288                                 dest="west_intf_gateway_addr")
289     optional_named.add_argument("--latency_histogram",
290                                 required=False,
291                                 action="store_true",
292                                 help="latency histogram is required in output?",
293                                 dest="latency_histogram")
294     parser.add_argument("-v",
295                         "--verbose",
296                         required=False,
297                         default=True,
298                         help="More output during operation when present",
299                         action="store_true",
300                         dest="verbose")
301     args = parser.parse_args()
302
303     if args.verbose:
304         _LOGGER.debug("Creating results directory")
305     create_dir(args.results_dir)
306
307     session_name = args.test_session_name
308     user_name = args.test_user_name
309     # pylint: disable=import-error
310     try:
311         # Load Spirent REST Library
312         from stcrestclient import stchttp
313
314         stc = stchttp.StcHttp(args.lab_server_addr)
315         session_id = stc.new_session(user_name, session_name)
316         stc.join_session(session_id)
317     except RuntimeError as err:
318         _LOGGER.error(err)
319         raise
320
321     # Get STC system info.
322     tx_port_loc = "//%s/%s/%s" % (args.east_chassis_addr,
323                                   args.east_slot_num,
324                                   args.east_port_num)
325     rx_port_loc = "//%s/%s/%s" % (args.west_chassis_addr,
326                                   args.west_slot_num,
327                                   args.west_port_num)
328
329     # Retrieve and display the server information
330     if args.verbose:
331         _LOGGER.debug("SpirentTestCenter system version: %s",
332                       stc.get("system1", "version"))
333
334     # pylint: disable=too-many-nested-blocks
335     try:
336         device_list = []
337         port_list = []
338         if args.verbose:
339             _LOGGER.debug("Bring up license server")
340         license_mgr = stc.get("system1", "children-licenseservermanager")
341         if args.verbose:
342             _LOGGER.debug("license_mgr = %s", license_mgr)
343         stc.create("LicenseServer", under=license_mgr, attributes={
344             "server": args.license_server_addr})
345
346         # Create the root project object
347         if args.verbose:
348             _LOGGER.debug("Creating project ...")
349         project = stc.get("System1", "children-Project")
350
351         # Configure any custom traffic parameters
352         if args.traffic_custom == "cont":
353             if args.verbose:
354                 _LOGGER.debug("Configure Continuous Traffic")
355             stc.create("ContinuousTestConfig", under=project)
356
357         # Create ports
358         if args.verbose:
359             _LOGGER.debug("Creating ports ...")
360         east_chassis_port = stc.create('port', project)
361         if args.verbose:
362             _LOGGER.debug("Configuring TX port ...")
363         stc.config(east_chassis_port, {'location': tx_port_loc})
364         port_list.append(east_chassis_port)
365
366         west_chassis_port = stc.create('port', project)
367         if args.verbose:
368             _LOGGER.debug("Configuring RX port ...")
369         stc.config(west_chassis_port, {'location': rx_port_loc})
370         port_list.append(west_chassis_port)
371
372         # Create emulated genparam for east port
373         east_device_gen_params = stc.create("EmulatedDeviceGenParams",
374                                             under=project,
375                                             attributes={"Port":
376                                                         east_chassis_port})
377         # Create the DeviceGenEthIIIfParams object
378         stc.create("DeviceGenEthIIIfParams",
379                    under=east_device_gen_params,
380                    attributes={'UseDefaultPhyMac':True})
381
382         # Configuring Ipv4 interfaces
383         stc.create("DeviceGenIpv4IfParams",
384                    under=east_device_gen_params,
385                    attributes={"Addr": args.east_intf_addr,
386                                "Gateway": args.east_intf_gateway_addr})
387         # Create Devices using the Device Wizard
388         device_gen_config = stc.perform("DeviceGenConfigExpand",
389                                         params={"DeleteExisting": "No",
390                                                 "GenParams":
391                                                 east_device_gen_params})
392         # Append to the device list
393         device_list.append(device_gen_config['ReturnList'])
394
395         # Create emulated genparam for west port
396         west_device_gen_params = stc.create("EmulatedDeviceGenParams",
397                                             under=project,
398                                             attributes={"Port":
399                                                         west_chassis_port})
400         # Create the DeviceGenEthIIIfParams object
401         stc.create("DeviceGenEthIIIfParams",
402                    under=west_device_gen_params,
403                    attributes={'UseDefaultPhyMac':True})
404
405         # Configuring Ipv4 interfaces
406         stc.create("DeviceGenIpv4IfParams",
407                    under=west_device_gen_params,
408                    attributes={"Addr": args.west_intf_addr,
409                                "Gateway": args.west_intf_gateway_addr})
410         # Create Devices using the Device Wizard
411         device_gen_config = stc.perform("DeviceGenConfigExpand",
412                                         params={"DeleteExisting": "No",
413                                                 "GenParams":
414                                                 west_device_gen_params})
415         # Append to the device list
416         device_list.append(device_gen_config['ReturnList'])
417         if args.verbose:
418             _LOGGER.debug(device_list)
419
420         # Configure Histogram
421         if args.latency_histogram:
422             # Generic Configuration
423             histResOptions = stc.get("project1", 'children-ResultOptions')
424             stc.config(histResOptions, {'ResultViewMode': 'HISTOGRAM'})
425             # East Port Configuration
426             histAnaEast = stc.get(east_chassis_port, 'children-Analyzer')
427             histAnaEastConfig = stc.get(histAnaEast, 'children-AnalyzerConfig')
428             stc.config(histAnaEastConfig, {'HistogramMode': 'LATENCY'})
429             eLatHist = stc.get(histAnaEastConfig, 'children-LatencyHistogram')
430             stc.config(eLatHist, {'ConfigMode': 'CONFIG_LIMIT_MODE',
431                                   'BucketSizeUnit': 'ten_nanoseconds',
432                                   'Active': 'TRUE',
433                                   'DistributionMode': 'CENTERED_MODE'})
434             # West Port Configuration
435             histAnaWest = stc.get(west_chassis_port, 'children-Analyzer')
436             histAnaWestConfig = stc.get(histAnaWest, 'children-AnalyzerConfig')
437             stc.config(histAnaWestConfig, {'HistogramMode': 'LATENCY'})
438             wLatHist = stc.get(histAnaWestConfig, 'children-LatencyHistogram')
439             stc.config(wLatHist, {'ConfigMode': 'CONFIG_LIMIT_MODE',
440                                   'BucketSizeUnit': 'ten_nanoseconds',
441                                   'Active': 'TRUE',
442                                   'DistributionMode': 'CENTERED_MODE'})
443             gBucketSizeList = stc.get(wLatHist, 'BucketSizeList')
444             # gLimitSizeList  = stc.get(wLatHist, 'LimitList')
445
446         # Create the RFC 2544 'metric test
447         if args.metric == "throughput":
448             if args.verbose:
449                 _LOGGER.debug("Set up the RFC2544 throughput test...")
450             stc.perform("Rfc2544SetupThroughputTestCommand",
451                         params={"AcceptableFrameLoss":
452                                 args.acceptable_frame_loss_pct,
453                                 "Duration": args.trial_duration_sec,
454                                 "FrameSizeList": args.frame_size_list,
455                                 "LearningMode": args.learning_mode,
456                                 "NumOfTrials": args.num_trials,
457                                 "RateInitial": args.rate_initial_pct,
458                                 "RateLowerLimit": args.rate_lower_limit_pct,
459                                 "RateStep": args.rate_step_pct,
460                                 "RateUpperLimit": args.rate_upper_limit_pct,
461                                 "Resolution": args.resolution_pct,
462                                 "SearchMode": args.search_mode,
463                                 "TrafficPattern": args.traffic_pattern})
464         elif args.metric == "backtoback":
465             stc.perform("Rfc2544SetupBackToBackTestCommand",
466                         params={"AcceptableFrameLoss":
467                                 args.acceptable_frame_loss_pct,
468                                 "Duration": args.trial_duration_sec,
469                                 "FrameSizeList": args.frame_size_list,
470                                 "LearningMode": args.learning_mode,
471                                 "LatencyType": args.latency_type,
472                                 "NumOfTrials": args.num_trials,
473                                 "RateInitial": args.rate_initial_pct,
474                                 "RateLowerLimit": args.rate_lower_limit_pct,
475                                 "RateStep": args.rate_step_pct,
476                                 "RateUpperLimit": args.rate_upper_limit_pct,
477                                 "Resolution": args.resolution_pct,
478                                 "SearchMode": args.search_mode,
479                                 "TrafficPattern": args.traffic_pattern})
480         elif args.metric == "frameloss":
481             stc.perform("Rfc2544SetupFrameLossTestCommand",
482                         params={"AcceptableFrameLoss":
483                                 args.acceptable_frame_loss_pct,
484                                 "Duration": args.trial_duration_sec,
485                                 "FrameSizeList": args.frame_size_list,
486                                 "LearningMode": args.learning_mode,
487                                 "LatencyType": args.latency_type,
488                                 "NumOfTrials": args.num_trials,
489                                 "RateInitial": args.rate_initial_pct,
490                                 "RateLowerLimit": args.rate_lower_limit_pct,
491                                 "RateStep": args.rate_step_pct,
492                                 "RateUpperLimit": args.rate_upper_limit_pct,
493                                 "Resolution": args.resolution_pct,
494                                 "SearchMode": args.search_mode,
495                                 "TrafficPattern": args.traffic_pattern})
496         elif args.metric == "latency":
497             stc.perform("Rfc2544SetupLatencyTestCommand",
498                         params={"AcceptableFrameLoss":
499                                 args.acceptable_frame_loss_pct,
500                                 "Duration": args.trial_duration_sec,
501                                 "FrameSizeList": args.frame_size_list,
502                                 "LearningMode": args.learning_mode,
503                                 "LatencyType": args.latency_type,
504                                 "NumOfTrials": args.num_trials,
505                                 "RateInitial": args.rate_initial_pct,
506                                 "RateLowerLimit": args.rate_lower_limit_pct,
507                                 "RateStep": args.rate_step_pct,
508                                 "RateUpperLimit": args.rate_upper_limit_pct,
509                                 "Resolution": args.resolution_pct,
510                                 "SearchMode": args.search_mode,
511                                 "TrafficPattern": args.traffic_pattern})
512
513         # Save the configuration
514         stc.perform("SaveToTcc", params={"Filename": "2544.tcc"})
515         # Connect to the hardware...
516         stc.perform("AttachPorts", params={"portList": stc.get(
517             "system1.project", "children-port"), "autoConnect": "TRUE"})
518         # Apply configuration.
519         if args.verbose:
520             _LOGGER.debug("Apply configuration...")
521         stc.apply()
522
523         if args.verbose:
524             _LOGGER.debug("Starting the sequencer...")
525         stc.perform("SequencerStart")
526
527         # Wait for sequencer to finish
528         _LOGGER.info(
529             "Starting test... Please wait for the test to complete...")
530         stc.wait_until_complete()
531         _LOGGER.info("The test has completed... Saving results...")
532
533         # Determine what the results database filename is...
534         lab_server_resultsdb = stc.get(
535             "system1.project.TestResultSetting", "CurrentResultFileName")
536
537         if args.verbose:
538             _LOGGER.debug("The lab server results database is %s",
539                           lab_server_resultsdb)
540
541         # Create Latency Histogram CSV file()
542         if args.latency_histogram:
543             hist_dict_counts = {}
544             for file_url in stc.files():
545                 if '-FrameSize-' in file_url:
546                     stc.download(file_url)
547                     filename = file_url.split('/')[-1]
548                     if os.path.exists(os.getcwd() + '/' + filename):
549                         conn = sqlite3.connect(os.getcwd() + '/' + filename)
550                         # cursor = conn.execute(
551                         #    'select * from RxEotStreamResults')
552                         # names = [desc[0] for desc in cursor.description]
553                         counts = conn.execute("SELECT \
554                                               HistBin1Count, HistBin2Count,\
555                                               HistBin3Count, HistBin4Count,\
556                                               HistBin5Count, HistBin6Count,\
557                                               HistBin7Count, HistBin8Count,\
558                                               HistBin9Count, HistBin10Count,\
559                                               HistBin11Count, HistBin12Count,\
560                                               HistBin13Count, HistBin14Count, \
561                                               HistBin15Count, HistBin16Count \
562                                               from RxEotStreamResults")
563                         strs = filename.split('-')
564                         key = strs[strs.index('FrameSize')+1]
565                         if key in hist_dict_counts:
566                             hist_dict_counts[key] = [a+b for a, b in
567                                                      zip(counts.fetchone(),
568                                                          hist_dict_counts[key])]
569                         else:
570                             hist_dict_counts[key] = counts.fetchone()
571                         conn.close()
572
573             write_histogram_to_csv(args.vsperf_results_dir, 'Histogram',
574                                    hist_dict_counts,
575                                    gBucketSizeList)
576
577         stc.perform("CSSynchronizeFiles",
578                     params={"DefaultDownloadDir": args.results_dir})
579
580         resultsdb = args.results_dir + \
581             lab_server_resultsdb.split("/Results")[1]
582
583         if not os.path.exists(resultsdb):
584             resultsdb = lab_server_resultsdb
585             _LOGGER.info("Failed to create the local summary DB File, using"
586                          " the remote DB file instead.")
587         else:
588             _LOGGER.info(
589                 "The local summary DB file has been saved to %s", resultsdb)
590
591         # The returns the "RFC2544ThroughputTestResultDetailedSummaryView"
592         # table view from the results database.
593         # There are other views available.
594
595         if args.metric == "throughput":
596             resultsdict = (
597                 stc.perform("QueryResult",
598                             params={
599                                 "DatabaseConnectionString":
600                                 resultsdb,
601                                 "ResultPath":
602                                 ("RFC2544ThroughputTestResultDetailed"
603                                  "SummaryView")}))
604
605         # The returns the "RFC2544BacktoBackTestResultDetailedSummaryView"
606         # table view from the results database.
607         # There are other views available.
608         elif args.metric == "backtoback":
609             resultsdict = (
610                 stc.perform("QueryResult",
611                             params={
612                                 "DatabaseConnectionString":
613                                 resultsdb,
614                                 "ResultPath":
615                                 ("RFC2544Back2BackTestResultDetailed"
616                                  "SummaryView")}))
617
618         # The returns the "RFC2544LatencyTestResultDetailedSummaryView"
619         # table view from the results database.
620         # There are other views available.
621         elif args.metric == "latency":
622             resultsdict = (
623                 stc.perform("QueryResult",
624                             params={
625                                 "DatabaseConnectionString":
626                                 resultsdb,
627                                 "ResultPath":
628                                 ("RFC2544LatencyTestResultDetailed"
629                                  "SummaryView")}))
630
631         # The returns the "RFC2544FrameLossTestResultDetailedSummaryView"
632         # table view from the results database.
633         # There are other views available.
634         elif args.metric == "frameloss":
635             resultsdict = (
636                 stc.perform("QueryResult",
637                             params={
638                                 "DatabaseConnectionString":
639                                 resultsdb,
640                                 "ResultPath":
641                                 ("RFC2544FrameLossTestResultDetailed"
642                                  "SummaryView")}))
643         if args.verbose:
644             _LOGGER.debug("resultsdict[\"Columns\"]: %s",
645                           resultsdict["Columns"])
646             _LOGGER.debug("resultsdict[\"Output\"]: %s", resultsdict["Output"])
647             _LOGGER.debug("Result paths: %s",
648                           stc.perform("GetTestResultSettingPaths"))
649
650             # Write results to csv
651             _LOGGER.debug("Writing CSV file to results directory %s",
652                           args.results_dir)
653         write_query_results_to_csv(
654             args.results_dir, args.csv_results_file_prefix, resultsdict)
655
656     except RuntimeError as e:
657         _LOGGER.error(e)
658
659     if args.verbose:
660         _LOGGER.debug("Destroy session on lab server")
661     stc.end_session()
662
663     _LOGGER.info("Test complete!")
664
665 if __name__ == "__main__":
666     main()