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