Commit c8a4ccd3 authored by Otto J Wittner's avatar Otto J Wittner
Browse files

Merge branch 'master' of scm.uninett.no:IOU/microdep

parents abe28d0a fe91ae0b
Pipeline #40300 passed with stages
in 1 minute and 3 seconds
......@@ -11,6 +11,7 @@ NOTE: The project contains submodules, hence `git submodule update --init` is re
sudo apt install ntp
sudo apt install traceroute tcptraceroute
sudo apt install curl libstatistics-linefit-perl libstatistics-basic-perl libdatetime-perl libjson-xs-perl
sudo apt install tcpdump rsync
### ntp configuration
insert in /etc/ntp.conf if in Uninett - else replace server addresses :
......@@ -22,6 +23,7 @@ insert in /etc/ntp.conf if in Uninett - else replace server addresses :
git clone https://scm.uninett.no/rude/rude.git
cd microdep
bash ./build.sh
./configure
make
......@@ -68,4 +70,4 @@ Enter node in config files
do scripts
* ~/sw/microdep/server/script/do-dragonlab-config
* ~/sw/microdep/server/script/microdep-config-load
* ~/sw/microdep/server/script/microdep-config-load /var/lib/microdep/mp-dragonlab/etc/mp-names.txt
......@@ -34,12 +34,8 @@ else
$crude -k -p $crude_port -b $udp_buffer -z $pipe_size >> $PIPE &
sleep 3 # allow pipe to be created
fi
if ! pgrep -u $USER -f "bin/crude-zip " >/dev/null; then
bash $root/bin/crude-zip $PIPE $PIPE2 $logpath/$date/crude-$T.gz &
sleep 3 # allow pipe2 to be created
fi
if ! pgrep -u $USER -f "bin/crude-ana " >/dev/null; then
bash $root/bin/crude-ana "$index" $PIPE2 $event_dir/${index}-events-`date +%T`.log &
if ! pgrep -u $USER -f "bin/crude-spread-queue " >/dev/null; then
$root/bin/crude-spread-queue "$index" $logdir $PIPE &
fi
if ! pgrep -u $USER -f "bin/trace.sh " >/dev/null; then
......
......@@ -20,7 +20,7 @@ if test -s $root/etc/mp-address.txt; then
fi
cat $PIPE | $root/bin/qstream-gap-ana $optadr \
-index $INDEX -json $LOG -minloss 5 -head -win 50 -jitter 60 -
-index $INDEX -json $LOG -minloss 5 -head -win 50 -jitter 600 -
exit 0
......
#!/usr/bin/perl
# spread file input to parallel commands on a measurement point
# set pipe_size so that disk flush does not throttle back
# use non-blocking write and buffer internally
my $debug=1;
use Fcntl;
use File::Basename;
# use FileHandle;
# use IO::Handle;
use IO::File;
if ( $#ARGV < 1 ){
printf "Usage: $0 index output-directory [data-file]..\n";
exit 1;
}
my $index=shift;
my $lager=shift;
my $pipe_size=2**20; # 1MB
my $bin = dirname(__FILE__);
my $root = "$bin/..";
my @fds=();
my @out_buffer=(); # buffer output for blocking pipes
my $ext=`date +%T`;
chomp $ext;
my @cmds = (
"nice perl $bin/qstream-gap-ana -v -minloss 5 -head -win 50 -jitter 600 -names $root/etc/mp-address.txt -rtp 5" .
" -json $lager/gap-ana-$ext.json" .
" >> $lager/gap-ana-$ext.txt 2>> $lager/gap-log-$ext.txt",
"gzip >> $lager/crude-$ext.gz" );
# only done at central site - mostly for debug purposes :
# "nice perl $bin/qstream-gap-list >> $lager/gap-list-$ext.txt";
foreach $cmd ( @cmds ){
my $fh= open_pipe($cmd);
$fh->blocking(0) || die "Could not set non-blocking : $! : $cmd\n"; # set in non-blocking mode
push( @fds, $fh);
}
while(<>){
foreach $i ( 0 .. $#fds ){
push( @{$out_buffer[$i]}, $_); # buffer output
$pushed[$i]++;
}
foreach $i ( 0 .. $#fds ){
foreach my $buffer( @{$out_buffer[$i]} ){ # eat buffer
my $rv = syswrite( $fds[$i], $buffer );
# die "Could not write to pipe to $i rv $rv: $!\n";
if ( ! defined($rv) && $!{EAGAIN} ) { # output blocked
$blocked[$i]++;
last; # leave buffer and try next time a record comes in.
} elsif ($rv != length $buffer) { # incomplete write
my $lth = length $buffer;
die "Partial write $lth bytes to : $cmds[$i] : $!";
} else { # write OK
shift @{$out_buffer[$i]};
$ok[$i]++;
}
}
}
}
foreach $fd( @fds){
close $fd || die "could not close $fd : $!\n";
}
if ($debug){
for ( $i=0; $i<$#cmds; $i++){
printf "pushed %d %d \n", $i, $pushed[$i];
printf "OK %d %d \n", $i, $ok[$i];
printf "blocked %d %d \n", $i, $blocked[$i];
printf "cmd %s\n", $cmds[$i];
}
}
exit 0;
################################################################################
sub open_pipe{
my $cmd = shift;
print "$cmd\n" if $debug > 1;
my $fh = IO::File->new();
$fh->open( $fh, "|$cmd" or die "Could not open $cmd : $!");
return $fh;
}
# ignore pipe_size setting because internal buffering is OK
sub set_pipe_size{
my $fh=shift;
if ($debug and -p $fh ) {
printf "fh %d is pipe\n", $fh;
printf "pipe size was %d\n", fcntl($fh, Fcntl::F_GETPIPE_SZ, 0);
}
my $new = fcntl( $fh, Fcntl::F_SETPIPE_SZ, int($pipe_size))
|| die "### could not set pipe size $pipe_size for $cmd: $!\n";
warn sprintf "### set-pipe-size for $cmd failed: new size $new, got: %d",
fcntl($fh, Fcntl::F_GETPIPE_SZ, 0)
if $new < $pipe_size;
return $fh;
}
#!/usr/bin/perl
# spread file input to parallel commands on a measurement point
# using threads and queueing
my $debug=1;
use threads;
use Thread::Queue;
use FileHandle;
use File::Basename;
use Getopt::Long;
my (@opt_cmd, $opt_v, $opt_h);
my $usage="Usage\n$0 --cmd <command> --cmd ...
-v - print summary stats at end
$0 index output-directory [data-file]..\n
";
GetOptions('cmd=s'=> \@opt_cmd, 'v'=>\$opt_v, 'h'=>\$opt_h) or die "$usage : $!" ;
die $usage if $opt_h;
my @cmds=();
if ( $#opt_cmd >= 0 ){ # externally spec'd commands
@cmds=@opt_cmd;
} else { # builtin commands
if ( $#ARGV < 1 ){
printf "Usage: $0 index output-directory [data-file]..\n";
exit 1;
}
my $index=shift;
my $lager=shift;
my $pipe_size=2**20; # 1MB
my $bin = dirname(__FILE__);
my $root = "$bin/..";
my $ext=`date +%T`;
chomp $ext;
@cmds = (
"nice perl $bin/qstream-gap-ana -v -minloss 5 -head -win 50 -jitter 600 -names $root/etc/mp-address.txt -rtp 5" .
" -json $lager/gap-ana-$ext.json" .
" >> $lager/gap-ana-$ext.txt 2>> $lager/gap-log-$ext.txt",
"gzip >> $lager/crude-$ext.gz" );
# only done at central site - mostly for debug purposes :
# "nice perl $bin/qstream-gap-list >> $lager/gap-list-$ext.txt";
$opt_v=1;
}
$SIG{INT} = 'stop_threads';
my @queues=();
my @writers=();
foreach $i ( 0.. $#cmds ){
my $q= new Thread::Queue;
push( @queues, $q );
push( @writers, threads->create('writer', $cmds[$i], $q) );
}
while(<>){
foreach $q ( @queues){
$q->enqueue($_);
}
}
foreach $q ( @queues){
$q->enqueue("end of civilization");
while ( $q->pending()>0 ) { sleep 1; }
}
# stop_threads();
gather_results();
exit 0;
#================================================================================
sub writer{
sub slutt{
close $fh || die "Could not close filehandle : $!\n$cmd";
return $n;
}
$SIG{INT} = 'slutt';
$SIG{HUP} = 'slutt';
my ($cmd, $q)=@_;
my $n=0;
my $fh = IO::File->new();
$fh->open( "|$cmd" or die "Could not open cmd : $! :\n$cmd");
# $0=$cmd; - changes name of parent process in ps/pkill so don't use
while(1){
my $l=$q->dequeue;
last if $l =~ /^end of civilization$/;
my $lth = syswrite($fh, $l ) || die sprintf "could not write to fh %s : $! \n$cmd" ;
if ( $lth != length $l ) { die sprintf "Invalid length $lth : buffer is %d", length $l; }
$n++;
}
close $fh || die "Could not close filehandle : $!\n$cmd";
# return sprintf "%8d %s", $n, substr($cmd, 0, 20);
return $n;
}
sub stop_threads{
foreach $t (0..$#writers){
$writers[$t]->kill('SIGHUP');
}
}
sub gather_results(){
my @lines = sprintf "%8d input\n", $.;
foreach $t (0..$#writers){
push ( @lines, sprintf "%8d %s\n", $writers[$t]->join(), substr( $cmds[$t], 0, 80) );
}
print @lines if $opt_v;
}
......@@ -10,9 +10,7 @@ fi
if pgrep -u $USER -f "bin/crude " >/dev/null; then
pkill -u $USER -f "bin/crude " || echo crude kill failed
fi
pkill -u $USER -f "bin/crude-zip "
pkill -u $USER -f "cat $root/data/"
pkill -u $USER -f "bin/crude-ana "
pkill -u $USER -f "bin/crude-spread-ana "
if pgrep -u $USER -f "bin/trace.sh " >/dev/null; then
pkill -u $USER -f "bin/trace.sh " || echo trace kill failed
fi
......
......@@ -7,6 +7,6 @@ source $(dirname "${BASH_SOURCE[0]}")/../etc/start.cfg
cd $root/data
log=$date/mp-sync-events.log
rsync -e "ssh -4 -i $rsync_key -o ConnectTimeout=3 -o StrictHostKeyChecking=no" -t --update --bwlimit=100m --relative $date/*-events-*.log $rsync_url/$variant/$node_name/ >> $log 2>&1
rsync -e "ssh -4 -i $rsync_key -o ConnectTimeout=3 -o StrictHostKeyChecking=no" -t --update --inplace --bwlimit=100m --relative $date/{*-events-*.log,*traceroute*.gz} $rsync_url/$variant/$node_name/ >> $log 2>&1
This diff is collapsed.
#!/usr/bin/perl
# usage: name-of-open-fifo size-in-bytes
# http://unix.stackexchange.com/a/353761/119298
use strict;
use Fcntl;
my $fifo = shift @ARGV or die "usage: fifo size";
my $size = shift @ARGV or die "usage: fifo size";
my $verbose = shift @ARGV or 0;
open(FD, "<", $fifo) or die "### set-pipe-size cannot open $fifo : $!";
printf "old size %d\n", fcntl(\*FD, Fcntl::F_GETPIPE_SZ, 0) if $verbose > 0 ;
my $new=5;
my $new = fcntl(\*FD, Fcntl::F_SETPIPE_SZ, int($size));
printf "new size %d\n",fcntl(\*FD, Fcntl::F_GETPIPE_SZ, 0) if $verbose > 0 ;
die "### set-pipe-sized for $fifo failed: new size $new" if $new<$size;
......@@ -8,7 +8,7 @@ phour=-1
hour=$(date +%H)
while test $hour -ge $phour; do # do until midnight
date +%s | gzip -c >> $logpath/$date/tcptraceroute_$1.gz
date "+%s starttime %T" | gzip -c >> $logpath/$date/tcptraceroute_$1.gz
$tcptrace_bin -q 6 -n $1 2> /dev/null | gzip -c >> $logpath/$date/tcptraceroute_$1.gz
sleep $traceroute_interval
......
......@@ -20,6 +20,12 @@ else # get config from server
tar xf $tmp
if test $? -gt 0 ; then
cat $tmp
else
if test -f upgrade_software; then
cd $root
git pull
rm etc/upgrade_software
fi
fi
fi
......
......@@ -7,7 +7,7 @@ net.core.wmem_default = 67108864
net.core.wmem_max = 2056196096
# man 7 pipe
fs.pipe-max-size = 2147483648
fs.pipe-max-size = 2056196096
#udp buffer for crude
# man 7 udp
......
# udp input buffer size small nodes
net.ipv4.udp_mem = 512 768 1024
# set socket/pipe size to default 1M and max 2M
net.core.rmem_default = 1048576
net.core.rmem_max = 2097152
net.core.wmem_default = 1048576
net.core.wmem_max = 2097152
fs.pipe-max-size = 2097152
......@@ -78,9 +78,9 @@ my $query2 = '
my $aggr='
"aggs": {
"from": { "terms": {"field": "from.keyword" },
"from": { "terms": {"field": "from.keyword", "size": 100 },
"aggs": {
"to": { "terms": {"field": "to.keyword" },
"to": { "terms": {"field": "to.keyword", "size": 100 },
"aggs": {
"h_ddelay": { "stats": { "field": "h_ddelay" } },
"h_jit": { "stats": { "field": "h_jit" } },
......@@ -95,7 +95,8 @@ my $aggr='
my $search;
if ( $type eq "jitter" ){
$search = '{ "size":0, ' . $query_jit . ", " . $aggr . '}';
# $search = '{ "size":0, ' . $query_jit . ", " . $aggr . '}';
$search = '{ "size":0, ' . $query . ", " . $aggr . '}';
} else {
$search = '{ "size":10000, ' . $query . '}';
}
......@@ -120,7 +121,7 @@ my $url='http://admin:no+nz+br@localhost:9200/' . $index . '/_search?';
#}
my $cmd='curl -X POST -H "Content-Type: application/json" "' . $url . '" -d \'' . $search . '\' 2>/dev/null';
print "$cmd\n" if $debug > 0;
print "<p>$cmd\n" if $debug > 0;
print `$cmd`;
......
......@@ -155,7 +155,11 @@ if ( parm('start')){ # list active peers
print "client_dir $client_dir\n" if $debug;
chdir $client_dir;
my $client_config = `tar cf - rude.cfg trace.cfg parms.cfg`;
my $files="rude.cfg trace.cfg parms.cfg";
if ( -f "upgrade_software"){
$files.= " upgrade_software";
}
my $client_config = `tar cf - $files `;
print $q->header(
-type=>'application/octet-stream',
......
......@@ -203,30 +203,6 @@ var refresh_color="Aqua";
var no_coords= new LatLon(70.98584, -8.49243); // Jan Mayen
var points=[];
/* now file on web-server
var points_dragonlab=[
{id:"trondheim-mp", name:"Trondheim", url:"http://uninett.no", lat:63.41594176538965, lon: 10.394740104675295},
{id:"ntnumus", name:"NTNU", url:"http://ntnu.no", lat:63.43375, lon:10.40338},
{id:"auckland-mp", name:"Auckland", url:"http://auckland.ac.nz", lat:-36.8532151, lon:174.7651248},
{id:"zurich-mp", name:"Zurich", url:"http://switch.ch", lat:47.3732963, lon:8.5289406},
{id:"saopaulo-mp", name:"Sao Paulo", url:"http://www.rnp.br", lat:-23.5442517, lon:-46.7304324},
{id:"madrid-mp", name:"Madrid", url:"http://rediris.es/", lat:40.4478261, lon:-3.696328},
{id:"praha-mp", name:"Praha", url:"http://cesnet.cz", lat:50.101751, lon:14.3885883},
{id:"copenhagen-mp", name:"Copenhagen", url:"http://nordu.net", lat:55.77396, lon:12.4999963},
{id:"stockholm-mp", name:"Stockholm", url:"http://nordu.net", lat:59.3498092, lon:18.0684705},
{id:"sunet-mp", name:"Sunet", url:"http://sunet.se", lat:59.3419616, lon:18.0601304},
{id:"nte-mp", name:"NTE", url:"http://nte.no/index.php/en/", lat:64.010731, lon:11.4864445},
{id:"adelaide-mp", name:"Adelaide", url:"https://www.aarnet.edu.au/", lat:-35.103500, lon:138.533600},
{id:"azure-mp", name:"Azure US west", url:"https://azure.microsoft.com/en-us/regions/", lat:36.795038, lon:-119.403085},
{id:"azurene-mp", name:"Azure North Europe", url:"https://azure.microsoft.com/en-us/regions/", lat:53.269292, lon:-6.19990936},
{id:"amazon-mp", name:"Amazon US West gml", url:"http://aws.amazon.com", lat:45.829598, lon:-119.648004},
{id:"amazonuw2-mp", name:"Amazon US West", url:"http://aws.amazon.com", lat:45.839598, lon:-119.658004},
{id:"amazonff-mp", name:"Amazon Frankfuhrt", url:"http://aws.amazon.com", lat:50.16068, lon:8.68738},
{id:"amazonie-mp", name:"Amazon Dublin", url:"http://aws.amazon.com", lat:53.3307, lon:-6.25833},
{id:"amazonsth-mp", name:"Amazon Eskilstuna", url:"http://aws.amazon.com", lat:59.3487387, lon:16.7041466},
{id:"googleeu-mp", name:"Google Europe West", url:"http://cloud.google.com", lat:50.44273, lon:3.81924},
{id:"runar-mp", name:"Runar RPi", url:"http://www.uninett.no", lat:63.27204, lon:10.24971}
]; */
var empty_color="LightGray";
var prop_names_list = {gapsum: "down_ppm h_ddelay h_jit h_min_d big_gaps big_time small_gaps small_time".split(" "),
......@@ -240,10 +216,10 @@ var prop_desc= { down_ppm:"Unavailability (PPM)", h_ddelay:"Queue(ms)", h_jit:"
big_gaps:"Big gaps(#)", big_time:"Big gap time(s)", small_gaps:"Small gaps(#)", small_time:"Small gap time(s)",
};
var thresholds={
h_delay:[20,50],
h_min_d:[20,50],
h_ddelay:[10,50],
h_jit:[5,20],
h_delay:[10,50],
h_min_d:[10,50],
h_ddelay:[5,50],
h_jit:[2,20],
down_ppm:[100,1000],
h_slope_10:[0.1,0.2]
};
......@@ -415,7 +391,7 @@ function make_markers ( network, points, focus) {
}
marker.bindPopup("<b><a href=\"" + url + "\">" + "Home for " + id + "</a></b>"+html);
$("#" + id ).on('click', "a.trigger", function(e){
focus_links( e.id );
focus_links( e.id, 'flip' );
});
}
......@@ -514,8 +490,8 @@ function only_links_by_color(color){
}
function focus_links( node ){
if ( focus_node === node ){ // flip back
function focus_links( node, mode ){
if ( mode === 'flip' && focus_node === node ){ // flip back
focus_node = "";
links_on=true;
show_links();
......@@ -648,7 +624,7 @@ function update_legend(title, threshes){
function link_popup(link){
var dato = $("#datepicker").val();
//if (parms.net === "dragonlab") dato = dato.replace(/-/g, "");
var html = make_tooltip(link.from + ' til ' + link.to, link);
var html = make_tooltip(link.from + ' to ' + link.to, link);
var to_adr=link.to; // aggregations don't have *_adr.
if (link.to_adr)
......@@ -683,14 +659,28 @@ function link_popup(link){
function make_tooltip(title, link){
var tip="<table><caption><b>" + title + '</b></caption>';
if ( link.big_time){
tip+= '<tr><td>Sum downtime(s)<td>' + ( (link.big_time + link.small_time)/ 1000 ).toFixed(3)
+ '<tr><td>Sum gaps(#)<td>' + ( parseInt( link.small_gaps) + parseInt( link.big_gaps) );
tip+= '<tr><td>Sum downtime(s)<td align=right>' + ( link.big_time + link.small_time ).toFixed(1)
+ '<tr><td>Sum gaps(#)<td align=right>' + ( parseInt( link.small_gaps) + parseInt( link.big_gaps) );
}
for ( var prop of prop_names){
if ( prop in link ){
var desc=prop_desc[prop] != null ? prop_desc[prop] : "no description";
var val= link[prop] != null ? link[prop].toFixed(1) : 'no data';
// var val= ! isNaN(link[prop]) ? link[prop].toFixed(1) : 'no data';
var val;
if ( isNaN(link[prop]) ){
val='no data';
console.log('Invalid value of "' + title + '" prop:' + prop + ' val:' + link[prop] );
} else {
try {val = parseInt( link[prop] ).toFixed(1) ; }
catch(e) {
console.log('Invalid value of "' + title + '" prop:' + prop + ' val:' + link[prop] );
}
}
tip+= "<tr><td>" + desc + '<td align=right>' + val + "\n";
if ( prop === "down_ppm" && typeof link[prop] == 'number' ){
tip+= "<tr><td>" + "Unavail (sec/day)" + '<td align=right>' +
( link[prop] * 86400 / 10**6 ).toFixed(1) + "\n";
}
} else {
console.log('Invalid property ' + prop + ' of ' + title );
}
......@@ -703,9 +693,12 @@ function link_tooltip( title, link, prop){
if ( prop in link){
var val=link[prop];
var tip='<b>' + title + '</b>' + "<p>" + prop_desc[prop] + ": " ;
if ( typeof(val) !== "string" )
val = val.toFixed(1);
tip += val;
if ( typeof(val) !== "string" ){
tip += val.toFixed(1);
if ( prop === "down_ppm" && typeof link[prop] == 'number' ){
tip+= " ( " + ( val * 86400 / 10**6 ).toFixed(0) + " sec/day )";
}
}
} else {
tip += "undef";
}
......@@ -784,8 +777,8 @@ function gap_list( from, to){
for ( var hit of last_hits){
var gap = hit._source;
if ( gap.from === from && gap.to === to && gap.event_type === "gap"){
var d = new Date( Number(gap.timestamp_ms) );
var t = zero_fill( d.getDate() ) + " " + zero_fill( d.getHours() ) + ":" + zero_fill( d.getMinutes() ); // ddhh
var d = new Date( Number(gap.timestamp * 1000) );
var tid = zero_fill( d.getDate() ) + " " + zero_fill( d.getHours() ) + ":" + zero_fill( d.getMinutes() ); // ddhh
var syslog_url = 'https://iou2.uninett.no/es-syslog-lookup/es-syslog-lookup.cgi?syslogwindow=3600&epoch=1&redirect=1'
+ '&timestamp=' + gap.timestamp
......@@ -793,7 +786,7 @@ function gap_list( from, to){
var telemetry_url = 'https://telemetri.uninett.no/telemetri-lookup/telemetri-lookup.cgi?telemetrywindow=60'
+ '&redirect=1&timestamp=' + gap.timestamp
+ '&from=' + gap.from_adr + '&to=' + gap.to_adr + '&ip=1';
var telemetry_href = t;
var telemetry_href = tid;
var sec = ( gap.tloss / 1000 ).toFixed(1);
var syslog_href= sec;
var tail="";
......@@ -803,9 +796,10 @@ function gap_list( from, to){
tail = "<td><button class=knapp>" + syslog_href + "</button>"
+ "<td><button class=knapp>" + telemetry_href + "</button>";
}
var h_ddelay=gap.h_ddelay;
if ( typeof h_ddelay == "numeric") h_ddelay = h_ddelay.toFixed(1) ;
//html += "<tr><td>" + syslog_href + "<td>" + telemetry_href + "<td>" + gap.h_ddelay.toFixed(1) + "<td>" + "\n";
html += "<tr><td>" + t + "<td>" + sec + "<td>" + gap.h_ddelay.toFixed(1) + tail + "\n";
html += "<tr><td>" + tid + "<td>" + sec + "<td>" + h_ddelay + tail + "\n";
n++;
}
}
......@@ -912,7 +906,7 @@ function draw_links(hits, prop){
var color=get_color( link._source[prop], threshes);
if ( ! linkByName[ab]){ // draw line
var tooltip= link_tooltip( link._source.from + " til " + link._source.to , link._source, prop );
var tooltip= link_tooltip( link._source.from + " to " + link._source.to , link._source, prop );
var l=draw_link(abs, color, tooltip, link_popup(link._source) );
if (l){
......@@ -1043,7 +1037,7 @@ function taint_topology( topo, prop){
taint_link( linkByName[ab], color );
var popup=link_popup(link._source);
var tooltip= link_tooltip( link._source.from + " til " + link._source.to , link._source, prop );
var tooltip= link_tooltip( link._source.from + " to " + link._source.to , link._source, prop );
annotate_link( ab, linkByName[ab], tooltip, popup );
}
}
......@@ -1068,7 +1062,7 @@ function taint_links( hits, prop){
taint_link( linkByName[ab], color );
var popup=link_popup(link._source);
var tooltip= link_tooltip( link._source.from + " til " + link._source.to , link._source, prop );
var tooltip= link_tooltip( link._source.from + " to " + link._source.to , link._source, prop );
annotate_link( ab, linkByName[ab], tooltip, popup );
} else
console.log("Missing link for data for " + prop + " : " + ab);
......@@ -1084,6 +1078,9 @@ function taint_links( hits, prop){
}
}
refresh_links_by_color();