You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by at...@apache.org on 2015/05/26 14:11:30 UTC

ambari git commit: AMBARI-11388 Enhanced Dashboard: Aggregate calls for widget metrics. (atkach)

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 <at...@hortonworks.com>
Authored: Tue May 26 14:26:20 2015 +0300
Committer: Andrii Tkach <at...@hortonworks.com>
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;
+    });
+  });
+});