vmtp/nuttcp_tool.py

195 lines
9.1 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.
#
import re
from perf_tool import PerfTool
import sshutils
class NuttcpTool(PerfTool):
def __init__(self, instance, perf_tool_path):
PerfTool.__init__(self, 'nuttcp-7.3.2', perf_tool_path, instance)
def get_server_launch_cmd(self):
'''Return the commands to launch the server side.'''
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 udp, 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 udp:
continue
if udp:
self.instance.display('Measuring UDP Throughput (packet size=%d)...',
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 xrange(loop_count):
res = self.run_client_dir(target_ip, mss,
reverse_dir=reverse_dir,
bandwidth_kbps=bandwidth,
udp=udp,
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:
# Start the server on the client (this tool instance)
self.instance.display('Start UDP server for reverse dir')
if self.start_server():
# Start the client on the target instance
target_instance.display('Starting UDP client for reverse dir')
for pkt_size in self.instance.config.udp_pkt_sizes:
self.instance.display('Measuring UDP Throughput packet size=%d'
' (reverse direction)...',
pkt_size)
res = target_instance.tp_tool.run_client_dir(self.instance.internal_ip,
mss,
bandwidth_kbps=bandwidth,
udp=True,
length=pkt_size)
res[0]['direction'] = 'reverse'
res_list.extend(res)
else:
self.instance.display('Failed to start UDP server for reverse dir')
return res_list
def run_client_dir(self, target_ip,
mss,
reverse_dir=False,
bandwidth_kbps=0,
udp=False,
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 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 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 = ''
if mss:
opts += "-M" + str(mss)
if reverse_dir:
opts += " -F -r"
if length:
opts += " -l" + str(length)
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_udp_bdw(length, target_ip)
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 -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(str(exc))]
if udp:
# UDP output (unicast and multicast):
# 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*)'
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))
# nuttcp uses multiple of 1000 for Kbps - not 1024
return [self.parse_results('UDP',
int(rate_mbps * 1000),
lossrate=round(drop * 100 / pkt, 2),
reverse_dir=reverse_dir,
msg_size=length,
cpu_load=cpu_load)]
else:
# TCP output:
# megabytes=1083.4252 real_seconds=10.04 rate_Mbps=905.5953 tx_cpu=3 rx_cpu=19
# retrans=0 rtt_ms=0.55
re_tcp = r'rate_Mbps=([\d\.]*) tx_cpu=\d* rx_cpu=\d* retrans=(\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('TCP',
int(rate_mbps * 1000),
retrans=retrans,
rtt_ms=rtt_ms,
reverse_dir=reverse_dir,
msg_size=length,
cpu_load=cpu_load)]
return [self.parse_error('Could not parse: %s' % (cmd_out))]