# -*- coding: utf-8 -*- """Module allowing to execute an iperf performance test between 2 PCs and to generate the corresponding curves in data and images files""" import time import re import os import numpy import pylab import signal import subprocess import ssh import plug import spc300 import power_strip import hpav_test import host_token config = { "host_a":{"non_plc_ip_address":"blovac", "user":"spidcom", "plc_ip_address":"10.3.0.1", "power_strip_key":(1, 1) }, "host_b":{"non_plc_ip_address":"fronton", "user":"spidcom", "plc_ip_address":"10.3.0.2", "power_strip_key":(1, 5) } } #Default frame size, including Ethernet, IP and TCP/UDP headers default_frame_size = 1514 #Fraction of the window width reserved for a nominal column column_width = 0.045 #Fraction of the window width reserved for the left and right margins of the #table table_margin = 0.015 #Fraction of the window width reserved for the Y axis label y_axis_label_width = 0.05 #Number of nominal columns reserved for the parameter values column columns_nb_for_parameter_values = 3.0 #Suffix used for the throughput measurement file name throughput_suffix_file_name = "Throughput" #Suffix used for the ce statistics measurement file name ce_suffix_file_name = "CE_stats" #String displayed for the maximum throughput test str_max_throughput = "Maximum throughput" def update_config(new_config): """Update the configuration""" global config config = new_config #This will assert if the configuration is not consistent has_mstar_plugs() def check_tokens(): for host in ["host_a", "host_b"]: user = config[host]["user"] non_plc_ip_address = config[host]["non_plc_ip_address"] host_token.check(user, non_plc_ip_address) def clear_tokens(): for host in ["host_a", "host_b"]: user = config[host]["user"] non_plc_ip_address = config[host]["non_plc_ip_address"] host_token.clear(user, non_plc_ip_address) def set_tokens(): for host in ["host_a", "host_b"]: user = config[host]["user"] non_plc_ip_address = config[host]["non_plc_ip_address"] host_token.set(user, non_plc_ip_address) def switch_on_plugs(): """Switch on the plugs and erase the traces files""" for host in ["host_a", "host_b"]: key = config[host]["power_strip_key"] plug.switch_on(key) clean_trace(host) def bench_data(): return dict(non_plc_ip_address_a = non_plc_ip_address("host_a"), non_plc_ip_address_b = non_plc_ip_address("host_b"), version_a = version("host_a"), version_b = version("host_b"), plug_model_a = plug_model("host_a"), plug_version_a = plug_version("host_a"), plug_model_b = plug_model("host_b"), plug_version_b = plug_version("host_b")) def plug_model(host): key = config[host]["power_strip_key"] return plug.get_model(key) def plug_version(host): key = config[host]["power_strip_key"] return plug.get_version(key) def version(host): """Returns the version of iperf installed on a host""" return check_output(host, "iperf -version 2>&1") def non_plc_ip_address(host): """Returns the non-PLC IP address of the host""" return config[host]["non_plc_ip_address"] def clean_traces(): clean_trace("host_a") clean_trace("host_b") def clean_trace(host): if has_mstar_plug(host): check_token(host) key = config[host]["power_strip_key"] spc300.clean_trace(key, 0) def check_traces(): """Check the files containing the traces on the two hosts""" for host in ["host_a", "host_b"]: if has_mstar_plug(host): key = config[host]["power_strip_key"] assert False == spc300.trace_is_present(key, 0), host def has_mstar_plugs(): has_mstar_plug("host_a") has_mstar_plug("host_b") def has_mstar_plug(host): key = config[host]["power_strip_key"] return plug.is_mstar(key) def kill_iperfs(): """Kill iperf on the two hosts""" for host in ["host_a", "host_b"]: tries_nb = 5 iperf_is_dead = False while (iperf_is_dead == False and tries_nb > 0): check_output(host, "killall iperf 2>/dev/null") time.sleep(1) iperf_is_dead = (check_output(host, "pgrep iperf") == "") tries_nb = tries_nb - 1 assert tries_nb > 0, ("Failed to kill iperf", host) def update_tone_maps(time_s, ping = False): """Update the tone maps on the plugs once the channel has changed. It can be done with a ping or with an iperf.""" if ping == True: plc_address_a = config["host_a"]["plc_ip_address"] plc_address_b = config["host_b"]["plc_ip_address"] period_s = 0.2 count = float(time_s)/period_s arguments = " -i" + str(period_s) + " -c" + str(count)+ "-s 1500" call("host_a", "ping " + plc_address_b + arguments) call("host_b", "ping " + plc_address_a + arguments) else: udp_parameters = ((default_frame_size, 103), (default_frame_size, 88)) make_measurement("UDP", udp_parameters, time_s, ["bi"], None, False) class TimeoutException(Exception): pass def set_timer(duration_s): def timeout_handler(signum, frame): raise TimeoutException() signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(duration_s) def make_measurement(protocol, udp_parameters, duration_s, measurements_specs_keys, file_prefix, get_ce_stats): """Execute the new measurements and dump the results in files. udp_parameters contains 2 couples of values: one for unidirectional tests, and another one for bidirectional tests. And each couple contains the frame size in bytes and the TX throughput in Mbits/sec.""" log = file_prefix is not None get_ce_stats = log and get_ce_stats for key, value in measurements_specs().items(): if key in measurements_specs_keys: t0 = time.time() #To take into account the kill, the dump of the log files, etc. max_duration_s = duration_s + 30 #We must set a timer since, for some unknown reasons, #when the channel is bad, iperf never returns set_timer(max_duration_s) try: kill_iperfs() results = [] for (client_host, server_host, direction) in value: ce_stats_before = retrieve_ce_stats(direction, get_ce_stats) results = results + [ (ce_stats_before, launch_client_server(protocol, udp_parameters, client_host, server_host, duration_s, direction))] for (ce_stats_before, (client, server, direction)) in results: ce_stats_after = retrieve_ce_stats(direction, get_ce_stats) ce_stats = [ce_stats_before, ce_stats_after] if log: log_datarates(client, server, file_prefix, direction) log_ce_stats(ce_stats, file_prefix, direction) check_traces() signal.alarm(0) except TimeoutException: print "\nWARNING: %f seconds elapsed while iperf was " \ "supposed to end in less than %d seconds\n" % \ (time.time() - t0, max_duration_s) def measurements_specs(): """Correspondance between the curves specified for a test and the iperf pairs that must be launched, as well as the direction of the test: * 'a_b_uni' means 'unidirectional from host A to host B' * 'bi' means 'bidirectional' * 'b_a_uni' means 'unidirectional from host B to host A' Typically, if we want to get the results in bidirectional mode ("bi"), we \ need to launch two iperf pairs simultaneously, one for each direction. The third item, called 'direction', is in particular used as suffix of the measurements files which are generated.""" return {"a_b_uni":[("host_a", "host_b", "a_b_uni")], "bi":[("host_a", "host_b", "a_b_bi"), ("host_b", "host_a", "b_a_bi")], "b_a_uni":[("host_b", "host_a", "b_a_uni")]} def get_all_directions(measurements_specs_keys): """Correspondance between the curves specified for a test and all the possible directions corresponding to these curves""" all_directions = [ get_directions(key) for key in measurements_specs_keys ] result = [] for directions_list in all_directions: result.extend(directions_list) return result def get_directions(key): """Correspondance between a curves specified for a test and all the possible directions corresponding to this curve""" values = measurements_specs()[key] return [ direction for (_, _, direction) in values ] def get_udp_parameters((udp_parameters_uni, udp_parameters_bi), direction): """Parameters used for the UDP protocol""" if direction == "a_b_uni" or direction == "b_a_uni": return udp_parameters_uni else: return udp_parameters_bi def launch_client_server(protocol, udp_parameters, client_host, server_host, duration_s, direction): """Launch a client/server pair""" server_non_plc_ip_address = config[server_host]["non_plc_ip_address"] server_plc_ip_address = config[server_host]["plc_ip_address"] client_non_plc_ip_address = config[client_host]["non_plc_ip_address"] print protocol, "from", client_non_plc_ip_address, \ "to", server_non_plc_ip_address server = Popen(server_host, server_command(protocol)) check_is_alive(server_host) new_udp_parameters = get_udp_parameters(udp_parameters, direction) command = client_command(protocol, new_udp_parameters, duration_s, server_plc_ip_address) client = Popen(client_host, command) return (client, server, direction) def check_is_alive(host): """Check that Iperf is alive on a host""" tries_nb = 5 iperf_is_alive = False while (iperf_is_alive == False and tries_nb > 0): time.sleep(1) iperf_is_alive = (check_output(host, "pgrep iperf") != "") tries_nb = tries_nb - 1 assert tries_nb > 0, ("Iperf is not alive", host) def client_command(protocol, udp_parameters, duration_s, plc_address): """Command to run an Iperf client""" if protocol == "UDP": (frame_size, max_throughput_Mbs) = udp_parameters info = (protocol, udp_parameters, duration_s, plc_address) assert frame_size > overhead(), info useful_frame_size = frame_size - overhead() # The argument passed to iperf designates the size of the payload, # excluding the Ethernet and UDP overhead payload_M = float(max_throughput_Mbs * useful_frame_size / frame_size) protocol_options = "-u -b" + str(round(payload_M, 1)) + "M" + \ " -l" + str(useful_frame_size) elif protocol == "TCP": protocol_options = "-w512k -P4" # -f m to make easier to report parsing because it formats result # in Mbits/sec return "iperf -c " + plc_address + " -f m " + \ "-t" + str(duration_s) + " " + protocol_options def server_command(protocol): """Command to run an Iperf server""" # -f m to make easier to report parsing because it formats result # in Mbits/sec if protocol == "UDP": protocol_options = "-u" if protocol == "TCP": protocol_options = "-w512k" return "iperf -s -f m -i1 " + protocol_options def log_datarates(client, server, file_prefix, direction): """Terminate the client and the server and dump their standard output in a file""" log_file_name = file_name([file_prefix, throughput_suffix_file_name, direction]) (stdoutdata, _) = client.communicate() log_file = open(log_file_name, "w") log_file.write(stdoutdata) server.terminate() (stdoutdata, _) = server.communicate() log_file.write(stdoutdata) log_file.close() def retrieve_ce_stats(direction, get_ce_stats): """Send mme spid_vs_get_ce_stats and get results""" if direction == "a_b_uni" or direction == "a_b_bi": (host_tx, host_rx) = ("host_a", "host_b") elif direction == "b_a_uni" or direction == "b_a_bi": (host_tx, host_rx) = ("host_b", "host_a") else: assert False, direction if has_mstar_plug(host_rx) and get_ce_stats: hpav_host = (config[host_rx]["user"], config[host_rx]["non_plc_ip_address"]) rx_key = config[host_rx]["power_strip_key"] tx_key = config[host_tx]["power_strip_key"] return hpav_test.send_spid_vs_get_ce_stats_request(hpav_host, rx_key, tx_key) def log_ce_stats(ce_stats, file_prefix, direction): if ce_stats != len(ce_stats) * [None]: log_file_name = file_name([file_prefix, ce_suffix_file_name, direction]) log_file = open(log_file_name, "w") for ce_stat in ce_stats: log_file.write(ce_stat) log_file.close() def extract_datarates_from_report(protocol, frame_size, file_prefix, file_suffix, duration_s, yname): """Returns the datarates contained in a measurement file. The first element is a list containing the individual datarates. The second element is an average of the individual datarates.""" try: report_name = file_name([file_prefix, throughput_suffix_file_name, file_suffix]) log_file = open(report_name, "r") file_content = log_file.read() log_file.close() raw_values = extract_datarates_from_report_aux(protocol, file_content) # The datarate returned by iperf corresponds only to the payload # while we want to display the overall throughput datarates = [ round (raw_value * frame_size / \ (frame_size - overhead()), 1) for raw_value in raw_values ] if datarates == []: return (duration_s * [0], 0) else: average = datarates[len(datarates) - 1] values = datarates[0:len(datarates) - 1] #iperf sometimes does not execute the exact requested time #especially when the channel is bad def print_warning(): print "\nWARNING: the duration_s (%d) does not match the " \ "number of values (%s) in the file (%s)\n" % \ (duration_s, str(values), report_name) if duration_s > len(values): print_warning() values = values + (duration_s - len(values)) * [0] elif duration_s < len(values): print_warning() values = values[0:duration_s] return (values, average) except IOError: return (duration_s * [0], 0) def extract_ce_stat_from_report(protocol, frame_size, file_prefix, file_suffix, duration_s, ce_stat_name): """Returns CE statistics contained in two measurement files. The first measurement file is a list containing the individual CE statistics before the measurement and the second one after the measurement and make the difference between these two measurement files.""" try: report_name = file_name([file_prefix, ce_suffix_file_name, file_suffix]) log_file = open(report_name, "r") file_content = log_file.read() log_file.close() ce_stats = extract_ce_stat_from_report_aux(file_content, ce_stat_name) return ce_stats except IOError: return (0) def extract_ce_stat_from_report_aux(file_content, ce_stat_name): try: lines = file_content.split("\n") pattern = ".* " + ce_stat_name + " *: (\d+)" ce_stats = [] for line in lines: result = re.search(pattern, line) if result is not None: ce_stats.append(int(result.groups()[0])) [ce_stat_before, ce_stat_after] = ce_stats return ce_stat_format(ce_stat_before, ce_stat_after, ce_stat_name) except: return -1 def ce_stat_format(ce_stat_before, ce_stat_after, ce_stat_name): if ce_stat_name == "BLE": # see Eq. 4.3, 4.4 and 4.5 in ยง 4.4.1.5.2.10 in HPAV standard mant = int(ce_stat_after) >> 3 exp = int(ce_stat_after) & 0x7 return (mant + 32) * pow (2, exp - 4) + pow (2, exp - 5) else: return ce_stat_after - ce_stat_before def ce_stat_description(ce_stat_name): if ce_stat_name == "BLE": return "BLE at the end of the measurement " \ "(a value of (-1) means that the statistic " \ "is invalid)" else: return "difference between the statistics " \ "retrieved before and after each measurement " \ "(a value of (-1) means that the statistics " \ "are invalid)" def unit(name): dictionary = {str_max_throughput:"Mbits/sec", "Restart BER":"no unit", "Restart PB error rate":"no unit", "Restart peer request":"no unit", "Restart tone map update":"no unit", "Restart ber margin update":"no unit", "BLE": "Mbits/sec"} return dictionary[name] def extract_datarates_from_report_aux(protocol, string): """Returns the datarates contained in a string""" lines = string.split("\n") datarates = [] if protocol == "UDP": drop_line = True for line in lines: if "port 5001 connected with" in line: drop_line = False if drop_line == False: datarates = update_datarates(datarates, line) elif protocol == "TCP": for line in lines: if "[SUM]" in line: datarates = update_datarates(datarates, line) if datarates != []: datarates = datarates[1:len(datarates)] + [datarates[0]] return datarates def update_datarates(datarates, line): criteria = r"MBytes *(.*) Mbits/sec.*" datarate = re.search(criteria, line) if datarate is not None: datarates = datarates + [float(datarate.groups()[0])] return datarates def dump_measurement(directions, test_type, parameter_values, file_name, values): """Generate a data file containing the wanted values organised in lines""" for key in values.keys(): assert len(parameter_values) == len(values[key]), \ (len(parameter_values), len(values[key]), parameter_values, values[key], file_name) data_file = open(file_name, "w") keys = ["a_b_uni", "b_a_uni", "a_b_bi", "b_a_bi"] line = ",".join([test_type] + keys) + "\n" data_file.write(line) for index in range(len(parameter_values)): line = values_line(directions, parameter_values, values, index, keys) data_file.write(line) data_file.close() def values_line(directions, parameter_values, values, index, keys): acc = [str(parameter_values[index])] for key in keys: try: value = values[key][index] except KeyError: value = 0 acc = acc + [str(value)] return ",".join(acc) + "\n" def draw(title, xlabel, ylabel, directions, test_type, parameter_values, show, file_name, datarates): """Generate an image file containing the curves and the data organised in tables. The 'parameter_values' are displayed on the X-axis. The 'datarates', corresponding to the different 'directions', are displayed on the the Y-axis.""" for key in datarates.keys(): assert len(parameter_values) == len(datarates[key]), \ (len(parameter_values), len(datarates[key]), file_name) curves_display = { "a_b_uni":("blue", "Host A -> Host B (Uni)"), "b_a_uni":("red", "Host B -> Host A (Uni)"), "a_b_bi":("lightblue", "Host A -> Host B (Bi)"), "b_a_bi":("pink", "Host B -> Host A (Bi)"), "all_bi":('g', "Host A/B -> Host B/A (Bi)")} columns_nb = columns_nb_for_parameter_values + len(curves_display) \ - len(["all_bi"]) x_fig_left = columns_nb * column_width + 2.0 * table_margin + \ y_axis_label_width x_fig_right = 0.97 x_fig_size = 10. y_fig_size = 8.1 fig_subplot_size = 6 fig_subplot_bottom = (y_fig_size - fig_subplot_size)/y_fig_size pylab.rcParams.update({'legend.fontsize':12, 'figure.subplot.left':x_fig_left, 'figure.subplot.right':x_fig_right, 'figure.subplot.bottom':fig_subplot_bottom, 'figure.figsize':(x_fig_size, y_fig_size) }) first_values = map(first_value, parameter_values) # Draw the curves for direction in directions: pylab.plot(first_values, datarates[direction], color = curves_display[direction][0], label = curves_display[direction][1], fillstyle = 'full') if "a_b_bi" in directions: pylab.plot(first_values, numpy.add(datarates["a_b_bi"], datarates["b_a_bi"]), color = curves_display["all_bi"][0], label = curves_display["all_bi"][1], linestyle = "--" ) y_legend_offset = -0.15 if ("a_b_uni" in directions): y_legend_offset = y_legend_offset - 0.05 if ("b_a_uni" in directions): y_legend_offset = y_legend_offset - 0.05 if ("a_b_bi" in directions): y_legend_offset = y_legend_offset - 0.15 pylab.legend(loc = 'lower left', bbox_to_anchor=(0, y_legend_offset)) pylab.xlabel(xlabel, {'fontsize':16}) pylab.ylabel(ylabel, {'fontsize':16}) pylab.title(title) # Write column headers write_column(columns_nb_for_parameter_values + 0.5, 1.0, ["unidir"], weight='bold') write_column(columns_nb_for_parameter_values + 2.5, 1.0, ["bidir"], weight='bold') # Write column data write_column(0.0, 2.0, [test_type, ""] + parameter_values) if ("a_b_uni" in directions): write_column(columns_nb_for_parameter_values, 2.0, ["a->b", ""] + datarates["a_b_uni"]) if ("b_a_uni" in directions): write_column(columns_nb_for_parameter_values + 1.0, 2.0, ["b->a", ""] + datarates["b_a_uni"]) if ("a_b_bi" in directions): write_column(columns_nb_for_parameter_values + 2.0, 2.0, ["a->b", ""] + datarates["a_b_bi"]) if ("b_a_bi" in directions): write_column(columns_nb_for_parameter_values + 3.0, 2.0, ["b->a", ""] + datarates["b_a_bi"]) pylab.grid(True) xticklabels = pylab.getp(pylab.gca(), 'xticklabels') yticklabels = pylab.getp(pylab.gca(), 'yticklabels') pylab.setp(yticklabels, 'color', 'r', fontsize = 'medium') pylab.setp(xticklabels, 'color', 'r', fontsize = 'medium') pylab.gcf().savefig(file_name) if show == True: pylab.show() pylab.clf() def write_column(column_nb, line_nb, column_data, weight = 'normal', size = 'small'): """Insert in an image a column of data""" max_line_nb = 50 if (line_nb + len(column_data)) > max_line_nb: print "WARNING: the number of lines to be displayed (" + \ str(int(line_nb + len(column_data))) + ") exceeds the maximum (" + \ str(max_line_nb) + ")" line_height = 1.0 / 50.0 for data in column_data: pylab.figtext(table_margin + column_nb * column_width, 1.0 - line_nb * line_height, data, ha = 'left', va = 'top', color = 'black', weight = weight, size = size) line_nb = line_nb + 1 def file_name(items): assert items != [] return "_".join(items) + ".txt" def check_output(host, command): """Get the standard output of a command executed on a host""" non_plc_ip_address = config[host]["non_plc_ip_address"] user = config[host]["user"] return ssh.check_output(user, non_plc_ip_address, command) def Popen(host, command): """Returns a process executing a command on a host""" non_plc_ip_address = config[host]["non_plc_ip_address"] user = config[host]["user"] return ssh.Popen(user, non_plc_ip_address, command) def call(host, command): """Execute a command on a host and returns""" non_plc_ip_address = config[host]["non_plc_ip_address"] user = config[host]["user"] return ssh.call(user, non_plc_ip_address, command) def overhead(): '''Size of the UDP and Ethernet headers in an Ethernet frame''' ethernet_header_length = 18.0 ip_header_length = 16.0 udp_or_tcp_header_length = 8.0 return ethernet_header_length + ip_header_length + udp_or_tcp_header_length def first_value(X): """First value of a tuple""" if hasattr(X, '__iter__') == True: return X[0] else: return X if __name__ == "__main__": assert [46.8, 45.8, 45.7, 45.9, 45.7, 46.0] == extract_datarates_from_report_aux( "UDP", "------------------------------------------------------------\n" "Client connecting to 10.3.0.2, UDP port 5001\n" "Sending 1472 byte datagrams\n" "UDP buffer size: 0.11 MByte (default)\n" "------------------------------------------------------------\n" "[ 3] local 10.3.0.1 port 34241 connected with 10.3.0.2 port 5001\n" "[ ID] Interval Transfer Bandwidth\n" "[ 3] 0.0- 5.0 sec 57.0 MBytes 95.6 Mbits/sec\n" "[ 3] Sent 40606 datagrams\n" "[ 3] Server Report:\n" "[ 3] 0.0- 5.2 sec 28.4 MBytes 46.0 Mbits/sec 0.164 ms 20336/40605 (50%)\n" "[ 3] 0.0- 5.2 sec 1 datagrams received out-of-order\n" "------------------------------------------------------------\n" "Server listening on UDP port 5001\n" "Receiving 1470 byte datagrams\n" "UDP buffer size: 0.11 MByte (default)\n" "------------------------------------------------------------\n" "[ 3] local 10.3.0.2 port 5001 connected with 10.3.0.1 port 34241\n" "[ ID] Interval Transfer Bandwidth Jitter Lost/Total Datagrams\n" "[ 3] 0.0- 1.0 sec 5.58 MBytes 46.8 Mbits/sec 0.318 ms 2657/ 6635 (40%)\n" "[ 3] 1.0- 2.0 sec 5.46 MBytes 45.8 Mbits/sec 0.186 ms 4217/ 8114 (52%)\n" "[ 3] 2.0- 3.0 sec 5.45 MBytes 45.7 Mbits/sec 0.065 ms 4191/ 8077 (52%)\n" "[ 3] 3.0- 4.0 sec 5.47 MBytes 45.9 Mbits/sec 0.212 ms 4260/ 8162 (52%)\n" "[ 3] 4.0- 5.0 sec 5.45 MBytes 45.7 Mbits/sec 0.093 ms 4261/ 8149 (52%)\n" "[ 3] 0.0- 5.2 sec 28.4 MBytes 46.0 Mbits/sec 0.164 ms 20336/40605 (50%)\n" "[ 3] 0.0- 5.2 sec 1 datagrams received out-of-order") assert [] == extract_datarates_from_report_aux( "UDP", "[ 3] 0.0-10.0 sec 114 MBytes 95.5 Mbits/sec \n" "0.009 ms 0/81405 (0%)") assert [34.9, 35.8, 34.8, 35.9, 36.8, 36.2] == extract_datarates_from_report_aux( "TCP", "------------------------------------------------------------\n" "Client connecting to 10.3.0.2, TCP port 5001\n" "TCP window size: 0.25 MByte (WARNING: requested 0.50 MByte)\n" "------------------------------------------------------------\n" "[ 4] local 10.3.0.1 port 43525 connected with 10.3.0.2 port 5001\n" "[ 5] local 10.3.0.1 port 43526 connected with 10.3.0.2 port 5001\n" "[ 3] local 10.3.0.1 port 43524 connected with 10.3.0.2 port 5001\n" "[ 6] local 10.3.0.1 port 43527 connected with 10.3.0.2 port 5001\n" "[ ID] Interval Transfer Bandwidth\n" "[ 4] 0.0- 5.1 sec 6.38 MBytes 10.6 Mbits/sec\n" "[ 6] 0.0- 5.1 sec 5.75 MBytes 9.53 Mbits/sec\n" "[ 5] 0.0- 5.1 sec 5.12 MBytes 8.44 Mbits/sec\n" "[ 3] 0.0- 5.2 sec 5.12 MBytes 8.28 Mbits/sec\n" "[SUM] 0.0- 5.2 sec 22.4 MBytes 36.2 Mbits/sec\n" "------------------------------------------------------------\n" "Server listening on TCP port 5001\n" "TCP window size: 0.25 MByte (WARNING: requested 0.50 MByte)\n" "------------------------------------------------------------\n" "[ 6] local 10.3.0.2 port 5001 connected with 10.3.0.1 port 43526\n" "[ 5] local 10.3.0.2 port 5001 connected with 10.3.0.1 port 43525\n" "[ 4] local 10.3.0.2 port 5001 connected with 10.3.0.1 port 43524\n" "[ 7] local 10.3.0.2 port 5001 connected with 10.3.0.1 port 43527\n" "[ ID] Interval Transfer Bandwidth\n" "[ 7] 0.0- 1.0 sec 1.10 MBytes 9.26 Mbits/sec\n" "[ 5] 0.0- 1.0 sec 1.24 MBytes 10.4 Mbits/sec\n" "[ 6] 0.0- 1.0 sec 0.92 MBytes 7.69 Mbits/sec\n" "[ 4] 0.0- 1.0 sec 0.89 MBytes 7.49 Mbits/sec\n" "[SUM] 0.0- 1.0 sec 4.16 MBytes 34.9 Mbits/sec\n" "[ 6] 1.0- 2.0 sec 0.94 MBytes 7.88 Mbits/sec\n" "[ 4] 1.0- 2.0 sec 0.95 MBytes 7.95 Mbits/sec\n" "[ 5] 1.0- 2.0 sec 1.27 MBytes 10.6 Mbits/sec\n" "[ 7] 1.0- 2.0 sec 1.12 MBytes 9.36 Mbits/sec\n" "[SUM] 1.0- 2.0 sec 4.27 MBytes 35.8 Mbits/sec\n" "[ 7] 2.0- 3.0 sec 1.07 MBytes 8.94 Mbits/sec\n" "[ 4] 2.0- 3.0 sec 0.94 MBytes 7.89 Mbits/sec\n" "[ 6] 2.0- 3.0 sec 0.97 MBytes 8.10 Mbits/sec\n" "[ 5] 2.0- 3.0 sec 1.17 MBytes 9.83 Mbits/sec\n" "[SUM] 2.0- 3.0 sec 4.14 MBytes 34.8 Mbits/sec\n" "[ 5] 3.0- 4.0 sec 1.21 MBytes 10.2 Mbits/sec\n" "[ 7] 3.0- 4.0 sec 1.12 MBytes 9.42 Mbits/sec\n" "[ 4] 3.0- 4.0 sec 0.97 MBytes 8.17 Mbits/sec\n" "[ 6] 3.0- 4.0 sec 0.97 MBytes 8.18 Mbits/sec\n" "[SUM] 3.0- 4.0 sec 4.28 MBytes 35.9 Mbits/sec\n" "[ 6] 4.0- 5.0 sec 1.01 MBytes 8.44 Mbits/sec\n" "[ 5] 4.0- 5.0 sec 1.24 MBytes 10.4 Mbits/sec\n" "[ 7] 4.0- 5.0 sec 1.10 MBytes 9.27 Mbits/sec\n" "[ 4] 4.0- 5.0 sec 1.04 MBytes 8.76 Mbits/sec\n" "[SUM] 4.0- 5.0 sec 4.39 MBytes 36.8 Mbits/sec\n" "[ 7] 0.0- 5.2 sec 5.75 MBytes 9.34 Mbits/sec\n" "[ 5] 0.0- 5.2 sec 6.38 MBytes 10.3 Mbits/sec") assert [] == extract_datarates_from_report_aux( "TCP", "[ 3] 0.0-10.0 sec 114 MBytes 95.5 Mbits/sec \n" "0.009 ms 0/81405 (0%)") assert ["a_b_uni"] == get_all_directions(["a_b_uni"]) assert ["a_b_uni", "b_a_uni"] == get_all_directions(["a_b_uni", "b_a_uni"]) assert ["a_b_uni", "b_a_uni", "a_b_bi", "b_a_bi"] == \ get_all_directions(["a_b_uni", "b_a_uni", "bi"]) assert first_value(1) == 1 assert first_value((2, 3)) == 2 assert "iperf -c 10.3.0.1 -f m -t5 -u -b100.1M -l1472.0"== \ client_command("UDP", (1514, 103), 5, "10.3.0.1") assert "iperf -c 10.3.0.1 -f m -t5 -w512k -P4" == \ client_command("TCP", (1514, 103), 5, "10.3.0.1") assert "iperf -s -f m -i1 -u" == server_command("UDP") assert "iperf -s -f m -i1 -w512k" == server_command("TCP") assert -1 == extract_ce_stat_from_report_aux( "[0] baz : 0\n" "[0] bar : 1\n" "[0] bar : 5\n", "foo") assert -1 == extract_ce_stat_from_report_aux( "[0] baz : 0\n" "[0] bar : 1\n" "[0] bar : 5\n" "[0] foo : 5\n", "foo") assert 4 == extract_ce_stat_from_report_aux( "[0] foo : 1\n" "[0] foo : 5\n", "foo") assert -4 == extract_ce_stat_from_report_aux( "[0] foo : 5\n" "[0] foo : 1\n", "foo") assert -1 == extract_ce_stat_from_report_aux( "[0] foo : 0\n" "[0] foo : 1\n" "[0] foo : 5\n", "foo") assert -1 == extract_ce_stat_from_report_aux( "[0] foo : 0\n" "[0] foo : 1\n" "[0] foo : 5\n" "[0] bar : 4\n" "[0] foo : 2\n", "foo") assert 3 == extract_ce_stat_from_report_aux( "[0] foo : 2\n" "[0] bar : 4\n" "[0] foo : 5\n", "foo") assert "a.txt" == file_name(["a"]) assert "a_b.txt" == file_name(["a", "b"]) assert "a_b_c.txt" == file_name(["a", "b", "c"])