From 680930a27621b975fa105c4df0cd8528ee78bd7f Mon Sep 17 00:00:00 2001 From: Mirko Arena Date: Sun, 2 Oct 2016 13:27:43 +0200 Subject: [PATCH 01/16] #998 - Init commit to open PR --- .../static/storageadmin/js/views/dashboard/disk_utilization.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js index 706473198..c386942ec 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js +++ b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js @@ -3,7 +3,7 @@ * @licstart The following is the entire license notice for the * JavaScript code in this page. * - * Copyright (c) 2012-2013 RockStor, Inc. + * Copyright (c) 2012-2016 RockStor, Inc. * This file is part of RockStor. * * RockStor is free software; you can redistribute it and/or modify From b5e01cbe422a5811a6e6a667c3f9e90fd57eee82 Mon Sep 17 00:00:00 2001 From: Mirko Arena Date: Mon, 10 Oct 2016 09:36:28 +0200 Subject: [PATCH 02/16] #998 Disks Widget - Added chart.js data skeleton --- .../js/views/dashboard/disk_utilization.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js index c386942ec..bf692c6bb 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js +++ b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js @@ -118,6 +118,23 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ shadowSize: 0 // Drawing is faster without shadows } }; + this.TopDisksChart = null; + this.TopDisksChartOptions = { +animation: { +duration: 1500, +easing: 'linear' +}, + responsive: true, + title: { + display: true, + text: 'Top Disks', + padding: 5 + } +}; + this.TopDisksChartData = { + labels: ['Writes', 'Reads', 'kB Written', 'kB read', 'ms Writing', 'ms Reading', 'ms spent on I/Os'], + datasets: [] + }; }, render: function() { From 3c76bb2718d7df38ebc0dd382e3228c853e720e3 Mon Sep 17 00:00:00 2001 From: Mirko Arena Date: Mon, 10 Oct 2016 09:57:18 +0200 Subject: [PATCH 03/16] #998 Disks Widget - Add default options and data structures for kb read written and write/read charts --- .../js/views/dashboard/disk_utilization.js | 96 ++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js index bf692c6bb..ef65b17cb 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js +++ b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js @@ -42,6 +42,18 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ this.topDiskColors.push(startColor.toString()); startColor = startColor.brighter(2); } + Chart.defaults.global.tooltips.enabled = false; + Chart.defaults.global.elements.line.tension = 0.2; + Chart.defaults.global.elements.line.borderCapStyle = 'butt'; + Chart.defaults.global.elements.line.borderDash = []; + Chart.defaults.global.elements.line.borderDashOffset = 0.0; + Chart.defaults.global.elements.line.borderWidth = 1; + Chart.defaults.global.elements.line.borderJoinStyle = 'miter'; + Chart.defaults.global.elements.line.fill = false; + Chart.defaults.global.elements.point.radius = 0; + Chart.defaults.global.elements.point.hoverRadius = 0; + this.Diskslabels = ['Reads', 'Writes', 'kB read', 'kB written', 'ms reading', 'ms writing', 'ms spent on I/Os']; + this.colors2 = ['77, 175, 74', '55, 126, 184'] this.colors = ["#4DAF4A", "#377EB8"]; // disks data is a map of diskname to array of values of length // dataLength @@ -118,6 +130,55 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ shadowSize: 0 // Drawing is faster without shadows } }; + this.LineGraphsDefaultOptions = { + showLines: true, + animation: { + duration: 1000, + easing: 'linear' + }, + responsive: true, + legend: { + display: false + }, + scales: { + yAxes: [{ + position: 'left', + scaleLabel: { + display: true, + fontSize: 11, + labelString: 'Data' + }, + ticks: { + fontSize: 9, + beginAtZero: true, + min: 0 + }, + gridLines: { + drawTicks: false + } + }], + xAxes: [{ + scaleLabel: { + display: true, + fontSize: 11, + labelString: 'Time' + }, + gridLines: { + display: true, + drawTicks: false + }, + ticks: { + fontSize: 9, + maxRotation: 0, + autoSkip: false, + callback: function(value) { + return (value.toString().length > 0 ? value : null); + } + } + }] + } + }; + this.TopDisksChart = null; this.TopDisksChartOptions = { animation: { @@ -132,11 +193,44 @@ easing: 'linear' } }; this.TopDisksChartData = { - labels: ['Writes', 'Reads', 'kB Written', 'kB read', 'ms Writing', 'ms Reading', 'ms spent on I/Os'], + labels: this.labels, datasets: [] }; + + this.DiskReadWriteChart = null; + this.DiskReadWriteChartOptions = this.LineGraphsDefaultOptions; + this.DiskReadWriteChartData = { + label: this.Diskslabels[0], + backgroundColor: 'rgba(' + this.colors2[0] + ', 0.4)', + borderColor: 'rgba(' + this.colors2[0] + ', 1)', + data: [] + }, { + label: this.Diskslabels[1], + backgroundColor: 'rgba(' + this.colors[1] + ', 0.4)', + borderColor: 'rgba(' + this.colors2[1] + ', 1)', + data: [] + }; + + this.DiskkBChart = null; + this.DiskkBChartOptions = this.LineGraphsDefaultOptions; + this.DiskkBChartOptions.scales.yAxes[0].ticks.callback = function(value) { + return humanize.filesize(value); + } + this.DiskkBChartData = { + label: this.Diskslabels[2], + backgroundColor: 'rgba(' + this.colors2[0] + ', 0.4)', + borderColor: 'rgba(' + this.colors2[0] + ', 1)', + data: [] + }, { + label: this.Diskslabels[3], + backgroundColor: 'rgba(' + this.colors[1] + ', 0.4)', + borderColor: 'rgba(' + this.colors2[1] + ', 1)', + data: [] + } + }; }, + render: function() { var _this = this; // call render of base From c6571126136f97a0762e656ea912a02591ce5b8b Mon Sep 17 00:00:00 2001 From: Mirko Arena Date: Mon, 10 Oct 2016 20:33:02 +0200 Subject: [PATCH 04/16] #998 Disks Widget - Testing first radar chart updates - data_collector update required 1 object vs n expected - data normalization required --- .../js/views/dashboard/disk_utilization.js | 961 ++++++++++-------- 1 file changed, 538 insertions(+), 423 deletions(-) diff --git a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js index ef65b17cb..9d2534a70 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js +++ b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js @@ -27,21 +27,23 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ - initialize: function() { - RockStorSocket.diskWidget = io.connect('/disk-widget', {'secure': true, - 'force new connection': true}); - var _this = this; - this.constructor.__super__.initialize.apply(this, arguments); - this.template = window.JST.dashboard_widgets_disk_utilization; - this.diskUtilSelect = window.JST.dashboard_widgets_disk_util_select; - this.dataLength = 300; - this.topDiskColors = []; - // calculate colors from dark to light for top disks - var startColor = d3.rgb('#CC6104'); - for (var i=0; i<5; i++) { - this.topDiskColors.push(startColor.toString()); - startColor = startColor.brighter(2); - } + initialize: function() { + RockStorSocket.diskWidget = io.connect('/disk-widget', { + 'secure': true, + 'force new connection': true + }); + var _this = this; + this.constructor.__super__.initialize.apply(this, arguments); + this.template = window.JST.dashboard_widgets_disk_utilization; + this.diskUtilSelect = window.JST.dashboard_widgets_disk_util_select; + this.dataLength = 300; + this.topDiskColors = []; + // calculate colors from dark to light for top disks + var startColor = d3.rgb('#CC6104'); + for (var i = 0; i < 5; i++) { + this.topDiskColors.push(startColor.toString()); + startColor = startColor.brighter(2); + } Chart.defaults.global.tooltips.enabled = false; Chart.defaults.global.elements.line.tension = 0.2; Chart.defaults.global.elements.line.borderCapStyle = 'butt'; @@ -52,84 +54,94 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ Chart.defaults.global.elements.line.fill = false; Chart.defaults.global.elements.point.radius = 0; Chart.defaults.global.elements.point.hoverRadius = 0; - this.Diskslabels = ['Reads', 'Writes', 'kB read', 'kB written', 'ms reading', 'ms writing', 'ms spent on I/Os']; - this.colors2 = ['77, 175, 74', '55, 126, 184'] - this.colors = ["#4DAF4A", "#377EB8"]; - // disks data is a map of diskname to array of values of length - // dataLength - // each value is of the format of the data returned by the api - // see genEmptyDiskData for an example of this format - this.disksData = {}; - this.disks = new DiskCollection(); - this.disks.pageSize = RockStorGlobals.maxPageSize; - - this.topDisks = []; - this.topDisksWidth = this.maximized ? 520 : 240; - this.topDisksHeight = 50; - - this.selectedDisk = null; - - this.updateFreq = 1000; - this.sortAttrs = ['reads_completed']; // attrs to sort by - // maximum number of top disks to display - this.numTop = this.maximized ? 5 : 3; - this.partition = d3.layout.partition() - .value(function(d) { - return _.reduce(_this.sortAttrs, function(s, a) { return s + d[a]; }, 0); - }); - this.graphOptions = { - grid : { - //hoverable : true, - borderWidth: { - top: 1, - right: 1, - bottom: 1, - left: 1 - }, - borderColor: "#ddd" - }, - xaxis: { - min: 0, - max: this.dataLength-1, - tickFormatter: this.timeTickFormatter(this.dataLength), - axisLabel: "Time (minutes)", - axisLabelColour: "#000" - }, - yaxis: { - min: 0 - }, - series: { - lines: {show: true, fill: false}, - shadowSize: 0 // Drawing is faster without shadows - } - }; - this.dataGraphOptions = { - grid : { - //hoverable : true, - borderWidth: { - top: 1, - right: 1, - bottom: 1, - left: 1 - }, - borderColor: "#ddd" - }, - xaxis: { - min: 0, - max: this.dataLength-1, - tickFormatter: this.timeTickFormatter(this.dataLength), - axisLabel: "Time (minutes)", - axisLabelColour: "#000" - }, - yaxis: { - min: 0, - tickFormatter: this.valueTickFormatter - }, - series: { - lines: {show: true, fill: false}, - shadowSize: 0 // Drawing is faster without shadows - } - }; + this.Diskslabels = ['ms on I/Os', 'kB written', 'Writes', 'ms writing', 'ms reading', 'Reads', 'kB read']; + this.TopDiskscolors = ['242, 0, 0', '36, 229, 84', '41, 108, 232', '232, 200, 41', '146, 41, 232'] + this.colors2 = ['77, 175, 74', '55, 126, 184'] + this.TopDisksRadar = []; + this.colors = ["#4DAF4A", "#377EB8"]; + // disks data is a map of diskname to array of values of length + // dataLength + // each value is of the format of the data returned by the api + // see genEmptyDiskData for an example of this format + this.disksData = {}; + this.disks = new DiskCollection(); + this.disks.pageSize = RockStorGlobals.maxPageSize; + + this.topDisks = []; + this.topDisksWidth = this.maximized ? 520 : 240; + this.topDisksHeight = 50; + + this.selectedDisk = null; + + this.updateFreq = 1000; + this.sortAttrs = ['reads_completed']; // attrs to sort by + // maximum number of top disks to display + this.numTop = this.maximized ? 5 : 3; + this.partition = d3.layout.partition() + .value(function(d) { + return _.reduce(_this.sortAttrs, function(s, a) { + return s + d[a]; + }, 0); + }); + this.graphOptions = { + grid: { + //hoverable : true, + borderWidth: { + top: 1, + right: 1, + bottom: 1, + left: 1 + }, + borderColor: "#ddd" + }, + xaxis: { + min: 0, + max: this.dataLength - 1, + tickFormatter: this.timeTickFormatter(this.dataLength), + axisLabel: "Time (minutes)", + axisLabelColour: "#000" + }, + yaxis: { + min: 0 + }, + series: { + lines: { + show: true, + fill: false + }, + shadowSize: 0 // Drawing is faster without shadows + } + }; + this.dataGraphOptions = { + grid: { + //hoverable : true, + borderWidth: { + top: 1, + right: 1, + bottom: 1, + left: 1 + }, + borderColor: "#ddd" + }, + xaxis: { + min: 0, + max: this.dataLength - 1, + tickFormatter: this.timeTickFormatter(this.dataLength), + axisLabel: "Time (minutes)", + axisLabelColour: "#000" + }, + yaxis: { + min: 0, + tickFormatter: this.valueTickFormatter + }, + series: { + lines: { + show: true, + fill: false + }, + shadowSize: 0 // Drawing is faster without shadows + } + }; this.LineGraphsDefaultOptions = { showLines: true, animation: { @@ -179,364 +191,467 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ } }; - this.TopDisksChart = null; - this.TopDisksChartOptions = { -animation: { -duration: 1500, -easing: 'linear' -}, + this.TopDisksChart = null; + this.TopDisksChartOptions = { + animation: { + duration: 1500, + easing: 'linear' + }, responsive: true, - title: { - display: true, - text: 'Top Disks', - padding: 5 + scale: { + ticks: { + display: false + } } -}; - this.TopDisksChartData = { - labels: this.labels, - datasets: [] - }; - - this.DiskReadWriteChart = null; - this.DiskReadWriteChartOptions = this.LineGraphsDefaultOptions; - this.DiskReadWriteChartData = { - label: this.Diskslabels[0], + }; + this.TopDisksChartData = { + labels: this.Diskslabels, + datasets: [] + }; + + this.DiskReadWriteChart = null; + this.DiskReadWriteChartOptions = this.LineGraphsDefaultOptions; + this.DiskReadWriteChartData = { + datasets: [{ + label: this.Diskslabels[0], backgroundColor: 'rgba(' + this.colors2[0] + ', 0.4)', borderColor: 'rgba(' + this.colors2[0] + ', 1)', data: [] }, { label: this.Diskslabels[1], - backgroundColor: 'rgba(' + this.colors[1] + ', 0.4)', + backgroundColor: 'rgba(' + this.colors2[1] + ', 0.4)', borderColor: 'rgba(' + this.colors2[1] + ', 1)', data: [] - }; - - this.DiskkBChart = null; - this.DiskkBChartOptions = this.LineGraphsDefaultOptions; - this.DiskkBChartOptions.scales.yAxes[0].ticks.callback = function(value) { - return humanize.filesize(value); - } - this.DiskkBChartData = { - label: this.Diskslabels[2], + }] + }; + + this.DiskkBChart = null; + this.DiskkBChartOptions = this.LineGraphsDefaultOptions; + this.DiskkBChartOptions.scales.yAxes[0].ticks.callback = function(value) { + return humanize.filesize(value); + } + this.DiskkBChartData = { + datasets: [{ + label: this.Diskslabels[2], backgroundColor: 'rgba(' + this.colors2[0] + ', 0.4)', borderColor: 'rgba(' + this.colors2[0] + ', 1)', data: [] }, { label: this.Diskslabels[3], - backgroundColor: 'rgba(' + this.colors[1] + ', 0.4)', + backgroundColor: 'rgba(' + this.colors2[1] + ', 0.4)', borderColor: 'rgba(' + this.colors2[1] + ', 1)', data: [] + }] + }; + }, + + + render: function() { + var _this = this; + // call render of base + this.constructor.__super__.render.apply(this, arguments); + $(this.el).html(this.template({ + module_name: this.module_name, + displayName: this.displayName, + maximized: this.maximized + })); + + this.$('.diskSortAttr').change(function(event) { + var cbox = $(event.currentTarget); + var v = cbox.val(); + if (cbox.is(':checked')) { + if (_.indexOf(_this.sortAttrs, v) == -1) { + _this.sortAttrs.push(v); + } + } else { + if (_.indexOf(_this.sortAttrs, v) != -1) { + if (_this.sortAttrs.length > 1) { + _this.sortAttrs = _.without(_this.sortAttrs, v); + } else { + // dont allow the last attr to be unchecked + cbox.prop('checked', true); + } + } + } + }); + this.$('#top-disks-ph').css('width', this.topDisksWidth); + this.topDisksPh = d3.select(this.el).select('#top-disks-ph'); + + this.topDisksVis = this.topDisksPh + .append('svg:svg') + .attr('id', 'top-disks-svg') + .attr('height', 75) + .attr('width', this.topDisksWidth); + + this.disks.fetch({ + success: function(collection, response, options) { + _this.initializeDisksData(); + _this.initTopDisksChart(); + RockStorSocket.addListener(_this.getData, _this, 'diskWidget:top_disks'); + } + }); + return this; + }, + + // initialize disksData with disk names and empty value arrays + initializeDisksData: function() { + var _this = this; + this.disks.each(function(disk) { + var name = disk.get('name'); + var temp_name = disk.get('temp_name'); + _this.disksData[name] = []; + for (var i = 0; i < _this.dataLength; i++) { + _this.disksData[name].push(_this.genEmptyDiskData()); } - }; - }, - - - render: function() { - var _this = this; - // call render of base - this.constructor.__super__.render.apply(this, arguments); - $(this.el).html(this.template({ - module_name: this.module_name, - displayName: this.displayName, - maximized: this.maximized - })); - - this.$('.diskSortAttr').change(function(event) { - var cbox = $(event.currentTarget); - var v = cbox.val(); - if (cbox.is(':checked')) { - if (_.indexOf(_this.sortAttrs, v) == -1 ) { - _this.sortAttrs.push(v); + }); + if (this.maximized) { + // initialize disk-select + this.$('#disk-details-ph').html(this.diskUtilSelect({ + disks: this.disks.toJSON() + })); + if (this.selectedDisk) { + this.$('#disk-select').val(this.selectedDisk); + } + this.$('#disk-select').change(function(event) { + _this.selectedDisk = _this.$('#disk-select').val(); + }); + } else { + this.$('#disk-details-ph').html("Expand for details"); + } + + }, + + getData: function(data) { + var _this = this; + if (!_this.graphRendered) { + _this.initGraphs(); + _this.graphRendered = true; } - } else { - if (_.indexOf(_this.sortAttrs, v) != -1 ) { - if (_this.sortAttrs.length > 1) { - _this.sortAttrs = _.without(_this.sortAttrs, v); - } else { - // dont allow the last attr to be unchecked - cbox.prop('checked', true); - } + _this.startTime = new Date().getTime(); + _this.update(data); + }, + + initTopDisksChart: function() { + var _this = this; + for (var i = 0; i < this.numTop; i++) { + var dataset = { + label: '', + borderWidth: 1, + borderColor: 'rgba(' + _this.TopDiskscolors[i] + ', 1)', + backgroundColor: 'rgba(' + _this.TopDiskscolors[i] + ', 0.1)', + pointBackgroundColor: 'rgba(' + _this.TopDiskscolors[i] + ', 1)', + pointBorderColor: '#fff', + pointHoverBackgroundColor: '#fff', + pointHoverBorderColor: 'rgba(' + _this.TopDiskscolors[i] + ', 1)', + data: [null, null, null, null, null, null, null] + }; + _this.TopDisksChartData.datasets.push(dataset); } - } - }); - this.$('#top-disks-ph').css('width', this.topDisksWidth); - this.topDisksPh = d3.select(this.el).select('#top-disks-ph'); - - this.topDisksVis = this.topDisksPh - .append('svg:svg') - .attr('id', 'top-disks-svg') - .attr('height', 75) - .attr('width', this.topDisksWidth); - - this.disks.fetch({ - success: function(collection, response, options) { - _this.initializeDisksData(); - RockStorSocket.addListener(_this.getData, _this, 'diskWidget:top_disks'); - } - }); - return this; - }, - - // initialize disksData with disk names and empty value arrays - initializeDisksData: function() { - var _this = this; - this.disks.each(function(disk) { - var name = disk.get('name'); - var temp_name = disk.get('temp_name'); - _this.disksData[name] = []; - for (var i=0; i<_this.dataLength; i++) { - _this.disksData[name].push(_this.genEmptyDiskData()); - } - }); - if (this.maximized) { - // initialize disk-select - this.$('#disk-details-ph').html(this.diskUtilSelect({ - disks: this.disks.toJSON() - })); - if (this.selectedDisk) { - this.$('#disk-select').val(this.selectedDisk); - } - this.$('#disk-select').change(function(event) { - _this.selectedDisk = _this.$('#disk-select').val(); - }); - } else { - this.$('#disk-details-ph').html("Expand for details"); - } - }, - - getData: function(data) { - var _this = this; - _this.startTime = new Date().getTime(); - _this.update(data); - }, - - update: function(data) { - this.updateDisksData(data); - this.updateTopDisks(); - this.renderTopDisks(); - if (this.maximized) this.renderDiskGraph(); - }, - - updateDisksData: function(data) { - var _this = this; - _.each(data, function(d) { - _this.disksData[d.name].push(d); - }); - _.each(_.keys(_this.disksData), function(diskName) { - var diskData = _this.disksData[diskName]; - if (diskData.length > _this.dataLength) { - diskData.splice(0, diskData.length - _this.dataLength); - } - }); - }, - - // sorts latest values in disksData by sortAttrs and returns top n - updateTopDisks: function() { - var _this = this; - var tmp = _.map(_.keys(_this.disksData), function(k) { - return _this.disksData[k][_this.dataLength - 1]; - }); - tmp = _.reject(tmp, function(d) { - var x = _.reduce(_this.sortAttrs, function(s, a) { return s + d[a]; }, 0); - return x == 0; - }); - var sorted = _.sortBy(tmp, function(d) { - return _.reduce(_this.sortAttrs, function(s, a) { return s + d[a]; }, 0); - }).reverse(); - this.topDisks = sorted.slice(0,_this.numTop); - }, - - // render bars for top disks. the width of each bar is proportional - // to the sort value. Use d3 partition layout to calculate coordinates. - renderTopDisks: function() { - var _this = this; - var w = this.topDisksWidth; - var h = this.topDisksHeight; - // calculate total value of all sortAttrs over all disks - var totalAttr = _.reduce(this.topDisks, function(total, disk) { - return total + _.reduce(_this.sortAttrs, function(s, a) { - return s + disk[a]; - }, 0); - }, 0); - this.$('#attr-total').html(totalAttr); - - if (this.topDisks.length == 0) { - if (!this.noDisks) { - this.$('#top-disks-svg').empty(); - this.topDisksVis.append('g') - .append('svg:text') - .attr("transform", function(d) { - return 'translate(0,' + 32 + ')'; - }) - .text('No disk activity') - .attr('fill-opacity', 1.0); - this.noDisks = true; - } - } else { - if (this.noDisks) { - // clear no disk activity msg - this.$('#top-disks-svg').empty(); - } - this.noDisks = false; - var root = {name: 'root', - reads_completed: 0, - writes_completed: 0, - children: this.topDisks - }; - var x = d3.scale.linear().range([0, w]); - var y = d3.scale.linear().range([0, h]); - var diskNodes = this.partition.nodes(root); - var kx = w / root.dx, ky = h / 1; - var duration = 200; - - var disk = this.topDisksVis.selectAll('g') - .data(diskNodes, function(d, i) { return d.name; }); - - // Create g elements - each g element is positioned at appropriate - // x coordinate, and contains a rect with width acc to disk sort value, - // and a text element with the disk name - var diskEnter = disk - .enter().append('svg:g'); - - diskEnter.append('svg:rect') - .attr('class', 'diskRect') - .attr('height', function(d) { - if (d.name == 'root') { - return 0; + }, + update: function(data) { + + this.updateDisksData(data); + this.updateTopDisks(); + this.sortDisks(); + this.renderRadar(); + this.renderTopDisks(); + if (this.maximized) this.renderDiskGraph(); + }, + initGraphs: function() { + + var _this = this; + _this.TopDisksChart = new Chart(this.$('#top-disks-chart'), { + type: 'radar', + data: _this.TopDisksChartData, + options: _this.TopDisksChartOptions + }); + }, + + renderRadar: function() { + + var _this = this; + for (var i=0; i < _this.numTop; i++) { + var data = []; + data.push(_this.TopDisksRadar[i].ms_ios); + data.push(_this.TopDisksRadar[i].sectors_written * 512); + data.push(_this.TopDisksRadar[i].writes_completed); + data.push(_this.TopDisksRadar[i].ms_writing); + data.push(_this.TopDisksRadar[i].ms_reading); + data.push(_this.TopDisksRadar[i].reads_completed); + data.push(_this.TopDisksRadar[i].sectors_read * 512); + _this.TopDisksChartData.datasets[i].data = data; + _this.TopDisksChartData.datasets[i].label = _this.TopDisksRadar[i].name; + } + _this.TopDisksChart.update(); + }, + + //Chart.js radar chart don't have multiple scales + //so we have to normalize our data + // + normalizeData: function() { + + }, + + sortDisks: function() { + + var _this = this; + //this.TopDisksRadar + var tmp = _.map(_.keys(_this.disksData), function(k) { + return _this.disksData[k][_this.dataLength - 1]; + }); + var sorter = _this.sortAttrs[0]; + _this.TopDisksRadar = _.sortBy(tmp, function(d) { + return d[sorter]; + }).reverse(); + }, + + updateDisksData: function(data) { + var _this = this; + _.each(data, function(d) { + _this.disksData[d.name].push(d); + _this.disksData[d.name].shift(); + }); + _.each(_.keys(_this.disksData), function(diskName) { + var diskData = _this.disksData[diskName]; + if (diskData.length > _this.dataLength) { + diskData.splice(0, diskData.length - _this.dataLength); + } + }); + }, + + // sorts latest values in disksData by sortAttrs and returns top n + updateTopDisks: function() { + var _this = this; + var tmp = _.map(_.keys(_this.disksData), function(k) { + return _this.disksData[k][_this.dataLength - 1]; + }); + //console.log(tmp); + tmp = _.reject(tmp, function(d) { + var x = _.reduce(_this.sortAttrs, function(s, a) { + return s + d[a]; + }, 0); + return x == 0; + }); + //console.log(tmp); + var sorted = _.sortBy(tmp, function(d) { + return _.reduce(_this.sortAttrs, function(s, a) { + return s + d[a]; + }, 0); + }).reverse(); + this.topDisks = sorted.slice(0, _this.numTop); + }, + + // render bars for top disks. the width of each bar is proportional + // to the sort value. Use d3 partition layout to calculate coordinates. + renderTopDisks: function() { + var _this = this; + var w = this.topDisksWidth; + var h = this.topDisksHeight; + // calculate total value of all sortAttrs over all disks + var totalAttr = _.reduce(this.topDisks, function(total, disk) { + return total + _.reduce(_this.sortAttrs, function(s, a) { + return s + disk[a]; + }, 0); + }, 0); + this.$('#attr-total').html(totalAttr); + + if (this.topDisks.length == 0) { + if (!this.noDisks) { + this.$('#top-disks-svg').empty(); + this.topDisksVis.append('g') + .append('svg:text') + .attr("transform", function(d) { + return 'translate(0,' + 32 + ')'; + }) + .text('No disk activity') + .attr('fill-opacity', 1.0); + this.noDisks = true; + } } else { - return 25; + if (this.noDisks) { + // clear no disk activity msg + this.$('#top-disks-svg').empty(); + } + this.noDisks = false; + var root = { + name: 'root', + reads_completed: 0, + writes_completed: 0, + children: this.topDisks + }; + var x = d3.scale.linear().range([0, w]); + var y = d3.scale.linear().range([0, h]); + var diskNodes = this.partition.nodes(root); + var kx = w / root.dx, + ky = h / 1; + var duration = 200; + + var disk = this.topDisksVis.selectAll('g') + .data(diskNodes, function(d, i) { + return d.name; + }); + + // Create g elements - each g element is positioned at appropriate + // x coordinate, and contains a rect with width acc to disk sort value, + // and a text element with the disk name + var diskEnter = disk + .enter().append('svg:g'); + + diskEnter.append('svg:rect') + .attr('class', 'diskRect') + .attr('height', function(d) { + if (d.name == 'root') { + return 0; + } else { + return 25; + } + }) + .attr('fill', function(d, i) { + return _this.topDiskColors[i - 1]; + }); + + diskEnter.append("svg:text") + .attr('class', 'diskText') + .attr("transform", function(d) { + return 'translate(0,' + 32 + ')'; + }) + .text(function(d) { + if (d.name == 'root') { + return ''; + } else { + return d.name.split('_').pop(); + } + }) + .attr('fill-opacity', 1.0); + + var diskUpdate = disk.transition() + .duration(duration) + .attr('transform', function(d) { + return 'translate(' + x(d.x) + ',' + y(d.y) + ')'; + }); + + var diskRectUpdate = diskUpdate.select('rect.diskRect') + .attr('width', function(d) { + return (d.dx * w) - 1; + }) + .attr('fill', function(d, i) { + return _this.topDiskColors[i - 1]; + }); + + var diskExit = disk.exit().remove(); + } + }, + + renderDiskGraph: function() { + if (!this.selectedDisk) { + if (this.topDisks.length > 0) { + this.selectedDisk = this.topDisks[0].name; + } else { + this.selectedDisk = this.disks.at(0).get('name'); + } + this.$('#disk-select').val(this.selectedDisk); + } + + var vals = this.disksData[this.selectedDisk]; + var tmpReads = []; + for (var i = 0; i < this.dataLength; i++) { + tmpReads.push([i, vals[i].reads_completed]); + } + var tmpWrites = []; + for (var i = 0; i < this.dataLength; i++) { + tmpWrites.push([i, vals[i].writes_completed]); + } + var series1 = [{ + label: 'Reads', + data: tmpReads, + color: this.colors[0] + }, { + label: 'Writes', + data: tmpWrites, + color: this.colors[1] + }]; + $.plot(this.$('#disk-graph-reads-ph'), series1, this.graphOptions); + + var tmpReadData = []; + for (var i = 0; i < this.dataLength; i++) { + tmpReadData.push([i, vals[i].sectors_read * 512]); } - }) - .attr('fill', function(d,i) { return _this.topDiskColors[i-1]; }); - - diskEnter.append("svg:text") - .attr('class', 'diskText') - .attr("transform", function(d) { - return 'translate(0,' + 32 + ')'; - }) - .text(function(d) { - if (d.name == 'root') { - return ''; + var tmpWriteData = []; + for (var i = 0; i < this.dataLength; i++) { + tmpWriteData.push([i, vals[i].sectors_written * 512]); + } + var series2 = [{ + label: 'KB read', + data: tmpReadData, + color: this.colors[0] + }, { + label: 'KB written', + data: tmpWriteData, + color: this.colors[1] + }]; + $.plot(this.$('#disk-graph-data-ph'), series2, this.dataGraphOptions); + }, + + genEmptyDiskData: function() { + // empty disk data + return { + "reads_completed": 0, + "reads_merged": 0, + "sectors_read": 0, + "ms_reading": 0, + "writes_completed": 0, + "writes_merged": 0, + "sectors_written": 0, + "ms_writing": 0, + "ios_progress": 0, + "ms_ios": 0, + "weighted_ios": 0, + "ts": 0 + }; + }, + + resize: function(event) { + var _this = this; + this.constructor.__super__.resize.apply(this, arguments); + this.topDisksWidth = this.maximized ? 520 : 240; + // maximum number of top disks to display + this.numTop = this.maximized ? 5 : 3; + //this.$('#top-disks-ph').empty(); + this.$('#top-disks-ph').css('width', this.topDisksWidth); + this.topDisksVis.attr('width', this.topDisksWidth); + this.renderTopDisks(); + if (this.maximized) { + this.$('#disk-details-ph').html(this.diskUtilSelect({ + disks: this.disks.toJSON() + })); + if (this.selectedDisk) { + this.$('#disk-select').val(this.selectedDisk); + } + this.$('#disk-select').change(function(event) { + _this.selectedDisk = _this.$('#disk-select').val(); + }); } else { - return d.name.split('_').pop(); + this.$('#disk-details-ph').html("Expand for details"); } - }) - .attr('fill-opacity', 1.0); + }, - var diskUpdate = disk.transition() - .duration(duration) - .attr('transform', function(d) { - return 'translate(' + x(d.x) + ',' + y(d.y) + ')'; - }); + timeTickFormatter: function(dataLength) { + return function(val, axis) { + return ((dataLength / 60) - (parseInt(val / 60))).toString() + ' m'; + }; + }, - var diskRectUpdate = diskUpdate.select('rect.diskRect') - .attr('width', function(d) { return (d.dx * w) - 1; }) - .attr('fill', function(d,i) { return _this.topDiskColors[i-1]; }); + valueTickFormatter: function(val, axis) { + return humanize.filesize(val, 1024, 2); + }, - var diskExit = disk.exit().remove(); - } - }, - - renderDiskGraph: function() { - if (!this.selectedDisk) { - if (this.topDisks.length > 0) { - this.selectedDisk = this.topDisks[0].name; - } else { - this.selectedDisk = this.disks.at(0).get('name'); - } - this.$('#disk-select').val(this.selectedDisk); - } + setSelectedDisk: function(event) { + this.selectedDisk = this.$('#disk-select').val(); + }, - var vals = this.disksData[this.selectedDisk]; - var tmpReads = []; - for (var i=0; iExpand for details"); - } - }, - - timeTickFormatter: function(dataLength) { - return function(val, axis) { - return ((dataLength/60) - (parseInt(val/60))).toString() + ' m'; - }; - }, - - valueTickFormatter: function(val, axis) { - return humanize.filesize(val, 1024, 2); - }, - - setSelectedDisk: function(event) { - this.selectedDisk = this.$('#disk-select').val(); - }, - - cleanup: function() { - RockStorSocket.removeOneListener('diskWidget'); - } }); From 1abd95b666956e4217433325b4f1aaf93be05160 Mon Sep 17 00:00:00 2001 From: Mirko Arena Date: Mon, 10 Oct 2016 22:47:16 +0200 Subject: [PATCH 05/16] #998 Disks Widget - Fixed data_collector emitting 1 disk/time - Now emits disks array length = number of disks --- src/rockstor/smart_manager/data_collector.py | 35 ++++++++++---------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/rockstor/smart_manager/data_collector.py b/src/rockstor/smart_manager/data_collector.py index 8d8d70108..3e4481294 100644 --- a/src/rockstor/smart_manager/data_collector.py +++ b/src/rockstor/smart_manager/data_collector.py @@ -419,6 +419,7 @@ def recv_disconnect(self): def send_top_disks(self): def disk_stats(prev_stats): + disks_stats = [] # invoke body of disk_stats with empty cur_stats stats_file_path = '/proc/diskstats' cur_stats = {} @@ -455,23 +456,23 @@ def disk_stats(prev_stats): else: datum = (float(cur[i]) - float(prev[i]))/interval data.append(datum) - self.emit('diskWidget:top_disks', { - 'key': 'diskWidget:top_disks', 'data': [{ - 'name': disk, - 'reads_completed': data[0], - 'reads_merged': data[1], - 'sectors_read': data[2], - 'ms_reading': data[3], - 'writes_completed': data[4], - 'writes_merged': data[5], - 'sectors_written': data[6], - 'ms_writing': data[7], - 'ios_progress': data[8], - 'ms_ios': data[9], - 'weighted_ios': data[10], - 'ts': str(datetime.utcnow().replace(tzinfo=utc).isoformat()) - }] - }) + disks_stats.append({ + 'name': disk, + 'reads_completed': data[0], + 'reads_merged': data[1], + 'sectors_read': data[2], + 'ms_reading': data[3], + 'writes_completed': data[4], + 'writes_merged': data[5], + 'sectors_written': data[6], + 'ms_writing': data[7], + 'ios_progress': data[8], + 'ms_ios': data[9], + 'weighted_ios': data[10], + 'ts': str(datetime.utcnow().replace(tzinfo=utc).isoformat()) + }) + + self.emit('diskWidget:top_disks',{ 'key': 'diskWidget:top_disks', 'data': disks_stats }) return cur_stats def get_stats(): From c5121a973b2dfa919951ae39599e6089f82cdfab Mon Sep 17 00:00:00 2001 From: Mirko Arena Date: Mon, 10 Oct 2016 23:49:07 +0200 Subject: [PATCH 06/16] #998 Disks Widget - Full working radar chart - required code cleaning, linegraphs and helpers to switch monitoring attributes --- .../js/views/dashboard/disk_utilization.js | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js index 9d2534a70..516a1c006 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js +++ b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js @@ -54,6 +54,7 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ Chart.defaults.global.elements.line.fill = false; Chart.defaults.global.elements.point.radius = 0; Chart.defaults.global.elements.point.hoverRadius = 0; + this.Disksfields = ['ms_ios', 'sectors_written', 'writes_completed', 'ms_writing', 'ms_reading', 'reads_completed', 'sectors_read']; this.Diskslabels = ['ms on I/Os', 'kB written', 'Writes', 'ms writing', 'ms reading', 'Reads', 'kB read']; this.TopDiskscolors = ['242, 0, 0', '36, 229, 84', '41, 108, 232', '232, 200, 41', '146, 41, 232'] this.colors2 = ['77, 175, 74', '55, 126, 184'] @@ -198,9 +199,21 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ easing: 'linear' }, responsive: true, + legend: { + display: true, + position: 'top', + labels: { + boxWidth: 10, + padding: 5, + fontSize: 10 + } + }, scale: { ticks: { - display: false + display: false, + min: 0, + max: 100, + stepSize: 20 } } }; @@ -337,19 +350,19 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ var dataset = { label: '', borderWidth: 1, + fill: true, borderColor: 'rgba(' + _this.TopDiskscolors[i] + ', 1)', backgroundColor: 'rgba(' + _this.TopDiskscolors[i] + ', 0.1)', pointBackgroundColor: 'rgba(' + _this.TopDiskscolors[i] + ', 1)', pointBorderColor: '#fff', pointHoverBackgroundColor: '#fff', pointHoverBorderColor: 'rgba(' + _this.TopDiskscolors[i] + ', 1)', - data: [null, null, null, null, null, null, null] + data: [0, 0, 0, 0, 0, 0, 0] }; _this.TopDisksChartData.datasets.push(dataset); } }, update: function(data) { - this.updateDisksData(data); this.updateTopDisks(); this.sortDisks(); @@ -372,13 +385,9 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ var _this = this; for (var i=0; i < _this.numTop; i++) { var data = []; - data.push(_this.TopDisksRadar[i].ms_ios); - data.push(_this.TopDisksRadar[i].sectors_written * 512); - data.push(_this.TopDisksRadar[i].writes_completed); - data.push(_this.TopDisksRadar[i].ms_writing); - data.push(_this.TopDisksRadar[i].ms_reading); - data.push(_this.TopDisksRadar[i].reads_completed); - data.push(_this.TopDisksRadar[i].sectors_read * 512); + _.each(_this.Disksfields, function(field) { + data.push(_this.normalizeData(field, _this.TopDisksRadar[i][field])); + }); _this.TopDisksChartData.datasets[i].data = data; _this.TopDisksChartData.datasets[i].label = _this.TopDisksRadar[i].name; } @@ -387,9 +396,14 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ //Chart.js radar chart don't have multiple scales //so we have to normalize our data - // - normalizeData: function() { - + //data normalization has new_x = (x - x_min) / (x_max -x_min) and returns x [0..1] + //we assume our x_min = 0, so new_x = x /x_max + normalizeData: function(field, val) { + + var _this = this; + var val_max = _.max(_.pluck(_this.TopDisksRadar, field)); + var new_val = val == 0 ? 0 : (val * 100 / val_max).toFixed(2); //we use a 0..100 range with 2 decimals + return new_val; }, sortDisks: function() { From ccd389ab08e28815381de7d716d510e35bb616b4 Mon Sep 17 00:00:00 2001 From: Mirko Arena Date: Tue, 11 Oct 2016 00:08:51 +0200 Subject: [PATCH 07/16] #998 Disks Widget - Code cleaning, removed old d3.js topdisks bar, no more g errors :) --- .../dashboard/widgets/disk_utilization.jst | 38 +++- .../js/views/dashboard/disk_utilization.js | 184 +++--------------- 2 files changed, 55 insertions(+), 167 deletions(-) diff --git a/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_utilization.jst b/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_utilization.jst index cd7924749..e7af5c5f6 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_utilization.jst +++ b/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_utilization.jst @@ -1,17 +1,37 @@ - + +
-
{{ displayName }}
-
- - -
+
{{ displayName }}
+
+ + +
-

Top Disks

-
-
+
+ +
Sort by: Reads  diff --git a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js index 516a1c006..8c2c31817 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js +++ b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js @@ -28,6 +28,7 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ initialize: function() { + RockStorSocket.diskWidget = io.connect('/disk-widget', { 'secure': true, 'force new connection': true @@ -58,7 +59,6 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ this.Diskslabels = ['ms on I/Os', 'kB written', 'Writes', 'ms writing', 'ms reading', 'Reads', 'kB read']; this.TopDiskscolors = ['242, 0, 0', '36, 229, 84', '41, 108, 232', '232, 200, 41', '146, 41, 232'] this.colors2 = ['77, 175, 74', '55, 126, 184'] - this.TopDisksRadar = []; this.colors = ["#4DAF4A", "#377EB8"]; // disks data is a map of diskname to array of values of length // dataLength @@ -260,6 +260,7 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ render: function() { + var _this = this; // call render of base this.constructor.__super__.render.apply(this, arguments); @@ -287,14 +288,6 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ } } }); - this.$('#top-disks-ph').css('width', this.topDisksWidth); - this.topDisksPh = d3.select(this.el).select('#top-disks-ph'); - - this.topDisksVis = this.topDisksPh - .append('svg:svg') - .attr('id', 'top-disks-svg') - .attr('height', 75) - .attr('width', this.topDisksWidth); this.disks.fetch({ success: function(collection, response, options) { @@ -303,15 +296,16 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ RockStorSocket.addListener(_this.getData, _this, 'diskWidget:top_disks'); } }); + return this; }, // initialize disksData with disk names and empty value arrays initializeDisksData: function() { + var _this = this; this.disks.each(function(disk) { var name = disk.get('name'); - var temp_name = disk.get('temp_name'); _this.disksData[name] = []; for (var i = 0; i < _this.dataLength; i++) { _this.disksData[name].push(_this.genEmptyDiskData()); @@ -334,17 +328,8 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ }, - getData: function(data) { - var _this = this; - if (!_this.graphRendered) { - _this.initGraphs(); - _this.graphRendered = true; - } - _this.startTime = new Date().getTime(); - _this.update(data); - }, - initTopDisksChart: function() { + var _this = this; for (var i = 0; i < this.numTop; i++) { var dataset = { @@ -362,14 +347,26 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ _this.TopDisksChartData.datasets.push(dataset); } }, + + getData: function(data) { + + var _this = this; + if (!_this.graphRendered) { + _this.initGraphs(); + _this.graphRendered = true; + } + _this.startTime = new Date().getTime(); + _this.update(data); + }, + update: function(data) { + this.updateDisksData(data); - this.updateTopDisks(); this.sortDisks(); - this.renderRadar(); - this.renderTopDisks(); + this.updateTopDisksChart(); if (this.maximized) this.renderDiskGraph(); }, + initGraphs: function() { var _this = this; @@ -380,16 +377,16 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ }); }, - renderRadar: function() { + updateTopDisksChart: function() { var _this = this; for (var i=0; i < _this.numTop; i++) { var data = []; _.each(_this.Disksfields, function(field) { - data.push(_this.normalizeData(field, _this.TopDisksRadar[i][field])); + data.push(_this.normalizeData(field, _this.topDisks[i][field])); }); _this.TopDisksChartData.datasets[i].data = data; - _this.TopDisksChartData.datasets[i].label = _this.TopDisksRadar[i].name; + _this.TopDisksChartData.datasets[i].label = _this.topDisks[i].name; } _this.TopDisksChart.update(); }, @@ -401,7 +398,7 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ normalizeData: function(field, val) { var _this = this; - var val_max = _.max(_.pluck(_this.TopDisksRadar, field)); + var val_max = _.max(_.pluck(_this.topDisks, field)); var new_val = val == 0 ? 0 : (val * 100 / val_max).toFixed(2); //we use a 0..100 range with 2 decimals return new_val; }, @@ -409,151 +406,22 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ sortDisks: function() { var _this = this; - //this.TopDisksRadar var tmp = _.map(_.keys(_this.disksData), function(k) { return _this.disksData[k][_this.dataLength - 1]; }); var sorter = _this.sortAttrs[0]; - _this.TopDisksRadar = _.sortBy(tmp, function(d) { + _this.topDisks = _.sortBy(tmp, function(d) { return d[sorter]; }).reverse(); }, updateDisksData: function(data) { + var _this = this; _.each(data, function(d) { _this.disksData[d.name].push(d); _this.disksData[d.name].shift(); }); - _.each(_.keys(_this.disksData), function(diskName) { - var diskData = _this.disksData[diskName]; - if (diskData.length > _this.dataLength) { - diskData.splice(0, diskData.length - _this.dataLength); - } - }); - }, - - // sorts latest values in disksData by sortAttrs and returns top n - updateTopDisks: function() { - var _this = this; - var tmp = _.map(_.keys(_this.disksData), function(k) { - return _this.disksData[k][_this.dataLength - 1]; - }); - //console.log(tmp); - tmp = _.reject(tmp, function(d) { - var x = _.reduce(_this.sortAttrs, function(s, a) { - return s + d[a]; - }, 0); - return x == 0; - }); - //console.log(tmp); - var sorted = _.sortBy(tmp, function(d) { - return _.reduce(_this.sortAttrs, function(s, a) { - return s + d[a]; - }, 0); - }).reverse(); - this.topDisks = sorted.slice(0, _this.numTop); - }, - - // render bars for top disks. the width of each bar is proportional - // to the sort value. Use d3 partition layout to calculate coordinates. - renderTopDisks: function() { - var _this = this; - var w = this.topDisksWidth; - var h = this.topDisksHeight; - // calculate total value of all sortAttrs over all disks - var totalAttr = _.reduce(this.topDisks, function(total, disk) { - return total + _.reduce(_this.sortAttrs, function(s, a) { - return s + disk[a]; - }, 0); - }, 0); - this.$('#attr-total').html(totalAttr); - - if (this.topDisks.length == 0) { - if (!this.noDisks) { - this.$('#top-disks-svg').empty(); - this.topDisksVis.append('g') - .append('svg:text') - .attr("transform", function(d) { - return 'translate(0,' + 32 + ')'; - }) - .text('No disk activity') - .attr('fill-opacity', 1.0); - this.noDisks = true; - } - } else { - if (this.noDisks) { - // clear no disk activity msg - this.$('#top-disks-svg').empty(); - } - this.noDisks = false; - var root = { - name: 'root', - reads_completed: 0, - writes_completed: 0, - children: this.topDisks - }; - var x = d3.scale.linear().range([0, w]); - var y = d3.scale.linear().range([0, h]); - var diskNodes = this.partition.nodes(root); - var kx = w / root.dx, - ky = h / 1; - var duration = 200; - - var disk = this.topDisksVis.selectAll('g') - .data(diskNodes, function(d, i) { - return d.name; - }); - - // Create g elements - each g element is positioned at appropriate - // x coordinate, and contains a rect with width acc to disk sort value, - // and a text element with the disk name - var diskEnter = disk - .enter().append('svg:g'); - - diskEnter.append('svg:rect') - .attr('class', 'diskRect') - .attr('height', function(d) { - if (d.name == 'root') { - return 0; - } else { - return 25; - } - }) - .attr('fill', function(d, i) { - return _this.topDiskColors[i - 1]; - }); - - diskEnter.append("svg:text") - .attr('class', 'diskText') - .attr("transform", function(d) { - return 'translate(0,' + 32 + ')'; - }) - .text(function(d) { - if (d.name == 'root') { - return ''; - } else { - return d.name.split('_').pop(); - } - }) - .attr('fill-opacity', 1.0); - - var diskUpdate = disk.transition() - .duration(duration) - .attr('transform', function(d) { - return 'translate(' + x(d.x) + ',' + y(d.y) + ')'; - }); - - var diskRectUpdate = diskUpdate.select('rect.diskRect') - .attr('width', function(d) { - return (d.dx * w) - 1; - }) - .attr('fill', function(d, i) { - return _this.topDiskColors[i - 1]; - }); - - var diskExit = disk.exit().remove(); - } }, renderDiskGraph: function() { From 0cfcd2707640dcf4acedf3dab602afb4676883c7 Mon Sep 17 00:00:00 2001 From: Mirko Arena Date: Tue, 11 Oct 2016 09:39:10 +0200 Subject: [PATCH 08/16] #998 Disks Widget - Ensure top disks number always max equal to available disks --- .../js/views/dashboard/disk_utilization.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js index 8c2c31817..f929b2d78 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js +++ b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js @@ -380,7 +380,9 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ updateTopDisksChart: function() { var _this = this; - for (var i=0; i < _this.numTop; i++) { + //If avail disks < numTop, use only avail disks + var num_disks = Object.keys(_this.disksData).length < _this.numTop ? Object.keys(_this.disksData).length : _this.numTop; + for (var i=0; i < num_disks; i++) { var data = []; _.each(_this.Disksfields, function(field) { data.push(_this.normalizeData(field, _this.topDisks[i][field])); @@ -493,15 +495,12 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ }, resize: function(event) { + var _this = this; this.constructor.__super__.resize.apply(this, arguments); this.topDisksWidth = this.maximized ? 520 : 240; // maximum number of top disks to display this.numTop = this.maximized ? 5 : 3; - //this.$('#top-disks-ph').empty(); - this.$('#top-disks-ph').css('width', this.topDisksWidth); - this.topDisksVis.attr('width', this.topDisksWidth); - this.renderTopDisks(); if (this.maximized) { this.$('#disk-details-ph').html(this.diskUtilSelect({ disks: this.disks.toJSON() @@ -515,6 +514,7 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ } else { this.$('#disk-details-ph').html("Expand for details"); } + _this.TopDisksChart.resize(); }, timeTickFormatter: function(dataLength) { From eda822be3b1c672bf0c087a478f3afe94336ca97 Mon Sep 17 00:00:00 2001 From: Mirko Arena Date: Tue, 11 Oct 2016 17:52:30 +0200 Subject: [PATCH 09/16] #998 Disks Widget - Got full working Disk widget, both Top disks and single disk charts - to @schakrava: almost complete, code cleaning - beautify + special topdisk selection algo :) --- .../js/views/dashboard/disk_utilization.js | 231 +++++++++++++----- 1 file changed, 168 insertions(+), 63 deletions(-) diff --git a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js index f929b2d78..f9a4e16dd 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js +++ b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js @@ -55,10 +55,11 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ Chart.defaults.global.elements.line.fill = false; Chart.defaults.global.elements.point.radius = 0; Chart.defaults.global.elements.point.hoverRadius = 0; + this.Disksfields = ['ms_ios', 'sectors_written', 'writes_completed', 'ms_writing', 'ms_reading', 'reads_completed', 'sectors_read']; this.Diskslabels = ['ms on I/Os', 'kB written', 'Writes', 'ms writing', 'ms reading', 'Reads', 'kB read']; this.TopDiskscolors = ['242, 0, 0', '36, 229, 84', '41, 108, 232', '232, 200, 41', '146, 41, 232'] - this.colors2 = ['77, 175, 74', '55, 126, 184'] + this.SingleDiskcolors = ['7, 233, 7', '21, 124, 217', '255, 184, 7', '255, 25, 7'] this.colors = ["#4DAF4A", "#377EB8"]; // disks data is a map of diskname to array of values of length // dataLength @@ -143,29 +144,48 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ shadowSize: 0 // Drawing is faster without shadows } }; - this.LineGraphsDefaultOptions = { - showLines: true, - animation: { - duration: 1000, - easing: 'linear' - }, + this.SingleDiskChart = null; + this.SingleDiskChartOptions = { + animation: false, responsive: true, - legend: { - display: false + legend: { + display: true, + position: 'bottom', + labels: { + boxWidth: 10, + padding: 5, + fontSize: 10 + } }, scales: { yAxes: [{ + id: 'IOs', position: 'left', scaleLabel: { - display: true, - fontSize: 11, - labelString: 'Data' + display: false }, ticks: { fontSize: 9, beginAtZero: true, min: 0 }, + gridLines: { + drawTicks: true + } + }, { + id: 'Data', + position: 'right', + scaleLabel: { + display: false + }, + ticks: { + fontSize: 9, + beginAtZero: true, + min: 0, + callback: function(value) { + return humanize.filesize(value); + } + }, gridLines: { drawTicks: false } @@ -191,6 +211,34 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ }] } }; + this.SingleDiskChartData = { + labels: [], + datasets: [{ + label: this.Diskslabels[5], + yAxisID: 'IOs', + backgroundColor: 'rgba(' + this.SingleDiskcolors[0] + ', 0.4)', + borderColor: 'rgba(' + this.SingleDiskcolors[0] + ', 1)', + data: [] + }, { + label: this.Diskslabels[2], + yAxisID: 'IOs', + backgroundColor: 'rgba(' + this.SingleDiskcolors[1] + ', 0.4)', + borderColor: 'rgba(' + this.SingleDiskcolors[1] + ', 1)', + data: [] + }, { + label: this.Diskslabels[6], + yAxisID: 'Data', + backgroundColor: 'rgba(' + this.SingleDiskcolors[2] + ', 0.4)', + borderColor: 'rgba(' + this.SingleDiskcolors[2] + ', 1)', + data: [] + }, { + label: this.Diskslabels[1], + yAxisID: 'Data', + backgroundColor: 'rgba(' + this.SingleDiskcolors[3] + ', 0.4)', + borderColor: 'rgba(' + this.SingleDiskcolors[3] + ', 1)', + data: [] + }] + } this.TopDisksChart = null; this.TopDisksChartOptions = { @@ -201,7 +249,7 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ responsive: true, legend: { display: true, - position: 'top', + position: 'bottom', labels: { boxWidth: 10, padding: 5, @@ -222,40 +270,7 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ datasets: [] }; - this.DiskReadWriteChart = null; - this.DiskReadWriteChartOptions = this.LineGraphsDefaultOptions; - this.DiskReadWriteChartData = { - datasets: [{ - label: this.Diskslabels[0], - backgroundColor: 'rgba(' + this.colors2[0] + ', 0.4)', - borderColor: 'rgba(' + this.colors2[0] + ', 1)', - data: [] - }, { - label: this.Diskslabels[1], - backgroundColor: 'rgba(' + this.colors2[1] + ', 0.4)', - borderColor: 'rgba(' + this.colors2[1] + ', 1)', - data: [] - }] - }; - - this.DiskkBChart = null; - this.DiskkBChartOptions = this.LineGraphsDefaultOptions; - this.DiskkBChartOptions.scales.yAxes[0].ticks.callback = function(value) { - return humanize.filesize(value); - } - this.DiskkBChartData = { - datasets: [{ - label: this.Diskslabels[2], - backgroundColor: 'rgba(' + this.colors2[0] + ', 0.4)', - borderColor: 'rgba(' + this.colors2[0] + ', 1)', - data: [] - }, { - label: this.Diskslabels[3], - backgroundColor: 'rgba(' + this.colors2[1] + ', 0.4)', - borderColor: 'rgba(' + this.colors2[1] + ', 1)', - data: [] - }] - }; + this.initHandlebarHelpers(); }, @@ -292,7 +307,8 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ this.disks.fetch({ success: function(collection, response, options) { _this.initializeDisksData(); - _this.initTopDisksChart(); + _this.initTopDisksData(); + _this.initSingleDiskData(); RockStorSocket.addListener(_this.getData, _this, 'diskWidget:top_disks'); } }); @@ -328,10 +344,11 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ }, - initTopDisksChart: function() { + initTopDisksData: function() { var _this = this; - for (var i = 0; i < this.numTop; i++) { + var num_disks = Object.keys(_this.disksData).length < _this.numTop ? Object.keys(_this.disksData).length : _this.numTop; + for (var i = 0; i < num_disks; i++) { var dataset = { label: '', borderWidth: 1, @@ -347,27 +364,47 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ _this.TopDisksChartData.datasets.push(dataset); } }, + + initSingleDiskData: function() { + + var _this = this; + for (var i= 0; i < _this.dataLength; i++) { + _.each(_this.SingleDiskChartData.datasets, function(dataset) { + dataset.data.push(null); + }); + _this.SingleDiskChartData.labels.push(''); + } + }, getData: function(data) { var _this = this; - if (!_this.graphRendered) { - _this.initGraphs(); - _this.graphRendered = true; - } - _this.startTime = new Date().getTime(); _this.update(data); }, update: function(data) { - this.updateDisksData(data); - this.sortDisks(); - this.updateTopDisksChart(); - if (this.maximized) this.renderDiskGraph(); + var _this = this; + _this.updateDisksData(data); + _this.sortDisks(); + + if (!_this.TopDisksgraphRendered) { + _this.initTopDisksGraph(); + _this.TopDisksgraphRendered = true; + } + _this.updateTopDisksChart(); + + if (_this.maximized) { + if (!_this.SingleDiskgraphRendered) { + _this.initSingleDiskGraph(); + _this.SingleDiskgraphRendered = true; + } + _this.updateSingleDiskChart(); + this.renderDiskGraph(); + } }, - initGraphs: function() { + initTopDisksGraph: function() { var _this = this; _this.TopDisksChart = new Chart(this.$('#top-disks-chart'), { @@ -376,6 +413,16 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ options: _this.TopDisksChartOptions }); }, + + initSingleDiskGraph: function() { + + var _this = this; + _this.SingleDiskChart = new Chart(this.$('#single-disk-chart'), { + type: 'line', + data: _this.SingleDiskChartData, + options: _this.SingleDiskChartOptions + }); + }, updateTopDisksChart: function() { @@ -392,6 +439,45 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ } _this.TopDisksChart.update(); }, + + updateSingleDiskChart: function() { + + var _this = this; + if (!_this.selectedDisk) { + if (_this.topDisks.length > 0) { + _this.selectedDisk = _this.topDisks[0].name; + } else { + _this.selectedDisk = _this.disks.at(0).get('name'); + } + this.$('#disk-select').val(_this.selectedDisk); + } + var current_disk = _this.disksData[_this.selectedDisk]; + var singlediskdata = { + reads_completed: [], + writes_completed: [], + sectors_read: [], + sectors_written: [], + }; + var singledisklabels = []; + + for (var i = 0; i < _this.dataLength; i++) { + _.each(singlediskdata, function(dataval, datakey) { + var multiplier = datakey.indexOf('sectors') > -1 ? 512 : 1; + singlediskdata[datakey].push(current_disk[i][datakey] * multiplier); + }); + var csecs = moment(current_disk[i].ts).format('s'); + var label = ''; + if (csecs % 30 === 0) { + label = csecs == '0' ? moment(current_disk[i].ts).format('HH:mm') : moment(current_disk[i].ts).format(':ss'); + } + singledisklabels.push(label); + } + _.each(_.values(singlediskdata), function(val, index) { + _this.SingleDiskChartData.datasets[index].data = val; + }); + _this.SingleDiskChartData.labels = singledisklabels; + _this.SingleDiskChart.update(); + }, //Chart.js radar chart don't have multiple scales //so we have to normalize our data @@ -490,7 +576,7 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ "ios_progress": 0, "ms_ios": 0, "weighted_ios": 0, - "ts": 0 + "ts": '' }; }, @@ -526,14 +612,33 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ valueTickFormatter: function(val, axis) { return humanize.filesize(val, 1024, 2); }, - + setSelectedDisk: function(event) { this.selectedDisk = this.$('#disk-select').val(); }, cleanup: function() { RockStorSocket.removeOneListener('diskWidget'); - } + }, + + initHandlebarHelpers: function(){ + Handlebars.registerHelper('getAdminUsers', function(adminUsers){ + var html = ''; + var userNames = _.reduce(adminUsers, function(s, user, i, list) { + if (i < (list.length-1)) { + return s + user.username + ','; + } else{ + return s + user.username; + } + }, ''); + if(userNames.length != 0){ + html += userNames; + }else { + html += ' --'; + } + return new Handlebars.SafeString(html); + }); + } }); From e6dd21480a254a5183470553317ded0c8581217b Mon Sep 17 00:00:00 2001 From: Mirko Arena Date: Tue, 11 Oct 2016 18:06:43 +0200 Subject: [PATCH 10/16] #998 Disks Widget - Completely removed all disk widget charts, all working. Adding sort algos and make charts fit inside widget --- .../js/views/dashboard/disk_utilization.js | 151 +----------------- 1 file changed, 5 insertions(+), 146 deletions(-) diff --git a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js index f9a4e16dd..70b83fcc1 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js +++ b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js @@ -37,14 +37,7 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ this.constructor.__super__.initialize.apply(this, arguments); this.template = window.JST.dashboard_widgets_disk_utilization; this.diskUtilSelect = window.JST.dashboard_widgets_disk_util_select; - this.dataLength = 300; - this.topDiskColors = []; - // calculate colors from dark to light for top disks - var startColor = d3.rgb('#CC6104'); - for (var i = 0; i < 5; i++) { - this.topDiskColors.push(startColor.toString()); - startColor = startColor.brighter(2); - } + Chart.defaults.global.tooltips.enabled = false; Chart.defaults.global.elements.line.tension = 0.2; Chart.defaults.global.elements.line.borderCapStyle = 'butt'; @@ -56,11 +49,13 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ Chart.defaults.global.elements.point.radius = 0; Chart.defaults.global.elements.point.hoverRadius = 0; + this.numTop = this.maximized ? 5 : 3; + this.dataLength = 300; this.Disksfields = ['ms_ios', 'sectors_written', 'writes_completed', 'ms_writing', 'ms_reading', 'reads_completed', 'sectors_read']; this.Diskslabels = ['ms on I/Os', 'kB written', 'Writes', 'ms writing', 'ms reading', 'Reads', 'kB read']; this.TopDiskscolors = ['242, 0, 0', '36, 229, 84', '41, 108, 232', '232, 200, 41', '146, 41, 232'] this.SingleDiskcolors = ['7, 233, 7', '21, 124, 217', '255, 184, 7', '255, 25, 7'] - this.colors = ["#4DAF4A", "#377EB8"]; + // disks data is a map of diskname to array of values of length // dataLength // each value is of the format of the data returned by the api @@ -70,80 +65,10 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ this.disks.pageSize = RockStorGlobals.maxPageSize; this.topDisks = []; - this.topDisksWidth = this.maximized ? 520 : 240; - this.topDisksHeight = 50; - this.selectedDisk = null; - this.updateFreq = 1000; this.sortAttrs = ['reads_completed']; // attrs to sort by - // maximum number of top disks to display - this.numTop = this.maximized ? 5 : 3; - this.partition = d3.layout.partition() - .value(function(d) { - return _.reduce(_this.sortAttrs, function(s, a) { - return s + d[a]; - }, 0); - }); - this.graphOptions = { - grid: { - //hoverable : true, - borderWidth: { - top: 1, - right: 1, - bottom: 1, - left: 1 - }, - borderColor: "#ddd" - }, - xaxis: { - min: 0, - max: this.dataLength - 1, - tickFormatter: this.timeTickFormatter(this.dataLength), - axisLabel: "Time (minutes)", - axisLabelColour: "#000" - }, - yaxis: { - min: 0 - }, - series: { - lines: { - show: true, - fill: false - }, - shadowSize: 0 // Drawing is faster without shadows - } - }; - this.dataGraphOptions = { - grid: { - //hoverable : true, - borderWidth: { - top: 1, - right: 1, - bottom: 1, - left: 1 - }, - borderColor: "#ddd" - }, - xaxis: { - min: 0, - max: this.dataLength - 1, - tickFormatter: this.timeTickFormatter(this.dataLength), - axisLabel: "Time (minutes)", - axisLabelColour: "#000" - }, - yaxis: { - min: 0, - tickFormatter: this.valueTickFormatter - }, - series: { - lines: { - show: true, - fill: false - }, - shadowSize: 0 // Drawing is faster without shadows - } - }; + this.SingleDiskChart = null; this.SingleDiskChartOptions = { animation: false, @@ -400,7 +325,6 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ _this.SingleDiskgraphRendered = true; } _this.updateSingleDiskChart(); - this.renderDiskGraph(); } }, @@ -512,56 +436,6 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ }); }, - renderDiskGraph: function() { - if (!this.selectedDisk) { - if (this.topDisks.length > 0) { - this.selectedDisk = this.topDisks[0].name; - } else { - this.selectedDisk = this.disks.at(0).get('name'); - } - this.$('#disk-select').val(this.selectedDisk); - } - - var vals = this.disksData[this.selectedDisk]; - var tmpReads = []; - for (var i = 0; i < this.dataLength; i++) { - tmpReads.push([i, vals[i].reads_completed]); - } - var tmpWrites = []; - for (var i = 0; i < this.dataLength; i++) { - tmpWrites.push([i, vals[i].writes_completed]); - } - var series1 = [{ - label: 'Reads', - data: tmpReads, - color: this.colors[0] - }, { - label: 'Writes', - data: tmpWrites, - color: this.colors[1] - }]; - $.plot(this.$('#disk-graph-reads-ph'), series1, this.graphOptions); - - var tmpReadData = []; - for (var i = 0; i < this.dataLength; i++) { - tmpReadData.push([i, vals[i].sectors_read * 512]); - } - var tmpWriteData = []; - for (var i = 0; i < this.dataLength; i++) { - tmpWriteData.push([i, vals[i].sectors_written * 512]); - } - var series2 = [{ - label: 'KB read', - data: tmpReadData, - color: this.colors[0] - }, { - label: 'KB written', - data: tmpWriteData, - color: this.colors[1] - }]; - $.plot(this.$('#disk-graph-data-ph'), series2, this.dataGraphOptions); - }, - genEmptyDiskData: function() { // empty disk data return { @@ -584,7 +458,6 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ var _this = this; this.constructor.__super__.resize.apply(this, arguments); - this.topDisksWidth = this.maximized ? 520 : 240; // maximum number of top disks to display this.numTop = this.maximized ? 5 : 3; if (this.maximized) { @@ -603,20 +476,6 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ _this.TopDisksChart.resize(); }, - timeTickFormatter: function(dataLength) { - return function(val, axis) { - return ((dataLength / 60) - (parseInt(val / 60))).toString() + ' m'; - }; - }, - - valueTickFormatter: function(val, axis) { - return humanize.filesize(val, 1024, 2); - }, - - setSelectedDisk: function(event) { - this.selectedDisk = this.$('#disk-select').val(); - }, - cleanup: function() { RockStorSocket.removeOneListener('diskWidget'); }, From 204743262ddd9093333d485922cb9c1a27b62f05 Mon Sep 17 00:00:00 2001 From: Mirko Arena Date: Wed, 12 Oct 2016 10:40:37 +0200 Subject: [PATCH 11/16] #998 Disks Widget - Fixed single disk chart wrong resize --- .../dashboard/widgets/disk_util_select.jst | 5 +-- .../dashboard/widgets/disk_utilization.jst | 2 +- .../js/views/dashboard/disk_utilization.js | 42 +++++++++++++++---- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_util_select.jst b/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_util_select.jst index 9cdb1aa9a..37a640023 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_util_select.jst +++ b/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_util_select.jst @@ -1,4 +1,4 @@ -
+
Disk activity for disk
-
-
+
diff --git a/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_utilization.jst b/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_utilization.jst index e7af5c5f6..233d22268 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_utilization.jst +++ b/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_utilization.jst @@ -29,7 +29,7 @@
-
+
diff --git a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js index 70b83fcc1..45838c45a 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js +++ b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js @@ -52,7 +52,11 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ this.numTop = this.maximized ? 5 : 3; this.dataLength = 300; this.Disksfields = ['ms_ios', 'sectors_written', 'writes_completed', 'ms_writing', 'ms_reading', 'reads_completed', 'sectors_read']; + this.sortFields = this.Disksfields.slice(0); this.Diskslabels = ['ms on I/Os', 'kB written', 'Writes', 'ms writing', 'ms reading', 'Reads', 'kB read']; + this.sortLabels = this.Diskslabels.slice(0); + this.sortFields.unshift('best_draft'); + this.sortLabels.unshift('Best Draft'); this.TopDiskscolors = ['242, 0, 0', '36, 229, 84', '41, 108, 232', '232, 200, 41', '146, 41, 232'] this.SingleDiskcolors = ['7, 233, 7', '21, 124, 217', '255, 184, 7', '255, 25, 7'] @@ -67,12 +71,14 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ this.topDisks = []; this.selectedDisk = null; + this.best_draftSort = ['reads_completed', 'writes_completed', 'sectors_read', 'sectors_written', 'ms_ios', 'ms_writing', 'ms_reading']; + this.selectedAttr = ''; this.sortAttrs = ['reads_completed']; // attrs to sort by this.SingleDiskChart = null; this.SingleDiskChartOptions = { animation: false, - responsive: true, + responsive: false, legend: { display: true, position: 'bottom', @@ -198,7 +204,6 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ this.initHandlebarHelpers(); }, - render: function() { var _this = this; @@ -209,7 +214,7 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ displayName: this.displayName, maximized: this.maximized })); - + if (this.maximized) this.$('#top-disks-container').css('width','60%'); this.$('.diskSortAttr').change(function(event) { var cbox = $(event.currentTarget); var v = cbox.val(); @@ -391,8 +396,8 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ }); var csecs = moment(current_disk[i].ts).format('s'); var label = ''; - if (csecs % 30 === 0) { - label = csecs == '0' ? moment(current_disk[i].ts).format('HH:mm') : moment(current_disk[i].ts).format(':ss'); + if (csecs % 60 === 0) { + label = moment(current_disk[i].ts).format('HH:mm'); } singledisklabels.push(label); } @@ -421,10 +426,25 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ var tmp = _.map(_.keys(_this.disksData), function(k) { return _this.disksData[k][_this.dataLength - 1]; }); - var sorter = _this.sortAttrs[0]; - _this.topDisks = _.sortBy(tmp, function(d) { - return d[sorter]; - }).reverse(); + var sort_attr = _this.sortAttrs[0]; + if (sort_attr == 'best_draft') { + var selected_top = []; + for (var i=0; i < _this.numTop; i++) { + _.each(_this.best_draftSort, function (d) { + var current_top = _.sortBy(tmp, function(k) { + return k[d]; + }).reverse(); + if (!_.contains(selected_top, current_top[i].name) && selected_top.length < _this.numTop) { + selected_top.push(current_top[i].name); + } + }); + } + } else { + _this.topDisks = _.sortBy(tmp, function(d) { + return d[sort_attr]; + }).reverse(); + + } }, updateDisksData: function(data) { @@ -470,7 +490,10 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ this.$('#disk-select').change(function(event) { _this.selectedDisk = _this.$('#disk-select').val(); }); + this.$('#top-disks-container').css('width','60%'); } else { + this.$('#top-disks-container').css('width','70%'); + _this.SingleDiskgraphRendered = false; this.$('#disk-details-ph').html("Expand for details"); } _this.TopDisksChart.resize(); @@ -481,6 +504,7 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ }, initHandlebarHelpers: function(){ + Handlebars.registerHelper('getAdminUsers', function(adminUsers){ var html = ''; var userNames = _.reduce(adminUsers, function(s, user, i, list) { From a40e25badc1d235bfbdb5d95406776082b988135 Mon Sep 17 00:00:00 2001 From: Mirko Arena Date: Wed, 12 Oct 2016 12:56:15 +0200 Subject: [PATCH 12/16] Ref #998 Disks Widget - Best Draft algorithm Added new 'Best Draft' algorithm, going to be default sorting On new data we cycle over disks and best_draftSort array, pick current disk[i] if not in tmp topdisks array push it, else skip This way we ensure having biggest number of disks listed by property --- .../js/views/dashboard/disk_utilization.js | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js index 45838c45a..5e0bfc472 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js +++ b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js @@ -426,24 +426,30 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ var tmp = _.map(_.keys(_this.disksData), function(k) { return _this.disksData[k][_this.dataLength - 1]; }); - var sort_attr = _this.sortAttrs[0]; + + //var sort_attr = _this.sortAttrs[0]; + var sort_attr = 'best_draft'; if (sort_attr == 'best_draft') { var selected_top = []; - for (var i=0; i < _this.numTop; i++) { + for (var i=0; i < Object.keys(tmp).length; i++) { _.each(_this.best_draftSort, function (d) { - var current_top = _.sortBy(tmp, function(k) { + var sorted = _.sortBy(tmp, function(k) { return k[d]; }).reverse(); - if (!_.contains(selected_top, current_top[i].name) && selected_top.length < _this.numTop) { - selected_top.push(current_top[i].name); + if (!_.contains(selected_top, sorted[i].name) && selected_top.length < _this.numTop) { + selected_top.push(sorted[i].name); } }); } + _.each(selected_top, function(disk) { + _.each(tmp, function(d){ + if (d.name == disk) _this.topDisks.push(d); + }); + }); } else { _this.topDisks = _.sortBy(tmp, function(d) { return d[sort_attr]; }).reverse(); - } }, @@ -505,7 +511,7 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ initHandlebarHelpers: function(){ - Handlebars.registerHelper('getAdminUsers', function(adminUsers){ + Handlebars.registerHelper('selectSortAttrs', function(){ var html = ''; var userNames = _.reduce(adminUsers, function(s, user, i, list) { if (i < (list.length-1)) { From f9f095dba333a0bd379d4273b3e19a2b3f2e2ac8 Mon Sep 17 00:00:00 2001 From: Mirko Arena Date: Wed, 12 Oct 2016 14:01:54 +0200 Subject: [PATCH 13/16] #998 - Fixed wrong topdisks assignement --- .../storageadmin/js/views/dashboard/disk_utilization.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js index 5e0bfc472..94b7d9496 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js +++ b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js @@ -441,11 +441,16 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ } }); } + var local_topdisks = [] _.each(selected_top, function(disk) { _.each(tmp, function(d){ - if (d.name == disk) _this.topDisks.push(d); + if (d.name == disk) local_topdisks.push(d); }); }); + _this.topDisks = local_topdisks; + /*_this.topDisks = _.filter(tmp, function (disk) { + return _.contains(selected_top, disk.name); + });*/ } else { _this.topDisks = _.sortBy(tmp, function(d) { return d[sort_attr]; @@ -511,7 +516,7 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ initHandlebarHelpers: function(){ - Handlebars.registerHelper('selectSortAttrs', function(){ + Handlebars.registerHelper('getAdminUsers', function(adminUsers){ var html = ''; var userNames = _.reduce(adminUsers, function(s, user, i, list) { if (i < (list.length-1)) { From 9c618a88c37169513ca920594e035d4c4a6ef257 Mon Sep 17 00:00:00 2001 From: Mirko Arena Date: Wed, 12 Oct 2016 14:50:08 +0200 Subject: [PATCH 14/16] Ending #998 chapter - Disks Widget working - having last tests then ready to merge Fixes #998 - Closing main issue Fixes #1152 - No more checkboxes, opening a more generic issue about persistent dashboard opts data Fixes #919 - Added enough IO infos over charts Fixes #916 - Best draft algorithm grants having all possible info, radar chart has a pure visualization function while single disk chart give more details Fixes #831 - Not required at all Fixes #807 - No more "g" errors, related to svg elements (no more used) --- .../dashboard/widgets/disk_util_select.jst | 22 +- .../dashboard/widgets/disk_utilization.jst | 25 +- .../js/views/dashboard/disk_utilization.js | 226 ++++++++---------- 3 files changed, 122 insertions(+), 151 deletions(-) diff --git a/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_util_select.jst b/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_util_select.jst index 37a640023..afb58832d 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_util_select.jst +++ b/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_util_select.jst @@ -1,11 +1,11 @@ -
-
- Disk activity for disk - -
- -
+
+
+ Disk activity for disk + +
+ +
\ No newline at end of file diff --git a/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_utilization.jst b/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_utilization.jst index 233d22268..6b02e47cb 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_utilization.jst +++ b/src/rockstor/storageadmin/static/storageadmin/js/templates/dashboard/widgets/disk_utilization.jst @@ -29,20 +29,17 @@
-
- -
-
- Sort by: - Reads  - Writes - -    - Total: - -
-
-
+
+ +
+
+ Top disks sort by: + +
+
+
diff --git a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js index 94b7d9496..42f2f087a 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js +++ b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js @@ -49,17 +49,13 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ Chart.defaults.global.elements.point.radius = 0; Chart.defaults.global.elements.point.hoverRadius = 0; - this.numTop = this.maximized ? 5 : 3; + this.numTop = this.maximized ? 5 : 3; this.dataLength = 300; this.Disksfields = ['ms_ios', 'sectors_written', 'writes_completed', 'ms_writing', 'ms_reading', 'reads_completed', 'sectors_read']; - this.sortFields = this.Disksfields.slice(0); this.Diskslabels = ['ms on I/Os', 'kB written', 'Writes', 'ms writing', 'ms reading', 'Reads', 'kB read']; - this.sortLabels = this.Diskslabels.slice(0); - this.sortFields.unshift('best_draft'); - this.sortLabels.unshift('Best Draft'); this.TopDiskscolors = ['242, 0, 0', '36, 229, 84', '41, 108, 232', '232, 200, 41', '146, 41, 232'] this.SingleDiskcolors = ['7, 233, 7', '21, 124, 217', '255, 184, 7', '255, 25, 7'] - + // disks data is a map of diskname to array of values of length // dataLength // each value is of the format of the data returned by the api @@ -72,14 +68,13 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ this.selectedDisk = null; this.best_draftSort = ['reads_completed', 'writes_completed', 'sectors_read', 'sectors_written', 'ms_ios', 'ms_writing', 'ms_reading']; - this.selectedAttr = ''; - this.sortAttrs = ['reads_completed']; // attrs to sort by + this.selectedAttr = 'best_draft'; - this.SingleDiskChart = null; + this.SingleDiskChart = null; this.SingleDiskChartOptions = { animation: false, responsive: false, - legend: { + legend: { display: true, position: 'bottom', labels: { @@ -142,34 +137,34 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ }] } }; - this.SingleDiskChartData = { - labels: [], - datasets: [{ + this.SingleDiskChartData = { + labels: [], + datasets: [{ label: this.Diskslabels[5], - yAxisID: 'IOs', + yAxisID: 'IOs', backgroundColor: 'rgba(' + this.SingleDiskcolors[0] + ', 0.4)', borderColor: 'rgba(' + this.SingleDiskcolors[0] + ', 1)', data: [] }, { label: this.Diskslabels[2], - yAxisID: 'IOs', + yAxisID: 'IOs', backgroundColor: 'rgba(' + this.SingleDiskcolors[1] + ', 0.4)', borderColor: 'rgba(' + this.SingleDiskcolors[1] + ', 1)', data: [] }, { label: this.Diskslabels[6], - yAxisID: 'Data', + yAxisID: 'Data', backgroundColor: 'rgba(' + this.SingleDiskcolors[2] + ', 0.4)', borderColor: 'rgba(' + this.SingleDiskcolors[2] + ', 1)', data: [] }, { label: this.Diskslabels[1], - yAxisID: 'Data', + yAxisID: 'Data', backgroundColor: 'rgba(' + this.SingleDiskcolors[3] + ', 0.4)', borderColor: 'rgba(' + this.SingleDiskcolors[3] + ', 1)', data: [] }] - } + } this.TopDisksChart = null; this.TopDisksChartOptions = { @@ -178,7 +173,7 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ easing: 'linear' }, responsive: true, - legend: { + legend: { display: true, position: 'bottom', labels: { @@ -201,7 +196,7 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ datasets: [] }; - this.initHandlebarHelpers(); + this.initHandlebarHelpers(); }, render: function() { @@ -214,31 +209,19 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ displayName: this.displayName, maximized: this.maximized })); - if (this.maximized) this.$('#top-disks-container').css('width','60%'); - this.$('.diskSortAttr').change(function(event) { + if (this.maximized) this.$('#top-disks-container').css('width', '60%'); + + this.$('#attr-select').change(function(event) { var cbox = $(event.currentTarget); var v = cbox.val(); - if (cbox.is(':checked')) { - if (_.indexOf(_this.sortAttrs, v) == -1) { - _this.sortAttrs.push(v); - } - } else { - if (_.indexOf(_this.sortAttrs, v) != -1) { - if (_this.sortAttrs.length > 1) { - _this.sortAttrs = _.without(_this.sortAttrs, v); - } else { - // dont allow the last attr to be unchecked - cbox.prop('checked', true); - } - } - } + _this.selectedAttr = v; }); this.disks.fetch({ success: function(collection, response, options) { _this.initializeDisksData(); _this.initTopDisksData(); - _this.initSingleDiskData(); + _this.initSingleDiskData(); RockStorSocket.addListener(_this.getData, _this, 'diskWidget:top_disks'); } }); @@ -277,7 +260,7 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ initTopDisksData: function() { var _this = this; - var num_disks = Object.keys(_this.disksData).length < _this.numTop ? Object.keys(_this.disksData).length : _this.numTop; + var num_disks = Object.keys(_this.disksData).length < _this.numTop ? Object.keys(_this.disksData).length : _this.numTop; for (var i = 0; i < num_disks; i++) { var dataset = { label: '', @@ -294,17 +277,17 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ _this.TopDisksChartData.datasets.push(dataset); } }, - - initSingleDiskData: function() { - - var _this = this; - for (var i= 0; i < _this.dataLength; i++) { - _.each(_this.SingleDiskChartData.datasets, function(dataset) { - dataset.data.push(null); - }); - _this.SingleDiskChartData.labels.push(''); - } - }, + + initSingleDiskData: function() { + + var _this = this; + for (var i = 0; i < _this.dataLength; i++) { + _.each(_this.SingleDiskChartData.datasets, function(dataset) { + dataset.data.push(null); + }); + _this.SingleDiskChartData.labels.push(''); + } + }, getData: function(data) { @@ -314,7 +297,7 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ update: function(data) { - var _this = this; + var _this = this; _this.updateDisksData(data); _this.sortDisks(); @@ -325,12 +308,12 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ _this.updateTopDisksChart(); if (_this.maximized) { - if (!_this.SingleDiskgraphRendered) { - _this.initSingleDiskGraph(); - _this.SingleDiskgraphRendered = true; - } - _this.updateSingleDiskChart(); - } + if (!_this.SingleDiskgraphRendered) { + _this.initSingleDiskGraph(); + _this.SingleDiskgraphRendered = true; + } + _this.updateSingleDiskChart(); + } }, initTopDisksGraph: function() { @@ -342,8 +325,8 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ options: _this.TopDisksChartOptions }); }, - - initSingleDiskGraph: function() { + + initSingleDiskGraph: function() { var _this = this; _this.SingleDiskChart = new Chart(this.$('#single-disk-chart'), { @@ -351,14 +334,14 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ data: _this.SingleDiskChartData, options: _this.SingleDiskChartOptions }); - }, - + }, + updateTopDisksChart: function() { var _this = this; //If avail disks < numTop, use only avail disks var num_disks = Object.keys(_this.disksData).length < _this.numTop ? Object.keys(_this.disksData).length : _this.numTop; - for (var i=0; i < num_disks; i++) { + for (var i = 0; i < num_disks; i++) { var data = []; _.each(_this.Disksfields, function(field) { data.push(_this.normalizeData(field, _this.topDisks[i][field])); @@ -368,10 +351,10 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ } _this.TopDisksChart.update(); }, - - updateSingleDiskChart: function() { - var _this = this; + updateSingleDiskChart: function() { + + var _this = this; if (!_this.selectedDisk) { if (_this.topDisks.length > 0) { _this.selectedDisk = _this.topDisks[0].name; @@ -380,33 +363,33 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ } this.$('#disk-select').val(_this.selectedDisk); } - var current_disk = _this.disksData[_this.selectedDisk]; - var singlediskdata = { - reads_completed: [], - writes_completed: [], - sectors_read: [], - sectors_written: [], - }; - var singledisklabels = []; - + var current_disk = _this.disksData[_this.selectedDisk]; + var singlediskdata = { + reads_completed: [], + writes_completed: [], + sectors_read: [], + sectors_written: [], + }; + var singledisklabels = []; + for (var i = 0; i < _this.dataLength; i++) { - _.each(singlediskdata, function(dataval, datakey) { - var multiplier = datakey.indexOf('sectors') > -1 ? 512 : 1; - singlediskdata[datakey].push(current_disk[i][datakey] * multiplier); - }); - var csecs = moment(current_disk[i].ts).format('s'); - var label = ''; - if (csecs % 60 === 0) { - label = moment(current_disk[i].ts).format('HH:mm'); - } - singledisklabels.push(label); + _.each(singlediskdata, function(dataval, datakey) { + var multiplier = datakey.indexOf('sectors') > -1 ? 512 : 1; + singlediskdata[datakey].push(current_disk[i][datakey] * multiplier); + }); + var csecs = moment(current_disk[i].ts).format('s'); + var label = ''; + if (csecs % 60 === 0) { + label = moment(current_disk[i].ts).format('HH:mm'); + } + singledisklabels.push(label); } - _.each(_.values(singlediskdata), function(val, index) { - _this.SingleDiskChartData.datasets[index].data = val; - }); - _this.SingleDiskChartData.labels = singledisklabels; - _this.SingleDiskChart.update(); - }, + _.each(_.values(singlediskdata), function(val, index) { + _this.SingleDiskChartData.datasets[index].data = val; + }); + _this.SingleDiskChartData.labels = singledisklabels; + _this.SingleDiskChart.update(); + }, //Chart.js radar chart don't have multiple scales //so we have to normalize our data @@ -419,7 +402,7 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ var new_val = val == 0 ? 0 : (val * 100 / val_max).toFixed(2); //we use a 0..100 range with 2 decimals return new_val; }, - + sortDisks: function() { var _this = this; @@ -427,12 +410,11 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ return _this.disksData[k][_this.dataLength - 1]; }); - //var sort_attr = _this.sortAttrs[0]; - var sort_attr = 'best_draft'; + var sort_attr = _this.selectedAttr; if (sort_attr == 'best_draft') { var selected_top = []; - for (var i=0; i < Object.keys(tmp).length; i++) { - _.each(_this.best_draftSort, function (d) { + for (var i = 0; i < Object.keys(tmp).length; i++) { + _.each(_this.best_draftSort, function(d) { var sorted = _.sortBy(tmp, function(k) { return k[d]; }).reverse(); @@ -441,16 +423,13 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ } }); } - var local_topdisks = [] - _.each(selected_top, function(disk) { - _.each(tmp, function(d){ - if (d.name == disk) local_topdisks.push(d); - }); - }); - _this.topDisks = local_topdisks; - /*_this.topDisks = _.filter(tmp, function (disk) { - return _.contains(selected_top, disk.name); - });*/ + var local_topdisks = [] + _.each(selected_top, function(disk) { + _.each(tmp, function(d) { + if (d.name == disk) local_topdisks.push(d); + }); + }); + _this.topDisks = local_topdisks; } else { _this.topDisks = _.sortBy(tmp, function(d) { return d[sort_attr]; @@ -501,38 +480,33 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ this.$('#disk-select').change(function(event) { _this.selectedDisk = _this.$('#disk-select').val(); }); - this.$('#top-disks-container').css('width','60%'); + this.$('#top-disks-container').css('width', '60%'); } else { - this.$('#top-disks-container').css('width','70%'); - _this.SingleDiskgraphRendered = false; + this.$('#top-disks-container').css('width', '70%'); + _this.SingleDiskgraphRendered = false; this.$('#disk-details-ph').html("Expand for details"); } _this.TopDisksChart.resize(); }, cleanup: function() { + RockStorSocket.removeOneListener('diskWidget'); }, - initHandlebarHelpers: function(){ - - Handlebars.registerHelper('getAdminUsers', function(adminUsers){ - var html = ''; - var userNames = _.reduce(adminUsers, function(s, user, i, list) { - if (i < (list.length-1)) { - return s + user.username + ','; - } else{ - return s + user.username; - } - }, ''); - if(userNames.length != 0){ - html += userNames; - }else { - html += ' --'; - } - return new Handlebars.SafeString(html); - }); - } + initHandlebarHelpers: function() { + + var _this = this; + + Handlebars.registerHelper('genAttrSelect', function() { + + var html = ''; + _.each(_this.Disksfields, function(field, index) { + html += ''; + }); + return new Handlebars.SafeString(html); + }); + } }); From e472c01d3071809e32f1cf8710876aff750e92aa Mon Sep 17 00:00:00 2001 From: Mirko Arena Date: Wed, 12 Oct 2016 15:32:20 +0200 Subject: [PATCH 15/16] #998 Disks Widget - fixed minor error on widget maximized opened from another page - probably because of a delay on render. Fixes #998 Fixes #1152 Fixes #919 Fixes #916 Fixes #831 Fixes #807 --- .../js/views/dashboard/disk_utilization.js | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js index 42f2f087a..4ac70e764 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js +++ b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js @@ -364,31 +364,33 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ this.$('#disk-select').val(_this.selectedDisk); } var current_disk = _this.disksData[_this.selectedDisk]; - var singlediskdata = { - reads_completed: [], - writes_completed: [], - sectors_read: [], - sectors_written: [], - }; - var singledisklabels = []; + if (current_disk) { + var singlediskdata = { + reads_completed: [], + writes_completed: [], + sectors_read: [], + sectors_written: [], + }; + var singledisklabels = []; - for (var i = 0; i < _this.dataLength; i++) { - _.each(singlediskdata, function(dataval, datakey) { - var multiplier = datakey.indexOf('sectors') > -1 ? 512 : 1; - singlediskdata[datakey].push(current_disk[i][datakey] * multiplier); - }); - var csecs = moment(current_disk[i].ts).format('s'); - var label = ''; - if (csecs % 60 === 0) { - label = moment(current_disk[i].ts).format('HH:mm'); + for (var i = 0; i < _this.dataLength; i++) { + _.each(singlediskdata, function(dataval, datakey) { + var multiplier = datakey.indexOf('sectors') > -1 ? 512 : 1; + singlediskdata[datakey].push(current_disk[i][datakey] * multiplier); + }); + var csecs = moment(current_disk[i].ts).format('s'); + var label = ''; + if (csecs % 60 === 0) { + label = moment(current_disk[i].ts).format('HH:mm'); + } + singledisklabels.push(label); } - singledisklabels.push(label); + _.each(_.values(singlediskdata), function(val, index) { + _this.SingleDiskChartData.datasets[index].data = val; + }); + _this.SingleDiskChartData.labels = singledisklabels; + _this.SingleDiskChart.update(); } - _.each(_.values(singlediskdata), function(val, index) { - _this.SingleDiskChartData.datasets[index].data = val; - }); - _this.SingleDiskChartData.labels = singledisklabels; - _this.SingleDiskChart.update(); }, //Chart.js radar chart don't have multiple scales From b65e1454cd3e4f4aa513485d7933d89fecbbc7e8 Mon Sep 17 00:00:00 2001 From: Mirko Arena Date: Wed, 12 Oct 2016 16:23:55 +0200 Subject: [PATCH 16/16] #998 Disks Widget - Added new mini func, now Single Disk chart title has avg ms_ios, ms_reading and ms_writing --- .../js/views/dashboard/disk_utilization.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js index 4ac70e764..65a00dc34 100644 --- a/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js +++ b/src/rockstor/storageadmin/static/storageadmin/js/views/dashboard/disk_utilization.js @@ -74,6 +74,12 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ this.SingleDiskChartOptions = { animation: false, responsive: false, + title: { + display: true, + text: '', + padding: 5, + fontSize: 11 + }, legend: { display: true, position: 'bottom', @@ -370,6 +376,9 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ writes_completed: [], sectors_read: [], sectors_written: [], + ms_ios: [], + ms_writing: [], + ms_reading: [] }; var singledisklabels = []; @@ -385,9 +394,19 @@ DiskUtilizationWidget = RockStorWidgetView.extend({ } singledisklabels.push(label); } + var msios = _.reduce(singlediskdata.ms_ios, function(s, n){ return s + n; }, 0)/singlediskdata.ms_ios.length; + var msw = _.reduce(singlediskdata.ms_writing, function(s, n){ return s + n; }, 0)/singlediskdata.ms_writing.length; + var msr = _.reduce(singlediskdata.ms_reading, function(s, n){ return s + n; }, 0)/singlediskdata.ms_reading.length; + var title = ': Avg I/Os: ' + msios.toFixed(2) + 'ms - '; + title +='Avg writing: ' + msw.toFixed(2) + 'ms - '; + title +='Avg reading: ' + msr.toFixed(2) + 'ms :'; + delete singlediskdata.ms_ios; + delete singlediskdata.ms_writing; + delete singlediskdata.ms_reading; _.each(_.values(singlediskdata), function(val, index) { _this.SingleDiskChartData.datasets[index].data = val; }); + _this.SingleDiskChart.options.title.text = title; _this.SingleDiskChartData.labels = singledisklabels; _this.SingleDiskChart.update(); }