
It's mostly done via 2to3 and completed by manual updates fixing TypeErrors and former false print calls. It has been double checked via Functest which cannot cover all logics. Change-Id: If272524f147735a942a84ce1d2bec4e3423817c2 Signed-off-by: Cédric Ollivier <ollivier.cedric@gmail.com>
227 lines
11 KiB
Python
227 lines
11 KiB
Python
# 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.
|
|
#
|
|
|
|
from .log import LOG
|
|
from .perf_tool import PerfTool
|
|
import re
|
|
from . import sshutils
|
|
|
|
|
|
class NuttcpTool(PerfTool):
|
|
def __init__(self, instance):
|
|
PerfTool.__init__(self, 'nuttcp-8.1.4', instance)
|
|
|
|
def get_server_launch_cmd(self):
|
|
'''Return the commands to launch the server side.'''
|
|
if self.instance.config.ipv6_mode:
|
|
return [self.dest_path + ' -P5002 -S --single-threaded -6 &']
|
|
else:
|
|
return [self.dest_path + ' -P5002 -S --single-threaded &']
|
|
|
|
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 = []
|
|
if bidirectional:
|
|
reverse_dir_list = [False, True]
|
|
else:
|
|
reverse_dir_list = [False]
|
|
|
|
# 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):
|
|
for pkt_size in pkt_size_list:
|
|
for reverse_dir in reverse_dir_list:
|
|
# nuttcp does not support reverse dir for UDP...
|
|
if reverse_dir and proto != "TCP":
|
|
continue
|
|
if proto != "TCP":
|
|
self.instance.display('Measuring %s Throughput (packet size=%d)...',
|
|
proto, pkt_size)
|
|
loop_count = 1
|
|
else:
|
|
# For accuracy purpose, TCP throughput will be measured 3 times
|
|
self.instance.display('Measuring TCP Throughput (packet size=%d)...',
|
|
pkt_size)
|
|
loop_count = self.instance.config.tcp_tp_loop_count
|
|
for _ in range(loop_count):
|
|
res = self.run_client_dir(target_ip, mss,
|
|
reverse_dir=reverse_dir,
|
|
bandwidth_kbps=bandwidth,
|
|
protocol=proto,
|
|
length=pkt_size)
|
|
res_list.extend(res)
|
|
|
|
# For UDP reverse direction we need to start the server on self.instance
|
|
# and run the client on target_instance
|
|
if bidirectional:
|
|
for proto in proto_list:
|
|
if proto == 'TCP':
|
|
continue
|
|
# Start the server on the client (this tool instance)
|
|
self.instance.display('Start ' + proto + ' server for reverse dir')
|
|
if self.start_server():
|
|
# Start the client on the target instance
|
|
target_instance.display('Starting ' + proto + ' client for reverse dir')
|
|
|
|
for pkt_size in self.instance.config.udp_pkt_sizes:
|
|
self.instance.display('Measuring %s Throughput packet size=%d'
|
|
' (reverse direction)...',
|
|
proto, pkt_size)
|
|
res = target_instance.tp_tool.run_client_dir(self.instance.internal_ip,
|
|
mss,
|
|
bandwidth_kbps=bandwidth,
|
|
protocol=proto,
|
|
length=pkt_size)
|
|
res[0]['direction'] = 'reverse'
|
|
res_list.extend(res)
|
|
else:
|
|
self.instance.display('Failed to start ' + proto + ' server for reverse dir')
|
|
return res_list
|
|
|
|
def run_client_dir(self, target_ip,
|
|
mss,
|
|
reverse_dir=False,
|
|
bandwidth_kbps=0,
|
|
protocol='TCP',
|
|
length=0,
|
|
no_cpu_timed=0):
|
|
'''Run client in one direction
|
|
:param reverse_dir: True if reverse the direction (tcp only for now)
|
|
:param bandwidth_kbps: transmit rate limit in Kbps
|
|
:param protocol: (TCP|UDP|Multicast)
|
|
: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 1 dictionary with the 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 = ''
|
|
multicast = protocol == 'Multicast'
|
|
tcp = protocol == 'TCP'
|
|
udp = protocol == 'UDP'
|
|
if mss:
|
|
opts += "-M" + str(mss)
|
|
if reverse_dir:
|
|
opts += " -F -r"
|
|
if length:
|
|
opts += " -l" + str(length)
|
|
if self.instance.config.ipv6_mode:
|
|
opts += " -6 "
|
|
if multicast:
|
|
opts += " -m32 -o -j -g" + self.instance.config.multicast_addr
|
|
if not tcp:
|
|
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 miught as well return
|
|
# the results found through iteration
|
|
return [udp_res]
|
|
bandwidth_kbps = udp_res['throughput_kbps']
|
|
if bandwidth_kbps:
|
|
opts += " -R%sK" % (bandwidth_kbps)
|
|
|
|
if no_cpu_timed:
|
|
duration_sec = no_cpu_timed
|
|
else:
|
|
duration_sec = self.instance.get_cmd_duration()
|
|
# use data port 5001 and control port 5002
|
|
# must be enabled in the VM security group
|
|
cmd = "%s -a -T%d %s -p5001 -P5002 -fparse %s" % (self.dest_path,
|
|
duration_sec,
|
|
opts,
|
|
target_ip)
|
|
self.instance.buginf(cmd)
|
|
try:
|
|
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)
|
|
except sshutils.SSHError as exc:
|
|
# Timout or any SSH error
|
|
self.instance.display('SSH Error:' + str(exc))
|
|
return [self.parse_error(protocol, str(exc))]
|
|
|
|
try:
|
|
if udp or multicast:
|
|
# UDP output:
|
|
# megabytes=1.1924 real_seconds=10.01 rate_Mbps=0.9997 tx_cpu=99 rx_cpu=0
|
|
# drop=0 pkt=1221 data_loss=0.00000
|
|
re_udp = r'rate_Mbps=([\d\.]*) tx_cpu=\d* rx_cpu=\d* drop=(\-*\d*) pkt=(\d*)'
|
|
if multicast:
|
|
re_udp += r' data_loss=[\d\.]* msmaxjitter=([\d\.]*) msavgOWD=([\-\d\.]*)'
|
|
match = re.search(re_udp, cmd_out)
|
|
if match:
|
|
rate_mbps = float(match.group(1))
|
|
drop = float(match.group(2))
|
|
pkt = int(match.group(3))
|
|
jitter = None
|
|
|
|
if multicast:
|
|
jitter = float(match.group(4))
|
|
|
|
# Workaround for a bug of nuttcp that sometimes it will return a
|
|
# negative number for drop.
|
|
if drop < 0:
|
|
drop = 0
|
|
|
|
return [self.parse_results(protocol,
|
|
int(rate_mbps * 1024),
|
|
lossrate=round(drop * 100 / pkt, 2),
|
|
reverse_dir=reverse_dir,
|
|
msg_size=length,
|
|
cpu_load=cpu_load,
|
|
jitter=jitter)]
|
|
else:
|
|
# TCP output:
|
|
# megabytes=1083.4252 real_seconds=10.04 rate_Mbps=905.5953 tx_cpu=3 rx_cpu=19
|
|
# retrans=0 cwnd=3202 rtt_ms=0.55
|
|
re_tcp = \
|
|
r'rate_Mbps=([\d\.]*) tx_cpu=\d* rx_cpu=\d*' \
|
|
' retrans=(\d*) cwnd=\d* rtt_ms=([\d\.]*)'
|
|
match = re.search(re_tcp, cmd_out)
|
|
if match:
|
|
rate_mbps = float(match.group(1))
|
|
retrans = int(match.group(2))
|
|
rtt_ms = float(match.group(3))
|
|
return [self.parse_results(protocol,
|
|
int(rate_mbps * 1024),
|
|
retrans=retrans,
|
|
rtt_ms=rtt_ms,
|
|
reverse_dir=reverse_dir,
|
|
msg_size=length,
|
|
cpu_load=cpu_load)]
|
|
except Exception as exc:
|
|
LOG.exception(cmd_out)
|
|
self.instance.display('Parsing Error:' + str(exc))
|
|
return [self.parse_error(protocol, "cmd=%s: out=%s: exc=%s" % (cmd, cmd_out, str(exc)))]
|
|
|
|
return [self.parse_error(protocol, 'Could not parse: %s' % (cmd_out))]
|