[pve-devel] [PATCH manager 6/7] add new ceph dashboard

Dominik Csapak d.csapak at proxmox.com
Tue Nov 22 12:32:14 CET 2016


this patch changes the ceph dashboard

now we show the information in a more graphical way, namely:

the overall status is displayed by a big icon (+health)
the warnings/errors are in a list (with severity)

we show more detailed information about monitors, osds, and pgs

we show the usage of the cluster as a gauge graph, and
the reads, writes and iops as running charts (the last 5 minutes)

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 www/manager6/Utils.js       |   8 ++
 www/manager6/ceph/Status.js | 338 +++++++++++++++++++++++++++++---------------
 2 files changed, 231 insertions(+), 115 deletions(-)

diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index a32697a..7f261a2 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -893,6 +893,14 @@ Ext.define('PVE.Utils', { utilities: {
 	return PVE.Utils.format_size(value);
     },
 
+    render_bandwidth: function(value) {
+	if (!Ext.isNumeric(value)) {
+	    return '';
+	}
+
+	return PVE.Utils.format_size(value) + '/s';
+    },
+
     render_timestamp: function(value, metaData, record, rowIndex, colIndex, store) {
 	var servertime = new Date(value * 1000);
 	return Ext.Date.format(servertime, 'Y-m-d H:i:s');
diff --git a/www/manager6/ceph/Status.js b/www/manager6/ceph/Status.js
index ab97a9f..f04fca0 100644
--- a/www/manager6/ceph/Status.js
+++ b/www/manager6/ceph/Status.js
@@ -1,128 +1,236 @@
 Ext.define('PVE.node.CephStatus', {
-    extend: 'PVE.grid.ObjectGrid',
-    alias: ['widget.pveNodeCephStatus'],
+    extend: 'Ext.panel.Panel',
+    alias: 'widget.pveNodeCephStatus',
+
     onlineHelp: 'chapter_pveceph',
-    cwidth1: 150,
-    interval: 3000,
+
+    scrollable: true,
+
+    bodyPadding: '10 0 0 0',
+
+    defaults: {
+	width: 762,
+	userCls: 'inline-block',
+	padding: '0 0 10 10'
+    },
+
+    items: [
+	{
+	    xtype: 'panel',
+	    title: gettext('Health'),
+	    bodyPadding: '0 10 10 10',
+	    minHeight: 210,
+	    layout: {
+		type: 'hbox',
+		align: 'top'
+	    },
+	    items: [
+		{
+		    flex: 1,
+		    itemId: 'overallhealth',
+		    xtype: 'pveHealthWidget',
+		    title: gettext('Status')
+		},
+		{
+		    flex: 2,
+		    itemId: 'warnings',
+		    stateful: true,
+		    stateId: 'ceph-status-warnings',
+		    padding: '15 0 0 0',
+		    xtype: 'grid',
+		    minHeight: 100,
+		    // since we load the store manually,
+		    // to show the emptytext, we have to
+		    // specify an empty store
+		    store: { data:[] },
+		    emptyText: gettext('No Warnings/Errors'),
+		    columns: [
+			{
+			    dataIndex: 'severity',
+			    header: gettext('Severity'),
+			    align: 'center',
+			    width: 70,
+			    renderer: function(value) {
+				var health = PVE.Utils.map_ceph_health[value];
+				var classes = PVE.Utils.get_health_icon(health);
+
+				return '<i class="fa fa-fw ' + classes + '"></i>';
+			    },
+			    sorter: {
+				sorterFn: function(a,b) {
+				    var healthArr = ['HEALTH_ERR', 'HEALTH_WARN', 'HEALTH_OK'];
+				    return healthArr.indexOf(b.data.severity) - healthArr.indexOf(a.data.severity);
+				}
+			    }
+			},
+			{
+			    dataIndex: 'summary',
+			    header: gettext('Summary'),
+			    flex: 1
+			}
+		    ]
+		}
+	    ]
+	},
+	{
+	    xtype: 'pveCephStatusDetail',
+	    itemId: 'statusdetail',
+	    title: gettext('Status')
+	},
+	{
+	    xtype: 'panel',
+	    title: gettext('Performance'),
+	    bodyPadding: '0 10 10 10',
+	    layout: {
+		type: 'hbox',
+		align: 'center'
+	    },
+	    items: [
+		{
+		    flex: 1,
+		    xtype: 'pveGauge',
+		    itemId: 'space',
+		    title: gettext('Usage')
+		},
+		{
+		    flex: 2,
+		    xtype: 'container',
+		    defaults: {
+			padding: '0 0 0 30',
+			height: 100
+		    },
+		    items: [
+			{
+			    itemId: 'reads',
+			    xtype: 'pveRunningChart',
+			    title: gettext('Reads'),
+			    renderer: PVE.Utils.render_bandwidth
+			},
+			{
+			    itemId: 'writes',
+			    xtype: 'pveRunningChart',
+			    title: gettext('Writes'),
+			    renderer: PVE.Utils.render_bandwidth
+			},
+			{
+			    itemId: 'iops',
+			    xtype: 'pveRunningChart',
+			    hidden: true,
+			    title: gettext('IOPS'),
+			    renderer: Ext.util.Format.numberRenderer('0,000')
+			},
+			{
+			    itemId: 'readiops',
+			    xtype: 'pveRunningChart',
+			    hidden: true,
+			    title: gettext('Read IOPS'),
+			    renderer: Ext.util.Format.numberRenderer('0,000')
+			},
+			{
+			    itemId: 'writeiops',
+			    xtype: 'pveRunningChart',
+			    hidden: true,
+			    title: gettext('Write IOPS'),
+			    renderer: Ext.util.Format.numberRenderer('0,000')
+			}
+		    ]
+		}
+	    ]
+	}
+    ],
+
+    updateAll: function(store, records, success) {
+	if (!success || records.length === 0) {
+	    return;
+	}
+
+	var me = this;
+	var rec = records[0];
+
+	// add health panel
+	me.down('#overallhealth').updateHealth(PVE.Utils.render_ceph_health(rec));
+	// add errors to gridstore
+	me.down('#warnings').getStore().loadRawData(rec.data.health.summary, false);
+
+	// update detailstatus panel
+	me.getComponent('statusdetail').updateAll(rec);
+
+	// add performance data
+	var used = rec.data.pgmap.bytes_used;
+	var total = rec.data.pgmap.bytes_total;
+
+	var text = Ext.String.format(gettext('{0} of {1}'),
+	    PVE.Utils.render_size(used),
+	    PVE.Utils.render_size(total)
+	);
+
+	// update the usage widget
+	me.down('#space').updateValue(used/total, text);
+
+	// TODO: logic for jewel (iops splitted in read/write)
+
+	var iops = rec.data.pgmap.op_per_sec;
+	var readiops = rec.data.pgmap.read_op_per_sec;
+	var writeiops = rec.data.pgmap.write_op_per_sec0;
+	var reads = rec.data.pgmap.read_bytes_sec || 0;
+	var writes = rec.data.pgmap.write_bytes_sec || 0;
+
+	if (iops !== undefined && me.version !== 'hammer') {
+	    me.change_version('hammer');
+	} else if((readiops !== undefined || writeiops !== undefined) && me.version !== 'jewel') {
+	    me.change_version('jewel');
+	}
+	// update the graphs
+	me.reads.addDataPoint(reads);
+	me.writes.addDataPoint(writes);
+	me.iops.addDataPoint(iops);
+	me.readiops.addDataPoint(readiops);
+	me.writeiops.addDataPoint(writeiops);
+    },
+
+    change_version: function(version) {
+	var me = this;
+	me.version = version;
+	me.sp.set('ceph-version', version);
+	me.iops.setVisible(version === 'hammer');
+	me.readiops.setVisible(version === 'jewel');
+	me.writeiops.setVisible(version === 'jewel');
+    },
+
     initComponent: function() {
-	 /*jslint confusion: true */
-        var me = this;
+	var me = this;
 
 	var nodename = me.pveSelNode.data.node;
 	if (!nodename) {
 	    throw "no node name specified";
 	}
 
-	var renderquorum = function(value) {
-	    if (!value || value.length < 0) {
-		return 'No';
-	    }
-
-	    return 'Yes {' + value.join(' ') + '}';
-	};
-
-	var rendermonmap = function(d) {
-	    if (!d) {
-		return '';
-	    }
-
-	    var txt =  'e' + d.epoch + ': ' + d.mons.length + " mons at ";
-
-	    Ext.Array.each(d.mons, function(d) {
-		txt += d.name + '=' + d.addr + ',';
-	    });
-
-	    return txt;
-	};
-
-	var renderosdmap = function(value) {
-	    if (!value || !value.osdmap) {
-		return '';
-	    }
-
-	    var d = value.osdmap;
-
-	    var txt = 'e' + d.epoch + ': ';
-
-	    txt += d.num_osds + ' osds: ' + d.num_up_osds + ' up, ' +
-		d.num_in_osds + " in";
-
-	    return txt;
-	};
-
-	var renderhealth = function(value) {
-	    if (!value || !value.overall_status) {
-		return '';
-	    }
-
-	    var txt = value.overall_status;
-
-	    Ext.Array.each(value.summary, function(d) {
-		txt += " " + d.summary + ';';
-	    });
-
-	    return txt;
-	};
-
-	var renderpgmap = function(d) {
-	    if (!d) {
-		return '';
-	    }
-
-	    var txt = 'v' + d.version + ': ';
-
-	    txt += d.num_pgs + " pgs:";
-
-	    Ext.Array.each(d.pgs_by_state, function(s) {
-		txt += " " + s.count + " " + s.state_name;
-	    });
-	    txt += '; ';
-
-	    txt += PVE.Utils.format_size(d.data_bytes) + " data, ";
-	    txt += PVE.Utils.format_size(d.bytes_used) + " used, ";
-	    txt += PVE.Utils.format_size(d.bytes_avail) + " avail";
-
-	    return txt;
-	};
-
-	Ext.applyIf(me, {
-	    url: "/api2/json/nodes/" + nodename + "/ceph/status",
-	    rows: {
-		health: {
-		    header: 'health',
-		    renderer: renderhealth,
-		    required: true
-		},
-		quorum_names: {
-		    header: 'quorum',
-		    renderer: renderquorum,
-		    required: true
-		},
-		fsid: {
-		    header: 'cluster',
-		    required: true
-		},
-		monmap: {
-		    header: 'monmap',
-		    renderer: rendermonmap,
-		    required: true
-		},
-		osdmap: {
-		    header: 'osdmap',
-		    renderer: renderosdmap,
-		    required: true
-		},
-		pgmap: {
-		    header: 'pgmap',
-		    renderer: renderpgmap,
-		    required: true
-		}
-	    }
-	});
-
 	me.callParent();
+	me.store = Ext.create('PVE.data.UpdateStore', {
+	    storeid: 'ceph-status-' + nodename,
+	    interval: 5000,
+	    proxy: {
+		type: 'pve',
+		url: '/api2/json/nodes/' + nodename + '/ceph/status'
+	    }
+	});
 
-	me.on('activate', me.rstore.startUpdate);
-	me.on('destroy', me.rstore.stopUpdate);
+	// save references for the updatefunction
+	me.iops = me.down('#iops');
+	me.readiops = me.down('#readiops');
+	me.writeiops = me.down('#writeiops');
+	me.reads = me.down('#reads');
+	me.writes = me.down('#writes');
+
+	// get ceph version
+	me.sp = Ext.state.Manager.getProvider();
+	me.version = me.sp.get('ceph-version');
+	me.change_version(me.version);
+
+	PVE.Utils.monStoreErrors(me,me.store);
+	me.mon(me.store, 'load', me.updateAll, me);
+	me.on('destroy', me.store.stopUpdate);
+	me.store.startUpdate();
     }
+
 });
-- 
2.1.4





More information about the pve-devel mailing list