function formatSeriesLabel(labels) {
var host = labels[0],
plugin = labels[1],
instance = labels[2],
metric = labels[3],
name;
if (plugin == "irq") {
name = name.replace(/^/, 'irq ')
}
// Plugin specific labeling
else if (plugin == "interface") {
name = instance.replace(/^if_(.*)-(.*)/, '$2 $1') + ' (' + metric + ')'
}
else if (["processes", "memory"].contains(plugin) || plugin.test(/^cpu-\d+/) ) {
name = instance.split('-')[1]
}
else if (plugin == "swap") {
if (instance.test(/^swap_io/)) {
name = instance.replace(/^swap_(\w*)-(.*)$/, '$1 $2')
}
if (instance.test(/^swap-/)) {
name = instance.split('-')[1]
}
}
else if (plugin == "load") {
name = metric.replace(/^\((.*)\)$/, '$1')
}
else if (plugin.test(/^disk/)) {
name = instance.replace(/^disk_/, '') + ' (' + metric + ')'
}
else if (["entropy","users"].contains(plugin)) {
name = metric
}
else if (plugin == "uptime") {
name = instance
}
else if (plugin == "ping") {
if (instance.test(/^ping_/)) {
name = instance.replace(/^ping_(.*)-(.*)$/, '$1 $2')
} else {
name = metric + ' ' + instance.split('-')[1]
}
}
else if (plugin.test(/^vmem/)) {
if (instance.test(/^vmpage_number-/)) {
name = instance.replace(/^vmpage_number-(.*)$/, '$1').replace('_', ' ')
}
if (instance.test(/^vmpage_io/)) {
name = instance.replace(/^vmpage_io-(.*)$/, '$1 ') + metric
}
if (instance.test(/^vmpage_faults/)) {
name = metric.trim() == "minflt" ? 'minor' : 'major'
name += ' faults'
}
if (instance.test(/^vmpage_action-/)) {
name = instance.replace(/^vmpage_action-(.*)$/, '$1').replace('_', ' ')
}
}
else if (plugin.test(/^tcpconns/)) {
name = instance.split('-')[1].replace('_', ' ')
}
else if (plugin.test(/^tail/)) {
name = plugin.split('-').slice(1).join('-') + ' '
name = instance.split('-').slice(1).join('-')
}
else if (plugin == "apache") {
var stash = instance.split('_')[1]
if (stash.test(/^scoreboard/)) {
name = 'connections: ' + stash.split('-')[1]
} else {
name = stash
}
}
else if ( plugin.test(/^curl_json/) ) {
var stash = instance.split('-')[2];
var stash = stash.replace(/[-|_]/, ' ');
name = stash
}
else {
// Generic label building
name = instance
name = name.replace(plugin.split('-')[0], '')
name += metric == "value" ? "" : " (" + metric + ")"
name = name.replace(/^[-|_]*/, '')
name = name.trim().replace(/^\((.*)\)$/, '$1')
}
return name.trim()
}
function formatValue(value, options) {
var precision = options.precision,
min = options.min,
max = options.max;
switch(true) {
case (Math.abs(max) > 1125899906842624):
var label = value / 1125899906842624,
unit = 'P';
break
case (Math.abs(max) > 1099511627776):
var label = value / 1099511627776,
unit = 'T';
break
case (Math.abs(max) > 1073741824):
var label = value / 1073741824,
unit = 'G';
break
case (Math.abs(max) > 1048576):
var label = value / 1048576,
unit = 'M';
break
case (Math.abs(max) > 1024):
var label = value / 1024,
unit = 'K';
break
default:
var label = value,
unit = '';
break
}
var rounded = label.round(precision)
return rounded + unit
}
function formatDate(d) {
var datetime = new Date(d * 1000)
return datetime.format("%Y-%m-%d %H:%M:%S UTC%T")
}
function formatPluginName(name) {
if (name.test(/^curl_json/)) {
name = name.split('-')[1].replace(/(-|_)/, ' ');
}
return name
}
/*
* visageBase()
*
* Base class for fetching data and setting graph options.
* Should be used by other classes to build specialised graphing behaviour.
*
*/
var visageBase = new Class({
Implements: [ Options, Events ],
options: {
secureJSON: false,
httpMethod: 'get',
live: false
},
initialize: function(element, host, plugin, options) {
this.parentElement = element
this.options.host = host
this.options.plugin = plugin
this.setOptions(options)
var data = new Hash()
if($chk(this.options.start)) {
data.set('start', this.options.start)
}
if($chk(this.options.finish)) {
data.set('finish', this.options.finish)
}
this.requestData = data;
this.getData(); // calls graphData
},
dataURL: function() {
var url = ['data', this.options.host, this.options.plugin]
// if the data exists on another host (useful for embedding)
if ($defined(this.options.baseurl)) {
url.unshift(this.options.baseurl.replace(/\/$/, ''))
}
// for specific plugin instances
if ($chk(this.options.pluginInstance)) {
url.push(this.options.pluginInstance)
}
// if no url is specified
if (!url[0].match(/http\:\/\//)) {
url[0] = '/' + url[0]
}
return url.join('/')
},
getData: function() {
this.request = new Request.JSONP({
url: this.dataURL(),
data: this.requestData,
secure: this.options.secureJSON,
method: this.options.httpMethod,
onComplete: function(json) {
this.graphData(json);
}.bind(this),
onFailure: function(header, value) {
$(this.parentElement).set('html', header)
}.bind(this)
});
this.request.send();
},
graphName: function() {
if ($chk(this.options.name)) {
var name = this.options.name
} else {
var name = [ formatPluginName(this.options.plugin),
'on',
this.options.host ].join(' ')
}
return name
},
});
/*
* visageGraph()
*
* General purpose graph for rendering data from a single plugin
* with multiple plugin instances.
*
* Builds upon visageBase().
*
*/
var visageGraph = new Class({
Extends: visageBase,
Implements: Chain,
// assemble data to graph, then draw it
graphData: function(data) {
this.response = data
this.buildDataStructures()
this.lastStart = this.series[0].data[0][0]
this.lastFinish = this.series[0].data.getLast()[0]
switch(true) {
case $defined(this.chart) && this.requestData['live']:
this.series.each(function(series, index) {
var point = series.data[1];
this.chart.series[index].addPoint(point, false);
}, this);
this.chart.redraw();
break;
case $defined(this.chart):
this.series.each(function(series, index) {
this.chart.series[index].setData(series.data, false);
}, this);
/* Reset the zoom */
//this.chart.toolbar.remove('zoom');
this.chart.xAxis.concat(this.chart.yAxis).each(function(axis) {
axis.setExtremes(null,null,false);
});
this.chart.redraw();
break;
default:
this.drawChart()
break;
}
},
buildDataStructures: function (data) {
var series = this.series = []
var host = this.options.host
var plugin = this.options.plugin
var data = data ? data : this.response
$each(data[host][plugin], function(instance, iname) {
$each(instance, function(metric, mname) {
var start = metric.start,
finish = metric.finish,
interval = (finish - start) / metric.data.length;
var data = metric.data.map(function(value, index) {
var x = start + index * interval,
y = value;
return [ x, y ];
});
var set = {
name: [ host, plugin, iname, mname ],
data: data,
};
series.push(set)
}, this);
}, this);
return series
},
getSeriesMinMax: function(series) {
var min, max;
series.each(function(set) {
values = set.data.map(function(point) {
var value = point[1];
return value
});
var setMin = values.min()
var setMax = values.max()
if ($chk(min)) {
min = min > setMin ? setMin : min
} else {
min = setMin
}
if ($chk(max)) {
max = max < setMax ? setMax : max
} else {
max = setMax
}
});
return {'min': min, 'max': max};
},
drawChart: function() {
var series = this.series,
title = this.graphName(),
element = this.parentElement,
ytitle = formatPluginName(this.options.plugin),
min,
max;
/* Get the maximum value across all sets.
* Used later on to determine the decimal place in the label. */
meta = this.getSeriesMinMax(series);
var min = meta.min,
max = meta.max;
this.chart = new Highcharts.Chart({
chart: {
renderTo: element,
defaultSeriesType: 'line',
marginRight: 200,
marginBottom: 25,
zoomType: 'xy',
height: 300,
events: {
load: function(e) {
setInterval(function() {
if (this.options.live) {
var data = { 'start': this.lastFinish,
'finish': this.lastFinish + 10,
'live': true };
this.requestData = data;
this.getData()
}
}.bind(this), 10000);
}.bind(this)
}
},
title: {
text: title,
style: {
fontSize: '20px',
fontWeight: 'bold',
color: "#333333"
}
},
xAxis: {
type: 'datetime',
labels: {
y: 20,
formatter: function() {
var d = new Date(this.value * 1000)
return d.format("%H:%M")
}
},
title: {
text: null
}
},
yAxis: {
title: {
text: ytitle
},
startOnTick: false,
minPadding: 0.065,
max: max,
endOnTick: true,
labels: {
formatter: function() {
var precision = min - max < 1000 ? 2 : 0,
value = formatValue(this.value, {
'precision': precision,
'min': min,
'max': max
});
return value
}
}
},
plotOptions: {
series: {
shadow: false,
marker: {
enabled: false,
stacking: 'normal',
states: {
hover: {
enabled: true
}
}
}
}
},
tooltip: {
formatter: function() {
var tip;
tip = ''
tip += formatSeriesLabel(this.series.name).trim()
tip += '' + ' -> '
tip += ''
tip += formatValue(this.y, { 'precision': 2, 'min': min, 'max': max })
tip += ''
tip += ' (' + this.y + ')'
tip += ''
tip += ''
tip += '
'
tip += ''
tip += formatDate(this.x)
tip += ''
return tip
}
},
legend: {
layout: 'vertical',
align: 'right',
verticalAlign: 'top',
x: -10,
y: 60,
borderWidth: 0,
itemWidth: 186,
labelFormatter: function() {
return formatSeriesLabel(this.name)
},
itemStyle: {
cursor: 'pointer',
color: '#333333'
},
itemHoverStyle: {
color: '#777777'
}
},
series: series,
credits: {
enabled: false
}
});
this.buildDateSelector();
},
buildDateSelector: function() {
/*
* container
* \
* - form
* \
* - select
* | \
* | - option
* | |
* | - option
* |
* - submit
*/
var currentDate = new Date;
var currentUnixTime = parseInt(currentDate.getTime() / 1000);
var container = $(this.parentElement);
var form = new Element('form', {
'method': 'get',
'events': {
'submit': function(e, foo) {
e.stop();
e.target.getElement('select').getSelected().each(function(option) {
value = parseInt(option.value.split('=')[1])
data = { 'start': value }
});
this.requestData = data;
/* Draw everything again. */
this.getData();
}.bind(this)
}
});
var select = new Element('select', { 'class': 'date timescale' });
var timescales = new Hash({ 'hour': 1, '2 hours': 2, '6 hours': 6, '12 hours': 12,
'day': 24, '2 days': 48, '3 days': 72,
'week': 168, '2 weeks': 336, 'month': 672 });
timescales.each(function(hour, label) {
var current = this.currentTimePeriod == 'last {label}'.substitute({'label': label });
var value = "start={start}".substitute({'start': currentUnixTime - (hour * 3600)});
var html = 'last {label}'.substitute({'label': label });
var option = new Element('option', {
html: html,
value: value,
selected: (current ? 'selected' : '')
});
select.grab(option)
});
var submit = new Element('input', { 'type': 'submit', 'value': 'show' });
var liveToggler = new Element('input', {
'type': 'checkbox',
'id': this.parentElement + '-live',
'name': 'live',
'checked': this.options.live,
'events': {
'click': function() {
this.options.live = !this.options.live
}.bind(this)
},
'styles': {
'margin-left': '4px',
'cursor': 'pointer'
}
});
var liveLabel = new Element('label', {
'for': this.parentElement + '-live',
'html': 'Live',
'styles': {
'font-family': 'sans-serif',
'font-size': '11px',
'margin-left': '8px',
'cursor': 'pointer'
}
});
var exportLink = new Element('a', {
'href': this.dataURL(),
'html': 'Export data',
'styles': {
'font-family': 'sans-serif',
'font-size': '11px',
'margin-left': '8px',
},
'events': {
'mouseover': function(e) {
e.stop();
var url = e.target.get('href'),
extremes = this.chart.xAxis[0].getExtremes(),
options = { 'start': extremes.dataMin,
'finish': parseInt(extremes.dataMax) };
var options = new Hash(options).toQueryString(),
baseurl = this.dataURL(),
url = baseurl += '?' + options;
e.target.set('href', url)
}.bind(this)
}
});
form.grab(select)
form.grab(submit)
form.grab(liveToggler)
form.grab(liveLabel)
form.grab(exportLink)
container.grab(form, 'top')
},
});
// buildEmbedder: function() {
// var pre = new Element('textarea', {
// 'id': 'embedder',
// 'class': 'embedder',
// 'html': this.embedCode(),
// 'styles': {
// 'width': '500px',
// 'padding': '3px'
// }
// });
// this.embedderContainer.grab(pre);
//
// var slider = new Fx.Slide(pre, {
// duration: 200
// });
//
// slider.hide();
//
// var toggler = new Element('a', {
// 'id': 'toggler',
// 'class': 'toggler',
// 'html': '(embed)',
// 'href': '#',
// 'styles': {
// 'font-size': '0.7em',
// }
// });
// toggler.addEvent('click', function(e) {
// e.stop();
// slider.toggle();
// });
// this.embedderTogglerContainer.grab(toggler);
// },
// embedCode: function() {
// baseurl = "{protocol}//{host}".substitute({'host': window.location.host, 'protocol': window.location.protocol});
// code = "".substitute({'baseurl': baseurl});
// code += "