Repository: ambari Updated Branches: refs/heads/trunk 3410ba4e1 -> 599c1da81
AMBARI-11388 Enhanced Dashboard: Aggregate calls for widget metrics. (atkach) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/599c1da8 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/599c1da8 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/599c1da8 Branch: refs/heads/trunk Commit: 599c1da8138dd881ed863525cfa56a482677f9b4 Parents: 3410ba4 Author: Andrii Tkach <[email protected]> Authored: Tue May 26 14:26:20 2015 +0300 Committer: Andrii Tkach <[email protected]> Committed: Tue May 26 15:11:08 2015 +0300 ---------------------------------------------------------------------- .../app/controllers/global/update_controller.js | 6 +- .../service/widgets/create/step3_controller.js | 1 - .../app/mixins/common/widgets/widget_mixin.js | 206 +++++++++++++------ ambari-web/app/utils/ajax/ajax.js | 7 +- .../views/common/widget/graph_widget_view.js | 33 +-- .../test/mixins/common/widget_mixin_test.js | 157 ++++++++++---- 6 files changed, 283 insertions(+), 127 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/599c1da8/ambari-web/app/controllers/global/update_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/global/update_controller.js b/ambari-web/app/controllers/global/update_controller.js index f81a079..8e6252f 100644 --- a/ambari-web/app/controllers/global/update_controller.js +++ b/ambari-web/app/controllers/global/update_controller.js @@ -480,7 +480,11 @@ App.UpdateController = Em.Controller.extend({ updateUnhealthyAlertInstances: function (callback) { var testUrl = '/data/alerts/alert_instances.json'; var queryParams = this.get('queryParamsForUnhealthyAlertInstances'); - var realUrl = '/alerts?fields=*&Alert/state.in(CRITICAL,WARNING)&Alert/maintenance_state.in(OFF)&from=' + queryParams.from + '&page_size=' + queryParams.page_size; + var realUrl = '/alerts?fields=' + + 'Alert/component_name,Alert/definition_id,Alert/definition_name,Alert/host_name,Alert/id,Alert/instance,' + + 'Alert/label,Alert/latest_timestamp,Alert/maintenance_state,Alert/original_timestamp,Alert/scope,' + + 'Alert/service_name,Alert/state,Alert/text' + + '&Alert/state.in(CRITICAL,WARNING)&Alert/maintenance_state.in(OFF)&from=' + queryParams.from + '&page_size=' + queryParams.page_size; var url = this.getUrl(testUrl, realUrl); App.HttpClient.get(url, App.alertInstanceMapper, { http://git-wip-us.apache.org/repos/asf/ambari/blob/599c1da8/ambari-web/app/controllers/main/service/widgets/create/step3_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/service/widgets/create/step3_controller.js b/ambari-web/app/controllers/main/service/widgets/create/step3_controller.js index 8a0eaa3..4c9dc81 100644 --- a/ambari-web/app/controllers/main/service/widgets/create/step3_controller.js +++ b/ambari-web/app/controllers/main/service/widgets/create/step3_controller.js @@ -129,7 +129,6 @@ App.WidgetWizardStep3Controller = Em.Controller.extend({ author: this.get('widgetAuthor'), metrics: this.get('widgetMetrics').map(function (metric) { delete metric.data; - delete metric.actual_host_component_criteria; return metric; }), values: this.get('widgetValues').map(function (value) { http://git-wip-us.apache.org/repos/asf/ambari/blob/599c1da8/ambari-web/app/mixins/common/widgets/widget_mixin.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mixins/common/widgets/widget_mixin.js b/ambari-web/app/mixins/common/widgets/widget_mixin.js index 0220a7e..9e20cf4 100644 --- a/ambari-web/app/mixins/common/widgets/widget_mixin.js +++ b/ambari-web/app/mixins/common/widgets/widget_mixin.js @@ -110,14 +110,26 @@ App.WidgetMixin = Ember.Mixin.create({ }); } } else if (request.host_component_criteria) { - this.getHostComponentMetrics(request).always(function () { - requestCounter--; - if (requestCounter === 0) self.onMetricsLoaded(); + App.WidgetLoadAggregator.add({ + data: request, + context: this, + startCallName: 'getHostComponentMetrics', + successCallback: this.getHostComponentMetricsSuccessCallback, + completeCallback: function () { + requestCounter--; + if (requestCounter === 0) this.onMetricsLoaded(); + } }); } else { - this.getServiceComponentMetrics(request).complete(function () { - requestCounter--; - if (requestCounter === 0) self.onMetricsLoaded(); + App.WidgetLoadAggregator.add({ + data: request, + context: this, + startCallName: 'getServiceComponentMetrics', + successCallback: this.getMetricsSuccessCallback, + completeCallback: function () { + requestCounter--; + if (requestCounter === 0) this.onMetricsLoaded(); + } }); } } @@ -133,7 +145,6 @@ App.WidgetMixin = Ember.Mixin.create({ metrics.forEach(function (metric, index) { var key; if (metric.host_component_criteria) { - this.createActualHostComponentCriteria(metric); key = metric.service_name + '_' + metric.component_name + '_' + metric.host_component_criteria; } else { key = metric.service_name + '_' + metric.component_name; @@ -154,32 +165,30 @@ App.WidgetMixin = Ember.Mixin.create({ /** * Create actual host component criteria from persisted host component criteria - * NameNode HA and ReourceManager HA host component criteria is applicable only in HA mode + * NameNode HA and ResourceManager HA host component criteria is applicable only in HA mode + * @param {object} request */ - createActualHostComponentCriteria: function (metric) { - switch (metric.component_name) { + computeHostComponentCriteria: function (request) { + switch (request.component_name) { case 'NAMENODE': - if (metric.host_component_criteria === 'host_components/metrics/dfs/FSNamesystem/HAState=active') { + if (request.host_component_criteria === 'host_components/metrics/dfs/FSNamesystem/HAState=active') { var hdfs = App.HDFSService.find().objectAt(0); var activeNNHostName = !hdfs.get('snameNode') && hdfs.get('activeNameNode'); if (!activeNNHostName) { - metric.actual_host_component_criteria = 'host_components/HostRoles/component_name=NAMENODE'; - } else { - metric.actual_host_component_criteria = metric.host_component_criteria; + return ''; } } break; case 'RESOURCEMANAGER': - if (metric.host_component_criteria === 'host_components/HostRoles/ha_state=ACTIVE') { + if (request.host_component_criteria === 'host_components/HostRoles/ha_state=ACTIVE') { var yarn = App.YARNService.find().objectAt(0); if (!yarn.get('isRMHaEnabled')) { - metric.actual_host_component_criteria = 'host_components/HostRoles/component_name=RESOURCEMANAGER'; - } else { - metric.actual_host_component_criteria = metric.host_component_criteria; + return ''; } } break; } + return request.host_component_criteria.replace('host_components/', '&'); }, /** @@ -195,63 +204,32 @@ App.WidgetMixin = Ember.Mixin.create({ serviceName: request.service_name, componentName: request.component_name, metricPaths: request.metric_paths.join(',') - }, - success: 'getMetricsSuccessCallback' - }); - }, - - /** - * make GET call to server in order to fetch specifc host-component metrics - * @param {object} request - * @returns {$.Deferred} - */ - getHostComponentMetrics: function (request) { - var dfd; - var self = this; - dfd = $.Deferred(); - this.getHostComponentName(request).done(function (data) { - if (data) { - request.host_name = data.host_components[0].HostRoles.host_name; - App.ajax.send({ - name: 'widgets.hostComponent.metrics.get', - sender: self, - data: { - componentName: request.component_name, - hostName: request.host_name, - metricPaths: request.metric_paths.join(',') - } - }).done(function (metricData) { - self.getMetricsSuccessCallback(metricData); - dfd.resolve(); - }).fail(function (data) { - dfd.reject(); - }); } - }).fail(function (data) { - dfd.reject(); }); - return dfd.promise(); }, - /** - * make GET call to server in order to fetch host-component names + * make GET call to server in order to fetch specific host-component metrics * @param {object} request * @returns {$.ajax} */ - getHostComponentName: function (request) { + getHostComponentMetrics: function (request) { return App.ajax.send({ - name: 'widgets.hostComponent.get.hostName', + name: 'widgets.hostComponent.metrics.get', sender: this, data: { - serviceName: request.service_name, componentName: request.component_name, metricPaths: request.metric_paths.join(','), - hostComponentCriteria: request.actual_host_component_criteria || request.host_component_criteria + hostComponentCriteria: this.computeHostComponentCriteria(request) } }); }, + getHostComponentMetricsSuccessCallback: function (data) { + if (data.items[0]) { + this.getMetricsSuccessCallback(data.items[0]); + } + }, /** * callback on getting aggregated metrics and host component metrics @@ -626,4 +604,116 @@ App.WidgetPreviewMixin = Ember.Mixin.create({ onMetricsLoaded: function () { this.drawWidget(); } +}); + + +/** + * aggregate requests to load metrics by component name + * requests can be added via add method + * input example: + * { + * data: request, + * context: this, + * startCallName: this.getServiceComponentMetrics, + * successCallback: this.getMetricsSuccessCallback, + * completeCallback: function () { + * requestCounter--; + * if (requestCounter === 0) this.onMetricsLoaded(); + * } + * } + * @type {Em.Object} + */ +App.WidgetLoadAggregator = Em.Object.create({ + /** + * @type {Array} + */ + requests: [], + + /** + * @type {number|null} + */ + timeoutId: null, + + /** + * @type {number} + * @const + */ + BULK_INTERVAL: 1000, + + /** + * add request + * every {{BULK_INTERVAL}} requests get collected, aggregated and sent to server + * + * @param {object} request + */ + add: function (request) { + var self = this; + + this.get('requests').push(request); + if (Em.isNone(this.get('timeoutId'))) { + this.set('timeoutId', window.setTimeout(function () { + self.runRequests(self.get('requests')); + self.get('requests').clear(); + clearTimeout(self.get('timeoutId')); + self.set('timeoutId', null); + }, this.get('BULK_INTERVAL'))); + } + }, + + /** + * return requests which grouped into bulks + * @param {Array} requests + * @returns {object} bulks + */ + groupRequests: function (requests) { + var bulks = {}; + + requests.forEach(function (request) { + var id = request.startCallName + "_" + request.data.component_name; + + if (Em.isNone(bulks[id])) { + bulks[id] = { + data: request.data, + context: request.context, + startCallName: request.startCallName + }; + bulks[id].subRequests = [{ + context: request.context, + successCallback: request.successCallback, + completeCallback: request.completeCallback + }]; + } else { + bulks[id].data.metric_paths.pushObjects(request.data.metric_paths); + bulks[id].subRequests.push({ + context: request.context, + successCallback: request.successCallback, + completeCallback: request.completeCallback + }); + } + }, this); + return bulks; + }, + + /** + * run aggregated requests + * @param {Array} requests + */ + runRequests: function (requests) { + var bulks = this.groupRequests(requests); + + for (var id in bulks) { + (function (_request) { + _request.data.metric_paths = _request.data.metric_paths.uniq(); + _request.context[_request.startCallName].call(_request.context, _request.data).done(function (response) { + _request.subRequests.forEach(function (subRequest) { + subRequest.successCallback.call(subRequest.context, response); + }, this); + }).complete(function () { + _request.subRequests.forEach(function (subRequest) { + subRequest.completeCallback.call(subRequest.context); + }, this); + }); + })(bulks[id]); + } + } }); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/599c1da8/ambari-web/app/utils/ajax/ajax.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/ajax/ajax.js b/ambari-web/app/utils/ajax/ajax.js index 254e2a9..c0a24c1 100644 --- a/ambari-web/app/utils/ajax/ajax.js +++ b/ambari-web/app/utils/ajax/ajax.js @@ -2522,13 +2522,8 @@ var urls = { mock: '/data/metrics/{serviceName}/Append_num_ops_&_Delete_num_ops.json' }, - 'widgets.hostComponent.get.hostName': { - real: '/clusters/{clusterName}/services/{serviceName}/components/{componentName}?{hostComponentCriteria}', - mock: '/data/metrics/{serviceName}/Append_num_ops.json' - }, - 'widgets.hostComponent.metrics.get': { - real: '/clusters/{clusterName}/hosts/{hostName}/host_components/{componentName}?fields={metricPaths}&format=null_padding', + real: '/clusters/{clusterName}/host_components?HostRoles/component_name={componentName}{hostComponentCriteria}&fields={metricPaths}&format=null_padding', mock: '/data/metrics/{serviceName}/Append_num_ops.json' }, http://git-wip-us.apache.org/repos/asf/ambari/blob/599c1da8/ambari-web/app/views/common/widget/graph_widget_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/common/widget/graph_widget_view.js b/ambari-web/app/views/common/widget/graph_widget_view.js index 321cb91..acdb401 100644 --- a/ambari-web/app/views/common/widget/graph_widget_view.js +++ b/ambari-web/app/views/common/widget/graph_widget_view.js @@ -208,31 +208,16 @@ App.GraphWidgetView = Em.View.extend(App.WidgetMixin, { * @returns {$.ajax} */ getHostComponentMetrics: function (request) { - var dfd; - var self = this; - dfd = $.Deferred(); - this.getHostComponentName(request).done(function (data) { - if (data) { - request.host_name = data.host_components[0].HostRoles.host_name; - App.ajax.send({ - name: 'widgets.hostComponent.metrics.get', - sender: self, - data: { - componentName: request.component_name, - hostName: request.host_name, - metricPaths: self.addTimeProperties(request.metric_paths).join(',') - } - }).done(function(metricData) { - self.getMetricsSuccessCallback(metricData); - dfd.resolve(); - }).fail(function(data){ - dfd.reject(); - }); - } - }).fail(function(data){ - dfd.reject(); + return App.ajax.send({ + name: 'widgets.hostComponent.metrics.get', + sender: this, + data: { + componentName: request.component_name, + metricPaths: this.addTimeProperties(request.metric_paths).join(','), + hostComponentCriteria: this.computeHostComponentCriteria(request) + }, + success: 'getHostComponentMetricsSuccessCallback' }); - return dfd.promise(); }, /** http://git-wip-us.apache.org/repos/asf/ambari/blob/599c1da8/ambari-web/test/mixins/common/widget_mixin_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/mixins/common/widget_mixin_test.js b/ambari-web/test/mixins/common/widget_mixin_test.js index c9ec6fb..01fb277 100644 --- a/ambari-web/test/mixins/common/widget_mixin_test.js +++ b/ambari-web/test/mixins/common/widget_mixin_test.js @@ -25,35 +25,25 @@ describe('App.WidgetMixin', function() { var mixinObject = mixinClass.create(); beforeEach(function () { this.mock = sinon.stub(mixinObject, 'getRequestData'); - sinon.stub(mixinObject, 'getHostComponentMetrics').returns({always: function(callback){ - callback(); - }}); - sinon.stub(mixinObject, 'getServiceComponentMetrics').returns({complete: function(callback){ - callback(); - }}); - sinon.stub(mixinObject, 'onMetricsLoaded'); + sinon.stub(App.WidgetLoadAggregator, 'add'); }); afterEach(function () { this.mock.restore(); - mixinObject.getHostComponentMetrics.restore(); - mixinObject.getServiceComponentMetrics.restore(); - mixinObject.onMetricsLoaded.restore(); + App.WidgetLoadAggregator.add.restore(); }); it('has host_component_criteria', function () { this.mock.returns({'key1': {host_component_criteria: 'criteria'}}); mixinObject.set('isLoaded', false); mixinObject.loadMetrics(); - expect(mixinObject.getHostComponentMetrics.calledWith({host_component_criteria: 'criteria'})).to.be.true; - expect(mixinObject.onMetricsLoaded.calledOnce).to.be.true; + expect(App.WidgetLoadAggregator.add.calledOnce).to.be.true; }); it('host_component_criteria is absent', function () { this.mock.returns({'key1': {}}); mixinObject.set('isLoaded', false); mixinObject.loadMetrics(); - expect(mixinObject.getServiceComponentMetrics.calledWith({})).to.be.true; - expect(mixinObject.onMetricsLoaded.calledOnce).to.be.true; + expect(App.WidgetLoadAggregator.add.calledOnce).to.be.true; }); }); @@ -175,8 +165,7 @@ describe('App.WidgetMixin', function() { serviceName: 'S1', componentName: 'C1', metricPaths: 'w1,w2' - }, - success: 'getMetricsSuccessCallback' + } }) }); }); @@ -208,28 +197,12 @@ describe('App.WidgetMixin', function() { describe("#getHostComponentMetrics()", function () { var mixinObject = mixinClass.create(); before(function () { - sinon.stub(App.ajax, 'send').returns({done: function(callback){ - callback(); - return this; - },fail: function(callback){ - callback(); - return this; - }}); - sinon.stub(mixinObject, 'getHostComponentName').returns({done: function(callback){ - var data = {host_components: [{HostRoles:{host_name:"c6401"}}]}; - callback(data); - return this; - },fail: function(callback){ - callback(); - return this; - }}); - - sinon.stub(mixinObject, 'getMetricsSuccessCallback') + sinon.stub(App.ajax, 'send'); + sinon.stub(mixinObject, 'computeHostComponentCriteria').returns('criteria') }); after(function () { App.ajax.send.restore(); - mixinObject.getHostComponentName.restore(); - mixinObject.getMetricsSuccessCallback.restore(); + mixinObject.computeHostComponentCriteria.restore(); }); it("", function () { var request = { @@ -243,8 +216,8 @@ describe('App.WidgetMixin', function() { sender: mixinObject, data: { componentName: 'C1', - hostName: "c6401", - metricPaths: 'w1,w2' + metricPaths: 'w1,w2', + hostComponentCriteria: 'criteria' } }) }); @@ -362,3 +335,113 @@ describe('App.WidgetMixin', function() { }); }); }); + + +describe('App.WidgetLoadAggregator', function() { + var aggregator = App.WidgetLoadAggregator; + + describe("#add()", function () { + beforeEach(function () { + sinon.stub(window, 'setTimeout').returns('timeId'); + }); + afterEach(function () { + window.setTimeout.restore(); + }); + it("timeout started", function () { + aggregator.set('timeoutId', 'timeId'); + aggregator.get('requests').clear(); + aggregator.add({}); + expect(aggregator.get('requests')).to.not.be.empty; + expect(window.setTimeout.called).to.be.false; + }); + it("timeout started", function () { + aggregator.set('timeoutId', null); + aggregator.get('requests').clear(); + aggregator.add({}); + expect(aggregator.get('requests')).to.not.be.empty; + expect(window.setTimeout.calledOnce).to.be.true; + expect(aggregator.get('timeoutId')).to.equal('timeId'); + }); + }); + + describe("#groupRequests()", function () { + it("", function () { + var requests = [ + { + startCallName: 'n1', + data: { + component_name: 'C1', + metric_paths: ['m1'] + }, + context: 'c1' + }, + { + startCallName: 'n1', + data: { + component_name: 'C1', + metric_paths: ['m2'] + }, + context: 'c2' + }, + { + startCallName: 'n2', + data: { + component_name: 'C1', + metric_paths: ['m3'] + }, + context: 'c3' + }, + { + startCallName: 'n1', + data: { + component_name: 'C2', + metric_paths: ['m4'] + }, + context: 'c4' + } + ]; + var result = aggregator.groupRequests(requests); + + expect(result['n1_C1'].subRequests.length).to.equal(2); + expect(result['n1_C1'].data.metric_paths.length).to.equal(2); + expect(result['n2_C1'].subRequests.length).to.equal(1); + expect(result['n2_C1'].data.metric_paths.length).to.equal(1); + expect(result['n1_C2'].subRequests.length).to.equal(1); + expect(result['n1_C2'].data.metric_paths.length).to.equal(1); + }); + }); + + describe("#runRequests()", function() { + var mock = { + f1: function () { + return { + done: Em.K, + complete: Em.K + } + } + }; + beforeEach(function () { + sinon.stub(aggregator, 'groupRequests', function(requests){ + return requests; + }); + sinon.spy(mock, 'f1'); + }); + afterEach(function () { + aggregator.groupRequests.restore(); + mock.f1.restore(); + }); + it("", function() { + var requests = { + 'r1': { + data: { + metric_paths: ['m1', 'm1', 'm2'] + }, + context: mock, + startCallName: 'f1' + } + }; + aggregator.runRequests(requests); + expect(mock.f1.calledWith(requests['r1'].data)).to.be.true; + }); + }); +});
