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")