summaryrefslogtreecommitdiff
path: root/validation/test
diff options
context:
space:
mode:
authorJean-Philippe NOEL2011-12-15 14:27:21 +0100
committerJean-Philippe NOEL2012-04-03 13:22:48 +0200
commitfc182a9d94ed890d912e9d2025d6464964de35da (patch)
tree245fddcb4ca51af0619570ebddca32bb2b42d8ed /validation/test
parent69e0d58d89af7374ea4b31163749148531082285 (diff)
validation: automated generation of a performance test report, closes #2706
The covered tests are * TCP/UDP * attenuation/white noise/static jammer/dynamic jammer/frame size
Diffstat (limited to 'validation/test')
-rwxr-xr-xvalidation/test/test_av_avln/test.py (renamed from validation/test/test.py)0
-rw-r--r--validation/test/test_av_avln/test_bench.dia (renamed from validation/test/test_bench.dia)bin5787 -> 5787 bytes
-rw-r--r--validation/test/test_av_avln/test_io_group.py (renamed from validation/test/test_io_group.py)5
-rw-r--r--validation/test/test_av_avln/test_power_strip.py (renamed from validation/test/test_power_strip.py)5
-rw-r--r--validation/test/test_av_avln/test_spc300.py (renamed from validation/test/test_spc300.py)19
-rw-r--r--validation/test/test_iperf/test_bench.diabin0 -> 5242 bytes
-rw-r--r--validation/test/test_iperf/test_throughput.py698
-rw-r--r--validation/test/test_iperf/test_throughput.rst144
8 files changed, 862 insertions, 9 deletions
diff --git a/validation/test/test.py b/validation/test/test_av_avln/test.py
index 4d121fc345..4d121fc345 100755
--- a/validation/test/test.py
+++ b/validation/test/test_av_avln/test.py
diff --git a/validation/test/test_bench.dia b/validation/test/test_av_avln/test_bench.dia
index d6dd6b94e6..d6dd6b94e6 100644
--- a/validation/test/test_bench.dia
+++ b/validation/test/test_av_avln/test_bench.dia
Binary files differ
diff --git a/validation/test/test_io_group.py b/validation/test/test_av_avln/test_io_group.py
index ce376fe57f..f5d76d889b 100644
--- a/validation/test/test_io_group.py
+++ b/validation/test/test_av_avln/test_io_group.py
@@ -1,5 +1,8 @@
# -*- coding: utf-8 -*-
+"""Module allowing to validate that the groups of io's are correctly configured
+and can be controlled"""
+
import validlib.io_group as io_group
def test():
@@ -17,4 +20,4 @@ def test_io_group_control():
assert io_group.status(key) == "on"
print "OK: The io", key, "can be controlled"
print "OK: The group of io's", index, "can be controlled"
-
+
diff --git a/validation/test/test_power_strip.py b/validation/test/test_av_avln/test_power_strip.py
index 73c95ab892..ef672d7931 100644
--- a/validation/test/test_power_strip.py
+++ b/validation/test/test_av_avln/test_power_strip.py
@@ -1,5 +1,8 @@
# -*- coding: utf-8 -*-
+"""Module allowing to validate that the power strips are correctly configured
+and can be controlled"""
+
import validlib.power_strip as power_strip
def test():
@@ -17,4 +20,4 @@ def test_power_strip_control():
assert power_strip.status(key) == "on"
print "OK: The power plug", key, "can be controlled"
print "OK: The power strip", index, "can be controlled"
-
+
diff --git a/validation/test/test_spc300.py b/validation/test/test_av_avln/test_spc300.py
index 18f1e62f30..d5c1776b42 100644
--- a/validation/test/test_spc300.py
+++ b/validation/test/test_av_avln/test_spc300.py
@@ -1,5 +1,9 @@
# -*- coding: utf-8 -*-
+"""Module allowing to validate that a set a SPiDCOM AV plugs behaves well
+from an AVLN point of view, i.e. they can shape an AVLN, afford the death
+of the CCO, split, merge, split again, coexist, etc."""
+
import random
import validlib.power_strip as power_strip
import validlib.link as link
@@ -10,7 +14,8 @@ def test():
print
print "Executing", __file__, "..."
-# The OK or NOK in the comments below refer to the results obtained with av-1.2.1
+# The OK or NOK in the comments below refer to the results obtained
+# with av-1.2.1
# test_shape()
# test_kill()
@@ -20,23 +25,23 @@ def test():
# test_coexist()
def test_shape():
- test_shape_an_avln_cco_3_sta() # OK, except that everybody is UCCO
+ test_shape_an_avln_cco_3_sta() # OK
def test_kill():
- test_kill_cco_in_cco_2_sta() # OK, but the roles are not checked
+ test_kill_cco_in_cco_2_sta() # ping OK, but the roles are incorrect
def test_split():
- test_split_cco_1_sta_in_2_avlns_ucco() # OK, but the roles are not checked
+ test_split_cco_1_sta_in_2_avlns_ucco() # OK
test_split_cco_3_sta_in_2_avlns_cco_1_sta() # (#2869) NOK, it happens that avln_2 cannot be shaped
def test_merge():
- test_merge_ucco_with_ucco() # OK, but the roles are not checked
- test_merge_cco_1_sta_with_ucco_scenario_1() # OK, but the roles are not checked
+ test_merge_ucco_with_ucco() # ping OK, but the roles are incorrect
+ test_merge_cco_1_sta_with_ucco_scenario_1() # ping OK, but the roles are incorrect
test_merge_cco_1_sta_with_ucco_scenario_2() # (#2535) NOK, it happens that avln_1 cannot be shaped
test_merge_cco_1_sta_with_cco_1_sta() # (#2243) NOK, the final AVLN can never be shaped
def test_split_merge():
- test_replace_cco_in_cco_1_sta() # OK, but the roles are not checked
+ test_replace_cco_in_cco_1_sta() # ping OK, but the roles are incorrect
test_split_merge_split_cco_3_sta_in_2_avlns_cco_1_sta() # (#2543) To be done, when the split will work
def test_coexist():
diff --git a/validation/test/test_iperf/test_bench.dia b/validation/test/test_iperf/test_bench.dia
new file mode 100644
index 0000000000..f54dd3db5d
--- /dev/null
+++ b/validation/test/test_iperf/test_bench.dia
Binary files differ
diff --git a/validation/test/test_iperf/test_throughput.py b/validation/test/test_iperf/test_throughput.py
new file mode 100644
index 0000000000..a4561d0e6a
--- /dev/null
+++ b/validation/test/test_iperf/test_throughput.py
@@ -0,0 +1,698 @@
+# -*- coding: utf-8 -*-
+
+"""Module allowing to execute performance tests (attenuation, SNR, SJR,
+frame size and dynamic_jammer) with Iperf between 2 plugs and to generate a
+PDF report with the results"""
+
+import time
+import subprocess
+import os
+import numpy
+import math
+
+import validlib.attenuator as attenuator
+import validlib.waveform_generator as waveform_generator
+import validlib.iperf as iperf
+import validlib.spc300 as spc300
+import validlib.power_strip as power_strip
+
+config = {
+ #True|False
+ #If True, each image is displayed on the screen when it is generated
+ #If False, the PDF report is directly generated without showing the image
+ "show":False,
+ #True|False
+ #If True, the plugs are restarted before each serie of measurements
+ #If False, the plugs are not restarted before a serie of measurements
+ "restart_plugs":True,
+ #True|False
+ #If True, the measurements are reset, made and then the report is generated
+ #If False, the report is generated from the previous measurements
+ "make_measurements":True,
+ "attenuation":{
+ "values":numpy.arange(38, 98, 5).tolist(),
+ "duration_s":5,
+ #["a_b_uni"|"b_a_uni"|"bi"]
+ #see iperf.measurement_specs() for the meaning of the values
+ "curves":["a_b_uni", "b_a_uni", "bi"]},
+ "SNR": {
+ "values":numpy.arange(40, -10, -5).tolist(),
+ "duration_s":5,
+ #["a_b_uni"|"b_a_uni"|"bi"]
+ #see iperf.measurement_specs() for the meaning of the values
+ "curves":["a_b_uni", "b_a_uni", "bi"]},
+ "SJR":{
+ "values":numpy.arange(10, -35, -5).tolist(),
+ "duration_s":5,
+ #["a_b_uni"]
+ #see iperf.measurement_specs() for the meaning of the values
+ "curves":["a_b_uni"]},
+ "frame_size":{
+ #[(frame_size, max_throughput_M)]
+ #The frame size and the max throughput includes the Ethernet,
+ #IP and TCP/UDP headers
+ "values":[
+ (68, 11),
+ (128, 20),
+ (256, 50),
+ (512, 90),
+ (768, 120),
+ (1024, 120),
+ (1280, 120),
+ (1514, 120),
+ (1515, 120),
+ (1550, 120),
+ (1600, 120),
+ (1700, 120),
+ (2000, 120)
+ ],
+ "duration_s":5,
+ #["a_b_uni"|"b_a_uni"|"bi"]
+ #see iperf.measurement_specs() for the meaning of the values
+ "curves":["a_b_uni", "b_a_uni", "bi"]
+ },
+ "dynamic_jammer":{
+ #Must be ["time"]
+ "values":["time"],
+ #Must be seconds
+ "duration_s":120,
+ #["a_b_uni"|"b_a_uni"|"bi"]
+ #see iperf.measurement_specs() for the meaning of the values
+ "curves":["a_b_uni"]},
+ #[("UDP"|"TCP",
+ # "attenuation"|"SNR"|"frame_size"|"SJR"|"dynamic_jammer",
+ # (jammer frequency(MHz), AM form ("SINusoid"|"SQUare"),
+ # AM frequency (kHz), AM depth(%)) for "SJR",
+ # (SJR (dBm), sweep start frequency (MHz), sweep stop frequency (MHz),
+ # sweep time (seconds)) for "dynamic_jammer"
+ # and None otherwise)]
+ "fixtures":[
+ ("UDP", "attenuation", None),
+ ("TCP", "attenuation", None),
+ ("UDP", "SNR", None),
+ ("TCP", "SNR", None),
+ ("UDP", "SJR", (13.5, None)),
+ ("UDP", "SJR", (18.5, None)),
+ ("UDP", "SJR", (23.5, None)),
+ ("UDP", "SJR", (4, ("SINusoid", 1, 80))),
+ ("UDP", "SJR", (15, ("SINusoid", 1, 80))),
+ ("UDP", "SJR", (26, ("SINusoid", 1, 80))),
+ ("UDP", "SJR", (27.345, ("SINusoid", 1, 80))),
+ ("UDP", "frame_size", None),
+ ("UDP", "dynamic_jammer", (10, 2, 30, 60)),
+ ("UDP", "dynamic_jammer", (0, 2, 30, 60)),
+ ("UDP", "dynamic_jammer", (-10, 2, 30, 60)),
+ ("UDP", "dynamic_jammer", (-20, 2, 30, 60)),
+ ("UDP", "dynamic_jammer", (-30, 2, 30, 60))
+ ],
+ #Duration after the channel modification during which we generate traffic
+ #to let the CE converge
+ "ce_sync_time_s":10,
+ #Directory where the report is generated
+ "reports_directory":"reports",
+ #True|False
+ #If True, the AMN attenuation is taken into account when determining the
+ #noise or the attenuation to be applied on the devices
+ #True should be used in AV
+ #False should be used in EoC
+ "amn":True,
+ #Bandwidth of the useful signal
+ #Typical values are (2, 28) in AV and (1, 38) in EoC
+ "signal_band_mhz":(2, 28),
+ #Power Spectral Density
+ #Typical values are -50 in AV and -63 in EoC
+ "ppsd_dbm_per_hz":-50,
+ #We want to reach a very low SJR, but the output power of the
+ #waveform generator is limited. Therefore, we set the signal to
+ #a low value, actually the lowest for which the throughput
+ #would not be impacted without noise. This value should be deduced from
+ #the results obtained in the 'attenuation' test. If the curve starts going
+ #down at 55 dB and if the minimal attenuation of the bench is 38,
+ #sjr_attenuator_attenuation_db must be equal to (55 - 38) dB = 17 dB
+ "sjr_attenuator_attenuation_db": 17,
+ #Configuration passed to the 'power_strip' module
+ "power_strip":{1: ("192.168.0.10", "admin", "anel")},
+ #Configuration passed to the 'spc300' module
+ #If the devices are not MSTAR plugs, it has no impact
+ "spc300":{
+ "eth_netmask": "10.3.6.",
+ "plc_netmask": "unused",
+ "plugs":
+ {
+ (1, 1): ("143", "unused", "unused"),
+ (1, 5): ("141", "unused", "unused")
+ }
+ }
+ }
+
+#Peak to Average Ratio of the power, in OFDM
+par_db = 10
+#Signal attenuation on each side of the bench
+signal_attenuation_db = 10
+#Waveform attenuation at the output of the generator
+waveform_attenuation_db = 10
+#Attenuation of an AMN
+amn_attenuation_db = 6
+#Attenuation of the T
+attenuation_t_db = 6
+#Units of the "values" for each type of test
+units = {"attenuation":"dB",
+ "SNR":"dB",
+ "SJR":"dB",
+ "frame_size":"bytes",
+ "dynamic_jammer":"seconds"}
+
+def report():
+ """Generates the PDF report as specified in the configuration.
+ It requires to set up first the test bench as detailed in the test bench
+ description."""
+
+ # Configure the 'power_strip' module
+ power_strip.update_config(config["power_strip"])
+
+ # Configure the 'spc300' module
+ spc300.update_config(config["spc300"])
+
+ # Build the picture of the test bench
+ fig_name = build_test_bench_picture()
+
+ #Check the configuration
+ check_config()
+
+ # Make all the measurements
+ if config["make_measurements"] == True:
+ t0 = time.time()
+ estimated_min = measurements_estimated_time_min()
+ print "\nEstimated measurements time in minutes:", estimated_min, "\n"
+ for (test_protocol, test_type, test_params) in config["fixtures"]:
+ make_measurements(test_protocol, test_type, test_params)
+ actual_min = round((time.time() - t0) / 60.0, 2)
+ print "Actual/estimated measurements times in minutes:", \
+ actual_min, "/", estimated_min
+
+ # Extract the datarates from the measurements files and generate the
+ # images
+ for (test_protocol, test_type, test_params) in config["fixtures"]:
+ datarates = extract_datarates(test_protocol, test_type, test_params)
+ dump_and_draw(test_protocol, test_type, test_params, datarates)
+
+ # Build the rst file
+ build_rst_file(fig_name)
+
+ # Build the pdf file
+ subprocess.check_call(["rst2pdf", file_prefix() + ".rst",
+ "-o", file_prefix() + ".pdf"])
+
+def build_test_bench_picture():
+ fig_name = "test_bench"
+ input_file = os.path.join(os.path.dirname(__file__), fig_name + ".dia")
+ reports_directory = get_reports_directory()
+ output_file = os.path.join(reports_directory, fig_name + ".eps")
+ subprocess.check_call(["dia", input_file, "-t", "png", "-e", output_file])
+ return fig_name
+
+def check_config():
+
+ data = waveform_data()
+
+ for (test_protocol, test_type, test_params) in config["fixtures"]:
+ assert test_protocol in ["UDP", "TCP"]
+ assert test_type in ["attenuation", "SNR", "frame_size", "SJR", \
+ "dynamic_jammer"]
+ for curve in config[test_type]["curves"]:
+ assert curve in iperf.measurements_specs().keys()
+ assert type(config[test_type]["duration_s"]) == int
+ if test_type == "SNR":
+ #We check here that the values of SNR are consistent with the
+ #output power range of the waveform generator
+ for test_value in config[test_type]["values"]:
+ power_dbm = data["snr_offset"] - test_value
+ (min_power_dbm, max_power_dbm) = \
+ waveform_generator.power_range_dbm()
+ assert power_dbm <= max_power_dbm, \
+ ("SNR too low", test_value)
+ assert power_dbm >= min_power_dbm, \
+ ("SNR too high", test_value)
+ assert test_params == None
+ elif test_type == "SJR":
+ #Because of sjr_attenuator_attenuation_db, the bench is not
+ #symetrical on this test
+ assert curve == "a_b_uni", curve
+ #We check here that the values of SJR are consistent with the
+ #output power range of the waveform generator
+ for test_value in config[test_type]["values"]:
+ power_dbm = data["sjr_offset"] - test_value
+ (min_power_dbm, max_power_dbm) = \
+ waveform_generator.power_range_dbm()
+ assert power_dbm <= max_power_dbm, \
+ ("SJR too low", test_value)
+ assert power_dbm >= min_power_dbm, \
+ ("SJR too high", test_value)
+ elif test_type == "dynamic_jammer":
+ assert config[test_type]["values"] == ["time"]
+ else:
+ assert test_params == None, test_params
+
+def build_rst_file(fig_name):
+ data = waveform_data()
+ (non_plc_ip_address_a, non_plc_ip_address_b) = iperf.non_plc_ip_addresses()
+ (version_a, version_b) = iperf.versions()
+ rst_template = open(os.path.splitext(__file__)[0] + ".rst", "r").read()
+ (device_a, device_b) = iperf.devices()
+ (device_model_a, device_version_a, _) = device_a
+ (device_model_b, device_version_b, _) = device_b
+ rst_template_dict = dict(device_model_a = device_model_a,
+ device_version_a = device_version_a,
+ device_model_b = device_model_b,
+ device_version_b = device_version_b,
+ fig_name = fig_name,
+ version_a = version_a,
+ version_b = version_b,
+ non_plc_ip_address_a = non_plc_ip_address_a,
+ non_plc_ip_address_b = non_plc_ip_address_b)
+ rst_template_dict.update (data)
+ rst_filled_template = rst_template.format (**rst_template_dict)
+ rst_file = rst_filled_template + rst_results()
+ report = open(file_prefix() + ".rst", 'w')
+ report.write(rst_file)
+ report.close()
+
+def rst_page_break():
+ return "" \
+ ".. raw:: pdf\n" \
+ "\n" \
+ " PageBreak\n" \
+ "\n"
+
+def rst_results():
+ title = "" \
+ "Results\n" \
+ "=======\n" \
+ "\n"
+ results = [ rst_result(fixture) for fixture in config["fixtures"] ]
+ return title + rst_page_break().join(results)
+
+def rst_result((test_protocol, test_type, test_params)):
+ if test_type == "SJR":
+ (freq_mhz, am) = test_params
+ if am is None:
+ am_part = "None"
+ else:
+ am_part = "(%s - %s kHz - %s %%)" % am
+ test_params_title = " - (%s MHz - AM = %s)" % (freq_mhz, am_part)
+ elif test_type == "dynamic_jammer":
+ test_params_title = " - (%s dB - %s MHz - %s MHz - %s s)" \
+ % test_params
+ else:
+ test_params_title = ""
+ return test_protocol + " - " + test_type + test_params_title + "\n" \
+ "------------------------------------------------------------------\n" \
+ "\n" + \
+ rst_param("Duration of each measurement (seconds)",
+ config[test_type]["duration_s"]) + \
+ rst_comment(test_type, test_params) + \
+ "\n" + \
+ ".. figure:: " + file_prefix(test_protocol, test_type, test_params,
+ None, False) + \
+ ".png\n" \
+ " :scale: 65 %\n" \
+ "\n"
+
+def rst_comment(test_type, test_params):
+ if test_type in ["attenuation", "SNR"]:
+ return rst_param("Duration of the `Channel Estimation (CE)`_ (seconds)",
+ config["ce_sync_time_s"])
+ elif test_type == "frame_size":
+ return rst_param("Input parameter",
+ "(frame size (bytes), max throughput (Mbits/sec))")
+ elif test_type == "SJR":
+ (freq_mhz, am) = test_params
+ if am is None:
+ am_rst_params = rst_param("AM", None)
+ else:
+ am_rst_params = rst_param("AM form", am[0]) + \
+ rst_param("AM frequency (kHz)", am[1]) + \
+ rst_param("AM depth (%)", am[2])
+ return rst_param("Duration of the CE synchronisation before each " \
+ "measurement (seconds)",
+ config["ce_sync_time_s"]) + \
+ rst_param("Jammer frequency (MHz)", freq_mhz) + \
+ am_rst_params
+ elif test_type == "dynamic_jammer":
+ return rst_param("SJR (dB)", test_params[0]) + \
+ rst_param("Sweep start frequency (MHz)", test_params[1]) + \
+ rst_param("Sweep stop frequency (MHz)", test_params[2]) + \
+ rst_param("Sweep time (seconds)", test_params[3])
+ assert False
+
+def rst_param(label, value):
+ return "* " + label + ": " + str(value) + "\n\n"
+
+def measurements_estimated_time_min():
+ """Estimation of the time required to complete all measurements"""
+ time_s = 0
+ for (_, test_type, _) in config["fixtures"] :
+ if config["restart_plugs"] == True:
+ start_time_s = 15
+ time_s = time_s + 2 * start_time_s
+ duration_s = config[test_type]["duration_s"]
+ if test_type in ["attenuation", "SNR", "SJR"]:
+ duration_s = duration_s + config["ce_sync_time_s"]
+ elif test_type in ["frame_size", "dynamic_jammer"]:
+ pass
+ else:
+ assert test_type
+ curves = config[test_type]["curves"]
+ test_values = get_test_values(test_type)
+ time_s = time_s + len(curves) * len(test_values) * duration_s
+
+ # 1.5 is an average of the ratio between actual and estimated times
+ return 1.5 * time_s / 60
+
+def make_measurements(test_protocol, test_type, test_params):
+ """Remove the previous measurements, modify the channel, restart the
+ plugs, execute the new measurements and dump the results in files"""
+ print "Removing the previous files of results..."
+ files_pattern = file_prefix(test_protocol, test_type, test_params) + "*"
+ subprocess.call("rm " + files_pattern, shell = True)
+
+ time_s = config["ce_sync_time_s"]
+ print "Starting the measurements of the maximum", test_protocol, \
+ "throughput vs the", test_type, "..."
+ initialize_testing_devices()
+ if (config["restart_plugs"]) == True:
+ iperf.switch_on_plugs()
+ test_values = get_test_values(test_type)
+ if test_values == ["time"]:
+ change_channel(test_type, test_params, None)
+ make_measurement(test_protocol, test_type, test_params, None)
+ else:
+ for test_value in test_values:
+ if change_channel(test_type, test_params, test_value) == True:
+ iperf.update_tone_maps(time_s)
+ make_measurement(test_protocol, test_type, test_params, test_value)
+ iperf.kill()
+ initialize_testing_devices()
+ print "End of the measurements of the maximum", test_protocol, \
+ "throughput vs the", test_type
+
+def initialize_testing_devices():
+ attenuator.set_attenuation(0)
+ waveform_generator.switch("off")
+
+def change_channel(test_type, test_params, test_value):
+ print "\nSetting", test_value, units[test_type], "of", test_type
+
+ data = waveform_data()
+
+ if test_type == "attenuation":
+ #Offset allowing to convert the overall attenuation in the attenuation
+ #at the attenuator level (attenuation = overall attenuation - offset)
+ offset = 2 * (signal_attenuation_db + get_amn_attenuation_db()) + \
+ attenuation_t_db
+ assert test_value >= offset, (test_type, test_value)
+ attenuator.set_attenuation(test_value - offset)
+ return True
+ elif test_type == "SNR":
+ waveform_generator.switch("on")
+ power_dbm = data["snr_offset"] - test_value
+ waveform_generator.configure_white_noise(power_dbm)
+ return True
+ elif test_type == "frame_size":
+ return False
+ elif test_type == "SJR":
+ attenuator.set_attenuation(data["sjr_attenuator_attenuation_db"])
+ waveform_generator.switch("on")
+ power_dbm = data["sjr_offset"] - test_value
+ (freq_mhz, am) = test_params
+ waveform_generator.configure_sinus(
+ freq_mhz * 1000 * 1000,
+ power_dbm,
+ 0,
+ am,
+ None)
+ return True
+ elif test_type == "dynamic_jammer":
+ attenuator.set_attenuation(data["sjr_attenuator_attenuation_db"])
+ waveform_generator.switch("on")
+ power_dbm = data["sjr_offset"] - test_params[0]
+ waveform_generator.configure_sinus(
+ 1,
+ power_dbm,
+ 0,
+ None,
+ (test_params[1], test_params[2], test_params[3]))
+ return True
+ assert False, test_type
+
+def make_measurement(test_protocol, test_type, test_params, test_value):
+ """Execute the new measurement and dump the results in a file"""
+ duration_s = config[test_type]["duration_s"]
+ curves = config[test_type]["curves"]
+ prefix = file_prefix(test_protocol, test_type, test_params, test_value)
+ if test_type in ["attenuation", "SNR", "SJR", "dynamic_jammer"]:
+ udp_parameters = ((iperf.default_frame_size, 103),
+ (iperf.default_frame_size, 88))
+ elif test_type == "frame_size":
+ udp_parameters = (test_value, test_value)
+ else:
+ assert False, test_type
+ iperf.make_measurement(test_protocol, udp_parameters, duration_s, curves,
+ prefix)
+
+def extract_datarates(test_protocol, test_type, test_params):
+ """Extract the datarates from the measurement files"""
+ datarates = {}
+ for direction in get_all_directions(test_type):
+ test_values = get_test_values(test_type)
+ if test_values == ["time"]:
+ datarates[direction] = \
+ extract_datarates_from_report(test_protocol, test_type, \
+ test_params, None, \
+ direction)[0]
+ else:
+ datarates[direction] = \
+ [ extract_datarates_from_report(test_protocol, test_type, \
+ test_params, test_value, \
+ direction)[1]
+ for test_value in test_values ]
+ return datarates
+
+def extract_datarates_from_report(test_protocol, test_type, test_params,
+ test_value, direction):
+ """Extract the datarate from a measurement file"""
+ prefix = file_prefix(test_protocol, test_type, test_params, test_value)
+ if test_type in ["attenuation", "SNR", "SJR", "dynamic_jammer"]:
+ frame_size = iperf.default_frame_size
+ elif test_type == "frame_size":
+ frame_size = test_value[0]
+ else:
+ assert False, test_type
+ duration_s = config[test_type]["duration_s"]
+ return iperf.extract_datarates_from_report(test_protocol, frame_size,
+ prefix, direction, duration_s)
+
+def dump_and_draw(test_protocol, test_type, test_params, datarates):
+ """Dump the data and draw the curves of the datarates"""
+ directions = get_all_directions(test_type)
+ test_values = get_test_values(test_type)
+ if test_values == ["time"]:
+ test_values = range(1, config[test_type]["duration_s"] + 1)
+ file_name_base = file_prefix(test_protocol, test_type, test_params)
+ iperf.dump(directions, test_type, test_values, file_name_base + ".txt",
+ datarates)
+ title = "Maximum throughput vs " + test_type + " in " + test_protocol
+ xlabel = test_type + " (" + units[test_type] + ")"
+ iperf.draw(title, xlabel, directions, test_type, test_values,
+ config["show"], file_name_base + ".png", datarates)
+
+def get_all_directions(test_type):
+ curves = config[test_type]["curves"]
+ return iperf.get_all_directions(curves)
+
+def get_reports_directory():
+ directory = config["reports_directory"]
+ if os.path.exists(directory) == False:
+ os.makedirs(directory)
+ return directory
+
+def file_prefix(test_protocol = None, test_type = None, test_params = None,
+ test_value = None, directory = True):
+ if directory == False:
+ reports_directory = ""
+ else:
+ reports_directory = get_reports_directory()
+ if test_type is None:
+ test_unit = None
+ else:
+ test_unit = units[test_type]
+ return get_file_prefix(test_protocol, test_type, test_params, test_value,
+ test_unit, reports_directory)
+
+def get_file_prefix(test_protocol, test_type, test_params, test_value,
+ test_unit, reports_directory):
+ base_name = os.path.splitext(os.path.basename(__file__))[0]
+ if test_protocol is not None:
+ base_name += "_" + test_protocol
+ if test_type is not None:
+ base_name += "_" + test_type
+ if test_params is not None:
+ test_params_strings = [ str(elem) for elem in flatten(test_params) ]
+ base_name += "_%s" % "_".join(test_params_strings)
+ if test_value is not None:
+ base_name += "_%s%s" % (str(test_value), test_unit)
+ return os.path.join(reports_directory, base_name)
+
+def waveform_data():
+ ppsd_dbm_per_hz = config["ppsd_dbm_per_hz"]
+ signal_band_mhz = config["signal_band_mhz"]
+ sjr_attenuator_attenuation_db = config["sjr_attenuator_attenuation_db"]
+ amn_attenuation_db = get_amn_attenuation_db()
+ noise_band_mhz = waveform_generator.noise_band_mhz()
+ return get_waveform_data(ppsd_dbm_per_hz, signal_band_mhz,
+ noise_band_mhz, sjr_attenuator_attenuation_db,
+ amn_attenuation_db)
+
+def get_waveform_data(ppsd_dbm_per_hz, signal_band_mhz, noise_band_mhz,
+ sjr_attenuator_attenuation_db, amn_attenuation_db):
+ (signal_freq_min_mhz, signal_freq_max_mhz) = signal_band_mhz
+ signal_bandwith_mhz = signal_freq_max_mhz - signal_freq_min_mhz
+ #the term 60 corresponds to the conversion from MHz to Hz
+ tx_signal_power_dbm = int (ppsd_dbm_per_hz - \
+ par_db + \
+ 60 + \
+ round(10 * math.log10(float(signal_bandwith_mhz)), 0))
+
+ (noise_freq_min_mhz, noise_freq_max_mhz) = noise_band_mhz
+ noise_bandwith_mhz = noise_freq_max_mhz - noise_freq_min_mhz
+
+ bandwiths_ratio_db = int(round(10 * math.log10( \
+ float(signal_bandwith_mhz) / float(noise_bandwith_mhz)), 0))
+
+ snr_offset = int(tx_signal_power_dbm - \
+ amn_attenuation_db - \
+ signal_attenuation_db + \
+ waveform_attenuation_db - \
+ bandwiths_ratio_db)
+
+ sjr_offset = int(tx_signal_power_dbm - \
+ amn_attenuation_db - \
+ sjr_attenuator_attenuation_db - \
+ signal_attenuation_db + \
+ waveform_attenuation_db)
+
+ return {"ppsd_dbm_per_hz":ppsd_dbm_per_hz,
+ "par_db":par_db,
+ "signal_freq_min_mhz":signal_freq_min_mhz,
+ "signal_freq_max_mhz":signal_freq_max_mhz,
+ "signal_bandwith_mhz":signal_bandwith_mhz,
+ "noise_freq_min_mhz":noise_freq_min_mhz,
+ "noise_freq_max_mhz":noise_freq_max_mhz,
+ "noise_bandwith_mhz":noise_bandwith_mhz,
+ "tx_signal_power_dbm":tx_signal_power_dbm,
+ "bandwiths_ratio_db":bandwiths_ratio_db,
+ "signal_attenuation_db":signal_attenuation_db,
+ "sjr_attenuator_attenuation_db":sjr_attenuator_attenuation_db,
+ "amn_attenuation_db":amn_attenuation_db,
+ "waveform_attenuation_db":waveform_attenuation_db,
+ "snr_offset":snr_offset,
+ "sjr_offset":sjr_offset}
+
+def get_amn_attenuation_db():
+ if config["amn"] == True:
+ return amn_attenuation_db
+ else:
+ return 0
+
+def get_test_values(test_type):
+ return config[test_type]["values"]
+
+def flatten(items, acc = None):
+ """Flatten a tuple or a list of tuples or of lists and return it as a flat
+ list"""
+ if acc is None:
+ return flatten(items, [])
+ if type(items) == tuple:
+ return flatten(list(items), acc)
+ if items == []:
+ return acc
+ first_item = items.pop(0)
+ if type(first_item) == list:
+ return flatten(first_item + items, acc)
+ if type(first_item) == tuple:
+ return flatten(list(first_item) + items, acc)
+ else:
+ acc.append(first_item)
+ return flatten(items, acc)
+
+if __name__ == "__main__":
+
+ assert {"ppsd_dbm_per_hz":-50,
+ "par_db":10,
+ "signal_freq_min_mhz":2,
+ "signal_freq_max_mhz":28,
+ "signal_bandwith_mhz":26,
+ "noise_freq_min_mhz":0,
+ "noise_freq_max_mhz":50,
+ "noise_bandwith_mhz":50,
+ "tx_signal_power_dbm":14,
+ "bandwiths_ratio_db":-3,
+ "signal_attenuation_db":10,
+ "sjr_attenuator_attenuation_db":17,
+ "amn_attenuation_db":6,
+ "waveform_attenuation_db":10,
+ "snr_offset":11,
+ "sjr_offset":-9} == \
+ get_waveform_data(-50, (2, 28), (0, 50), 17, 6)
+
+ assert {"ppsd_dbm_per_hz":-63,
+ "par_db":10,
+ "signal_freq_min_mhz":1,
+ "signal_freq_max_mhz":38,
+ "signal_bandwith_mhz":37,
+ "noise_freq_min_mhz":0,
+ "noise_freq_max_mhz":50,
+ "noise_bandwith_mhz":50,
+ "tx_signal_power_dbm":3,
+ "bandwiths_ratio_db":-1,
+ "signal_attenuation_db":10,
+ "sjr_attenuator_attenuation_db":17,
+ "amn_attenuation_db":0,
+ "waveform_attenuation_db":10,
+ "snr_offset":4,
+ "sjr_offset":-14} == \
+ get_waveform_data(-63, (1, 38), (0, 50), 17, 0)
+
+ assert "./test_throughput_UDP_frame_size_(5, 5)bytes" == \
+ get_file_prefix("UDP", "frame_size", None, (5, 5), "bytes", ".")
+ assert "./test_throughput_UDP_SNR_-5dB" == \
+ get_file_prefix("UDP", "SNR", None, -5, "dB", ".")
+ assert "/reports/test_throughput_UDP_SNR_-5dB" == \
+ get_file_prefix("UDP", "SNR", None, -5, "dB", "/reports")
+ assert "reports/test_throughput_UDP_SNR_-5dB" == \
+ get_file_prefix("UDP", "SNR", None, -5, "dB", "reports")
+ assert "reports/test_throughput_UDP_SNR_-5dB" == \
+ get_file_prefix("UDP", "SNR", None, -5, "dB", "reports/")
+ assert "reports/subreports/test_throughput_UDP_SNR_-5dB" == \
+ get_file_prefix("UDP", "SNR", None, -5, "dB", "reports/subreports")
+ assert "reports/subreports/test_throughput_UDP_SJR_15_-5dB" == \
+ get_file_prefix("UDP", "SJR", (15,), -5, "dB", "reports/subreports")
+ assert "reports/test_throughput_TCP_SNR" == \
+ get_file_prefix("TCP", "SNR", None, None, None, "reports")
+ assert "reports/test_throughput_TCP" == \
+ get_file_prefix("TCP", None, None, None, None, "reports")
+ assert "reports/test_throughput" == \
+ get_file_prefix(None, None, None, None, None, "reports")
+ assert "test_throughput" == \
+ get_file_prefix(None, None, None, None, None, "")
+
+ assert flatten([]) == []
+ assert flatten([1]) == [1]
+ assert flatten([1, 2]) == [1, 2]
+ assert flatten([1, 2, []]) == [1, 2]
+ assert flatten([1, [], 2]) == [1, 2]
+ assert flatten([1, [2, 3], 4]) == [1, 2, 3, 4]
+ assert flatten((1, [2, 3], 4)) == [1, 2, 3, 4]
+ assert flatten((1, (2, 3), 4)) == [1, 2, 3, 4]
+
+ report()
diff --git a/validation/test/test_iperf/test_throughput.rst b/validation/test/test_iperf/test_throughput.rst
new file mode 100644
index 0000000000..dafa1cd52f
--- /dev/null
+++ b/validation/test/test_iperf/test_throughput.rst
@@ -0,0 +1,144 @@
+Validation report (Iperf)
+++++++++++++++++++++++++++++++++++++++++++++++++++
+.. _MSTAR: http://www.mstarsemi.com
+
+:Company:
+ MSTAR_
+:Device model/version (host A):
+ {device_model_a} / {device_version_a}
+:Device model/version (host B):
+ {device_model_b} / {device_version_b}
+
+.. sectnum::
+
+.. |date| date::
+
+.. contents:: Table of contents
+
+.. footer::
+ ###Title### - {device_model_a} vs {device_model_b} - |date| - page ###Page### / ###Total###
+
+.. raw:: pdf
+
+ PageBreak
+
+Abstract
+========
+This document describes the maximum throughput obtained between 2 PCs, connected in PLC, with the tool Iperf, for different types of traffic and different channel conditions.
+
+Test bench
+==========
+
+Description
+-----------
+
+.. figure:: {fig_name}.eps
+ :scale: 200 %
+
+On the figure, the host A controls the attenuator and the noise generator but it is not technically necessary and it could be any other PC.
+
+The attenuation of 10 dB on the noise generator is useful when the generator is off, in order to avoid any reflection.
+
+The AMN's are necessary only if the plugs are not EoC, i.e have no native output in coax.
+
+Configuration
+-------------
+* Start order: The host A is started first
+* Iperf version on the host A ({non_plc_ip_address_a}): {version_a}
+* Iperf version on the host B ({non_plc_ip_address_b}): {version_b}
+
+.. raw:: pdf
+
+ PageBreak
+
+Earthing guidelines
+-------------------
+The earthing guidelines are the following:
+
+* Separate the ground and the earth on the AMN's so that the signal does not radiate through the earth
+* Connect the grounds of the AMN's with a coax without core
+* Use non-armed Ethernet cables to connect the plugs to the PC's
+* Connect the earth of the power strip to the T
+
+Network settings
+----------------
+
+The PC of test and the hosts A and B should be configured so that the PC of test is able to connect to the host A and B in SSH without having to enter a password for each SSH session.
+
+The PC of test should be configured to be able to ping the power strip. To that aim, on the PC of test, execute the following command (where ethX is the interface between the PC of test and the power strip)::
+
+ sudo route add -net $POWER_STRIP_ADDRESS netmask 255.255.255.255 $ethX
+
+The PC of test and the host A (resp. B) should be configured so that the PC of test is able to ping the plug A (resp. B) via the host A (resp. B). To that aim:
+
+* On the PC of test, execute the following commands::
+
+ sudo route add -net $PLUG_A_ADDRESS netmask 255.255.255.255 gw $HOST_A
+ sudo route add -net $PLUG_B_ADDRESS netmask 255.255.255.255 gw $HOST_B
+
+* On the hosts A and B:
+
+ - Uncomment the following line in the file /etc/sysctl.conf::
+
+ #net.ipv4.ip_forward=1
+
+ - Execute the following command::
+
+ sudo /etc/init.d/procps restart
+
+ - Execute the following command, where ethX is the interface between the host and the plug::
+
+ sudo iptables -t nat -A POSTROUTING -s $TEST_PC_NETWORK -o sethX -j MASQUERADE
+
+.. raw:: pdf
+
+ PageBreak
+
+Notes
+=====
+
+SNR calculation
+---------------
+
+Hypothesis:
+
+ * Attenuation of the attenuator = 0 dB
+ * Peak to Average Ratio (PAR) = {par_db} dB
+ * Peak Power Spectral Density (PPSD) = {ppsd_dbm_per_hz} dBm/Hz
+ * Signal Bandwith (SB - {signal_freq_min_mhz} MHz to {signal_freq_max_mhz} MHz) = {signal_bandwith_mhz} MHz
+ * Noise Bandwith (NB - {noise_freq_min_mhz} MHz to {noise_freq_max_mhz} MHz) = {noise_bandwith_mhz} MHz
+
+Therefore:
+
+ * Tx Signal Power (SP) = PSD - PAR + 10 log10(SB) = {tx_signal_power_dbm} dBm
+ * Bandwiths Ratio (BR) = 10 log10 (SB/NB) = {bandwiths_ratio_db} dB
+ * Signal to Noise Ratio (dB) = (SP - {amn_attenuation_db} dB - {signal_attenuation_db} dB) - (Noise Power (dBm) - {waveform_attenuation_db} dB) - BR
+ * Signal to Noise Ratio (dB) = {snr_offset} - Noise Power (dBm)
+
+SJR calculation
+---------------
+
+Hypothesis:
+
+ * Attenuation of the attenuator = {sjr_attenuator_attenuation_db} dB
+ * Peak to Average Ratio (PAR) = {par_db} dB
+ * Peak Power Spectral Density (PPSD) = {ppsd_dbm_per_hz} dBm/Hz
+ * Signal Bandwith (SB - {signal_freq_min_mhz} MHz to {signal_freq_max_mhz} MHz) = {signal_bandwith_mhz} MHz
+
+Therefore:
+
+ * Tx Signal Power (SP) = PSD - PAR + 10 log10(SB) = {tx_signal_power_dbm} dBm
+ * Signal to Jammer Ratio (dB) = (SP - {amn_attenuation_db} dB - {signal_attenuation_db} dB - {sjr_attenuator_attenuation_db} dB) - (Jammer Power (dBm) - {waveform_attenuation_db} dB)
+ * Signal to Jammer Ratio (dB) = {sjr_offset} - Jammer Power (dBm)
+
+Channel Estimation (CE)
+-----------------------
+
+When the channel changes, the tone map must be recalculated, which is called the Channel Estimation (CE) and which takes some time.
+
+For that reason, in some tests related to the adaptation to the channel (attenuation, noise, etc.), a traffic is first generated during a certain time, in order to force the tone map adaptation, and then the measurement is done.
+
+.. raw:: pdf
+
+ PageBreak
+