]> git.proxmox.com Git - pve-manager.git/commitdiff
ext6migrate: add RRDStore class and RRDChart
authorDominik Csapak <d.csapak@proxmox.com>
Thu, 31 Mar 2016 08:30:18 +0000 (10:30 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Thu, 31 Mar 2016 08:38:28 +0000 (10:38 +0200)
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@proxmox.com>
PVE/ExtJSIndex6.pm
www/manager6/data/RRDStore.js [new file with mode: 0644]
www/manager6/panel/RRDChart.js [new file with mode: 0644]

index 61c60c0ea4aed7fe5655672a9d2e66f052c7180f..138a2ec4f475290e264a664d27f4c3b947c8c450 100644 (file)
@@ -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 (file)
index 0000000..d2275fb
--- /dev/null
@@ -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 (file)
index 0000000..ae606a5
--- /dev/null
@@ -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();
+    }
+});