vmtp/vmtp/nuttcp_tool.py
Cédric Ollivier dc79be8a3b Update all modules to py3
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>
2019-05-22 21:13:05 +02:00

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))]