[pve-devel] [PATCH manager 2/4] ext6migrate: add RRDStore class and RRDChart

Dominik Csapak d.csapak at proxmox.com
Thu Mar 31 10:30:18 CEST 2016


this patch adds two classes for the charts:

RRDStore:

based on our updatestore, but specialized on our rrddata output
it converts the percentage (cpu) and the time (from unix to milliseconds)

also it handles the changes for the timeframe and cf

it sets a default reload of 30seconds

RRDChart:

based on Ext.chart.CartesianChart,
with specialized options for our rrd graphs

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 PVE/ExtJSIndex6.pm             |   2 +
 www/manager6/data/RRDStore.js  | 114 +++++++++++++++++++++++++++++
 www/manager6/panel/RRDChart.js | 158 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 274 insertions(+)
 create mode 100644 www/manager6/data/RRDStore.js
 create mode 100644 www/manager6/panel/RRDChart.js

diff --git a/PVE/ExtJSIndex6.pm b/PVE/ExtJSIndex6.pm
index 61c60c0..138a2ec 100644
--- a/PVE/ExtJSIndex6.pm
+++ b/PVE/ExtJSIndex6.pm
@@ -59,6 +59,7 @@ data/UpdateStore.js
 data/DiffStore.js
 data/ObjectStore.js
 data/ResourceStore.js
+data/RRDStore.js
 form/VLanField.js
 form/Checkbox.js
 form/TextField.js
@@ -106,6 +107,7 @@ dc/Tasks.js
 dc/Log.js
 panel/StatusPanel.js
 panel/RRDView.js
+panel/RRDChart.js
 panel/InputPanel.js
 window/Edit.js
 window/LoginWindow.js
diff --git a/www/manager6/data/RRDStore.js b/www/manager6/data/RRDStore.js
new file mode 100644
index 0000000..d2275fb
--- /dev/null
+++ b/www/manager6/data/RRDStore.js
@@ -0,0 +1,114 @@
+/* Extends the PVE.data.UpdateStore type
+ *
+ *
+ */
+Ext.define('PVE.data.RRDStore', {
+    extend: 'PVE.data.UpdateStore',
+    alias: 'store.pveRRDStore',
+
+    setRRDUrl: function(timeframe, cf) {
+	var me = this;
+	if (!timeframe) {
+	    timeframe = me.timeframe;
+	}
+
+	if (!cf) {
+	    cf = me.cf;
+	}
+
+	me.proxy.url = me.rrdurl + "?timeframe=" + timeframe + "&cf=" + cf;
+    },
+
+    proxy: {
+	type: 'pve',
+    },
+    fields: [
+	// node rrd fields
+	{
+	    name:'cpu',
+	    // percentage
+	    convert: function(value) {
+		return value*100;
+	    }
+	},
+	{
+	    name:'iowait',
+	    // percentage
+	    convert: function(value) {
+		return value*100;
+	    }
+	},
+	'loadavg',
+	'maxcpu',
+	'memtotal',
+	'memused',
+	'netin',
+	'netout',
+	'roottotal',
+	'rootused',
+	'swaptotal',
+	'swapused',
+	'time',
+
+	// missing qemu/lxc fields
+	'maxmem',
+	'mem',
+	'disk',
+	'diskread',
+	'diskwrite',
+	'maxdisk',
+	// for time we generate unix timestamps, javascript uses milliseconds instead of seconds
+	{ name:'time', convert: function(value) { return value*1000; }},
+    ],
+    sorters: 'time',
+    timeframe: 'hour',
+    cf: 'AVERAGE',
+
+    constructor: function(config) {
+	var me = this;
+
+	config = config || {};
+
+	// set default interval to 30seconds
+	if (!config.interval) {
+	    config.interval = 30000;
+	}
+
+	// set a new storeid
+	if (!config.storeid) {
+	    config.storeid = 'rrdstore-' + (++Ext.idSeed);
+	}
+
+	// rrdurl is required
+	if (!config.rrdurl) {
+	    throw "no rrdurl specified";
+	}
+
+	var stateid = 'pveRRDTypeSelection';
+	var sp = Ext.state.Manager.getProvider();
+	var stateinit = sp.get(stateid);
+
+        if (stateinit) {
+	    if(stateinit.timeframe !== me.timeframe || stateinit.cf !== me.rrdcffn){
+		me.timeframe = stateinit.timeframe;
+		me.rrdcffn = stateinit.cf;
+	    }
+	}
+
+	me.callParent([config]);
+
+	me.setRRDUrl();
+	me.mon(sp, 'statechange', function(prov, key, state){
+	    if (key === stateid) {
+		if (state && state.id) {
+		    if (state.timeframe !== me.timeframe || state.cf !== me.cf) {
+		        me.timeframe = state.timeframe;
+		        me.cf = state.cf;
+			me.setRRDUrl();
+			me.reload();
+		    }
+		}
+	    }
+	});
+    }
+});
diff --git a/www/manager6/panel/RRDChart.js b/www/manager6/panel/RRDChart.js
new file mode 100644
index 0000000..ae606a5
--- /dev/null
+++ b/www/manager6/panel/RRDChart.js
@@ -0,0 +1,158 @@
+Ext.define('PVE.widget.RRDChart', {
+    extend: 'Ext.chart.CartesianChart',
+    alias: 'widget.pveRRDChart',
+
+
+    width: 800,
+    height: 300,
+    interactions: 'crosszoom',
+    axes: [{
+	type: 'numeric',
+	position: 'left',
+	grid: true,
+	renderer: 'leftAxisRenderer',
+	minimum: 0,
+    }, {
+	type: 'time',
+	position: 'bottom',
+	grid: true,
+	fields: ['time'],
+    }],
+    legend: {
+	docked: 'right',
+	// we set this that all graphs have same width
+	width: 140,
+    },
+    listeners: {
+	afterrender: 'onAfterRender'
+    },
+
+    bytesArr : [
+	'memtotal',
+	'memused',
+	'roottotal',
+	'rootused',
+	'swaptotal',
+	'swapused',
+	'maxmem',
+	'mem',
+	'disk',
+	'maxdisk'
+    ],
+    bytespersArr: [
+	'netin',
+	'netout',
+	'diskread',
+	'diskwrite'
+    ],
+
+    percentArr: [
+	'cpu',
+	'iowait'
+    ],
+
+    convertToUnits: function(value) {
+	var units = ['', 'k','M','G','T', 'P'];
+	var si = 0;
+	while(value >= 1000  && si < (units.length -1)){
+	    value = value / 1000;
+	    si++;
+	}
+	// javascript floating point weirdness
+	value = Ext.Number.correctFloat(value);
+
+	// limit to 2 decimal points
+	value = Ext.util.Format.number(value, "0.##");
+
+	return value + " " + units[si];
+    },
+
+    leftAxisRenderer: function(axis, label, layoutContext) {
+	var me = this;
+	return me.convertToUnits(label);
+    },
+
+    onSeriesTooltipRender: function (tooltip, record, item) {
+	var me = this;
+	var suffix = '';
+
+	if (me.percentArr.indexOf(item.field) != -1) {
+	    suffix = '%';
+	} else if (me.bytesArr.indexOf(item.field) != -1) {
+	    suffix = 'B';
+	} else if (me.bytespersArr.indexOf(item.field) != -1) {
+	    suffix = 'B/s';
+	}
+
+	var prefix = item.field;
+	if (me.fieldTitles && me.fieldTitles[me.fields.indexOf(item.field)]) {
+	    prefix = me.fieldTitles[me.fields.indexOf(item.field)];
+	}
+        tooltip.setHtml(prefix + ': ' + this.convertToUnits(record.get(item.field)) + suffix +
+	    '<br>' + new Date(record.get('time')));
+    },
+
+    onAfterRender: function(){
+	var me = this;
+
+	// add correct label for left axis
+	var axisTitle = "";
+	if (me.percentArr.indexOf(me.fields[0]) != -1) {
+	    axisTitle = "%";
+	} else if (me.bytesArr.indexOf(me.fields[0]) != -1) {
+	    axisTitle = "Bytes";
+	} else if (me.bytespersArr.indexOf(me.fields[0]) != -1) {
+	    axisTitle = "Bytes/s";
+	}
+	me.axes[0].setTitle(axisTitle);
+
+	// add a series for each field we get
+	me.fields.forEach(function(item, index){
+	    var title = item;
+	    if (me.fieldTitles && me.fieldTitles[index]) {
+		title = me.fieldTitles[index];
+	    }
+	    me.addSeries({
+		type: 'line',
+		xField: 'time',
+		yField: item,
+		title: title,
+		fill: true,
+		style: {
+		    lineWidth: 1.5,
+		    opacity: 0.60,
+		},
+		marker: {
+		    opacity: 0,
+		    scaling: 0.01,
+		    fx: {
+			duration: 200,
+			easing: 'easeOut'
+		    }
+		},
+		highlightCfg: {
+		    opacity: 1,
+		    scaling: 1.5
+		},
+		tooltip: {
+		    trackMouse: true,
+		    renderer: 'onSeriesTooltipRender'
+		}
+	    });
+	});
+    },
+
+    initComponent: function() {
+	var me = this;
+
+	if (!me.store) {
+	    throw "cannot work without store";
+	}
+
+	if (!me.fields) {
+	    throw "cannot work without fields";
+	}
+
+	me.callParent();
+    }
+});
-- 
2.1.4





More information about the pve-devel mailing list