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 1b5badb0ecb09f9d82dc1296c714e452f830150b
Author: Zhong, Yanghong <nju_y...@apache.org>
AuthorDate: Mon Jun 8 18:49:43 2020 +0800

    KYLIN-4533 Create a tool to check cube optimization benefit
---
 .../apache/kylin/common/restclient/RestClient.java |  12 +
 .../org/apache/kylin/tool/CubeOptimizationCLI.java | 363 +++++++++++++++++++++
 .../mail_templates/OPTIMIZATION_BENEFITS.ftl       | 123 +++++++
 3 files changed, 498 insertions(+)

diff --git 
a/core-common/src/main/java/org/apache/kylin/common/restclient/RestClient.java 
b/core-common/src/main/java/org/apache/kylin/common/restclient/RestClient.java
index 21b08f8..e23c3a9 100644
--- 
a/core-common/src/main/java/org/apache/kylin/common/restclient/RestClient.java
+++ 
b/core-common/src/main/java/org/apache/kylin/common/restclient/RestClient.java
@@ -382,6 +382,18 @@ public class RestClient {
         return content;
     }
 
+    public String getCuboidRecommendResponse(String cubeName) throws 
IOException {
+        String url = baseUrl + "/cubes/" + cubeName + "/cuboids/recommend";
+        HttpGet get = newGet(url);
+        HttpResponse response = client.execute(get);
+        String content = getContent(response);
+        if (response.getStatusLine().getStatusCode() != 200) {
+            throw new IOException(
+                    "Invalid response " + 
response.getStatusLine().getStatusCode() + " with url " + url + "\n");
+        }
+        return content;
+    }
+
     public void checkCompatibility(String jsonRequest) throws IOException {
         checkCompatibility(jsonRequest, false);
     }
diff --git a/tool/src/main/java/org/apache/kylin/tool/CubeOptimizationCLI.java 
b/tool/src/main/java/org/apache/kylin/tool/CubeOptimizationCLI.java
new file mode 100644
index 0000000..af4cfb1
--- /dev/null
+++ b/tool/src/main/java/org/apache/kylin/tool/CubeOptimizationCLI.java
@@ -0,0 +1,363 @@
+/*
+ * 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.tool;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.restclient.RestClient;
+import org.apache.kylin.common.util.AbstractApplication;
+import org.apache.kylin.common.util.JsonUtil;
+import org.apache.kylin.common.util.MailService;
+import org.apache.kylin.common.util.MailTemplateProvider;
+import org.apache.kylin.common.util.OptionsHelper;
+import org.apache.kylin.common.util.ToolUtil;
+import org.apache.kylin.cube.CubeInstance;
+import org.apache.kylin.cube.CubeManager;
+import org.apache.kylin.cube.cuboid.algorithm.OptimizationBenefit;
+import org.apache.kylin.metadata.project.ProjectInstance;
+import org.apache.kylin.metadata.project.ProjectManager;
+import org.apache.kylin.metadata.project.RealizationEntry;
+import org.apache.kylin.metadata.realization.RealizationType;
+import org.apache.kylin.shaded.com.google.common.base.Joiner;
+import org.apache.kylin.shaded.com.google.common.collect.Lists;
+import org.apache.kylin.shaded.com.google.common.collect.Maps;
+import org.apache.kylin.shaded.com.google.common.collect.Sets;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+public class CubeOptimizationCLI extends AbstractApplication {
+    private static final Logger logger = 
LoggerFactory.getLogger(CubeOptimizationCLI.class);
+
+    @SuppressWarnings("static-access")
+    private static final Option OPTION_KYLIN_CONFIG_URI = 
OptionBuilder.withArgName("kylinConfigUri").hasArg()
+            .isRequired(true).withDescription("Kylin configuration uri for 
finding related cubes")
+            .create("kylinConfigUri");
+    @SuppressWarnings("static-access")
+    private static final Option OPTION_USER = 
OptionBuilder.withArgName("user").hasArg().isRequired(true)
+            .withDescription("user for authentication").create("user");
+    @SuppressWarnings("static-access")
+    private static final Option OPTION_PASSWORD = 
OptionBuilder.withArgName("password").hasArg().isRequired(true)
+            .withDescription("password for authentication").create("password");
+    @SuppressWarnings("static-access")
+    public static final Option OPTION_All = 
OptionBuilder.withArgName("all").hasArg(false).isRequired(false)
+            .withDescription("check all of the cubes' optimization 
benefit").create("all");
+    @SuppressWarnings("static-access")
+    private static final Option OPTION_PROJECT = 
OptionBuilder.withArgName("project").hasArg().isRequired(false)
+            .withDescription("check all of the cubes' optimization benefit in 
this project").create("project");
+    @SuppressWarnings("static-access")
+    private static final Option OPTION_CUBE = 
OptionBuilder.withArgName("cube").hasArg().isRequired(false)
+            .withDescription("check the optimization benefit for this 
cube").create("cube");
+    @SuppressWarnings("static-access")
+    private static final Option OPTION_THRESHOLD = 
OptionBuilder.withArgName("threshold").hasArg().isRequired(false)
+            .withDescription("threshold of score").create("threshold");
+    @SuppressWarnings("static-access")
+    private static final Option OPTION_TOP_NUM = 
OptionBuilder.withArgName("topNum").hasArg().isRequired(false)
+            .withDescription("number of display cube item which order by total 
benefit").create("topNum");
+    @SuppressWarnings("static-access")
+    private static final Option OPTION_TO_OWNER = 
OptionBuilder.withArgName("toOwner").hasArg().isRequired(false)
+            .withDescription("whether send email to owner").create("toOwner");
+
+    private static final String OPTIMIZATION_BENEFITS = 
"OPTIMIZATION_BENEFITS";
+
+    private KylinConfig kylinConfig;
+    private ProjectManager projectManager;
+    private CubeManager cubeManager;
+    private RestClient client;
+    private double threshold;
+    private int topNum;
+    private boolean ifToOwner;
+
+    private Map<String, Set<CubeInstance>> cubes = Maps.newHashMap();
+
+    protected Options getOptions() {
+        OptionGroup cubeOrProject = new OptionGroup();
+        cubeOrProject.addOption(OPTION_CUBE);
+        cubeOrProject.addOption(OPTION_PROJECT);
+        cubeOrProject.addOption(OPTION_All);
+        cubeOrProject.setRequired(true);
+
+        Options options = new Options();
+        options.addOption(OPTION_KYLIN_CONFIG_URI);
+        options.addOption(OPTION_USER);
+        options.addOption(OPTION_PASSWORD);
+        options.addOptionGroup(cubeOrProject);
+        options.addOption(OPTION_THRESHOLD);
+        options.addOption(OPTION_TOP_NUM);
+        options.addOption(OPTION_TO_OWNER);
+        return options;
+    }
+
+    protected void init(OptionsHelper optionsHelper) throws Exception {
+        String kylinConfigUri = 
optionsHelper.getOptionValue(OPTION_KYLIN_CONFIG_URI);
+        kylinConfig = KylinConfig.createInstanceFromUri(kylinConfigUri);
+        projectManager = ProjectManager.getInstance(kylinConfig);
+        cubeManager = CubeManager.getInstance(kylinConfig);
+
+        String user = optionsHelper.getOptionValue(OPTION_USER);
+        String password = optionsHelper.getOptionValue(OPTION_PASSWORD);
+        client = new RestClient(user + ":" + password + "@" + kylinConfigUri);
+
+        threshold = optionsHelper.hasOption(OPTION_THRESHOLD)
+                ? 
Double.parseDouble(optionsHelper.getOptionValue(OPTION_THRESHOLD))
+                : 60;
+
+        topNum = optionsHelper.hasOption(OPTION_TOP_NUM) ? 
Integer.valueOf(optionsHelper.getOptionValue(OPTION_TOP_NUM))
+                : 10;
+
+        ifToOwner = optionsHelper.hasOption(OPTION_TO_OWNER)
+                && 
Boolean.parseBoolean(optionsHelper.getOptionValue(OPTION_TO_OWNER));
+
+        if (optionsHelper.hasOption(OPTION_All)) {
+            List<ProjectInstance> projects = projectManager.listAllProjects();
+            for (ProjectInstance projectEntry : projects) {
+                addCubesInProject(projectEntry);
+            }
+        } else if (optionsHelper.hasOption(OPTION_PROJECT)) {
+            Set<String> projectNames = 
Sets.newHashSet(optionsHelper.getOptionValue(OPTION_PROJECT).split(","));
+            for (String projectName : projectNames) {
+                ProjectInstance project = 
projectManager.getProject(projectName);
+                if (project == null) {
+                    throw new IllegalArgumentException("No project found with 
name of " + projectName);
+                }
+                addCubesInProject(project);
+            }
+        } else if (optionsHelper.hasOption(OPTION_CUBE)) {
+            String cubeNames = optionsHelper.getOptionValue(OPTION_CUBE);
+            for (String cubeName : cubeNames.split(",")) {
+                CubeInstance cube = cubeManager.getCube(cubeName);
+                if (cube == null) {
+                    throw new IllegalArgumentException("No cube found with 
name of " + cubeName);
+                } else {
+                    List<ProjectInstance> projects = 
projectManager.findProjects(RealizationType.CUBE, cubeName);
+                    if (projects.isEmpty()) {
+                        throw new IllegalArgumentException("No project 
contains cube " + cubeName);
+                    }
+                    addCubeInProject(projects.get(0), cube);
+                }
+            }
+        }
+    }
+
+    private void addCubesInProject(ProjectInstance projectEntry) {
+        List<RealizationEntry> cubeEntries = 
projectEntry.getRealizationEntries(RealizationType.CUBE);
+        for (RealizationEntry cubeEntry : cubeEntries) {
+            CubeInstance cubeInstance = 
cubeManager.getCube(cubeEntry.getRealization());
+            if (cubeInstance == null) {
+                logger.warn("Fail to find cube instance {}", 
cubeEntry.getRealization());
+                continue;
+            }
+            addCubeInProject(projectEntry, cubeInstance);
+        }
+    }
+
+    private void addCubeInProject(ProjectInstance projectEntry, CubeInstance 
cubeInstance) {
+        Set<CubeInstance> cubeSet = cubes.get(projectEntry.getName());
+        if (cubeSet == null) {
+            cubeSet = Sets.newHashSet();
+        }
+        cubeSet.add(cubeInstance);
+        cubes.put(projectEntry.getName(), cubeSet);
+    }
+
+    protected void execute(OptionsHelper optionsHelper) throws Exception {
+        init(optionsHelper);
+
+        if (cubes.isEmpty()) {
+            logger.warn("no cubes found for optimization benefit check");
+            return;
+        }
+
+        Map<CubeInstance, CubeOptBenefit> optBenefitMap = Maps.newHashMap();
+        for (Map.Entry<String, Set<CubeInstance>> projectEntry : 
cubes.entrySet()) {
+            for (CubeInstance cubeInstance : projectEntry.getValue()) {
+                try {
+                    String restContent = 
client.getCuboidRecommendResponse(cubeInstance.getName());
+                    JsonNode jsonNode = JsonUtil.readValueAsTree(restContent);
+                    JsonNode optBntNode = jsonNode.get("optBenefit");
+                    OptimizationBenefit optBenefit = 
JsonUtil.readValue(optBntNode.toString(),
+                            OptimizationBenefit.class);
+                    if (optBenefit.getTotalBenefit() >= threshold) {
+                        optBenefitMap.put(cubeInstance,
+                                new CubeOptBenefit(projectEntry.getKey(), 
cubeInstance.getName(), optBenefit));
+                    } else {
+                        logger.info("Will not optimize cube {} since its total 
benefit {} is lower than {}",
+                                cubeInstance.getName(), 
optBenefit.getTotalBenefit(), threshold);
+                    }
+                } catch (Exception e) {
+                    logger.warn("fail to get optimization benefit for cube {} 
due to " + e, cubeInstance.getName());
+                }
+            }
+        }
+
+        List<Map.Entry<CubeInstance, CubeOptBenefit>> optBenefitList = new 
ArrayList<>(optBenefitMap.entrySet());
+        Collections.sort(optBenefitList, new 
Comparator<Map.Entry<CubeInstance, CubeOptBenefit>>() {
+            @Override
+            public int compare(Map.Entry<CubeInstance, CubeOptBenefit> o1, 
Map.Entry<CubeInstance, CubeOptBenefit> o2) {
+                return o1.getValue().compareTo(o2.getValue());
+            }
+        });
+
+        if (optBenefitList.size() > topNum) {
+            optBenefitList = optBenefitList.subList(0, topNum);
+        }
+        
+        if (optBenefitList.isEmpty()) {
+            logger.info("Don't find any cubes that should be optimized");
+            return;
+        }
+
+        { // Send to admins
+            List<CubeOptBenefit> cubeOptBenefitList = 
optBenefitList.stream().map(Map.Entry::getValue)
+                    .collect(Collectors.toList());
+            Map<String, Object> rootMap = Maps.newHashMap();
+            List<Map<String, Object>> cubeBenefitList = 
getMailContent(cubeOptBenefitList);
+            rootMap.put("cubeBenefitList", cubeBenefitList);
+            String[] receiver = kylinConfig.getAdminDls();
+            sendMail(OPTIMIZATION_BENEFITS, rootMap, 
Lists.newArrayList(receiver));
+        }
+
+        if (ifToOwner) {
+            // Collect info for each cube owner
+            Map<String, List<CubeOptBenefit>> ownerOptBenefitMap = 
Maps.newHashMap();
+            for (Map.Entry<CubeInstance, CubeOptBenefit> entry : 
optBenefitList) {
+                String owner = entry.getKey().getOwner();
+                List<CubeOptBenefit> value = ownerOptBenefitMap.get(owner);
+                if (value == null) {
+                    value = Lists.newArrayList();
+                }
+                value.add(entry.getValue());
+                ownerOptBenefitMap.put(owner, value);
+            }
+
+            for (Map.Entry<String, List<CubeOptBenefit>> entry : 
ownerOptBenefitMap.entrySet()) {
+                Map<String, Object> rootMap = Maps.newHashMap();
+                List<Map<String, Object>> cubeBenefitList = 
getMailContent(entry.getValue());
+                rootMap.put("cubeBenefitList", cubeBenefitList);
+                String receiver = getMailAddress(entry.getKey());
+                sendMail(OPTIMIZATION_BENEFITS, rootMap, 
Lists.newArrayList(receiver));
+            }
+        }
+    }
+
+    private static List<Map<String, Object>> 
getMailContent(List<CubeOptBenefit> cubeOptBenefitList) {
+        List<Map<String, Object>> result = Lists.newArrayList();
+        for (CubeOptBenefit cubeOptBenefit : cubeOptBenefitList) {
+            Map<String, Object> resultEntry = Maps.newHashMap();
+            resultEntry.put("server", ToolUtil.getHostName());
+            resultEntry.put("projectName", cubeOptBenefit.project);
+            resultEntry.put("cube", cubeOptBenefit.cube);
+            OptimizationBenefit optBenefit = 
cubeOptBenefit.optimizationBenefit;
+            resultEntry.put("score", optBenefit.getScore());
+            resultEntry.put("totalBenefit", optBenefit.getTotalBenefit());
+            resultEntry.put("queryBenefit", optBenefit.getQueryBenefit());
+            resultEntry.put("spaceBenefit", optBenefit.getSpaceBenefit());
+            resultEntry.put("rollupBenefit", optBenefit.getRollupBenefit());
+            resultEntry.put("rollupCost", optBenefit.getRollupCost());
+            resultEntry.put("rollupInputCount", 
optBenefit.getRollupInputCount());
+            resultEntry.put("curTotalSize", optBenefit.getCurTotalSize());
+            resultEntry.put("recomTotalSize", optBenefit.getRecomTotalSize());
+            resultEntry.put("spaceLimit", optBenefit.getSpaceLimit());
+            resultEntry.put("k", optBenefit.getK());
+            resultEntry.put("scoreHint", optBenefit.getScoreHint());
+
+            result.add(resultEntry);
+        }
+        return result;
+    }
+
+    private static String getMailTitle(String... titleParts) {
+        return "[" + Joiner.on("]-[").join(titleParts) + "]";
+    }
+
+    private String getMailAddress(String account) {
+        return account + kylinConfig.getNotificationMailSuffix();
+    }
+
+    private void sendMail(String state, Map<String, Object> root, List<String> 
emailAddress) {
+        String content = 
MailTemplateProvider.getInstance().buildMailContent(state, root);
+        String title = getMailTitle("RECOMMEND", "CUBE_OPTIMIZATION", 
kylinConfig.getDeployEnv());
+        new MailService(kylinConfig).sendMail(emailAddress, title, content);
+    }
+
+    public static class CubeOptBenefit implements Comparable<CubeOptBenefit> {
+        public final OptimizationBenefit optimizationBenefit;
+        public final String project;
+        public final String cube;
+
+        public CubeOptBenefit(String project, String cube, OptimizationBenefit 
optimizationBenefit) {
+            this.optimizationBenefit = optimizationBenefit;
+            this.project = project;
+            this.cube = cube;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o)
+                return true;
+            if (o == null || getClass() != o.getClass())
+                return false;
+            CubeOptBenefit that = (CubeOptBenefit) o;
+            return Objects.equals(project, that.project) && 
Objects.equals(cube, that.cube);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(project, cube);
+        }
+
+        @Override
+        public int compareTo(CubeOptBenefit o) {
+            if (!this.equals(o)) {
+                if (optimizationBenefit.getTotalBenefit() > 
o.optimizationBenefit.getTotalBenefit()) {
+                    return -1;
+                } else if (optimizationBenefit.getTotalBenefit() < 
o.optimizationBenefit.getTotalBenefit()) {
+                    return 1;
+                } else if (project.compareTo(o.project) < 0) {
+                    return -1;
+                } else if (project.compareTo(o.project) > 0) {
+                    return 1;
+                } else if (cube.compareTo(o.cube) < 0) {
+                    return -1;
+                } else if (cube.compareTo(o.cube) > 0) {
+                    return 1;
+                }
+            }
+            return 0;
+        }
+    }
+
+    public static void main(String[] args) throws IOException {
+        new CubeOptimizationCLI().execute(args);
+    }
+}
diff --git a/tool/src/main/resources/mail_templates/OPTIMIZATION_BENEFITS.ftl 
b/tool/src/main/resources/mail_templates/OPTIMIZATION_BENEFITS.ftl
new file mode 100644
index 0000000..fd25b05
--- /dev/null
+++ b/tool/src/main/resources/mail_templates/OPTIMIZATION_BENEFITS.ftl
@@ -0,0 +1,123 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd";>
+<html xmlns="http://www.w3.org/1999/xhtml";>
+
+<head>
+    <meta http-equiv="Content-Type" content="Multipart/Alternative; 
charset=UTF-8"/>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+</head>
+
+<style type="text/css">
+    .tftable {
+        font-size: 12px;
+        color: #333333;
+        width: 100%;
+        border-width: 1px;
+        border-color: #bce8f1;
+        border-collapse: collapse;
+    }
+
+    .tftable th {
+        font-size: 12px;
+        border-width: 1px;
+        padding: 8px;
+        border-style: solid;
+        border-color: #bce8f1;
+        text-align: left;
+    }
+
+    .tftable tr {
+        background-color: #fff;
+    }
+
+    .tftable td {
+        font-size: 12px;
+        border-width: 1px;
+        padding: 8px;
+        border-style: solid;
+        border-color: #bce8f1;
+    }
+</style>
+
+<body>
+<hr/>
+<h4 style="margin-top: 0;
+margin-bottom: 0;
+font-size: 16px;
+font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+    The formula of cube optimization benefit :
+</h4>
+
+<table style="font-size: 12px">
+    <tr>
+        <td>queryBenefit</td>
+        <td>=</td>
+        <td>(rollupBenefit - rollupCost) / rollupInputCount</td>
+    </tr>
+    <tr>
+        <td>spaceBenefit</td>
+        <td>=</td>
+        <td>(curTotalSize - recomTotalSize) / spaceLimit</td>
+    </tr>
+    <tr>
+        <td>totalBenefit</td>
+        <td>=</td>
+        <td>queryBenefit + k * spaceBenefit</td>
+    </tr>
+    <tr>
+        <td>score</td>
+        <td>=</td>
+        <td>1 / (1 + totalBenefit)</td>
+    </tr>
+</table>
+
+<hr/>
+
+<h4 style="margin-top: 10px;
+margin-bottom: 0;
+font-size: 16px;
+font-family: 'Trebuchet MS ', Arial, Helvetica, sans-serif;">
+    Details for cube optimization
+</h4>
+
+<table class="tftable" border="1">
+
+    <tr>
+        <th bgcolor="#d9edf7">project name</th>
+        <th bgcolor="#d9edf7">cube</th>
+        <th bgcolor="#d9edf7"><font color="#ff8c00">score</font></th>
+        <th bgcolor="#d9edf7">totalBenefit</th>
+        <th bgcolor="#d9edf7">queryBenefit</th>
+        <th bgcolor="#d9edf7">spaceBenefit</th>
+        <th bgcolor="#d9edf7">rollupBenefit</th>
+        <th bgcolor="#d9edf7">rollupCost</th>
+        <th bgcolor="#d9edf7">rollupInputCount</th>
+        <th bgcolor="#d9edf7">curTotalSize</th>
+        <th bgcolor="#d9edf7">recomTotalSize</th>
+        <th bgcolor="#d9edf7">spaceLimit</th>
+        <th bgcolor="#d9edf7">k</th>
+    </tr>
+    <#list cubeBenefitList as benefit>
+        <tr>
+            <td>${benefit.projectName}</td>
+            <td>${benefit.cube}</td>
+            <td>${benefit.score}</td>
+            <td>${benefit.totalBenefit}</td>
+            <td>${benefit.queryBenefit}</td>
+            <td>${benefit.spaceBenefit}</td>
+            <td>${benefit.rollupBenefit}</td>
+            <td>${benefit.rollupCost}</td>
+            <td>${benefit.rollupInputCount}</td>
+            <td>${benefit.curTotalSize}</td>
+            <td>${benefit.recomTotalSize}</td>
+            <td>${benefit.spaceLimit}</td>
+            <td>${benefit.k}</td>
+        </tr>
+    </#list>
+
+
+</table>
+</body>
+
+</html>
+

Reply via email to