You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kylin.apache.org by ni...@apache.org on 2019/08/06 12:56:56 UTC

[kylin] branch master updated (40cab4f -> ec4bfa1)

This is an automated email from the ASF dual-hosted git repository.

nic pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/kylin.git.


    from 40cab4f  KYLIN-4121 Cleanup hive view intermediate tables after job be finished
     new 03a2be0  KYLIN-4108 Show slow query hit cube
     new ec4bfa1  KYLIN-4108 add hit cube info in bad query list

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../kylin/metadata/badquery/BadQueryEntry.java     |  13 +-
 .../badquery/BadQueryHistoryManagerTest.java       |  10 +-
 .../query/relnode/OLAPToEnumerableConverter.java   |   7 +
 .../kylin/query/util/QueryInfoCollector.java       |  39 ++--
 .../kylin/rest/service/BadQueryDetector.java       |  17 +-
 .../apache/kylin/rest/service/QueryService.java    |   2 +
 .../kylin/rest/service/BadQueryDetectorTest.java   |  49 ++++-
 .../kylin/rest/service/QueryInfoCollectorTest.java | 200 +++++++++++++++++++++
 webapp/app/js/model/jobConfig.js                   |   1 +
 webapp/app/partials/jobs/badQuery.html             |   1 +
 10 files changed, 311 insertions(+), 28 deletions(-)
 copy core-common/src/main/java/org/apache/kylin/common/lock/DistributedLockFactory.java => query/src/main/java/org/apache/kylin/query/util/QueryInfoCollector.java (52%)
 create mode 100644 server/src/test/java/org/apache/kylin/rest/service/QueryInfoCollectorTest.java


[kylin] 02/02: KYLIN-4108 add hit cube info in bad query list

Posted by ni...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nic pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kylin.git

commit ec4bfa1664d20dfbfd305c03a9b6dd4da7f4f6cc
Author: luguosheng1314 <55...@qq.com>
AuthorDate: Mon Aug 5 14:57:30 2019 +0800

    KYLIN-4108 add hit cube info in bad query list
---
 webapp/app/js/model/jobConfig.js       | 1 +
 webapp/app/partials/jobs/badQuery.html | 1 +
 2 files changed, 2 insertions(+)

diff --git a/webapp/app/js/model/jobConfig.js b/webapp/app/js/model/jobConfig.js
index 19ae882..cb07e1f 100644
--- a/webapp/app/js/model/jobConfig.js
+++ b/webapp/app/js/model/jobConfig.js
@@ -48,6 +48,7 @@ KylinApp.constant('jobConfig', {
   queryitems: [
   {attr: 'server', name: 'Server'},
   {attr: 'user', name: 'User'},
+  {attr: 'cube', name: 'Hit Cube'},
   {attr: 'sql', name: 'Sql'},
   {attr: 'adj', name: 'Description'},
   {attr: 'running_seconds', name: 'Running Seconds'},
diff --git a/webapp/app/partials/jobs/badQuery.html b/webapp/app/partials/jobs/badQuery.html
index b3e72c6..9ec420a 100644
--- a/webapp/app/partials/jobs/badQuery.html
+++ b/webapp/app/partials/jobs/badQuery.html
@@ -53,6 +53,7 @@
         {{bquery.server}}
       </td>
       <td>{{bquery.user}}</td>
+      <td>{{bquery.cube}}</td>
       <td style="max-width: 350px;word-wrap: break-word;word-break: normal;">{{bquery.sql}}</td>
       <td>
         {{bquery.adj}}


[kylin] 01/02: KYLIN-4108 Show slow query hit cube

Posted by ni...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nic pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kylin.git

commit 03a2be03de4dc294a4366e7e6e7da4ba6f7f22f0
Author: chao long <wa...@qq.com>
AuthorDate: Thu Aug 1 23:13:54 2019 +0800

    KYLIN-4108 Show slow query hit cube
---
 .../kylin/metadata/badquery/BadQueryEntry.java     |  13 +-
 .../badquery/BadQueryHistoryManagerTest.java       |  10 +-
 .../query/relnode/OLAPToEnumerableConverter.java   |   7 +
 .../kylin/query/util/QueryInfoCollector.java       |  55 ++++++
 .../kylin/rest/service/BadQueryDetector.java       |  17 +-
 .../apache/kylin/rest/service/QueryService.java    |   2 +
 .../kylin/rest/service/BadQueryDetectorTest.java   |  49 ++++-
 .../kylin/rest/service/QueryInfoCollectorTest.java | 200 +++++++++++++++++++++
 8 files changed, 339 insertions(+), 14 deletions(-)

diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/badquery/BadQueryEntry.java b/core-metadata/src/main/java/org/apache/kylin/metadata/badquery/BadQueryEntry.java
index 56b27ca..41b1d11 100644
--- a/core-metadata/src/main/java/org/apache/kylin/metadata/badquery/BadQueryEntry.java
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/badquery/BadQueryEntry.java
@@ -49,9 +49,11 @@ public class BadQueryEntry extends RootPersistentEntity implements Comparable<Ba
     private String user;
     @JsonProperty("query_id")
     private String queryId;
+    @JsonProperty("cube")
+    private String cube;
 
     public BadQueryEntry(String sql, String adj, long startTime, float runningSec, String server, String thread,
-            String user, String queryId) {
+            String user, String queryId, String cube) {
         this.updateRandomUuid();
         this.adj = adj;
         this.sql = sql;
@@ -61,6 +63,7 @@ public class BadQueryEntry extends RootPersistentEntity implements Comparable<Ba
         this.thread = thread;
         this.user = user;
         this.queryId = queryId;
+        this.cube = cube;
     }
 
     public BadQueryEntry() {
@@ -131,6 +134,14 @@ public class BadQueryEntry extends RootPersistentEntity implements Comparable<Ba
         this.thread = thread;
     }
 
+    public String getCube() {
+        return cube;
+    }
+
+    public void setCube(String cube) {
+        this.cube = cube;
+    }
+
     @Override
     public int compareTo(BadQueryEntry obj) {
         int comp = Long.compare(this.startTime, obj.startTime);
diff --git a/core-metadata/src/test/java/org/apache/kylin/metadata/badquery/BadQueryHistoryManagerTest.java b/core-metadata/src/test/java/org/apache/kylin/metadata/badquery/BadQueryHistoryManagerTest.java
index 436eb10..a3aa890 100644
--- a/core-metadata/src/test/java/org/apache/kylin/metadata/badquery/BadQueryHistoryManagerTest.java
+++ b/core-metadata/src/test/java/org/apache/kylin/metadata/badquery/BadQueryHistoryManagerTest.java
@@ -67,7 +67,7 @@ public class BadQueryHistoryManagerTest extends LocalFileMetadataTestCase {
         KylinConfig kylinConfig = getTestConfig();
         BadQueryHistoryManager manager = BadQueryHistoryManager.getInstance(kylinConfig);
         BadQueryEntry entry = new BadQueryEntry("sql", "adj", 1459362239992L, 100, "server", "t-0", "user",
-                RandomUtil.randomUUID().toString());
+                RandomUtil.randomUUID().toString(), "cube");
         BadQueryHistory history = manager.upsertEntryToProject(entry, "default");
         NavigableSet<BadQueryEntry> entries = history.getEntries();
         assertEquals(4, entries.size());
@@ -84,7 +84,7 @@ public class BadQueryHistoryManagerTest extends LocalFileMetadataTestCase {
 
         for (int i = 0; i < kylinConfig.getBadQueryHistoryNum(); i++) {
             BadQueryEntry tmp = new BadQueryEntry("sql", "adj", 1459362239993L + i, 100 + i, "server", "t-0", "user",
-                    RandomUtil.randomUUID().toString());
+                    RandomUtil.randomUUID().toString(), "cube");
             history = manager.upsertEntryToProject(tmp, "default");
         }
         assertEquals(kylinConfig.getBadQueryHistoryNum(), history.getEntries().size());
@@ -97,14 +97,14 @@ public class BadQueryHistoryManagerTest extends LocalFileMetadataTestCase {
 
         String queryId = RandomUtil.randomUUID().toString();
         manager.upsertEntryToProject(
-                new BadQueryEntry("sql", "adj", 1459362239000L, 100, "server", "t-0", "user", queryId),
+                new BadQueryEntry("sql", "adj", 1459362239000L, 100, "server", "t-0", "user", queryId, "cube"),
                 "default");
         BadQueryHistory history = manager.upsertEntryToProject(
-                new BadQueryEntry("sql", "adj2", 1459362239000L, 120, "server2", "t-1", "user", queryId), "default");
+                new BadQueryEntry("sql", "adj2", 1459362239000L, 120, "server2", "t-1", "user", queryId, "cube"), "default");
 
         NavigableSet<BadQueryEntry> entries = history.getEntries();
         BadQueryEntry newEntry = entries
-                .floor(new BadQueryEntry("sql", "adj2", 1459362239000L, 120, "server2", "t-1", "user", queryId));
+                .floor(new BadQueryEntry("sql", "adj2", 1459362239000L, 120, "server2", "t-1", "user", queryId, "cube"));
         System.out.println(newEntry);
         assertEquals("adj2", newEntry.getAdj());
         assertEquals("server2", newEntry.getServer());
diff --git a/query/src/main/java/org/apache/kylin/query/relnode/OLAPToEnumerableConverter.java b/query/src/main/java/org/apache/kylin/query/relnode/OLAPToEnumerableConverter.java
index ebfb6f4..e3b2272 100644
--- a/query/src/main/java/org/apache/kylin/query/relnode/OLAPToEnumerableConverter.java
+++ b/query/src/main/java/org/apache/kylin/query/relnode/OLAPToEnumerableConverter.java
@@ -19,6 +19,7 @@
 package org.apache.kylin.query.relnode;
 
 import java.util.List;
+import java.util.stream.Collectors;
 
 import org.apache.calcite.adapter.enumerable.EnumerableRel;
 import org.apache.calcite.adapter.enumerable.EnumerableRelImplementor;
@@ -40,6 +41,7 @@ import org.apache.kylin.query.security.QueryInterceptor;
 import org.apache.kylin.query.security.QueryInterceptorUtil;
 
 import com.google.common.collect.Lists;
+import org.apache.kylin.query.util.QueryInfoCollector;
 
 /**
  * If you're renaming this class, please keep it ending with OLAPToEnumerableConverter
@@ -93,6 +95,11 @@ public class OLAPToEnumerableConverter extends ConverterImpl implements Enumerab
 
         RealizationChooser.selectRealization(contexts);
 
+        QueryInfoCollector.current().setCubeNames(contexts.stream()
+                .filter(olapContext -> olapContext.realization != null)
+                .map(olapContext -> olapContext.realization.getCanonicalName())
+                .collect(Collectors.toList()));
+
         doAccessControl(contexts);
 
         // rewrite query if necessary
diff --git a/query/src/main/java/org/apache/kylin/query/util/QueryInfoCollector.java b/query/src/main/java/org/apache/kylin/query/util/QueryInfoCollector.java
new file mode 100644
index 0000000..d8f6171
--- /dev/null
+++ b/query/src/main/java/org/apache/kylin/query/util/QueryInfoCollector.java
@@ -0,0 +1,55 @@
+/*
+ * 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.query.util;
+
+import java.util.List;
+
+import org.apache.commons.lang.StringUtils;
+
+import com.google.common.collect.Lists;
+
+public class QueryInfoCollector {
+    private List<String> cubeNames;
+
+    private static final ThreadLocal<QueryInfoCollector> queryInfoThreadLocal = ThreadLocal.withInitial(QueryInfoCollector::new);
+
+    public static QueryInfoCollector current() {
+        return queryInfoThreadLocal.get();
+    }
+
+    public static void reset() {
+        queryInfoThreadLocal.remove();
+    }
+
+    private QueryInfoCollector() {
+        this.cubeNames = Lists.newArrayList();
+    }
+
+    public List<String> getCubeNames() {
+        return cubeNames;
+    }
+
+    public void setCubeNames(List<String> cubeNames) {
+        this.cubeNames = cubeNames;
+    }
+
+    public String getCubeNameString() {
+        return StringUtils.join(this.cubeNames, ",");
+    }
+}
diff --git a/server-base/src/main/java/org/apache/kylin/rest/service/BadQueryDetector.java b/server-base/src/main/java/org/apache/kylin/rest/service/BadQueryDetector.java
index 3f3db45..edabbb5 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/service/BadQueryDetector.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/service/BadQueryDetector.java
@@ -28,6 +28,7 @@ import java.util.concurrent.ConcurrentMap;
 import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.metadata.badquery.BadQueryEntry;
 import org.apache.kylin.metadata.badquery.BadQueryHistoryManager;
+import org.apache.kylin.query.util.QueryInfoCollector;
 import org.apache.kylin.rest.request.SQLRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -102,7 +103,7 @@ public class BadQueryDetector extends Thread {
         for (Notifier notifier : notifiers) {
             try {
                 notifier.badQueryFound(adj, runningSec, //
-                        e.startTime, e.sqlRequest.getProject(), e.sqlRequest.getSql(), e.user, e.thread, e.queryId);
+                        e.startTime, e.sqlRequest.getProject(), e.sqlRequest.getSql(), e.user, e.thread, e.queryId, e.collector);
             } catch (Exception ex) {
                 logger.error("", ex);
             }
@@ -110,7 +111,7 @@ public class BadQueryDetector extends Thread {
     }
 
     public void queryStart(Thread thread, SQLRequest sqlRequest, String user, String queryId) {
-        runningQueries.put(thread, new Entry(sqlRequest, user, thread, queryId));
+        runningQueries.put(thread, new Entry(sqlRequest, user, thread, queryId, QueryInfoCollector.current()));
     }
 
     public void queryEnd(Thread thread) {
@@ -193,13 +194,13 @@ public class BadQueryDetector extends Thread {
 
     public interface Notifier {
         void badQueryFound(String adj, float runningSec, long startTime, String project, String sql, String user,
-                Thread t, String queryId);
+                Thread t, String queryId, QueryInfoCollector collector);
     }
 
     private class LoggerNotifier implements Notifier {
         @Override
         public void badQueryFound(String adj, float runningSec, long startTime, String project, String sql, String user,
-                Thread t, String queryId) {
+                Thread t, String queryId, QueryInfoCollector collector) {
             logger.info("{} query has been running {} seconds (project:{}, thread: 0x{}, user:{}, query id:{}) -- {}",
                     adj, runningSec, project, Long.toHexString(t.getId()), user, queryId, sql);
         }
@@ -220,10 +221,10 @@ public class BadQueryDetector extends Thread {
 
         @Override
         public void badQueryFound(String adj, float runningSec, long startTime, String project, String sql, String user,
-                Thread t, String queryId) {
+                Thread t, String queryId, QueryInfoCollector collector) {
             try {
                 BadQueryEntry entry = new BadQueryEntry(sql, adj, startTime, runningSec, serverHostname, t.getName(),
-                        user, queryId);
+                        user, queryId, collector.getCubeNameString());
                 badQueryManager.upsertEntryToProject(entry, project);
             } catch (IOException e) {
                 logger.error("Error in bad query persistence.", e);
@@ -237,13 +238,15 @@ public class BadQueryDetector extends Thread {
         final Thread thread;
         final String user;
         final String queryId;
+        final QueryInfoCollector collector;
 
-        Entry(SQLRequest sqlRequest, String user, Thread thread, String queryId) {
+        Entry(SQLRequest sqlRequest, String user, Thread thread, String queryId, QueryInfoCollector collector) {
             this.sqlRequest = sqlRequest;
             this.startTime = System.currentTimeMillis();
             this.thread = thread;
             this.user = user;
             this.queryId = queryId;
+            this.collector = collector;
         }
 
         @Override
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 59b2b61..171bd07 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
@@ -97,6 +97,7 @@ 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;
+import org.apache.kylin.query.util.QueryInfoCollector;
 import org.apache.kylin.query.util.QueryUtil;
 import org.apache.kylin.query.util.TempStatementUtil;
 import org.apache.kylin.rest.constant.Constant;
@@ -446,6 +447,7 @@ public class QueryService extends BasicService {
         } finally {
             BackdoorToggles.cleanToggles();
             QueryContextFacade.resetCurrent();
+            QueryInfoCollector.reset();
         }
     }
 
diff --git a/server/src/test/java/org/apache/kylin/rest/service/BadQueryDetectorTest.java b/server/src/test/java/org/apache/kylin/rest/service/BadQueryDetectorTest.java
index ce1bedc..d1e2af9 100644
--- a/server/src/test/java/org/apache/kylin/rest/service/BadQueryDetectorTest.java
+++ b/server/src/test/java/org/apache/kylin/rest/service/BadQueryDetectorTest.java
@@ -20,12 +20,21 @@ package org.apache.kylin.rest.service;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 
+import java.io.IOException;
 import java.util.ArrayList;
+import java.util.NavigableSet;
+import java.util.Set;
 
+import com.google.common.collect.Lists;
+import org.apache.kylin.common.KylinConfig;
 import org.apache.kylin.common.util.LocalFileMetadataTestCase;
 import org.apache.kylin.common.util.RandomUtil;
 import org.apache.kylin.metadata.badquery.BadQueryEntry;
+import org.apache.kylin.metadata.badquery.BadQueryHistory;
+import org.apache.kylin.metadata.badquery.BadQueryHistoryManager;
+import org.apache.kylin.query.util.QueryInfoCollector;
 import org.apache.kylin.rest.request.SQLRequest;
 import org.junit.After;
 import org.junit.Before;
@@ -55,7 +64,7 @@ public class BadQueryDetectorTest extends LocalFileMetadataTestCase {
         badQueryDetector.registerNotifier(new BadQueryDetector.Notifier() {
             @Override
             public void badQueryFound(String adj, float runningSec, long startTime, String project, String sql,
-                    String user, Thread t, String queryId) {
+                    String user, Thread t, String queryId, QueryInfoCollector collect) {
                 alerts.add(new String[] { adj, sql });
             }
         });
@@ -82,4 +91,42 @@ public class BadQueryDetectorTest extends LocalFileMetadataTestCase {
         // end notifies a Pushdown
         assertArrayEquals(new String[] { BadQueryEntry.ADJ_PUSHDOWN, mockSql }, alerts.get(1));
     }
+
+    @Test
+    public void testSlowQuery() throws InterruptedException, IOException {
+        int alertMB = BadQueryDetector.getSystemAvailMB() * 2;
+        int alertRunningSec = 2;
+        String mockSql = "select * from just_a_test";
+
+        BadQueryDetector badQueryDetector = new BadQueryDetector(alertRunningSec * 1000, alertMB, alertRunningSec, 1000);
+        badQueryDetector.start();
+
+        SQLRequest sqlRequest = new SQLRequest();
+        sqlRequest.setProject("test_project");
+        sqlRequest.setSql(mockSql);
+        badQueryDetector.queryStart(Thread.currentThread(), sqlRequest, "user", RandomUtil.randomUUID().toString());
+
+        try {
+            Thread.sleep(1000);
+
+            QueryInfoCollector.current().setCubeNames(Lists.newArrayList("[CUBE[name=TEST_CUBE]]"));
+
+            Thread.sleep((alertRunningSec * 2 + 1) * 1000);
+
+            BadQueryHistory badQueryHistory = BadQueryHistoryManager.getInstance(KylinConfig.getInstanceFromEnv()).getBadQueriesForProject("test_project");
+            Set entries = badQueryHistory.getEntries();
+
+            assertEquals(1, entries.size());
+
+            BadQueryEntry entry = (BadQueryEntry) ((NavigableSet) entries).pollFirst();
+
+            assertNotNull(entry);
+            assertEquals(BadQueryEntry.ADJ_SLOW, entry.getAdj());
+            assertEquals("[CUBE[name=TEST_CUBE]]", entry.getCube());
+        } finally {
+            badQueryDetector.queryEnd(Thread.currentThread(), BadQueryEntry.ADJ_SLOW);
+            badQueryDetector.interrupt();
+            QueryInfoCollector.reset();
+        }
+    }
 }
diff --git a/server/src/test/java/org/apache/kylin/rest/service/QueryInfoCollectorTest.java b/server/src/test/java/org/apache/kylin/rest/service/QueryInfoCollectorTest.java
new file mode 100644
index 0000000..91cf190
--- /dev/null
+++ b/server/src/test/java/org/apache/kylin/rest/service/QueryInfoCollectorTest.java
@@ -0,0 +1,200 @@
+/*
+ * 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.io.IOException;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.QueryContextFacade;
+import org.apache.kylin.common.debug.BackdoorToggles;
+import org.apache.kylin.common.util.LocalFileMetadataTestCase;
+import org.apache.kylin.cube.CubeInstance;
+import org.apache.kylin.cube.CubeManager;
+import org.apache.kylin.metadata.realization.RealizationStatusEnum;
+import org.apache.kylin.query.QueryConnection;
+import org.apache.kylin.query.util.QueryInfoCollector;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class QueryInfoCollectorTest extends LocalFileMetadataTestCase {
+    private Connection connection = null;
+    private Statement statement = null;
+    private ResultSet resultSet = null;
+
+    @Before
+    public void setUp() throws Exception {
+        this.createTestMetadata();
+    }
+
+    @After
+    public void after() throws Exception {
+        this.cleanupTestMetadata();
+    }
+
+    @Test
+    public void testQueryInfoCollector() throws Exception {
+        prepareContexts();
+        enableCube("ci_inner_join_cube", "ci_left_join_cube");
+
+        try {
+            connection = QueryConnection.getConnection("default");
+            statement = connection.createStatement();
+            String sql = "select count(*) as cnt1 from test_kylin_fact inner join test_account on seller_id = account_id\n" +
+                    "union all\n" +
+                    "select count(*) as cnt2 from test_kylin_fact left join test_account on seller_id = account_id";
+            resultSet = statement.executeQuery(sql);
+
+            Assert.assertNotNull(resultSet);
+
+            List<String> cubes = QueryInfoCollector.current().getCubeNames();
+
+            Assert.assertTrue(cubes.contains("HYBRID[name=ci_inner_join_hybrid]"));
+            Assert.assertTrue(cubes.contains("CUBE[name=ci_left_join_cube]"));
+        } finally {
+            cleanContexts();
+            close();
+        }
+    }
+
+    @Test
+    public void testQueryInfoCollectorReset() throws Exception {
+        prepareContexts();
+        enableCube("ci_left_join_cube");
+
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        try {
+            String project = "default";
+            String expectedCube = "CUBE[name=ci_left_join_cube]";
+
+            String sqlWithCube = "select count(*) from test_kylin_fact";
+            FutureTask<String> queryTask1 = new FutureTask<String>(new QueryCallable(sqlWithCube, project, false));
+            executorService.submit(queryTask1);
+
+            String cubeName1 = queryTask1.get(2, TimeUnit.MINUTES);
+
+            Assert.assertTrue(queryTask1.isDone());
+            Assert.assertEquals(expectedCube, cubeName1);
+
+            String sqlNoCube = "select * from test_account";
+            FutureTask<String> queryTask2 = new FutureTask<String>(new QueryCallable(sqlNoCube, project, true));
+            executorService.submit(queryTask2);
+
+            String cubeName2 = queryTask2.get(2, TimeUnit.MINUTES);
+
+            Assert.assertTrue(queryTask2.isDone());
+            Assert.assertEquals(cubeName1, cubeName2);
+
+            FutureTask<String> queryTask3 = new FutureTask<String>(new QueryCallable(sqlNoCube, project, true));
+            executorService.submit(queryTask3);
+
+            String cubeName3 = queryTask3.get(2, TimeUnit.MINUTES);
+
+            Assert.assertTrue(queryTask3.isDone());
+            Assert.assertEquals("", cubeName3);
+        } finally {
+            executorService.shutdown();
+            cleanContexts();
+        }
+    }
+
+    class QueryCallable implements Callable<String> {
+        private String sql;
+        private String project;
+        private boolean reset;
+
+        public QueryCallable(String sql, String project, boolean reset) {
+            this.sql = sql;
+            this.project = project;
+            this.reset = reset;
+        }
+
+        @Override
+        public String call() throws Exception {
+            Connection connection = QueryConnection.getConnection(project);
+            Statement statement = connection.createStatement();
+
+            try {
+                statement.executeQuery(sql);
+                return QueryInfoCollector.current().getCubeNameString();
+            } catch (Exception e) {
+                return QueryInfoCollector.current().getCubeNameString();
+            } finally {
+                if (reset) {
+                    QueryInfoCollector.reset();
+                }
+            }
+        }
+    }
+
+    private void cleanContexts() {
+        QueryContextFacade.resetCurrent();
+        QueryInfoCollector.reset();
+        BackdoorToggles.cleanToggles();
+    }
+
+    private void prepareContexts() {
+        QueryContextFacade.resetCurrent();
+        BackdoorToggles.addToggle(BackdoorToggles.DEBUG_TOGGLE_PREPARE_ONLY, "true");
+    }
+
+    private void enableCube(String... cubes) throws IOException {
+        CubeManager cubeManager = CubeManager.getInstance(KylinConfig.getInstanceFromEnv());
+
+        for (String cube : cubes) {
+            CubeInstance cubeInstance = cubeManager.getCube(cube);
+            cubeManager.updateCubeStatus(cubeInstance, RealizationStatusEnum.READY);
+        }
+    }
+
+    private void close() {
+        if (resultSet != null) {
+            try {
+                resultSet.close();
+            } catch (SQLException e) {
+                // ignore
+            }
+        }
+        if (statement != null) {
+            try {
+                statement.close();
+            } catch (SQLException e) {
+                // ignore
+            }
+        }
+        if (connection != null) {
+            try {
+                connection.close();
+            } catch (SQLException e) {
+                // ignore
+            }
+        }
+    }
+}