Skip to content

Commit

Permalink
Fix nv graph updating for time series charts
Browse files Browse the repository at this point in the history
  • Loading branch information
psiniemi committed Jul 13, 2015
1 parent 46cdb07 commit 8667700
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Box.Application.addModule('access-graph', function(context) {
{ legend: "req/h", step: hour }, { legend: "req/2h", step: twoHours } ];
var legend = "req/min";
var moduleElement, moduleConf, isTimescaleLongerThanDay, detailsStart, detailsStop, detailsStep = 60000;
var chart, chartData;

function xTicks(d) {
var date = new Date(d);
Expand All @@ -24,9 +25,16 @@ Box.Application.addModule('access-graph', function(context) {
}
}

var onmessage = function(parsedData) {
if (!parsedData.length || !parsedData[0].values.length) { return; }
utils.mergePoints(chartData, parsedData);
chart.update();
};

function createAccessGraph(data) {
chartData = data;
nv.addGraph(function() {
var chart = nv.models.multiBarChart()
chart = nv.models.multiBarChart()
.margin({top: 30, right: 20, bottom: 50, left: 75})
.showControls(false)
.stacked(true)
Expand All @@ -43,41 +51,12 @@ Box.Application.addModule('access-graph', function(context) {
.attr("y", 35)
.attr("text-anchor", "left")
.text(legend);

var latestTimestamp = data[0].values[data[0].values.length - 1].x, // keeps track of latest processed bit of data
socket = utils.configureSocket({
utils.configureSocket({
start: detailsStart,
stop: detailsStop,
step: detailsStep,
metricKey: moduleConf.chart.type
});

socket.onmessage = function(event) {
var parsedData = JSON.parse(event.data),
parsedDataTimestamp;

// no data or no values = there's nothing to update
if (!parsedData.length || !parsedData[0].values.length) { return; }

// we can assume all values in a set have same timestamp
parsedDataTimestamp = parsedData[0].values[0].x;
// make sure we're showing new data only
if (latestTimestamp >= parsedDataTimestamp) { return; }
// update the value for next cycle
latestTimestamp = parsedData[0].values[0].x;

// feed new data into chart data
parsedData.forEach(function(set, setIndex) {
set.values.forEach(function(value) {
data[setIndex].values.push(value);
data[setIndex].values.shift();
});
});

// update the chart once all data is in place
chart.update();
};

}, onmessage);
return chart;
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ Box.Application.addModule('childcpu-graph', function(context) {
var nv, d3, host, windowSvc, metrics, store, utils;

var moduleElement, moduleConf, detailsStart, detailsStop, detailsStep, isTimescaleLongerThanDay;
var chart, chartData;

var onmessage = function(parsedData) {
if (!parsedData.length || !parsedData[0].values.length) { return; }
utils.mergePoints(chartData, parsedData);
chart.update();
};

function xTicks(d) {
var date = new Date(d);
Expand All @@ -16,8 +23,9 @@ Box.Application.addModule('childcpu-graph', function(context) {
}

function createChildCpuGraph(data) {
chartData = data;
nv.addGraph(function() {
var chart = nv.models.lineChart();
chart = nv.models.lineChart();
chart.margin({right: 25});
chart.xAxis.tickFormat(xTicks);
chart.yAxis.tickFormat(d3.format('%'));
Expand All @@ -26,35 +34,12 @@ Box.Application.addModule('childcpu-graph', function(context) {
.transition().duration(500)
.call(chart);

var latestTimestamp = data[0].values[data[0].values.length - 1].x,
socket = utils.configureSocket({
utils.configureSocket({
start: detailsStart,
stop: detailsStop,
step: detailsStep,
metricKey: moduleConf.chart.type
});

socket.onmessage = function(event) {
var parsedData = JSON.parse(event.data),
parsedDataTimestamp;

// no data or no values = there's nothing to update
if (!parsedData.length || !parsedData[0].values.length) { return; }

// we can assume all values in a set have same timestamp
parsedDataTimestamp = parsedData[0].values[0].x;

if (latestTimestamp >= parsedDataTimestamp) { return; }
// update the value for next cycle
latestTimestamp = parsedData[0].values[0].x;

parsedData[0].values.forEach(function(value) {
data[0].values.push(value);
data[0].values.shift();
});
// update the chart once all data is in place
chart.update();
};
}, onmessage);

return chart;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ Box.Application.addModule('heap-graph', function(context) {
var nv, d3, host, windowSvc, metrics, store, utils;

var moduleElement, moduleConf, detailsStart, detailsStop, detailsStep, isTimescaleLongerThanDay;
var chart, chartData;

var onmessage = function(parsedData) {
if (!parsedData.length || !parsedData[0].values.length) { return; }
utils.mergePoints(chartData, parsedData);
chart.update();
};

function xTicks(d) {
var date = new Date(d);
Expand All @@ -16,46 +23,22 @@ Box.Application.addModule('heap-graph', function(context) {
}

function createHeapGraph(data) {
chartData = data;
nv.addGraph(function() {
var chart = nv.models.lineChart();
chart = nv.models.lineChart();
chart.margin({right: 25});
chart.xAxis.tickFormat(xTicks);
chart.yAxis.tickFormat(d3.format(',.2s'));
moduleElement.select(".graph")
.datum(data)
.transition().duration(500)
.call(chart);

var latestTimestamp = data[0].values[data[0].values.length - 1].x,
socket = utils.configureSocket({
start: detailsStart,
stop: detailsStop,
step: detailsStep,
metricKey: moduleConf.chart.type
});

socket.onmessage = function(event) {
var parsedData = JSON.parse(event.data),
parsedDataTimestamp;

// no data or no values = there's nothing to update
if (!parsedData.length || !parsedData[0].values.length) { return; }

// we can assume all values in a set have same timestamp
parsedDataTimestamp = parsedData[0].values[0].x;

if (latestTimestamp >= parsedDataTimestamp) { return; }
// update the value for next cycle
latestTimestamp = parsedData[0].values[0].x;

parsedData[0].values.forEach(function(value) {
data[0].values.push(value);
data[0].values.shift();
});
// update the chart once all data is in place
chart.update();
};

utils.configureSocket({
start: detailsStart,
stop: detailsStop,
step: detailsStep,
metricKey: moduleConf.chart.type
}, onmessage);
return chart;
});
}
Expand Down
100 changes: 83 additions & 17 deletions willow-servers/src/main/resources/webapp/scripts/services/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,51 @@ Box.Application.addService('utils', function(application) {
'use strict';
var $ = application.getGlobal('jQuery');
var d3 = application.getGlobal('d3');
var socket;
var messageHandlers = {};
var pollConfs = {};
var registeredConfs = [];

var onmessage = function(event) {
var eventData = JSON.parse(event.data);
if (messageHandlers[eventData.id]) {
messageHandlers[eventData.id](eventData.data);
}
};
var registerPending = function() {
if (socket && socket.readyState === 1) {
for (var metricId in pollConfs) {
if (registeredConfs.indexOf(metricId) === -1) {
socket.send( JSON.stringify(pollConfs[metricId]) );
registeredConfs.push(metricId);
}
}
}
};
var mergePointArrays = function(chartPoints, parsedPoints) {
if (!chartPoints || !parsedPoints || parsedPoints.length === 0) {
return chartPoints;
}
var halfStep = (chartPoints[1].x - chartPoints[0].x) / 2;
for (var i=0; i<parsedPoints.length; i++) {
var nextPoint = parsedPoints[i];
if (nextPoint.x >= chartPoints[chartPoints.length - 1].x + halfStep) {
chartPoints.push(nextPoint);
chartPoints.shift();
} else {
for (var j=chartPoints.length - 1; j>-1; j--) {
if (nextPoint.x > chartPoints[j].x - halfStep &&
nextPoint.x <= chartPoints[j].x + halfStep) {
chartPoints[j].y = nextPoint.y;
chartPoints[j].y0 = nextPoint.y;
chartPoints[j].y1 = nextPoint.y;
continue;
}
}
}
}
return chartPoints;
};
return {
debouncer: function(func , timeout) {
var timeoutID , tmOut = timeout || 200;
Expand Down Expand Up @@ -118,26 +162,48 @@ Box.Application.addService('utils', function(application) {
}
},

configureSocket: function(opts) {
var loc = window.location,
ws_uri = (loc.protocol === 'https:' ? 'wss://' : 'ws://') + loc.host + "/poll/",
socket = new WebSocket(ws_uri);

socket.onopen = function(e) {
var pollConf = {
metricKey: "/" + opts.metricKey,
start: opts.start,
stop: opts.stop,
step: opts.step,
minSteps: 1
};

socket.send( JSON.stringify(pollConf) );
configureSocket: function(opts, onmsg) {
if (!socket) {
var loc = window.location;
var ctx = "/";
var ctxEnd = loc.pathname.lastIndexOf("/");
if (ctxEnd > 0) {
if (loc.pathname.indexOf("/") === 0) {
ctx = "";
}
ctx += loc.pathname.substring(0, contextEnd) + "/";
}
var ws_uri = (loc.protocol === 'https:' ? 'wss://' : 'ws://') + loc.host + ctx +"poll/";
socket = new WebSocket(ws_uri);
socket.onopen = registerPending;
}
var metricId = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
messageHandlers[metricId] = onmsg;
var pollConf = {
id: metricId,
metricKey: "/" + opts.metricKey,
start: opts.start,
stop: opts.stop,
step: opts.step,
minSteps: 10
};

pollConfs[metricId] = pollConf;
socket.onmessage = onmessage;
registerPending();
return socket;
},

mergePoints: function(chartData, parsedData) {
parsedData.forEach(function(series) {
for (var i=0; i<chartData.length; i++) {
if (chartData[i].key === series.key) {
chartData[i].values = mergePointArrays(chartData[i].values, series.values);
}
}
});
},
timeFormat: d3.time.format("%H:%M"),
dateFormat: d3.time.format("%a %e. %B"),
dayTimeFormat: d3.time.format("%a %H:%M")
Expand Down

0 comments on commit 8667700

Please sign in to comment.