570720e886d8ab9f73d67497d4c16b2cec13dc3c
[vswitchperf.git] / tools / pkt_gen / moongen / moongen.py
1 # Copyright 2016 Red Hat Inc
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 # Contributors:
16 #   Bill Michalowski, Red Hat Inc.
17 #   Andrew Theurer, Red Hat Inc.
18 """
19 Moongen Traffic Generator Model
20 """
21
22 # python imports
23 from collections import OrderedDict
24 import logging
25 import math
26 import re
27 import subprocess
28
29 # VSPerf imports
30 from conf import settings
31 from conf import merge_spec
32 from core.results.results_constants import ResultsConstants
33 from tools.pkt_gen.trafficgen.trafficgen import ITrafficGenerator
34
35 class Moongen(ITrafficGenerator):
36     """Moongen Traffic generator wrapper."""
37     _logger = logging.getLogger(__name__)
38
39     def __init__(self):
40         """Moongen class constructor."""
41         super().__init__()
42         self._logger.info("In moongen __init__ method")
43         self._params = {}
44         self._moongen_host_ip_addr = (
45             settings.getValue('TRAFFICGEN_MOONGEN_HOST_IP_ADDR'))
46         self._moongen_base_dir = (
47             settings.getValue('TRAFFICGEN_MOONGEN_BASE_DIR'))
48         self._moongen_user = settings.getValue('TRAFFICGEN_MOONGEN_USER')
49         self._moongen_ports = settings.getValue('TRAFFICGEN_MOONGEN_PORTS')
50
51         if settings.getValue('TRAFFICGEN_MOONGEN_LINE_SPEED_GBPS') == '10':
52             self._moongen_line_speed = math.pow(10, 10)
53         else:
54             raise RuntimeError(
55                 'MOONGEN: Invalid line speed in configuration ' + \
56                 'file (today 10Gbps supported)')
57
58     def create_moongen_cfg_file(self, traffic, duration=60,
59                                 acceptable_loss_pct=1, one_shot=0):
60         """Create the Moongen configuration file from VSPERF's traffic profile
61         :param traffic: Detailed "traffic" spec, i.e. IP address, VLAN tags
62         :param duration: The length of time to generate packet throughput
63         :param acceptable_loss: Maximum packet loss acceptable
64         :param one_shot: No RFC 2544 binary search,
65                         just packet flow at traffic specifics
66         """
67         logging.debug("traffic['frame_rate'] = " + \
68             str(traffic['frame_rate']))
69
70         logging.debug("traffic['multistream'] = " + \
71             str(traffic['multistream']))
72
73         logging.debug("traffic['stream_type'] = " + \
74             str(traffic['stream_type']))
75
76         logging.debug("traffic['l2']['srcmac'] = " + \
77             str(traffic['l2']['srcmac']))
78
79         logging.debug("traffic['l2']['dstmac'] = " + \
80             str(traffic['l2']['dstmac']))
81
82         logging.debug("traffic['l3']['proto'] = " + \
83             str(traffic['l3']['proto']))
84
85         logging.debug("traffic['l3']['srcip'] = " + \
86             str(traffic['l3']['srcip']))
87
88         logging.debug("traffic['l3']['dstip'] = " + \
89             str(traffic['l3']['dstip']))
90
91         logging.debug("traffic['l4']['srcport'] = " + \
92             str(traffic['l4']['srcport']))
93
94         logging.debug("traffic['l4']['dstport'] = " + \
95             str(traffic['l4']['dstport']))
96
97         logging.debug("traffic['vlan']['enabled'] = " + \
98             str(traffic['vlan']['enabled']))
99
100         logging.debug("traffic['vlan']['id'] = " + \
101             str(traffic['vlan']['id']))
102
103         logging.debug("traffic['vlan']['priority'] = " + \
104             str(traffic['vlan']['priority']))
105
106         logging.debug("traffic['vlan']['cfi'] = " + \
107             str(traffic['vlan']['cfi']))
108
109         logging.debug(traffic['l2']['framesize'])
110
111         out_file = open("opnfv-vsperf-cfg.lua", "wt")
112
113         out_file.write("VSPERF {\n")
114
115         out_file.write("testType = \"throughput\",\n")
116
117         out_file.write("nrFlows = " + \
118             str(traffic['multistream']) + ",\n")
119
120         out_file.write("runBidirec = " + \
121             traffic['bidir'].lower() + ",\n")
122
123         out_file.write("frameSize = " + \
124             str(traffic['l2']['framesize']) + ",\n")
125
126         out_file.write("srcMac = \"" + \
127             str(traffic['l2']['srcmac']) + "\",\n")
128
129         out_file.write("dstMac = \"" + \
130             str(traffic['l2']['dstmac']) + "\",\n")
131
132         out_file.write("srcIp = \"" + \
133             str(traffic['l3']['srcip']) + "\",\n")
134
135         out_file.write("dstIp = \"" + \
136             str(traffic['l3']['dstip']) + "\",\n")
137
138         if traffic['vlan']['enabled']:
139             out_file.write("vlanId = " + \
140                 str(traffic['vlan']['id']) + ",\n")
141
142         out_file.write("searchRunTime = " + \
143             str(duration) + ",\n")
144
145         out_file.write("validationRunTime = " + \
146             str(duration) + ",\n")
147
148         out_file.write("acceptableLossPct = " + \
149             str(acceptable_loss_pct) + ",\n")
150
151         out_file.write("ports = " +\
152             str(self._moongen_ports) +  ",\n")
153
154         if one_shot:
155             out_file.write("oneShot = true,\n")
156
157         # Need to convert VSPERF frame_rate (percentage of line rate)
158         # to Mpps for Moongen
159         start_rate = str(
160             (traffic['frame_rate'] / 100) * (self._moongen_line_speed / \
161             (8 * (traffic['l2']['framesize'] + 20)) / math.pow(10, 6)))
162
163         logging.debug("startRate = " + start_rate)
164
165         out_file.write("startRate = " + \
166             start_rate + "\n")
167
168         out_file.write("}" + "\n")
169         out_file.close()
170
171         copy_moongen_cfg = "scp opnfv-vsperf-cfg.lua " + \
172                             self._moongen_user + "@" + \
173                             self._moongen_host_ip_addr + ":" + \
174                             self._moongen_base_dir + \
175                             "/. && rm opnfv-vsperf-cfg.lua"
176
177         find_moongen = subprocess.Popen(copy_moongen_cfg,
178                                         shell=True,
179                                         stderr=subprocess.PIPE)
180
181         output, error = find_moongen.communicate()
182
183         if error:
184             logging.error(output)
185             logging.error(error)
186             raise RuntimeError('MOONGEN: Error copying configuration file')
187
188     def connect(self):
189         """Connect to Moongen traffic generator
190
191         Verify that Moongen is on the system indicated by
192         the configuration file
193         """
194         self._logger.info("MOONGEN:  In Moongen connect method...")
195
196         if self._moongen_host_ip_addr:
197             cmd_ping = "ping -c1 " + self._moongen_host_ip_addr
198         else:
199             raise RuntimeError('MOONGEN: Moongen host not defined')
200
201         ping = subprocess.Popen(cmd_ping, shell=True, stderr=subprocess.PIPE)
202         output, error = ping.communicate()
203
204         if ping.returncode:
205             self._logger.error(error)
206             self._logger.error(output)
207             raise RuntimeError('MOONGEN: Cannot ping Moongen host at ' + \
208                                self._moongen_host_ip_addr)
209
210         connect_moongen = "ssh " + self._moongen_user + \
211                           "@" + self._moongen_host_ip_addr
212
213         cmd_find_moongen = connect_moongen + " ls " + \
214                            self._moongen_base_dir + "/trafficgen.lua"
215
216         find_moongen = subprocess.Popen(cmd_find_moongen,
217                                         shell=True,
218                                         stderr=subprocess.PIPE)
219
220         output, error = find_moongen.communicate()
221
222         if find_moongen.returncode:
223             self._logger.error(error)
224             self._logger.error(output)
225             raise RuntimeError(
226                 'MOONGEN: Cannot locate Moongen program at %s within %s' \
227                 % (self._moongen_host_ip_addr, self._moongen_base_dir))
228
229         self._logger.info("MOONGEN: Moongen host successfully found...")
230
231     def disconnect(self):
232         """Disconnect from the traffic generator.
233
234         As with :func:`connect`, this function is optional.
235
236         Where implemented, this function should raise an exception on
237         failure.
238
239         :returns: None
240         """
241         self._logger.info("MOONGEN: In moongen disconnect method")
242
243     def send_burst_traffic(self, traffic=None, numpkts=100, duration=20):
244         """Send a burst of traffic.
245
246         Send a ``numpkts`` packets of traffic, using ``traffic``
247         configuration, with a timeout of ``time``.
248
249         :param traffic: Detailed "traffic" spec, i.e. IP address, VLAN tags
250         :param numpkts: Number of packets to send
251         :param duration: Time to wait to receive packets
252
253         :returns: dictionary of strings with following data:
254             - List of Tx Frames,
255             - List of Rx Frames,
256             - List of Tx Bytes,
257             - List of List of Rx Bytes,
258             - Payload Errors and Sequence Errors.
259         """
260         self._logger.info("In Moongen send_burst_traffic method")
261         return NotImplementedError('Moongen Burst traffic not implemented')
262
263     def send_cont_traffic(self, traffic=None, duration=20):
264         """Send a continuous flow of traffic
265
266         Send packets at ``frame rate``, using ``traffic`` configuration,
267         until timeout ``time`` occurs.
268
269         :param traffic: Detailed "traffic" spec, i.e. IP address, VLAN tags
270         :param duration: Time to wait to receive packets (secs)
271         :returns: dictionary of strings with following data:
272             - Tx Throughput (fps),
273             - Rx Throughput (fps),
274             - Tx Throughput (mbps),
275             - Rx Throughput (mbps),
276             - Tx Throughput (% linerate),
277             - Rx Throughput (% linerate),
278             - Min Latency (ns),
279             - Max Latency (ns),
280             - Avg Latency (ns)
281         """
282         self._logger.info("In Moongen send_cont_traffic method")
283
284         self._params.clear()
285         self._params['traffic'] = self.traffic_defaults.copy()
286
287         if traffic:
288             self._params['traffic'] = merge_spec(self._params['traffic'],
289                                                  traffic)
290
291         Moongen.create_moongen_cfg_file(self,
292                                         traffic,
293                                         duration=duration,
294                                         acceptable_loss_pct=100.0,
295                                         one_shot=1)
296
297         collected_results = Moongen.run_moongen_and_collect_results(self,
298                                                                     test_run=1)
299
300         results = OrderedDict()
301
302         results[ResultsConstants.THROUGHPUT_RX_FPS] = (
303             '{:.6f}'.format(
304                 float(collected_results[ResultsConstants.THROUGHPUT_RX_FPS])))
305
306         results[ResultsConstants.THROUGHPUT_RX_MBPS] = (
307             '{:.3f}'.format(
308                 float(collected_results[ResultsConstants.THROUGHPUT_RX_MBPS])))
309
310         results[ResultsConstants.THROUGHPUT_RX_PERCENT] = (
311             '{:.3f}'.format(
312                 float(
313                     collected_results[ResultsConstants.THROUGHPUT_RX_PERCENT])))
314
315         results[ResultsConstants.TX_RATE_FPS] = (
316             '{:.3f}'.format(
317                 float(collected_results[ResultsConstants.TX_RATE_FPS])))
318
319         results[ResultsConstants.TX_RATE_MBPS] = (
320             '{:.3f}'.format(
321                 float(collected_results[ResultsConstants.TX_RATE_MBPS])))
322
323         results[ResultsConstants.TX_RATE_PERCENT] = (
324             '{:.3f}'.format(
325                 float(collected_results[ResultsConstants.TX_RATE_PERCENT])))
326
327         results[ResultsConstants.MIN_LATENCY_NS] = 0
328
329         results[ResultsConstants.MAX_LATENCY_NS] = 0
330
331         results[ResultsConstants.AVG_LATENCY_NS] = 0
332
333         return results
334
335     def start_cont_traffic(self, traffic=None, duration=20):
336         """ Non-blocking version of 'send_cont_traffic'.
337
338         Start transmission and immediately return. Do not wait for
339         results.
340         :param traffic: Detailed "traffic" spec, i.e. IP address, VLAN tags
341         :param duration: Time to wait to receive packets (secs)
342         """
343         self._logger.info("In Moongen start_cont_traffic method")
344         return NotImplementedError('moongen continuous traffic not implemented')
345
346     def stop_cont_traffic(self):
347         # Stop continuous transmission and return results.
348         self._logger.info("In Moongen stop_cont_traffic method")
349
350     def run_moongen_and_collect_results(self, test_run=1):
351         """Execute Moongen and transform results into VSPERF format
352         :param test_run: The number of tests to run
353         """
354         # Start Moongen and create logfile of the run
355         connect_moongen = "ssh " + self._moongen_user + "@" + \
356             self._moongen_host_ip_addr
357
358         cmd_moongen = " 'cd " + self._moongen_base_dir + \
359             "; ./MoonGen/build/MoonGen trafficgen.lua | tee moongen_log.txt'"
360
361         cmd_start_moongen = connect_moongen + cmd_moongen
362
363         start_moongen = subprocess.Popen(cmd_start_moongen,
364                                          shell=True, stderr=subprocess.PIPE)
365
366         output, error = start_moongen.communicate()
367
368         if start_moongen.returncode:
369             logging.debug(error)
370             logging.debug(output)
371             raise RuntimeError(
372                 'MOONGEN: Error starting Moongen program at %s within %s' \
373                 % (self._moongen_host_ip_addr, self._moongen_base_dir))
374
375         cmd_moongen = "mkdir -p /tmp/moongen/" + str(test_run)
376
377         moongen_create_log_dir = subprocess.Popen(cmd_moongen,
378                                                   shell=True,
379                                                   stderr=subprocess.PIPE)
380
381         output, error = moongen_create_log_dir.communicate()
382
383         if moongen_create_log_dir.returncode:
384             logging.debug(error)
385             logging.debug(output)
386             raise RuntimeError(
387                 'MOONGEN: Error obtaining Moongen log from %s within %s' \
388                 % (self._moongen_host_ip_addr, self._moongen_base_dir))
389
390         cmd_moongen = " scp " + self._moongen_user + "@" + \
391             self._moongen_host_ip_addr + ":" + \
392             self._moongen_base_dir + "/moongen_log.txt /tmp/moongen/" + \
393             str(test_run) + "/moongen-run.log"
394
395         copy_moongen_log = subprocess.Popen(cmd_moongen,
396                                             shell=True,
397                                             stderr=subprocess.PIPE)
398
399         output, error = copy_moongen_log.communicate()
400
401         if copy_moongen_log.returncode:
402             logging.debug(error)
403             logging.debug(output)
404             raise RuntimeError(
405                 'MOONGEN: Error obtaining Moongen log from %s within %s' \
406                 % (self._moongen_host_ip_addr, self._moongen_base_dir))
407
408         log_file = "/tmp/moongen/" + str(test_run) + "/moongen-run.log"
409
410         with open(log_file, 'r') as logfile_handle:
411             mytext = logfile_handle.read()
412
413              # REPORT results line
414              # match.group(1) = Tx frames
415              # match.group(2) = Rx frames
416              # match.group(3) = Frame loss (count)
417              # match.group(4) = Frame loss (percentage)
418              # match.group(5) = Tx Mpps
419              # match.group(6) = Rx Mpps
420             search_pattern = re.compile(
421                 r'\[REPORT\]\s+total\:\s+'
422                 r'Tx\s+frames\:\s+(\d+)\s+'
423                 r'Rx\s+Frames\:\s+(\d+)\s+'
424                 r'frame\s+loss\:\s+(\d+)\,'
425                 r'\s+(\d+\.\d+|\d+)%\s+'
426                 r'Tx\s+Mpps\:\s+(\d+.\d+|\d+)\s+'
427                 r'Rx\s+Mpps\:\s+(\d+\.\d+|\d+)',
428                 re.IGNORECASE)
429
430             results_match = search_pattern.search(mytext)
431
432             if not results_match:
433                 logging.error('There was a problem parsing ' +\
434                     'Moongen REPORT section of Moongen log file')
435
436             moongen_results = OrderedDict()
437             moongen_results[ResultsConstants.THROUGHPUT_RX_FPS] = 0
438             moongen_results[ResultsConstants.THROUGHPUT_RX_MBPS] = 0
439             moongen_results[ResultsConstants.THROUGHPUT_RX_PERCENT] = 0
440             moongen_results[ResultsConstants.TX_RATE_FPS] = 0
441             moongen_results[ResultsConstants.TX_RATE_MBPS] = 0
442             moongen_results[ResultsConstants.TX_RATE_PERCENT] = 0
443             moongen_results[ResultsConstants.B2B_TX_COUNT] = 0
444             moongen_results[ResultsConstants.B2B_FRAMES] = 0
445             moongen_results[ResultsConstants.B2B_FRAME_LOSS_FRAMES] = 0
446             moongen_results[ResultsConstants.B2B_FRAME_LOSS_PERCENT] = 0
447
448             # find PARAMETERS line
449             # parameters_match.group(1) = Frame size
450
451             search_pattern = re.compile(
452                 r'\[PARAMETERS\]\s+.*frameSize\:\s+(\d+)',
453                 flags=re.IGNORECASE)
454             parameters_match = search_pattern.search(mytext)
455
456             if parameters_match:
457                 frame_size = int(parameters_match.group(1))
458             else:
459                 logging.error('There was a problem parsing Moongen ' +\
460                     'PARAMETERS section of Moongen log file')
461                 frame_size = 0
462
463             # Each packet stream in the MoonGen report is prefaced with the
464             # words '[REPORT]Device'.  Count the instances of this string to
465             # get the total aggregrate throughput.  For example:
466             #
467             # - If num_traffic_streams = 1, there is a single
468             #                               unidirectional stream
469             #
470             # - If num_traffic_streams = 2, there is a bidirectional
471             #                               traffic stream
472             num_traffic_streams = mytext.count('[REPORT]Device')
473
474         if results_match and parameters_match and num_traffic_streams:
475             # Assume for now 10G link speed
476             max_theoretical_fps = (
477                 num_traffic_streams * (self._moongen_line_speed / 8) / (frame_size + 20))
478
479             moongen_results[ResultsConstants.THROUGHPUT_RX_FPS] = (
480                 float(results_match.group(6)) * 1000000)
481
482             moongen_results[ResultsConstants.THROUGHPUT_RX_MBPS] = (
483                 float(results_match.group(6)) * (frame_size + 20) * 8)
484
485             moongen_results[ResultsConstants.THROUGHPUT_RX_PERCENT] = (
486                 (100 * float(results_match.group(6)) * 1000000) / max_theoretical_fps)
487
488             moongen_results[ResultsConstants.TX_RATE_FPS] = (
489                 float(results_match.group(5)) * 1000000)
490
491             moongen_results[ResultsConstants.TX_RATE_MBPS] = (
492                 float(results_match.group(5)) * (frame_size + 20) * 8)
493
494             moongen_results[ResultsConstants.TX_RATE_PERCENT] = (
495                 (100 * float(results_match.group(5)) * 1000000) / max_theoretical_fps)
496
497             moongen_results[ResultsConstants.B2B_TX_COUNT] = (
498                 float(results_match.group(1)))
499
500             moongen_results[ResultsConstants.B2B_FRAMES] = (
501                 float(results_match.group(2)))
502
503             moongen_results[ResultsConstants.B2B_FRAME_LOSS_FRAMES] = (
504                 float(results_match.group(3)))
505
506             moongen_results[ResultsConstants.B2B_FRAME_LOSS_PERCENT] = (
507                 float(results_match.group(4)))
508
509         return moongen_results
510
511     def send_rfc2544_throughput(self, traffic=None, duration=20,
512                                 lossrate=0.0, tests=1):
513         #
514         # Send traffic per RFC2544 throughput test specifications.
515         #
516         # Send packets at a variable rate, using ``traffic``
517         # configuration, until minimum rate at which no packet loss is
518         # detected is found.
519         #
520         # :param traffic: Detailed "traffic" spec, see design docs for details
521         # :param tests: Number of tests to execute
522         # :param duration: Per iteration duration
523         # :param lossrate: Acceptable lossrate percentage
524         # :returns: dictionary of strings with following data:
525         #     - Tx Throughput (fps),
526         #     - Rx Throughput (fps),
527         #     - Tx Throughput (mbps),
528         #     - Rx Throughput (mbps),
529         #     - Tx Throughput (% linerate),
530         #     - Rx Throughput (% linerate),
531         #     - Min Latency (ns),
532         #     - Max Latency (ns),
533         #     - Avg Latency (ns)
534         #
535         self._logger.info("In moongen send_rfc2544_throughput method")
536         self._params.clear()
537         self._params['traffic'] = self.traffic_defaults.copy()
538
539         if traffic:
540             self._params['traffic'] = merge_spec(self._params['traffic'],
541                                                  traffic)
542         Moongen.create_moongen_cfg_file(self,
543                                         traffic,
544                                         duration=duration,
545                                         acceptable_loss_pct=lossrate)
546
547         # Initialize RFC 2544 throughput specific results
548         results = OrderedDict()
549         results[ResultsConstants.THROUGHPUT_RX_FPS] = 0
550         results[ResultsConstants.THROUGHPUT_RX_MBPS] = 0
551         results[ResultsConstants.THROUGHPUT_RX_PERCENT] = 0
552         results[ResultsConstants.TX_RATE_FPS] = 0
553         results[ResultsConstants.TX_RATE_MBPS] = 0
554         results[ResultsConstants.TX_RATE_PERCENT] = 0
555         results[ResultsConstants.MIN_LATENCY_NS] = 0
556         results[ResultsConstants.MAX_LATENCY_NS] = 0
557         results[ResultsConstants.AVG_LATENCY_NS] = 0
558
559         for test_run in range(1, tests+1):
560             collected_results = (
561                 Moongen.run_moongen_and_collect_results(self, test_run=test_run))
562
563             results[ResultsConstants.THROUGHPUT_RX_FPS] += (
564                 float(collected_results[ResultsConstants.THROUGHPUT_RX_FPS]))
565
566             results[ResultsConstants.THROUGHPUT_RX_MBPS] += (
567                 float(collected_results[ResultsConstants.THROUGHPUT_RX_MBPS]))
568
569             results[ResultsConstants.THROUGHPUT_RX_PERCENT] += (
570                 float(collected_results[ResultsConstants.THROUGHPUT_RX_PERCENT]))
571
572             results[ResultsConstants.TX_RATE_FPS] += (
573                 float(collected_results[ResultsConstants.TX_RATE_FPS]))
574
575             results[ResultsConstants.TX_RATE_MBPS] += (
576                 float(collected_results[ResultsConstants.TX_RATE_MBPS]))
577
578             results[ResultsConstants.TX_RATE_PERCENT] += (
579                 float(collected_results[ResultsConstants.TX_RATE_PERCENT]))
580
581         results[ResultsConstants.THROUGHPUT_RX_FPS] = (
582             '{:.6f}'.format(results[ResultsConstants.THROUGHPUT_RX_FPS] /
583                             tests))
584
585         results[ResultsConstants.THROUGHPUT_RX_MBPS] = (
586             '{:.3f}'.format(results[ResultsConstants.THROUGHPUT_RX_MBPS] /
587                             tests))
588
589         results[ResultsConstants.THROUGHPUT_RX_PERCENT] = (
590             '{:.3f}'.format(results[ResultsConstants.THROUGHPUT_RX_PERCENT] /
591                             tests))
592
593         results[ResultsConstants.TX_RATE_FPS] = (
594             '{:.6f}'.format(results[ResultsConstants.TX_RATE_FPS] /
595                             tests))
596
597         results[ResultsConstants.TX_RATE_MBPS] = (
598             '{:.3f}'.format(results[ResultsConstants.TX_RATE_MBPS] /
599                             tests))
600
601         results[ResultsConstants.TX_RATE_PERCENT] = (
602             '{:.3f}'.format(results[ResultsConstants.TX_RATE_PERCENT] /
603                             tests))
604
605         results[ResultsConstants.MIN_LATENCY_NS] = (
606             '{:.3f}'.format(results[ResultsConstants.MIN_LATENCY_NS] /
607                             tests))
608
609         results[ResultsConstants.MAX_LATENCY_NS] = (
610             '{:.3f}'.format(results[ResultsConstants.MAX_LATENCY_NS] /
611                             tests))
612
613         results[ResultsConstants.AVG_LATENCY_NS] = (
614             '{:.3f}'.format(results[ResultsConstants.AVG_LATENCY_NS] /
615                             tests))
616
617         return results
618
619     def start_rfc2544_throughput(self, traffic=None, tests=1, duration=20,
620                                  lossrate=0.0):
621         """Non-blocking version of 'send_rfc2544_throughput'.
622
623         Start transmission and immediately return. Do not wait for
624         results.
625         """
626         self._logger.info(
627             "MOONGEN: In moongen start_rfc2544_throughput method")
628
629     def wait_rfc2544_throughput(self):
630         """Wait for and return results of RFC2544 test.
631         """
632         self._logger.info('In moongen wait_rfc2544_throughput')
633
634     def send_rfc2544_back2back(self, traffic=None, duration=60,
635                                lossrate=0.0, tests=1):
636         """Send traffic per RFC2544 back2back test specifications.
637
638         Send packets at a fixed rate, using ``traffic``
639         configuration, for duration seconds.
640
641         :param traffic: Detailed "traffic" spec, see design docs for details
642         :param tests: Number of tests to execute
643         :param duration: Per iteration duration
644         :param lossrate: Acceptable loss percentage
645
646         :returns: Named tuple of Rx Throughput (fps), Rx Throughput (mbps),
647             Tx Rate (% linerate), Rx Rate (% linerate), Tx Count (frames),
648             Back to Back Count (frames), Frame Loss (frames), Frame Loss (%)
649         :rtype: :class:`Back2BackResult`
650         """
651         self._logger.info("In moongen send_rfc2544_back2back method")
652         self._params.clear()
653         self._params['traffic'] = self.traffic_defaults.copy()
654
655         if traffic:
656             self._params['traffic'] = merge_spec(self._params['traffic'],
657                                                  traffic)
658
659         Moongen.create_moongen_cfg_file(self,
660                                         traffic,
661                                         duration=duration,
662                                         acceptable_loss_pct=lossrate)
663
664         # Initialize RFC 2544 B2B specific results
665         results = OrderedDict()
666         results[ResultsConstants.B2B_RX_FPS] = 0
667         results[ResultsConstants.B2B_TX_FPS] = 0
668         results[ResultsConstants.B2B_RX_PERCENT] = 0
669         results[ResultsConstants.B2B_TX_PERCENT] = 0
670         results[ResultsConstants.B2B_TX_COUNT] = 0
671         results[ResultsConstants.B2B_FRAMES] = 0
672         results[ResultsConstants.B2B_FRAME_LOSS_FRAMES] = 0
673         results[ResultsConstants.B2B_FRAME_LOSS_PERCENT] = 0
674         results[ResultsConstants.SCAL_STREAM_COUNT] = 0
675         results[ResultsConstants.SCAL_STREAM_TYPE] = 0
676         results[ResultsConstants.SCAL_PRE_INSTALLED_FLOWS] = 0
677
678         for test_run in range(1, tests+1):
679             collected_results = (
680                 Moongen.run_moongen_and_collect_results(self, test_run=test_run))
681
682             results[ResultsConstants.B2B_RX_FPS] += (
683                 float(collected_results[ResultsConstants.THROUGHPUT_RX_FPS]))
684
685             results[ResultsConstants.B2B_RX_PERCENT] += (
686                 float(collected_results[ResultsConstants.THROUGHPUT_RX_PERCENT]))
687
688             results[ResultsConstants.B2B_TX_FPS] += (
689                 float(collected_results[ResultsConstants.TX_RATE_FPS]))
690
691             results[ResultsConstants.B2B_TX_PERCENT] += (
692                 float(collected_results[ResultsConstants.TX_RATE_PERCENT]))
693
694             results[ResultsConstants.B2B_TX_COUNT] += (
695                 int(collected_results[ResultsConstants.B2B_TX_COUNT]))
696
697             results[ResultsConstants.B2B_FRAMES] += (
698                 int(collected_results[ResultsConstants.B2B_FRAMES]))
699
700             results[ResultsConstants.B2B_FRAME_LOSS_FRAMES] += (
701                 int(collected_results[ResultsConstants.B2B_FRAME_LOSS_FRAMES]))
702
703             results[ResultsConstants.B2B_FRAME_LOSS_PERCENT] += (
704                 int(collected_results[ResultsConstants.B2B_FRAME_LOSS_PERCENT]))
705
706         # Calculate average results
707         results[ResultsConstants.B2B_RX_FPS] = (
708             results[ResultsConstants.B2B_RX_FPS] / tests)
709
710         results[ResultsConstants.B2B_RX_PERCENT] = (
711             results[ResultsConstants.B2B_RX_PERCENT] / tests)
712
713         results[ResultsConstants.B2B_TX_FPS] = (
714             results[ResultsConstants.B2B_TX_FPS] / tests)
715
716         results[ResultsConstants.B2B_TX_PERCENT] = (
717             results[ResultsConstants.B2B_TX_PERCENT] / tests)
718
719         results[ResultsConstants.B2B_TX_COUNT] = (
720             results[ResultsConstants.B2B_TX_COUNT] / tests)
721
722         results[ResultsConstants.B2B_FRAMES] = (
723             results[ResultsConstants.B2B_FRAMES] / tests)
724
725         results[ResultsConstants.B2B_FRAME_LOSS_FRAMES] = (
726             results[ResultsConstants.B2B_FRAME_LOSS_FRAMES] / tests)
727
728         results[ResultsConstants.B2B_FRAME_LOSS_PERCENT] = (
729             results[ResultsConstants.B2B_FRAME_LOSS_PERCENT] / tests)
730
731         results[ResultsConstants.SCAL_STREAM_COUNT] = 0
732         results[ResultsConstants.SCAL_STREAM_TYPE] = 0
733         results[ResultsConstants.SCAL_PRE_INSTALLED_FLOWS] = 0
734
735         return results
736
737     def start_rfc2544_back2back(self, traffic=None, tests=1, duration=20,
738                                 lossrate=0.0):
739         #
740         # Non-blocking version of 'send_rfc2544_back2back'.
741         #
742         # Start transmission and immediately return. Do not wait for results.
743         #
744         self._logger.info("In Moongen start_rfc2544_back2back method")
745         return NotImplementedError(
746             'Moongen start back2back traffic not implemented')
747
748     def wait_rfc2544_back2back(self):
749         self._logger.info("In moongen wait_rfc2544_back2back method")
750         #
751         # Wait and set results of RFC2544 test.
752         #
753         return NotImplementedError(
754             'Moongen wait back2back traffic not implemented')
755
756 if __name__ == "__main__":
757     pass