Thomas Vachuska

GUI -- Fixed intent perf GUI styling.

Change-Id: I552d3a50f7f4dd5bb1df7115c15eb6a04f538378
......@@ -37,10 +37,8 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static org.onlab.util.Tools.groupedThreads;
import static org.onlab.util.SharedExecutors.getPoolThreadExecutor;
import static org.slf4j.LoggerFactory.getLogger;
/**
......@@ -78,18 +76,13 @@ public class IntentPerfCollector {
private Map<NodeId, Integer> nodeToIndex;
private NodeId nodeId;
private ExecutorService messageHandlingExecutor;
@Activate
public void activate() {
nodeId = clusterService.getLocalNode().id();
// TODO: replace with shared executor
messageHandlingExecutor = Executors.newSingleThreadExecutor(
groupedThreads("onos/perf", "message-handler"));
communicationService.addSubscriber(SAMPLE, new InternalSampleCollector(),
messageHandlingExecutor);
getPoolThreadExecutor());
nodes = clusterService.getNodes().toArray(new ControllerNode[]{});
Arrays.sort(nodes, (a, b) -> a.id().toString().compareTo(b.id().toString()));
......@@ -99,14 +92,13 @@ public class IntentPerfCollector {
nodeToIndex.put(nodes[i].id(), i);
}
ui.setHeaders(getSampleHeaders());
clearSamples();
ui.setCollector(this);
log.info("Started");
}
@Deactivate
public void deactivate() {
messageHandlingExecutor.shutdown();
communicationService.removeSubscriber(SAMPLE);
log.info("Stopped");
}
......
......@@ -156,7 +156,7 @@ public class IntentPerfInstaller {
private ExecutorService workers;
private ApplicationId appId;
private Listener listener;
private boolean stopped;
private boolean stopped = true;
private Timer reportTimer;
......@@ -247,13 +247,18 @@ public class IntentPerfInstaller {
}
public void start() {
communicationService.broadcast(new ClusterMessage(nodeId, CONTROL, START.getBytes()));
startTestRun();
if (stopped) {
stopped = false;
communicationService.broadcast(new ClusterMessage(nodeId, CONTROL, START.getBytes()));
startTestRun();
}
}
public void stop() {
communicationService.broadcast(new ClusterMessage(nodeId, CONTROL, STOP.getBytes()));
stopTestRun();
if (!stopped) {
communicationService.broadcast(new ClusterMessage(nodeId, CONTROL, STOP.getBytes()));
stopTestRun();
}
}
private void logConfig(String prefix) {
......@@ -282,7 +287,6 @@ public class IntentPerfInstaller {
}
private void stopTestRun() {
stopped = true;
if (reporterTask != null) {
reporterTask.cancel();
reporterTask = null;
......@@ -293,6 +297,11 @@ public class IntentPerfInstaller {
} catch (InterruptedException e) {
log.warn("Failed to stop worker", e);
}
sampleCollector.recordSample(0, 0);
sampleCollector.recordSample(0, 0);
stopped = true;
log.info("Stopped test run");
}
......
......@@ -36,9 +36,7 @@ import org.onosproject.ui.UiView;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.TimerTask;
import static java.util.Collections.synchronizedSet;
......@@ -58,25 +56,11 @@ public class IntentPerfUi {
private UiExtension uiExtension = new UiExtension(views, this::newHandlers,
getClass().getClassLoader());
private List<String> headers = ImmutableList.of("One", "Two", "Three", "Four", "Five");
private Random random = new Random();
private TimerTask task;
private IntentPerfCollector collector;
@Activate
protected void activate() {
uiExtensionService.register(uiExtension);
// task = new TimerTask() {
// @Override
// public void run() {
// Sample sample = new Sample(System.currentTimeMillis(), headers.size());
// for (int i = 0; i < headers.size(); i++) {
// sample.data[i] = 25_000 + random.nextInt(20_000) - 5_000;
// }
// reportSample(sample);
// }
// };
// SharedExecutors.getTimer().scheduleAtFixedRate(task, 1000, 1000);
}
@Deactivate
......@@ -96,12 +80,12 @@ public class IntentPerfUi {
}
/**
* Sets the headers for the subsequently reported samples.
* Binds the sample collector.
*
* @param headers list of headers for future samples
* @param collector list of headers for future samples
*/
public void setHeaders(List<String> headers) {
this.headers = headers;
public void setCollector(IntentPerfCollector collector) {
this.collector = collector;
}
// Creates and returns session specific message handler.
......@@ -122,20 +106,8 @@ public class IntentPerfUi {
public void process(ObjectNode message) {
streamingEnabled = message.path("event").asText("unknown").equals("intentPerfStart");
if (streamingEnabled) {
sendHeaders();
}
}
private void sendHeaders() {
ArrayNode an = mapper.createArrayNode();
for (String header : headers) {
an.add(header);
sendInitData();
}
ObjectNode sn = mapper.createObjectNode();
sn.set("headers", an);
connection().sendMessage("intentPerfHeaders", 0, sn);
}
@Override
......@@ -152,18 +124,34 @@ public class IntentPerfUi {
private void send(Sample sample) {
if (streamingEnabled) {
ArrayNode an = mapper.createArrayNode();
for (double d : sample.data) {
an.add(d);
}
connection().sendMessage("intentPerfSample", 0, sampleNode(sample));
}
}
private void sendInitData() {
ObjectNode rootNode = mapper.createObjectNode();
ArrayNode an = mapper.createArrayNode();
ArrayNode sn = mapper.createArrayNode();
rootNode.set("headers", an);
rootNode.set("samples", sn);
ObjectNode sn = mapper.createObjectNode();
sn.put("time", sample.time);
sn.set("data", an);
collector.getSampleHeaders().forEach(an::add);
collector.getSamples().forEach(s -> sn.add(sampleNode(s)));
connection().sendMessage("intentPerfInit", 0, rootNode);
}
connection().sendMessage("intentPerfSample", 0, sn);
private ObjectNode sampleNode(Sample sample) {
ObjectNode sampleNode = mapper.createObjectNode();
ArrayNode an = mapper.createArrayNode();
sampleNode.put("time", sample.time);
sampleNode.set("data", an);
for (double d : sample.data) {
an.add(d);
}
return sampleNode;
}
}
}
......
date,value,node
00:55:15,68.38,node1
00:55:15,55.61,node2
00:55:15,74.00,node3
00:55:30,74.20,node1
00:55:30,77.60,node2
00:55:30,74.80,node3
00:55:45,74.60,node1
00:55:45,72.80,node2
00:55:45,77.00,node3
00:56:00,73.60,node1
00:56:00,75.00,node2
00:56:00,76.98,node3
00:56:15,75.82,node1
00:56:15,75.40,node2
00:56:15,76.00,node3
00:56:30,75.60,node1
00:56:30,74.59,node2
00:56:30,74.01,node3
\ No newline at end of file
key,value,date
Group1,37,00:23:00
Group2,12,00:23:00
Group3,46,00:23:00
Group1,32,00:23:05
Group2,19,00:23:05
Group3,42,00:23:05
Group1,45,00:23:10
Group2,16,00:23:10
Group3,44,00:23:10
Group1,24,00:23:15
Group2,52,00:23:15
Group3,64,00:23:15
Group1,34,00:23:20
Group2,62,00:23:20
Group3,74,00:23:20
Group1,34,00:23:25
Group2,62,00:23:25
Group3,74,00:23:25
\ No newline at end of file
......@@ -22,9 +22,8 @@ svg {
font: 12px sans-serif;
}
.line {
.line,.lineTotal {
fill: none;
stroke: #000;
stroke-width: 2px;
}
......@@ -37,16 +36,20 @@ svg {
.light .axis path,
.light .axis line,
.light .lineTotal {
stroke: #333;
}
.light .axis text {
stroke: #999;
fill: #333;
}
.dark .axis path,
.dark .axis line,
.dark .axis text {
.dark .lineTotal {
stroke: #eee;
}
.axis text {
stroke-width: 0.3;
}
\ No newline at end of file
.dark .axis text {
fill: #eee;
}
......
......@@ -31,7 +31,7 @@
// ==========================
function createGraph(h) {
function createGraph(h, samples) {
var stopped = false,
n = 243,
duration = 750,
......@@ -75,7 +75,27 @@
headers.forEach(function (h, li) {
// Prime the data to match the headers and zero it out.
data[li] = d3.range(n).map(function() { return 0 });
theSample[li] = 0;
if (li < headers.length - 1) {
samples.forEach(function (s, i) {
var di = dataIndex(s.time);
if (di >= 0) {
data[li][di] = s.data[li];
}
});
data[li].forEach(function (d, i) {
if (!d && i > 0) {
data[li][i] = data[li][i - 1];
}
});
} else {
data[li].forEach(function (t, i) {
for (var si = 0; si < headers.length - 1; si++) {
data[li][i] = data[si][i];
}
});
}
// Create the lines
lines[li] = d3.svg.line()
......@@ -88,15 +108,24 @@
.attr("clip-path", "url(#intent-perf-clip)")
.append("path")
.datum(function () { return data[li]; })
.attr("id", "line" + li)
.style("stroke", lineColor(li))
.attr("class", "line");
.attr("id", "line" + li);
if (li < headers.length - 1) {
paths[li].attr("class", "line").style("stroke", lineColor(li));
} else {
paths[li].attr("class", "lineTotal");
}
});
function dataIndex(time) {
var delta = now.getTime() - time;
var di = Math.round(n - 2 - (delta / duration));
// $log.info('now=' + now.getTime() + '; then=' + time + '; delta=' + delta + '; di=' + di + ';');
return di >= n || di < 0 ? -1 : di;
}
function lineColor(li) {
return li < headers.length - 1 ?
sus.cat7().getColor(li, false, ts.theme()) :
ts.theme() === 'light' ? '#333' : '#eee';
return sus.cat7().getColor(li, false, ts.theme());
}
function tick() {
......@@ -130,13 +159,17 @@
function start() {
stopped = false;
headers.forEach(function (h, li) {
theSample[li] = 0;
theSample[li] = data[li][n-1];
});
tick();
}
function stop() {
stopped = true;
headers.forEach(function (h, li) {
theSample[li] = 0;
});
// Schedule delayed stop to allow 0s to render.
setTimeout(function () { stopped = true; }, 1000);
}
function resize(dim) {
......@@ -190,7 +223,7 @@
function createAndInitGraph(d) {
if (!graph) {
d.headers.push("total");
graph = createGraph(d.headers);
graph = createGraph(d.headers, d.samples);
}
graph.start();
}
......@@ -213,7 +246,7 @@
function createHandlerMap() {
handlerMap = {
intentPerfHeaders: createAndInitGraph,
intentPerfInit: createAndInitGraph,
intentPerfSample: recordSample
};
}
......
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.line {
fill: none;
stroke: darkgreen;
stroke-width: 2px;
}
.axis path,
.axis line {
fill: none;
stroke: #999;
stroke-width: 2px;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
(function () {
var cs = 0,
samples = [
89.53,
37515.81,
104609.6,
113105.11,
103194.74,
122151.63,
128623.9,
137325.33,
154897.31,
161235.07,
162025.4,
164902.64,
158196.26,
161072.44,
160792.54,
164692.44,
161979.74,
162137.4,
159325.19,
170465.44,
168186.46,
171152.34,
168221.02,
167440.73,
165003.39,
166855.18,
157268.79,
164087.54,
162265.21,
165990.16,
176364.01,
172064.07,
184872.24,
183249.8,
182282.47,
171475.11,
158880.58,
166016.69,
168233.16,
177759.92,
179742.87,
170819.44,
167577.73,
169479.9,
175544.89,
183792.01,
184689.52,
178503.87,
173219.27,
179085.49,
179700.54,
174281.17,
181353.08,
180173.14,
184093.16,
186011.5,
176952.79,
175319.2,
169001.05,
174545.12,
169156.29,
171804.3,
159155.54,
154709.96,
157263.97
],
theSample,
headers = [ "Whole", "Half", "Third" ];
var n = 243,
duration = 750,
now = new Date(Date.now() - duration),
data = [];
headers.forEach(function (d, li) {
data[li] = d3.range(n).map(function () { return 0; });
});
var margin = {top: 20, right: 100, bottom: 20, left: 100},
width = 960 - margin.right,
height = 512 - margin.top - margin.bottom;
var x = d3.time.scale()
.domain([now - (n - 2) * duration, now - duration])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, 200000])
.range([height, 0]);
var svg = d3.select("body").append("p").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var axis = svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(x.axis = d3.svg.axis().scale(x).orient("bottom"));
svg.append("g")
.attr("class", "y axis")
.call(d3.svg.axis().scale(y).orient("left"));
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + " ,0)")
.call(d3.svg.axis().scale(y).orient("right"));
var lines = [], paths = [];
data.forEach(function (p, li) {
lines[li]= d3.svg.line()
.interpolate("basis")
.x(function (d, i) {
return x(now - (n - 1 - i) * duration);
})
.y(function (d, i) {
return y(d);
});
paths[li] = svg.append("g")
.attr("clip-path", "url(#clip)")
.append("path")
.datum(function () { return data[li]; })
.attr("id", "line" + li)
.attr("class", "line");
});
var transition = d3.select({}).transition()
.duration(750)
.ease("linear");
function tick() {
transition = transition.each(function () {
// update the domains
now = new Date();
x.domain([now - (n - 2) * duration, now - duration]);
data.forEach(function (d, li) {
// push the new most recent sample onto the back
d.push(theSample[li]);
// redraw the line and slide it left
paths[li].attr("d", lines[li]).attr("transform", null);
paths[li].transition().attr("transform", "translate(" + x(now - (n - 1) * duration) + ")");
// pop the old data point off the front
d.shift();
});
// slide the x-axis left
axis.call(x.axis);
}).transition().each("start", tick);
}
function setSample() {
var v = samples[cs++];
theSample = [ v, v/2, v/3 ];
}
setSample();
setInterval(setSample, 1000);
tick();
})()
</script>
</body>
</html>