This is an automated email from the ASF dual-hosted git repository. nju_yaho pushed a commit to tag ebay-3.1.0-release-20200701 in repository https://gitbox.apache.org/repos/asf/kylin.git
commit 637d83f5d75dab7eb1ec72868290f8bb14a618d0 Author: Zhong, Yanghong <nju_y...@apache.org> AuthorDate: Thu Jun 11 18:16:14 2020 +0800 KYLIN-4535 Frontend support for query & storage trend --- .../kylin/rest/service/DashboardService.java | 1 + .../apache/kylin/rest/service/QueryService.java | 1 - .../apache/kylin/rest/util/SqlCreationUtil.java | 6 ++- webapp/app/js/controllers/cube.js | 57 +++++++++++++++++++--- webapp/app/js/controllers/dashboard.js | 12 +++++ webapp/app/js/model/cubeConfig.js | 45 ++++++++++++++++- webapp/app/js/model/dashboardConfig.js | 5 +- webapp/app/js/services/cubes.js | 3 +- webapp/app/js/services/dashboard.js | 2 +- webapp/app/less/app.less | 6 +-- webapp/app/partials/cubes/cube_detail.html | 18 +++++++ webapp/app/partials/dashboard/dashboard.html | 26 ++++++++-- 12 files changed, 158 insertions(+), 24 deletions(-) diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/DashboardService.java b/server-base/src/main/java/org/apache/kylin/rest/service/DashboardService.java index 17d51aa..4792b1b 100644 --- a/server-base/src/main/java/org/apache/kylin/rest/service/DashboardService.java +++ b/server-base/src/main/java/org/apache/kylin/rest/service/DashboardService.java @@ -115,6 +115,7 @@ public class DashboardService extends BasicService { jobMetrics.increase("avgJobBuildTime", getMetricValue(row.get(1))); jobMetrics.increase("maxJobBuildTime", getMetricValue(row.get(2))); jobMetrics.increase("minJobBuildTime", getMetricValue(row.get(3))); + jobMetrics.increase("avgJobExpansionRate", getMetricValue(row.get(4))); } return jobMetrics; diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java b/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java index 625b688..7d9b481 100644 --- a/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java +++ b/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java @@ -95,7 +95,6 @@ import org.apache.kylin.metadata.querymeta.SelectedColumnMeta; import org.apache.kylin.metadata.querymeta.TableMeta; import org.apache.kylin.metadata.querymeta.TableMetaWithType; import org.apache.kylin.metadata.realization.IRealization; -import org.apache.kylin.metrics.MetricsManager; import org.apache.kylin.query.QueryConnection; import org.apache.kylin.query.relnode.OLAPContext; import org.apache.kylin.query.util.PushDownUtil; diff --git a/server-base/src/main/java/org/apache/kylin/rest/util/SqlCreationUtil.java b/server-base/src/main/java/org/apache/kylin/rest/util/SqlCreationUtil.java index 4b6acf9..021bb8b 100644 --- a/server-base/src/main/java/org/apache/kylin/rest/util/SqlCreationUtil.java +++ b/server-base/src/main/java/org/apache/kylin/rest/util/SqlCreationUtil.java @@ -95,7 +95,8 @@ public class SqlCreationUtil { public static PrepareSqlRequest createPrepareSqlRequestOfTotalJobMetrics(String projectName, String cubeName, String startTime, String endTime) { String[] measures = new String[] { JobMeasureEnum.JOB_COUNT.toSQL(), JobMeasureEnum.AVG_JOB_BUILD_TIME.toSQL(), - JobMeasureEnum.MAX_JOB_BUILD_TIME.toSQL(), JobMeasureEnum.MIN_JOB_BUILD_TIME.toSQL() }; + JobMeasureEnum.MAX_JOB_BUILD_TIME.toSQL(), JobMeasureEnum.MIN_JOB_BUILD_TIME.toSQL(), + JobMeasureEnum.EXPANSION_RATE.toSQL() }; return createPrepareSqlRequestOfJobMetrics(projectName, cubeName, startTime, endTime, null, measures); } @@ -373,7 +374,8 @@ public class SqlCreationUtil { JOB_COUNT("count(*)"), // AVG_JOB_BUILD_TIME("avg(" + JobPropertyEnum.PER_BYTES_TIME_COST.toString() + ")"), // MAX_JOB_BUILD_TIME("max(" + JobPropertyEnum.PER_BYTES_TIME_COST.toString() + ")"), // - MIN_JOB_BUILD_TIME("min(" + JobPropertyEnum.PER_BYTES_TIME_COST.toString() + ")"); + MIN_JOB_BUILD_TIME("min(" + JobPropertyEnum.PER_BYTES_TIME_COST.toString() + ")"), // + EXPANSION_RATE(getExpansionRateMetric()); private final String sql; diff --git a/webapp/app/js/controllers/cube.js b/webapp/app/js/controllers/cube.js index 5664690..89e0ea2 100755 --- a/webapp/app/js/controllers/cube.js +++ b/webapp/app/js/controllers/cube.js @@ -125,7 +125,7 @@ KylinApp.controller('CubeCtrl', function ($scope, $rootScope, AccessService, Mes if (!cube.currentCuboids) { CubeService.getCurrentCuboids({cubeId: cube.name}, function(data) { if (data && data.nodeInfos) { - $scope.createChart(data, 'current'); + $scope.createPlannerChart(data, 'current'); cube.currentCuboids = data; } else { $scope.currentOptions = angular.copy(cubeConfig.chartOptions); @@ -136,7 +136,7 @@ KylinApp.controller('CubeCtrl', function ($scope, $rootScope, AccessService, Mes console.error('current cuboid error', e.data); }); } else { - $scope.createChart(cube.currentCuboids, 'current'); + $scope.createPlannerChart(cube.currentCuboids, 'current'); } }; @@ -151,7 +151,7 @@ KylinApp.controller('CubeCtrl', function ($scope, $rootScope, AccessService, Mes if (data.nodeInfos.length === 1 && !data.nodeInfos[0].cuboid_id) { SweetAlert.swal('Loading', 'Please wait a minute, servers are recommending for you', 'success'); } else { - $scope.createChart(data, 'recommend'); + $scope.createPlannerChart(data, 'recommend'); cube.recommendCuboids = data; // update current chart mark delete node gray. angular.forEach(cube.currentCuboids.nodeInfos, function(nodeInfo) { @@ -160,7 +160,7 @@ KylinApp.controller('CubeCtrl', function ($scope, $rootScope, AccessService, Mes nodeInfo.deleted = true; } }); - $scope.createChart(cube.currentCuboids, 'current'); + $scope.createPlannerChart(cube.currentCuboids, 'current'); $scope.currentChart.api.refresh(); } } else { @@ -173,7 +173,7 @@ KylinApp.controller('CubeCtrl', function ($scope, $rootScope, AccessService, Mes console.error('recommend cuboid error', e.data); }); } else { - $scope.createChart(cube.recommendCuboids, 'recommend'); + $scope.createPlannerChart(cube.recommendCuboids, 'recommend'); } }; @@ -219,11 +219,11 @@ KylinApp.controller('CubeCtrl', function ($scope, $rootScope, AccessService, Mes }; // transform chart data and customized options. - $scope.createChart = function(data, type) { + $scope.createPlannerChart = function(data, type) { var chartData = data.treeNode; if ('current' === type) { $scope.currentData = [chartData]; - $scope.currentOptions = angular.copy(cubeConfig.baseChartOptions); + $scope.currentOptions = angular.copy(cubeConfig.basePlannerChartOptions); $scope.currentOptions.caption = angular.copy(cubeConfig.currentCaption); if ($scope.cube.recommendCuboids){ $scope.currentOptions.caption.css['text-align'] = 'right'; @@ -242,7 +242,7 @@ KylinApp.controller('CubeCtrl', function ($scope, $rootScope, AccessService, Mes $scope.currentOptions.subtitle.text = '[Cuboid Count: ' + data.nodeInfos.length + '] [Row Count: ' + data.totalRowCount + ']'; } else if ('recommend' === type) { $scope.recommendData = [chartData]; - $scope.recommendOptions = angular.copy(cubeConfig.baseChartOptions); + $scope.recommendOptions = angular.copy(cubeConfig.basePlannerChartOptions); $scope.recommendOptions.caption = angular.copy(cubeConfig.recommendCaption); $scope.recommendOptions.chart.color = function(d) { var cuboid = _.find(data.nodeInfos, function(o) { return o.name == d; }); @@ -290,6 +290,47 @@ KylinApp.controller('CubeCtrl', function ($scope, $rootScope, AccessService, Mes } } + // click trend tab to get trend chart + $scope.getOptimizationTrend = function(cube) { + $scope.optimizationTrendData = []; + $scope.optimizationTrendOptions = angular.copy(cubeConfig.baseOptimizationTrendChartOptions); + CubeService.getOptimizationTrend({cubeId: cube.name}, function (trendData) { + var i; + if (trendData.trendOfQueryLatency.length) { + var queryLatency = { + key: 'Query Latency' + }; + var queryLatencyValues = []; + for (i = 0; i < trendData.trendOfQueryLatency.length; i++) { + queryLatencyValues.push({ + x: (new Date(trendData.timeSequence[i])).getTime(), + y: trendData.trendOfQueryLatency[i] + }); + } + queryLatency.values = _.sortBy(queryLatencyValues, 'x'); + $scope.optimizationTrendData.push(queryLatency); + } + if (trendData.trendOfStorageUsage.length) { + var storageUsage = { + key: 'Storage Usage', + bar: true + }; + var storageUsageValues = []; + for (i = 0; i < trendData.trendOfStorageUsage.length; i++) { + storageUsageValues.push({ + x: (new Date(trendData.timeSequence[i])).getTime(), + y: trendData.trendOfStorageUsage[i] + }); + } + storageUsage.values = _.sortBy(storageUsageValues, 'x'); + $scope.optimizationTrendData.push(storageUsage); + } + }, function (e) { + SweetAlert.swal('Oops...', 'Failed to get trend data', 'error'); + console.error('get trend error', e.data); + }); + }; + // streaming cube status $scope.getStreamingInfo = function(cube) { AdminStreamingService.getCubeRealTimeStats({cubeName: cube.name}, function(data){ diff --git a/webapp/app/js/controllers/dashboard.js b/webapp/app/js/controllers/dashboard.js index f27ceb0..9d123b5 100644 --- a/webapp/app/js/controllers/dashboard.js +++ b/webapp/app/js/controllers/dashboard.js @@ -255,6 +255,18 @@ KylinApp.controller('DashboardCtrl', function ($scope, $location, storage, kylin $scope.createCharts(); }; + // Click job expansion rate + $scope.jobExpansionChart = function() { + $scope.currentSquare = 'jobExpansionRate'; + $scope.barchartCategory = dashboardConfig.categories[1]; + $scope.barchartMetric = dashboardConfig.metrics[4]; + $scope.linechartCategory = dashboardConfig.categories[1]; + $scope.linechartMetric = dashboardConfig.metrics[4]; + + $scope.removeChart('all'); + $scope.createCharts(); + }; + // Line chart granularity change. $scope.changeDimensionFilter = function(chartType) { if (chartType === 'line') { diff --git a/webapp/app/js/model/cubeConfig.js b/webapp/app/js/model/cubeConfig.js index 9dc9a68..43e8fed 100644 --- a/webapp/app/js/model/cubeConfig.js +++ b/webapp/app/js/model/cubeConfig.js @@ -128,7 +128,7 @@ KylinApp.constant('cubeConfig', { {name: 'Meta Store', value: 'metaStore'}, {name: 'HBase', value: 'hbase'} ], - baseChartOptions: { + basePlannerChartOptions: { chart: { type: 'sunburstChart', height: 500, @@ -197,5 +197,48 @@ KylinApp.constant('cubeConfig', { 'text-align': 'left', 'left': '93px' } + }, + baseOptimizationTrendChartOptions: { + chart: { + type: 'linePlusBarChart', + height: 600, + duration: 500, + focusEnable: false, + margin : { + top: 60, + right: 100, + bottom: 60, + left: 100 + }, + xAxis: { + axisLabel: "Date", + tickFormat: function(d) { + return d3.time.format('%Y-%m-%d %H:%M:%S')(new Date(d)); + }, + }, + y1Axis: { + axisLabel: 'Expansion Rate', + tickFormat: function(d) { + return d3.format('.2f')(d); + }, + showMaxMin: false + }, + y2Axis: { + axisLabel: 'Query Latency (ms)', + tickFormat: function(d) { + if (d < 1000) { + if (parseFloat(d) === d) { + return d3.format('.1')(d); + } else { + return d3.format('.2f')(d); + } + } else { + var prefix = d3.formatPrefix(d); + return prefix.scale(d) + prefix.symbol; + } + }, + showMaxMin: false + }, + } } }); diff --git a/webapp/app/js/model/dashboardConfig.js b/webapp/app/js/model/dashboardConfig.js index 1620d1d..21c1f85 100644 --- a/webapp/app/js/model/dashboardConfig.js +++ b/webapp/app/js/model/dashboardConfig.js @@ -26,7 +26,8 @@ KylinApp.constant('dashboardConfig', { {name: 'query count', value: 'QUERY_COUNT'}, {name: 'avg query latency', value: 'AVG_QUERY_LATENCY'}, {name: 'job count', value: 'JOB_COUNT'}, - {name: 'avg build time', value: 'AVG_JOB_BUILD_TIME'} + {name: 'avg build time', value: 'AVG_JOB_BUILD_TIME'}, + {name: 'avg expansion rate', value: 'EXPANSION_RATE'} ], dimensions: [ {name: 'project', value: 'PROJECT'}, @@ -93,4 +94,4 @@ KylinApp.constant('dashboardConfig', { } } } -}); \ No newline at end of file +}); diff --git a/webapp/app/js/services/cubes.js b/webapp/app/js/services/cubes.js index 13de034..f625d67 100644 --- a/webapp/app/js/services/cubes.js +++ b/webapp/app/js/services/cubes.js @@ -95,6 +95,7 @@ KylinApp.factory('CubeService', ['$resource', function ($resource, config) { checkDuplicateCubeName: {method: 'GET', params: {action: 'validate'}, isArray: false}, migrate: {method: 'PUT', params: {action: 'migrateRequest'}, isArray: false}, approve: {method: 'PUT', params: {action: 'migrateApprove'}, isArray: false}, - reject: {method: 'PUT', params: {action: 'migrateReject'}, isArray: false} + reject: {method: 'PUT', params: {action: 'migrateReject'}, isArray: false}, + getOptimizationTrend: {method: 'GET', params: {propName: 'optimization', action: 'trend'}, isArray: false} }); }]); diff --git a/webapp/app/js/services/dashboard.js b/webapp/app/js/services/dashboard.js index 4340119..be39bea 100644 --- a/webapp/app/js/services/dashboard.js +++ b/webapp/app/js/services/dashboard.js @@ -57,7 +57,7 @@ KylinApp.factory('DashboardService', ['$resource', '$location', function ($resou var data = response.data; var jobMetrics; if (data) { - jobMetrics = {jobCount: data.jobCount, buildingTime: {avg: data.avgJobBuildTime*1024*1024/1000, max: data.maxJobBuildTime*1024*1024/1000, min: data.minJobBuildTime*1024*1024/1000}}; + jobMetrics = {jobCount: data.jobCount, buildingTime: {avg: data.avgJobBuildTime*1024*1024/1000, max: data.maxJobBuildTime*1024*1024/1000, min: data.minJobBuildTime*1024*1024/1000}, expansionRate: data.avgJobExpansionRate}; } return jobMetrics; } diff --git a/webapp/app/less/app.less b/webapp/app/less/app.less index 1b62294..708d430 100644 --- a/webapp/app/less/app.less +++ b/webapp/app/less/app.less @@ -878,13 +878,13 @@ pre { .square { border: 2px solid #ddd; text-align: center; - width: 215px; + width: 100%; height: 170px; cursor: zoom-in; padding-top: 15px; padding-bottom: 15px; .title { - font-size: 22px; + font-size: 18px; height: 60px; } .metric { @@ -1170,4 +1170,4 @@ tags-input .tags .tag-item { } .pagination{ cursor: pointer; -} \ No newline at end of file +} diff --git a/webapp/app/partials/cubes/cube_detail.html b/webapp/app/partials/cubes/cube_detail.html index b747e84..fec8f17 100755 --- a/webapp/app/partials/cubes/cube_detail.html +++ b/webapp/app/partials/cubes/cube_detail.html @@ -44,6 +44,9 @@ <li class="{{cube.visiblePage=='planner'? 'active':''}}" ng-if="(userService.hasRole('ROLE_ADMIN') || hasPermission('cube', cube, permissions.ADMINISTRATION.mask) && !newAccess) && isShowCubeplanner"> <a href="" ng-click="cube.visiblePage='planner';getCubePlanner(cube);">Planner</a> </li> + <li class="{{cube.visiblePage=='trend'? 'active':''}}" ng-if="userService.hasRole('ROLE_ADMIN') || hasPermission(cube, permissions.ADMINISTRATION.mask)"> + <a href="" ng-click="cube.visiblePage='trend';getOptimizationTrend(cube);">Trend</a> + </li> <li class="{{cube.visiblePage=='streaming'? 'active':''}}" ng-if="(userService.hasRole('ROLE_ADMIN') || hasPermission('cube', cube, permissions.ADMINISTRATION.mask) && !newAccess) && cube.streamingV2"> <a href="" ng-click="cube.visiblePage='streaming';getStreamingInfo(cube)">Streaming</a> </li> @@ -176,6 +179,21 @@ </div> </div> + <div class="cube-detail" ng-if="cube.visiblePage=='trend'"> + <div style="margin: 15px;"> + <div ng-if="cube.cuboid_optimized_timestamp_serial.length"> + <div class="row"> + <div class="col-md-12"> + <nvd3 options="optimizationTrendOptions" data="optimizationTrendData"></nvd3> + </div> + </div> + </div> + <div ng-if="cube.cuboid_optimized_timestamp_serial.length == 0"> + <h5>No Optimization Trend Info.</h5> + </div> + </div> + </div> + <div class="cube-detail" ng-if="cube.visiblePage=='streaming'"> <div style="padding: 15px; color: #212121;"> <div class="row" ng-repeat="(rsId, replicaSet) in replicaSets" style="padding-left: 10px; padding-right: 10px;"> diff --git a/webapp/app/partials/dashboard/dashboard.html b/webapp/app/partials/dashboard/dashboard.html index 0d46098..3804509 100644 --- a/webapp/app/partials/dashboard/dashboard.html +++ b/webapp/app/partials/dashboard/dashboard.html @@ -62,7 +62,7 @@ </div> <div class="col-sm-10"> <div class="row"> - <div class="col-sm-3"> + <div class="col-sm-2" style="width:20%"> <div class="square" ng-class="{'square-active': currentSquare ==='queryCount'}" ng-click="queryCountChart()"> <div class="title"> QUERY<br/>COUNT @@ -78,7 +78,7 @@ </a> </div> </div> - <div class="col-sm-3"> + <div class="col-sm-2" style="width:20%"> <div class="square" ng-class="{'square-active': currentSquare ==='queryAvg'}" tooltip-placement="bottom" tooltip="Max: {{queryMetrics.queryLatency.max ? (queryMetrics.queryLatency.max | number:2) : '--'}} sec | Min: {{queryMetrics.queryLatency.min ? (queryMetrics.queryLatency.min| number:2) : '--'}} sec" ng-click="queryAvgChart()"> <div class="title"> AVG QUERY LATENCY @@ -94,7 +94,7 @@ </a> </div> </div> - <div class="col-sm-3"> + <div class="col-sm-2" style="width:20%"> <div class="square" ng-class="{'square-active': currentSquare ==='jobCount'}" ng-click="jobCountChart()"> <div class="title"> JOB<br/>COUNT @@ -110,7 +110,7 @@ </a> </div> </div> - <div class="col-sm-3" > + <div class="col-sm-2" style="width:20%" > <div class="square" ng-class="{'square-active': currentSquare ==='jobBuildTime'}" tooltip-placement="bottom" tooltip="Max: {{jobMetrics.buildingTime.max ? (jobMetrics.buildingTime.max | number:2) : '--'}} sec | Min: {{jobMetrics.buildingTime.min ? ( jobMetrics.buildingTime.min | number:2) : '--'}} sec" ng-click="jobBuildTimeChart()"> <div class="title"> AVG BUILD TIME PER MB @@ -126,11 +126,27 @@ </a> </div> </div> + <div class="col-sm-2" style="width:20%"> + <div class="square" ng-class="{'square-active': currentSquare ==='jobExpansionRate'}" ng-click="jobExpansionChart()"> + <div class="title"> + AVG BUILD EXPANSION RATE + </div> + <div class="metric" ng-if="jobMetrics.expansionRate || jobMetrics.expansionRate === 0"> + {{jobMetrics.expansionRate | number:2}}<span class="unit"> sec</span> + </div> + <div class="metric" ng-if="!jobMetrics.expansionRate && (jobMetrics.expansionRate !== 0)"> + -- + </div> + <a class="description" ng-href="jobs" ng-click="$event.stopPropagation();"> + More Details + </a> + </div> + </div> </div> <div class="row charts"> <div class="col-sm-6" ng-if="barChart"> <div style="border: 2px solid #ddd; margin-top:15px;"> - <div class="form-group" style="width: 96px; position: absolute; right: 15px; bottom: 265px;"> + <div class="form-group" style="width: 95px; position: absolute; right: 20px; bottom: 265px;"> Show Value: <input type="checkbox" ng-model="barChart.options.chart.showValues"> </div> <nvd3 options="barChart.options" data="barChart.data"></nvd3>