Repository: tez Updated Branches: refs/heads/master baa506557 -> d7ba1098e
TEZ-2893. Tez UI: Retain vertex info displayed in DAG details page even after completion (sree) Project: http://git-wip-us.apache.org/repos/asf/tez/repo Commit: http://git-wip-us.apache.org/repos/asf/tez/commit/d7ba1098 Tree: http://git-wip-us.apache.org/repos/asf/tez/tree/d7ba1098 Diff: http://git-wip-us.apache.org/repos/asf/tez/diff/d7ba1098 Branch: refs/heads/master Commit: d7ba1098e8d2c0050d6acf490906a4c86346f256 Parents: baa5065 Author: Sreenath Somarajapuram <[email protected]> Authored: Thu Nov 5 07:45:10 2015 +0530 Committer: Sreenath Somarajapuram <[email protected]> Committed: Thu Nov 5 07:45:10 2015 +0530 ---------------------------------------------------------------------- CHANGES.txt | 1 + tez-ui/src/main/webapp/app/scripts/app.js | 15 +- .../bs-progress-animated-component.js | 4 +- .../app/scripts/controllers/dag_controller.js | 151 +++-------- .../scripts/controllers/dag_index_controller.js | 263 ++++++++++++------- .../app/scripts/controllers/dag_vertices.js | 6 +- .../scripts/controllers/polling-controller.js | 1 + .../controllers/table-page-controller.js | 8 + .../main/webapp/app/scripts/helpers/em-data.js | 59 +++++ .../scripts/helpers/entity-array-pollster.js | 51 +--- .../app/scripts/models/TimelineRestAdapter.js | 38 ++- .../src/main/webapp/app/scripts/models/dag.js | 16 +- tez-ui/src/main/webapp/app/scripts/router.js | 10 +- .../src/main/webapp/app/templates/dag/index.hbs | 20 +- 14 files changed, 375 insertions(+), 268 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tez/blob/d7ba1098/CHANGES.txt ---------------------------------------------------------------------- diff --git a/CHANGES.txt b/CHANGES.txt index 4d63906..355a85c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -7,6 +7,7 @@ INCOMPATIBLE CHANGES TEZ-2679. Admin forms of launch env settings ALL CHANGES: + TEZ-2893. Tez UI: Retain vertex info displayed in DAG details page even after completion TEZ-2878. Tez UI: AM error handling - Make the UI handle cases in which AM returns unexpected/no data TEZ-2922. Tez Live UI gives access denied for admins TEZ-2849. Implement Specific Workaround for JDK-8026049 & JDK-8073093. http://git-wip-us.apache.org/repos/asf/tez/blob/d7ba1098/tez-ui/src/main/webapp/app/scripts/app.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/scripts/app.js b/tez-ui/src/main/webapp/app/scripts/app.js index 5d7e28d..c1c2c45 100644 --- a/tez-ui/src/main/webapp/app/scripts/app.js +++ b/tez-ui/src/main/webapp/app/scripts/app.js @@ -194,16 +194,23 @@ App.ready = function () { // v2 version of am web services App.DagInfoAdapter = App.AMInfoAdapter.extend({ namespace: App.Configs.restNamespace.aminfoV2, + findQuery: function(store, type, query) { + var record = query.metadata; + delete query.metadata; + return this.ajax( + this.buildURL(Ember.String.pluralize(type.typeKey), + record.dagID, Em.Object.create(record)), 'GET', { data: query}); + }, buildURL: function(type, id, record) { - var url = this._super(type, null, record); - return url.replace('__app_id__', record.get('appId')) - .fmt(record.get('dagIdx')); + var url = this._super(type, undefined, record); + return url.replace('__app_id__', record.get('appID')).fmt(id); }, - pathForType: function() { + pathForType: function(typeName) { return 'dagInfo?dagID=%@'; } }); + App.VertexInfoAdapter = App.AMInfoAdapter.extend({ namespace: App.Configs.restNamespace.aminfoV2, findQuery: function(store, type, query) { http://git-wip-us.apache.org/repos/asf/tez/blob/d7ba1098/tez-ui/src/main/webapp/app/scripts/components/bs-progress-animated-component.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/scripts/components/bs-progress-animated-component.js b/tez-ui/src/main/webapp/app/scripts/components/bs-progress-animated-component.js index 22f380d..6a260ea 100644 --- a/tez-ui/src/main/webapp/app/scripts/components/bs-progress-animated-component.js +++ b/tez-ui/src/main/webapp/app/scripts/components/bs-progress-animated-component.js @@ -25,7 +25,9 @@ Bootstrap.BsProgressAnimatedComponent = Bootstrap.BsProgressComponent.extend({ }, progressDecimalObserver: function () { - this.set('progress', parseInt(this.get('progressDecimal') * 100).toString()); + var progress = parseFloat(this.get('progressDecimal')); + progress = (typeof progress == 'number' && !isNaN(progress)) ? progress : 0; + this.set('progress', parseInt(progress * 100).toString()); }.observes('progressDecimal'), progressObserver: function () { http://git-wip-us.apache.org/repos/asf/tez/blob/d7ba1098/tez-ui/src/main/webapp/app/scripts/controllers/dag_controller.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/scripts/controllers/dag_controller.js b/tez-ui/src/main/webapp/app/scripts/controllers/dag_controller.js index 9643f64..95c6a8f 100644 --- a/tez-ui/src/main/webapp/app/scripts/controllers/dag_controller.js +++ b/tez-ui/src/main/webapp/app/scripts/controllers/dag_controller.js @@ -16,10 +16,47 @@ * limitations under the License. */ -App.DagController = Em.ObjectController.extend(App.Helpers.DisplayHelper, { +App.DagController = App.PollingController.extend(App.Helpers.DisplayHelper, { controllerName: 'DagController', pageTitle: 'Dag', + loading: true, + isActive: false, + + pollingType: 'dagInfo', + + setup: function () { + this.set('isActive', true); + }, + reset: function () { + this.set('isActive', false); + }, + + pollsterControl: function () { + if(this.get('status') == 'RUNNING' && + this.get('amWebServiceVersion') != '1' && + this.get('isActive')) { + this.get('pollster').start(); + } + else { + this.get('pollster').stop(); + } + }.observes('status', 'amWebServiceVersion', 'isActive'), + + pollsterOptionsObserver: function () { + var model = this.get('model'); + + this.get('pollster').setProperties( (model && model.get('status') != 'SUCCEEDED') ? { + targetRecords: [model], + options: { + appID: this.get('applicationId'), + dagID: App.Helpers.misc.getIndexFromId(this.get('id')), + } + } : { + targetRecords: [], + options: null + }); + }.observes('applicationId', 'model', 'model.status', 'id'), loadAdditional: function(dag) { var that = this; @@ -68,19 +105,10 @@ App.DagController = Em.ObjectController.extend(App.Helpers.DisplayHelper, { var allLoaders = Em.RSVP.all(loaders); allLoaders.then(function(){ - ['dagProgress', 'dagInfo', 'vertexInfo'].forEach(function(itemType){ - that.store.unloadAll(itemType); - }); if (dag.get('status') === 'RUNNING') { // update the progress info if available. this need not block the UI if (dag.get('amWebServiceVersion') == '1') { that.updateInfoFromAM(dag); - } else { - // if AM version is v2 we keep updating the status, progress etc live. - ["loading", "id", "model.status"].forEach(function(item) { - Em.addObserver(that, item, that.startAMInfoUpdateService); - }); - that.startAMInfoUpdateService(); } } }); @@ -91,116 +119,17 @@ App.DagController = Em.ObjectController.extend(App.Helpers.DisplayHelper, { // called only for v1 version of am api. updateInfoFromAM: function(dag) { var that = this; + App.Helpers.misc.removeRecord(this.get('store'), 'dagProgress', dag.get('id')); var aminfoLoader = this.store.find('dagProgress', dag.get('id'), { appId: dag.get('applicationId'), dagIdx: dag.get('idx') }).then(function(dagProgressInfo) { - that.set('amDagInfo', dagProgressInfo); + that.set('dag.progress', dagProgressInfo.get('progress')); }).catch(function (error) { Em.Logger.error("Failed to fetch dagProgress" + e); }); }, - updateAMDagInfo: function() { - var dagId = this.get('id') - that = this, - dagInfoLoader = null; - - if (!dagId) return; - - if (this.store.recordIsLoaded("dagInfo", dagId)) { - var dagInfoRecord = this.store.recordForId("dagInfo", dagId); - if (dagInfoRecord.get('isLoading')) return; - dagInfoLoader = dagInfoRecord.reload(); - } else { - dagInfoLoader = this.store.find("dagInfo", dagId, { - appId: that.get('applicationId'), - dagIdx: that.get('idx') - }) - } - - dagInfoLoader.then(function(dagInfo){ - that.set('amDagInfo', dagInfo); - //TODO: find another way to trigger notification - that.set('amDagInfo._amInfoLastUpdatedTime', moment()); - that.set('amProgressInfoSucceededOnce', true); - }).catch(function(e){ - if (that.get('amProgressInfoSucceededOnce') === true) { - that.set('amProgressInfoSucceededOnce', false); - App.Helpers.ErrorBar.getInstance().show( - "Failed to get in-progress status. Manually refresh to get the updated status", - "Application Manager either exited or is not running."); - } - }); - }, - - updateAMVerticesInfo: function() { - var dagId = this.get('id') - that = this, - verticesInfoLoader = null; - - if (!dagId) return; - - verticesInfoLoader = this.store.findQuery('vertexInfo', { - metadata: { - appID: that.get('applicationId'), - dagID: that.get('idx'), - counters: App.get('vertexCounters') - } - }); - - verticesInfoLoader.then(function(verticesInfo) { - that.set('amVertexInfo', verticesInfo); - }).catch(function(e){ - // do nothing - }); - - }, - - startAMInfoUpdateService: function() { - if (this.get('loading') || !this.get('model.id') || this.get('model.status') != 'RUNNING') { - return; - } - - var amInfoUpdateService = this.get('amInfoUpdateService') - that = this; - - if (Em.isNone(amInfoUpdateService)) { - amInfoUpdateService = App.Helpers.Pollster.create({ - onPoll: function() { - that.updateAMDagInfo(); - that.updateAMVerticesInfo(); - } - }); - that.set('amInfoUpdateService', amInfoUpdateService); - amInfoUpdateService.start(true); - - ["loading", "id", "model.status"].forEach(function(item) { - Em.addObserver(that, item, that.stopAMInfoUpdateService); - }); - } - else { - that.updateAMDagInfo(); - that.updateAMVerticesInfo(); - } - }, - - dostopAMInfoUpdateService: function() { - var amInfoUpdateService = this.get('amInfoUpdateService'); - if (!Em.isNone(amInfoUpdateService)) { - amInfoUpdateService.stop(); - this.set('amInfoUpdateService', undefined); - } - }, - - // stop the update service if the status changes. see startAMInfoUpdateService - stopAMInfoUpdateService: function() { - that.set('amProgressInfoSucceededOnce', false); - if (this.get('loading') || this.get('model.status') != 'RUNNING') { - this.dostopAMInfoUpdateService(); - } - }, - enableAppIdLink: function() { return !!this.get('tezApp'); }.property('applicationId', 'tezApp'), http://git-wip-us.apache.org/repos/asf/tez/blob/d7ba1098/tez-ui/src/main/webapp/app/scripts/controllers/dag_index_controller.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/scripts/controllers/dag_index_controller.js b/tez-ui/src/main/webapp/app/scripts/controllers/dag_index_controller.js index a85c707..ad3c454 100644 --- a/tez-ui/src/main/webapp/app/scripts/controllers/dag_index_controller.js +++ b/tez-ui/src/main/webapp/app/scripts/controllers/dag_index_controller.js @@ -16,21 +16,59 @@ * limitations under the License. */ - //TODO: watch individual counters. -App.DagIndexController = Em.ObjectController.extend(App.ModelRefreshMixin, { +App.DagIndexController = App.TablePageController.extend({ controllerName: 'DagIndexController', + needs: "dag", - needs: 'dag', + entityType: 'dagVertex', + filterEntityType: 'dag', + filterEntityId: Ember.computed.alias('controllers.dag.id'), - liveData: null, + cacheDomain: Ember.computed.alias('controllers.dag.id'), - succeededTasks: null, - totalTasks: null, - completedVertices: null, + pollingType: 'vertexInfo', - liveDataObserver: function () { - var vertexInfoContent = this.get('amVertexInfo.content'), - liveData = null, + init: function () { + this._super(); + this.set('pollster.mergeProperties', ['progress', 'status', 'runningTasks', 'pendingTasks', + 'sucessfulTasks', 'failedTaskAttempts', 'killedTaskAttempts']); + }, + + reset: function () { + this._super(); + this.set('data', null); + }, + + pollsterControl: function () { + if(this.get('status') == 'RUNNING' && + this.get('amWebServiceVersion') != '1' && + !this.get('loading') && + this.get('isActive') && + this.get('rowsDisplayed.length') > 0) { + this.get('pollster').start(); + } + else { + this.get('pollster').stop(); + } + }.observes('status', 'amWebServiceVersion', 'loading', 'isActive'), + + applicationComplete: function () { + this._super(); + this.set('controllers.dag.progress', 1); + }, + + pollsterOptionsObserver: function () { + this.set('pollster.options', { + appID: this.get('applicationId'), + dagID: this.get('idx') + }); + }.observes('applicationId', 'idx').on('init'), + + progressDetails: null, + + progressObserver: function () { + var vertexInfoContent = this.get('pollster.polledRecords.content'), + progressDetails = null, succeededTasks = null, totalTasks = null, completedVertices = null; @@ -42,65 +80,86 @@ App.DagIndexController = Em.ObjectController.extend(App.ModelRefreshMixin, { completedVertices = 0; liveData.forEach(function (vertex) { - succeededTasks += parseInt(vertex.get('succeededTasks')); - totalTasks += parseInt(vertex.get('totalTasks')); + succeededTasks += parseInt(vertex.get('sucessfulTasks')); + totalTasks += parseInt(vertex.get('numTasks')); if(vertex.get('progress') >= 1) { completedVertices++; } }); + + progressDetails = { + succeededTasks: succeededTasks, + completedVertices: completedVertices, + totalTasks: totalTasks + }; } - this.setProperties({ - liveData: liveData, - succeededTasks: succeededTasks, - totalTasks: totalTasks, - completedVertices: completedVertices + this.set('progressDetails', progressDetails); + }.observes('pollster.polledRecords'), + + dataObserver: function () { + var data = this.get('data.content'); + this.set('rowsDisplayed', data ? data.slice(0) : null); + }.observes('data'), + + beforeLoad: function () { + var dagController = this.get('controllers.dag'), + model = dagController.get('model'); + return model.reload().then(function () { + return dagController.loadAdditional(model); }); - }.observes('amVertexInfo'), + }, - dagRunning: function () { - var progress = this.get('dagProgress'); - return progress != null && progress < 1; - }.property('dagProgress'), + afterLoad: function () { + var data = this.get('data'), + runningVerticesIdx, + isUnsuccessfulDag = App.Helpers.misc.isStatusInUnsuccessful( + this.get('controllers.dag.status') + ); - actions: { - downloadDagJson: function() { - var dagID = this.get('id'); - var downloader = App.Helpers.misc.downloadDAG(this.get('id'), { - batchSize: 500, - onSuccess: function() { - Bootstrap.ModalManager.close('downloadModal'); - }, - onFailure: function() { - $('#modalMessage').html('<i class="fa fa-lg fa-exclamation-circle margin-small-horizontal" ' + - 'style="color:red"></i> Error downloading data'); - } + if(isUnsuccessfulDag) { + data.filterBy('status', 'RUNNING').forEach(function (vertex) { + vertex.set('status', 'KILLED'); }); - this.set('tmpDownloader', downloader); - var modalDialogView = Ember.View.extend({ - template: Em.Handlebars.compile( - '<p id="modalMessage"><i class="fa fa-lg fa-spinner fa-spin margin-small-horizontal" ' + - 'style="color:green"></i>Downloading data for dag %@</p>'.fmt(dagID) - ) - }); - var buttons = [ - Ember.Object.create({title: 'Cancel', dismiss: 'modal', clicked: 'cancelDownload'}) - ]; - Bootstrap.ModalManager.open('downloadModal', 'Download data', - modalDialogView, buttons, this); - }, + } - cancelDownload: function() { - var currentDownloader = this.get('tmpDownloader'); - if (!!currentDownloader) { - currentDownloader.cancel(); - } - this.set('tmpDownloader', undefined); + if (this.get('controllers.dag.amWebServiceVersion') == '1') { + this._loadProgress(data); } + return this._super(); + }, + + // Load progress in parallel for v1 version of the api + _loadProgress: function (vertices) { + var that = this, + runningVerticesIdx = vertices + .filterBy('status', 'RUNNING') + .map(function(item) { + return item.get('id').split('_').splice(-1).pop(); + }); + + if (runningVerticesIdx.length > 0) { + this.store.unloadAll('vertexProgress'); + this.store.findQuery('vertexProgress', { + metadata: { + appId: that.get('applicationId'), + dagIdx: that.get('idx'), + vertexIds: runningVerticesIdx.join(',') + } + }).then(function(vertexProgressInfo) { + App.Helpers.emData.mergeRecords( + that.get('rowsDisplayed'), + vertexProgressInfo, + ['progress'] + ); + }).catch(function(error) { + Em.Logger.debug("failed to fetch vertex progress") + }); + } }, - liveColumns: function () { + defaultColumnConfigs: function() { var vertexIdToNameMap = this.get('vertexIdToNameMap'); return App.Helpers.misc.createColumnDescription([ @@ -118,16 +177,11 @@ App.DagIndexController = Em.ObjectController.extend(App.ModelRefreshMixin, { } }, { - id: 'progress', - headerCellName: 'Progress', - contentPath: 'progress', - templateName: 'components/basic-table/progress-cell' - }, - { id: 'status', headerCellName: 'Status', templateName: 'components/basic-table/status-cell', contentPath: 'status', + observePath: true, getCellContent: function(row) { var status = row.get('status'); return { @@ -138,53 +192,92 @@ App.DagIndexController = Em.ObjectController.extend(App.ModelRefreshMixin, { } }, { + id: 'progress', + headerCellName: 'Progress', + contentPath: 'progress', + observePath: true, + templateName: 'components/basic-table/progress-cell' + }, + { id: 'totalTasks', headerCellName: 'Total Tasks', - contentPath: 'totalTasks', + contentPath: 'numTasks', + observePath: true, }, { id: 'succeededTasks', headerCellName: 'Succeeded Tasks', - contentPath: 'succeededTasks', + contentPath: 'sucessfulTasks', + observePath: true, }, { id: 'runningTasks', headerCellName: 'Running Tasks', contentPath: 'runningTasks', + observePath: true, }, { id: 'pendingTasks', headerCellName: 'Pending Tasks', contentPath: 'pendingTasks', + observePath: true, }, { id: 'failedTasks', headerCellName: 'Failed Task Attempts', contentPath: 'failedTaskAttempts', + observePath: true, }, { id: 'killedTasks', headerCellName: 'Killed Task Attempts', contentPath: 'killedTaskAttempts', + observePath: true, } ]); - }.property('id'), + }.property('vertexIdToNameMap'), - load: function () { - var dag = this.get('controllers.dag.model'), - controller = this.get('controllers.dag'), - t = this; - t.set('loading', true); - dag.reload().then(function () { - return controller.loadAdditional(dag); - }).catch(function(error){ - Em.Logger.error(error); - var err = App.Helpers.misc.formatError(error, defaultErrMsg); - var msg = 'error code: %@, message: %@'.fmt(err.errCode, err.msg); - App.Helpers.ErrorBar.getInstance().show(msg, err.details); - }); + actions: { + downloadDagJson: function() { + var dagID = this.get('id'); + var downloader = App.Helpers.misc.downloadDAG(this.get('id'), { + batchSize: 500, + onSuccess: function() { + Bootstrap.ModalManager.close('downloadModal'); + }, + onFailure: function() { + $('#modalMessage').html('<i class="fa fa-lg fa-exclamation-circle margin-small-horizontal" ' + + 'style="color:red"></i> Error downloading data'); + } + }); + this.set('tmpDownloader', downloader); + var modalDialogView = Ember.View.extend({ + template: Em.Handlebars.compile( + '<p id="modalMessage"><i class="fa fa-lg fa-spinner fa-spin margin-small-horizontal" ' + + 'style="color:green"></i>Downloading data for dag %@</p>'.fmt(dagID) + ) + }); + var buttons = [ + Ember.Object.create({title: 'Cancel', dismiss: 'modal', clicked: 'cancelDownload'}) + ]; + Bootstrap.ModalManager.open('downloadModal', 'Download data', + modalDialogView, buttons, this); + }, + + cancelDownload: function() { + var currentDownloader = this.get('tmpDownloader'); + if (!!currentDownloader) { + currentDownloader.cancel(); + } + this.set('tmpDownloader', undefined); + } }, + dagRunning: function () { + var progress = this.get('dagProgress'); + return progress != null && progress < 1; + }.property('dagProgress'), + taskIconStatus: function() { return App.Helpers.misc.getStatusClassForEntity(this.get('model.status'), this.get('hasFailedTaskAttempts')); @@ -246,22 +339,4 @@ App.DagIndexController = Em.ObjectController.extend(App.ModelRefreshMixin, { } }.property('appContextInfo.appType'), - updateAMInfo: function() { - var status = this.get('amDagInfo.status'); - progress = this.get('amDagInfo.progress'), - infoUpdated = false; - if (!Em.isNone(status)) { - this.set('status', status); - infoUpdated = true; - } - - if (!Em.isNone(progress)) { - this.set('progress', progress); - infoUpdated = true; - } - - if (infoUpdated) { - Em.tryInvoke(this.get('model'), 'didLoad'); - } - }.observes('amDagInfo', 'amDagInfo._amInfoLastUpdatedTime') }); http://git-wip-us.apache.org/repos/asf/tez/blob/d7ba1098/tez-ui/src/main/webapp/app/scripts/controllers/dag_vertices.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/scripts/controllers/dag_vertices.js b/tez-ui/src/main/webapp/app/scripts/controllers/dag_vertices.js index f25c59e..44d29da 100644 --- a/tez-ui/src/main/webapp/app/scripts/controllers/dag_vertices.js +++ b/tez-ui/src/main/webapp/app/scripts/controllers/dag_vertices.js @@ -103,7 +103,11 @@ App.DagVerticesController = App.TablePageController.extend({ vertexIds: runningVerticesIdx.join(',') } }).then(function(vertexProgressInfo) { - that.set('controllers.dag.amVertexInfo', vertexProgressInfo); + App.Helpers.emData.mergeRecords( + that.get('displayedRecords'), + vertexProgressInfo, + ['progress'] + ); }).catch(function(error) { Em.Logger.debug("failed to fetch vertex progress") }); http://git-wip-us.apache.org/repos/asf/tez/blob/d7ba1098/tez-ui/src/main/webapp/app/scripts/controllers/polling-controller.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/scripts/controllers/polling-controller.js b/tez-ui/src/main/webapp/app/scripts/controllers/polling-controller.js index c15db57..507ee49 100644 --- a/tez-ui/src/main/webapp/app/scripts/controllers/polling-controller.js +++ b/tez-ui/src/main/webapp/app/scripts/controllers/polling-controller.js @@ -61,6 +61,7 @@ App.PollingController = Em.ObjectController.extend({ }, applicationComplete: function () { + this.set('pollster.polledRecords', null); if(this.load) { this.load(); } http://git-wip-us.apache.org/repos/asf/tez/blob/d7ba1098/tez-ui/src/main/webapp/app/scripts/controllers/table-page-controller.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/scripts/controllers/table-page-controller.js b/tez-ui/src/main/webapp/app/scripts/controllers/table-page-controller.js index a91dafc..285cc69 100644 --- a/tez-ui/src/main/webapp/app/scripts/controllers/table-page-controller.js +++ b/tez-ui/src/main/webapp/app/scripts/controllers/table-page-controller.js @@ -46,6 +46,14 @@ App.TablePageController = App.PollingController.extend( this.set('pollster.targetRecords', this.get('rowsDisplayed')); }.observes('rowsDisplayed', 'pollster'), + parentStatusObserver: function () { + var parentStatus = this.get('status'); + if(parentStatus && parentStatus != 'RUNNING') { + this.get('pollster').stop(); + this.loadData(true); + } + }.observes('status'), + applicationComplete: function () { this.loadData(true); }, http://git-wip-us.apache.org/repos/asf/tez/blob/d7ba1098/tez-ui/src/main/webapp/app/scripts/helpers/em-data.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/scripts/helpers/em-data.js b/tez-ui/src/main/webapp/app/scripts/helpers/em-data.js new file mode 100644 index 0000000..5c4dadc --- /dev/null +++ b/tez-ui/src/main/webapp/app/scripts/helpers/em-data.js @@ -0,0 +1,59 @@ +/** + * 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.Helpers.emData = { + /** + * Merge data from an array of records to another + * @param target {Array} Target record array + * @param source {Array} Source record array + * @param mergeProps {Array} Array of strings of property names to be merged + * @return true if merge was success, else false + */ + mergeRecords: function (target, source, mergeProps) { + + if(source && target && mergeProps) { + target.forEach(function (row) { + var info = source.findBy('id', row.get('id')), + merge = !!info; + + if(merge && row.get('progress') && info.get('progress')) { + if(row.get('progress') >= info.get('progress')) { + merge = false; + } + } + + if(merge) { + row.setProperties(info.getProperties.apply(info, mergeProps)); + + if(info.get('counters')) { + row.set('counterGroups', + App.Helpers.misc.mergeCounterInfo( + row.get('counterGroups'), + info.get('counters') + ).slice(0) + ); + } + + row.didLoad();// To update the record time stamp + } + }); + return true; + } + return false; + } +}; http://git-wip-us.apache.org/repos/asf/tez/blob/d7ba1098/tez-ui/src/main/webapp/app/scripts/helpers/entity-array-pollster.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/scripts/helpers/entity-array-pollster.js b/tez-ui/src/main/webapp/app/scripts/helpers/entity-array-pollster.js index 31181be..4cf97cf 100644 --- a/tez-ui/src/main/webapp/app/scripts/helpers/entity-array-pollster.js +++ b/tez-ui/src/main/webapp/app/scripts/helpers/entity-array-pollster.js @@ -37,20 +37,14 @@ App.Helpers.EntityArrayPollster = App.Helpers.Pollster.extend({ start: function(interval) { if(!this.get('isRunning')) { - if (!!interval && interval > 1000) { - this.set('_interval', interval) - } - + this._super(true, interval); this.set('isRunning', true); - - this.onPoll(); - this.set('timer', this.schedule(this.onPoll, true)); } }, stop: function() { if(this.get('isRunning')) { - Ember.run.cancel(this.get('timer')); + this._super(); this.set('isRunning', false); } }, @@ -68,7 +62,9 @@ App.Helpers.EntityArrayPollster = App.Helpers.Pollster.extend({ }, _optionObserver: function () { - Em.run.later(this, this.onPoll, 10); + if(this.get('options')) { + Em.run.later(this, this.onPoll, 10); + } }.observes('options'), _callIfRunning: function (that, funName) { @@ -94,37 +90,10 @@ App.Helpers.EntityArrayPollster = App.Helpers.Pollster.extend({ }, mergeToTarget: function () { - var polledRecords = this.get('polledRecords'), - targetRecords = this.get('targetRecords'), - mergeProperties = this.get('mergeProperties') || []; - - if(polledRecords && targetRecords) { - targetRecords.forEach(function (row) { - var info = polledRecords.findBy('id', row.get('id')), - merge = !!info; - - row.didLoad(); - - if(merge && row.get('progress') && info.get('progress')) { - if(row.get('progress') >= info.get('progress')) { - merge = false; - } - } - - if(merge) { - row.setProperties(info.getProperties.apply(info, mergeProperties)); - - if(info.get('counters')) { - row.set('counterGroups', - App.Helpers.misc.mergeCounterInfo( - row.get('counterGroups'), - info.get('counters') - ).slice(0) - ); - } - - } - }); - } + App.Helpers.emData.mergeRecords( + this.get('targetRecords'), + this.get('polledRecords'), + this.get('mergeProperties') || [] + ); }.observes('targetRecords').on('init') }); http://git-wip-us.apache.org/repos/asf/tez/blob/d7ba1098/tez-ui/src/main/webapp/app/scripts/models/TimelineRestAdapter.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/scripts/models/TimelineRestAdapter.js b/tez-ui/src/main/webapp/app/scripts/models/TimelineRestAdapter.js index e7fe876..d54c8b9 100644 --- a/tez-ui/src/main/webapp/app/scripts/models/TimelineRestAdapter.js +++ b/tez-ui/src/main/webapp/app/scripts/models/TimelineRestAdapter.js @@ -94,6 +94,13 @@ var timelineJsonToDagMap = { name: 'primaryfilters.dagName.0', user: 'primaryfilters.user.0', status: 'otherinfo.status', + + progress: { + custom: function(source) { + return Em.get(source, 'otherinfo.status') == 'SUCCEEDED' ? 1 : null; + } + }, + containerLogs: { custom: function(source) { @@ -254,6 +261,16 @@ var timelineJsonToVertexMap = { return Em.get(source, 'otherinfo.status') == 'SUCCEEDED' ? 1 : null; } }, + runningTasks: { + custom: function(source) { + return Em.get(source, 'otherinfo.status') == 'SUCCEEDED' ? 0 : null; + } + }, + pendingTasks: { + custom: function(source) { + return Em.get(source, 'otherinfo.status') == 'SUCCEEDED' ? 0 : null; + } + }, status: 'otherinfo.status', hasFailedTaskAttempts: { @@ -266,6 +283,9 @@ var timelineJsonToVertexMap = { }, diagnostics: 'otherinfo.diagnostics', + failedTaskAttempts: 'otherinfo.numFailedTaskAttempts', + killedTaskAttempts: 'otherinfo.numKilledTaskAttempts', + failedTasks: 'otherinfo.numFailedTasks', sucessfulTasks: 'otherinfo.numSucceededTasks', numTasks: 'otherinfo.numTasks', @@ -570,16 +590,30 @@ App.VertexProgressSerializer = App.DagProgressSerializer = DS.RESTSerializer.ext App.DagInfoSerializer = DS.RESTSerializer.extend({ normalizePayload: function(rawPayload) { return { - dagInfo : rawPayload.dag + dagInfo : [rawPayload.dag] } } }); App.VertexInfoSerializer = DS.RESTSerializer.extend({ + map: { + id: 'id', + progress: 'progress', + status: 'status', + numTasks: 'totalTasks', + runningTasks: 'runningTasks', + sucessfulTasks: 'succeededTasks', + failedTaskAttempts: 'failedTaskAttempts', + killedTaskAttempts: 'killedTaskAttempts', + counters: 'counters' + }, normalizePayload: function(rawPayload) { return { vertexInfo : rawPayload.vertices } + }, + normalize: function(type, hash, prop) { + return Em.JsonMapper.map(hash, this.get('map')); } }); @@ -620,4 +654,4 @@ App.ClusterAppSerializer = App.TimelineSerializer.extend({ normalize: function(type, hash, prop) { return Em.JsonMapper.map(hash, this.get('map')); } -}); +}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tez/blob/d7ba1098/tez-ui/src/main/webapp/app/scripts/models/dag.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/scripts/models/dag.js b/tez-ui/src/main/webapp/app/scripts/models/dag.js index a21b983..0457157 100644 --- a/tez-ui/src/main/webapp/app/scripts/models/dag.js +++ b/tez-ui/src/main/webapp/app/scripts/models/dag.js @@ -50,6 +50,8 @@ App.Dag = App.AbstractEntity.extend({ tezApp: DS.belongsTo('tezApp'), appDetail: DS.belongsTo('appDetail'), + progress: DS.attr('number'), + // status status: DS.attr('string'), hasFailedTaskAttempts: DS.attr('boolean'), @@ -215,9 +217,14 @@ App.Vertex = App.AbstractEntity.extend({ failedTasks: DS.attr('number'), sucessfulTasks: DS.attr('number'), + runningTasks: DS.attr('number'), + pendingTasks: DS.attr('number'), numTasks: DS.attr('number'), killedTasks: DS.attr('number'), + failedTaskAttempts: DS.attr('number'), + killedTaskAttempts: DS.attr('number'), + diagnostics: DS.attr('string'), counterGroups: DS.attr('array'), @@ -434,14 +441,15 @@ App.VertexInfo = DS.Model.extend({ progress: DS.attr('number'), status: DS.attr('string'), - totalTasks: DS.attr('number'), + numTasks: DS.attr('number'), runningTasks: DS.attr('number'), - succeededTasks: DS.attr('number'), + sucessfulTasks: DS.attr('number'), failedTaskAttempts: DS.attr('number'), killedTaskAttempts: DS.attr('number'), + pendingTasks: function() { - return this.get('totalTasks') - this.get('runningTasks') - this.get('succeededTasks'); - }.property('totalTasks', 'runningTasks', 'succeededTasks'), + return this.get('numTasks') - this.get('runningTasks') - this.get('sucessfulTasks'); + }.property('numTasks', 'runningTasks', 'sucessfulTasks'), counters: DS.attr('object') }); http://git-wip-us.apache.org/repos/asf/tez/blob/d7ba1098/tez-ui/src/main/webapp/app/scripts/router.js ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/scripts/router.js b/tez-ui/src/main/webapp/app/scripts/router.js index 1f7f646..ab551a1 100644 --- a/tez-ui/src/main/webapp/app/scripts/router.js +++ b/tez-ui/src/main/webapp/app/scripts/router.js @@ -155,7 +155,6 @@ App.DagRoute = Em.Route.extend({ }, setupController: setupControllerFactory('Dag: %@ (%@)', 'name', 'id'), resetController: function() { - this.controller.dostopAMInfoUpdateService(); if(this.controller.reset) { this.controller.reset(); } @@ -314,6 +313,15 @@ App.TezAppConfigsRoute = Em.Route.extend({ }); /* --- Shared routes --- */ +App.DagIndexRoute = Em.Route.extend({ + resetController: function () { + if(this.controller.reset) { + this.controller.reset(); + } + }, + setupController: setupControllerFactory() +}); + App.DagTasksRoute = App.DagVerticesRoute = App.DagTaskAttemptsRoute = http://git-wip-us.apache.org/repos/asf/tez/blob/d7ba1098/tez-ui/src/main/webapp/app/templates/dag/index.hbs ---------------------------------------------------------------------- diff --git a/tez-ui/src/main/webapp/app/templates/dag/index.hbs b/tez-ui/src/main/webapp/app/templates/dag/index.hbs index f7ec99f..c287584 100644 --- a/tez-ui/src/main/webapp/app/templates/dag/index.hbs +++ b/tez-ui/src/main/webapp/app/templates/dag/index.hbs @@ -95,22 +95,24 @@ </div> </div> -{{#if liveData}} +{{#if data.content}} <br/> <h4>DAG Progress - ( Vertices {{completedVertices}}/{{liveData.length}} ) - {{#if totalTasks}} - ( Tasks {{succeededTasks}}/{{totalTasks}} ) + {{#if progressDetails}} + ( Vertices {{progressDetails.completedVertices}}/{{data.length}} ) + {{#if progressDetails.totalTasks}} + ( Tasks {{progressDetails.succeededTasks}}/{{progressDetails.totalTasks}} ) + {{/if}} + : + {{progressStr}} {{/if}} - : - {{progressStr}} </h4> {{bs-progress-animated progressDecimal=progress}} {{basic-table-component - columns=liveColumns - rows=liveData - rowCount=liveData.length + columns=defaultColumnConfigs + rows=data.content + rowCount=data.content.length enableStatus=false enableSort=true
