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 2016/03/30 15:16:47 UTC

kylin git commit: KYLIN-1550 Persist recent bad queries in scope of projects

Repository: kylin
Updated Branches:
  refs/heads/master 19ef6c374 -> cfb3c440c


KYLIN-1550 Persist recent bad queries in scope of projects


Project: http://git-wip-us.apache.org/repos/asf/kylin/repo
Commit: http://git-wip-us.apache.org/repos/asf/kylin/commit/cfb3c440
Tree: http://git-wip-us.apache.org/repos/asf/kylin/tree/cfb3c440
Diff: http://git-wip-us.apache.org/repos/asf/kylin/diff/cfb3c440

Branch: refs/heads/master
Commit: cfb3c440c0f86f6987ec6e898f58b7fe5f292b95
Parents: 19ef6c3
Author: lidongsjtu <li...@apache.org>
Authored: Wed Mar 30 21:10:22 2016 +0800
Committer: lidongsjtu <li...@apache.org>
Committed: Wed Mar 30 21:16:22 2016 +0800

----------------------------------------------------------------------
 .../apache/kylin/common/KylinConfigBase.java    |   8 ++
 .../kylin/common/persistence/ResourceStore.java |   1 +
 .../kylin/metadata/badquery/BadQueryEntry.java  |  96 ++++++++++++++++
 .../metadata/badquery/BadQueryHistory.java      |  72 ++++++++++++
 .../badquery/BadQueryHistoryManager.java        | 114 +++++++++++++++++++
 .../kylin/metadata/project/ProjectManager.java  |   2 +
 .../badquery/BadQueryHistoryManagerTest.java    |  85 ++++++++++++++
 .../localmeta/bad_query/default.json            |  25 ++++
 .../kylin/rest/service/BadQueryDetector.java    |  67 +++++++++--
 .../rest/service/BadQueryDetectorTest.java      |   2 +-
 10 files changed, 459 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kylin/blob/cfb3c440/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
----------------------------------------------------------------------
diff --git a/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java b/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
index c37f888..bdcdd1d 100644
--- a/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
+++ b/core-common/src/main/java/org/apache/kylin/common/KylinConfigBase.java
@@ -438,6 +438,14 @@ public class KylinConfigBase implements Serializable {
         return Integer.parseInt(getOptional("kylin.query.badquery.stacktrace.depth", "10"));
     }
 
+    public int getBadQueryHistoryNum() {
+        return Integer.parseInt(getOptional("kylin.query.badquery.history.num", "10"));
+    }
+
+    public int getBadQueryDefaultAlertingSeconds() {
+        return Integer.parseInt(getOptional("kylin.query.badquery.default.alerting.seconds", "90"));
+    }
+
     public int getCachedDictMaxEntrySize() {
         return Integer.parseInt(getOptional("kylin.dict.cache.max.entry", "3000"));
     }

http://git-wip-us.apache.org/repos/asf/kylin/blob/cfb3c440/core-common/src/main/java/org/apache/kylin/common/persistence/ResourceStore.java
----------------------------------------------------------------------
diff --git a/core-common/src/main/java/org/apache/kylin/common/persistence/ResourceStore.java b/core-common/src/main/java/org/apache/kylin/common/persistence/ResourceStore.java
index b07458c..cf00c05 100644
--- a/core-common/src/main/java/org/apache/kylin/common/persistence/ResourceStore.java
+++ b/core-common/src/main/java/org/apache/kylin/common/persistence/ResourceStore.java
@@ -59,6 +59,7 @@ abstract public class ResourceStore {
     public static final String KAFKA_RESOURCE_ROOT = "/kafka";
     public static final String STREAMING_OUTPUT_RESOURCE_ROOT = "/streaming_output";
     public static final String CUBE_STATISTICS_ROOT = "/cube_statistics";
+    public static final String BAD_QUERY_RESOURCE_ROOT = "/bad_query";
 
     private static final ConcurrentHashMap<KylinConfig, ResourceStore> CACHE = new ConcurrentHashMap<KylinConfig, ResourceStore>();
 

http://git-wip-us.apache.org/repos/asf/kylin/blob/cfb3c440/core-metadata/src/main/java/org/apache/kylin/metadata/badquery/BadQueryEntry.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..658cc0a
--- /dev/null
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/badquery/BadQueryEntry.java
@@ -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.
+ */
+
+package org.apache.kylin.metadata.badquery;
+
+import org.apache.kylin.common.persistence.RootPersistentEntity;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
+public class BadQueryEntry extends RootPersistentEntity implements Comparable<BadQueryEntry> {
+
+    @JsonProperty("adj")
+    private String adj;
+    @JsonProperty("sql")
+    private String sql;
+    @JsonProperty("start_time")
+    private long startTime;
+    @JsonProperty("server")
+    private String server;
+    @JsonProperty("thread")
+    private String thread;
+
+    public BadQueryEntry(String sql, String adj, long startTime, String server, String thread) {
+        this.updateRandomUuid();
+        this.adj = adj;
+        this.sql = sql;
+        this.startTime = startTime;
+        this.server = server;
+        this.thread = thread;
+    }
+
+    public BadQueryEntry() {
+    }
+
+    public String getAdj() {
+        return adj;
+    }
+
+    public void setAdj(String adj) {
+        this.adj = adj;
+    }
+
+    public String getSql() {
+        return sql;
+    }
+
+    public void setSql(String sql) {
+        this.sql = sql;
+    }
+
+    public long getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(long startTime) {
+        this.startTime = startTime;
+    }
+
+    public String getServer() {
+        return server;
+    }
+
+    public void setServer(String server) {
+        this.server = server;
+    }
+
+    public String getThread() {
+        return thread;
+    }
+
+    public void setThread(String thread) {
+        this.thread = thread;
+    }
+
+    @Override
+    public int compareTo(BadQueryEntry obj) {
+        return (int) (this.startTime - obj.startTime);
+    }
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/cfb3c440/core-metadata/src/main/java/org/apache/kylin/metadata/badquery/BadQueryHistory.java
----------------------------------------------------------------------
diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/badquery/BadQueryHistory.java b/core-metadata/src/main/java/org/apache/kylin/metadata/badquery/BadQueryHistory.java
new file mode 100644
index 0000000..7792f0b
--- /dev/null
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/badquery/BadQueryHistory.java
@@ -0,0 +1,72 @@
+/*
+ * 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.metadata.badquery;
+
+import java.util.NavigableSet;
+import java.util.TreeSet;
+
+import org.apache.kylin.common.persistence.ResourceStore;
+import org.apache.kylin.common.persistence.RootPersistentEntity;
+import org.apache.kylin.metadata.MetadataConstants;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@SuppressWarnings("serial")
+@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
+public class BadQueryHistory extends RootPersistentEntity {
+
+    @JsonProperty("project")
+    private String project;
+    @JsonProperty("entries")
+    private NavigableSet<BadQueryEntry> entries = new TreeSet<>();
+
+    public BadQueryHistory() {
+    }
+
+    public BadQueryHistory(String project) {
+        this.updateRandomUuid();
+        this.project = project;
+    }
+
+    public NavigableSet<BadQueryEntry> getEntries() {
+        return entries;
+    }
+
+    public void setEntries(NavigableSet<BadQueryEntry> entries) {
+        this.entries = entries;
+    }
+
+    public String getProject() {
+        return project;
+    }
+
+    public void setProject(String project) {
+        this.project = project;
+    }
+
+    public String getResourcePath() {
+        return ResourceStore.BAD_QUERY_RESOURCE_ROOT + "/" + project + MetadataConstants.FILE_SURFIX;
+    }
+
+    @Override
+    public String toString() {
+        return "BadQueryHistory [ project=" + project + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/cfb3c440/core-metadata/src/main/java/org/apache/kylin/metadata/badquery/BadQueryHistoryManager.java
----------------------------------------------------------------------
diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/badquery/BadQueryHistoryManager.java b/core-metadata/src/main/java/org/apache/kylin/metadata/badquery/BadQueryHistoryManager.java
new file mode 100644
index 0000000..3165c13
--- /dev/null
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/badquery/BadQueryHistoryManager.java
@@ -0,0 +1,114 @@
+/*
+ * 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.metadata.badquery;
+
+import java.io.IOException;
+import java.util.NavigableSet;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.persistence.JsonSerializer;
+import org.apache.kylin.common.persistence.ResourceStore;
+import org.apache.kylin.common.persistence.Serializer;
+import org.apache.kylin.metadata.MetadataConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BadQueryHistoryManager {
+    public static final Serializer<BadQueryHistory> BAD_QUERY_INSTANCE_SERIALIZER = new JsonSerializer<>(BadQueryHistory.class);
+    private static final Logger logger = LoggerFactory.getLogger(BadQueryHistoryManager.class);
+
+    private static final ConcurrentHashMap<KylinConfig, BadQueryHistoryManager> CACHE = new ConcurrentHashMap<>();
+    private KylinConfig kylinConfig;
+
+    private BadQueryHistoryManager(KylinConfig config) throws IOException {
+        logger.info("Initializing BadQueryHistoryManager with config " + config);
+        this.kylinConfig = config;
+    }
+
+    public static BadQueryHistoryManager getInstance(KylinConfig config) {
+        BadQueryHistoryManager r = CACHE.get(config);
+        if (r != null) {
+            return r;
+        }
+
+        synchronized (BadQueryHistoryManager.class) {
+            r = CACHE.get(config);
+            if (r != null) {
+                return r;
+            }
+            try {
+                r = new BadQueryHistoryManager(config);
+                CACHE.put(config, r);
+                if (CACHE.size() > 1) {
+                    logger.warn("More than one singleton exist");
+                }
+                return r;
+            } catch (IOException e) {
+                throw new IllegalStateException("Failed to init BadQueryHistoryManager from " + config, e);
+            }
+        }
+    }
+
+    public static void clearCache() {
+        CACHE.clear();
+    }
+
+    private ResourceStore getStore() {
+        return ResourceStore.getStore(this.kylinConfig);
+    }
+
+    public BadQueryHistory getBadQueriesForProject(String project) throws IOException {
+        BadQueryHistory badQueryHistory = getStore().getResource(getResourcePathForProject(project), BadQueryHistory.class, BAD_QUERY_INSTANCE_SERIALIZER);
+        if (badQueryHistory == null) {
+            badQueryHistory = new BadQueryHistory(project);
+        }
+
+        logger.debug("Loaded " + badQueryHistory.getEntries().size() + " Bad Query(s)");
+        return badQueryHistory;
+    }
+
+    public BadQueryHistory addEntryToProject(BadQueryEntry badQueryEntry, String project) throws IOException {
+        if (StringUtils.isEmpty(project) || badQueryEntry.getAdj() == null || badQueryEntry.getSql() == null)
+            throw new IllegalArgumentException();
+
+        BadQueryHistory badQueryHistory = getBadQueriesForProject(project);
+        NavigableSet<BadQueryEntry> entries = badQueryHistory.getEntries();
+        if (entries.size() >= kylinConfig.getBadQueryHistoryNum()) {
+            entries.pollFirst();
+        }
+        entries.add(badQueryEntry);
+
+        getStore().putResource(badQueryHistory.getResourcePath(), badQueryHistory, BAD_QUERY_INSTANCE_SERIALIZER);
+        return badQueryHistory;
+    }
+
+    public BadQueryHistory addEntryToProject(String sql, String adj, long startTime, String server, String threadName, String project) throws IOException {
+        return addEntryToProject(new BadQueryEntry(sql, adj, startTime, server, threadName), project);
+    }
+
+    public void removeBadQueryHistory(String project) throws IOException {
+        getStore().deleteResource(getResourcePathForProject(project));
+    }
+
+    public String getResourcePathForProject(String project) {
+        return ResourceStore.BAD_QUERY_RESOURCE_ROOT + "/" + project + MetadataConstants.FILE_SURFIX;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/kylin/blob/cfb3c440/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java
----------------------------------------------------------------------
diff --git a/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java b/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java
index c8d0793..65b3719 100644
--- a/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java
+++ b/core-metadata/src/main/java/org/apache/kylin/metadata/project/ProjectManager.java
@@ -31,6 +31,7 @@ import org.apache.kylin.common.persistence.Serializer;
 import org.apache.kylin.common.restclient.Broadcaster;
 import org.apache.kylin.common.restclient.CaseInsensitiveStringCache;
 import org.apache.kylin.metadata.MetadataManager;
+import org.apache.kylin.metadata.badquery.BadQueryHistoryManager;
 import org.apache.kylin.metadata.model.ColumnDesc;
 import org.apache.kylin.metadata.model.ExternalFilterDesc;
 import org.apache.kylin.metadata.model.MeasureDesc;
@@ -168,6 +169,7 @@ public class ProjectManager {
         logger.info("Dropping project '" + projectInstance.getName() + "'");
 
         removeProject(projectInstance);
+        BadQueryHistoryManager.getInstance(config).removeBadQueryHistory(projectName);
 
         return projectInstance;
     }

http://git-wip-us.apache.org/repos/asf/kylin/blob/cfb3c440/core-metadata/src/test/java/org/apache/kylin/metadata/badquery/BadQueryHistoryManagerTest.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..28b1805
--- /dev/null
+++ b/core-metadata/src/test/java/org/apache/kylin/metadata/badquery/BadQueryHistoryManagerTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.metadata.badquery;
+
+import java.io.IOException;
+import java.util.NavigableSet;
+
+import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.common.util.JsonUtil;
+import org.apache.kylin.common.util.LocalFileMetadataTestCase;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class BadQueryHistoryManagerTest extends LocalFileMetadataTestCase {
+    @Before
+    public void setUp() throws Exception {
+        this.createTestMetadata();
+    }
+
+    @After
+    public void after() throws Exception {
+        this.cleanupTestMetadata();
+    }
+
+    @Test
+    public void testBasics() throws Exception {
+        BadQueryHistory history = BadQueryHistoryManager.getInstance(getTestConfig()).getBadQueriesForProject("default");
+        System.out.println(JsonUtil.writeValueAsIndentString(history));
+
+        NavigableSet<BadQueryEntry> entries = history.getEntries();
+        assertEquals(2, entries.size());
+
+        BadQueryEntry entry1 = entries.first();
+        assertEquals("Slow", entry1.getAdj());
+        assertEquals("sandbox.hortonworks.com", entry1.getServer());
+        assertEquals("select count(*) from test_kylin_fact", entry1.getSql());
+
+        entries.pollFirst();
+        BadQueryEntry entry2 = entries.first();
+        assertTrue(entry2.getStartTime() > entry1.getStartTime());
+    }
+
+    @Test
+    public void testAddEntryToProject() throws IOException {
+        KylinConfig kylinConfig = getTestConfig();
+        BadQueryHistoryManager manager = BadQueryHistoryManager.getInstance(kylinConfig);
+        BadQueryHistory history = manager.addEntryToProject("sql", "adj", 123, "server", "t-0", "default");
+
+        NavigableSet<BadQueryEntry> entries = history.getEntries();
+        assertEquals(3, entries.size());
+
+        BadQueryEntry newEntry = entries.last();
+        assertEquals("sql", newEntry.getSql());
+        assertEquals("adj", newEntry.getAdj());
+        assertEquals(123, newEntry.getStartTime());
+        assertEquals("server", newEntry.getServer());
+        assertEquals("t-0", newEntry.getThread());
+
+        for (int i = 0; i < kylinConfig.getBadQueryHistoryNum(); i++) {
+            history = manager.addEntryToProject("sql", "adj", 123 + i + 1, "server", "t-0", "default");
+        }
+
+        assertEquals(kylinConfig.getBadQueryHistoryNum(), history.getEntries().size());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/kylin/blob/cfb3c440/examples/test_case_data/localmeta/bad_query/default.json
----------------------------------------------------------------------
diff --git a/examples/test_case_data/localmeta/bad_query/default.json b/examples/test_case_data/localmeta/bad_query/default.json
new file mode 100644
index 0000000..c7f26e1
--- /dev/null
+++ b/examples/test_case_data/localmeta/bad_query/default.json
@@ -0,0 +1,25 @@
+{
+  "uuid": "ba0c9cad-35c1-4f4b-8c10-669248842c2f",
+  "project": "default",
+  "entries": [
+    {
+      "uuid": "ca0c9cad-35c1-4f4b-8c10-669248842c2f",
+      "adj": "Slow",
+      "sql": "select count(*) from test_kylin_fact",
+      "server": "sandbox.hortonworks.com",
+      "thread": "t-0",
+      "last_modified": 0,
+      "start_time": 1459362236585
+    },
+    {
+      "uuid": "da0c9cad-35c1-4f4b-8c10-669248842c2f",
+      "adj": "Slow",
+      "sql": "select * from test_kylin_fact limit 1",
+      "server": "sandbox.hortonworks.com",
+      "thread": "t-0",
+      "last_modified": 0,
+      "start_time": 1459362239990
+    }
+  ],
+  "last_modified": 1459362294902
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/kylin/blob/cfb3c440/server/src/main/java/org/apache/kylin/rest/service/BadQueryDetector.java
----------------------------------------------------------------------
diff --git a/server/src/main/java/org/apache/kylin/rest/service/BadQueryDetector.java b/server/src/main/java/org/apache/kylin/rest/service/BadQueryDetector.java
index 71ab26b..4e313ba 100644
--- a/server/src/main/java/org/apache/kylin/rest/service/BadQueryDetector.java
+++ b/server/src/main/java/org/apache/kylin/rest/service/BadQueryDetector.java
@@ -18,11 +18,15 @@
 
 package org.apache.kylin.rest.service;
 
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.concurrent.ConcurrentMap;
 
 import org.apache.kylin.common.KylinConfig;
+import org.apache.kylin.metadata.badquery.BadQueryHistoryManager;
 import org.apache.kylin.rest.request.SQLRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -37,11 +41,19 @@ public class BadQueryDetector extends Thread {
     private final long detectionInterval;
     private final int alertMB;
     private final int alertRunningSec;
+    private KylinConfig kylinConfig;
 
     private ArrayList<Notifier> notifiers = new ArrayList<Notifier>();
 
     public BadQueryDetector() {
-        this(60 * 1000, 100, 90); // 1 minute, 100 MB, 90 seconds
+        super("BadQueryDetector");
+        this.setDaemon(true);
+        this.kylinConfig = KylinConfig.getInstanceFromEnv();
+        this.detectionInterval = 60 * 1000;
+        this.alertMB = 100;
+        this.alertRunningSec = kylinConfig.getBadQueryDefaultAlertingSeconds();
+
+        initNotifiers();
     }
 
     public BadQueryDetector(long detectionInterval, int alertMB, int alertRunningSec) {
@@ -50,23 +62,24 @@ public class BadQueryDetector extends Thread {
         this.detectionInterval = detectionInterval;
         this.alertMB = alertMB;
         this.alertRunningSec = alertRunningSec;
+        this.kylinConfig = KylinConfig.getInstanceFromEnv();
 
-        this.notifiers.add(new Notifier() {
-            @Override
-            public void badQueryFound(String adj, int runningSec, String sql, Thread t) {
-                logger.info(adj + " query has been running " + runningSec + " seconds (thread id 0x" + Long.toHexString(t.getId()) + ") -- " + sql);
-            }
-        });
+        initNotifiers();
+    }
+
+    private void initNotifiers() {
+        this.notifiers.add(new LoggerNotifier());
+        this.notifiers.add(new PersistenceNotifier());
     }
 
     public void registerNotifier(Notifier notifier) {
         notifiers.add(notifier);
     }
 
-    private void notify(String adj, int runningSec, String sql, Thread t) {
+    private void notify(String adj, int runningSec, long startTime, String project, String sql, Thread t) {
         for (Notifier notifier : notifiers) {
             try {
-                notifier.badQueryFound(adj, runningSec, sql, t);
+                notifier.badQueryFound(adj, runningSec, startTime, project, sql, t);
             } catch (Exception e) {
                 logger.error("", e);
             }
@@ -74,7 +87,37 @@ public class BadQueryDetector extends Thread {
     }
 
     public interface Notifier {
-        void badQueryFound(String adj, int runningSec, String sql, Thread t);
+        void badQueryFound(String adj, int runningSec, long startTime, String project, String sql, Thread t);
+    }
+
+    private class LoggerNotifier implements Notifier {
+        @Override
+        public void badQueryFound(String adj, int runningSec, long startTime, String project, String sql, Thread t) {
+            logger.info(adj + " query has been running " + runningSec + " seconds (project:" + project + ", thread: 0x" + Long.toHexString(t.getId()) + ") -- " + sql);
+        }
+    }
+
+    private class PersistenceNotifier implements Notifier {
+        BadQueryHistoryManager badQueryManager = BadQueryHistoryManager.getInstance(kylinConfig);
+        String serverHostname;
+
+        public PersistenceNotifier() {
+            try {
+                serverHostname = InetAddress.getLocalHost().getHostName();
+            } catch (UnknownHostException e) {
+                serverHostname = "Unknow";
+                logger.warn("Error in get current hostname.", e);
+            }
+        }
+
+        @Override
+        public void badQueryFound(String adj, int runningSec, long startTime, String project, String sql, Thread t) {
+            try {
+                badQueryManager.addEntryToProject(sql, adj, startTime, serverHostname, t.getName(), project);
+            } catch (IOException e) {
+                logger.error("Error in bad query persistence.", e);
+            }
+        }
     }
 
     public void queryStart(Thread thread, SQLRequest sqlRequest) {
@@ -128,7 +171,7 @@ public class BadQueryDetector extends Thread {
         for (Entry e : entries) {
             int runningSec = (int) ((now - e.startTime) / 1000);
             if (runningSec >= alertRunningSec) {
-                notify("Slow", runningSec, e.sqlRequest.getSql(), e.thread);
+                notify("Slow", runningSec, e.startTime, e.sqlRequest.getProject(), e.sqlRequest.getSql(), e.thread);
                 dumpStackTrace(e.thread);
             } else {
                 break; // entries are sorted by startTime
@@ -143,7 +186,7 @@ public class BadQueryDetector extends Thread {
 
     // log the stack trace of bad query thread for further analysis
     private void dumpStackTrace(Thread t) {
-        int maxStackTraceDepth = KylinConfig.getInstanceFromEnv().getBadQueryStackTraceDepth();
+        int maxStackTraceDepth = kylinConfig.getBadQueryStackTraceDepth();
         int current = 0;
 
         StackTraceElement[] stackTrace = t.getStackTrace();

http://git-wip-us.apache.org/repos/asf/kylin/blob/cfb3c440/server/src/test/java/org/apache/kylin/rest/service/BadQueryDetectorTest.java
----------------------------------------------------------------------
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 3481fa7..d4223a8 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
@@ -51,7 +51,7 @@ public class BadQueryDetectorTest extends LocalFileMetadataTestCase {
         BadQueryDetector badQueryDetector = new BadQueryDetector(alertRunningSec * 1000, alertMB, alertRunningSec);
         badQueryDetector.registerNotifier(new BadQueryDetector.Notifier() {
             @Override
-            public void badQueryFound(String adj, int runningSec, String sql, Thread t) {
+            public void badQueryFound(String adj, int runningSec, long startTime, String project, String sql, Thread t) {
                 alerts.add(new String[] { adj, sql });
             }
         });