You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kylin.apache.org by li...@apache.org on 2017/11/23 05:53:05 UTC
[03/18] kylin git commit: APACHE-KYLIN-2726 Introduce a dashboard for
showing kylin service related metrics, like query count, query latency,
job count, etc
APACHE-KYLIN-2726 Introduce a dashboard for showing kylin service related metrics, like query count, query latency, job count, etc
Project: http://git-wip-us.apache.org/repos/asf/kylin/repo
Commit: http://git-wip-us.apache.org/repos/asf/kylin/commit/d608fc10
Tree: http://git-wip-us.apache.org/repos/asf/kylin/tree/d608fc10
Diff: http://git-wip-us.apache.org/repos/asf/kylin/diff/d608fc10
Branch: refs/heads/ci-dong
Commit: d608fc10822c82e56fc5bd464c975716d4edd5f9
Parents: f7d265b
Author: liapan <li...@ebay.com>
Authored: Fri Nov 17 14:56:12 2017 +0800
Committer: lidongsjtu <li...@apache.org>
Committed: Thu Nov 23 13:31:34 2017 +0800
----------------------------------------------------------------------
.../rest/controller/DashboardController.java | 129 +++++++
.../apache/kylin/rest/service/BasicService.java | 5 +
.../kylin/rest/service/DashboardService.java | 333 +++++++++++++++++++
.../apache/kylin/rest/service/QueryService.java | 8 +
webapp/app/index.html | 8 +-
webapp/app/js/controllers/dashboard.js | 296 +++++++++++++++++
webapp/app/js/controllers/job.js | 3 +
webapp/app/js/controllers/query.js | 3 +
webapp/app/js/directives/directives.js | 97 ++++++
webapp/app/js/filters/filter.js | 9 +
webapp/app/js/model/dashboardConfig.js | 96 ++++++
webapp/app/js/services/dashboard.js | 95 ++++++
webapp/app/less/app.less | 58 ++++
webapp/app/partials/dashboard/dashboard.html | 151 +++++++++
webapp/app/partials/header.html | 3 +
webapp/app/routes.json | 8 +
webapp/bower.json | 8 +-
webapp/grunt.json | 3 +
18 files changed, 1309 insertions(+), 4 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/kylin/blob/d608fc10/server-base/src/main/java/org/apache/kylin/rest/controller/DashboardController.java
----------------------------------------------------------------------
diff --git a/server-base/src/main/java/org/apache/kylin/rest/controller/DashboardController.java b/server-base/src/main/java/org/apache/kylin/rest/controller/DashboardController.java
new file mode 100644
index 0000000..35ba615
--- /dev/null
+++ b/server-base/src/main/java/org/apache/kylin/rest/controller/DashboardController.java
@@ -0,0 +1,129 @@
+/*
+ * 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.
+*/
+
+
+package org.apache.kylin.rest.controller;
+
+import org.apache.kylin.cube.CubeInstance;
+import org.apache.kylin.metadata.project.ProjectInstance;
+import org.apache.kylin.metrics.MetricsManager;
+import org.apache.kylin.rest.request.SQLRequest;
+import org.apache.kylin.rest.response.MetricsResponse;
+import org.apache.kylin.rest.response.SQLResponse;
+import org.apache.kylin.rest.service.CubeService;
+import org.apache.kylin.rest.service.DashboardService;
+import org.apache.kylin.rest.service.QueryService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import java.util.List;
+
+@Controller
+@RequestMapping(value = "/dashboard")
+public class DashboardController extends BasicController {
+ private static final Logger logger = LoggerFactory.getLogger(DashboardController.class);
+
+ @Autowired
+ private DashboardService dashboardService;
+
+ @Autowired
+ private QueryService queryService;
+
+ @Autowired
+ private CubeService cubeService;
+
+ @RequestMapping(value = "/metric/cube", method = { RequestMethod.GET })
+ @ResponseBody
+ public MetricsResponse getCubeMetrics(@RequestParam(value = "projectName", required = false) String projectName, @RequestParam(value = "cubeName", required = false) String cubeName) {
+ checkAuthorization(projectName);
+ return dashboardService.getCubeMetrics(projectName, cubeName);
+ }
+
+ @RequestMapping(value = "/metric/query", method = RequestMethod.GET)
+ @ResponseBody
+ public MetricsResponse getQueryMetrics(@RequestParam(value = "projectName", required = false) String projectName, @RequestParam(value = "cubeName", required = false) String cubeName, @RequestParam(value = "startTime") String startTime, @RequestParam(value = "endTime") String endTime) {
+ checkAuthorization(projectName);
+ MetricsResponse queryMetrics = new MetricsResponse();
+ SQLRequest sqlRequest = new SQLRequest();
+ sqlRequest.setProject(MetricsManager.SYSTEM_PROJECT);
+ String sql = dashboardService.getQueryMetricsSQL(startTime, endTime, projectName, cubeName);
+ sqlRequest.setSql(sql);
+ SQLResponse sqlResponse = queryService.queryWithoutSecure(sqlRequest);
+ if(!sqlResponse.getIsException()){
+ queryMetrics.increase("queryCount", dashboardService.getMetricValue(sqlResponse.getResults().get(0).get(0)));
+ queryMetrics.increase("avgQueryLatency", dashboardService.getMetricValue(sqlResponse.getResults().get(0).get(1)));
+ queryMetrics.increase("maxQueryLatency", dashboardService.getMetricValue(sqlResponse.getResults().get(0).get(2)));
+ queryMetrics.increase("minQueryLatency", dashboardService.getMetricValue(sqlResponse.getResults().get(0).get(3)));
+ }
+ return queryMetrics;
+ }
+
+ @RequestMapping(value = "/metric/job", method = RequestMethod.GET)
+ @ResponseBody
+ public MetricsResponse getJobMetrics(@RequestParam(value = "projectName", required = false) String projectName, @RequestParam(value = "cubeName", required = false) String cubeName, @RequestParam(value = "startTime") String startTime, @RequestParam(value = "endTime") String endTime) {
+ checkAuthorization(projectName);
+ MetricsResponse jobMetrics = new MetricsResponse();
+ SQLRequest sqlRequest = new SQLRequest();
+ sqlRequest.setProject(MetricsManager.SYSTEM_PROJECT);
+ String sql = dashboardService.getJobMetricsSQL(startTime, endTime, projectName, cubeName);
+ sqlRequest.setSql(sql);
+ SQLResponse sqlResponse = queryService.queryWithoutSecure(sqlRequest);
+ if(!sqlResponse.getIsException()){
+ jobMetrics.increase("jobCount", dashboardService.getMetricValue(sqlResponse.getResults().get(0).get(0)));
+ jobMetrics.increase("avgJobBuildTime", dashboardService.getMetricValue(sqlResponse.getResults().get(0).get(1)));
+ jobMetrics.increase("maxJobBuildTime", dashboardService.getMetricValue(sqlResponse.getResults().get(0).get(2)));
+ jobMetrics.increase("minJobBuildTime", dashboardService.getMetricValue(sqlResponse.getResults().get(0).get(3)));
+ }
+ return jobMetrics;
+ }
+
+ @RequestMapping(value = "/chart/{category}/{metric}/{dimension}", method = RequestMethod.GET)
+ @ResponseBody
+ public MetricsResponse getChartData(@PathVariable String dimension, @PathVariable String metric, @PathVariable String category, @RequestParam(value = "projectName", required = false) String projectName, @RequestParam(value = "cubeName", required = false) String cubeName, @RequestParam(value = "startTime") String startTime, @RequestParam(value = "endTime") String endTime) {
+ checkAuthorization(projectName);
+ SQLRequest sqlRequest = new SQLRequest();
+ sqlRequest.setProject(MetricsManager.SYSTEM_PROJECT);
+ String sql = dashboardService.getChartSQL(startTime, endTime, projectName, cubeName, dimension, metric, category);
+ sqlRequest.setSql(sql);
+ return dashboardService.transformChartData(queryService.queryWithoutSecure(sqlRequest));
+ }
+
+ private void checkAuthorization(String projectName){
+ if (projectName!=null && !projectName.isEmpty()) {
+ ProjectInstance project = dashboardService.getProjectManager().getProject(projectName);
+ try {
+ dashboardService.checkAuthorization(project);
+ } catch (AccessDeniedException e) {
+ List<CubeInstance> cubes = cubeService.listAllCubes(null, projectName, null, true);
+ if (cubes.isEmpty()) {
+ throw new AccessDeniedException("Access is denied");
+ }
+ }
+ } else {
+ dashboardService.checkAuthorization();
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/kylin/blob/d608fc10/server-base/src/main/java/org/apache/kylin/rest/service/BasicService.java
----------------------------------------------------------------------
diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/BasicService.java b/server-base/src/main/java/org/apache/kylin/rest/service/BasicService.java
index 0e9ee7a..f8f50f3 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/service/BasicService.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/service/BasicService.java
@@ -31,6 +31,7 @@ import org.apache.kylin.metadata.draft.DraftManager;
import org.apache.kylin.metadata.model.DataModelManager;
import org.apache.kylin.metadata.project.ProjectManager;
import org.apache.kylin.metadata.streaming.StreamingManager;
+import org.apache.kylin.metrics.MetricsManager;
import org.apache.kylin.source.kafka.KafkaConfigManager;
import org.apache.kylin.storage.hybrid.HybridManager;
@@ -94,4 +95,8 @@ public abstract class BasicService {
return TableACLManager.getInstance(getConfig());
}
+ public MetricsManager getMetricsManager() {
+ return MetricsManager.getInstance();
+ }
+
}
http://git-wip-us.apache.org/repos/asf/kylin/blob/d608fc10/server-base/src/main/java/org/apache/kylin/rest/service/DashboardService.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..f1084f3
--- /dev/null
+++ b/server-base/src/main/java/org/apache/kylin/rest/service/DashboardService.java
@@ -0,0 +1,333 @@
+/*
+ * 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.
+*/
+
+
+package org.apache.kylin.rest.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.google.common.collect.Lists;
+import org.apache.kylin.cube.CubeInstance;
+import org.apache.kylin.metadata.project.ProjectInstance;
+import org.apache.kylin.metadata.project.RealizationEntry;
+import org.apache.kylin.metadata.realization.RealizationType;
+import org.apache.kylin.metrics.MetricsManager;
+import org.apache.kylin.metrics.lib.impl.TimePropertyEnum;
+import org.apache.kylin.metrics.property.JobPropertyEnum;
+import org.apache.kylin.metrics.property.QueryPropertyEnum;
+import org.apache.kylin.rest.constant.Constant;
+import org.apache.kylin.rest.exception.BadRequestException;
+import org.apache.kylin.rest.response.MetricsResponse;
+import org.apache.kylin.rest.response.SQLResponse;
+import org.apache.kylin.storage.hybrid.HybridInstance;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+
+import com.google.common.base.Strings;
+
+@Component("dashboardService")
+public class DashboardService extends BasicService {
+
+ private static final Logger logger = LoggerFactory.getLogger(DashboardService.class);
+
+ @Autowired
+ private CubeService cubeService;
+
+ private enum CategoryEnum {QUERY, JOB}
+
+ private enum QueryDimensionEnum {
+ PROJECT(QueryPropertyEnum.PROJECT.toString()),
+ CUBE(QueryPropertyEnum.REALIZATION.toString()),
+ DAY(TimePropertyEnum.DAY_DATE.toString()),
+ WEEK(TimePropertyEnum.WEEK_BEGIN_DATE.toString()),
+ MONTH(TimePropertyEnum.MONTH.toString());
+ private final String sql;
+
+ QueryDimensionEnum(String sql) {
+ this.sql = sql;
+ }
+
+ public String toSQL() {
+ return this.sql;
+ }
+ };
+
+ private enum JobDimensionEnum {
+ PROJECT(JobPropertyEnum.PROJECT.toString()),
+ CUBE(JobPropertyEnum.CUBE.toString()),
+ DAY(TimePropertyEnum.DAY_DATE.toString()),
+ WEEK(TimePropertyEnum.WEEK_BEGIN_DATE.toString()),
+ MONTH(TimePropertyEnum.MONTH.toString());
+ private final String sql;
+
+ JobDimensionEnum(String sql) {
+ this.sql = sql;
+ }
+
+ public String toSQL() {
+ return this.sql;
+ }
+ };
+
+ private enum QueryMetricEnum {
+ QUERY_COUNT("count(*)"),
+ AVG_QUERY_LATENCY("sum(" + QueryPropertyEnum.TIME_COST.toString() + ")/(count(" + QueryPropertyEnum.TIME_COST.toString() + "))"),
+ MAX_QUERY_LATENCY("max(" + QueryPropertyEnum.TIME_COST.toString() + ")"),
+ MIN_QUERY_LATENCY("min(" + QueryPropertyEnum.TIME_COST.toString() + ")");
+
+ private final String sql;
+
+ QueryMetricEnum(String sql) {
+ this.sql = sql;
+ }
+
+ public String toSQL() {
+ return this.sql;
+ }
+ }
+
+ private enum JobMetricEnum {
+ JOB_COUNT("count(*)"),
+ AVG_JOB_BUILD_TIME("sum(" + JobPropertyEnum.PER_BYTES_TIME_COST.toString() + ")/count(" + JobPropertyEnum.PER_BYTES_TIME_COST + ")"),
+ MAX_JOB_BUILD_TIME("max(" + JobPropertyEnum.PER_BYTES_TIME_COST.toString() + ")"),
+ MIN_JOB_BUILD_TIME("min(" + JobPropertyEnum.PER_BYTES_TIME_COST.toString() + ")");
+
+ private final String sql;
+
+ JobMetricEnum(String sql) {
+ this.sql = sql;
+ }
+
+ public String toSQL() {
+ return this.sql;
+ }
+ }
+
+ public MetricsResponse getCubeMetrics(String projectName, String cubeName) {
+ MetricsResponse cubeMetrics = new MetricsResponse();
+ Float totalCubeSize = 0f;
+ long totalRecoadSize = 0;
+ List<CubeInstance> cubeInstances = cubeService.listAllCubes(cubeName, projectName, null, true);
+ Integer totalCube = cubeInstances.size();
+ if (projectName == null) {
+ totalCube += getHybridManager().listHybridInstances().size();
+ } else {
+ ProjectInstance project = getProjectManager().getProject(projectName);
+ totalCube += project.getRealizationCount(RealizationType.HYBRID);
+ }
+ Float minCubeExpansion = Float.POSITIVE_INFINITY;
+ Float maxCubeExpansion = Float.NEGATIVE_INFINITY;
+ cubeMetrics.increase("totalCube", totalCube.floatValue());
+ for (CubeInstance cubeInstance : cubeInstances) {
+ if (cubeInstance.getInputRecordSize() > 0) {
+ totalCubeSize += cubeInstance.getSizeKB();
+ totalRecoadSize += cubeInstance.getInputRecordSize();
+ Float cubeExpansion = new Float(cubeInstance.getSizeKB()) * 1024 / cubeInstance.getInputRecordSize();
+ if (cubeExpansion > maxCubeExpansion) {
+ maxCubeExpansion = cubeExpansion;
+ }
+ if (cubeExpansion < minCubeExpansion) {
+ minCubeExpansion = cubeExpansion;
+ }
+ }
+ }
+ Float avgCubeExpansion = 0f;
+ if (totalRecoadSize != 0) {
+ avgCubeExpansion = totalCubeSize * 1024 / totalRecoadSize;
+ }
+ cubeMetrics.increase("avgCubeExpansion", avgCubeExpansion);
+ cubeMetrics.increase("maxCubeExpansion", maxCubeExpansion == Float.NEGATIVE_INFINITY ? 0 : maxCubeExpansion);
+ cubeMetrics.increase("minCubeExpansion", minCubeExpansion == Float.POSITIVE_INFINITY ? 0 : minCubeExpansion);
+ return cubeMetrics;
+ }
+
+ private List<CubeInstance> getCubeByHybrid(HybridInstance hybridInstance) {
+ List<CubeInstance> cubeInstances = Lists.newArrayList();
+ List<RealizationEntry> realizationEntries = hybridInstance.getRealizationEntries();
+ for (RealizationEntry realizationEntry : realizationEntries) {
+ String reName = realizationEntry.getRealization();
+ if (RealizationType.CUBE == realizationEntry.getType()) {
+ CubeInstance cubeInstance = getCubeManager().getCube(reName);
+ cubeInstances.add(cubeInstance);
+ } else if (RealizationType.HYBRID == realizationEntry.getType()) {
+ HybridInstance innerHybridInstance = getHybridManager().getHybridInstance(reName);
+ cubeInstances.addAll(getCubeByHybrid(innerHybridInstance));
+ }
+ }
+ return cubeInstances;
+ }
+
+ public String getQueryMetricsSQL(String startTime, String endTime, String projectName, String cubeName) {
+ String[] metrics = new String[] {QueryMetricEnum.QUERY_COUNT.toSQL(), QueryMetricEnum.AVG_QUERY_LATENCY.toSQL(), QueryMetricEnum.MAX_QUERY_LATENCY.toSQL(), QueryMetricEnum.MIN_QUERY_LATENCY.toSQL()};
+ List<String> filters = getBaseFilters(CategoryEnum.QUERY, projectName, startTime, endTime);
+ filters = addCubeFilter(filters, CategoryEnum.QUERY, cubeName);
+ return createSql(null, metrics, getMetricsManager().getSystemTableFromSubject(getConfig().getKylinMetricsSubjectQuery()), filters.toArray(new String[filters.size()]));
+ }
+
+ public String getJobMetricsSQL(String startTime, String endTime, String projectName, String cubeName) {
+ String[] metrics = new String[] {JobMetricEnum.JOB_COUNT.toSQL(), JobMetricEnum.AVG_JOB_BUILD_TIME.toSQL(), JobMetricEnum.MAX_JOB_BUILD_TIME.toSQL(), JobMetricEnum.MIN_JOB_BUILD_TIME.toSQL()};
+ List<String> filters = getBaseFilters(CategoryEnum.JOB, projectName, startTime, endTime);
+ filters = addCubeFilter(filters, CategoryEnum.JOB, cubeName);
+ return createSql(null, metrics, getMetricsManager().getSystemTableFromSubject(getConfig().getKylinMetricsSubjectJob()), filters.toArray(new String[filters.size()]));
+ }
+
+ public String getChartSQL(String startTime, String endTime, String projectName, String cubeName, String dimension, String metric, String category) {
+ try{
+ CategoryEnum categoryEnum = CategoryEnum.valueOf(category);
+ String table = "";
+ String[] dimensionSQL = null;
+ String[] metricSQL = null;
+
+ if(categoryEnum == CategoryEnum.QUERY) {
+ dimensionSQL = new String[] {QueryDimensionEnum.valueOf(dimension).toSQL()};
+ metricSQL = new String[] {QueryMetricEnum.valueOf(metric).toSQL()};
+ table = getMetricsManager().getSystemTableFromSubject(getConfig().getKylinMetricsSubjectQuery());
+ } else if (categoryEnum == CategoryEnum.JOB) {
+ dimensionSQL = new String[] {JobDimensionEnum.valueOf(dimension).toSQL()};
+ metricSQL = new String[] {JobMetricEnum.valueOf(metric).toSQL()};
+ table = getMetricsManager().getSystemTableFromSubject(getConfig().getKylinMetricsSubjectJob());
+ }
+
+ List<String> filters = getBaseFilters(categoryEnum, projectName, startTime, endTime);
+ filters = addCubeFilter(filters, categoryEnum, cubeName);
+
+ return createSql(dimensionSQL, metricSQL, table, filters.toArray(new String[filters.size()]));
+ } catch (IllegalArgumentException e) {
+ String message = "Generate dashboard chart sql failed. Please double check the input parameter: dimension, metric or category.";
+ logger.error(message, e);
+ throw new BadRequestException(message + " Caused by: " + e.getMessage(), null, e.getCause());
+ }
+ }
+
+ public MetricsResponse transformChartData(SQLResponse sqlResponse) {
+ if(!sqlResponse.getIsException()){
+ MetricsResponse metrics = new MetricsResponse();
+ List<List<String>> results = sqlResponse.getResults();
+ for (List<String> result : results) {
+ String dimension = result.get(0);
+ if (dimension !=null && !dimension.isEmpty()) {
+ String metric = result.get(1);
+ metrics.increase(dimension, getMetricValue(metric));
+ }
+ }
+ return metrics;
+ }
+ return null;
+ }
+
+ public Float getMetricValue(String value) {
+ if (value == null || value.isEmpty()) {
+ return 0f;
+ } else {
+ return Float.valueOf(value);
+ }
+ }
+
+ @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN + " or hasPermission(#project, 'ADMINISTRATION')")
+ public void checkAuthorization(ProjectInstance project) throws AccessDeniedException {
+ }
+
+ @PreAuthorize(Constant.ACCESS_HAS_ROLE_ADMIN)
+ public void checkAuthorization() throws AccessDeniedException{
+ }
+
+ private List<String> getBaseFilters(CategoryEnum category, String projectName, String startTime, String endTime) {
+ List<String> filters = new ArrayList<String>();
+ String project = "";
+ if (category == CategoryEnum.QUERY) {
+ project = QueryDimensionEnum.PROJECT.toSQL();
+ } else {
+ project = JobDimensionEnum.PROJECT.toSQL();
+ }
+ filters.add(TimePropertyEnum.DAY_DATE.toString() + " >= '" + startTime + "'");
+ filters.add(TimePropertyEnum.DAY_DATE.toString() + " <= '" + endTime + "'");
+ if (!Strings.isNullOrEmpty(projectName)) {
+ filters.add(project + " ='" + ProjectInstance.getNormalizedProjectName(projectName) + "'");
+ } else {
+ filters.add(project + " <> '" + MetricsManager.SYSTEM_PROJECT + "'");
+ }
+ return filters;
+ }
+
+ private List<String> addCubeFilter(List<String> baseFilter, CategoryEnum category, String cubeName) {
+ if (category == CategoryEnum.QUERY) {
+ baseFilter.add(QueryPropertyEnum.EXCEPTION.toString() + " = 'NULL'");
+ if (!Strings.isNullOrEmpty(cubeName)) {
+ baseFilter.add(QueryPropertyEnum.REALIZATION + " = '" + cubeName + "'");
+ }
+ } else if (category == CategoryEnum.JOB && !Strings.isNullOrEmpty(cubeName)) {
+ HybridInstance hybridInstance = getHybridManager().getHybridInstance(cubeName);
+ if (null != hybridInstance) {
+ StringBuffer cubeNames = new StringBuffer();
+ for (CubeInstance cube:getCubeByHybrid(hybridInstance)) {
+ cubeNames.append(",'" + cube.getName() + "'");
+ }
+ baseFilter.add(JobPropertyEnum.CUBE.toString() + " IN (" + cubeNames.substring(1) + ")");
+ } else {
+ baseFilter.add(JobPropertyEnum.CUBE.toString() + " ='" + cubeName + "'");
+ }
+ }
+ return baseFilter;
+ }
+
+ private String createSql(String[] dimensions, String[] metrics, String category, String[] filters) {
+ StringBuffer baseSQL = new StringBuffer("select ");
+ StringBuffer groupBy = new StringBuffer("");
+ if (dimensions != null && dimensions.length > 0) {
+ groupBy.append(" group by ");
+ StringBuffer dimensionSQL = new StringBuffer("");
+ for (String dimension : dimensions) {
+ dimensionSQL.append(",");
+ dimensionSQL.append(dimension);
+ }
+ baseSQL.append(dimensionSQL.substring(1));
+ groupBy.append(dimensionSQL.substring(1));
+ }
+ if (metrics != null && metrics.length > 0) {
+ StringBuffer metricSQL = new StringBuffer("");
+ for (String metric : metrics) {
+ metricSQL.append(",");
+ metricSQL.append(metric);
+ }
+ if (groupBy.length() > 0) {
+ baseSQL.append(metricSQL);
+ } else {
+ baseSQL.append(metricSQL.substring(1));
+ }
+ }
+ baseSQL.append(" from ");
+ baseSQL.append(category);
+ if (filters != null && filters.length > 0) {
+ StringBuffer filterSQL = new StringBuffer(" where ");
+ filterSQL.append(filters[0]);
+ for(int i = 1; i < filters.length; i++) {
+ filterSQL.append(" and ");
+ filterSQL.append(filters[i]);
+ }
+ baseSQL.append(filterSQL.toString());
+ }
+ baseSQL.append(groupBy);
+
+ return baseSQL.toString();
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/kylin/blob/d608fc10/server-base/src/main/java/org/apache/kylin/rest/service/QueryService.java
----------------------------------------------------------------------
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 26b25d2..3f16646 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
@@ -371,7 +371,15 @@ public class QueryService extends BasicService {
}
}
+ public SQLResponse queryWithoutSecure(SQLRequest sqlRequest) {
+ return doQueryWithCache(sqlRequest, false);
+ }
+
public SQLResponse doQueryWithCache(SQLRequest sqlRequest) {
+ return doQueryWithCache(sqlRequest, true);
+ }
+
+ public SQLResponse doQueryWithCache(SQLRequest sqlRequest, boolean secureEnabled) {
Message msg = MsgPicker.getMsg();
sqlRequest.setUsername(getUserName());
http://git-wip-us.apache.org/repos/asf/kylin/blob/d608fc10/webapp/app/index.html
----------------------------------------------------------------------
diff --git a/webapp/app/index.html b/webapp/app/index.html
index d235b06..791aa4a 100644
--- a/webapp/app/index.html
+++ b/webapp/app/index.html
@@ -53,6 +53,7 @@
<link rel="stylesheet" type="text/css" href="components/angular-toggle-switch/angular-toggle-switch.css">
<link rel="stylesheet" type="text/css" href="components/angular-ui-select/dist/select.css">
<link rel="stylesheet" type="text/css" href="components/angular-bootstrap-datetimepicker/src/css/datetimepicker.css">
+ <link rel="stylesheet" type="text/css" href="components/bootstrap-daterangepicker/daterangepicker-bs3.css" />
<link rel="stylesheet/less" href="less/build.less">
<!-- endref -->
@@ -111,7 +112,9 @@
<script src="components/angular-ui-sortable/sortable.js"></script>
<script src="components/angular-toggle-switch/angular-toggle-switch.js"></script>
<script src="components/angular-sanitize/angular-sanitize.js"></script>
-<script src="components/angular-nvd3/dist/angular-nvd3.min.js"></script>
+<script src="components/angular-nvd3/dist/angular-nvd3.js"></script>
+<script src="components/moment-timezone/builds/moment-timezone-with-data.js"></script>
+<script src="components/bootstrap-daterangepicker/daterangepicker.js"></script>
<script src="js/app.js"></script>
<script src="js/config.js"></script>
@@ -145,6 +148,7 @@
<script src="js/services/ngLoading.js"></script>
<!--New GUI-->
<script src="js/services/models.js"></script>
+<script src="js/services/dashboard.js"></script>
<script src="js/model/cubeConfig.js"></script>
<script src="js/model/jobConfig.js"></script>
@@ -163,6 +167,7 @@
<script src="js/model/jobListModel.js"></script>
<script src="js/model/cubesManager.js"></script>
<script src="js/model/queryConfig.js"></script>
+<script src="js/model/dashboardConfig.js"></script>
<!--New GUI-->
<script src="js/model/modelsManager.js"></script>
@@ -202,6 +207,7 @@
<!--New GUI-->
<script src="js/controllers/models.js"></script>
+<script src="js/controllers/dashboard.js"></script>
<!-- endref -->
http://git-wip-us.apache.org/repos/asf/kylin/blob/d608fc10/webapp/app/js/controllers/dashboard.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/controllers/dashboard.js b/webapp/app/js/controllers/dashboard.js
new file mode 100644
index 0000000..3114919
--- /dev/null
+++ b/webapp/app/js/controllers/dashboard.js
@@ -0,0 +1,296 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+KylinApp.controller('DashboardCtrl', function ($scope, $location, storage, kylinConfig, dashboardConfig, DashboardService, MessageService, SweetAlert, loadingRequest, UserService, ProjectModel, $filter) {
+
+ $scope.init = function(){
+ $scope.timezone = 'GMT';
+
+ // Init date range
+ storage.bind($scope, 'dateRange', {defaultValue: {
+ startDate: moment().subtract(7, 'days').clone().tz($scope.timezone).startOf('day').format('x'),
+ endDate: moment().subtract(1, 'days').clone().tz($scope.timezone).endOf('day').format('x')
+ }, storeName: 'dashboard.dateRange'});
+
+ storage.bind($scope, 'barchartDimension', {defaultValue: dashboardConfig.dimensions[0], storeName: 'dashboard.barchart.dimension'});
+ storage.bind($scope, 'barchartCategory', {defaultValue: dashboardConfig.categories[0], storeName: 'dashboard.barchart.category'});
+ storage.bind($scope, 'barchartMetric', {defaultValue: dashboardConfig.metrics[0], storeName: 'dashboard.barchart.metric'});
+
+ storage.bind($scope, 'linechartDimension', {defaultValue: dashboardConfig.dimensions[2], storeName: 'dashboard.linechart.dimension'});
+ storage.bind($scope, 'linechartCategory', {defaultValue: dashboardConfig.categories[0], storeName: 'dashboard.linechart.category'});
+ storage.bind($scope, 'linechartMetric', {defaultValue: dashboardConfig.metrics[0], storeName: 'dashboard.linechart.metric'});
+
+ storage.bind($scope, 'currentSquare', 'queryCount');
+ $scope.initReady = true;
+ };
+
+ $scope.formatDatetime = function(dateTime) {
+ return moment(dateTime, 'x').tz($scope.timezone).format('YYYY-MM-DD');
+ };
+
+ $scope.refreshCubeMetric = function() {
+ $scope.cubeMetricsRefreshTime = moment().tz($scope.timezone).format('YYYY-MM-DD');
+ DashboardService.getCubeMetrics({projectName: ProjectModel.getSelectedProject(), cubeName: $scope.selectedCube}, {}, function (data) {
+ $scope.cubeMetrics = data;
+ }, function(e) {
+ SweetAlert.swal('Oops...', 'Failed to load cube metrics', 'error');
+ console.error('cube metrics error', e.data);
+ });
+ };
+
+ // Need to change name for this function
+ $scope.refreshOtherMetrics = function(){
+ DashboardService.getQueryMetrics({projectName: ProjectModel.getSelectedProject(), cubeName: $scope.selectedCube, startTime: $scope.formatDatetime($scope.dateRange.startDate), endTime: $scope.formatDatetime($scope.dateRange.endDate)}, {}, function (data) {
+ $scope.queryMetrics = data;
+ }, function(e) {
+ SweetAlert.swal('Oops...', 'Failed to load query metrics.', 'error');
+ console.error('query metrics error:', e.data);
+ });
+
+ DashboardService.getJobMetrics({projectName: ProjectModel.getSelectedProject(), cubeName: $scope.selectedCube, startTime: $scope.formatDatetime($scope.dateRange.startDate), endTime: $scope.formatDatetime($scope.dateRange.endDate)}, {}, function (data) {
+ $scope.jobMetrics = data;
+ }, function(e) {
+ SweetAlert.swal('Oops...', 'Failed to load job metrics.', 'error');
+ console.error('job metrics error:', e.data);
+ });
+
+ $scope.createCharts();
+ };
+
+ // Daterangepicker call back
+ $scope.changeDateRange = function(start, end) {
+ console.log("start time:", start);
+ console.log("end time:", end);
+ $scope.dateRange.startDate = start;
+ $scope.dateRange.endDate = end;
+ $scope.refreshOtherMetrics();
+ };
+
+ // Create chart option and data
+ $scope.createChart = function(dataQuery, chartType) {
+ var chartObj = {
+ queryObj: dataQuery
+ };
+
+ // get base options
+ var baseOptions = dashboardConfig.baseChartOptions;
+
+ var title = $filter('startCase')(dataQuery.metric.name) + ' by ' + $filter('startCase')(dataQuery.dimension.name);
+
+ // set title to options
+ chartObj.options = angular.copy(baseOptions);
+ chartObj.options.chart.xAxis.axisLabel = dataQuery.dimension.name;
+ chartObj.options.title.text = title;
+
+ var groupByOptions = [];
+ angular.forEach(dashboardConfig.granularityFilter, function(option) {
+ groupByOptions.push(option.value);
+ });
+ if (groupByOptions.indexOf(dataQuery.dimension.name) > -1) {
+ var formatPattern = '%Y-%m-%d';
+ if (dataQuery.dimension.name === dashboardConfig.granularityFilter[2].value) {
+ formatPattern = '%Y-%m';
+ }
+ chartObj.options.chart.xAxis.tickFormat = function (d) {
+ return d3.time.format(formatPattern)(moment.unix(d/1000).toDate());
+ };
+ chartObj.options.chart.tooltip.contentGenerator = function (d) {
+ return '<table><tr><td class="legend-color-guide"><div style="background-color: '+d.point.color+';"></div></td><td class="key">' + d3.time.format(formatPattern)(moment.unix(d.point.label/1000).toDate()) + '</td><td class="value">'+d.point.value.toFixed(2)+'</td></tr></table>';
+ };
+
+ // chartObj.options.chart.interpolate = 'cardinal';
+
+ chartObj.options.chart.legend = {
+ margin: {
+ left: 15
+ }
+ };
+
+ // Add filter for change month
+ chartObj.dimension = {};
+ chartObj.dimension.options = dashboardConfig.granularityFilter;
+ chartObj.dimension.selected = dataQuery.dimension;
+ angular.forEach(chartObj.dimension.options, function(option, ind) {
+ if (dataQuery.dimension.name.indexOf(option.value) > -1) {
+ chartObj.dimension.selected = chartObj.dimension.options[ind];
+ }
+ });
+ }
+
+ chartObj.data = [];
+
+ if (chartType === 'line') {
+ chartObj.options.chart.type = 'lineChart';
+ $scope.lineChart = chartObj;
+ DashboardService.getChartData({category: dataQuery.category, metric: dataQuery.metric.value, dimension: dataQuery.dimension.value, projectName: ProjectModel.getSelectedProject(), cubeName: $scope.selectedCube, startTime: $scope.formatDatetime($scope.dateRange.startDate), endTime: $scope.formatDatetime($scope.dateRange.endDate)}, {}, function (data) {
+ if (data.length > 6) {
+ $scope.lineChart.options.chart.xAxis.rotateLabels = -50;
+ $scope.lineChart.options.chart.xAxis.axisLabel = '';
+ }
+
+ $scope.lineChart.data = [{key: dataQuery.category, values: _.sortBy(data, 'label')}];
+ }, function(e) {
+ SweetAlert.swal('Oops...', 'Failed to load line chart.', 'error');
+ console.error('line chart error:', e.data);
+ });
+ } else if (chartType === 'bar'){
+ chartObj.options.chart.type = 'discreteBarChart';
+ chartObj.options.chart.discretebar = {
+ dispatch: {
+ elementClick: function(el) {
+ if (ProjectModel.getSelectedProject()) {
+ $scope.selectedCube = el.data.label;
+ } else {
+ var project = el.data.label;
+ ProjectModel.projects.forEach(function(pro) {
+ if (pro.name.toLowerCase() === project.toLowerCase()) {
+ project = pro.name;
+ }
+ });
+ ProjectModel.setSelectedProject(project);
+ }
+ $scope.$apply();
+ }
+ }
+ };
+ $scope.barChart = chartObj;
+ DashboardService.getChartData({category: dataQuery.category, metric: dataQuery.metric.value, dimension: dataQuery.dimension.value, projectName: ProjectModel.getSelectedProject(), cubeName: $scope.selectedCube, startTime: $scope.formatDatetime($scope.dateRange.startDate), endTime: $scope.formatDatetime($scope.dateRange.endDate)}, {}, function (data) {
+ if (data.length > 6) {
+ $scope.barChart.options.chart.xAxis.rotateLabels = -50;
+ $scope.barChart.options.chart.xAxis.axisLabel = '';
+ }
+ $scope.barChart.data = [{key: dataQuery.category, values: data}];
+ if ($scope.selectedCube) {
+ angular.forEach($scope.barChart.data[0].values, function (value, index){
+ if (value.label != $scope.selectedCube) {
+ value.color = '#ddd';
+ }
+ });
+ }
+ }, function(e) {
+ SweetAlert.swal('Oops...', 'Failed to load bar chart.', 'error');
+ console.error('bar chart error:', e.data);
+ });
+ }
+ };
+
+ // Clean and remove chart
+ $scope.removeChart = function(chartType) {
+ if (chartType === 'all') {
+ $scope.barChart = undefined;
+ $scope.lineChart = undefined;
+ } else if (chartType == 'bar') {
+ $scope.barChart = undefined;
+ } else if (chartType == 'line') {
+ $scope.lineChart = undefined;
+ }
+ };
+
+ $scope.createCharts = function() {
+ $scope.createChart({dimension: $scope.barchartDimension, category: $scope.barchartCategory, metric: $scope.barchartMetric}, 'bar');
+ $scope.createChart({dimension: $scope.linechartDimension, category: $scope.linechartCategory, metric: $scope.linechartMetric}, 'line');
+ };
+
+ // Click query count square
+ $scope.queryCountChart = function() {
+ $scope.currentSquare = 'queryCount';
+ $scope.barchartCategory = dashboardConfig.categories[0];
+ $scope.barchartMetric = dashboardConfig.metrics[0];
+ $scope.linechartCategory = dashboardConfig.categories[0];
+ $scope.linechartMetric = dashboardConfig.metrics[0];
+
+ $scope.removeChart('all');
+ $scope.createCharts();
+ };
+
+ // Click avg query latency
+ $scope.queryAvgChart = function() {
+ $scope.currentSquare = 'queryAvg';
+ $scope.barchartCategory = dashboardConfig.categories[0];
+ $scope.barchartMetric = dashboardConfig.metrics[1];
+ $scope.linechartCategory = dashboardConfig.categories[0];
+ $scope.linechartMetric = dashboardConfig.metrics[1];
+
+ $scope.removeChart('all');
+ $scope.createCharts();
+ };
+
+ // Click job count
+ $scope.jobCountChart = function() {
+ $scope.currentSquare = 'jobCount';
+ $scope.barchartCategory = dashboardConfig.categories[1];
+ $scope.barchartMetric = dashboardConfig.metrics[2];
+ $scope.linechartCategory = dashboardConfig.categories[1];
+ $scope.linechartMetric = dashboardConfig.metrics[2];
+
+ $scope.removeChart('all');
+ $scope.createCharts();
+ };
+
+ // Click job count
+ $scope.jobBuildTimeChart = function() {
+ $scope.currentSquare = 'jobBuildTime';
+ $scope.barchartCategory = dashboardConfig.categories[1];
+ $scope.barchartMetric = dashboardConfig.metrics[3];
+ $scope.linechartCategory = dashboardConfig.categories[1];
+ $scope.linechartMetric = dashboardConfig.metrics[3];
+
+ $scope.removeChart('all');
+ $scope.createCharts();
+ };
+
+ // Line chart granularity change.
+ $scope.changeDimensionFilter = function(chartType) {
+ if (chartType === 'line') {
+ var dataQuery = $scope.lineChart.queryObj;
+ angular.forEach(dashboardConfig.dimensions, function(dimension, ind) {
+ if (dimension.name === $scope.lineChart.dimension.selected.value) {
+ dataQuery.dimension = dashboardConfig.dimensions[ind];
+ $scope.linechartDimension = dashboardConfig.dimensions[ind];
+ }
+ });
+ $scope.removeChart(chartType);
+ $scope.createChart(dataQuery, chartType);
+ }
+ };
+
+ // watch the project or cube change
+ $scope.$watch('projectModel.selectedProject +"~"+ selectedCube', function (newValues, oldValues) {
+ if ($scope.initReady) {
+ if (ProjectModel.getSelectedProject() != null) {
+ $scope.barchartDimension = dashboardConfig.dimensions[1];
+ } else {
+ $scope.barchartDimension = dashboardConfig.dimensions[0];
+ }
+ if (newValues.split('~')[0] != oldValues.split('~')[0]) {
+ $scope.selectedCube = undefined;
+ }
+ $scope.refreshCubeMetric();
+ $scope.refreshOtherMetrics();
+ }
+ });
+
+ $scope.init();
+
+ $scope.cleanSelectedCube = function() {
+ $scope.selectedCube = undefined;
+ };
+
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/kylin/blob/d608fc10/webapp/app/js/controllers/job.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/controllers/job.js b/webapp/app/js/controllers/job.js
index abb3bed..8e460fa 100644
--- a/webapp/app/js/controllers/job.js
+++ b/webapp/app/js/controllers/job.js
@@ -28,6 +28,9 @@ KylinApp
//$scope.projects = [];
$scope.action = {};
$scope.timeFilter = jobConfig.timeFilter[1];
+ if ($routeParams.jobTimeFilter) {
+ $scope.timeFilter = jobConfig.timeFilter[$routeParams.jobTimeFilter];
+ }
$scope.status = [];
$scope.toggleSelection = function toggleSelection(current) {
http://git-wip-us.apache.org/repos/asf/kylin/blob/d608fc10/webapp/app/js/controllers/query.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/controllers/query.js b/webapp/app/js/controllers/query.js
index 4014093..90699df 100644
--- a/webapp/app/js/controllers/query.js
+++ b/webapp/app/js/controllers/query.js
@@ -21,6 +21,9 @@
KylinApp
.controller('QueryCtrl', function ($scope, storage, $base64, $q, $location, $anchorScroll, $routeParams, QueryService, $modal, MessageService, $domUtilityService, $timeout, TableService, SweetAlert, VdmUtil) {
$scope.mainPanel = 'query';
+ if ($routeParams.queryPanel) {
+ $scope.mainPanel = $routeParams.queryPanel;
+ }
$scope.rowsPerPage = 50000;
$scope.base64 = $base64;
$scope.queryString = "";
http://git-wip-us.apache.org/repos/asf/kylin/blob/d608fc10/webapp/app/js/directives/directives.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/directives/directives.js b/webapp/app/js/directives/directives.js
index bca3b03..d6ed304 100644
--- a/webapp/app/js/directives/directives.js
+++ b/webapp/app/js/directives/directives.js
@@ -430,4 +430,101 @@ KylinApp.directive('kylinPagination', function ($parse, $q) {
});
}
}
+}).directive('kylinDaterangepicker', function() {
+ return {
+ restrict: 'E',
+ scope: {
+ startDate:'=',
+ endDate:'=',
+ minDate:'=',
+ maxDate:'=',
+ timezone: '=',
+ ranges: '=',
+ callbackHandler:'&callback',
+ },
+ template: '<button class="btn btn-default" style="background-color:#ffffff">' +
+ '<i class="fa fa-calendar"></i> ' +
+ ' <span></span> ' +
+ '<b class="caret"></b>' +
+ '</button>',
+ link: function(scope, element, attrs) {
+ // Init
+ var timezone = scope.timezone || 'GMT';
+ var format = attrs.format || 'YYYY-MM-DD';
+ var separator = ' - ';
+ var callback = scope.callbackHandler ? scope.callbackHandler() : function() {};
+
+ function startOfToday() {
+ return moment().tz(timezone).startOf('day');
+ }
+ function endOfToday() {
+ return moment().tz(timezone).endOf('day');
+ }
+
+ function getOption() {
+ var ranges = {
+ 'Last 7 Days': [
+ startOfToday().subtract(1, 'weeks'),
+ endOfToday().subtract(1, 'days')
+ ],
+ 'This Month': [
+ startOfToday().startOf('month'),
+ endOfToday()
+ ],
+ 'Last Month': [
+ startOfToday().subtract(1, 'month').startOf('month'),
+ endOfToday().subtract(1, 'month').endOf('month')
+ ]
+ };
+
+ // Create datepicker, full list of options at https://github.com/dangrossman/bootstrap-daterangepicker
+ var maxDate = moment.tz(moment().tz(timezone).format(format), timezone);
+ var minDate = maxDate.clone().subtract(18, 'month');
+ return {
+ maxDate: scope.maxDate || maxDate,
+ minDate: scope.minDate || minDate,
+ format: format,
+ showDropdowns: true,
+ opens: attrs.opens || 'left',
+ ranges: scope.ranges || ranges
+ };
+ }
+
+ function _refresh() {
+ element.daterangepicker(getOption(), function(start, end, label) {
+ scope.startDate = moment.tz(start.startOf('day').format('YYYY-MM-DD HH:mm:ss'), timezone).format('x');
+ scope.endDate = moment.tz(end.endOf('day').format('YYYY-MM-DD HH:mm:ss'), timezone).format('x');
+ callback(scope.startDate, scope.endDate);
+ scope.$apply();
+ });
+ }
+
+ if (timezone) {
+ _refresh();
+ }
+
+ // Use $watch, update the view if either start or end change. (angular version 1.2 not support $watchGroup)
+ scope.$watch('startDate + "~" + endDate + "~" + timezone', function(newValues) {
+ var valueArr = newValues.split('~');
+
+ if (valueArr[2]) {
+ timezone = scope.timezone;
+ _refresh();
+ }
+
+ if (timezone) {
+ var startDate = valueArr[0] ? moment(valueArr[0], 'x').tz(timezone).format(format) : null;
+ var endDate = valueArr[1] ? moment(valueArr[1], 'x').tz(timezone).format(format) : null;
+ }
+
+ if (startDate && endDate) {
+ var val = startDate + separator + endDate;
+ element.find('span').html(val);
+ element.data('daterangepicker').setStartDate(startDate);
+ element.data('daterangepicker').setEndDate(endDate);
+ }
+ });
+
+ }
+ };
});
http://git-wip-us.apache.org/repos/asf/kylin/blob/d608fc10/webapp/app/js/filters/filter.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/filters/filter.js b/webapp/app/js/filters/filter.js
index 8b6cffa..0d7eb5b 100755
--- a/webapp/app/js/filters/filter.js
+++ b/webapp/app/js/filters/filter.js
@@ -242,5 +242,14 @@ KylinApp
});
return out;
}
+ }).filter('startCase', function($filter) {
+ return function (item) {
+ var words = item.split(' ');
+ var formatWord = '';
+ angular.forEach(words, function(word, ind) {
+ formatWord += ' ' + word.charAt(0).toUpperCase() + word.slice(1);
+ })
+ return formatWord.slice(1);
+ };
});
http://git-wip-us.apache.org/repos/asf/kylin/blob/d608fc10/webapp/app/js/model/dashboardConfig.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/model/dashboardConfig.js b/webapp/app/js/model/dashboardConfig.js
new file mode 100644
index 0000000..1620d1d
--- /dev/null
+++ b/webapp/app/js/model/dashboardConfig.js
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+KylinApp.constant('dashboardConfig', {
+ granularityFilter: [
+ {name: 'Daily', value: 'day'},
+ {name: 'Weekly', value: 'week'},
+ {name: 'Monthly', value: 'month'}
+ ],
+ metrics: [
+ {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'}
+ ],
+ dimensions: [
+ {name: 'project', value: 'PROJECT'},
+ {name: 'cube', value: 'CUBE'},
+ {name: 'day', value: 'DAY'},
+ {name: 'week', value: 'WEEK'},
+ {name: 'month', value: 'MONTH'}
+ ],
+ categories: [
+ 'QUERY', 'JOB'
+ ],
+ baseChartOptions: {
+ chart: {
+ height: 272,
+ margin : {
+ top: 20,
+ right: 40,
+ bottom: 60,
+ left: 45
+ },
+ useInteractiveGuideline: false,
+ x: function(d){return d.label;},
+ y: function(d){return d.value;},
+ xAxis: {
+ axisLabelDistance: 50,
+ staggerLabels: false,
+ tickFormat: function(d) {
+ if (d.length > 10) {
+ return d.substring(0,10) + '...';
+ } else {
+ return d;
+ }
+ }
+ },
+ yAxis: {
+ 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
+ },
+ valueFormat: function(d){
+ return d3.format('.1')(d);
+ },
+ transitionDuration: 500,
+ tooltip: {
+ contentGenerator: function (d) {
+ return '<table><tr><td class="legend-color-guide"><div style="background-color: '+d.color+';"></div></td><td class="key">' + d.data.label + '</td><td class="value">'+d.data.value.toFixed(2)+'</td></tr></table>';
+ }
+ }
+ },
+ title: {
+ enable: true,
+ css: {
+ 'margin-top': '20px'
+ }
+ }
+ }
+});
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/kylin/blob/d608fc10/webapp/app/js/services/dashboard.js
----------------------------------------------------------------------
diff --git a/webapp/app/js/services/dashboard.js b/webapp/app/js/services/dashboard.js
new file mode 100644
index 0000000..4340119
--- /dev/null
+++ b/webapp/app/js/services/dashboard.js
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+KylinApp.factory('DashboardService', ['$resource', '$location', function ($resource, $location, config) {
+ return $resource(Config.service.url + 'dashboard/:type/:category/:metric/:dimension', {}, {
+ getCubeMetrics: {
+ method: 'GET',
+ params: {type: 'metric', category: 'cube'},
+ isArray: false,
+ interceptor: {
+ response: function(response) {
+ var data = response.data;
+ var cubeMetrics;
+ if (data) {
+ cubeMetrics = {totalCubes: data.totalCube, expansionRate: {avg: data.avgCubeExpansion, max: data.maxCubeExpansion, min: data.minCubeExpansion}};
+ }
+ return cubeMetrics;
+ }
+ }
+ },
+ getQueryMetrics: {
+ method: 'GET',
+ params: {type: 'metric', category: 'query'},
+ isArray: false,
+ interceptor: {
+ response: function(response) {
+ var data = response.data;
+ var queryMetrics;
+ if (data) {
+ queryMetrics = {queryCount: data.queryCount, queryLatency: {avg: data.avgQueryLatency/1000, max: data.maxQueryLatency/1000, min: data.minQueryLatency/1000}};
+ }
+ return queryMetrics;
+ }
+ }
+ },
+ getJobMetrics: {
+ method: 'GET',
+ params: {type: 'metric', category: 'job'},
+ isArray: false,
+ interceptor: {
+ response: function(response) {
+ 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}};
+ }
+ return jobMetrics;
+ }
+ }
+ },
+ getChartData: {
+ method: 'GET',
+ params: {type: 'chart'},
+ isArray: false,
+ interceptor: {
+ response: function(response) {
+ var data = response.data;
+ var chartValues = [];
+ if (data) {
+ angular.forEach(Object.keys(data), function(key) {
+ var label = key;
+ var regEx = /^\d{4}-\d{1,2}-\d{1,2}$/;
+ if(key.match(regEx)) {
+ label = moment(key, 'YYYY-MM-DD').format('x');
+ }
+ var value = data[key];
+ if (response.config.url.indexOf('/JOB/AVG_JOB_BUILD_TIME') > -1) { // AVG Job Build time format ms to sec
+ value = value*1024*1024/1000;
+ } else if (response.config.url.indexOf('/QUERY/AVG_QUERY_LATENCY') > -1) { // AVG Query Latency format ms to sec
+ value = value/1000;
+ }
+ chartValues.push({'label': label, 'value': value});
+ });
+ }
+ return chartValues;
+ }
+ }
+ }
+ });
+}]);
http://git-wip-us.apache.org/repos/asf/kylin/blob/d608fc10/webapp/app/less/app.less
----------------------------------------------------------------------
diff --git a/webapp/app/less/app.less b/webapp/app/less/app.less
index 85ad937..fcba436 100644
--- a/webapp/app/less/app.less
+++ b/webapp/app/less/app.less
@@ -842,3 +842,61 @@ pre {
white-space: -o-pre-wrap;
word-wrap: break-word;
}
+/* dashboard page style*/
+.square {
+ border: 2px solid #ddd;
+ text-align: center;
+ width: 215px;
+ height: 170px;
+ cursor: zoom-in;
+ padding-top: 15px;
+ padding-bottom: 15px;
+ .title {
+ font-size: 22px;
+ height: 60px;
+ }
+ .metric {
+ font-size: 35px ;
+ font-weight: bolder;
+ color: #8b1;
+ display: inline-block;
+ white-space: nowrap;
+ .unit {
+ font-size: 16px;
+ }
+ }
+ .description {
+ font-size: 15px;
+ color: #6a6a6a;
+ display: block;
+ }
+}
+.square-active {
+ border: 2px solid #6a6a6a;
+}
+.square-big {
+ border: 5px solid #ddd;
+ text-align: center;
+ width: 165px;
+ height: 240px;
+ padding: 20px 0;
+ margin-bottom: 20px;
+ .title {
+ font-size: 24px;
+ height: 55px;
+ }
+ .metric {
+ padding: 15px 0;
+ font-size: 50px ;
+ font-weight: bolder;
+ height: 100px;
+ color: #06d;
+ .unit {
+ font-size: 35px;
+ }
+ }
+ .description {
+ font-size: 18px;
+ color: #6a6a6a;
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/kylin/blob/d608fc10/webapp/app/partials/dashboard/dashboard.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/dashboard/dashboard.html b/webapp/app/partials/dashboard/dashboard.html
new file mode 100644
index 0000000..891b4b9
--- /dev/null
+++ b/webapp/app/partials/dashboard/dashboard.html
@@ -0,0 +1,151 @@
+<!--
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+-->
+<div class="container" style="min-width: 1140px;">
+ <div class="row" style="margin-top:22px">
+ <div class="col-sm-12">
+ <ol class="breadcrumb" style="display: inline-block; padding: 7px 0px; background-color: #ffffff;" ng-if="projectModel.selectedProject">
+ <li ng-if="selectedCube" ><a href="javascript:void(0)" ng-click="cleanSelectedCube()"><i class="fa fa-dashboard"></i> {{projectModel.selectedProject}}</a></li>
+ <li ng-if="!selectedCube"><i class="fa fa-dashboard"></i> {{projectModel.selectedProject}}</li>
+ <li class="active" ng-if="selectedCube">{{selectedCube}}</li>
+ </ol>
+ <div class="input-group pull-right" style="margin-bottom: 20px;">
+ <kylin-daterangepicker start-date="dateRange.startDate" end-date="dateRange.endDate" callback="changeDateRange"></kylin-daterangepicker>
+ </div>
+ </div>
+ </div>
+ <!-- metric boxs-->
+ <div class="row">
+ <div class="col-sm-2">
+ <div class="square-big" tooltip-placement="bottom" tooltip="As of {{cubeMetricsRefreshTime}}">
+ <div class="title">
+ TOTAL CUBE COUNT
+ </div>
+ <div class="metric" ng-if="cubeMetrics.totalCubes || (cubeMetrics.totalCubes === 0)">
+ {{cubeMetrics.totalCubes | number:0}}
+ </div>
+ <div class="metric" ng-if="!cubeMetrics.totalCubes && (cubeMetrics.totalCubes !== 0)">
+ --
+ </div>
+ <a class="description" ng-href="model">
+ More Details
+ </a>
+ </div>
+ <div class="square-big" tooltip-placement="bottom" tooltip-html-unsafe="Max: {{cubeMetrics.expansionRate.max | number:2}} | Min: {{cubeMetrics.expansionRate.min | number:2}}</br> As of {{cubeMetricsRefreshTime}}">
+ <div class="title">
+ AVG CUBE EXPANSION
+ </div>
+ <div class="metric" ng-if="cubeMetrics.expansionRate.avg || (cubeMetrics.expansionRate.avg === 0)">
+ {{cubeMetrics.expansionRate.avg | number:2}}
+ </div>
+ <div class="metric" ng-if="!cubeMetrics.expansionRate.avg && (cubeMetrics.expansionRate.avg !== 0)">
+ --
+ </div>
+ <a class="description" ng-href="model">
+ More Details
+ </a>
+ </div>
+ </div>
+ <div class="col-sm-10">
+ <div class="row">
+ <div class="col-sm-3">
+ <div class="square" ng-class="{'square-active': currentSquare ==='queryCount'}" ng-click="queryCountChart()">
+ <div class="title">
+ QUERY<br/>COUNT
+ </div>
+ <div class="metric" ng-if="queryMetrics.queryCount || (queryMetrics.queryCount === 0)">
+ {{queryMetrics.queryCount | number:0}}
+ </div>
+ <div class="metric" ng-if="!queryMetrics.queryCount && (queryMetrics.queryCount !== 0)">
+ --
+ </div>
+ <a class="description" ng-href="query?queryPanel=cached" ng-click="$event.stopPropagation();">
+ More Details
+ </a>
+ </div>
+ </div>
+ <div class="col-sm-3">
+ <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
+ </div>
+ <div class="metric" ng-if="queryMetrics.queryLatency.avg || (queryMetrics.queryLatency.avg === 0)">
+ {{queryMetrics.queryLatency.avg | number:2}}<span class="unit"> sec</span>
+ </div>
+ <div class="metric" ng-if="!queryMetrics.queryLatency.avg && (queryMetrics.queryLatency.avg !== 0)">
+ --
+ </div>
+ <a class="description" ng-href="query?queryPanel=cached" ng-click="$event.stopPropagation();">
+ More Details
+ </a>
+ </div>
+ </div>
+ <div class="col-sm-3">
+ <div class="square" ng-class="{'square-active': currentSquare ==='jobCount'}" ng-click="jobCountChart()">
+ <div class="title">
+ JOB<br/>COUNT
+ </div>
+ <div class="metric" ng-if="jobMetrics.jobCount || (jobMetrics.jobCount === 0)">
+ {{jobMetrics.jobCount | number:0}}
+ </div>
+ <div class="metric" ng-if="!jobMetrics.jobCount && (jobMetrics.jobCount !== 0)">
+ --
+ </div>
+ <a class="description" ng-href="jobs" ng-click="$event.stopPropagation();">
+ More Details
+ </a>
+ </div>
+ </div>
+ <div class="col-sm-3" >
+ <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
+ </div>
+ <div class="metric" ng-if="jobMetrics.buildingTime.avg || jobMetrics.buildingTime.avg === 0">
+ {{jobMetrics.buildingTime.avg | number:2}}<span class="unit"> sec</span>
+ </div>
+ <div class="metric" ng-if="!jobMetrics.buildingTime.avg && (jobMetrics.buildingTime.avg !== 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: 90px; position: absolute; right: 15px; bottom: 265px;">
+ showValue: <input type="checkbox" ng-model="barChart.options.chart.showValues">
+ </div>
+ <nvd3 options="barChart.options" data="barChart.data"></nvd3>
+ </div>
+ </div>
+ <div class="col-sm-6" ng-if="lineChart">
+ <div style="border: 2px solid #ddd; margin-top:15px;">
+ <div class="form-group">
+ <select class="form-control" style="width: 80px; position: absolute; right: 20px; bottom: 270px;" ng-options="option.name for option in lineChart.dimension.options track by option.value" ng-model="lineChart.dimension.selected" ng-change="changeDimensionFilter('line')">
+ </select>
+ </div>
+ <nvd3 options="lineChart.options" data="lineChart.data"></nvd3>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/kylin/blob/d608fc10/webapp/app/partials/header.html
----------------------------------------------------------------------
diff --git a/webapp/app/partials/header.html b/webapp/app/partials/header.html
index 0e37e63..fd22f9c 100644
--- a/webapp/app/partials/header.html
+++ b/webapp/app/partials/header.html
@@ -53,6 +53,9 @@
<li class="{{activeTab=='admin'?'purple':'green'}}" ng-if="userService.hasRole('ROLE_ADMIN')">
<a href="admin">System</a>
</li>
+ <li class="{{activeTab=='dashboard'?'purple':'green'}}" ng-if="userService.isAuthorized()">
+ <a href="dashboard">Dashboard</a>
+ </li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown light-blue" ng-if="config.documents.length > 0">
http://git-wip-us.apache.org/repos/asf/kylin/blob/d608fc10/webapp/app/routes.json
----------------------------------------------------------------------
diff --git a/webapp/app/routes.json b/webapp/app/routes.json
index 6729daf..65140a0 100644
--- a/webapp/app/routes.json
+++ b/webapp/app/routes.json
@@ -112,5 +112,13 @@
"tab": "models",
"controller": "ModelEditCtrl"
}
+ },
+ {
+ "url": "/dashboard",
+ "params": {
+ "templateUrl": "partials/dashboard/dashboard.html",
+ "tab": "dashboard",
+ "controller": "DashboardCtrl"
+ }
}
]
http://git-wip-us.apache.org/repos/asf/kylin/blob/d608fc10/webapp/bower.json
----------------------------------------------------------------------
diff --git a/webapp/bower.json b/webapp/bower.json
index eac2cb3..f108862 100755
--- a/webapp/bower.json
+++ b/webapp/bower.json
@@ -17,7 +17,7 @@
"angular-tree-control": "0.1.4",
"angularLocalStorage": "~0.3.0",
"messenger": "1.4.1",
- "moment": "2.5.1",
+ "moment": "2.10.6",
"d3": "3.4.4",
"nvd3": "1.8.4",
"angular-sweetalert": "~1.0.3",
@@ -35,7 +35,9 @@
"angular-sanitize": "1.2.18",
"angular-tree-control": "0.2.8",
"angular-bootstrap-datetimepicker": "0.3.15",
- "angular-nvd3": "1.0.9"
+ "angular-nvd3": "1.0.9",
+ "bootstrap-daterangepicker": "~1.3.23",
+ "moment-timezone" : "~0.5.5"
},
"devDependencies": {
"less.js": "~1.4.0",
@@ -45,7 +47,7 @@
"angular": "1.2.29",
"nvd3": "1.8.4",
"d3": "3.4.4",
- "moment": "2.4.0",
+ "moment": "2.10.6",
"angular-resource": "1.2.15",
"angularLocalStorage": "0.1.7",
"angular-cookies": "~1.2.0-rc.2",
http://git-wip-us.apache.org/repos/asf/kylin/blob/d608fc10/webapp/grunt.json
----------------------------------------------------------------------
diff --git a/webapp/grunt.json b/webapp/grunt.json
index 06720b2..b86f60b 100755
--- a/webapp/grunt.json
+++ b/webapp/grunt.json
@@ -48,6 +48,8 @@
"app/components/angular-ui-select/dist/select.js",
"app/components/angular-sanitize/angular-sanitize.js",
"app/components/angular-nvd3/dist/angular-nvd3.js",
+ "app/components/moment-timezone/builds/moment-timezone-with-data.js",
+ "app/components/bootstrap-daterangepicker/daterangepicker.js",
"tmp/js/scripts.js"
],
"dest": "tmp/js/scripts.min.js"
@@ -73,6 +75,7 @@
"app/components/angular-toggle-switch/angular-toggle-switch.css",
"app/components/angular-ui-select/dist/select.css",
"app/components/angular-bootstrap-datetimepicker/src/css/datetimepicker.css",
+ "app/components/bootstrap-daterangepicker/daterangepicker-bs3.css",
"tmp/css/styles.css"
],
"dest": "tmp/css/styles.min.css"