diff --git a/LANforge/Utils.pm b/LANforge/Utils.pm index e52b883d..c157b1bd 100644 --- a/LANforge/Utils.pm +++ b/LANforge/Utils.pm @@ -137,13 +137,53 @@ sub normalize_bucket_hdr { return $rv; } +# Normalize lat1, taking peer latency (lat2) into account for negative latency and such. +sub normalize_latency { + my $self = shift; + my $lat1 = shift; + my $lat2 = shift; + + #print "lat1 -:$lat1:-\n"; + #print "lat2 -:$lat2:-\n"; + + my $min1 = 0; + my $min2 = 0; + + # Looks like this: 5 -:5:- 6 [ 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] (1) + if ($lat1 =~ /(\S+)\s+-:(\S+):-\s+(\S+)\s+\[\s+(.*)\s+\]\s+\((\S+)\)/) { + $min1 = $1; + } + if ($lat2 =~ /(\S+)\s+-:(\S+):-\s+(\S+)\s+\[\s+(.*)\s+\]\s+\((\S+)\)/) { + $min2 = $1; + } + + # For instance, min1 is -5, min2 is 25, rt-latency is 20. + # Adjust lat1 by (25 - -5) / 2 + # For instance, min1 is 25, min2 is -5, rt-latency is 20. + # Adjust lat1 by (-5 -25) / 2 + #print "min1: $min1 min2: $min2 half: " . int(($min2 - $min1) / 2) . "\n"; + # So, the above seems nice, but often we have a small negative value due to + # clock drift in one direction, and large latency in the other (due to real one-way latency) + # So, we will just adjust enough to make the smallest value positive. + my $adjust = 0; + if ($min1 < 0) { + $adjust = -$min1; + } + elsif ($min2 < 0) { + $adjust = -$min2; + } + return $self->normalize_bucket($lat1, $adjust); +} + sub normalize_bucket { my $self = shift; my $line = shift; + my $adjust = shift; + #print "line -:$line:-\n"; # Looks like this: 5 -:5:- 6 [ 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] (1) - if ($line =~ /(\d+)\s+-:(\d+):-\s+(\d+)\s+\[\s+(.*)\s+\]\s+\((\d+)\)/) { + if ($line =~ /(\S+)\s+-:(\S+):-\s+(\S+)\s+\[\s+(.*)\s+\]\s+\((\S+)\)/) { my $min = $1; my $avg = $2; my $max = $3; @@ -156,7 +196,7 @@ sub normalize_bucket { my @bkts = split(/\s+/, $bks); @bkts = (@bkts, "0"); my $i; - my $rv = "$min $max $avg "; + my $rv = ($min + $adjust) . " " . ($max + $adjust) . " " . ($avg + $adjust) . " "; #print "bkts len: " . @bkts . "\n"; my @nbkts = (0) x (@bkts); for ($i = 0; $i<@bkts; $i++) { @@ -170,6 +210,10 @@ sub normalize_bucket { $minv += $min; $maxv += $min; + # And adjust based on round-trip time to deal with clock lag + $minv += $adjust; + $maxv += $adjust; + # And now find the normalized bucket this fits in #print "maxv: $maxv\n"; my $z; diff --git a/lf_firemod.pl b/lf_firemod.pl index e69c11f4..f5eb86ab 100755 --- a/lf_firemod.pl +++ b/lf_firemod.pl @@ -58,6 +58,8 @@ our $use_csums = "NO"; # Use LANforge checksums in payload? our $ttl = 32; our $report_timer = 5000; our $tos = ""; +our $lat1 = ""; +our $lat2 = ""; our $arm_pps = ""; our $arm_cpu_id = "NA"; # For cross connects @@ -136,6 +138,8 @@ $0 [ --action { Example: $0 --action set_endp --endp_name udp1-A --speed 154000 + $0 --action normalize_latency --lat1 "latency-buckets info for endpA" --lat2 "latency-buckets-info for endpB" + $0 --action create_endp --endp_name mcast_xmit_1 --speed 154000 \\ --endp_type mc_udp --mcast_addr 224.9.9.8 --mcast_port 9998 \\ --rcv_mcast NO --port_name eth1 \\ @@ -233,6 +237,8 @@ GetOptions 'use_max_speeds=s' => \$::use_max_speeds, 'test_mgr=s' => \$::test_mgr, 'tos=s' => \$::tos, + 'lat1=s' => \$::lat1, + 'lat2=s' => \$::lat2, ) || die("$::usage"); @@ -287,7 +293,7 @@ if ($::do_cmd ne "NA") { $::action = "do_cmd"; } our @valid_actions = qw( - create_arm create_cx create_endp + create_arm create_cx create_endp normalize_latency delete_cx delete_cxe delete_endp do_cmd list_cx list_endp list_ports set_endp show_cx show_endp show_port start_endp stop_endp @@ -321,10 +327,14 @@ if (!defined $::stats_from_file || ("" eq $::stats_from_file)) { $::utils->connect($lfmgr_host, $lfmgr_port); } -if (grep {$_ eq $::action} split(',', "show_endp,set_endp,create_endp,create_arm,list_endp")) { +if (grep {$_ eq $::action} split(',', "show_endp,set_endp,create_endp,create_arm,list_endp,normalize_latency")) { $::max_speed = $::speed if ($::max_speed eq "-1"); - if ($::action eq "list_endp") { + if ($::action eq "normalize_latency") { + my $val = $::utils->normalize_latency($::lat1, $::lat2); + print("Normalized-Latency: $val\n"); + } + elsif ($::action eq "list_endp") { $::utils->cli_rcv_silent(0); $::quiet = "no"; my @lines = split(/\r?\n/, $::utils->doAsyncCmd("nc_show_endpoints all")); @@ -408,7 +418,6 @@ if (grep {$_ eq $::action} split(',', "show_endp,set_endp,create_endp,create_arm #print "val -:$val:-\n"; $option_map{"Normalized-Hdr"} = $::utils->normalize_bucket_hdr(17); $option_map{"Latency"} = $val; - $option_map{"Latency-Normalized"} = $::utils->normalize_bucket($val); } } elsif ($end_val =~ /Pkt-Gaps/) { @@ -416,7 +425,6 @@ if (grep {$_ eq $::action} split(',', "show_endp,set_endp,create_endp,create_arm my $val = $1; $option_map{"Normalized-Hdr"} = $::utils->normalize_bucket_hdr(17); $option_map{"Pkt-Gaps"} = $val; - $option_map{"Pkt-Gaps-Normalized"} = $::utils->normalize_bucket($val); } } elsif ($end_val =~ /RX-Silence/) { @@ -424,7 +432,6 @@ if (grep {$_ eq $::action} split(',', "show_endp,set_endp,create_endp,create_arm my $val = $1; $option_map{"Normalized-Hdr"} = $::utils->normalize_bucket_hdr(17); $option_map{"RX-Silence"} = $val; - $option_map{"RX-Silence-Normalized"} = $::utils->normalize_bucket($val); } } elsif ($end_val =~ /Cx Detected/) { @@ -456,7 +463,7 @@ if (grep {$_ eq $::action} split(',', "show_endp,set_endp,create_endp,create_arm else { $value = 0 + $parts[0]; } - #print "\n B end_val[$end_val] option[$option] now ".$value."\n"; + print "\n B end_val[$end_val] option[$option] now ".$value."\n"; $option_map{ $end_val } = $value; # For backwards compat with older logic diff --git a/lf_tos_test.py b/lf_tos_test.py index f0df6841..2f10e094 100755 --- a/lf_tos_test.py +++ b/lf_tos_test.py @@ -173,24 +173,29 @@ def main(): green_left.set_border(1) worksheet.set_row(0, 45) # Set height - worksheet.set_column(0, 0, 10) # Set width bucket_hdrs = "0-0 1-1 2-3 4-7 8-15 16-31 32-63 64-127 128-255 256-511 512-1023 1024-2047 2048-4095 4096-8191 8192-16383 16384-32767 32768-65535".split() col = 0 row = 0 + + worksheet.set_column(0, 35, 13) # Set width to 14 for all columns by default worksheet.write(row, col, 'CX-Name', dblue_bold); col += 1 - worksheet.write(row, col, 'Station', dblue_bold); col += 1 + worksheet.set_column(col, col, 15) + worksheet.write(row, col, 'Endp-Name', dblue_bold); col += 1 + worksheet.set_column(col, col, 12) + worksheet.write(row, col, 'Port', dblue_bold); col += 1 worksheet.write(row, col, 'Protocol', dblue_bold); col += 1 worksheet.write(row, col, 'ToS', dblue_bold); col += 1 + worksheet.set_column(col, col, 20) worksheet.write(row, col, 'AP BSSID', dblue_bold); col += 1 - worksheet.write(row, col, 'Bandwidth', dblue_bold); col += 1 + worksheet.write(row, col, 'Band\nwidth', dblue_bold); col += 1 worksheet.write(row, col, 'Mode', dblue_bold); col += 1 worksheet.write(row, col, 'Last MCS\nRx', dblue_bold); col += 1 worksheet.write(row, col, 'Combined\nRSSI', dpeach_bold); col += 1 worksheet.write(row, col, 'Endpoint\nOffered\nLoad', dblue_bold); col += 1 - worksheet.write(row, col, 'Endpoint\n\Rx\nThroughput', dblue_bold); col += 1 + worksheet.write(row, col, 'Endpoint\nRx\nThroughput', dblue_bold); col += 1 worksheet.write(row, col, 'Cx\nOffered\nLoad', dblue_bold); col += 1 - worksheet.write(row, col, 'Cx\n\Rx\nThroughput', dblue_bold); col += 1 + worksheet.write(row, col, 'Cx\nRx\nThroughput', dblue_bold); col += 1 worksheet.write(row, col, 'Avg\nLatency', dblue_bold); col += 1 worksheet.write(row, col, 'Min\nLatency', dblue_bold); col += 1 worksheet.write(row, col, 'Max\nLatency', dblue_bold); col += 1 @@ -376,11 +381,11 @@ def main(): results = [""] * 7 endp_stats = subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--endp_vals", "tx_bps,rx_bps,Tx Bytes,Rx Bytes,Tx Pkts,Rx Pkts,Latency", - "--endp_name", ena], capture_output=True); + "--endp_name", ename], capture_output=True); pss = endp_stats.stdout.decode('utf-8', 'ignore'); for line in pss.splitlines(): - print("probe-line: %s"%(line)) + print("probe-line, endp: %s: %s"%(ename, line)) m = re.search('Rx Bytes:\s+(\d+)', line) if (m != None): results[1] = int(m.group(1)) @@ -412,7 +417,7 @@ def main(): if (m != None): results[5] = m.group(1) - m = re.search('Latency-Normalized:\s+(.*)', line) + m = re.search('Latency:\s+(.*)', line) if (m != None): results[6] = m.group(1) @@ -421,30 +426,65 @@ def main(): else: resultsB = results.copy() + # Now that we know both latencies, we can normalize them. + endp_statsA = subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "normalize_latency", + "--lat1", resultsA[6], "--lat2", resultsB[6]], capture_output=True); + pssA = endp_statsA.stdout.decode('utf-8', 'ignore'); + endp_statsB = subprocess.run(["./lf_firemod.pl", "--manager", lfmgr, "--action", "normalize_latency", + "--lat1", resultsB[6], "--lat2", resultsA[6]], capture_output=True); + pssB = endp_statsB.stdout.decode('utf-8', 'ignore'); + + print("latA: %s"%resultsA[6]) + print("latB: %s"%resultsB[6]) + print("pssA: %s"%pssA) + print("pssB: %s"%pssB) + + for line in pssA.splitlines(): + m = re.search('Normalized-Latency:\s+(.*)', line) + if (m != None): + resultsA[6] = m.group(1); + + for line in pssB.splitlines(): + m = re.search('Normalized-Latency:\s+(.*)', line) + if (m != None): + resultsB[6] = m.group(1); + for ename in enames: col = 0 if (ena == ename): - results = resultsA + results = resultsA.copy() else: - results = resultsB + results = resultsB.copy() lat_cols = results[6].split() # min, max, avg, columns.... worksheet.write(row, col, cxn, center_blue); col += 1 worksheet.write(row, col, ename, center_blue); col += 1 - worksheet.write(row, col, "%s.%s"%(sta_resource, sta_name), center_blue); col += 1 + if ename == ena: + worksheet.write(row, col, "%s.%s"%(sta_resource, sta_name), center_blue); col += 1 + else: + worksheet.write(row, col, "%s.%s"%(u_resource, u_name), center_blue); col += 1 worksheet.write(row, col, proto, center_blue); col += 1 worksheet.write(row, col, tos, center_blue); col += 1 - worksheet.write(row, col, _ap, center_blue); col += 1 - worksheet.write(row, col, _bw, center_blue); col += 1 - worksheet.write(row, col, _mode, center_blue); col += 1 - worksheet.write(row, col, _rxrate, center_blue); col += 1 - worksheet.write(row, col, _signal, center_blue); col += 1 - worksheet.write(row, col, results[2], center_blue); col += 1 - worksheet.write(row, col, results[3], center_tan); col += 1 - worksheet.write(row, col, "%s"%(int(resultsA[2]) + int(resultsB[2])), center_blue); col += 1 - worksheet.write(row, col, "%s"%(int(resultsA[3]) + int(resultsB[3])), center_tan); col += 1 + if ename == ena: + worksheet.write(row, col, _ap, center_blue); col += 1 + worksheet.write(row, col, _bw, center_blue); col += 1 + worksheet.write(row, col, _mode, center_blue); col += 1 + worksheet.write(row, col, _rxrate, center_blue); col += 1 + worksheet.write(row, col, _signal, center_blue); col += 1 + else: + # Upstream is likely wired, don't print station info + worksheet.write(row, col, "", center_blue); col += 1 + worksheet.write(row, col, "", center_blue); col += 1 + worksheet.write(row, col, "", center_blue); col += 1 + worksheet.write(row, col, "", center_blue); col += 1 + worksheet.write(row, col, "", center_blue); col += 1 + + worksheet.write(row, col, float(results[2]) / 1000000, center_blue); col += 1 + worksheet.write(row, col, float(results[3]) / 1000000, center_tan); col += 1 + worksheet.write(row, col, "%s"%((float(resultsA[2]) + float(resultsB[2])) / 1000000), center_blue); col += 1 + worksheet.write(row, col, "%s"%((float(resultsA[3]) + float(resultsB[3])) / 1000000), center_tan); col += 1 worksheet.write(row, col, lat_cols[2], center_tan); col += 1 worksheet.write(row, col, lat_cols[0], center_tan); col += 1 worksheet.write(row, col, lat_cols[1], center_tan); col += 1