[EAGLE-574] UI refactor for support 0.5 api Eagle 0.5 updates the rest api interface and UI need also update for support the api.
* Remove feature * Support app provider * Create JPM UI application * Redesign site logic * Widget support Author: jiljiang <jilji...@ebay.com> Closes #460 from zombieJ/ui. Project: http://git-wip-us.apache.org/repos/asf/incubator-eagle/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-eagle/commit/afb89794 Tree: http://git-wip-us.apache.org/repos/asf/incubator-eagle/tree/afb89794 Diff: http://git-wip-us.apache.org/repos/asf/incubator-eagle/diff/afb89794 Branch: refs/heads/master Commit: afb897940021caec79982e29f7194f2b6c733b49 Parents: 1fa490e Author: zombiej <ji...@apache.org> Authored: Wed Sep 28 13:38:17 2016 +0800 Committer: jiljiang <jilji...@ebay.com> Committed: Wed Sep 28 13:38:17 2016 +0800 ---------------------------------------------------------------------- .../webapp/app/apps/jpm/ctrl/compareCtrl.js | 380 ++++++ .../main/webapp/app/apps/jpm/ctrl/detailCtrl.js | 192 +++ .../webapp/app/apps/jpm/ctrl/jobTaskCtrl.js | 551 ++++++++ .../main/webapp/app/apps/jpm/ctrl/listCtrl.js | 239 ++++ .../webapp/app/apps/jpm/ctrl/overviewCtrl.js | 140 ++ .../webapp/app/apps/jpm/ctrl/statisticCtrl.js | 386 ++++++ .../src/main/webapp/app/apps/jpm/index.js | 489 +++++++ .../app/apps/jpm/partials/job/compare.html | 274 ++++ .../app/apps/jpm/partials/job/detail.html | 256 ++++ .../webapp/app/apps/jpm/partials/job/list.html | 131 ++ .../app/apps/jpm/partials/job/overview.html | 347 +++++ .../app/apps/jpm/partials/job/statistic.html | 120 ++ .../webapp/app/apps/jpm/partials/job/task.html | 149 +++ .../main/webapp/app/apps/jpm/style/index.css | 76 ++ .../webapp/app/apps/jpm/widget/jobStatistic.js | 108 ++ eagle-server/.gitignore | 7 + eagle-server/pom.xml | 23 +- eagle-server/src/main/webapp/app/.editorconfig | 27 + eagle-server/src/main/webapp/app/Gruntfile.js | 190 +++ eagle-server/src/main/webapp/app/README.md | 4 + eagle-server/src/main/webapp/app/build/index.js | 144 +++ eagle-server/src/main/webapp/app/dev/index.html | 250 ++++ .../webapp/app/dev/partials/alert/list.html | 21 + .../webapp/app/dev/partials/alert/main.html | 29 + .../app/dev/partials/alert/policyEdit.back.html | 108 ++ .../app/dev/partials/alert/policyEdit.html | 29 + .../app/dev/partials/alert/policyList.html | 63 + .../src/main/webapp/app/dev/partials/home.html | 60 + .../partials/integration/applicationList.html | 80 ++ .../app/dev/partials/integration/main.html | 29 + .../app/dev/partials/integration/site.html | 95 ++ .../app/dev/partials/integration/siteList.html | 60 + .../dev/partials/integration/streamList.html | 52 + .../src/main/webapp/app/dev/partials/setup.html | 46 + .../webapp/app/dev/public/css/animation.css | 47 + .../src/main/webapp/app/dev/public/css/main.css | 317 +++++ .../webapp/app/dev/public/css/sortTable.css | 61 + .../webapp/app/dev/public/images/favicon.png | Bin 0 -> 4209 bytes .../app/dev/public/images/favicon_white.png | Bin 0 -> 1621 bytes .../src/main/webapp/app/dev/public/js/app.js | 311 +++++ .../src/main/webapp/app/dev/public/js/common.js | 387 ++++++ .../app/dev/public/js/components/chart.js | 188 +++ .../webapp/app/dev/public/js/components/main.js | 24 + .../app/dev/public/js/components/sortTable.js | 231 ++++ .../app/dev/public/js/components/widget.js | 52 + .../webapp/app/dev/public/js/ctrls/alertCtrl.js | 119 ++ .../app/dev/public/js/ctrls/integrationCtrl.js | 226 ++++ .../main/webapp/app/dev/public/js/ctrls/main.js | 27 + .../webapp/app/dev/public/js/ctrls/mainCtrl.js | 62 + .../webapp/app/dev/public/js/ctrls/siteCtrl.js | 33 + .../src/main/webapp/app/dev/public/js/index.js | 326 +++++ .../dev/public/js/services/applicationSrv.js | 71 + .../app/dev/public/js/services/entitySrv.js | 135 ++ .../webapp/app/dev/public/js/services/main.js | 23 + .../app/dev/public/js/services/pageSrv.js | 137 ++ .../app/dev/public/js/services/siteSrv.js | 111 ++ .../app/dev/public/js/services/timeSrv.js | 277 ++++ .../webapp/app/dev/public/js/services/uiSrv.js | 276 ++++ .../app/dev/public/js/services/widgetSrv.js | 79 ++ .../app/dev/public/js/services/wrapStateSrv.js | 119 ++ .../app/dev/public/js/worker/sortTableFunc.js | 93 ++ .../app/dev/public/js/worker/sortTableWorker.js | 32 + eagle-server/src/main/webapp/app/index.html | 11 +- eagle-server/src/main/webapp/app/package.json | 47 + eagle-server/src/main/webapp/package.json | 0 eagle-server/ui-build.sh | 40 + eagle-webservice/src/main/webapp/Gruntfile.js | 175 --- eagle-webservice/src/main/webapp/README.md | 4 - .../src/main/webapp/_app/index.html | 281 ++++ .../_app/partials/config/application.html | 124 ++ .../webapp/_app/partials/config/feature.html | 85 ++ .../main/webapp/_app/partials/config/site.html | 115 ++ .../src/main/webapp/_app/partials/landing.html | 30 + .../src/main/webapp/_app/partials/login.html | 54 + .../main/webapp/_app/public/css/animation.css | 46 + .../src/main/webapp/_app/public/css/main.css | 805 ++++++++++++ .../public/feature/classification/controller.js | 358 +++++ .../classification/page/sensitivity.html | 40 + .../classification/page/sensitivity/folder.html | 110 ++ .../classification/page/sensitivity/job.html | 92 ++ .../classification/page/sensitivity/table.html | 150 +++ .../_app/public/feature/common/controller.js | 1224 ++++++++++++++++++ .../public/feature/common/page/alertDetail.html | 67 + .../public/feature/common/page/alertList.html | 67 + .../feature/common/page/policyDetail.html | 173 +++ .../public/feature/common/page/policyEdit.html | 346 +++++ .../public/feature/common/page/policyList.html | 84 ++ .../_app/public/feature/metadata/controller.js | 66 + .../feature/metadata/page/streamList.html | 84 ++ .../_app/public/feature/metrics/controller.js | 571 ++++++++ .../public/feature/metrics/page/dashboard.html | 250 ++++ .../_app/public/feature/topology/controller.js | 257 ++++ .../feature/topology/page/management.html | 52 + .../feature/topology/page/monitoring.html | 151 +++ .../public/feature/userProfile/controller.js | 268 ++++ .../public/feature/userProfile/page/detail.html | 87 ++ .../public/feature/userProfile/page/list.html | 138 ++ .../main/webapp/_app/public/images/favicon.png | Bin 0 -> 4209 bytes .../webapp/_app/public/images/favicon_white.png | Bin 0 -> 1621 bytes .../main/webapp/_app/public/js/app.config.js | 126 ++ .../src/main/webapp/_app/public/js/app.js | 499 +++++++ .../src/main/webapp/_app/public/js/app.time.js | 70 + .../src/main/webapp/_app/public/js/app.ui.js | 76 ++ .../src/main/webapp/_app/public/js/common.js | 304 +++++ .../_app/public/js/components/charts/line3d.js | 348 +++++ .../webapp/_app/public/js/components/file.js | 50 + .../webapp/_app/public/js/components/main.js | 19 + .../webapp/_app/public/js/components/nvd3.js | 418 ++++++ .../_app/public/js/components/sortTable.js | 113 ++ .../_app/public/js/components/sortable.js | 166 +++ .../webapp/_app/public/js/components/tabs.js | 247 ++++ .../_app/public/js/ctrl/authController.js | 91 ++ .../public/js/ctrl/configurationController.js | 377 ++++++ .../src/main/webapp/_app/public/js/ctrl/main.js | 42 + .../webapp/_app/public/js/srv/applicationSrv.js | 170 +++ .../_app/public/js/srv/authorizationSrv.js | 143 ++ .../webapp/_app/public/js/srv/entitiesSrv.js | 301 +++++ .../src/main/webapp/_app/public/js/srv/main.js | 72 ++ .../main/webapp/_app/public/js/srv/pageSrv.js | 131 ++ .../main/webapp/_app/public/js/srv/siteSrv.js | 193 +++ .../src/main/webapp/_app/public/js/srv/uiSrv.js | 247 ++++ .../webapp/_app/public/js/srv/wrapStateSrv.js | 109 ++ eagle-webservice/src/main/webapp/app/index.html | 281 ---- .../webapp/app/partials/config/application.html | 124 -- .../webapp/app/partials/config/feature.html | 85 -- .../main/webapp/app/partials/config/site.html | 115 -- .../src/main/webapp/app/partials/landing.html | 30 - .../src/main/webapp/app/partials/login.html | 54 - .../main/webapp/app/public/css/animation.css | 46 - .../src/main/webapp/app/public/css/main.css | 805 ------------ .../public/feature/classification/controller.js | 358 ----- .../classification/page/sensitivity.html | 40 - .../classification/page/sensitivity/folder.html | 110 -- .../classification/page/sensitivity/job.html | 92 -- .../classification/page/sensitivity/table.html | 150 --- .../app/public/feature/common/controller.js | 1224 ------------------ .../public/feature/common/page/alertDetail.html | 67 - .../public/feature/common/page/alertList.html | 67 - .../feature/common/page/policyDetail.html | 173 --- .../public/feature/common/page/policyEdit.html | 346 ----- .../public/feature/common/page/policyList.html | 84 -- .../app/public/feature/metadata/controller.js | 66 - .../feature/metadata/page/streamList.html | 84 -- .../app/public/feature/metrics/controller.js | 571 -------- .../public/feature/metrics/page/dashboard.html | 250 ---- .../app/public/feature/topology/controller.js | 257 ---- .../feature/topology/page/management.html | 52 - .../feature/topology/page/monitoring.html | 151 --- .../public/feature/userProfile/controller.js | 268 ---- .../public/feature/userProfile/page/detail.html | 87 -- .../public/feature/userProfile/page/list.html | 138 -- .../main/webapp/app/public/images/favicon.png | Bin 4209 -> 0 bytes .../webapp/app/public/images/favicon_white.png | Bin 1621 -> 0 bytes .../src/main/webapp/app/public/js/app.config.js | 126 -- .../src/main/webapp/app/public/js/app.js | 499 ------- .../src/main/webapp/app/public/js/app.time.js | 70 - .../src/main/webapp/app/public/js/app.ui.js | 76 -- .../src/main/webapp/app/public/js/common.js | 304 ----- .../app/public/js/components/charts/line3d.js | 348 ----- .../webapp/app/public/js/components/file.js | 50 - .../webapp/app/public/js/components/main.js | 19 - .../webapp/app/public/js/components/nvd3.js | 418 ------ .../app/public/js/components/sortTable.js | 113 -- .../webapp/app/public/js/components/sortable.js | 166 --- .../webapp/app/public/js/components/tabs.js | 247 ---- .../webapp/app/public/js/ctrl/authController.js | 91 -- .../public/js/ctrl/configurationController.js | 377 ------ .../src/main/webapp/app/public/js/ctrl/main.js | 42 - .../webapp/app/public/js/srv/applicationSrv.js | 170 --- .../app/public/js/srv/authorizationSrv.js | 143 -- .../webapp/app/public/js/srv/entitiesSrv.js | 301 ----- .../src/main/webapp/app/public/js/srv/main.js | 72 -- .../main/webapp/app/public/js/srv/pageSrv.js | 131 -- .../main/webapp/app/public/js/srv/siteSrv.js | 193 --- .../src/main/webapp/app/public/js/srv/uiSrv.js | 240 ---- .../webapp/app/public/js/srv/wrapStateSrv.js | 109 -- eagle-webservice/src/main/webapp/grunt.json | 42 - eagle-webservice/src/main/webapp/index.html | 28 - eagle-webservice/src/main/webapp/package.json | 48 - eagle-webservice/ui-build.sh | 23 +- 180 files changed, 19503 insertions(+), 10801 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/compareCtrl.js ---------------------------------------------------------------------- diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/compareCtrl.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/compareCtrl.js new file mode 100644 index 0000000..121e80e --- /dev/null +++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/compareCtrl.js @@ -0,0 +1,380 @@ +/* + * 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. + */ + +(function () { + /** + * `register` without params will load the module which using require + */ + register(function (jpmApp) { + /** + * @param {{}} $scope + * @param {{}} $scope.trendChart + */ + jpmApp.controller("compareCtrl", function ($q, $wrapState, $scope, PageConfig, Time, Entity, JPM) { + $scope.site = $wrapState.param.siteId; + $scope.jobDefId = $wrapState.param.jobDefId; + + $scope.jobTrendCategory = []; + + $scope.fromJob = null; + $scope.toJob = null; + + PageConfig.title = "Job History"; + PageConfig.subTitle = $scope.jobDefId; + + $scope.getStateClass = JPM.getStateClass; + + var browserAction = true; + + // ================================================================== + // = Fetch Job List = + // ================================================================== + var jobList = $scope.jobList = JPM.findMRJobs($scope.site, $scope.jobDefId); + jobList._promise.then(function () { + if(jobList.length <= 1) { + $.dialog({ + title: "No statistic info", + content: "Current job do not have enough statistic info. Please check 'jobDefId' if your job have run many times." + }); + } + + function findJob(jobId) { + return common.array.find(jobId, jobList, "tags.jobId"); + } + + var TASK_BUCKET_TIMES = [0, 30, 60, 120, 300, 600, 1800, 3600, 7200, 18000]; + function taskDistribution(jobId) { + return JPM.taskDistribution($scope.site, jobId, TASK_BUCKET_TIMES.join(",")).then( + /** + * @param {{}} res + * @param {{}} res.data + * @param {[]} res.data.finishedTaskCount + * @param {[]} res.data.runningTaskCount + */ + function (res) { + var result = {}; + var data = res.data; + var finishedTaskCount = data.finishedTaskCount; + var runningTaskCount = data.runningTaskCount; + + /** + * @param {number} item.taskCount + */ + var finishedTaskData = $.map(finishedTaskCount, function (item) { + return item.taskCount; + }); + /** + * @param {number} item.taskCount + */ + var runningTaskData = $.map(runningTaskCount, function (item) { + return item.taskCount; + }); + + result.taskSeries = [{ + name: "Finished Tasks", + type: "bar", + stack: jobId, + data: finishedTaskData + }, { + name: "Running Tasks", + type: "bar", + stack: jobId, + data: runningTaskData + }]; + + result.finishedTaskCount = finishedTaskCount; + result.runningTaskCount = runningTaskCount; + + return result; + }); + } + + var taskDistributionCategory = $.map(TASK_BUCKET_TIMES, function (current, i) { + var curDes = Time.diffStr(TASK_BUCKET_TIMES[i] * 1000); + var nextDes = Time.diffStr(TASK_BUCKET_TIMES[i + 1] * 1000); + + if(!curDes && nextDes) { + return "<" + nextDes; + } else if(nextDes) { + return curDes + "\n~\n" + nextDes; + } + return ">" + curDes; + }); + + // ========================= Job Trend ========================== + function refreshParam() { + browserAction = false; + $wrapState.go(".", { + from: common.getValueByPath($scope.fromJob, "tags.jobId"), + to: common.getValueByPath($scope.toJob, "tags.jobId") + }); + setTimeout(function () { + browserAction = true; + }, 0); + } + + function getMarkPoint(name, x, y, color) { + return { + name: name, + silent: true, + coord: [x, y], + symbolSize: 20, + label: { + normal: { show: false }, + emphasis: { show: false } + }, itemStyle: { + normal: { color: color } + } + }; + } + + function refreshTrendMarkPoint() { + var fromX = null, fromY = null; + var toX = null, toY = null; + $.each(jobList, function (index, job) { + if($scope.fromJob && $scope.fromJob.tags.jobId === job.tags.jobId) { + fromX = index; + fromY = job.durationTime; + } + if($scope.toJob && $scope.toJob.tags.jobId === job.tags.jobId) { + toX = index; + toY = job.durationTime; + } + }); + + markPoint.data = []; + if(!common.isEmpty(fromX)) { + markPoint.data.push(getMarkPoint("<From Job>", fromX, fromY, "#00c0ef")); + } + if(!common.isEmpty(toX)) { + markPoint.data.push(getMarkPoint("<To Job>", toX, toY, "#3c8dbc")); + } + + $scope.trendChart.refresh(); + } + + var jobListTrend = $.map(jobList, function (job) { + var time = Time.format(job.startTime); + $scope.jobTrendCategory.push(time); + return job.durationTime; + }); + + $scope.jobTrendOption = { + yAxis: [{ + axisLabel: { + formatter: function (value) { + return Time.diffStr(value); + } + } + }], + tooltip: { + formatter: function (points) { + var point = points[0]; + return point.name + "<br/>" + + '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:' + point.color + '"></span> ' + + point.seriesName + ": " + Time.diffStr(point.value); + } + } + }; + + var markPoint = { + data: [], + silent: true + }; + + $scope.jobTrendSeries = [{ + name: "Job Duration", + type: "line", + data: jobListTrend, + symbolSize: 10, + showSymbol: false, + markPoint: markPoint + }]; + + $scope.compareJobSelect = function (e, job) { + var index = e.dataIndex; + job = job || jobList[index]; + if(!job) return; + + var event = e.event ? e.event.event : e; + + if(event.ctrlKey) { + $scope.fromJob = job; + } else if(event.shiftKey) { + $scope.toJob = job; + } else { + if ($scope.fromJob) { + $scope.toJob = $scope.fromJob; + } + $scope.fromJob = job; + } + + refreshTrendMarkPoint(); + refreshParam(); + refreshComparisonDashboard(); + }; + + $scope.exchangeJobs = function () { + var tmpJob = $scope.fromJob; + $scope.fromJob = $scope.toJob; + $scope.toJob = tmpJob; + refreshTrendMarkPoint(); + refreshParam(); + refreshComparisonDashboard(); + }; + + // ======================= Job Comparison ======================= + $scope.taskOption = { + xAxis: {axisTick: { show: true }} + }; + + function getComparedValue(path) { + var val1 = common.getValueByPath($scope.fromJob, path); + var val2 = common.getValueByPath($scope.toJob, path); + return common.number.compare(val1, val2); + } + + $scope.jobCompareClass = function (path) { + var diff = getComparedValue(path); + if(typeof diff !== "number" || Math.abs(diff) < 0.01) return "hide"; + if(diff < 0.05) return "label label-success"; + if(diff < 0.15) return "label label-warning"; + return "label label-danger"; + }; + + $scope.jobCompareValue = function (path) { + var diff = getComparedValue(path); + return (diff >= 0 ? "+" : "") + Math.floor(diff * 100) + "%"; + }; + + /** + * get 2 interval data list category. (minutes level) + * @param {[]} list1 + * @param {[]} list2 + * @return {Array} + */ + function intervalCategories(list1, list2) { + var len = Math.max(list1.length, list2.length); + var category = []; + for(var i = 0 ; i < len ; i += 1) { + category.push((i + 1) + "min"); + } + return category; + } + + function refreshComparisonDashboard() { + if(!$scope.fromJob || !$scope.toJob) return; + + var fromJobCond = { + jobId: $scope.fromJob.tags.jobId, + site: $scope.site + }; + var toJobCond = { + jobId: $scope.toJob.tags.jobId, + site: $scope.site + }; + + var from_startTime = Time($scope.fromJob.startTime).subtract(1, "hour"); + var from_endTime = ($scope.fromJob.endTime ? Time($scope.fromJob.endTime) : Time()).add(1, "hour"); + var to_startTime = Time($scope.toJob.startTime).subtract(1, "hour"); + var to_endTime = ($scope.toJob.endTime ? Time($scope.toJob.endTime) : Time()).add(1, "hour"); + + $scope.fromJob._cache = $scope.fromJob._cache || {}; + $scope.toJob._cache = $scope.toJob._cache || {}; + + /** + * Generate metric level chart series + * @param {string} metric + * @param {string} seriesName + */ + function metricComparison(metric, seriesName) { + var from_metric = $scope.fromJob._cache[metric] = + $scope.fromJob._cache[metric] || JPM.metrics(fromJobCond, metric, from_startTime, from_endTime); + var to_metric = $scope.toJob._cache[metric] = + $scope.toJob._cache[metric] || JPM.metrics(toJobCond, metric, to_startTime, to_endTime); + + var holder = {}; + + $q.all([from_metric._promise, to_metric._promise]).then(function () { + from_metric = JPM.metricsToInterval(from_metric, 1000 * 60); + to_metric = JPM.metricsToInterval(to_metric, 1000 * 60); + + var series_from = JPM.metricsToSeries("from job " + seriesName, from_metric, true); + var series_to = JPM.metricsToSeries("to job " + seriesName, to_metric, true); + + holder.categories = intervalCategories(from_metric, to_metric); + holder.series = [series_from, series_to]; + }); + + return holder; + } + + // Dashboard1: Containers metrics + $scope.comparisonChart_Container = metricComparison("hadoop.job.runningcontainers", "running containers"); + + // Dashboard 2: Allocated + $scope.comparisonChart_allocatedMB = metricComparison("hadoop.job.allocatedmb", "allocated MB"); + + // Dashboard 3: vCores + $scope.comparisonChart_vCores = metricComparison("hadoop.job.allocatedvcores", "vCores"); + + // Dashboard 4: Task distribution + var from_distributionPromise = $scope.fromJob._cache.distributionPromise = + $scope.fromJob._cache.distributionPromise || taskDistribution($scope.fromJob.tags.jobId); + var to_distributionPromise = $scope.toJob._cache.distributionPromise = + $scope.toJob._cache.distributionPromise || taskDistribution($scope.toJob.tags.jobId); + var comparisonChart_taskDistribution = $scope.comparisonChart_taskDistribution = { + categories: taskDistributionCategory + }; + + $q.all([from_distributionPromise, to_distributionPromise]).then(function (args) { + var from_data = args[0]; + var to_data = args[1]; + + var from_taskSeries = $.map(from_data.taskSeries, function (series) { + return $.extend({}, series, {name: "From " + series.name}); + }); + var to_taskSeries = $.map(to_data.taskSeries, function (series) { + return $.extend({}, series, {name: "To " + series.name}); + }); + + comparisonChart_taskDistribution.series = from_taskSeries.concat(to_taskSeries); + }); + } + + // ======================== Job Refresh ========================= + function jobRefresh() { + $scope.fromJob = findJob($wrapState.param.from); + $scope.toJob = findJob($wrapState.param.to); + $(window).resize(); + refreshTrendMarkPoint(); + refreshComparisonDashboard(); + } + + // ======================= Initialization ======================= + jobRefresh(); + + $scope.$on('$locationChangeSuccess', function() { + if(browserAction) { + jobRefresh(); + } + }); + }); + }); + }); +})(); http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/detailCtrl.js ---------------------------------------------------------------------- diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/detailCtrl.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/detailCtrl.js new file mode 100644 index 0000000..be7631f --- /dev/null +++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/detailCtrl.js @@ -0,0 +1,192 @@ +/* + * 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. + */ + +(function () { + /** + * `register` without params will load the module which using require + */ + register(function (jpmApp) { + jpmApp.controller("detailCtrl", function ($q, $wrapState, $element, $scope, PageConfig, Time, Entity, JPM) { + var TASK_BUCKET_TIMES = [0, 30, 60, 120, 300, 600, 1800, 3600, 7200, 18000]; + var i; + var startTime, endTime; + var metric_allocatedMB, metric_allocatedVCores, metric_runningContainers; + var nodeTaskCountList; + + $scope.site = $wrapState.param.siteId; + $scope.jobId = $wrapState.param.jobId; + + PageConfig.title = "Job"; + PageConfig.subTitle = $scope.jobId; + + $scope.getStateClass = JPM.getStateClass; + $scope.compareChart = null; + + var jobCond = { + jobId: $scope.jobId, + site: $scope.site + }; + + function taskDistribution(jobId) { + return JPM.taskDistribution($scope.site, jobId, TASK_BUCKET_TIMES.join(",")).then( + /** + * @param {{}} res + * @param {{}} res.data + * @param {[]} res.data.finishedTaskCount + * @param {[]} res.data.runningTaskCount + */ + function (res) { + var result = {}; + var data = res.data; + var finishedTaskCount = data.finishedTaskCount; + var runningTaskCount = data.runningTaskCount; + + /** + * @param {number} item.taskCount + */ + var finishedTaskData = $.map(finishedTaskCount, function (item) { + return item.taskCount; + }); + /** + * @param {number} item.taskCount + */ + var runningTaskData = $.map(runningTaskCount, function (item) { + return item.taskCount; + }); + + result.taskSeries = [{ + name: "Finished Tasks", + type: "bar", + stack: jobId, + data: finishedTaskData + }, { + name: "Running Tasks", + type: "bar", + stack: jobId, + data: runningTaskData + }]; + + result.finishedTaskCount = finishedTaskCount; + result.runningTaskCount = runningTaskCount; + + return result; + }); + } + + $scope.taskCategory = $.map(TASK_BUCKET_TIMES, function (current, i) { + var curDes = Time.diffStr(TASK_BUCKET_TIMES[i] * 1000); + var nextDes = Time.diffStr(TASK_BUCKET_TIMES[i + 1] * 1000); + + if(!curDes && nextDes) { + return "<" + nextDes; + } else if(nextDes) { + return curDes + "\n~\n" + nextDes; + } + return ">" + curDes; + }); + + // ========================================================================= + // = Fetch Job = + // ========================================================================= + JPM.findMRJobs($scope.site, undefined, $scope.jobId)._promise.then(function (list) { + $scope.job = list[list.length - 1]; + console.log("[JPM] Fetch job:", $scope.job); + + if(!$scope.job) { + $.dialog({ + title: "OPS!", + content: "Job not found!" + }, function () { + $wrapState.go("jpmList", {siteId: $scope.site}); + }); + return; + } + + startTime = Time($scope.job.startTime).subtract(1, "hour"); + endTime = Time().add(1, "hour"); + $scope.startTimestamp = $scope.job.startTime; + $scope.endTimestamp = $scope.job.endTime; + $scope.isRunning = !$scope.job.currentState || ($scope.job.currentState || "").toUpperCase() === 'RUNNING'; + + // Dashboard 1: Allocated MB + metric_allocatedMB = JPM.metrics(jobCond, "hadoop.job.allocatedmb", startTime, endTime); + + metric_allocatedMB._promise.then(function () { + var series_allocatedMB = JPM.metricsToSeries("allocated MB", metric_allocatedMB); + $scope.allocatedSeries = [series_allocatedMB]; + }); + + // Dashboard 2: vCores & Containers metrics + metric_allocatedVCores = JPM.metrics(jobCond, "hadoop.job.allocatedvcores", startTime, endTime); + metric_runningContainers = JPM.metrics(jobCond, "hadoop.job.runningcontainers", startTime, endTime); + + $q.all([metric_allocatedVCores._promise, metric_runningContainers._promise]).then(function () { + var series_allocatedVCores = JPM.metricsToSeries("vCores", metric_allocatedVCores); + var series_runningContainers = JPM.metricsToSeries("running containers", metric_runningContainers); + $scope.vCoresSeries = [series_allocatedVCores, series_runningContainers]; + }); + + // Dashboard 3: Task duration + taskDistribution($scope.job.tags.jobId).then(function (data) { + $scope.taskSeries = data.taskSeries; + + $scope.taskSeriesClick = function (e) { + var taskCount = e.seriesIndex === 0 ? data.finishedTaskCount : data.runningTaskCount; + $scope.taskBucket = taskCount[e.dataIndex]; + }; + + $scope.backToTaskSeries = function () { + $scope.taskBucket = null; + setTimeout(function () { + $(window).resize(); + }, 0); + }; + }); + + // Dashboard 4: Running task + nodeTaskCountList = JPM.groups( + $scope.isRunning ? "RunningTaskExecutionService" : "TaskExecutionService", + jobCond, ["hostname"], "count", null, startTime, endTime, null, 1000000); + nodeTaskCountList._promise.then(function () { + var nodeTaskCountMap = []; + + $.each(nodeTaskCountList, function (i, obj) { + var count = obj.value[0]; + nodeTaskCountMap[count] = (nodeTaskCountMap[count] || 0) + 1; + }); + + $scope.nodeTaskCountCategory = []; + for(i = 0 ; i < nodeTaskCountMap.length ; i += 1) { + nodeTaskCountMap[i] = nodeTaskCountMap[i] || 0; + $scope.nodeTaskCountCategory.push(i + " tasks"); + } + + nodeTaskCountMap.splice(0, 1); + $scope.nodeTaskCountCategory.splice(0, 1); + + $scope.nodeTaskCountSeries = [{ + name: "node count", + type: "bar", + data: nodeTaskCountMap + }]; + }); + + }); + }); + }); +})(); http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/jobTaskCtrl.js ---------------------------------------------------------------------- diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/jobTaskCtrl.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/jobTaskCtrl.js new file mode 100644 index 0000000..9f0e7f4 --- /dev/null +++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/jobTaskCtrl.js @@ -0,0 +1,551 @@ +/* + * 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. + */ + + +(function () { + /** + * `register` without params will load the module which using require + */ + register(function (jpmApp) { + var TREND_INTERVAL = 60; + var SCHEDULE_BUCKET_COUNT = 30; + var TASK_STATUS = ["SUCCEEDED", "FAILED", "KILLED"]; + var TASK_TYPE = ["MAP", "REDUCE"]; + var DURATION_BUCKETS = [0, 30 * 1000, 60 * 1000, 120 * 1000, 300 * 1000, 600 * 1000, 1800 * 1000, 3600 * 1000, 2 * 3600 * 1000, 3 * 3600 * 1000]; + var TASK_FIELDS = [ + "rack", + "hostname", + "taskType", + "taskId", + "taskStatus", + "startTime", + "endTime", + "jobCounters" + ]; + + function getCommonHeatMapOption(categoryList, maxCount) { + return { + animation: false, + tooltip: { + trigger: 'item' + }, + xAxis: {splitArea: {show: true}}, + yAxis: [{ + type: 'category', + data: categoryList, + splitArea: {show: true}, + axisTick: {show: false} + }], + grid: { bottom: "50" }, + visualMap: { + min: 0, + max: maxCount, + calculable: true, + orient: 'horizontal', + left: 'right', + inRange: { + color: ["#00a65a", "#ffdc62", "#dd4b39"] + } + } + }; + } + + function getCommonHeatMapSeries(name, data) { + return { + name: name, + type: "heatmap", + data: data, + itemStyle: { + normal: { + borderColor: "#FFF" + } + } + }; + } + + /** + * @typedef {{}} Task + * @property {string} taskStatus + * @property {number} startTime + * @property {number} endTime + * @property {{}} jobCounters + * @property {{}} jobCounters.counters + * @property {{}} tags + * @property {string} tags.taskType + * @property {number} _bucket + * @property {number} _bucketStart + * @property {number} _bucketEnd + * @property {number} _duration + * @property {number} _durationBucket + */ + + jpmApp.controller("jobTaskCtrl", function ($wrapState, $scope, PageConfig, Time, JPM) { + $scope.site = $wrapState.param.siteId; + $scope.jobId = $wrapState.param.jobId; + + var startTime = Number($wrapState.param.startTime); + var endTime = Number($wrapState.param.endTime); + + PageConfig.title = "Task"; + PageConfig.subTitle = $scope.jobId; + + var timeDiff = endTime - startTime; + var timeDes = Math.ceil(timeDiff / SCHEDULE_BUCKET_COUNT); + + $scope.bucketScheduleCategory = []; + for(var i = 0 ; i < SCHEDULE_BUCKET_COUNT ; i += 1) { + $scope.bucketScheduleCategory.push(Time.format(startTime + i * timeDes, "HH:mm:SS") + "\n~\n" + Time.format(startTime + (i + 1) * timeDes, "HH:mm:SS")); + } + + $scope.bucketDurationCategory = []; + $.each(DURATION_BUCKETS, function (i, start) { + var end = DURATION_BUCKETS[i + 1]; + if(!start) { + $scope.bucketDurationCategory.push("<" + Time.diffStr(end)); + } else if(!end) { + $scope.bucketDurationCategory.push(">" + Time.diffStr(start)); + } else { + $scope.bucketDurationCategory.push(Time.diffStr(start) + "\n~\n" + Time.diffStr(end)); + } + }); + + // ============================================================================ + // ============================================================================ + // == Fetch Task == + // ============================================================================ + // ============================================================================ + $scope.list = JPM.list("TaskExecutionService", {site: $scope.site, jobId: $scope.jobId}, startTime, endTime, TASK_FIELDS, 1000000); + $scope.list._promise.then(function () { + var i; + + // ========================= Schedule Trend ========================= + var trend_map_countList = []; + var trend_reduce_countList = []; + $.each($scope.list, + /** + * @param {number} i + * @param {Task} task + */ + function (i, task) { + var _task = { + _bucketStart: Math.floor((task.startTime - startTime) / TREND_INTERVAL), + _bucketEnd: Math.floor((task.endTime - startTime) / TREND_INTERVAL) + }; + + switch (task.tags.taskType) { + case "MAP": + fillBucket(trend_map_countList, _task); + break; + case "REDUCE": + fillBucket(trend_reduce_countList, _task); + break; + default: + console.warn("Task type not match:", task.tags.taskType, task); + } + }); + + $scope.scheduleCategory = []; + for(i = 0 ; i < Math.max(trend_map_countList.length, trend_reduce_countList.length) ; i += 1) { + $scope.scheduleCategory.push(Time.format(startTime + i * TREND_INTERVAL).replace(" ", "\n")); + } + + $scope.scheduleSeries = [{ + name: "Map Task Count", + type: "line", + showSymbol: false, + areaStyle: {normal: {}}, + data: trend_map_countList + }, { + name: "Reduce Task Count", + type: "line", + showSymbol: false, + areaStyle: {normal: {}}, + data: trend_reduce_countList + }]; + + // ======================= Bucket Distribution ====================== + $.each($scope.list, + /** + * @param {number} i + * @param {Task} task + */ + function (i, task) { + task._bucketStart = Math.floor((task.startTime - startTime) / timeDes); + task._bucketEnd = Math.floor((task.endTime - startTime) / timeDes); + task._duration = task.endTime - task.startTime; + task._durationBucket = common.number.inRange(DURATION_BUCKETS, task._duration); + }); + + // ================================================================== + // = Schedule Distribution = + // ================================================================== + function fillBucket(countList, task, maxCount) { + for(var bucketId = task._bucketStart ; bucketId <= task._bucketEnd ; bucketId += 1) { + var count = countList[bucketId] = (countList[bucketId] || 0) + 1; + maxCount = Math.max(maxCount, count); + } + return maxCount; + } + + function getHeatMapOption(categoryList, maxCount) { + var option = getCommonHeatMapOption(categoryList, maxCount); + return common.merge(option, { + tooltip: { + formatter: function (point) { + if(point.data) { + return categoryList[point.data[1]] + ":<br/>" + + '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:' + point.color + '"></span> ' + + $scope.bucketScheduleCategory[point.data[0]] + ": " + + point.data[2]; + } + return ""; + } + } + }); + } + + function bucketToSeries(categoryList, buckets, name) { + var bucket_data = $.map(categoryList, function (category, index) { + var list = []; + var dataList = buckets[category] || []; + for(var i = 0 ; i < SCHEDULE_BUCKET_COUNT ; i += 1) { + list.push([i, index, dataList[i] || 0]); + } + return list; + }); + + return [common.merge(getCommonHeatMapSeries(name, bucket_data), { + label: { + normal: { + show: true, + formatter: function (point) { + if(point.data[2] === 0) return "-"; + return " "; + } + } + } + })]; + } + + // ======================== Status Statistic ======================== + var bucket_status = {}; + var bucket_status_maxCount = 0; + $.each($scope.list, + /** + * @param {number} i + * @param {Task} task + */ + function (i, task) { + var countList = bucket_status[task.taskStatus] = (bucket_status[task.taskStatus] || []); + + bucket_status_maxCount = fillBucket(countList, task, bucket_status_maxCount); + }); + + $scope.statusSeries = bucketToSeries(TASK_STATUS, bucket_status, "Task Status"); + $scope.statusOption = getHeatMapOption(TASK_STATUS, bucket_status_maxCount); + + // ======================= Duration Statistic ======================= + var TASK_DURATION = [0, 120 * 1000, 300 * 1000, 600 * 1000, 1800 * 1000, 3600 * 1000]; + var bucket_durations = {}; + var bucket_durations_maxCount = 0; + + var TASK_DURATION_DISTRIBUTION = $.map(TASK_DURATION, function (start, i) { + var end = TASK_DURATION[i + 1]; + if(i === 0) { + return "<" + Time.diffStr(end); + } else if(end) { + return Time.diffStr(start) + "~" + Time.diffStr(end); + } + return ">" + Time.diffStr(start); + }); + + $.each($scope.list, + /** + * @param {number} i + * @param {Task} task + */ + function (i, task) { + var durationBucket = TASK_DURATION_DISTRIBUTION[common.number.inRange(TASK_DURATION, task._duration)]; + var countList = bucket_durations[durationBucket] = (bucket_durations[durationBucket] || []); + + bucket_durations_maxCount = fillBucket(countList, task, bucket_durations_maxCount); + }); + + $scope.durationSeries = bucketToSeries(TASK_DURATION_DISTRIBUTION, bucket_durations, "Task Duration Distribution"); + $scope.durationOption = getHeatMapOption(TASK_DURATION_DISTRIBUTION, bucket_durations_maxCount); + + // ======================= HDFS Read Statistic ====================== + var TASK_HDFS_BYTES = [0, 5 * 1024 * 1024, 20 * 1024 * 1024, 100 * 1024 * 1024, 256 * 1024 * 1024, 1024 * 1024 * 1024]; + var bucket_hdfs_reads = {}; + var bucket_hdfs_reads_maxCount = 0; + + var TASK_HDFS_DISTRIBUTION = $.map(TASK_HDFS_BYTES, function (start, i) { + var end = TASK_HDFS_BYTES[i + 1]; + if(i === 0) { + return "<" + common.number.abbr(end, true); + } else if(end) { + return common.number.abbr(start, true) + "~" + common.number.abbr(end, true); + } + return ">" + common.number.abbr(start, true); + }); + + $.each($scope.list, + /** + * @param {number} i + * @param {Task} task + */ + function (i, task) { + var durationBucket = TASK_HDFS_DISTRIBUTION[common.number.inRange(TASK_HDFS_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_READ)]; + var countList = bucket_hdfs_reads[durationBucket] = (bucket_hdfs_reads[durationBucket] || []); + + bucket_hdfs_reads_maxCount = fillBucket(countList, task, bucket_hdfs_reads_maxCount); + }); + + $scope.hdfsReadSeries = bucketToSeries(TASK_HDFS_DISTRIBUTION, bucket_hdfs_reads, "Task HDFS Read Distribution"); + $scope.hdfsReadOption = getHeatMapOption(TASK_HDFS_DISTRIBUTION, bucket_hdfs_reads_maxCount); + + // ====================== HDFS Write Statistic ====================== + var bucket_hdfs_writes = {}; + var bucket_hdfs_writes_maxCount = 0; + + $.each($scope.list, + /** + * @param {number} i + * @param {Task} task + */ + function (i, task) { + var durationBucket = TASK_HDFS_DISTRIBUTION[common.number.inRange(TASK_HDFS_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_WRITTEN)]; + var countList = bucket_hdfs_writes[durationBucket] = (bucket_hdfs_writes[durationBucket] || []); + + bucket_hdfs_writes_maxCount = fillBucket(countList, task, bucket_hdfs_writes_maxCount); + }); + + $scope.hdfsWriteSeries = bucketToSeries(TASK_HDFS_DISTRIBUTION, bucket_hdfs_writes, "Task HDFS Write Distribution"); + $scope.hdfsWriteOption = getHeatMapOption(TASK_HDFS_DISTRIBUTION, bucket_hdfs_writes_maxCount); + + // ====================== Local Read Statistic ====================== + var TASK_LOCAL_BYTES = [0, 20 * 1024 * 1024, 100 * 1024 * 1024, 256 * 1024 * 1024, 1024 * 1024 * 1024, 2 * 1024 * 1024 * 1024]; + var bucket_local_reads = {}; + var bucket_local_reads_maxCount = 0; + + var TASK_LOCAL_DISTRIBUTION = $.map(TASK_LOCAL_BYTES, function (start, i) { + var end = TASK_LOCAL_BYTES[i + 1]; + if(i === 0) { + return "<" + common.number.abbr(end, true); + } else if(end) { + return common.number.abbr(start, true) + "~" + common.number.abbr(end, true); + } + return ">" + common.number.abbr(start, true); + }); + + $.each($scope.list, + /** + * @param {number} i + * @param {Task} task + */ + function (i, task) { + var durationBucket = TASK_LOCAL_DISTRIBUTION[common.number.inRange(TASK_LOCAL_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_READ)]; + var countList = bucket_local_reads[durationBucket] = (bucket_local_reads[durationBucket] || []); + + bucket_local_reads_maxCount = fillBucket(countList, task, bucket_local_reads_maxCount); + }); + + $scope.localReadSeries = bucketToSeries(TASK_LOCAL_DISTRIBUTION, bucket_local_reads, "Task Local Read Distribution"); + $scope.localReadOption = getHeatMapOption(TASK_LOCAL_DISTRIBUTION, bucket_local_reads_maxCount); + + // ====================== Local Write Statistic ===================== + var bucket_local_writes = {}; + var bucket_local_writes_maxCount = 0; + + $.each($scope.list, + /** + * @param {number} i + * @param {Task} task + */ + function (i, task) { + var durationBucket = TASK_LOCAL_DISTRIBUTION[common.number.inRange(TASK_HDFS_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_WRITTEN)]; + var countList = bucket_local_writes[durationBucket] = (bucket_local_writes[durationBucket] || []); + + bucket_local_writes_maxCount = fillBucket(countList, task, bucket_local_writes_maxCount); + }); + + $scope.localWriteSeries = bucketToSeries(TASK_LOCAL_DISTRIBUTION, bucket_local_writes, "Task Local Write Distribution"); + $scope.localWriteOption = getHeatMapOption(TASK_LOCAL_DISTRIBUTION, bucket_local_writes_maxCount); + + // ================================================================== + // = Duration Distribution = + // ================================================================== + function fillDurationBucket(countList, task, maxCount) { + var count = countList[task._durationBucket] = (countList[task._durationBucket] || 0) + 1; + maxCount = Math.max(maxCount, count); + return maxCount; + } + + function getDurationHeatMapOption(categoryList, maxCount) { + var option = getCommonHeatMapOption(categoryList, maxCount); + return common.merge(option, { + tooltip: { + formatter: function (point) { + if(point.data) { + return categoryList[point.data[1]] + ":<br/>" + + '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:' + point.color + '"></span> ' + + $scope.bucketDurationCategory[point.data[0]] + ": " + + point.data[2]; + } + return ""; + } + } + }); + } + + function bucketToDurationSeries(categoryList, buckets, name) { + var bucket_data = $.map(categoryList, function (category, index) { + var list = []; + var dataList = buckets[category] || []; + for(var i = 0 ; i < DURATION_BUCKETS.length ; i += 1) { + list.push([i, index, dataList[i] || 0]); + } + return list; + }); + + return [common.merge(getCommonHeatMapSeries(name, bucket_data), { + label: { + normal: { + show: true, + formatter: function (point) { + if(point.data[2] === 0) return "-"; + return point.data[2] + ""; + } + } + } + })]; + } + + // ======================== Status Statistic ======================== + var duration_status = {}; + var duration_status_maxCount = 0; + $.each($scope.list, + /** + * @param {number} i + * @param {Task} task + */ + function (i, task) { + var countList = duration_status[task.taskStatus] = (duration_status[task.taskStatus] || []); + + duration_status_maxCount = fillDurationBucket(countList, task, duration_status_maxCount); + }); + + $scope.durationStatusSeries = bucketToDurationSeries(TASK_STATUS, duration_status, "Task Status"); + $scope.durationStatusOption = getDurationHeatMapOption(TASK_STATUS, duration_status_maxCount); + + // ===================== Map / Reduce Statistic ===================== + var mapReduce_status = {}; + var mapReduce_status_maxCount = 0; + $.each($scope.list, + /** + * @param {number} i + * @param {Task} task + */ + function (i, task) { + var countList = mapReduce_status[task.tags.taskType] = (mapReduce_status[task.tags.taskType] || []); + + mapReduce_status_maxCount = fillDurationBucket(countList, task, mapReduce_status_maxCount); + }); + + $scope.durationMapReduceSeries = bucketToDurationSeries(TASK_TYPE, mapReduce_status, "Task Type"); + $scope.durationMapReduceOption = getDurationHeatMapOption(TASK_TYPE, mapReduce_status_maxCount); + + // ======================= HDFS Read Statistic ====================== + var duration_hdfs_reads = {}; + var duration_hdfs_reads_maxCount = 0; + + $.each($scope.list, + /** + * @param {number} i + * @param {Task} task + */ + function (i, task) { + var durationBucket = TASK_HDFS_DISTRIBUTION[common.number.inRange(TASK_HDFS_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_READ)]; + var countList = duration_hdfs_reads[durationBucket] = (duration_hdfs_reads[durationBucket] || []); + + duration_hdfs_reads_maxCount = fillDurationBucket(countList, task, duration_hdfs_reads_maxCount); + }); + + $scope.durationHdfsReadSeries = bucketToDurationSeries(TASK_HDFS_DISTRIBUTION, duration_hdfs_reads, "Task HDFS Read Distribution"); + $scope.durationHdfsReadOption = getDurationHeatMapOption(TASK_HDFS_DISTRIBUTION, duration_hdfs_reads_maxCount); + + // ====================== HDFS Write Statistic ====================== + var duration_hdfs_writes = {}; + var duration_hdfs_writes_maxCount = 0; + + $.each($scope.list, + /** + * @param {number} i + * @param {Task} task + */ + function (i, task) { + var durationBucket = TASK_HDFS_DISTRIBUTION[common.number.inRange(TASK_HDFS_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].HDFS_BYTES_WRITTEN)]; + var countList = duration_hdfs_writes[durationBucket] = (duration_hdfs_writes[durationBucket] || []); + + duration_hdfs_writes_maxCount = fillDurationBucket(countList, task, duration_hdfs_writes_maxCount); + }); + + $scope.durationHdfsWriteSeries = bucketToDurationSeries(TASK_HDFS_DISTRIBUTION, duration_hdfs_writes, "Task HDFS Write Distribution"); + $scope.durationHdfsWriteOption = getDurationHeatMapOption(TASK_HDFS_DISTRIBUTION, duration_hdfs_writes_maxCount); + + // ====================== Local Read Statistic ====================== + var duration_local_reads = {}; + var duration_local_reads_maxCount = 0; + + $.each($scope.list, + /** + * @param {number} i + * @param {Task} task + */ + function (i, task) { + var durationBucket = TASK_LOCAL_DISTRIBUTION[common.number.inRange(TASK_LOCAL_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_READ)]; + var countList = duration_local_reads[durationBucket] = (duration_local_reads[durationBucket] || []); + + duration_local_reads_maxCount = fillDurationBucket(countList, task, duration_local_reads_maxCount); + }); + + $scope.durationLocalReadSeries = bucketToDurationSeries(TASK_LOCAL_DISTRIBUTION, duration_local_reads, "Task Local Read Distribution"); + $scope.durationLocalReadOption = getDurationHeatMapOption(TASK_LOCAL_DISTRIBUTION, duration_local_reads_maxCount); + + // ====================== Local Write Statistic ===================== + var duration_local_writes = {}; + var duration_local_writes_maxCount = 0; + + $.each($scope.list, + /** + * @param {number} i + * @param {Task} task + */ + function (i, task) { + var durationBucket = TASK_LOCAL_DISTRIBUTION[common.number.inRange(TASK_HDFS_BYTES, task.jobCounters.counters["org.apache.hadoop.mapreduce.FileSystemCounter"].FILE_BYTES_WRITTEN)]; + var countList = duration_local_writes[durationBucket] = (duration_local_writes[durationBucket] || []); + + duration_local_writes_maxCount = fillDurationBucket(countList, task, duration_local_writes_maxCount); + }); + + $scope.durationLocalWriteSeries = bucketToDurationSeries(TASK_LOCAL_DISTRIBUTION, duration_local_writes, "Task Local Write Distribution"); + $scope.durationLocalWriteOption = getDurationHeatMapOption(TASK_LOCAL_DISTRIBUTION, duration_local_writes_maxCount); + }); + }); + }); +})(); http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/listCtrl.js ---------------------------------------------------------------------- diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/listCtrl.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/listCtrl.js new file mode 100644 index 0000000..ff9ed5e --- /dev/null +++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/listCtrl.js @@ -0,0 +1,239 @@ +/* + * 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. + */ + +(function () { + /** + * `register` without params will load the module which using require + */ + register(function (jpmApp) { + var JOB_STATES = ["NEW", "NEW_SAVING", "SUBMITTED", "ACCEPTED", "RUNNING", "FINISHED", "SUCCEEDED", "FAILED", "KILLED"]; + + jpmApp.controller("listCtrl", function ($wrapState, $element, $scope, $q, PageConfig, Time, Entity, JPM) { + // Initialization + PageConfig.title = "YARN Jobs"; + $scope.getStateClass = JPM.getStateClass; + $scope.tableScope = {}; + + $scope.site = $wrapState.param.siteId; + $scope.searchPathList = [["tags", "jobId"], ["tags", "user"], ["tags", "queue"], ["currentState"]]; + + function getCommonOption(left) { + return { + grid: { + left: left, + bottom: 20, + containLabel: false + } + }; + } + + $scope.chartLeftOption = getCommonOption(45); + $scope.chartRightOption = getCommonOption(80); + + $scope.fillSearch = function (key) { + $("#jobList").find(".search-box input").val(key).trigger('input'); + }; + + $scope.refreshList = function () { + var startTime = Time.startTime(); + var endTime = Time.endTime(); + + // ========================================================== + // = Job List = + // ========================================================== + + /** + * @namespace + * @property {[]} jobList + * @property {{}} jobList.tags unique job key + * @property {string} jobList.tags.jobId Job Id + * @property {string} jobList.tags.user Submit user + * @property {string} jobList.tags.queue Queue + * @property {string} jobList.currentState Job state + * @property {string} jobList.submissionTime Submission time + * @property {string} jobList.startTime Start time + * @property {string} jobList.endTime End time + * @property {string} jobList.numTotalMaps Maps count + * @property {string} jobList.numTotalReduces Reduce count + * @property {string} jobList.runningContainers Running container count + */ + + $scope.jobList = Entity.merge($scope.jobList, JPM.jobList({site: $scope.site}, startTime, endTime, [ + "jobId", + "jobDefId", + "jobName", + "jobExecId", + "currentState", + "user", + "queue", + "submissionTime", + "startTime", + "endTime", + "numTotalMaps", + "numTotalReduces", + "runningContainers" + ], 100000)); + $scope.jobStateList = []; + + $scope.jobList._then(function () { + var now = Time(); + var jobStates = {}; + $.each($scope.jobList, function (i, job) { + jobStates[job.currentState] = (jobStates[job.currentState] || 0) + 1; + job.duration = Time.diff(job.startTime, job.endTime || now); + }); + + $scope.jobStateList = $.map(JOB_STATES, function (state) { + var value = jobStates[state]; + delete jobStates[state]; + if(!value) return null; + return { + key: state, + value: value + }; + }); + + $.each(jobStates, function (key, value) { + $scope.jobStateList.push({ + key: key, + value: value + }); + }); + }); + + // =========================================================== + // = Statistic Trend = + // =========================================================== + var interval = Time.diffInterval(startTime, endTime); + var intervalMin = interval / 1000 / 60; + var trendStartTime = Time.align(startTime, interval); + var trendEndTime = Time.align(endTime, interval); + var trendStartTimestamp = trendStartTime.valueOf(); + + // ==================== Running Job Trend ==================== + JPM.get(JPM.getQuery("MR_JOB_COUNT"), { + site: $scope.site, + intervalInSecs: interval / 1000, + durationBegin: Time.format(trendStartTime), + durationEnd: Time.format(trendEndTime) + }).then( + /** + * @param {{}} res + * @param {{}} res.data + * @param {[]} res.data.jobCounts + */ + function (res) { + var data = res.data; + var jobCounts = data.jobCounts; + var jobTypesData = {}; + $.each(jobCounts, + /** + * @param index + * @param {{}} jobCount + * @param {{}} jobCount.timeBucket + * @param {{}} jobCount.jobCountByType + */ + function (index, jobCount) { + $.each(jobCount.jobCountByType, function (type, count) { + var countList = jobTypesData[type] = jobTypesData[type] || []; + countList[index] = count; + }); + }); + + $scope.runningTrendSeries = $.map(jobTypesData, function (countList, type) { + var dataList = []; + for(var i = 0 ; i < jobCounts.length ; i += 1) { + dataList[i] = { + x: trendStartTimestamp + i * interval, + y: countList[i] || 0 + }; + } + + return { + name: type, + type: "line", + stack: "job", + showSymbol: false, + areaStyle: {normal: {}}, + data: dataList + }; + }); + }); + + // ================= Running Container Trend ================= + JPM.aggMetricsToEntities( + JPM.aggMetrics({site: $scope.site}, "hadoop.cluster.runningcontainers", ["site"], "max(value)", intervalMin, trendStartTime, trendEndTime), + true)._promise.then(function (list) { + $scope.runningContainersSeries = [JPM.metricsToSeries("Running Containers", list, {areaStyle: {normal: {}}})]; + }); + + // ================= Allocated vCores Trend ================== + JPM.aggMetricsToEntities( + JPM.aggMetrics({site: $scope.site}, "hadoop.cluster.allocatedvcores", ["site"], "max(value)", intervalMin, trendStartTime, trendEndTime), + true)._promise.then(function (list) { + $scope.allocatedvcoresSeries = [JPM.metricsToSeries("Allocated vCores", list, {areaStyle: {normal: {}}})]; + }); + + // ==================== AllocatedMB Trend ==================== + var allocatedMBEntities = JPM.aggMetricsToEntities( + JPM.aggMetrics({site: $scope.site}, "hadoop.cluster.allocatedmb", ["site"], "max(value)", intervalMin, trendStartTime, trendEndTime), + true); + + var totalMemoryEntities = JPM.aggMetricsToEntities( + JPM.aggMetrics({site: $scope.site}, "hadoop.cluster.totalmemory", ["site"], "max(value)", intervalMin, trendStartTime, trendEndTime), + true); + + $q.all([allocatedMBEntities._promise, totalMemoryEntities._promise]).then(function (args) { + var allocatedMBList = args[0]; + var totalMemoryList = args[1]; + + var mergedList = $.map(allocatedMBList, function (obj, index) { + var value = obj.value[0] / totalMemoryList[index].value[0] * 100 || 0; + return $.extend({}, obj, { + value: [value] + }); + }); + + $scope.allocatedMBSeries = [JPM.metricsToSeries("Allocated GB", mergedList, {areaStyle: {normal: {}}})]; + $scope.allocatedMBOption = $.extend({}, $scope.chartRightOption, { + yAxis: [{ + axisLabel: { + formatter: "{value}%" + }, + max: 100 + }], + tooltip: { + formatter: function (points) { + var point = points[0]; + var index = point.dataIndex; + return point.name + "<br/>" + + '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:' + point.color + '"></span> ' + + point.seriesName + ": " + common.number.format(allocatedMBList[index].value[0] / 1024, 2); + } + } + }); + }); + }; + + Time.onReload($scope.refreshList, $scope); + + // Load list + $scope.refreshList(); + }); + }); +})(); http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/overviewCtrl.js ---------------------------------------------------------------------- diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/overviewCtrl.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/overviewCtrl.js new file mode 100644 index 0000000..7d7b949 --- /dev/null +++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/overviewCtrl.js @@ -0,0 +1,140 @@ +/* + * 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. + */ + +(function () { + /** + * `register` without params will load the module which using require + */ + register(function (jpmApp) { + jpmApp.controller("overviewCtrl", function ($q, $wrapState, $element, $scope, $timeout, PageConfig, Time, Entity, JPM) { + var cache = {}; + $scope.aggregationMap = { + job: "jobId", + user: "user", + jobType: "jobType" + }; + + $scope.site = $wrapState.param.siteId; + + PageConfig.title = "Overview"; + + $scope.type = "job"; + + $scope.commonOption = { + animation: false, + tooltip: { + formatter: function (points) { + return points[0].name + "<br/>" + + $.map(points, function (point) { + return '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:' + point.color + '"></span> ' + + point.seriesName + ": " + + common.number.format(point.value, true); + }).reverse().join("<br/>"); + } + }, + grid: { + top: 70 + }, + yAxis: [{ + axisLabel: {formatter: function (value) { + return common.number.abbr(value, true); + }} + }] + }; + + // ====================================================================== + // = Refresh Overview = + // ====================================================================== + $scope.typeChange = function () { + $timeout($scope.refresh, 1); + }; + + // TODO: Optimize the chart count + // TODO: ECharts dynamic refresh series bug: https://github.com/ecomfe/echarts/issues/4033 + $scope.refresh = function () { + var startTime = Time.startTime(); + var endTime = Time.endTime(); + var intervalMin = Time.diffInterval(startTime, endTime) / 1000 / 60; + + function getTopList(metric, scopeVariable) { + var deferred = $q.defer(); + + metric = common.template(metric, { + type: $scope.type.toLocaleLowerCase() + }); + + if(scopeVariable) { + $scope[scopeVariable] = []; + $scope[scopeVariable]._done = false; + $scope[scopeVariable + "List"] = []; + } + + var aggregation = $scope.aggregationMap[$scope.type]; + + var aggPromise = cache[metric] = cache[metric] || JPM.aggMetricsToEntities( + JPM.aggMetrics({site: $scope.site}, metric, [aggregation], "avg(value), sum(value) desc", intervalMin, startTime, endTime, 10) + )._promise.then(function (list) { + var series = $.map(list, function (metrics) { + return JPM.metricsToSeries(metrics[0].tags[aggregation], metrics, { + stack: "stack", + areaStyle: {normal: {}} + }); + }); + + var topList = $.map(series, function (series) { + return { + name: series.name, + total: common.number.sum(series.data, "y") * intervalMin + }; + }).sort(function (a, b) { + return b.total - a.total; + }); + + return [series, topList]; + }); + + aggPromise.then(function (args) { + if(scopeVariable) { + $scope[scopeVariable] = args[0]; + $scope[scopeVariable]._done = true; + $scope[scopeVariable + "List"] = args[1]; + } + }); + + return deferred.promise; + } + + getTopList("hadoop.${type}.history.minute.cpu_milliseconds", "cpuUsageSeries"); + getTopList("hadoop.${type}.history.minute.physical_memory_bytes", "physicalMemorySeries"); + getTopList("hadoop.${type}.history.minute.virtual_memory_bytes", "virtualMemorySeries"); + getTopList("hadoop.${type}.history.minute.hdfs_bytes_read", "hdfsBtyesReadSeries"); + getTopList("hadoop.${type}.history.minute.hdfs_bytes_written", "hdfsBtyesWrittenSeries"); + getTopList("hadoop.${type}.history.minute.hdfs_read_ops", "hdfsReadOpsSeries"); + getTopList("hadoop.${type}.history.minute.hdfs_write_ops", "hdfsWriteOpsSeries"); + getTopList("hadoop.${type}.history.minute.file_bytes_read", "fileBytesReadSeries"); + getTopList("hadoop.${type}.history.minute.file_bytes_written", "fileBytesWrittenSeries"); + }; + + Time.onReload(function () { + cache = {}; + $scope.refresh(); + }, $scope); + $scope.refresh(); + }); + }); +})(); http://git-wip-us.apache.org/repos/asf/incubator-eagle/blob/afb89794/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/statisticCtrl.js ---------------------------------------------------------------------- diff --git a/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/statisticCtrl.js b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/statisticCtrl.js new file mode 100644 index 0000000..6dff7a1 --- /dev/null +++ b/eagle-jpm/eagle-jpm-web/src/main/webapp/app/apps/jpm/ctrl/statisticCtrl.js @@ -0,0 +1,386 @@ +/* + * 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. + */ + +(function () { + /** + * `register` without params will load the module which using require + */ + register(function (jpmApp) { + var colorMap = { + "SUCCEEDED": "#00a65a", + "FAILED": "#dd4b39", + "KILLED": "#CCCCCC", + "ERROR": "#f39c12" + }; + + var DURATION_BUCKETS = [0, 30, 60, 120, 300, 600, 1800, 3600, 2 * 3600, 3 * 3600]; + + jpmApp.controller("statisticCtrl", function ($wrapState, $element, $scope, PageConfig, Time, Entity, JPM, Chart) { + $scope.site = $wrapState.param.siteId; + + PageConfig.title = "Job Statistics"; + + $scope.type = "hourly"; + + $scope.switchType = function (type) { + $scope.type = type; + $scope.refreshDistribution(); + }; + + // =============================================================== + // = Time Level Distribution = + // =============================================================== + function parseDayBuckets(startTime, endTime) { + startTime = startTime.clone().hour(0).minute(0).second(0); + endTime = endTime.clone().hour(0).minute(0).second(0); + + var _buckets = []; + var _start = startTime.clone(); + + do { + var _end = _start.clone().date(1).add(1, "month").date(0); + if (_end.isAfter(endTime)) { + _end = endTime.clone(); + } + var _dayDes = moment.duration(_end.diff(_start)).asDays() + 1; + _buckets.push(_dayDes); + + _start = _end.clone().add(1, "day"); + } while (!_start.isAfter(endTime)); + + return _buckets; + } + + var distributionCache = {}; + $scope.distributionSelectedType = ""; + $scope.distributionSelectedIndex = -1; + + $scope.jobDistributionSeriesOption = {}; + + $scope.refreshDistribution = function () { + var type = $scope.type; + var startTime, endTime; + var metric, minInterval, field; + $scope.distributionSelectedIndex = -1; + + switch (type) { + case "monthly": + endTime = Time("monthEnd"); + startTime = endTime.clone().subtract(365, "day").date(1).hours(0).minutes(0).seconds(0); + metric = "hadoop.job.history.day.count"; + minInterval = 1440; + field = "max(value)"; + break; + case "weekly": + endTime = Time("weekEnd"); + startTime = Time("week").subtract(7 * 12, "day"); + metric = "hadoop.job.history.day.count"; + minInterval = 1440 * 7; + field = "sum(value)"; + break; + case "daily": + endTime = Time("dayEnd"); + startTime = Time("day").subtract(31, "day"); + metric = "hadoop.job.history.day.count"; + minInterval = 1440; + field = "max(value)"; + break; + case "hourly": + endTime = Time("hourEnd"); + startTime = Time("day").subtract(2, "day"); + metric = "hadoop.job.history.hour.count"; + minInterval = 60; + field = "sum(value)"; + break; + } + + $scope.jobDistributionSeries = []; + $scope.jobDistributionCategoryFunc = function (value) { + if(type === "hourly") { + return Time.format(value, "HH:mm"); + } + return Time.format(value, "MM-DD"); + }; + var promise = distributionCache[type] = distributionCache[type] || JPM.aggMetricsToEntities( + JPM.aggMetrics({site: $scope.site}, metric, ["jobStatus"], field, minInterval, startTime, endTime) + )._promise.then(function (list) { + if(type === "monthly") { + var buckets = parseDayBuckets(startTime, endTime); + list = $.map(list, function (units) { + // Merge by day buckets + units = units.concat(); + return [$.map(buckets, function (dayCount) { + var subUnits = units.splice(0, dayCount); + var sum = common.number.sum(subUnits, ["value", 0]); + + return $.extend({}, subUnits[0], { + value: [sum] + }); + })]; + }); + } + return list; + }).then(function(list) { + /** + * @param {Object[]} metrics + * @param {{}} metrics[].tags + * @param {string} metrics[].tags.jobStatus + */ + var series = $.map(list, function (metrics) { + return JPM.metricsToSeries(metrics[0].tags.jobStatus, metrics, { + stack: "stack", + type: "bar", + itemStyle: { + normal: { + borderWidth: 2 + } + } + }); + }); + common.array.doSort(series, "name", true, ["SUCCEEDED", "FAILED", "KILLED", "ERROR"]); + $scope.jobDistributionSeriesOption.color = $.map(series, function (series) { + return colorMap[series.name]; + }); + + return series; + }); + + promise.then(function(series) { + $scope.jobDistributionSeries = series; + }); + }; + + // ============================================================== + // = Drill Down = + // ============================================================== + $scope.commonChartOption = { + grid: { + left: 42, + bottom: 60, + containLabel: false + }, + yAxis: [{ + minInterval: 1 + }], + xAxis: { + axisLabel: { + interval: 0 + } + } + }; + $scope.commonTrendChartOption = { + yAxis: [{ + minInterval: 1 + }], + grid: { + top: 60, + left: 42, + bottom: 20, + containLabel: false + } + }; + + $scope.topUserJobCountSeries = []; + $scope.topTypeJobCountSeries = []; + + $scope.drillDownCategoryFunc = function (value) { + switch ($scope.type) { + case "monthly": + return Time.format(value, "MM-DD"); + case "weekly": + case "daily": + return Time.format(value, "MM-DD HH:mm"); + default: + return Time.format(value, "HH:mm"); + } + }; + + $scope.bucketDurationCategory = []; + $.each(DURATION_BUCKETS, function (i, start) { + var end = DURATION_BUCKETS[i + 1]; + + start *= 1000; + end *= 1000; + + if(!start) { + $scope.bucketDurationCategory.push("<" + Time.diffStr(end)); + } else if(!end) { + $scope.bucketDurationCategory.push(">" + Time.diffStr(start)); + } else { + $scope.bucketDurationCategory.push(Time.diffStr(start) + "\n~\n" + Time.diffStr(end)); + } + }); + + function flattenTrendSeries(name, series) { + var len = series.length; + var category = []; + var data = []; + var needBreakLine = series.length > 6; + $.each(series, function (i, series) { + category.push((needBreakLine && i % 2 !== 0 ? "\n" : "") + series.name); + data.push(common.number.sum(series.data, ["y"])); + }); + return { + category: category.reverse(), + series: [{ + name: name, + data: data.reverse(), + type: "bar", + itemStyle: { + normal: { + color: function (point) { + return Chart.color[len - point.dataIndex - 1]; + } + } + } + }] + }; + } + + $scope.distributionClick = function (event) { + if(event.componentType !== "series") return; + + $scope.distributionSelectedType = event.seriesName; + $scope.distributionSelectedIndex = event.dataIndex; + var timestamp = 0; + + // Highlight logic + $.each($scope.jobDistributionSeries, function (i, series) { + timestamp = series.data[$scope.distributionSelectedIndex].x; + + common.merge(series, { + itemStyle: { + normal: { + color: function (point) { + if(point.seriesName === $scope.distributionSelectedType && point.dataIndex === $scope.distributionSelectedIndex) { + return "#60C0DD"; + } + return colorMap[point.seriesName]; + } + } + } + }); + }); + + // Data fetch + var startTime = Time(timestamp); + var endTime; + + switch ($scope.type) { + case "monthly": + endTime = startTime.clone().add(1, "month").subtract(1, "s"); + break; + case "weekly": + endTime = startTime.clone().add(7, "day").subtract(1, "s"); + break; + case "daily": + endTime = startTime.clone().add(1, "day").subtract(1, "s"); + break; + case "hourly": + endTime = startTime.clone().add(1, "hour").subtract(1, "s"); + break; + } + + var intervalMin = Time.diffInterval(startTime, endTime) / 1000 / 60; + + // ===================== Top User Job Count ===================== + $scope.topUserJobCountSeries = []; + $scope.topUserJobCountTrendSeries = []; + JPM.aggMetricsToEntities( + JPM.groups("JobExecutionService", {site: $scope.site, currentState: $scope.distributionSelectedType}, ["user"], "count desc", intervalMin, startTime, endTime, 10, 1000000) + )._promise.then(function (list) { + $scope.topUserJobCountTrendSeries = $.map(list, function (subList) { + return JPM.metricsToSeries(subList[0].tags.user, subList, { + stack: "user", + areaStyle: {normal: {}} + }); + }); + + var flatten = flattenTrendSeries("User", $scope.topUserJobCountTrendSeries); + $scope.topUserJobCountSeries = flatten.series; + $scope.topUserJobCountSeriesCategory = flatten.category; + }); + + // ===================== Top Type Job Count ===================== + $scope.topTypeJobCountSeries = []; + $scope.topTypeJobCountTrendSeries = []; + JPM.aggMetricsToEntities( + JPM.groups("JobExecutionService", {site: $scope.site, currentState: $scope.distributionSelectedType}, ["jobType"], "count desc", intervalMin, startTime, endTime, 10, 1000000) + )._promise.then(function (list) { + $scope.topTypeJobCountTrendSeries = $.map(list, function (subList) { + return JPM.metricsToSeries(subList[0].tags.jobType, subList, { + stack: "type", + areaStyle: {normal: {}} + }); + }); + + var flatten = flattenTrendSeries("Job Type", $scope.topTypeJobCountTrendSeries); + $scope.topTypeJobCountSeries = flatten.series; + $scope.topTypeJobCountSeriesCategory = flatten.category; + }); + + if($scope.distributionSelectedType === "FAILED") { + // ====================== Failure Job List ====================== + $scope.jobList = JPM.jobList({site: $scope.site, currentState: "FAILED"}, startTime, endTime, [ + "jobId", + "jobName", + "user", + "startTime", + "jobType" + ]); + } else { + // ============== Job Duration Distribution Count =============== + $scope.jobList = null; + + $scope.jobDurationDistributionSeries = []; + /** + * @param {{}} res + * @param {{}} res.data + * @param {[]} res.data.jobTypes + * @param {[]} res.data.jobCounts + */ + JPM.jobDistribution($scope.site, $scope.type, DURATION_BUCKETS.join(","), startTime, endTime).then(function (res) { + var data = res.data; + var jobTypes = {}; + $.each(data.jobTypes, function (i, type) { + jobTypes[type] = []; + }); + $.each(data.jobCounts, function (index, statistic) { + $.each(statistic.jobCountByType, function (type, value) { + jobTypes[type][index] = value; + }); + }); + + $scope.jobDurationDistributionSeries = $.map(jobTypes, function (list, type) { + return { + name: type, + data: list, + type: "bar", + stack: "jobType" + }; + }); + }); + } + + return true; + }; + + $scope.refreshDistribution(); + }); + }); +})();