You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tez.apache.org by pr...@apache.org on 2015/05/06 12:11:49 UTC

tez git commit: TEZ-2406. Tez UI: Display per-io counter columns in task and attempt pages under vertex (Sreenath Somarajapuram via pramachandran)

Repository: tez
Updated Branches:
  refs/heads/master 5679b2838 -> 12ef073c2


TEZ-2406. Tez UI: Display per-io counter columns in task and attempt pages under vertex (Sreenath Somarajapuram via pramachandran)


Project: http://git-wip-us.apache.org/repos/asf/tez/repo
Commit: http://git-wip-us.apache.org/repos/asf/tez/commit/12ef073c
Tree: http://git-wip-us.apache.org/repos/asf/tez/tree/12ef073c
Diff: http://git-wip-us.apache.org/repos/asf/tez/diff/12ef073c

Branch: refs/heads/master
Commit: 12ef073c205ce7b09827a7ecbeea457d589c1c5f
Parents: 5679b28
Author: Prakash Ramachandran <pr...@hortonworks.com>
Authored: Wed May 6 15:36:49 2015 +0530
Committer: Prakash Ramachandran <pr...@hortonworks.com>
Committed: Wed May 6 15:36:49 2015 +0530

----------------------------------------------------------------------
 CHANGES.txt                                     |  1 +
 .../components/basic-table/search-view.js       |  2 +-
 .../task_task_attempts_controller.js            | 16 +---
 .../vertex_task_attempts_controller.js          | 16 +---
 .../controllers/vertex_tasks_controller.js      | 16 +---
 .../src/main/webapp/app/scripts/helpers/misc.js | 94 +++++++++++++++++++-
 .../scripts/mixins/auto-counter-column-mixin.js | 58 ++++++++++++
 .../app/scripts/mixins/column-selector-mixin.js | 85 ++++++++++++++----
 .../main/webapp/app/scripts/views/checkbox.js   | 29 ++++++
 .../app/scripts/views/multi-select-view.js      | 72 +++++++++++++++
 tez-ui/src/main/webapp/app/styles/colors.less   |  1 +
 tez-ui/src/main/webapp/app/styles/main.less     | 59 ++++++++++--
 tez-ui/src/main/webapp/app/styles/shared.less   |  4 +
 .../main/webapp/app/templates/common/table.hbs  | 42 ++++-----
 .../app/templates/components/basic-table.hbs    | 41 +++++----
 .../components/basic-table/header-cell.hbs      |  2 +-
 .../components/basic-table/search-view.hbs      |  2 +-
 tez-ui/src/main/webapp/app/templates/dags.hbs   |  3 -
 .../webapp/app/templates/views/multi-select.hbs | 55 ++++++++++++
 19 files changed, 491 insertions(+), 107 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 2446f6a..f060a8c 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -15,6 +15,7 @@ INCOMPATIBLE CHANGES
   TEZ-1993. Implement a pluggable InputSizeEstimator for grouping fairly
 
 ALL CHANGES:
+  TEZ-2406. Tez UI: Display per-io counter columns in task and attempt pages under vertex
   TEZ-2384. Add warning message in the case of prewarn under non-session mode.
   TEZ-2415. PMC RDF needs to use asfext:pmc, not asfext:PMC.
   TEZ-1752. Inputs / Outputs in the Runtime library should be interruptable.

http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/tez-ui/src/main/webapp/app/scripts/components/basic-table/search-view.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/components/basic-table/search-view.js b/tez-ui/src/main/webapp/app/scripts/components/basic-table/search-view.js
index 4dd41a1..36a2d4f 100644
--- a/tez-ui/src/main/webapp/app/scripts/components/basic-table/search-view.js
+++ b/tez-ui/src/main/webapp/app/scripts/components/basic-table/search-view.js
@@ -23,7 +23,7 @@ App.BasicTableComponent.SearchView = Ember.View.extend({
 
   text: '',
   _boundText: function () {
-    return this.get('text');
+    return this.get('text') || '';
   }.property(),
 
   _validRegEx: function () {

http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/tez-ui/src/main/webapp/app/scripts/controllers/task_task_attempts_controller.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/controllers/task_task_attempts_controller.js b/tez-ui/src/main/webapp/app/scripts/controllers/task_task_attempts_controller.js
index 558a740..c5c9eea 100644
--- a/tez-ui/src/main/webapp/app/scripts/controllers/task_task_attempts_controller.js
+++ b/tez-ui/src/main/webapp/app/scripts/controllers/task_task_attempts_controller.js
@@ -16,12 +16,13 @@
  * limitations under the License.
  */
 
-App.TaskAttemptsController = App.TablePageController.extend({
+App.TaskAttemptsController = App.TablePageController.extend(App.AutoCounterColumnMixin, {
 
   controllerName: 'TaskAttemptsController',
   needs: "task",
 
   entityType: 'taskTaskAttempt',
+  baseEntityType: 'taskAttempt',
   filterEntityType: 'task',
   filterEntityId: Ember.computed.alias('controllers.task.id'),
 
@@ -193,18 +194,7 @@ App.TaskAttemptsController = App.TablePageController.extend({
         }
       }
     ];
-  }.property(),
-
-  columnConfigs: function() {
-    return this.get('defaultColumnConfigs').concat(
-      App.Helpers.misc.normalizeCounterConfigs(
-        App.get('Configs.defaultCounters').concat(
-          App.get('Configs.tables.entity.taskAttempt') || [],
-          App.get('Configs.tables.sharedColumns') || []
-        )
-      )
-    );
-  }.property(),
+  }.property('filterEntityId'),
 
 });
 

http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/tez-ui/src/main/webapp/app/scripts/controllers/vertex_task_attempts_controller.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/controllers/vertex_task_attempts_controller.js b/tez-ui/src/main/webapp/app/scripts/controllers/vertex_task_attempts_controller.js
index f395b40..b4ed89a 100644
--- a/tez-ui/src/main/webapp/app/scripts/controllers/vertex_task_attempts_controller.js
+++ b/tez-ui/src/main/webapp/app/scripts/controllers/vertex_task_attempts_controller.js
@@ -16,12 +16,13 @@
  * limitations under the License.
  */
 
-App.VertexTaskAttemptsController = App.TablePageController.extend({
+App.VertexTaskAttemptsController = App.TablePageController.extend(App.AutoCounterColumnMixin, {
 
   controllerName: 'VertexTaskAttemptsController',
   needs: "vertex",
 
   entityType: 'vertexTaskAttempt',
+  baseEntityType: 'taskAttempt',
   filterEntityType: 'vertex',
   filterEntityId: Ember.computed.alias('controllers.vertex.id'),
 
@@ -196,17 +197,6 @@ App.VertexTaskAttemptsController = App.TablePageController.extend({
         }
       }
     ];
-  }.property(),
-
-  columnConfigs: function() {
-    return this.get('defaultColumnConfigs').concat(
-      App.Helpers.misc.normalizeCounterConfigs(
-        App.get('Configs.defaultCounters').concat(
-          App.get('Configs.tables.entity.taskAttempt') || [],
-          App.get('Configs.tables.sharedColumns') || []
-        )
-      )
-    );
-  }.property(),
+  }.property('filterEntityId'),
 
 });

http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/tez-ui/src/main/webapp/app/scripts/controllers/vertex_tasks_controller.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/controllers/vertex_tasks_controller.js b/tez-ui/src/main/webapp/app/scripts/controllers/vertex_tasks_controller.js
index 953ffcd..2cc0518 100644
--- a/tez-ui/src/main/webapp/app/scripts/controllers/vertex_tasks_controller.js
+++ b/tez-ui/src/main/webapp/app/scripts/controllers/vertex_tasks_controller.js
@@ -16,12 +16,13 @@
  * limitations under the License.
  */
 
-App.VertexTasksController = App.TablePageController.extend({
+App.VertexTasksController = App.TablePageController.extend(App.AutoCounterColumnMixin, {
 
   controllerName: 'VertexTasksController',
   needs: "vertex",
 
   entityType: 'vertexTask',
+  baseEntityType: 'task',
   filterEntityType: 'vertex',
   filterEntityId: Ember.computed.alias('controllers.vertex.id'),
 
@@ -172,17 +173,6 @@ App.VertexTasksController = App.TablePageController.extend({
         }
       }
     ];
-  }.property('id'),
-
-  columnConfigs: function() {
-    return this.get('defaultColumnConfigs').concat(
-      App.Helpers.misc.normalizeCounterConfigs(
-        App.get('Configs.defaultCounters').concat(
-          App.get('Configs.tables.entity.task') || [],
-          App.get('Configs.tables.sharedColumns') || []
-        )
-      )
-    );
-  }.property(),
+  }.property('filterEntityId')
 
 });
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/tez-ui/src/main/webapp/app/scripts/helpers/misc.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/helpers/misc.js b/tez-ui/src/main/webapp/app/scripts/helpers/misc.js
index 6794e95..81a7693 100644
--- a/tez-ui/src/main/webapp/app/scripts/helpers/misc.js
+++ b/tez-ui/src/main/webapp/app/scripts/helpers/misc.js
@@ -109,6 +109,50 @@ App.Helpers.misc = {
     return classPath.substr(classPath.lastIndexOf('.') + 1);
   },
 
+  /**
+   * Return a normalized group name for a counter name
+   * @param groupName {String}
+   * @return Normlaized name
+   */
+  getCounterGroupDisplayName: function (groupName) {
+    var displayName = App.Helpers.misc.getClassName(groupName), // Remove path
+        ioParts,
+        toText;
+
+    function removeCounterFromEnd(text) {
+      if(text.substr(-7) == 'Counter') {
+        text = text.substr(0, text.length - 7);
+      }
+      return text;
+    }
+
+    displayName = removeCounterFromEnd(displayName);
+
+    // Reformat per-io counters
+    switch(App.Helpers.misc.checkIOCounterGroup(displayName)) {
+      case 'in':
+        ioParts = displayName.split('_INPUT_');
+        toText = 'to %@ Input'.fmt(ioParts[1]);
+      break;
+      case 'out':
+        ioParts = displayName.split('_OUTPUT_');
+        toText = 'to %@ Output'.fmt(ioParts[1]);
+      break;
+    }
+    if(ioParts) {
+      ioParts = ioParts[0].split('_');
+      if(ioParts.length > 1) {
+        displayName = '%@ - %@ %@'.fmt(
+          removeCounterFromEnd(ioParts.shift()),
+          ioParts.join('_'),
+          toText
+        );
+      }
+    }
+
+    return displayName;
+  },
+
   /*
    * Normalizes counter style configurations
    * @param counterConfigs Array
@@ -116,9 +160,14 @@ App.Helpers.misc = {
    */
   normalizeCounterConfigs: function (counterConfigs) {
     return counterConfigs.map(function (configuration) {
-      configuration.headerCellName = configuration.counterName || configuration.counterId;
-      configuration.id = '%@/%@'.fmt(configuration.counterGroupName || configuration.groupId,
-          configuration.counterName || configuration.counterId),
+      var groupName = configuration.counterGroupName || configuration.groupId,
+          counterName = configuration.counterName || configuration.counterId;
+
+      configuration.headerCellName = '%@ - %@'.fmt(
+        App.Helpers.misc.getCounterGroupDisplayName(groupName),
+        counterName
+      );
+      configuration.id = '%@/%@'.fmt(groupName, counterName),
 
       configuration.getSortValue = App.Helpers.misc.getCounterCellContent;
       configuration.getCellContent =
@@ -413,6 +462,45 @@ App.Helpers.misc = {
     }
   },
 
+  /**
+   * Returns in/out/empty string based counter group type
+   * @param counterGroupName {String}
+   * @return in/out/empty string
+   */
+  checkIOCounterGroup: function (counterGroupName) {
+    if(counterGroupName == undefined){
+      debugger;
+    }
+    var relationPart = counterGroupName.substr(counterGroupName.indexOf('_') + 1);
+    if(relationPart.match('_INPUT_')) {
+      return 'in';
+    }
+    else if(relationPart.match('_OUTPUT_')) {
+      return 'out';
+    }
+    return '';
+  },
+
+  /**
+   * Return unique values form array based on a property
+   * @param array {Array}
+   * @param property {String}
+   * @return uniqueArray {Array}
+   */
+  getUniqueByProperty: function (array, property) {
+    var propHash = {},
+        uniqueArray = [];
+
+    array.forEach(function (item) {
+      if(item && !propHash[item[property]]) {
+        uniqueArray.push(item);
+        propHash[item[property]] = true;
+      }
+    });
+
+    return uniqueArray;
+  },
+
   timelinePathForType: (function () {
     var typeToPathMap = {
       dag: 'TEZ_DAG_ID',

http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/tez-ui/src/main/webapp/app/scripts/mixins/auto-counter-column-mixin.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/mixins/auto-counter-column-mixin.js b/tez-ui/src/main/webapp/app/scripts/mixins/auto-counter-column-mixin.js
new file mode 100644
index 0000000..2c6b531
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/mixins/auto-counter-column-mixin.js
@@ -0,0 +1,58 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+App.AutoCounterColumnMixin = Em.Mixin.create({
+
+  baseEntityType: null, // Must be set in the controller that uses this Mixin
+
+  columnSelectorMessage: function () {
+    return "<span class='per-io'>Per-IO counter</span> selection wouldn't persist across %@.".fmt(
+      this.get('filterEntityType').pluralize()
+    );
+  }.property('filterEntityType'),
+
+  columnConfigs: function() {
+    var counterConfigs = App.Helpers.misc.normalizeCounterConfigs(
+      App.get('Configs.defaultCounters').concat(
+        App.get('Configs.tables.entity.' + this.get('baseEntityType')) || [],
+        App.get('Configs.tables.sharedColumns') || []
+      )
+    ), dynamicCounterConfigs = [];
+
+    this.get('data').forEach(function (row) {
+      var counterGroups = row.get('counterGroups');
+      if(counterGroups) {
+        counterGroups.forEach(function (group) {
+          group.counters.forEach(function (counter) {
+            dynamicCounterConfigs.push({
+              counterName: counter.counterName,
+              counterGroupName: group.counterGroupName
+            });
+          });
+        });
+      }
+    });
+
+    return this.get('defaultColumnConfigs').concat(
+      App.Helpers.misc.getUniqueByProperty(counterConfigs.concat(
+        App.Helpers.misc.normalizeCounterConfigs(dynamicCounterConfigs)
+      ), 'id')
+    );
+  }.property('data', 'defaultColumnConfigs', 'baseEntityType')
+
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/tez-ui/src/main/webapp/app/scripts/mixins/column-selector-mixin.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/mixins/column-selector-mixin.js b/tez-ui/src/main/webapp/app/scripts/mixins/column-selector-mixin.js
index d3b3bbd..3a76d61 100644
--- a/tez-ui/src/main/webapp/app/scripts/mixins/column-selector-mixin.js
+++ b/tez-ui/src/main/webapp/app/scripts/mixins/column-selector-mixin.js
@@ -38,8 +38,10 @@ App.ColumnSelectorMixin = Em.Mixin.create({
   _storeKey: '',
   visibleColumnIds: {},
   columnConfigs: [],
+  selectOptions: [],
 
   columnSelectorTitle: 'Column Selector',
+  columnSelectorMessage: '',
 
   init: function(){
     var visibleColumnIds;
@@ -59,7 +61,7 @@ App.ColumnSelectorMixin = Em.Mixin.create({
 
     this._super();
     this.set('visibleColumnIds', visibleColumnIds);
-  },
+  }.observes('defaultColumnConfigs'), //To reset on entity change
 
   columns: function() {
     var visibleColumnConfigs = this.get('columnConfigs').filter(function (column) {
@@ -67,23 +69,76 @@ App.ColumnSelectorMixin = Em.Mixin.create({
     }, this);
 
     return App.Helpers.misc.createColumnDescription(visibleColumnConfigs);
-  }.property('visibleColumnIds'),
+  }.property('visibleColumnIds', 'columnConfigs'),
+
+  _getSelectOptions: function () {
+    var group = null,
+        highlight = false,
+        visibleColumnIds = this.get('visibleColumnIds');
+
+    return this.get('columnConfigs').map(function (config) {
+      var css = '';
+
+      highlight = highlight ^ (config.counterGroupName != group),
+      group = config.counterGroupName;
+
+      if(highlight) {
+        css += ' highlight';
+      }
+      if(group && App.Helpers.misc.checkIOCounterGroup(group)) {
+        css += ' per-io';
+      }
+
+      return Em.Object.create({
+        id: config.id,
+        displayText: config.headerCellName,
+        css: css,
+        selected: visibleColumnIds[config.id]
+      });
+    });
+  },
 
   actions: {
     selectColumns: function () {
-      var that = this;
-
-      App.Dialogs.displayMultiSelect(this.get('columnSelectorTitle'), this.get('columnConfigs'), this.visibleColumnIds, {
-        displayText: 'headerCellName'
-      }).then(function (data) {
-        if(isObjectsDifferent(data, that.visibleColumnIds)) {
-          try {
-            localStorage.setItem(that._storeKey , JSON.stringify(data));
-          }catch(e){}
-          that.set('visibleColumnIds', data);
-        }
-      });
+      this.set('selectOptions', this._getSelectOptions());
+
+      Bootstrap.ModalManager.open(
+        'columnSelector',
+        this.get('columnSelectorTitle'),
+        App.MultiSelectView.extend({
+          options: this.get('selectOptions'),
+          message: this.get('columnSelectorMessage')
+        }),
+        [Ember.Object.create({
+          title: 'Ok',
+          dismiss: 'modal',
+          clicked: 'selectionChange'
+        })],
+        this
+      );
+    },
+
+    selectionChange: function () {
+      var visibleColumnIds = this.get('selectOptions').reduce(function (obj, option) {
+            if(option.get('selected')) {
+              obj[option.get('id')] = true;
+            }
+            return obj;
+          }, {}),
+          selectionToSave = this.get('selectOptions').reduce(function (obj, option) {
+            var id = option.id;
+            if(!id.match('_INPUT_') && !id.match('_OUTPUT_') && visibleColumnIds[id]) {
+              obj[id] = true;
+            }
+            return obj;
+          }, {});
+
+      if(isObjectsDifferent(visibleColumnIds, this.get('visibleColumnIds'))) {
+        try {
+          localStorage.setItem(this._storeKey , JSON.stringify(selectionToSave));
+        }catch(e){}
+        this.set('visibleColumnIds', visibleColumnIds);
+      }
     }
   }
-
 });
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/tez-ui/src/main/webapp/app/scripts/views/checkbox.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/views/checkbox.js b/tez-ui/src/main/webapp/app/scripts/views/checkbox.js
new file mode 100644
index 0000000..da2de14
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/views/checkbox.js
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+App.Checkbox = Em.Checkbox.extend({
+  change: function() {
+    var value = this.get('checked'),
+        target = this.get('target') || this.get('context');
+
+    if(target) {
+      Em.run.later(target.send.bind(target, this.get('action'), value), 100);
+    }
+    return true;
+  }
+});

http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/tez-ui/src/main/webapp/app/scripts/views/multi-select-view.js
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/scripts/views/multi-select-view.js b/tez-ui/src/main/webapp/app/scripts/views/multi-select-view.js
new file mode 100644
index 0000000..ace7b94
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/scripts/views/multi-select-view.js
@@ -0,0 +1,72 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+App.MultiSelectView = Ember.View.extend({
+  templateName: 'views/multi-select',
+  classNames: ['multi-select'],
+
+  selectAll: false,
+  searchRegex: '',
+
+  options: null, //Must be set by sub-classes or instances
+
+  _validRegEx: function () {
+    var regExText = this.get('searchRegex');
+    regExText = regExText.substr(regExText.indexOf(':') + 1);
+    try {
+      new RegExp(regExText, 'im');
+    }
+    catch(e) {
+      return false;
+    }
+    return true;
+  }.property('searchRegex'),
+
+  visibleOptions: function () {
+    var options = this.get('options'),
+        regExText = this.get('searchRegex'),
+        regEx;
+
+    if (Em.isEmpty(regExText) || !this.get('_validRegEx')) {
+      return options;
+    }
+
+    regEx = new RegExp(regExText, 'i');
+    return options.filter(function (option) {
+      return regEx.test(option.get('displayText'));
+    });
+  }.property('options', 'searchRegex'),
+
+  _selectObserver: function () {
+    var selectedCount = 0;
+    this.get('visibleOptions').forEach(function (option) {
+      if(option.get('selected')) {
+        selectedCount++;
+      }
+    });
+    this.set('selectAll', selectedCount > 0 && selectedCount == this.get('visibleOptions.length'));
+  }.observes('visibleOptions.@each.selected'),
+
+  actions: {
+    selectAll: function (checked) {
+      this.get('visibleOptions').forEach(function (option) {
+        option.set('selected', checked);
+      });
+    }
+  }
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/tez-ui/src/main/webapp/app/styles/colors.less
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/styles/colors.less b/tez-ui/src/main/webapp/app/styles/colors.less
index aa0d96a..a426893 100644
--- a/tez-ui/src/main/webapp/app/styles/colors.less
+++ b/tez-ui/src/main/webapp/app/styles/colors.less
@@ -33,6 +33,7 @@
 @text-color: #666666;
 @text-red: red;
 @text-light: #BBBBBB;
+@text-green: green;
 
 @top-nav-bg-color-from: #d5d5d5;
 @top-nav-bg-color-to: #f0f0f0;

http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/tez-ui/src/main/webapp/app/styles/main.less
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/styles/main.less b/tez-ui/src/main/webapp/app/styles/main.less
index 5da7e06..ad3f132 100644
--- a/tez-ui/src/main/webapp/app/styles/main.less
+++ b/tez-ui/src/main/webapp/app/styles/main.less
@@ -657,6 +657,10 @@ body, html {
 
       overflow: hidden;
       transform: translateZ(0);
+
+      i {
+        position: static;
+      }
     }
 
     .horizontal-half {
@@ -715,6 +719,11 @@ body, html {
 
     overflow: auto;
 
+    .data-availability-message {
+      font-size: 24px;
+      text-align: center;
+    }
+
     .table-body {
       .noise-background;
       white-space: nowrap;
@@ -788,11 +797,6 @@ body, html {
 
           padding-right: 15px;
 
-          -webkit-user-select: none;
-          -moz-user-select: none;
-          -ms-user-select: none;
-          user-select: none;
-
           background-color: @bg-grey;
           border-bottom: 1px solid @border-color;
         }
@@ -849,3 +853,48 @@ body, html {
   margin-left: -1px;
   margin-top: -1px;
 }
+
+.multi-select {
+  .message {
+    text-align: right;
+    font-size: 10px;
+  }
+
+  .selection-list {
+    border: 1px solid @border-color;
+
+    .highlight {
+      background-color: @bg-lite;
+    }
+    .select-option, .search-option {
+      border-top: 1px dotted @border-color;
+      padding: 5px;
+
+      .checkbox {
+        margin-right: 10px;
+        float: left;
+        vertical-align: middle;
+      }
+    }
+    .search-option {
+      border: none;
+
+      .form-group {
+        .inline-block;
+        .align-top;
+
+        width: 470px;
+        margin: 4px 0px 0px 20px;
+      }
+    }
+  }
+}
+
+.modal-body {
+  max-height:500px;
+  overflow-y: auto;
+}
+
+.per-io {
+  color: @text-green;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/tez-ui/src/main/webapp/app/styles/shared.less
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/styles/shared.less b/tez-ui/src/main/webapp/app/styles/shared.less
index 7cece16..a3cdf2a 100644
--- a/tez-ui/src/main/webapp/app/styles/shared.less
+++ b/tez-ui/src/main/webapp/app/styles/shared.less
@@ -49,6 +49,10 @@
   white-space: nowrap;
 }
 
+.no-border {
+  border: none !important;
+}
+
 .align-top {
   vertical-align: top;
 }

http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/tez-ui/src/main/webapp/app/templates/common/table.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/templates/common/table.hbs b/tez-ui/src/main/webapp/app/templates/common/table.hbs
index 631d08d..462089d 100644
--- a/tez-ui/src/main/webapp/app/templates/common/table.hbs
+++ b/tez-ui/src/main/webapp/app/templates/common/table.hbs
@@ -17,35 +17,31 @@
 }}
 
 {{#unless loading}}
-  {{#if data.length}}
-    {{load-time-component
-      isRefreshable=isRefreshable
-      time=data.content.0.timeStamp
-      refresh='refresh'
-    }}
+  {{load-time-component
+    isRefreshable=isRefreshable
+    time=data.content.0.timeStamp
+    refresh='refresh'
+  }}
 
-    {{basic-table-component
-      columns=columns
-      rows=data.content
+  {{basic-table-component
+    columns=columns
+    rows=data.content
 
-      extraHeaderItem=App.ExtraTableButtonsView
-      statusMessage=statusMessage
+    extraHeaderItem=App.ExtraTableButtonsView
+    statusMessage=statusMessage
 
-      enableSearch=true
-      enablePagination=true
-      enableSort=true
+    enableSearch=true
+    enablePagination=true
+    enableSort=true
 
-      pageNumBinding='pageNum'
-      rowCountBinding='rowCount'
+    pageNumBinding='pageNum'
+    rowCountBinding='rowCount'
 
-      searchTextBinding='searchText'
+    searchTextBinding='searchText'
 
-      sortColumnIdBinding='sortColumnId'
-      sortOrderBinding='sortOrder'
-    }}
-  {{else}}
-    <h1>No records available!</n1>
-  {{/if}}
+    sortColumnIdBinding='sortColumnId'
+    sortOrderBinding='sortOrder'
+  }}
 {{else}}
   {{partial 'partials/loading-spinner'}}
   <div class="text-align-center">

http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/tez-ui/src/main/webapp/app/templates/components/basic-table.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/templates/components/basic-table.hbs b/tez-ui/src/main/webapp/app/templates/components/basic-table.hbs
index 175a3b9..d1e48b2 100644
--- a/tez-ui/src/main/webapp/app/templates/components/basic-table.hbs
+++ b/tez-ui/src/main/webapp/app/templates/components/basic-table.hbs
@@ -21,7 +21,10 @@
     <div class='table-header'>
       <div class="horizontal-half align-top">
         {{#if enableSearch}}
-          {{view App.BasicTableComponent.SearchView text=searchText}}
+          {{view App.BasicTableComponent.SearchView
+            text=searchText
+            placeholder="RegEx or Column1, Column2... :RegEx"
+          }}
         {{/if}}
         <div class="table-message">
           {{#if _statusMessage}}
@@ -40,21 +43,27 @@
     </div>
   {{/if}}
   <div class='table-body-container'>
-    <div class='table-body'>
-      {{#each column in _columns}}
-        <div {{bind-attr
-            style=column.customStyle
-            class=":table-column _view.contentIndex::first-item"
-        }}>
-          {{view column.headerCellView}}
-          {{#each row in _rows}}
-            <div class='table-cell {{unbound firstItemCSS _view}}'>
-              {{view column.cellView row=row}}
-            </div>
-          {{/each}}
-        </div>
-      {{/each}}
-    </div>
+    {{#unless _columns.length}}
+      <div class="data-availability-message">No columns available!</div>
+    {{else}}{{#unless _rows.length}}
+      <div class="data-availability-message">No records available!</div>
+    {{else}}
+      <div class='table-body'>
+        {{#each column in _columns}}
+          <div {{bind-attr
+              style=column.customStyle
+              class=":table-column _view.contentIndex::first-item"
+          }}>
+            {{view column.headerCellView}}
+            {{#each row in _rows}}
+              <div class='table-cell {{unbound firstItemCSS _view}}'>
+                {{view column.cellView row=row}}
+              </div>
+            {{/each}}
+          </div>
+        {{/each}}
+      </div>
+    {{/unless}}{{/unless}}
   </div>
   <div class='table-footer'>
     <div class="horizontal-half align-top">

http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/tez-ui/src/main/webapp/app/templates/components/basic-table/header-cell.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/templates/components/basic-table/header-cell.hbs b/tez-ui/src/main/webapp/app/templates/components/basic-table/header-cell.hbs
index c975d32..394752c 100644
--- a/tez-ui/src/main/webapp/app/templates/components/basic-table/header-cell.hbs
+++ b/tez-ui/src/main/webapp/app/templates/components/basic-table/header-cell.hbs
@@ -16,7 +16,7 @@
 * limitations under the License.
 }}
 
-<div class='table-header-cell'>
+<div class='table-header-cell' title='{{unbound column.headerCellName}}'>
   <div class='cell-content'>
     {{unbound column.headerCellName}}
   </div>

http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/tez-ui/src/main/webapp/app/templates/components/basic-table/search-view.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/templates/components/basic-table/search-view.hbs b/tez-ui/src/main/webapp/app/templates/components/basic-table/search-view.hbs
index 949fc24..4795bbb 100644
--- a/tez-ui/src/main/webapp/app/templates/components/basic-table/search-view.hbs
+++ b/tez-ui/src/main/webapp/app/templates/components/basic-table/search-view.hbs
@@ -19,7 +19,7 @@
 <div {{bind-attr class=":input-group view._validRegEx::has-error"}}>
   {{input
     class="form-control"
-    placeholder="RegEx or Column1, Column2... :RegEx"
+    placeholder=view.placeholder
     action="search"
     value=view._boundText
     targetObject=view

http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/tez-ui/src/main/webapp/app/templates/dags.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/templates/dags.hbs b/tez-ui/src/main/webapp/app/templates/dags.hbs
index 727e28a..0f272c5 100644
--- a/tez-ui/src/main/webapp/app/templates/dags.hbs
+++ b/tez-ui/src/main/webapp/app/templates/dags.hbs
@@ -89,9 +89,6 @@
 
       statusMessage=statusMessage
     }}
-    {{#unless sortedContent.length}}
-      <h1 class="no-margin">No records available!</h1>
-    {{/unless}}
   {{else}}
     {{partial 'partials/loading-spinner'}}
     <div class="text-align-center">

http://git-wip-us.apache.org/repos/asf/tez/blob/12ef073c/tez-ui/src/main/webapp/app/templates/views/multi-select.hbs
----------------------------------------------------------------------
diff --git a/tez-ui/src/main/webapp/app/templates/views/multi-select.hbs b/tez-ui/src/main/webapp/app/templates/views/multi-select.hbs
new file mode 100644
index 0000000..ebab317
--- /dev/null
+++ b/tez-ui/src/main/webapp/app/templates/views/multi-select.hbs
@@ -0,0 +1,55 @@
+{{!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+}}
+
+<div class="message">
+  {{{view.message}}}
+</div>
+<div class="selection-list">
+  <div class="search-option highlight">
+    <div class="inline-block">
+      Select All<br/>
+      {{view App.Checkbox
+        classNames='inline-display checkbox'
+        checked=view.selectAll
+        action='selectAll'
+        target=view
+      }}
+    </div>
+    <div {{bind-attr class=":form-group view._validRegEx::has-error"}}>
+      {{input
+        class="form-control"
+        placeholder="Filter options"
+        value=view.searchRegex
+      }}
+    </div>
+  </div>
+  {{#if view.visibleOptions.length}}
+    {{#each option in view.visibleOptions}}
+      <div class="select-option {{unbound option.css}}"}}>
+        {{input
+          type="checkbox"
+          classNames='inline-display checkbox'
+          checked=option.selected
+        }}
+        {{option.displayText}}
+      </div>
+    {{/each}}
+  {{else}}
+    <h4>&nbsp;No options available...</h4>
+  {{/if}}
+</div>
\ No newline at end of file