# Copyright 2014 Cisco Systems, Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # import re from log import LOG from perf_tool import PerfTool # The resulting unit should be in K MULTIPLIERS = {'K': 1, 'M': 1.0e3, 'G': 1.0e6} def get_bdw_kbps(bdw, bdw_unit): if not bdw_unit: # bits/sec return bdw / 1000 if bdw_unit in MULTIPLIERS: return int(bdw * MULTIPLIERS[bdw_unit]) LOG.error('Error: unknown multiplier: ' + bdw_unit) return bdw class IperfTool(PerfTool): def __init__(self, instance): PerfTool.__init__(self, 'iperf-2.0.9', instance) def get_server_launch_cmd(self): '''Return the command to launch the server side.''' # Need 1 server for tcp (port 5001) and 1 for udp (port 5001) if self.instance.config.ipv6_mode: return [self.dest_path + ' -s -V >/dev/null &', self.dest_path + ' -s -u -V >/dev/null &'] else: return [self.dest_path + ' -s >/dev/null &', self.dest_path + ' -s -u >/dev/null &'] def run_client(self, target_ip, target_instance, mss=None, bandwidth=0, bidirectional=False): '''Run the test :return: list containing one or more dictionary results ''' res_list = [] # Get list of protocols and packet sizes to measure (proto_list, proto_pkt_sizes) = self.get_proto_profile() for proto, pkt_size_list in zip(proto_list, proto_pkt_sizes): # bidirectional is not supported for udp # (need to find the right iperf options to make it work as there are # issues for the server to send back results to the client in reverse # direction if proto == 'UDP': bidir = False loop_count = 1 else: # For accuracy purpose, TCP throughput will be measured multiple times bidir = bidirectional loop_count = self.instance.config.tcp_tp_loop_count for pkt_size in pkt_size_list: self.instance.display('Measuring %s Throughput (packet size=%d)...', proto, pkt_size) for _ in xrange(loop_count): res = self.run_client_dir(target_ip, mss, bandwidth_kbps=bandwidth, bidirectional=bidir, protocol=proto, length=pkt_size) # for bidirectional the function returns a list of 2 results res_list.extend(res) return res_list def run_client_dir(self, target_ip, mss, bidirectional=False, bandwidth_kbps=0, protocol='TCP', length=0, no_cpu_timed=0): '''Run client for given protocol and packet size :param bandwidth_kbps: transmit rate limit in Kbps :param udp: if true get UDP throughput, else get TCP throughput :param length: length of network write|read buf (default 1K|8K/udp, 64K/tcp) for udp is the packet size :param no_cpu_timed: if non zero will disable cpu collection and override the time with the provided value - used mainly for udp to find quickly the optimal throughput using short tests at various throughput values :return: a list of dictionary with the 1 or 2 results (see parse_results()) ''' # run client using the default TCP window size (tcp window # scaling is normally enabled by default so setting explicit window # size is not going to help achieve better results) opts = '' udp = protocol == "UDP" # run iperf client using the default TCP window size (tcp window # scaling is normally enabled by default so setting explicit window # size is not going to help achieve better results) if mss: opts += " -M " + str(mss) if bidirectional: opts += " -r" if length: opts += " -l" + str(length) if self.instance.config.ipv6_mode: opts += " -V " if udp: opts += " -u" # for UDP if the bandwidth is not provided we need to calculate # the optimal bandwidth if not bandwidth_kbps: udp_res = self.find_bdw(length, target_ip, protocol) if 'error' in udp_res: return [udp_res] if not self.instance.gmond_svr: # if we do not collect CPU we might as well return # the results found through iteration return [udp_res] bandwidth_kbps = udp_res['throughput_kbps'] if bandwidth_kbps: opts += " -b%dK" % (bandwidth_kbps) if no_cpu_timed: duration_sec = no_cpu_timed else: duration_sec = self.instance.get_cmd_duration() cmd = "%s -c %s -t %d %s" % (self.dest_path, target_ip, duration_sec, opts) self.instance.buginf(cmd) if no_cpu_timed: # force the timeout value with 20 second extra for the command to # complete and do not collect CPU cpu_load = None cmd_out = self.instance.exec_command(cmd, duration_sec + 20) else: (cmd_out, cpu_load) = self.instance.exec_with_cpu(cmd) if udp: # Decode UDP output (unicast and multicast): # # [ 3] local 127.0.0.1 port 54244 connected with 127.0.0.1 port 5001 # [ ID] Interval Transfer Bandwidth # [ 3] 0.0-10.0 sec 1.25 MBytes 1.05 Mbits/sec # [ 3] Sent 893 datagrams # [ 3] Server Report: # [ ID] Interval Transfer Bandwidth Jitter Lost/Total Da # [ 3] 0.0-10.0 sec 1.25 MBytes 1.05 Mbits/sec 0.032 ms 1/894 (0.11%) # [ 3] 0.0-15.0 sec 14060 datagrams received out-of-order re_udp = r'([\d\.]*)\s*([KMG]?)bits/sec\s*[\d\.]*\s*ms\s*(\d*)/\s*(\d*) ' match = re.search(re_udp, cmd_out) if match: bdw = float(match.group(1)) bdw_unit = match.group(2) drop = float(match.group(3)) pkt = int(match.group(4)) # iperf uses multiple of 1000 for K - not 1024 return [self.parse_results('UDP', get_bdw_kbps(bdw, bdw_unit), lossrate=round(drop * 100 / pkt, 2), msg_size=length, cpu_load=cpu_load)] else: # TCP output: # [ 3] local 127.0.0.1 port 57936 connected with 127.0.0.1 port 5001 # [ ID] Interval Transfer Bandwidth # [ 3] 0.0-10.0 sec 2.09 GBytes 1.79 Gbits/sec # # For bi-directional option (-r), last 3 lines: # [ 5] 0.0-10.0 sec 36.0 GBytes 31.0 Gbits/sec # [ 4] local 127.0.0.1 port 5002 connected with 127.0.0.1 port 39118 # [ 4] 0.0-10.0 sec 36.0 GBytes 30.9 Gbits/sec re_tcp = r'Bytes\s*([\d\.]*)\s*([KMG])bits/sec' match = re.search(re_tcp, cmd_out) if match: bdw = float(match.group(1)) bdw_unit = match.group(2) res = [self.parse_results('TCP', get_bdw_kbps(bdw, bdw_unit), msg_size=length, cpu_load=cpu_load)] if bidirectional: # decode the last row results re_tcp = r'Bytes\s*([\d\.]*)\s*([KMG])bits/sec$' match = re.search(re_tcp, cmd_out) if match: bdw = float(match.group(1)) bdw_unit = match.group(2) # use the same cpu load since the same run # does both directions res.append(self.parse_results('TCP', get_bdw_kbps(bdw, bdw_unit), reverse_dir=True, msg_size=length, cpu_load=cpu_load)) return res return [self.parse_error(protocol, 'Could not parse: %s' % (cmd_out))]