mirror of https://gitee.com/bigwinds/arangodb
720 lines
19 KiB
JavaScript
720 lines
19 KiB
JavaScript
/*jshint browser: true */
|
|
/*jshint unused: false */
|
|
/*global require, exports, Backbone, EJS, $, flush, window, arangoHelper, nv, d3, localStorage*/
|
|
/*global document, console, Dygraph, _,templateEngine */
|
|
|
|
(function () {
|
|
"use strict";
|
|
|
|
function fmtNumber (n, nk) {
|
|
if (n === undefined || n === null) {
|
|
n = 0;
|
|
}
|
|
|
|
return n.toFixed(nk);
|
|
}
|
|
|
|
window.DashboardView = Backbone.View.extend({
|
|
el: '#content',
|
|
interval: 10000, // in milliseconds
|
|
defaultTimeFrame: 20 * 60 * 1000, // 20 minutes in milliseconds
|
|
defaultDetailFrame: 2 * 24 * 60 * 60 * 1000,
|
|
history: {},
|
|
graphs: {},
|
|
|
|
events: {
|
|
// will be filled in initialize
|
|
},
|
|
|
|
tendencies: {
|
|
asyncPerSecondCurrent: [
|
|
"asyncPerSecondCurrent", "asyncPerSecondPercentChange"
|
|
],
|
|
|
|
syncPerSecondCurrent: [
|
|
"syncPerSecondCurrent", "syncPerSecondPercentChange"
|
|
],
|
|
|
|
clientConnectionsCurrent: [
|
|
"clientConnectionsCurrent", "clientConnectionsPercentChange"
|
|
],
|
|
|
|
clientConnectionsAverage: [
|
|
"clientConnections15M", "clientConnections15MPercentChange"
|
|
],
|
|
|
|
numberOfThreadsCurrent: [
|
|
"numberOfThreadsCurrent", "numberOfThreadsPercentChange"
|
|
],
|
|
|
|
numberOfThreadsAverage: [
|
|
"numberOfThreads15M", "numberOfThreads15MPercentChange"
|
|
],
|
|
|
|
virtualSizeCurrent: [
|
|
"virtualSizeCurrent", "virtualSizePercentChange"
|
|
],
|
|
|
|
virtualSizeAverage: [
|
|
"virtualSize15M", "virtualSize15MPercentChange"
|
|
]
|
|
},
|
|
|
|
barCharts: {
|
|
totalTimeDistribution: [
|
|
"queueTimeDistributionPercent", "requestTimeDistributionPercent"
|
|
],
|
|
dataTransferDistribution: [
|
|
"bytesSentDistributionPercent", "bytesReceivedDistributionPercent"
|
|
]
|
|
},
|
|
|
|
barChartsElementNames: {
|
|
queueTimeDistributionPercent: "Queue",
|
|
requestTimeDistributionPercent: "Computation",
|
|
bytesSentDistributionPercent: "Bytes sent",
|
|
bytesReceivedDistributionPercent: "Bytes received"
|
|
|
|
},
|
|
|
|
getDetailFigure : function (e) {
|
|
var figure = $(e.currentTarget).attr("id").replace(/ChartButton/g, "");
|
|
return figure;
|
|
},
|
|
|
|
showDetail: function (e) {
|
|
var self = this,
|
|
figure = this.getDetailFigure(e),
|
|
options;
|
|
|
|
options = this.dygraphConfig.getDetailChartConfig(figure);
|
|
|
|
this.getHistoryStatistics(figure);
|
|
this.detailGraphFigure = figure;
|
|
|
|
window.modalView.hideFooter = true;
|
|
window.modalView.hide();
|
|
window.modalView.show(
|
|
"modalGraph.ejs",
|
|
options.header,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
this.events
|
|
);
|
|
|
|
window.modalView.hideFooter = false;
|
|
|
|
$('#modal-dialog').on('hidden', function () {
|
|
self.hidden();
|
|
});
|
|
|
|
$('#modal-dialog').toggleClass("modal-chart-detail", true);
|
|
|
|
options.height = $(window).height() * 0.7;
|
|
options.width = $('.modal-inner-detail').width();
|
|
|
|
// Reselect the labelsDiv. It was not known when requesting options
|
|
options.labelsDiv = $(options.labelsDiv)[0];
|
|
|
|
this.detailGraph = new Dygraph(
|
|
document.getElementById("lineChartDetail"),
|
|
this.history[this.server][figure],
|
|
options
|
|
);
|
|
},
|
|
|
|
hidden: function () {
|
|
this.detailGraph.destroy();
|
|
delete this.detailGraph;
|
|
delete this.detailGraphFigure;
|
|
},
|
|
|
|
|
|
getCurrentSize: function (div) {
|
|
if (div.substr(0,1) !== "#") {
|
|
div = "#" + div;
|
|
}
|
|
var height, width;
|
|
$(div).attr("style", "");
|
|
height = $(div).height();
|
|
width = $(div).width();
|
|
return {
|
|
height: height,
|
|
width: width
|
|
};
|
|
},
|
|
|
|
prepareDygraphs: function () {
|
|
var self = this, options;
|
|
this.dygraphConfig.getDashBoardFigures().forEach(function (f) {
|
|
options = self.dygraphConfig.getDefaultConfig(f);
|
|
var dimensions = self.getCurrentSize(options.div);
|
|
options.height = dimensions.height;
|
|
options.width = dimensions.width;
|
|
self.graphs[f] = new Dygraph(
|
|
document.getElementById(options.div),
|
|
self.history[self.server][f] || [],
|
|
options
|
|
);
|
|
});
|
|
},
|
|
|
|
initialize: function () {
|
|
this.dygraphConfig = this.options.dygraphConfig;
|
|
this.d3NotInitialised = true;
|
|
this.events["click .dashboard-sub-bar-menu-sign"] = this.showDetail.bind(this);
|
|
this.events["mousedown .dygraph-rangesel-zoomhandle"] = this.stopUpdating.bind(this);
|
|
this.events["mouseup .dygraph-rangesel-zoomhandle"] = this.startUpdating.bind(this);
|
|
|
|
this.server = this.options.serverToShow;
|
|
|
|
if (! this.server) {
|
|
this.server = "-local-";
|
|
}
|
|
|
|
this.history[this.server] = {};
|
|
},
|
|
|
|
updateCharts: function () {
|
|
var self = this;
|
|
if (this.detailGraph) {
|
|
this.updateLineChart(this.detailGraphFigure, true);
|
|
return;
|
|
}
|
|
this.prepareD3Charts(this.isUpdating);
|
|
this.prepareResidentSize(this.isUpdating);
|
|
this.updateTendencies();
|
|
Object.keys(this.graphs).forEach(function (f) {
|
|
self.updateLineChart(f, false);
|
|
});
|
|
},
|
|
|
|
updateTendencies: function () {
|
|
var self = this, map = this.tendencies;
|
|
|
|
var tempColor = "";
|
|
Object.keys(map).forEach(function (a) {
|
|
var v = self.history[self.server][a][1];
|
|
var p = "";
|
|
|
|
if (v < 0) {
|
|
tempColor = "red";
|
|
}
|
|
else {
|
|
tempColor = "green";
|
|
p = "+";
|
|
}
|
|
$("#" + a).html(self.history[self.server][a][0] + '<br/><span class="dashboard-figurePer" style="color: '
|
|
+ tempColor +';">' + p + v + '%</span>');
|
|
});
|
|
},
|
|
|
|
|
|
updateDateWindow: function (graph, isDetailChart) {
|
|
var t = new Date().getTime();
|
|
var borderLeft, borderRight;
|
|
if (isDetailChart && graph.dateWindow_) {
|
|
borderLeft = graph.dateWindow_[0];
|
|
borderRight = t - graph.dateWindow_[1] - this.interval * 5 > 0 ?
|
|
graph.dateWindow_[1] : t;
|
|
return [borderLeft, borderRight];
|
|
}
|
|
return [t - this.defaultTimeFrame, t];
|
|
|
|
|
|
},
|
|
|
|
updateLineChart: function (figure, isDetailChart) {
|
|
var g = isDetailChart ? this.detailGraph : this.graphs[figure],
|
|
opts = {
|
|
file: this.history[this.server][figure],
|
|
dateWindow: this.updateDateWindow(g, isDetailChart)
|
|
};
|
|
g.updateOptions(opts);
|
|
},
|
|
|
|
mergeDygraphHistory: function (newData, i) {
|
|
var self = this, valueList;
|
|
|
|
this.dygraphConfig.getDashBoardFigures(true).forEach(function (f) {
|
|
|
|
// check if figure is known
|
|
if (! self.dygraphConfig.mapStatToFigure[f]) {
|
|
return;
|
|
}
|
|
|
|
// need at least an empty history
|
|
if (! self.history[self.server][f]) {
|
|
self.history[self.server][f] = [];
|
|
}
|
|
|
|
// generate values for this key
|
|
valueList = [];
|
|
|
|
self.dygraphConfig.mapStatToFigure[f].forEach(function (a) {
|
|
if (! newData[a]) {
|
|
return;
|
|
}
|
|
|
|
if (a === "times") {
|
|
valueList.push(new Date(newData[a][i] * 1000));
|
|
}
|
|
else {
|
|
valueList.push(newData[a][i]);
|
|
}
|
|
});
|
|
|
|
// if we found at list one value besides times, then use the entry
|
|
if (valueList.length > 1) {
|
|
self.history[self.server][f].push(valueList);
|
|
}
|
|
});
|
|
},
|
|
|
|
cutOffHistory: function (f, cutoff) {
|
|
var self = this;
|
|
|
|
while (self.history[self.server][f].length !== 0) {
|
|
var v = self.history[self.server][f][0][0];
|
|
|
|
if (v >= cutoff) {
|
|
break;
|
|
}
|
|
|
|
self.history[self.server][f].shift();
|
|
}
|
|
},
|
|
|
|
cutOffDygraphHistory: function (cutoff) {
|
|
var self = this;
|
|
var cutoffDate = new Date(cutoff);
|
|
|
|
this.dygraphConfig.getDashBoardFigures(true).forEach(function (f) {
|
|
|
|
// check if figure is known
|
|
if (! self.dygraphConfig.mapStatToFigure[f]) {
|
|
return;
|
|
}
|
|
|
|
// history must be non-empty
|
|
if (! self.history[self.server][f]) {
|
|
return;
|
|
}
|
|
|
|
self.cutOffHistory(f, cutoffDate);
|
|
});
|
|
},
|
|
|
|
mergeHistory: function (newData) {
|
|
var self = this, i;
|
|
|
|
for (i = 0; i < newData.times.length; ++i) {
|
|
this.mergeDygraphHistory(newData, i);
|
|
}
|
|
|
|
this.cutOffDygraphHistory(new Date().getTime() - this.defaultTimeFrame);
|
|
|
|
// convert tendency values
|
|
Object.keys(this.tendencies).forEach(function (a) {
|
|
var n1 = 1;
|
|
var n2 = 1;
|
|
|
|
if (a === "virtualSizeCurrent" || a === "virtualSizeAverage") {
|
|
newData[self.tendencies[a][0]] /= (1024 * 1024 * 1024);
|
|
n1 = 2;
|
|
}
|
|
else if (a === "clientConnectionsCurrent") {
|
|
n1 = 0;
|
|
}
|
|
else if (a === "numberOfThreadsCurrent") {
|
|
n1 = 0;
|
|
}
|
|
|
|
self.history[self.server][a] = [
|
|
fmtNumber(newData[self.tendencies[a][0]], n1),
|
|
fmtNumber(newData[self.tendencies[a][1]] * 100, n2)
|
|
];
|
|
});
|
|
|
|
// update distribution
|
|
Object.keys(this.barCharts).forEach(function (a) {
|
|
self.history[self.server][a] = self.mergeBarChartData(self.barCharts[a], newData);
|
|
});
|
|
|
|
// update physical memory
|
|
self.history[self.server].physicalMemory = newData.physicalMemory;
|
|
self.history[self.server].residentSizeCurrent = newData.residentSizeCurrent;
|
|
self.history[self.server].residentSizePercent = newData.residentSizePercent;
|
|
|
|
// generate chart description
|
|
self.history[self.server].residentSizeChart =
|
|
[
|
|
{
|
|
"key": "",
|
|
"color": this.dygraphConfig.colors[1],
|
|
"values": [
|
|
{
|
|
label: "used",
|
|
value: newData.residentSizePercent * 100
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"key": "",
|
|
"color": this.dygraphConfig.colors[0],
|
|
"values": [
|
|
{
|
|
label: "used",
|
|
value: 100 - newData.residentSizePercent * 100
|
|
}
|
|
]
|
|
}
|
|
]
|
|
;
|
|
|
|
// remember next start
|
|
this.nextStart = newData.nextStart;
|
|
},
|
|
|
|
mergeBarChartData: function (attribList, newData) {
|
|
var i, v1 = {
|
|
"key": this.barChartsElementNames[attribList[0]],
|
|
"color": this.dygraphConfig.colors[0],
|
|
"values": []
|
|
}, v2 = {
|
|
"key": this.barChartsElementNames[attribList[1]],
|
|
"color": this.dygraphConfig.colors[1],
|
|
"values": []
|
|
};
|
|
for (i = newData[attribList[0]].values.length - 1; 0 <= i; --i) {
|
|
v1.values.push({
|
|
label: this.getLabel(newData[attribList[0]].cuts, i),
|
|
value: newData[attribList[0]].values[i]
|
|
});
|
|
v2.values.push({
|
|
label: this.getLabel(newData[attribList[1]].cuts, i),
|
|
value: newData[attribList[1]].values[i]
|
|
});
|
|
}
|
|
return [v1, v2];
|
|
},
|
|
|
|
getLabel: function (cuts, counter) {
|
|
if (!cuts[counter]) {
|
|
return ">" + cuts[counter - 1];
|
|
}
|
|
return counter === 0 ? "0 - " +
|
|
cuts[counter] : cuts[counter - 1] + " - " + cuts[counter];
|
|
},
|
|
|
|
getStatistics: function (callback) {
|
|
var self = this;
|
|
var url = "/_db/_system/_admin/aardvark/statistics/short";
|
|
var urlParams = "?start=";
|
|
|
|
if (self.nextStart) {
|
|
urlParams += self.nextStart;
|
|
}
|
|
else {
|
|
urlParams += (new Date().getTime() - self.defaultTimeFrame) / 1000;
|
|
}
|
|
|
|
if (self.server !== "-local-") {
|
|
url = self.server.endpoint + "/_admin/aardvark/statistics/cluster";
|
|
urlParams += "&type=short&DBserver=" + self.server.target;
|
|
|
|
if (! self.history.hasOwnProperty(self.server)) {
|
|
self.history[self.server] = {};
|
|
}
|
|
}
|
|
|
|
$.ajax(
|
|
url + urlParams,
|
|
{async: true}
|
|
).done(
|
|
function (d) {
|
|
if (d.times.length > 0) {
|
|
self.isUpdating = true;
|
|
self.mergeHistory(d);
|
|
}
|
|
if (self.isUpdating === false) {
|
|
return;
|
|
}
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
self.updateCharts();
|
|
}
|
|
);
|
|
},
|
|
|
|
getHistoryStatistics: function (figure) {
|
|
var self = this;
|
|
var url = "statistics/long";
|
|
|
|
var urlParams
|
|
= "?filter=" + this.dygraphConfig.mapStatToFigure[figure].join();
|
|
|
|
if (self.server !== "-local-") {
|
|
url = self.server.endpoint + "/_admin/aardvark/statistics/cluster";
|
|
urlParams += "&type=long&DBserver=" + self.server.target;
|
|
|
|
if (! self.history.hasOwnProperty(self.server)) {
|
|
self.history[self.server] = {};
|
|
}
|
|
}
|
|
|
|
$.ajax(
|
|
url + urlParams,
|
|
{async: true}
|
|
).done(
|
|
function (d) {
|
|
var i;
|
|
|
|
self.history[self.server][figure] = [];
|
|
|
|
for (i = 0; i < d.times.length; ++i) {
|
|
self.mergeDygraphHistory(d, i, true);
|
|
}
|
|
}
|
|
);
|
|
},
|
|
|
|
prepareResidentSize: function (update) {
|
|
var self = this;
|
|
|
|
var dimensions = this.getCurrentSize('#residentSizeChartContainer');
|
|
|
|
var current = self.history[self.server].residentSizeCurrent / 1024 / 1024;
|
|
var currentA = "";
|
|
|
|
if (current < 1025) {
|
|
currentA = fmtNumber(current, 2) + " MB";
|
|
}
|
|
else {
|
|
currentA = fmtNumber(current / 1024, 2) + " GB";
|
|
}
|
|
|
|
var currentP = fmtNumber(self.history[self.server].residentSizePercent * 100, 2);
|
|
var data = [fmtNumber(self.history[self.server].physicalMemory / 1024 / 1024 / 1024, 0) + " GB"];
|
|
|
|
nv.addGraph(function () {
|
|
var chart = nv.models.multiBarHorizontalChart()
|
|
.x(function (d) {
|
|
return d.label;
|
|
})
|
|
.y(function (d) {
|
|
return d.value;
|
|
})
|
|
.width(dimensions.width)
|
|
.height(dimensions.height)
|
|
.margin({
|
|
top: ($("residentSizeChartContainer").outerHeight() - $("residentSizeChartContainer").height()) / 2,
|
|
right: 1,
|
|
bottom: ($("residentSizeChartContainer").outerHeight() - $("residentSizeChartContainer").height()) / 2,
|
|
left: 1
|
|
})
|
|
.showValues(false)
|
|
.showYAxis(false)
|
|
.showXAxis(false)
|
|
.transitionDuration(100)
|
|
.tooltips(false)
|
|
.showLegend(false)
|
|
.showControls(false)
|
|
.stacked(true);
|
|
|
|
chart.yAxis
|
|
.tickFormat(function (d) {return d + "%";})
|
|
.showMaxMin(false);
|
|
chart.xAxis.showMaxMin(false);
|
|
|
|
d3.select('#residentSizeChart svg')
|
|
.datum(self.history[self.server].residentSizeChart)
|
|
.call(chart);
|
|
|
|
d3.select('#residentSizeChart svg').select('.nv-zeroLine').remove();
|
|
|
|
if (update) {
|
|
d3.select('#residentSizeChart svg').select('#total').remove();
|
|
d3.select('#residentSizeChart svg').select('#percentage').remove();
|
|
}
|
|
|
|
d3.select('.dashboard-bar-chart-title .percentage')
|
|
.html(currentA + " ("+ currentP + " %)");
|
|
|
|
d3.select('.dashboard-bar-chart-title .absolut')
|
|
.html(data[0]);
|
|
|
|
nv.utils.windowResize(chart.update);
|
|
|
|
return chart;
|
|
}, function() {
|
|
d3.selectAll("#residentSizeChart .nv-bar").on('click',
|
|
function() {
|
|
// no idea why this has to be empty, well anyways...
|
|
}
|
|
);
|
|
});
|
|
},
|
|
|
|
prepareD3Charts: function (update) {
|
|
var self = this;
|
|
|
|
var barCharts = {
|
|
totalTimeDistribution: [
|
|
"queueTimeDistributionPercent", "requestTimeDistributionPercent"],
|
|
dataTransferDistribution: [
|
|
"bytesSentDistributionPercent", "bytesReceivedDistributionPercent"]
|
|
};
|
|
|
|
if (this.d3NotInitialised) {
|
|
update = false;
|
|
this.d3NotInitialised = false;
|
|
}
|
|
|
|
_.each(Object.keys(barCharts), function (k) {
|
|
var dimensions = self.getCurrentSize('#' + k
|
|
+ 'Container .dashboard-interior-chart');
|
|
|
|
var selector = "#" + k + "Container svg";
|
|
|
|
nv.addGraph(function () {
|
|
var tickMarks = [0, 0.25, 0.5, 0.75, 1];
|
|
var marginLeft = 75;
|
|
var marginBottom = 23;
|
|
var bottomSpacer = 6;
|
|
|
|
if (dimensions.width < 219) {
|
|
tickMarks = [0, 0.5, 1];
|
|
marginLeft = 72;
|
|
marginBottom = 21;
|
|
bottomSpacer = 5;
|
|
}
|
|
else if (dimensions.width < 299) {
|
|
tickMarks = [0, 0.3334, 0.6667, 1];
|
|
marginLeft = 77;
|
|
}
|
|
else if (dimensions.width < 379) {
|
|
marginLeft = 87;
|
|
}
|
|
else if (dimensions.width < 459) {
|
|
marginLeft = 95;
|
|
}
|
|
else if (dimensions.width < 539) {
|
|
marginLeft = 100;
|
|
}
|
|
else if (dimensions.width < 619) {
|
|
marginLeft = 105;
|
|
}
|
|
|
|
var chart = nv.models.multiBarHorizontalChart()
|
|
.x(function (d) {
|
|
return d.label;
|
|
})
|
|
.y(function (d) {
|
|
return d.value;
|
|
})
|
|
.width(dimensions.width)
|
|
.height(dimensions.height)
|
|
.margin({
|
|
top: 5,
|
|
right: 20,
|
|
bottom: marginBottom,
|
|
left: marginLeft
|
|
})
|
|
.showValues(false)
|
|
.showYAxis(true)
|
|
.showXAxis(true)
|
|
.transitionDuration(100)
|
|
.tooltips(false)
|
|
.showLegend(false)
|
|
.showControls(false)
|
|
.forceY([0,1]);
|
|
|
|
chart.yAxis
|
|
.showMaxMin(false);
|
|
|
|
var yTicks2 = d3.select('.nv-y.nv-axis')
|
|
.selectAll('text')
|
|
.attr('transform', 'translate (0, ' + bottomSpacer + ')') ;
|
|
|
|
chart.yAxis
|
|
.tickValues(tickMarks)
|
|
.tickFormat(function (d) {return fmtNumber(((d * 100 * 100) / 100), 0) + "%";});
|
|
|
|
d3.select(selector)
|
|
.datum(self.history[self.server][k])
|
|
.call(chart);
|
|
|
|
nv.utils.windowResize(chart.update);
|
|
|
|
return chart;
|
|
}, function() {
|
|
d3.selectAll(selector + " .nv-bar").on('click',
|
|
function() {
|
|
// no idea why this has to be empty, well anyways...
|
|
}
|
|
);
|
|
});
|
|
});
|
|
|
|
},
|
|
|
|
stopUpdating: function () {
|
|
this.isUpdating = false;
|
|
},
|
|
|
|
startUpdating: function () {
|
|
var self = this;
|
|
if (self.timer) {
|
|
return;
|
|
}
|
|
self.timer = window.setInterval(function () {
|
|
self.getStatistics();
|
|
},
|
|
self.interval
|
|
);
|
|
},
|
|
|
|
|
|
resize: function () {
|
|
if (!this.isUpdating) {
|
|
return;
|
|
}
|
|
var self = this, dimensions;
|
|
_.each(this.graphs,function (g) {
|
|
dimensions = self.getCurrentSize(g.maindiv_.id);
|
|
g.resize(dimensions.width, dimensions.height);
|
|
});
|
|
if (this.detailGraph) {
|
|
dimensions = this.getCurrentSize(this.detailGraph.maindiv_.id);
|
|
this.detailGraph.resize(dimensions.width, dimensions.height);
|
|
}
|
|
this.prepareD3Charts(true);
|
|
this.prepareResidentSize(true);
|
|
},
|
|
|
|
template: templateEngine.createTemplate("dashboardView.ejs"),
|
|
|
|
render: function (modalView) {
|
|
if (!modalView) {
|
|
$(this.el).html(this.template.render());
|
|
}
|
|
var callback = function() {
|
|
this.prepareDygraphs();
|
|
if (this.isUpdating) {
|
|
this.prepareD3Charts();
|
|
this.prepareResidentSize();
|
|
this.updateTendencies();
|
|
}
|
|
this.startUpdating();
|
|
}.bind(this);
|
|
this.getStatistics(callback);
|
|
|
|
|
|
|
|
}
|
|
});
|
|
}());
|