diff --git a/webapp/lib/traceroutecont.go b/webapp/lib/traceroutecont.go
index e964652f..a9048c1a 100644
--- a/webapp/lib/traceroutecont.go
+++ b/webapp/lib/traceroutecont.go
@@ -144,7 +144,7 @@ func GetTracerouteByTimeHandler(w http.ResponseWriter, r *http.Request, active b
returnError(w, err)
return
}
- // log.Debug("Requested data:", "tracerouteResults", tracerouteResults)
+ log.Debug("Requested data:", "tracerouteResults", tracerouteResults)
tracerouteJSON, err := json.Marshal(tracerouteResults)
if CheckError(err) {
@@ -156,7 +156,6 @@ func GetTracerouteByTimeHandler(w http.ResponseWriter, r *http.Request, active b
jsonBuf = append(jsonBuf, json...)
jsonBuf = append(jsonBuf, []byte(`}`)...)
- //log.Debug(string(jsonBuf))
// ensure % if any, is escaped correctly before writing to printf formatter
fmt.Fprintf(w, strings.Replace(string(jsonBuf), "%", "%%", -1))
}
diff --git a/webapp/tests/asviz/bwtester-d.json b/webapp/tests/asviz/bwtester-d.json
new file mode 100644
index 00000000..cbc37ce1
--- /dev/null
+++ b/webapp/tests/asviz/bwtester-d.json
@@ -0,0 +1,16 @@
+{
+ "graph": [
+ {
+ "Inserted": 1562683731800,
+ "ActualDuration": 5041,
+ "CSBandwidth": 80000,
+ "CSThroughput": 80000,
+ "SCBandwidth": 80000,
+ "SCThroughput": 80000,
+ "Error": "",
+ "Path": "Hops: [1-ff00:0:111 103>4094 1-ff00:0:112] Mtu: 1450",
+ "Log": "t=2019-07-09T10:48:46-0400 lvl=dbug msg=\"Path selection algorithm choice\" path=\"Hops: [1-ff00:0:111 103>4094 1-ff00:0:112] Mtu: 1450\" score=0.993\nt=2019-07-09T10:48:46-0400 lvl=dbug msg=\"Registered with dispatcher\" addr=\"1-ff00:0:111,[127.0.0.1]:30001 (UDP)\"\nClient DC \tNext Hop [127.0.0.74]:31070\tServer Host [127.0.0.2]:30101\nt=2019-07-09T10:48:46-0400 lvl=dbug msg=\"Registered with dispatcher\" addr=\"1-ff00:0:111,[127.0.0.1]:30002 (UDP)\"\n\nTest parameters:\nclientDCAddr -> serverDCAddr 1-ff00:0:111,[127.0.0.1]:30002 (UDP) -> 1-ff00:0:112,[127.0.0.2]:30101 (UDP)\nclient->server: 3 seconds, 1000 bytes, 30 packets\nserver->client: 3 seconds, 1000 bytes, 30 packets\n\nS->C results\nAttempted bandwidth: 80000 bps / 0.08 Mbps\nAchieved bandwidth: 80000 bps / 0.08 Mbps\nLoss rate: 0 %\nInterarrival time variance: 23ms, average interarrival time: 103ms\nInterarrival time min: 82ms, interarrival time max: 127ms\nWe need to sleep for 2 seconds before we can get the results\n\nC->S results\nAttempted bandwidth: 80000 bps / 0.08 Mbps\nAchieved bandwidth: 80000 bps / 0.08 Mbps\nLoss rate: 0 %\nInterarrival time variance: 23ms, average interarrival time: 103ms\nInterarrival time min: 82ms, interarrival time max: 126ms\n"
+ }
+ ],
+ "active": false
+}
diff --git a/webapp/tests/asviz/echo-d.json b/webapp/tests/asviz/echo-d.json
new file mode 100644
index 00000000..2204c9e7
--- /dev/null
+++ b/webapp/tests/asviz/echo-d.json
@@ -0,0 +1,15 @@
+{
+ "graph": [
+ {
+ "Inserted": 1562683883823,
+ "ActualDuration": 263,
+ "ResponseTime": 13.352,
+ "RunTime": 226.525,
+ "PktLoss": 0,
+ "CmdOutput": "Using path:\n Hops: [1-ff00:0:111 103>4094 1-ff00:0:112] Mtu: 1450\n104 bytes from 1-ff00:0:112,[127.0.0.2] scmp_seq=0 time=13.352ms\n\n--- 1-ff00:0:112,[[127.0.0.2]] statistics ---\n1 packets transmitted, 1 received, 0% packet loss, time 226.525ms\n",
+ "Path": "Hops: [1-ff00:0:111 103>4094 1-ff00:0:112] Mtu: 1450",
+ "Error": ""
+ }
+ ],
+ "active": false
+}
diff --git a/webapp/tests/asviz/traceroute-d.json b/webapp/tests/asviz/traceroute-d.json
new file mode 100644
index 00000000..d9fbd634
--- /dev/null
+++ b/webapp/tests/asviz/traceroute-d.json
@@ -0,0 +1,54 @@
+{
+ "graph": [
+ {
+ "Inserted": 1570192000394,
+ "ActualDuration": 35,
+ "TrHops": [
+ {
+ "HopIa": "1-ff00:0:111",
+ "HopAddr": "127.0.0.17",
+ "IntfID": 41,
+ "RespTime1": 0.558,
+ "RespTime2": 0.439,
+ "RespTime3": 0.41
+ },
+ {
+ "HopIa": "1-ff00:0:110",
+ "HopAddr": "127.0.0.9",
+ "IntfID": 1,
+ "RespTime1": 0.577,
+ "RespTime2": 0.597,
+ "RespTime3": 0.601
+ },
+ {
+ "HopIa": "1-ff00:0:110",
+ "HopAddr": "127.0.0.10",
+ "IntfID": 2,
+ "RespTime1": 0.797,
+ "RespTime2": 0.666,
+ "RespTime3": 0.771
+ },
+ {
+ "HopIa": "1-ff00:0:112",
+ "HopAddr": "127.0.0.25",
+ "IntfID": 1,
+ "RespTime1": 1.18,
+ "RespTime2": 1.922,
+ "RespTime3": 1.322
+ },
+ {
+ "HopIa": "1-ff00:0:112",
+ "HopAddr": "127.0.0.2",
+ "IntfID": -1,
+ "RespTime1": 1.107,
+ "RespTime2": 0.899,
+ "RespTime3": 0.835
+ }
+ ],
+ "CmdOutput": "Available paths to 1-ff00:0:112\n[ 0] Hops: [1-ff00:0:111 41>1 1-ff00:0:110 2>1 1-ff00:0:112] Mtu: 1280\nChoose path: Using path:\n Hops: [1-ff00:0:111 41>1 1-ff00:0:110 2>1 1-ff00:0:112] Mtu: 1280\n0 1-ff00:0:111,[127.0.0.17] IfID=41 558µs 439µs 410µs\n1 1-ff00:0:110,[127.0.0.9] IfID=1 577µs 597µs 601µs\n2 1-ff00:0:110,[127.0.0.10] IfID=2 797µs 666µs 771µs\n3 1-ff00:0:112,[127.0.0.25] IfID=1 1.18ms 1.922ms 1.322ms\n4 1-ff00:0:112,[127.0.0.2] 1.107ms 899µs 835µs\n",
+ "Error": "",
+ "Path": "Hops: [1-ff00:0:111 41>1 1-ff00:0:110 2>1 1-ff00:0:112] Mtu: 1280"
+ }
+ ],
+ "active": false
+}
diff --git a/webapp/web/static/css/style-viz.css b/webapp/web/static/css/style-viz.css
index 1baad7b0..1e8e3599 100644
--- a/webapp/web/static/css/style-viz.css
+++ b/webapp/web/static/css/style-viz.css
@@ -245,11 +245,21 @@ ul.tree li.open>ul {
ul.tree li a {
color: black;
text-decoration: none;
+}
+
+.path-text {
+ color: white;
padding-left: 5px; /* background shape */
padding-right: 5px; /* background shape */
border-radius: 10px; /* background shape */
}
+.latency-text {
+ color: purple;
+ position: absolute;
+ right: 0;
+}
+
ul.tree li a:before {
height: 1em;
padding: 0 .1em;
diff --git a/webapp/web/static/js/asviz.js b/webapp/web/static/js/asviz.js
index 3a082fb9..1f9c1eb7 100644
--- a/webapp/web/static/js/asviz.js
+++ b/webapp/web/static/js/asviz.js
@@ -68,7 +68,10 @@ function setPaths(type, idx, open) {
} else if (type == 'UP') {
addSegments(resUp, idx, num, colorSegUp, type);
} else if (type == 'PATH') {
- addPaths(resPath, idx, num, colorPaths, type);
+ // use min(best) for inter AS as it will be more accurate for length
+ var latencies = getPathLatencyLast(formatPathString(resPath, idx,
+ type));
+ addPaths(resPath, idx, num, colorPaths, type, latencies);
}
self.segType = type;
self.segNum = idx;
@@ -108,12 +111,26 @@ function formatPathString(res, idx, type) {
return path;
}
+function formatPathStringAll(res, type) {
+ var paths = "";
+ for (var i = 0; i < res.if_lists.length; i++) {
+ var path = formatPathString(res, i, type);
+ if (path != "") {
+ if (i > 0) {
+ paths += ",";
+ }
+ paths += formatPathString(res, i, type);
+ }
+ }
+ return paths;
+}
+
/*
* Adds D3 forwarding path links with arrows and a title to paths graph.
*/
-function addPaths(res, idx, num, color, type) {
+function addPaths(res, idx, num, color, type, latencies) {
if (graphPath) {
- drawPath(res, idx, color);
+ drawPath(res, idx, color, latencies);
if (res.if_lists[idx].expTime) {
drawTitle(type + ' ' + num, color, res.if_lists[idx].expTime);
} else {
diff --git a/webapp/web/static/js/tab-paths.js b/webapp/web/static/js/tab-paths.js
index 36c5711a..65fbe1c2 100644
--- a/webapp/web/static/js/tab-paths.js
+++ b/webapp/web/static/js/tab-paths.js
@@ -17,7 +17,7 @@ var iaLabels;
var iaLocations = [];
var iaGeoLoc;
var g = {};
-var jPathColors = [];
+var jPathsAvailable = {};
function setupDebug(src, dst) {
var src = $('#ia_cli').val();
@@ -40,14 +40,104 @@ function path_colors(n) {
}
function getPathColor(hops) {
- var idx = jPathColors.indexOf(hops + '');
- if (idx < 0) {
+ if (!jPathsAvailable[hops]) {
return cMissingPath;
} else {
- return path_colors(idx);
+ return jPathsAvailable[hops].color;
}
}
+/**
+ * Updates statistics.
+ */
+function updateStats(fStat, oldStat) {
+ var newStat = {}
+ newStat.Last = fStat;
+ newStat.Num = oldStat ? (oldStat.Num + 1) : 1;
+ newStat.Avg = oldStat ? (((oldStat.Avg * oldStat.Num) + fStat) / newStat.Num)
+ : fStat;
+ newStat.Min = oldStat ? Math.min(fStat, oldStat.Min) : fStat;
+ newStat.Max = oldStat ? Math.max(fStat, oldStat.Max) : fStat;
+ return newStat;
+}
+
+function getPathLatencyLast(hops) {
+ return getPathLatency(hops, false);
+}
+
+function getPathLatencyAvg(hops) {
+ return getPathLatency(hops, true);
+}
+
+function formatLatency(lat) {
+ // ignore 1ms negative margin of error
+ if (lat > -1 && lat < 0) {
+ return 0;
+ } else {
+ return parseFloat(lat).toFixed(0);
+ }
+}
+
+/**
+ * Returns array of interface and full path latency stats.
+ */
+function getPathLatency(hops, avg) {
+ var path = {};
+ if (jPathsAvailable[hops]) {
+ path = jPathsAvailable[hops];
+ }
+ var latencies = [];
+ for (var i = 0; i < path.interfaces.length; i++) {
+ if (path.interfaces[i].latency) {
+ latencies.push(avg ? path.interfaces[i].latency.Last
+ : path.interfaces[i].latency.Avg);
+ } else {
+ latencies.push(undefined);
+ }
+ }
+ if (path.latency) {
+ latencies.push(avg ? path.latency.Last : path.latency.Avg);
+ } else {
+ latencies.push(undefined);
+ }
+ return latencies;
+}
+
+function setEchoLatency(hops, latency) {
+ var path = {};
+ if (jPathsAvailable[hops]) {
+ path = jPathsAvailable[hops];
+ }
+ path.latency = updateStats(latency, path.latency);
+ jPathsAvailable[hops] = path;
+ return path;
+}
+
+function setTracerouteLatency(hops, interfaces) {
+ var path = {};
+ if (jPathsAvailable[hops]) {
+ path = jPathsAvailable[hops];
+ }
+ for (var i = 0; i < interfaces.length; i++) {
+ var if_ = interfaces[i];
+ if (i < interfaces.length - 1) {
+ path.interfaces[i].addr = if_.HopAddr;
+ path.interfaces[i].latency = updateStats(if_.RespTime1,
+ path.interfaces[i].latency);
+ path.interfaces[i].latency = updateStats(if_.RespTime2,
+ path.interfaces[i].latency);
+ path.interfaces[i].latency = updateStats(if_.RespTime3,
+ path.interfaces[i].latency);
+ } else {
+ path.latency = updateStats(if_.RespTime1, path.latency);
+ path.latency = updateStats(if_.RespTime2, path.latency);
+ path.latency = updateStats(if_.RespTime3, path.latency);
+ }
+ }
+ jPathsAvailable[hops] = path;
+ return path;
+}
+
function isConfigComplete(data, textStatus, jqXHR) {
console.log(JSON.stringify(data));
g['nodes_xml_url'] = data.nodes_xml_url;
@@ -390,11 +480,18 @@ function get_path_html(paths, csegs, usegs, dsegs, show_segs) {
if_ = ent.Path.Interfaces;
var hops = if_.length / 2;
- var style = "style='background-color: "
- + getPathColor(formatPathJson(paths, parseInt(p))) + "; '";
- html += "
PATH " + (parseInt(p) + 1)
- + " " + hops + "";
+ var pathStr = formatPathJson(paths, parseInt(p));
+ var latencies = getPathLatencyLast(pathStr);
+ var latencyPath = latencies[latencies.length - 1];
+ var latPathStr = latencyPath ? formatLatency(latencyPath) : '';
+ var aStyle = "style='background-color:" + getPathColor(pathStr) + ";'";
+ html += "PATH "
+ + (parseInt(p) + 1) + " " + hops
+ + " "
+ + latPathStr + "";
exp.setUTCSeconds(ent.Path.ExpTime);
html += "";
}
@@ -676,6 +775,39 @@ function get_nonseg_links(paths, lType) {
return hops;
}
+function addAvailablePaths(paths) {
+ Object.keys(jPathsAvailable).forEach(function(key) {
+ jPathsAvailable[key].listIdx = undefined; // reset
+ });
+ if (!paths) {
+ return;
+ }
+ for (var idx = 0; idx < paths.length; idx++) {
+ var hops = formatPathJson(paths, idx, 'PATH');
+ if (!jPathsAvailable[hops]) {
+ jPathsAvailable[hops] = {};
+ }
+ // update path preserving old values
+ var path = jPathsAvailable[hops];
+ var pathLen = Object.keys(jPathsAvailable).length;
+ path.interfaces = [];
+ var ifs = paths[idx].Entry.Path.Interfaces;
+ for (var i = 0; i < ifs.length; i++) {
+ var if_ = {};
+ if_.ifid = ifs[i].IfID;
+ if_.isdas = iaRaw2Read(ifs[i].RawIsdas);
+ path.interfaces.push(if_);
+ }
+ path.expTime = paths[idx].Entry.Path.ExpTime;
+ path.mtu = paths[idx].Entry.Path.Mtu;
+ if (!path.color) {
+ path.color = path_colors(pathLen - 1);
+ }
+ path.listIdx = idx;
+ jPathsAvailable[hops] = path;
+ }
+}
+
function requestPaths() {
// make sure to get path topo after IAs are loaded
var form_data = $('#command-form').serializeArray();
@@ -699,12 +831,7 @@ function requestPaths() {
resDown = resSegs.down_segments;
// store incoming paths
- for (var idx = 0; idx < resPath.if_lists.length; idx++) {
- var hops = formatPathString(resPath, idx, 'PATH');
- if (!jPathColors.includes(hops)) {
- jPathColors.push(hops);
- }
- }
+ addAvailablePaths(data.paths);
jTopo = get_json_path_links(resPath, resCore, resUp, resDown);
$('#path-info').html(
@@ -717,7 +844,18 @@ function requestPaths() {
// setup path config based on defaults loaded
setupDebug();
- ajaxConfig();
+
+ // request config/paths if none exists
+ if (!iaLabels || !iaGeoLoc || !iaLocations) {
+ ajaxConfig(); // ajaxConfig => loadPathData
+ } else {
+ loadPathData(g.src, g.dst);
+ }
+
+ // auto survey latency if none exists
+ if (!$('#path-lat-0').text()) {
+ surveyEchoBackground();
+ }
// path info label switches
$('#switch_as_names').change(function() {
diff --git a/webapp/web/static/js/tab-topocola.js b/webapp/web/static/js/tab-topocola.js
index bb40f4a0..f097ea94 100644
--- a/webapp/web/static/js/tab-topocola.js
+++ b/webapp/web/static/js/tab-topocola.js
@@ -327,6 +327,19 @@ function update() {
});
markerPath.exit().remove();
+ svgPath.selectAll("text.latency").remove();
+ var markerText = pathsg.selectAll("path.latency").data(markerLinks)
+ markerText.enter().append("text").attr("dy", ".35em").attr("text-anchor",
+ "middle").style("font-size", "12px").style("fill", "purple").attr(
+ "class", function(d) {
+ return "latency " + d.type;
+ }).attr("id", function(d) {
+ return d.id;
+ }).text(function(d) {
+ return d.latency ? formatLatency(d.latency) : '';
+ });
+ markerText.exit().remove();
+
var node = circlesg.selectAll(".node").data(realGraphNodes, function(d) {
return d.name;
})
@@ -399,6 +412,13 @@ function update() {
path.attr("d", linkStraight);
markerPath.attr("d", linkArc);
node.attr("transform", nodeTransform);
+
+ markerText.attr("x", function(d) {
+ return ((d.source.x + d.target.x) / 2);
+ }).attr("y", function(d) {
+ return ((d.source.y + d.target.y) / 2);
+ });
+
});
colaPath.start(50, 100, 200);
@@ -591,7 +611,7 @@ function addFixedLabel(label, x, y, lastLabel) {
/*
* Post-rendering method to draw path arcs for the given path and color.
*/
-function drawPath(res, path, color) {
+function drawPath(res, path, color, lats) {
// get the index of the routes to render
var routes = [];
if (path < 0) {
@@ -624,21 +644,47 @@ function drawPath(res, path, color) {
graphPath.links = graphPath.links.filter(function(link) {
return !link.path;
});
+ console.debug(lats)
+ var fullLat = fullPathLatencies(lats);
for (var i = 0; i < path_ids.length - 1; i++) {
// prevent src == dst links from being formed
if (path_ids[i] != path_ids[i + 1]) {
- graphPath.links.push({
+ var linkLat = undefined;
+ if (fullLat) {
+ // report latency difference between inter-AS links
+ linkLat = lats ? (lats[i + 1] - lats[i]) : undefined;
+ }
+ var link = {
"color" : color,
"path" : true,
"source" : graphPath["ids"][path_ids[i]],
"target" : graphPath["ids"][path_ids[i + 1]],
- "type" : "PARENT"
- });
+ "type" : "PARENT",
+ "latency" : linkLat,
+ "id" : "path-lat-diff-" + path + "-" + i, // TODO
+ };
+ graphPath.links.push(link);
+ console.debug(link)
}
}
update();
}
+/**
+ * Interrogate latencies for missing values.
+ */
+function fullPathLatencies(lats) {
+ if (!lats) {
+ return false;
+ }
+ for (var i = 0; i < lats.length; i++) {
+ if (!lats[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
/*
* Removes all path arcs from the graph.
*/
diff --git a/webapp/web/static/js/webapp.js b/webapp/web/static/js/webapp.js
index f0f25551..b84bec8b 100644
--- a/webapp/web/static/js/webapp.js
+++ b/webapp/web/static/js/webapp.js
@@ -127,7 +127,7 @@ function initBwGraphs() {
// setup interval to manage smooth ticking
lastTime = (new Date()).getTime() - (ticks * tickMs) + xLeftTrimMs;
manageTickData();
- manageTestData();
+ // avoid manageTestData on startup before tests
}
function showOnlyConsoleGraphs(activeApp) {
@@ -386,26 +386,34 @@ function manageTestData() {
} else if (activeApp == "traceroute") {
requestTraceRouteByTime(form_data);
}
- lastTimeBwDb = now;
+ // lastTimeBwDb = now;
}, dataIntervalMs);
}
+function manageTestEnd(d, appSel, since) {
+ if (d.active != null) {
+ if (d.active) {
+ enableTestControls(false);
+ lockTab(appSel);
+ failContinuousOff();
+ } else {
+ enableTestControls(true);
+ releaseTabs();
+ if (d.graph != null) {
+ clearInterval(intervalGraphData);
+ // } else {
+ // lastTimeBwDb = since;
+ }
+ }
+ }
+}
+
function requestBwTestByTime(form_data) {
$.post("/getbwbytime", form_data, function(json) {
var d = JSON.parse(json);
console.info('resp:', JSON.stringify(d));
if (d != null) {
- if (d.active != null) {
- if (d.active) {
- enableTestControls(false);
- lockTab("bwtester");
- failContinuousOff();
- } else {
- enableTestControls(true);
- releaseTabs();
- clearInterval(intervalGraphData);
- }
- }
+ manageTestEnd(d, "bwtester", form_data.since);
if (d.graph != null) {
// write data on graph
for (var i = 0; i < d.graph.length; i++) {
@@ -432,6 +440,7 @@ function requestBwTestByTime(form_data) {
console.info(JSON.stringify(data));
console.info('continuous bwtester', 'duration:',
d.graph[i].ActualDuration, 'ms');
+ lastTimeBwDb = d.graph[i].Inserted;
// use the time the test began
var time = d.graph[i].Inserted - d.graph[i].ActualDuration;
updateBwGraph(data, time)
@@ -446,17 +455,7 @@ function requestEchoByTime(form_data) {
var d = JSON.parse(json);
console.info('resp:', JSON.stringify(d));
if (d != null) {
- if (d.active != null) {
- if (d.active) {
- enableTestControls(false);
- lockTab("echo");
- failContinuousOff();
- } else {
- enableTestControls(true);
- releaseTabs();
- clearInterval(intervalGraphData);
- }
- }
+ manageTestEnd(d, "echo", form_data.since);
if (d.graph != null) {
// write data on graph
for (var i = 0; i < d.graph.length; i++) {
@@ -477,11 +476,22 @@ function requestEchoByTime(form_data) {
data.runTime = d.graph[i].ActualDuration;
}
console.info(JSON.stringify(data));
- console.info('continous echo', 'duration:',
+ console.info('continuous echo', 'duration:',
d.graph[i].ActualDuration, 'ms');
+ lastTimeBwDb = d.graph[i].Inserted;
// use the time the test began
var time = d.graph[i].Inserted - d.graph[i].ActualDuration;
updatePingGraph(chartSE, data, time)
+
+ // update latency stats, when valid, use average
+ if (d.graph[i].ResponseTime > 0) {
+ var path = setEchoLatency(d.graph[i].Path
+ .match("\\[.*]"), d.graph[i].ResponseTime);
+ if (path.latency) {
+ var latStr = formatLatency(path.latency.Avg);
+ $('#path-lat-' + path.listIdx).html(latStr);
+ }
+ }
}
}
}
@@ -493,17 +503,7 @@ function requestTraceRouteByTime(form_data) {
var d = JSON.parse(json);
console.info('resp:', JSON.stringify(d));
if (d != null) {
- if (d.active != null) {
- $('#switch_cont').prop("checked", d.active);
- if (d.active) {
- enableTestControls(false);
- lockTab("traceroute");
- } else {
- enableTestControls(true);
- releaseTabs();
- clearInterval(intervalGraphData);
- }
- }
+ manageTestEnd(d, "traceroute", form_data.since);
if (d.graph != null) {
// write data on graph
for (var i = 0; i < d.graph.length; i++) {
@@ -513,10 +513,117 @@ function requestTraceRouteByTime(form_data) {
handleEndCmdDisplay(d.graph[i].CmdOutput);
}
- console.info('continous traceroute', 'duration:',
+ console.info('continuous traceroute', 'duration:',
d.graph[i].ActualDuration, 'ms');
+ lastTimeBwDb = d.graph[i].Inserted;
+ // use the time the test began
+ var time = d.graph[i].Inserted - d.graph[i].ActualDuration;
// TODO (mwfarb): implement traceroute graph
+
+ // update latency stats, when valid, use average
+ var trhops = ((d.graph[i].Path.split('>').length) * 2) - 1;
+ if (!d.graph[i].TrHops
+ || d.graph[i].TrHops.length != trhops) {
+ console.error("Did not receive expected " + trhops
+ + " traceroute hops.");
+ continue;
+ }
+ var path = setTracerouteLatency(d.graph[i].Path
+ .match("\\[.*]"), d.graph[i].TrHops);
+ console.debug(path)
+ for (var i = 0; i < path.interfaces.length; i++) {
+ var if_ = path.interfaces[i];
+ var if_prev = path.interfaces[i - 1];
+ if (if_.latency) {
+ var latStr = formatLatency(if_.latency.Avg);
+ console.debug("writing " + latStr + ' #path-lat-'
+ + path.listIdx + '-' + i)
+ $('#path-lat-' + path.listIdx + '-' + i).html(
+ latStr);
+ }
+ // update each inter-AS link with difference
+ if (i % 2 == 1) {
+ var diff = if_.latency.Avg - if_prev.latency.Avg;
+ var latStr = formatLatency(diff);
+ console.debug("writing " + latStr
+ + ' #path-lat-diff-' + path.listIdx + '-'
+ + (i - 1))
+ $('#path-lat-diff-' + path.listIdx + '-' + (i - 1))
+ .html(latStr);
+ }
+ }
+ if (path.latency) {
+ var latStr = formatLatency(path.latency.Avg);
+ console.debug("writing " + latStr + ' #path-lat-'
+ + path.listIdx)
+ $('#path-lat-' + path.listIdx).html(latStr);
+ }
+ }
+ }
+ }
+ });
+}
+
+var backgroundEcho;
+function surveyEchoBackground() {
+ var blastTimeBwDb = (new Date()).getTime();
+ var form_data = $('#command-form').serializeArray();
+ var interval = 0.1;
+ var count = 3;
+ form_data.push({
+ name : "apps",
+ value : "echo"
+ }, {
+ name : "count",
+ value : count
+ }, {
+ name : "continuous",
+ value : true
+ }, {
+ name : "pathStr",
+ value : formatPathStringAll(resPath, 'PATH')
+ }, {
+ name : "interval",
+ value : interval
+ });
+ // start background echo
+ console.info('req:', JSON.stringify(form_data));
+ $.post('/command', form_data, function(resp, status, jqXHR) {
+ console.info('resp:', resp);
+ }).fail(function(error) {
+ showError(error.responseJSON);
+ });
+
+ clearInterval(backgroundEcho);
+ backgroundEcho = setInterval(function() {
+ reportEchoBackground({
+ since : blastTimeBwDb
+ });
+ blastTimeBwDb = (new Date()).getTime();
+ }, dataIntervalMs);
+}
+
+function reportEchoBackground(form_data) {
+ $.post("/getechobytime", form_data, function(json) {
+ var d = JSON.parse(json);
+ console.info('resp:', JSON.stringify(d));
+ if (d != null) {
+ if (!d.active) {
+ // end background echo
+ clearInterval(backgroundEcho);
+ }
+ if (d.graph != null) {
+ for (var i = 0; i < d.graph.length; i++) {
+ // update latency stats, when valid, use average
+ if (d.graph[i].ResponseTime > 0) {
+ var path = setEchoLatency(d.graph[i].Path
+ .match("\\[.*]"), d.graph[i].ResponseTime);
+ if (path.latency) {
+ var latStr = formatLatency(path.latency.Avg);
+ $('#path-lat-' + path.listIdx).html(latStr);
+ }
+ }
}
}
}
@@ -654,13 +761,19 @@ function command(continuous) {
name : "continuous",
value : continuous
});
- if (self.segType == 'PATH') { // only full paths allowed
+ if (self.segType == 'PATH') { // single path open
form_data.push({
name : "pathStr",
value : formatPathString(resPath, self.segNum, self.segType)
});
+ } else if (continuous) { // all paths in survey
+ form_data.push({
+ name : "pathStr",
+ value : formatPathStringAll(resPath, 'PATH')
+ });
}
}
+
if (activeApp == "bwtester") {
form_data.push({
name : "interval",
@@ -803,7 +916,7 @@ function handleContResponse(resp, continuous, startTime) {
clearInterval(intervalGraphData);
}
if (!continuous) {
- manageTestData();
+ manageTestData(startTime);
}
}
diff --git a/webapp/web/template/index.html b/webapp/web/template/index.html
index ca095757..eeab871d 100644
--- a/webapp/web/template/index.html
+++ b/webapp/web/template/index.html
@@ -411,11 +411,17 @@ SCIONLab Apps
-
- Available Paths hops
-
+
+
+ Available Paths Hops Latency
+
+
diff --git a/webapp/webapp.go b/webapp/webapp.go
index bc89e398..f5108ba7 100644
--- a/webapp/webapp.go
+++ b/webapp/webapp.go
@@ -213,7 +213,7 @@ func initLocalIaOptions() {
}
func initServeHandlers() {
- serveExact("/favicon.ico", "./favicon.ico")
+ serveExact("/favicon.ico", path.Join(options.StaticRoot, "favicon.ico"))
http.HandleFunc("/", mainHandler)
http.HandleFunc("/about", aboutHandler)
http.HandleFunc("/apps", appsHandler)
@@ -394,10 +394,6 @@ func parseCmdItem2Cmd(dOrinial model.CmdItem, appSel string, pathStr string) []s
bwSC := fmt.Sprintf("-sc=%d,%d,%d,%dbps", d.SCDuration/1000, d.SCPktSize,
d.SCPackets, d.SCBandwidth)
command = append(command, bwCS, bwSC)
- if len(pathStr) > 0 {
- // if path choice provided, use interactive mode
- command = append(command, "-i")
- }
}
isdCli, _ = strconv.Atoi(strings.Split(d.CIa, "-")[0])
@@ -414,10 +410,6 @@ func parseCmdItem2Cmd(dOrinial model.CmdItem, appSel string, pathStr string) []s
optTimeout := fmt.Sprintf("-timeout=%fs", d.Timeout)
optInterval := fmt.Sprintf("-interval=%fs", d.Interval)
command = append(command, installpath, optApp, optRemote, optLocal, optCount, optTimeout, optInterval)
- if len(pathStr) > 0 {
- // if path choice provided, use interactive mode
- command = append(command, "-i")
- }
isdCli, _ = strconv.Atoi(strings.Split(d.CIa, "-")[0])
case "traceroute":
@@ -431,13 +423,13 @@ func parseCmdItem2Cmd(dOrinial model.CmdItem, appSel string, pathStr string) []s
optRemote := fmt.Sprintf("-remote=%s,[%s]", d.SIa, d.SAddr)
optTimeout := fmt.Sprintf("-timeout=%fs", d.Timeout)
command = append(command, installpath, optApp, optRemote, optLocal, optTimeout)
- if len(pathStr) > 0 {
- // if path choice provided, use interactive mode
- command = append(command, "-i")
- }
isdCli, _ = strconv.Atoi(strings.Split(d.CIa, "-")[0])
}
+ if len(pathStr) > 0 {
+ // if path choice provided, use interactive mode
+ command = append(command, "-i")
+ }
if isdCli < 16 {
// -sciondFromIA is better for localhost testing, with test isds
command = append(command, "-sciondFromIA")
@@ -452,6 +444,7 @@ func commandHandler(w http.ResponseWriter, r *http.Request) {
appSel := r.PostFormValue("apps")
continuous, _ := strconv.ParseBool(r.PostFormValue("continuous"))
interval, _ := strconv.Atoi(r.PostFormValue("interval"))
+ count, _ := strconv.Atoi(r.PostFormValue("count"))
if appSel == "" {
fmt.Fprintf(w, "Unknown SCION client app. Is one selected?")
return
@@ -476,7 +469,7 @@ func commandHandler(w http.ResponseWriter, r *http.Request) {
if !contCmdActive {
// run continuous goroutine
contCmdActive = true
- go continuousCmd(t)
+ go continuousCmd(t, count)
} else {
// continuous goroutine running?
if continuous {
@@ -489,13 +482,16 @@ func commandHandler(w http.ResponseWriter, r *http.Request) {
}
} else {
// single run
- executeCommand(w, r)
+ pathStr := r.PostFormValue("pathStr")
+ executeCommand(w, r, pathStr)
}
}
// Could either be bwtest, echo or traceroute
-func continuousCmd(t contCmd) {
+func continuousCmd(t contCmd, count int) {
log.Info(fmt.Sprintf("Starting continuous %s...", t))
+ pathIdx := 0
+ attempts := 0
defer func() {
log.Info(fmt.Sprintf("Ending continuous %s...", t))
}()
@@ -506,9 +502,16 @@ func continuousCmd(t contCmd) {
contCmdActive = false
break
}
+
r := contCmdRequest
+ thePath := ""
+ pathStr := r.PostFormValue("pathStr")
+ paths := strings.Split(pathStr, ",")
+ if pathIdx >= 0 && pathIdx < len(paths) {
+ thePath = paths[pathIdx]
+ }
start := time.Now()
- executeCommand(nil, r)
+ executeCommand(nil, r, thePath)
// block on cmd output finish
<-contCmdChanDone
@@ -522,14 +525,23 @@ func continuousCmd(t contCmd) {
}
log.Info(fmt.Sprintf("Test took %d ms, sleeping for remaining interval: %d ms",
elapsed.Nanoseconds()/1e6, remaining.Nanoseconds()/1e6))
+ if pathIdx >= (len(paths) - 1) { // iterate all paths given
+ pathIdx = 0
+ attempts++
+ } else {
+ pathIdx++
+ }
+ if attempts >= count {
+ log.Info("Continuous count reached ", "count", count, "attempts", attempts)
+ contCmdActive = false
+ }
time.Sleep(remaining)
}
}
-func executeCommand(w http.ResponseWriter, r *http.Request) {
+func executeCommand(w http.ResponseWriter, r *http.Request, pathStr string) {
r.ParseForm()
appSel := r.PostFormValue("apps")
- pathStr := r.PostFormValue("pathStr")
d, addlOpt := parseRequest2CmdItem(r, appSel)
command := parseCmdItem2Cmd(d, appSel, pathStr)
if addlOpt != "" {
@@ -707,7 +719,7 @@ func writeCmdOutput(w http.ResponseWriter, reader io.Reader, stdin io.WriteClose
}
if appSel == "traceroute" {
- // parse traceroute data/error
+ // parse scmp traceroute data/error
d, ok := d.(model.TracerouteItem)
if !ok {
log.Error("Parsing error, CmdItem category doesn't match its name")