From 134532ab2a268ed06795b5e4c6ec222ad1fe42a3 Mon Sep 17 00:00:00 2001 From: Tim Wilkinson Date: Tue, 2 Nov 2021 21:35:39 -0700 Subject: [PATCH] aredn: Meshoween Mesh status page optimizations (#157) * Memory and cpu performance improvements * Fix bandwidth reporting * Discard large arrays once we're done with them * Fixup whitespace * Improve string constructions * Use available mem * Print the Remote Nodes as we go (can be big) * Local variables * Stop re-reading arp/mac files * Reduce calls to system 'cat' * Simply lat/lon read * Only read route30 once * Whitespace * Make meshstatus limits configurable * and => &&, or => || * gzip content if we can fixes #155 --- files/etc/config.mesh/aredn | 2 + files/www/cgi-bin/advancedconfig | 12 + files/www/cgi-bin/mesh | 402 +++++++++++++++++-------------- 3 files changed, 230 insertions(+), 186 deletions(-) diff --git a/files/etc/config.mesh/aredn b/files/etc/config.mesh/aredn index a6170bbb..757caba2 100644 --- a/files/etc/config.mesh/aredn +++ b/files/etc/config.mesh/aredn @@ -11,3 +11,5 @@ config map option leafletjs 'http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js' option leafletcss 'http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css' option maptiles 'http://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}.jpg' + +config meshstatus diff --git a/files/www/cgi-bin/advancedconfig b/files/www/cgi-bin/advancedconfig index fd78c882..054648ea 100755 --- a/files/www/cgi-bin/advancedconfig +++ b/files/www/cgi-bin/advancedconfig @@ -162,6 +162,18 @@ push @setting, { precallback => "restrictTunnelLimitToValidRange", postcallback => "adjustTunnelInterfaceCount()" }; +push @setting, { + key => "aredn.\@meshstatus[0].lowmem", + type => "string", + desc => "Specifies the low memory threshold (in KB) when we will truncate the mesh status page", + default => "10000" +}; +push @setting, { + key => "aredn.\@meshstatus[0].lowroutes", + type => "string", + desc => "When low memory is detected, limit the number of routes shown on the mesh status page", + default => "1000" +}; push @setting, { key => "aredn.olsr.restart", type => "none", diff --git a/files/www/cgi-bin/mesh b/files/www/cgi-bin/mesh index 9393eda2..23d41fe5 100755 --- a/files/www/cgi-bin/mesh +++ b/files/www/cgi-bin/mesh @@ -81,14 +81,31 @@ use perlfunc; 'MCS13' => '115.6', 'MCS14' => '130', 'MCS15' => '144.4', + ); + +# Limit displayed nodes and services to the most reachable routes if memory on the node is small +%lowMemoryLimits = ( + memory => `/sbin/uci -q get aredn.\@meshstatus[0].lowmem` || 10000, + routes => `/sbin/uci -q get aredn.\@meshstatus[0].lowroutes` || 1000, ); +sub get_available_mem +{ + foreach(`free`) + { + next unless /^Mem[:]/; + my @tmp = split /\s+/, $_; + return $tmp[6]; + } + return "N/A"; +} + # collect some variables $node = nvram_get("node"); $node = "NOCALL" if $node eq ""; $tactical = nvram_get("tactical"); $config = nvram_get("config"); -$config = "not set" if $config eq "" or not -d "/etc/config.mesh"; +$config = "not set" if $config eq "" || not -d "/etc/config.mesh"; ($my_ip) = get_ip4_network(get_interface("wifi")); ${wifiif} = `uci -q get 'network.wifi.ifname'`; chomp ${wifiif}; @@ -106,105 +123,103 @@ system "touch /tmp/web/automesh" if $parms{auto}; system "rm -f /tmp/web/automesh" if $parms{stop}; #get location info if available -$lat_lon = "Location Not Available"; -if(-f "/etc/latlon") { - $rcgood=open(FILE, "/etc/latlon"); - if($rcgood) { - while(){ - chomp; - push @lat_lon,$_; - } - } - close(FILE); - $lat_lon = "
Location: $lat_lon[0] $lat_lon[1]
"; +open my $rcgood, '<', '/etc/latlon'; +if ($rcgood) +{ + $lat_lon = "
Location: " . <$rcgood> . <$rcgood> . "
"; + close $rcgood; } -$olsrTotal = `/sbin/ip route list table 30 | wc -l`; #num hosts olsr is keeping track of -$olsrNodes = `/sbin/ip route list table 30 | egrep "/" | wc -l`; #num *nodes* on the network (minus other hosts) +else +{ + $lat_lon = "Location Not Available"; +} +@route30 = `/sbin/ip route list table 30`; +$olsrTotal = scalar @route30; +$olsrNodes = scalar grep /\//, @route30; $node_desc = `/sbin/uci -q get system.\@system[0].description`; #pull the node description from uci +undef @route30; # parse the txtinfo output -$table = "none"; chomp($tmperr = `mktemp /tmp/web/nc.XXXXXX`); -foreach(`echo /all | nc 127.0.0.1 2006 2>$tmperr`) +foreach(`echo /rou | nc 127.0.0.1 2006 2>>$tmperr`) { - if(/^Table: (\w+)/) + next if /^\D/; + my ($ip, $junk, $junk, $etx) = split /\s+/, $_; + my ($net, $cidr) = split /\//, $ip; + if ( $etx <= 50 ) { $routes{$net}{etx} = $etx; } +} +if ($olsrTotal > $lowMemoryLimits{routes} && get_available_mem() < $lowMemoryLimits{memory}) +{ + my @oroutes = sort { $routes{$a}{etx} <=> $routes{$b}{etx} } keys %routes; + foreach ( @oroutes[$lowMemoryLimits{routes} .. $#oroutes] ) { - $table = $1; - next; + delete $routes{$_}; } +} +@arps = {}; +open my $fh, '<', '/proc/net/arp'; +foreach (<$fh>) { push @arps, $_ if (/$wifiif/ && !/00:00:00:00:00:00/) } +close $fh; +foreach(`echo /lin | nc 127.0.0.1 2006 2>>$tmperr`) +{ + next if /^\D/; + my ($junk, $ip, $junk, $lq, $nlq) = split /\s+/, $_; + $links{$ip} = { + lq => $lq, + nlq => $nlq, + mbps => "" + }; + $neighbor{$ip} = 1; - next if /^\s*$/ or /^\D/; + my ($mac) = grep /^$ip/, @arps; + $mac =~ s/^.*(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w).*$/$1/; + chomp $mac; - if($table eq "Links") + open my $fh, '<', "/sys/kernel/debug/ieee80211/${phy}/netdev:${wifiif}/stations/$mac/rc_stats_csv"; + if ( $mac && $fh ) { - ($junk, $ip, $junk, $lq, $nlq) = split /\s+/, $_; - $links{$ip}{lq} = $lq; - $links{$ip}{nlq} = $nlq; - - $mac = `grep $ip /proc/net/arp | grep ${wifiif} | grep -v "00:00:00:00:00:00" | head -1`; - $mac =~ s/^.*(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w).*$/$1/; - chomp $mac; - - if (! $mac or ! -e "/sys/kernel/debug/ieee80211/${phy}/netdev:${wifiif}/stations/$mac" ) - { - $mbps = ""; - } + my @csv = <$fh>; + close $fh; + #802.11b/n + my ($mbps) = grep /^([^,]*,){3}A/, @csv; + if ($mbps) + { + my ($gi, $dummy, $rate, $dummy, $ewma) = $mbps =~ /^[^,]*,([^,]*),([^,]*,){2}([^,]*),([^,]*,){4}([^,]*).*$/ ; + $rate =~ s/[ \t]//g; + $mbps = $gi eq "SGI" ? $rateS{$rate}*$ewma/100 : $rateL{$rate}*$ewma/100 ; + } else { - #802.11b/n - $mbps = `egrep '^([^,]*,){3}A' /sys/kernel/debug/ieee80211/${phy}/netdev\:${wifiif}/stations/$mac/rc_stats_csv`; - if ($mbps) - { - ($gi, $dummy, $rate, $dummy, $ewma) = $mbps =~ /^[^,]*,([^,]*),([^,]*,){2}([^,]*),([^,]*,){4}([^,]*).*$/ ; - $rate =~ s/[ \t]//g; - $mbps = $gi eq "SGI" ? $rateS{$rate}*$ewma/100 : $rateL{$rate}*$ewma/100 ; - } - else - { - #802.11a/b/g - $mbps = `egrep \"^A" /sys/kernel/debug/ieee80211/${phy}/netdev\:${wifiif}/stations/$mac/rc_stats_csv`; - if ($mbps) - { - $mbps =~ /^[^,]*,([^,]*),([^,]*,){4}([^,]*).*$/; - $mbps = $1*$3/100; - } - else { $mbps = "0"; } - } - if ( ! $mbps eq "" ) - { - $mbps /= $chanbw; - $links{$ip}{mbps} = sprintf "%.1f",$mbps; - } - else { $links{$ip}{mbps} = "0.0"; } - } + #802.11a/b/g + ($mbps) = grep /^A/, @csv; + if ($mbps) + { + $mbps =~ /^[^,]*,([^,]*),([^,]*,){4}([^,]*).*$/; + $mbps = $1*$3/100; + } + } + $links{$ip}{mbps} = $mbps ? sprintf "%.1f", $mbps / $chanbw : "0.0"; } - elsif($table eq "Neighbors") +} +undef @arps; +foreach(`echo /hna | nc 127.0.0.1 2006 2>>$tmperr`) +{ + next if /^\D/; + my ($iproute, $ip) = split /\s+/, $_; + my ($net, $cidr) = split /\//, $iproute; + if ( $net eq "0.0.0.0" ) { $wangateway{$ip} = 1; } +} +foreach(`echo /mid | nc 127.0.0.1 2006 2>>$tmperr`) +{ + next if /^\D/; + my ($ip, $junk) = $_ =~ /^(\S+)\s+(.*)$/; + foreach $aip ( split /\s+/, $junk ) { - } - elsif($table eq "Topology") - { - } - elsif($table eq "HNA") - { - ($iproute, $ip) = split /\s+/, $_; - ($net, $cidr) = split /\//, $iproute; - if ( $net eq "0.0.0.0" ) { $wangateway{$ip} = 1; } - } - elsif($table eq "MID") - { - ($ip, $junk) = $_ =~ /^(\S+)\s+(.*)$/; - foreach $aip ( split /\s+/, $junk ) { $ipalias{$aip} = $ip } - } - elsif($table eq "Routes") - { - ($ip, $junk, $junk, $etx) = split /\s+/, $_; - ($net, $cidr) = split /\//, $ip; - $routes{$net}{cidr} = $cidr; - $routes{$net}{etx} = $etx; - $routes{$net}{value} = ip2decimal($net); - $routes{$net}{mask} = 0xffffffff - ((1 << (32 - $cidr)) - 1); + $ipalias{$aip} = $ip; + $neighbor{$aip} = 1; + if ( $links{$aip} ) { $neighbor{$ip} = 1 } } } @@ -214,11 +229,13 @@ $txtinfo_err = $parts[4]; unlink $tmperr; # load the local hosts file -foreach(`cat /etc/hosts`) + +open my $fh, '<', '/etc/hosts'; +foreach (<$fh>) { next unless /^10[.]/; chomp; - ($ip, $name, $tactical) = split /\s+/, $_; + my ($ip, $name, $tactical) = split /\s+/, $_; next if $name =~ /^(localhost|localnode|localap|dtdlink\..*)$/; if ( $name !~ /\./ ) { $name="${name}.local.mesh"; } if($ip eq $my_ip) @@ -231,58 +248,71 @@ foreach(`cat /etc/hosts`) if($tactical eq "#NOPROP") { push @{$localhosts{$my_ip}{noprops}}, $name; } if($tactical eq "#ALIAS") { push @{$localhosts{$my_ip}{aliases}}, $name; } } +close $fh; # load the olsr hosts file -foreach(`cat /var/run/hosts_olsr 2>/dev/null`) +open my $fh, '<', '/var/run/hosts_olsr'; +foreach (<$fh>) { next unless /^\d/; chomp; - ($ip, $name, undef, $originator, undef, undef) = split /\s+/, $_; + my ($ip, $name, undef, $originator, undef, undef) = split /\s+/, $_; next unless $originator; next if $originator eq "myself"; + # Filter hosts which are unreachable + next unless $routes{$ip} || $routes{$originator}; + + my $etx = $routes{$ip}{etx} ? $routes{$ip}{etx} : $routes{$originator}{etx}; if (( $name !~ /\./ ) || ( $name =~ /^mid\.[^\.]*$/ )) { $name="${name}.local.mesh"; } if ( $ip eq $originator ) { - if($hosts{$ip}{name}) { $hosts{$ip}{tactical} = $name } + if ($hosts{$ip}{name}) + { + $hosts{$ip}{tactical} = $name; + } else - { - $hosts{$ip}{name} = $name; - if ( $routes{$ip} ) { $hosts{$ip}{etx} = $routes{$ip}{etx} ; } - else { $hosts{$ip}{etx} = "99.000"; } - } - } - elsif ( $name =~ /^dtdlink\..*$/ ) { $hosts{$ip}{name} = $name; - $dtd{$originator} = 1; - if ( $routes{$ip} ) { $hosts{$ip}{etx} = $routes{$ip}{etx} ; } - else { $hosts{$ip}{etx} = "99.000"; } + $hosts{$ip}{etx} = $etx; } - elsif ( $name =~ /^mid\d+\..*$/ ) - { - $midcount{$originator} = $midcount{$originator} ? $midcount{$originator}+1: 1 ; - if (! $hosts{$ip}{name} ) - { - if ( $routes{$ip} ) { $hosts{$ip}{etx} = $routes{$ip}{etx} ; } - else { $hosts{$ip}{etx} = "99.000"; } - $hosts{$ip}{name} = $name; - } - } - else { push @{$hosts{$originator}{hosts}}, $name; } + } + elsif ( $name =~ /^dtdlink\..*$/ ) + { + $dtd{$originator} = 1; + if ( $links{$ip} ) { $links{$ip}{dtd} = 1 } + } + elsif ( $name =~ /^mid\d+\..*$/ ) + { + $midcount{$originator} = $midcount{$originator} ? $midcount{$originator}+1: 1 ; + if ( $links{$ip} ) { $links{$ip}{tun} = 1 } + } + else + { + push @{$hosts{$originator}{hosts}}, $name; + } } +close $fh; + +# Discard +undef %routes; # load the olsr services file -foreach(`cat /var/run/services_olsr 2>/dev/null`) +open my $fh, '<', '/var/run/services_olsr'; +foreach (<$fh>) { next unless /^\w/; chomp; - ($url, $junk, $name) = split /\|/, $_; + my ($url, $junk, $name) = split /\|/, $_; next unless defined $name; - ($protocol, $host, $port, $path) = $url =~ /^(\w+):\/\/([\w\-\.]+):(\d+)\/(.*)/; + my ($protocol, $host, $port, $path) = $url =~ /^(\w+):\/\/([\w\-\.]+):(\d+)\/(.*)/; next unless defined $path; - ($name, $originator) = split /\#/, $name; + my ($name, $originator) = split /\#/, $name; + + # Filter services for unreachable hosts + next unless $hosts{$originator}{name} || $originator eq " my own service"; + $name =~ s/\s+$//; if ( $host !~ /\./ ) { $host="${host}.local.mesh"; } @@ -293,9 +323,11 @@ foreach(`cat /var/run/services_olsr 2>/dev/null`) $services{$host}{$name} = $port ? "$name" : $name; } +close $fh; # load the node history -foreach(`cat /tmp/node.history 2>/dev/null`) +open my $fh, '<', '/tmp/node.history'; +foreach (<$fh>) { chomp; ($ip, $age, $host) = split / /, $_; @@ -305,12 +337,23 @@ foreach(`cat /tmp/node.history 2>/dev/null`) $history{$ip}{age} = $age; $history{$ip}{host} = $host; } +close $fh; #delete $hosts{"127.0.0.1"}; +# compress the output if we can +if ( $ENV{HTTP_ACCEPT_ENCODING} =~ /gzip/ ) +{ + print "Content-type: text/html\r\nCache-Control: no-store\r\nContent-Encoding: gzip\r\n\r\n"; + open my $zout, "|gzip"; + select $zout; +} +else +{ + print "Content-type: text/html\r\nCache-Control: no-store\r\n\r\n"; +} # generate the page -http_header(); html_header("$node mesh status", 0); print "\n" if -f "/tmp/web/automesh"; print "\n"; @@ -353,7 +396,7 @@ if($txtinfo_err) print "

\n"; -unless(keys %localhosts or keys %links) +unless(keys %localhosts || keys %links) { print "No other nodes are available.\n"; print ""; @@ -373,13 +416,13 @@ print "
\n"; if(keys %localhosts) { - %rows = (); + my %rows = (); foreach $ip (keys %localhosts) { - $host = $localhosts{$ip}{name}; - $localpart = $host =~ s/.local.mesh//r; - $tactical = $localhosts{$ip}{tactical} ? " / " . $localhosts{$ip}{tactical} : ""; + my $host = $localhosts{$ip}{name}; + my $localpart = $host =~ s/.local.mesh//r; + my $tactical = $localhosts{$ip}{tactical} ? " / " . $localhosts{$ip}{tactical} : ""; $rows{$host} = sprintf "%s", $localpart . $tactical; if ( $wangateway{$ip} ) { $nodeiface = "wan" ; } @@ -397,15 +440,14 @@ if(keys %localhosts) foreach $dmzhost (@{$localhosts{$ip}{hosts}}) { #find non-propagated and aliased hosts and change color - $nopropd = 0; - $aliased = 0; + my $nopropd = 0; + my $aliased = 0; if(grep { /$dmzhost/ } @{$localhosts{$ip}{noprops}}) { $nopropd = 1; } if(grep { /$dmzhost/ } @{$localhosts{$ip}{aliases}}) { $aliased = 1; } $localpart = $dmzhost =~ s/.local.mesh//r; - if(!$nopropd and !$aliased) { $rows{$host} .= " $localpart"; } + if(!$nopropd && !$aliased) { $rows{$host} .= " $localpart"; } elsif($aliased) { $rows{$host} .= " $localpart"; } else { $rows{$host} .= " $localpart"; } - $rows{$host} .= "\n"; foreach(sort keys %{$services{$dmzhost}}) { @@ -429,67 +471,52 @@ print " \n"; print "Remote Nodes  ETX  Services\n"; print "
\n"; -%rows = (); -%sortrows = (); -foreach $ip (keys %hosts) +my $row; +foreach $ip (sort { $hosts{$a}{etx} <=> $hosts{$b}{etx} } keys %hosts) { - next if $links{$ip}; - next if $ipalias{$ip}; + next if $neighbor{$ip}; + my $host = $hosts{$ip}{name}; + next unless $host; + my $localpart = $host =~ s/.local.mesh//r; + my $tactical = $hosts{$ip}{tactical} ? " / " . $hosts{$ip}{tactical} : ""; + my $etx = sprintf "%.2f", $hosts{$ip}{etx}; - $isNeig=0; - foreach $aip (keys %ipalias) - { - if ($ipalias{$aip} eq $ip ) { if ($links{$aip} ) { $isNeig=1; last;} } - } - next if $isNeig; + $row = sprintf "%s", $host, $localpart . $tactical; - $host = $hosts{$ip}{name}; - $localpart = $host =~ s/.local.mesh//r; - $tactical = $hosts{$ip}{tactical} ? " / " . $hosts{$ip}{tactical} : ""; - $etx = sprintf "%.2f", $hosts{$ip}{etx}; - next if ($etx > 50 ); - next if ($etx == 0 ); - - $rows{$host} = sprintf "%s", $host, $localpart . $tactical; - - undef $nodeiface; - if ( $dtd{$ip} ) - { - if ( $midcount{$ip} ) { $midcount{$ip} -= 1; } # extra mid entry matching and with dtdlink in hosts_olsrd - } - if ( $hosts{$ip}{tactical} ) { $midcount{$ip} -= 1; } # extra mid entry if tactical name defined - if ( $midcount{$ip} ) { $nodeiface = "tun*$midcount{$ip}" ; } + my $nodeiface; + my $mcount = 0 + $midcount{$ip}; + if ( $dtd{$ip} ) { $mcount -= 1; } # extra mid entry matching and with dtdlink in hosts_olsrd + if ( $hosts{$ip}{tactical} ) { $mcount -= 1; } # extra mid entry if tactical name defined + if ( $mcount > 0 ) { $nodeiface = "tun*$mcount" ; } if ( $wangateway{$ip} ) { $nodeiface = $nodeiface ? $nodeiface . ",wan" : "wan" ; } - if ( $nodeiface ) { $rows{$host} .= "   ($nodeiface)"; } + if ( $nodeiface ) { $row .= "   ($nodeiface)"; } - $rows{$host} .= sprintf "%s\n", $etx; + $row .= sprintf "%s\n", $etx; foreach(sort keys %{$services{$host}}) { - $rows{$host} .= "" . $services{$host}{$_} . "
\n"; + $row .= "" . $services{$host}{$_} . "
\n"; } - $rows{$host} .= "\n"; + $row .= "\n"; # add advertised dmz hosts foreach $dmzhost (@{$hosts{$ip}{hosts}}) { - $localpart = $dmzhost =~ s/.local.mesh//r; - $rows{$host} .= " $localpart"; - $rows{$host} .= "\n"; + my $localpart = $dmzhost =~ s/.local.mesh//r; + $row .= " $localpart"; + $row .= "\n"; foreach(sort keys %{$services{$dmzhost}}) { - $rows{$host} .= "" . $services{$dmzhost}{$_} . "
\n"; + $row .= "" . $services{$dmzhost}{$_} . "
\n"; } - $rows{$host} .= "\n"; + $row .= "\n"; } - $sortrows{$ip}=$host; + print $row; } -if(keys %rows) -{ - foreach(sort { $hosts{$a}{etx} <=> $hosts{$b}{etx} } keys %sortrows) { print $rows{$sortrows{$_}} } -} -else +undef %neighbor; + +if(!$row) { print "none\n"; } @@ -504,49 +531,51 @@ print "
\n"; if(keys %links) { - %rows = (); + my %rows = (); foreach $ip (keys %links) { - $ipmain = exists $ipalias{$ip} ? $ipalias{$ip} : $ip ; - $host = $hosts{$ipmain}{name} ? $hosts{$ipmain}{name} : $ipmain; - $localpart = $host =~ s/.local.mesh//r; - $tactical = $hosts{$ipmain}{tactical} ? " / " . $hosts{$ipmain}{tactical} : ""; + my $ipmain = exists $ipalias{$ip} ? $ipalias{$ip} : $ip ; + my $host = $hosts{$ipmain}{name} ? $hosts{$ipmain}{name} : $ipmain; + my $localpart = $host =~ s/.local.mesh//r; + my $tactical = $hosts{$ipmain}{tactical} ? " / " . $hosts{$ipmain}{tactical} : ""; if ( $rows{$host} ) { $host .= " " ; } # avoid collision 2 links to same host {rf, dtd} - $no_space_host=$host; + my $no_space_host=$host; $no_space_host =~ s/\s+$//; - $rows{$host} = sprintf "%s", $no_space_host, $localpart . $tactical; + my $row = sprintf "%s", $no_space_host, $localpart . $tactical; - undef $nodeiface; + my $nodeiface; if ( $ipmain ne $ip ) # indicate if dtd or tunnel interface to neighbor { - if ( $hosts{$ip}{name} =~ /^dtdlink\..*$/ ){ $nodeiface="dtd" ; } - elsif ( $hosts{$ip}{name} =~ /^mid\d+\..*$/ ) { $nodeiface="tun" ; } - else { $nodeiface="?" ; } + if ( $links{$ip}{dtd} ){ $nodeiface="dtd" ; } + elsif ( $links{$ip}{tun} ){ $nodeiface="tun" ; } + else { $nodeiface="?" ; } } - if ( $wangateway{$ip} or $wangateway{$ipmain} ) { $nodeiface = $nodeiface ? $nodeiface . ",wan" : "wan" ; } - if ( $nodeiface ) { $rows{$host} .= "   ($nodeiface)"; } + if ( $wangateway{$ip} || $wangateway{$ipmain} ) { $nodeiface = $nodeiface ? $nodeiface . ",wan" : "wan" ; } + if ( $nodeiface ) { $row .= "   ($nodeiface)"; } - $rows{$host} .= sprintf ("%.0f%%%.0f%%%s\n", 100*$links{$ip}{lq}, 100*$links{$ip}{nlq},$links{$ip}{mbps}); + $row .= sprintf ("%.0f%%%.0f%%%s\n", 100*$links{$ip}{lq}, 100*$links{$ip}{nlq},$links{$ip}{mbps}); if ( ! exists $neighservices{$host} ) { - foreach(sort keys %{$services{$host}}) { $rows{$host} .= "" . $services{$host}{$_} . "
\n" } + foreach(sort keys %{$services{$host}}) { $row .= "" . $services{$host}{$_} . "
\n" } - $rows{$host} .= "\n"; + $row .= "\n"; # add advertised dmz hosts foreach $dmzhost (@{$hosts{$ipmain}{hosts}}) { - $localpart = $dmzhost =~ s/.local.mesh//r; - $rows{$host} .= " $localpart\n"; - foreach(sort keys %{$services{$dmzhost}}) { $rows{$host} .= $services{$dmzhost}{$_} . "
\n" } - $rows{$host} .= "\n"; + my $localpart = $dmzhost =~ s/.local.mesh//r; + $row .= " $localpart\n"; + foreach(sort keys %{$services{$dmzhost}}) { $row .= $services{$dmzhost}{$_} . "
\n" } + $row .= "\n"; } $neighservices{$host}=1; } + + $rows{$host}=$row; } foreach(sort keys %rows) { print $rows{$_} } @@ -556,6 +585,7 @@ else print "none\n"; } +undef %services; # show previous neighbors