You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@drill.apache.org by ja...@apache.org on 2014/07/21 04:46:34 UTC
[12/17] git commit: DRILL-77: REST API implemented using jersey and
jackson + graph viz.
DRILL-77: REST API implemented using jersey and jackson + graph viz.
Project: http://git-wip-us.apache.org/repos/asf/incubator-drill/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-drill/commit/65c83582
Tree: http://git-wip-us.apache.org/repos/asf/incubator-drill/tree/65c83582
Diff: http://git-wip-us.apache.org/repos/asf/incubator-drill/diff/65c83582
Branch: refs/heads/master
Commit: 65c835824c864192cbd8612b14d08d816360d31e
Parents: 02f1c82
Author: Sudheesh Katkam <sk...@maprtech.com>
Authored: Tue Jul 8 17:23:57 2014 -0700
Committer: Jacques Nadeau <ja...@apache.org>
Committed: Sun Jul 20 16:33:39 2014 -0700
----------------------------------------------------------------------
.../drill/exec/server/rest/DrillRestServer.java | 1 +
.../exec/server/rest/PluginConfigWrapper.java | 78 +++++++
.../exec/server/rest/ProfileResources.java | 155 ++++++++-----
.../drill/exec/server/rest/QueryResources.java | 115 +++-------
.../drill/exec/server/rest/QueryWrapper.java | 165 ++++++++++++++
.../exec/server/rest/StorageResources.java | 228 +++++++++++--------
.../exec/server/rest/WebResourceServer.java | 71 ++++++
.../src/main/resources/rest/generic.ftl | 3 +-
.../src/main/resources/rest/metrics/metrics.ftl | 16 +-
.../src/main/resources/rest/profile/list.ftl | 63 ++---
.../src/main/resources/rest/profile/profile.ftl | 33 ++-
.../src/main/resources/rest/query/query.ftl | 6 +-
.../src/main/resources/rest/query/result.ftl | 32 ++-
.../src/main/resources/rest/storage/list.ftl | 84 ++++---
.../src/main/resources/rest/storage/update.ftl | 54 ++++-
.../src/main/resources/rest/www/graph.js | 82 +++++++
.../src/main/resources/rest/www/style.css | 27 +++
17 files changed, 867 insertions(+), 346 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/65c83582/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/DrillRestServer.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/DrillRestServer.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/DrillRestServer.java
index cdbaaaf..ac6a537 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/DrillRestServer.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/DrillRestServer.java
@@ -44,6 +44,7 @@ public class DrillRestServer extends ResourceConfig {
register(QueryResources.class);
register(MetricsResources.class);
register(ThreadsResources.class);
+ register(WebResourceServer.class);
register(FreemarkerMvcFeature.class);
property(ServerProperties.METAINF_SERVICES_LOOKUP_DISABLE, true);
register(MultiPartFeature.class);
http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/65c83582/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/PluginConfigWrapper.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/PluginConfigWrapper.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/PluginConfigWrapper.java
new file mode 100644
index 0000000..003f8ae
--- /dev/null
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/PluginConfigWrapper.java
@@ -0,0 +1,78 @@
+/**
+ * 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.drill.exec.server.rest;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.drill.common.exceptions.ExecutionSetupException;
+import org.apache.drill.common.logical.StoragePluginConfig;
+import org.apache.drill.exec.store.StoragePluginRegistry;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class PluginConfigWrapper {
+
+ private String name;
+ private StoragePluginConfig config;
+ private boolean exists;
+
+ @JsonCreator
+ public PluginConfigWrapper(@JsonProperty("name") String name, @JsonProperty("config") StoragePluginConfig config) {
+ this.name = name;
+ this.config = config;
+ this.exists = config != null;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public StoragePluginConfig getConfig() {
+ return config;
+ }
+
+ public boolean enabled() {
+ return exists && config.isEnabled();
+ }
+
+ public void createOrUpdateInStorage(StoragePluginRegistry storage) throws ExecutionSetupException {
+ storage.createOrUpdate(name, config, true);
+ }
+
+ public boolean setEnabledInStorage(StoragePluginRegistry storage, boolean enabled) throws ExecutionSetupException {
+ if (exists) {
+ config.setEnabled(enabled);
+ createOrUpdateInStorage(storage);
+ }
+ return exists;
+ }
+
+ public boolean exists() {
+ return exists;
+ }
+
+ public boolean deleteFromStorage(StoragePluginRegistry storage) {
+ if (exists) {
+ storage.deletePlugin(name);
+ return true;
+ }
+ return false;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/65c83582/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/ProfileResources.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/ProfileResources.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/ProfileResources.java
index 7d29ea3..5ae4267 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/ProfileResources.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/ProfileResources.java
@@ -19,9 +19,8 @@ package org.apache.drill.exec.server.rest;
import java.io.IOException;
import java.text.SimpleDateFormat;
-import java.util.AbstractMap;
+import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -32,6 +31,7 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
+import javax.xml.bind.annotation.XmlRootElement;
import org.apache.drill.exec.proto.UserBitShared.QueryProfile;
import org.apache.drill.exec.proto.UserBitShared.QueryResult.QueryState;
@@ -42,82 +42,131 @@ import org.glassfish.jersey.server.mvc.Viewable;
import com.google.common.collect.Lists;
-@Path("/profiles")
+@Path("/")
public class ProfileResources {
static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(ProfileResources.class);
@Inject WorkManager work;
+ public static class ProfileInfo implements Comparable<ProfileInfo> {
+ public static final SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
- @GET
- @Path("/{queryid}")
- @Produces(MediaType.TEXT_HTML)
- public Viewable getQuery(@PathParam("queryid") String queryId) throws IOException {
- PStore<QueryProfile> profiles = work.getContext().getPersistentStoreProvider().getPStore(QueryStatus.QUERY_PROFILE);
- QueryProfile profile = profiles.get(queryId);
- if(profile == null) profile = QueryProfile.getDefaultInstance();
+ private String queryId;
+ private Date time;
+ private String location;
- ProfileWrapper wrapper = new ProfileWrapper(profile);
+ public ProfileInfo(String queryId, long time) {
+ this.queryId = queryId;
+ this.time = new Date(time);
+ this.location = "http://localhost:8047/profile/" + queryId + ".json";
+ }
- return new Viewable("/rest/profile/profile.ftl", wrapper);
+ public String getQueryId() {
+ return queryId;
+ }
+
+ public String getTime() {
+ return format.format(time);
+ }
+
+ public String getLocation() {
+ return location;
+ }
+ @Override
+ public int compareTo(ProfileInfo other) {
+ return time.compareTo(other.time);
+ }
}
- @GET
- @Produces(MediaType.TEXT_HTML)
- public Viewable getResults() throws IOException {
- PStore<QueryProfile> profiles = work.getContext().getPersistentStoreProvider().getPStore(QueryStatus.QUERY_PROFILE);
-
- List<Map.Entry<String, Long>> runningIds = Lists.newArrayList();
- List<Map.Entry<String, Long>> finishedIds = Lists.newArrayList();
- for(Map.Entry<String, QueryProfile> entry : profiles){
- QueryProfile q = entry.getValue();
- if (q.getState() == QueryState.RUNNING || q.getState() == QueryState.PENDING) {
- runningIds.add(new AbstractMap.SimpleEntry<>(entry.getKey(), q.getStart()));
- } else {
- finishedIds.add(new AbstractMap.SimpleEntry<>(entry.getKey(), q.getStart()));
- }
+ @XmlRootElement
+ public class QProfiles {
+ private List<ProfileInfo> runningQueries;
+ private List<ProfileInfo> finishedQueries;
+
+ public QProfiles(List<ProfileInfo> runningQueries, List<ProfileInfo> finishedQueries) {
+ this.runningQueries = runningQueries;
+ this.finishedQueries = finishedQueries;
}
- Comparator<Map.Entry<String,Long>> comparator = new Comparator<Map.Entry<String,Long>>() {
- @Override
- public int compare(Map.Entry<String, Long> o1, Map.Entry<String, Long> o2) {
- return o2.getValue().compareTo(o1.getValue());
- }
- };
+ public List<ProfileInfo> getRunningQueries() {
+ return runningQueries;
+ }
- Collections.sort(runningIds, comparator);
- Collections.sort(finishedIds, comparator);
+ public List<ProfileInfo> getFinishedQueries() {
+ return finishedQueries;
+ }
+ }
- List<Map.Entry<String, String>> runningQueries = Lists.newArrayList();
- List<Map.Entry<String, String>> oldQueries = Lists.newArrayList();
- for(Map.Entry<String, Long> entry : runningIds){
- runningQueries.add(new AbstractMap.SimpleEntry<>(entry.getKey(), new SimpleDateFormat("MM/dd/yyyy HH:mm:ss").format(new Date(entry.getValue()))));
+ @GET
+ @Path("/profiles.json")
+ @Produces(MediaType.APPLICATION_JSON)
+ public QProfiles getProfilesJSON() {
+ PStore<QueryProfile> store = null;
+ try {
+ store = work.getContext().getPersistentStoreProvider().getPStore(QueryStatus.QUERY_PROFILE);
+ } catch (IOException e) {
+ logger.debug("Failed to get profiles from persistent store.");
+ return new QProfiles(new ArrayList<ProfileInfo>(), new ArrayList<ProfileInfo>());
}
- for(Map.Entry<String, Long> entry : finishedIds){
- oldQueries.add(new AbstractMap.SimpleEntry<>(entry.getKey(), new SimpleDateFormat("MM/dd/yyyy HH:mm:ss").format(new Date(entry.getValue()))));
+ List<ProfileInfo> runningQueries = Lists.newArrayList();
+ List<ProfileInfo> finishedQueries = Lists.newArrayList();
+
+ for(Map.Entry<String, QueryProfile> entry : store){
+ QueryProfile profile = entry.getValue();
+ if (profile.getState() == QueryState.RUNNING || profile.getState() == QueryState.PENDING) {
+ runningQueries.add(new ProfileInfo(entry.getKey(), profile.getStart()));
+ } else {
+ finishedQueries.add(new ProfileInfo(entry.getKey(), profile.getStart()));
+ }
}
- // add status (running, done)
- Queries queries = new Queries();
- queries.runningQueries = runningQueries;
- queries.oldQueries = oldQueries;
+ Collections.sort(runningQueries, Collections.reverseOrder());
+ Collections.sort(finishedQueries, Collections.reverseOrder());
- return new Viewable("/rest/profile/list.ftl", queries);
+ return new QProfiles(runningQueries, finishedQueries);
}
- public static class Queries {
- List<Map.Entry<String, String>> runningQueries;
- List<Map.Entry<String, String>> oldQueries;
+ @GET
+ @Path("/profiles")
+ @Produces(MediaType.TEXT_HTML)
+ public Viewable getProfiles() {
+ QProfiles profiles = getProfilesJSON();
+ return new Viewable("/rest/profile/list.ftl", profiles);
+ }
- public List<Map.Entry<String, String>> getRunningQueries() {
- return runningQueries;
+ private QueryProfile getQueryProfile(String queryId) {
+ PStore<QueryProfile> store = null;
+ try {
+ store = work.getContext().getPersistentStoreProvider().getPStore(QueryStatus.QUERY_PROFILE);
+ } catch (IOException e) {
+ logger.debug("Failed to get profile for: " + queryId);
+ return QueryProfile.getDefaultInstance();
}
+ QueryProfile profile = store.get(queryId);
+ return profile == null ? QueryProfile.getDefaultInstance() : profile;
+ }
- public List<Map.Entry<String, String>> getOldQueries() {
- return oldQueries;
+ @GET
+ @Path("/profiles/{queryid}.json")
+ @Produces(MediaType.APPLICATION_JSON)
+ public String getProfileJSON(@PathParam("queryid") String queryId) {
+ try {
+ return new String(QueryStatus.QUERY_PROFILE.getSerializer().serialize(getQueryProfile(queryId)));
+ } catch (IOException e) {
+ logger.debug("Failed to serialize profile for: " + queryId);
+ return ("{ 'message' : 'error (unable to serialize profile)' }");
}
}
-}
+ @GET
+ @Path("/profiles/{queryid}")
+ @Produces(MediaType.TEXT_HTML)
+ public Viewable getProfile(@PathParam("queryid") String queryId) {
+ ProfileWrapper wrapper = new ProfileWrapper(getQueryProfile(queryId));
+
+ return new Viewable("/rest/profile/profile.ftl", wrapper);
+
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/65c83582/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/QueryResources.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/QueryResources.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/QueryResources.java
index 9c1dce1..8ad32b1 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/QueryResources.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/QueryResources.java
@@ -17,10 +17,9 @@
*/
package org.apache.drill.exec.server.rest;
-import java.util.LinkedList;
+import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.Map;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
@@ -30,24 +29,15 @@ import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
+import javax.xml.bind.annotation.XmlRootElement;
import org.apache.drill.common.config.DrillConfig;
-import org.apache.drill.exec.client.DrillClient;
import org.apache.drill.exec.coord.ClusterCoordinator;
-import org.apache.drill.exec.exception.SchemaChangeException;
import org.apache.drill.exec.memory.BufferAllocator;
-import org.apache.drill.exec.proto.UserBitShared;
-import org.apache.drill.exec.record.RecordBatchLoader;
-import org.apache.drill.exec.record.VectorWrapper;
-import org.apache.drill.exec.rpc.RpcException;
-import org.apache.drill.exec.rpc.user.ConnectionThrottle;
-import org.apache.drill.exec.rpc.user.QueryResultBatch;
-import org.apache.drill.exec.rpc.user.UserResultsListener;
-import org.apache.drill.exec.vector.ValueVector;
import org.apache.drill.exec.work.WorkManager;
import org.glassfish.jersey.server.mvc.Viewable;
-@Path("/query")
+@Path("/")
public class QueryResources {
static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(QueryResources.class);
@@ -55,92 +45,55 @@ public class QueryResources {
WorkManager work;
@GET
+ @Path("/query")
@Produces(MediaType.TEXT_HTML)
public Viewable getQuery() {
return new Viewable("/rest/query/query.ftl");
}
@POST
- @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
- @Produces(MediaType.TEXT_HTML)
- public Viewable submitQuery(@FormParam("query") String query, @FormParam("queryType") String queryType) throws Exception {
+ @Path("/query.json")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public List<Map<String, Object>> submitQueryJSON(QueryWrapper query) throws Exception {
final DrillConfig config = work.getContext().getConfig();
final ClusterCoordinator coordinator = work.getContext().getClusterCoordinator();
final BufferAllocator allocator = work.getContext().getAllocator();
- DrillClient client = new DrillClient(config, coordinator, allocator);
-
- UserBitShared.QueryType type = UserBitShared.QueryType.SQL;
- switch (queryType){
- case "SQL" : type = UserBitShared.QueryType.SQL; break;
- case "LOGICAL" : type = UserBitShared.QueryType.LOGICAL; break;
- case "PHYSICAL" : type = UserBitShared.QueryType.PHYSICAL; break;
- }
-
- client.connect();
- Listener listener = new Listener(new RecordBatchLoader(work.getContext().getAllocator()));
- client.runQuery(type, query, listener);
- List<LinkedList<String>> result = listener.waitForCompletion();
- client.close();
-
- return new Viewable("/rest/query/result.ftl", result);
+ return query.run(config, coordinator, allocator);
}
- private static class Listener implements UserResultsListener {
- private volatile Exception exception;
- private AtomicInteger count = new AtomicInteger();
- private CountDownLatch latch = new CountDownLatch(1);
- private LinkedList<LinkedList<String>> output = new LinkedList<>();
- private RecordBatchLoader loader;
+ @POST
+ @Path("/query")
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ @Produces(MediaType.TEXT_HTML)
+ public Viewable submitQuery(@FormParam("query") String query, @FormParam("queryType") String queryType) throws Exception {
+ List<Map<String, Object>> result = submitQueryJSON(new QueryWrapper(query, queryType));
- Listener(RecordBatchLoader loader) {
- this.loader = loader;
+ List<String> columnNames = new ArrayList<>(result.get(0).keySet());
+ List<List<Object>> records = new ArrayList<>();
+ for(Map m : result) {
+ records.add(new ArrayList<Object>(m.values()));
}
+ Table table = new Table(columnNames, records);
- @Override
- public void submissionFailed(RpcException ex) {
- exception = ex;
- System.out.println("Query failed: " + ex.getMessage());
- latch.countDown();
- }
+ return new Viewable("/rest/query/result.ftl", table);
+ }
+
+ public class Table {
+ private List<String> columnNames;
+ private List<List<Object>> records;
- @Override
- public void resultArrived(QueryResultBatch result, ConnectionThrottle throttle) {
- int rows = result.getHeader().getRowCount();
- if (result.getData() != null) {
- count.addAndGet(rows);
- try {
- loader.load(result.getHeader().getDef(), result.getData());
- output.add(new LinkedList<String>());
- for (int i = 0; i < loader.getSchema().getFieldCount(); ++i) {
- output.getLast().add(loader.getSchema().getColumn(i).getPath().getAsUnescapedPath());
- }
- } catch (SchemaChangeException e) {
- throw new RuntimeException(e);
- }
- for (int i = 0; i < rows; ++i) {
- output.add(new LinkedList<String>());
- for (VectorWrapper<?> vw : loader) {
- ValueVector.Accessor accessor = vw.getValueVector().getAccessor();
- output.getLast().add(accessor.getObject(i).toString());
- }
- }
- }
- result.release();
- if (result.getHeader().getIsLastChunk()) {
- latch.countDown();
- }
+ public Table(List<String> columnNames, List<List<Object>> records) {
+ this.columnNames = columnNames;
+ this.records = records;
}
- @Override
- public void queryIdArrived(UserBitShared.QueryId queryId) {
+ public List<String> getColumnNames() {
+ return columnNames;
}
- public List<LinkedList<String>> waitForCompletion() throws Exception {
- latch.await();
- if (exception != null) {
- throw exception;
- }
- return output;
+ public List<List<Object>> getRecords() {
+ return records;
}
}
}
http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/65c83582/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/QueryWrapper.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/QueryWrapper.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/QueryWrapper.java
new file mode 100644
index 0000000..83c82db
--- /dev/null
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/QueryWrapper.java
@@ -0,0 +1,165 @@
+/**
+ * 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.drill.exec.server.rest;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.apache.drill.common.config.DrillConfig;
+import org.apache.drill.exec.client.DrillClient;
+import org.apache.drill.exec.coord.ClusterCoordinator;
+import org.apache.drill.exec.exception.SchemaChangeException;
+import org.apache.drill.exec.memory.BufferAllocator;
+import org.apache.drill.exec.proto.UserBitShared;
+import org.apache.drill.exec.record.RecordBatchLoader;
+import org.apache.drill.exec.record.VectorWrapper;
+import org.apache.drill.exec.rpc.RpcException;
+import org.apache.drill.exec.rpc.user.ConnectionThrottle;
+import org.apache.drill.exec.rpc.user.QueryResultBatch;
+import org.apache.drill.exec.rpc.user.UserResultsListener;
+import org.apache.drill.exec.vector.ValueVector;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@XmlRootElement
+public class QueryWrapper {
+
+ private String query;
+ private String queryType;
+
+ @JsonCreator
+ public QueryWrapper(@JsonProperty("query") String query, @JsonProperty("queryType") String queryType) {
+ this.query = query;
+ this.queryType = queryType;
+ }
+
+ public String getQuery() {
+ return query;
+ }
+
+ public String getQueryType() {
+ return queryType;
+ }
+
+ public UserBitShared.QueryType getType() {
+ UserBitShared.QueryType type = UserBitShared.QueryType.SQL;
+ switch (queryType) {
+ case "SQL" : type = UserBitShared.QueryType.SQL; break;
+ case "LOGICAL" : type = UserBitShared.QueryType.LOGICAL; break;
+ case "PHYSICAL" : type = UserBitShared.QueryType.PHYSICAL; break;
+ }
+ return type;
+ }
+
+ public List<Map<String, Object>> run(DrillConfig config, ClusterCoordinator coordinator, BufferAllocator allocator)
+ throws Exception {
+ DrillClient client = new DrillClient(config, coordinator, allocator);
+ Listener listener = new Listener(new RecordBatchLoader(allocator));
+
+ client.connect();
+ client.runQuery(getType(), query, listener);
+
+ List<Map<String, Object>> result = listener.waitForCompletion();
+ client.close();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "QueryRequest [queryType=" + queryType + ", query=" + query + "]";
+ }
+
+
+ private static class Listener implements UserResultsListener {
+ private volatile Exception exception;
+ private AtomicInteger count = new AtomicInteger();
+ private CountDownLatch latch = new CountDownLatch(1);
+ private List<Map<String, Object>> output = new LinkedList<>();
+ private ArrayList<String> columnNames;
+ private RecordBatchLoader loader;
+ private boolean schemaAdded = false;
+
+ Listener(RecordBatchLoader loader) {
+ this.loader = loader;
+ }
+
+ @Override
+ public void submissionFailed(RpcException ex) {
+ exception = ex;
+ System.out.println("Query failed: " + ex.getMessage());
+ latch.countDown();
+ }
+
+ @Override
+ public void resultArrived(QueryResultBatch result, ConnectionThrottle throttle) {
+ int rows = result.getHeader().getRowCount();
+ if (result.getData() != null) {
+ count.addAndGet(rows);
+ try {
+ loader.load(result.getHeader().getDef(), result.getData());
+ if (!schemaAdded) {
+ columnNames = new ArrayList<>();
+ for (int i = 0; i < loader.getSchema().getFieldCount(); ++i) {
+ columnNames.add(loader.getSchema().getColumn(i).getPath().getAsUnescapedPath());
+ }
+ schemaAdded = true;
+ }
+ } catch (SchemaChangeException e) {
+ throw new RuntimeException(e);
+ }
+ for (int i = 0; i < rows; ++i) {
+ Map<String, Object> record = new HashMap<>();
+ int j = 0;
+ for (VectorWrapper<?> vw : loader) {
+ ValueVector.Accessor accessor = vw.getValueVector().getAccessor();
+ Object object = accessor.getObject(i);
+ if (! object.getClass().getName().startsWith("java.lang")) {
+ object = object.toString();
+ }
+ record.put(columnNames.get(j), object);
+ ++j;
+ }
+ output.add(record);
+ }
+ }
+ result.release();
+ if (result.getHeader().getIsLastChunk()) {
+ latch.countDown();
+ }
+ }
+
+ @Override
+ public void queryIdArrived(UserBitShared.QueryId queryId) {
+ }
+
+ public List<Map<String, Object>> waitForCompletion() throws Exception {
+ latch.await();
+ if (exception != null) {
+ throw exception;
+ }
+ return output;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/65c83582/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/StorageResources.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/StorageResources.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/StorageResources.java
index 714e711..e841430 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/StorageResources.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/StorageResources.java
@@ -17,160 +17,186 @@
*/
package org.apache.drill.exec.server.rest;
-import java.io.IOException;
-import java.io.StringReader;
-import java.net.URI;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.Lists;
+import org.apache.drill.common.exceptions.ExecutionSetupException;
+import org.apache.drill.common.logical.StoragePluginConfig;
+import org.apache.drill.exec.store.StoragePlugin;
+import org.apache.drill.exec.store.StoragePluginRegistry;
+import org.apache.drill.exec.store.sys.PStoreProvider;
+import org.glassfish.jersey.server.mvc.Viewable;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
-import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
-
-import org.apache.commons.lang3.tuple.ImmutablePair;
-import org.apache.commons.lang3.tuple.Pair;
-import org.apache.drill.common.exceptions.ExecutionSetupException;
-import org.apache.drill.common.logical.StoragePluginConfig;
-import org.apache.drill.exec.store.StoragePlugin;
-import org.apache.drill.exec.store.StoragePluginRegistry;
-import org.apache.drill.exec.store.sys.PStoreProvider;
-import org.glassfish.jersey.server.mvc.Viewable;
-
-import com.fasterxml.jackson.core.JsonParseException;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.JsonMappingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.collect.Lists;
-
-import freemarker.template.SimpleHash;
+import javax.xml.bind.annotation.XmlRootElement;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
-@Path("/storage")
+@Path("/")
public class StorageResources {
static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(StorageResources.class);
-
- static final Comparator<Pair<String, Boolean>> PLUGIN_NAME_COMPARATOR = new Comparator<Pair<String, Boolean>>() {
+
+ @Inject
+ StoragePluginRegistry storage;
+// @Inject
+// PStoreProvider storeProvider;
+ @Inject
+ ObjectMapper mapper;
+
+ static final Comparator<PluginConfigWrapper> PLUGIN_COMPARATOR = new Comparator<PluginConfigWrapper>() {
@Override
- public int compare(Pair<String, Boolean> o1, Pair<String, Boolean> o2) {
- return o1.getKey().compareTo(o2.getKey());
+ public int compare(PluginConfigWrapper o1, PluginConfigWrapper o2) {
+ return o1.getName().compareTo(o2.getName());
}
};
- @Inject StoragePluginRegistry storage;
- @Inject PStoreProvider storeProvider;
- @Inject ObjectMapper mapper;
-
@GET
- @Produces(MediaType.TEXT_HTML)
- public Viewable listPlugins() {
- // build a list and sort by plugin instance name
- List<Pair<String, Boolean>> pluginList = Lists.newArrayList();
- for (Map.Entry<String, StoragePluginConfig> config : storage.getStore()) {
- pluginList.add(ImmutablePair.of(config.getKey(), config.getValue().isEnabled()));
- }
- Collections.sort(pluginList, PLUGIN_NAME_COMPARATOR);
-
- // now use the sorted list to build the freemarker model
- List<SimpleHash> modelList = Lists.newArrayList();
- for (Pair<String, Boolean> plugin : pluginList) {
- SimpleHash map = new SimpleHash();
- map.put("name", plugin.getLeft());
- map.put("enabled", plugin.getRight());
- modelList.add(map);
+ @Path("/storage.json")
+ @Produces(MediaType.APPLICATION_JSON)
+ public List<PluginConfigWrapper> getStoragePluginsJSON() {
+
+ List<PluginConfigWrapper> list = Lists.newArrayList();
+ for (Map.Entry<String, StoragePluginConfig> entry : storage.getStore()) {
+ PluginConfigWrapper plugin = new PluginConfigWrapper(entry.getKey(), entry.getValue());
+ list.add(plugin);
}
- return new Viewable("/rest/storage/list.ftl", modelList);
+ Collections.sort(list, PLUGIN_COMPARATOR);
+
+ return list;
}
@GET
- @Path("/{name}/config/update")
+ @Path("/storage")
@Produces(MediaType.TEXT_HTML)
- public Viewable update(@PathParam("name") String name) throws JsonProcessingException {
- StoragePluginConfig config = findConfig(name);
- String conf = config == null ? "" : mapper.writeValueAsString(config);
-
- SimpleHash map = new SimpleHash();
- map.put("config", conf);
- map.put("name", name);
- map.put("exists", config != null);
- map.put("enabled", config != null && config.isEnabled());
- return new Viewable("/rest/storage/update.ftl", map);
+ public Viewable getStoragePlugins() {
+ List<PluginConfigWrapper> list = getStoragePluginsJSON();
+ return new Viewable("/rest/storage/list.ftl", list);
}
@GET
- @Path("/{name}/config/enable/{val}")
- @Produces(MediaType.TEXT_HTML)
- public Response setEnable(@Context UriInfo uriInfo, @PathParam("name") String name, @PathParam("val") Boolean enable) throws ExecutionSetupException {
- StoragePluginConfig config = findConfig(name);
- if (config != null) {
- config.setEnabled(enable);
- storage.createOrUpdate(name, config, true);
+ @Path("/storage/{name}.json")
+ @Produces(MediaType.APPLICATION_JSON)
+ public PluginConfigWrapper getStoragePluginJSON(@PathParam("name") String name) {
+ try {
+ StoragePlugin plugin = storage.getPlugin(name);
+ if (plugin != null) {
+ return new PluginConfigWrapper(name, plugin.getConfig());
+ }
+ } catch (Exception e) {
+ logger.info("Failure while trying to access storage config: {}", name, e);
}
-
- URI uri = uriInfo.getBaseUriBuilder().path("/storage").build();
- return Response.seeOther(uri).build();
+ return new PluginConfigWrapper(name, null);
}
@GET
- @Path("/{name}/config/delete")
+ @Path("/storage/{name}")
@Produces(MediaType.TEXT_HTML)
- public Viewable deleteConfig(@PathParam("name") String name) {
- storage.deletePlugin(name);
- return new Viewable("/rest/status.ftl", "Deleted " + name);
+ public Viewable getStoragePlugin(@PathParam("name") String name) {
+ PluginConfigWrapper plugin = getStoragePluginJSON(name);
+ return new Viewable("/rest/storage/update.ftl", plugin);
+ }
+
+ @GET
+ @Path("/storage/{name}/enable/{val}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public JsonResult enablePlugin(@PathParam("name") String name, @PathParam("val") Boolean enable) {
+ PluginConfigWrapper plugin = getStoragePluginJSON(name);
+ try {
+ if (plugin.setEnabledInStorage(storage, enable)) {
+ return message("success");
+ } else {
+ return message("error (plugin does not exist)");
+ }
+ } catch (ExecutionSetupException e) {
+ logger.debug("Error in enabling storage name: " + name + " flag: " + enable);
+ return message("error (unable to enable/ disable storage)");
+ }
+ }
+
+ @DELETE
+ @Path("/storage/{name}.json")
+ @Produces(MediaType.APPLICATION_JSON)
+ public JsonResult deletePluginJSON(@PathParam("name") String name) {
+ PluginConfigWrapper plugin = getStoragePluginJSON(name);
+ if (plugin.deleteFromStorage(storage)) {
+ return message("success");
+ } else {
+ return message("error (unable to delete storage)");
+ }
}
@GET
+ @Path("/storage/{name}/delete")
@Produces(MediaType.APPLICATION_JSON)
- @Path("/{name}/config")
- public StoragePluginConfig getConfig(@PathParam("name") String name) {
- return findConfig(name);
+ public JsonResult deletePlugin(@PathParam("name") String name) {
+ return deletePluginJSON(name);
}
- private StoragePluginConfig findConfig(String name) {
+ @POST
+ @Path("/storage/{name}.json")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public JsonResult createOrUpdatePluginJSON(PluginConfigWrapper plugin) {
try {
- StoragePlugin plugin = storage.getPlugin(name);
- if (plugin != null) {
- return plugin.getConfig();
- }
- } catch (Exception e) {
- logger.info("Failure while trying to access storage config: {}", name, e);
- ;
+ plugin.createOrUpdateInStorage(storage);
+ return message("success");
+ } catch (ExecutionSetupException e) {
+ logger.debug("Unable to create/ update plugin: " + plugin.getName());
+ return message("error (unable to create/ update storage)");
}
- return null;
}
@POST
- @Path("/config/update")
- @Produces(MediaType.TEXT_HTML)
+ @Path("/storage/{name}")
@Consumes("application/x-www-form-urlencoded")
- public Viewable createTrackInJSON(@FormParam("name") String name, @FormParam("config") String storagePluginConfig)
- throws ExecutionSetupException, JsonParseException, JsonMappingException, IOException {
- StoragePluginConfig config = mapper.readValue(new StringReader(storagePluginConfig), StoragePluginConfig.class);
- storage.createOrUpdate(name, config, true);
- return new Viewable("/rest/status.ftl", "Updated " + name);
+ @Produces(MediaType.APPLICATION_JSON)
+ public JsonResult createOrUpdatePlugin(@FormParam("name") String name, @FormParam("config") String storagePluginConfig) {
+ try {
+ StoragePluginConfig config = mapper.readValue(new StringReader(storagePluginConfig), StoragePluginConfig.class);
+ return createOrUpdatePluginJSON(new PluginConfigWrapper(name, config));
+ } catch (JsonMappingException e) {
+ logger.debug("Error in JSON mapping: " + storagePluginConfig);
+ return message("error (invalid JSON mapping)");
+ } catch (JsonParseException e) {
+ logger.debug("Error parsing JSON: " + storagePluginConfig);
+ return message("error (unable to parse JSON)");
+ } catch (IOException e) {
+ logger.debug("Failed to read: " + storagePluginConfig);
+ return message("error (unable to read)");
+ }
}
- private JsonResult r(String message) {
+ private JsonResult message(String message) {
return new JsonResult(message);
}
- public static class JsonResult {
- public String result = "data updated";
+ @XmlRootElement
+ public class JsonResult {
+
+ private String result;
public JsonResult(String result) {
- super();
this.result = result;
}
+ public String getResult() {
+ return result;
+ }
+
}
}
http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/65c83582/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebResourceServer.java
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebResourceServer.java b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebResourceServer.java
new file mode 100644
index 0000000..33b9be9
--- /dev/null
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/server/rest/WebResourceServer.java
@@ -0,0 +1,71 @@
+/**
+ * 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.drill.exec.server.rest;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLConnection;
+
+import javax.inject.Inject;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.drill.exec.work.WorkManager;
+
+@Path("/www")
+public class WebResourceServer {
+ static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(WebResourceServer.class);
+
+ @Inject WorkManager work;
+
+
+ @GET
+ @Path("/{path}")
+ @Produces(MediaType.TEXT_PLAIN)
+ public Response getResource(@PathParam("path") String path) throws IOException {
+ try {
+ String s = "rest/www/" + path;
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ InputStream is = new BufferedInputStream(cl.getResource(s).openStream());
+
+ String mime = "text/plain";
+ if (s.endsWith(".js")) {
+ mime = "text/javascript";
+ } else if (s.endsWith(".css")) {
+ mime = "text/css";
+ } else {
+ mime = URLConnection.guessContentTypeFromStream(is);
+ }
+
+ byte[] d = IOUtils.toByteArray(is);
+ return Response.ok(d).type(mime).build();
+ } catch (Exception e) {
+ e.printStackTrace();
+ e.printStackTrace(System.out);
+ }
+
+ return Response.noContent().status(Status.NOT_FOUND).build();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/65c83582/exec/java-exec/src/main/resources/rest/generic.ftl
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/resources/rest/generic.ftl b/exec/java-exec/src/main/resources/rest/generic.ftl
index 256d582..1cbfc23 100644
--- a/exec/java-exec/src/main/resources/rest/generic.ftl
+++ b/exec/java-exec/src/main/resources/rest/generic.ftl
@@ -49,11 +49,10 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
- <a class="navbar-brand" href="/">Apache Drill</a>
+ <a class="navbar-brand">Apache Drill</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
- <li><a href="/status">Status</a></li>
<li><a href="/query">Query</a></li>
<li><a href="/profiles">Profiles</a></li>
<li><a href="/storage">Storage</a></li>
http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/65c83582/exec/java-exec/src/main/resources/rest/metrics/metrics.ftl
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/resources/rest/metrics/metrics.ftl b/exec/java-exec/src/main/resources/rest/metrics/metrics.ftl
index b0ccde8..1600a2d 100644
--- a/exec/java-exec/src/main/resources/rest/metrics/metrics.ftl
+++ b/exec/java-exec/src/main/resources/rest/metrics/metrics.ftl
@@ -60,7 +60,7 @@
<h3 id="histograms">Histograms</h3>
<div id="histogramsVal">
<div class="alert alert-info">
- <strong>No counters.</strong>
+ <strong>No histograms.</strong>
</div>
</div>
<h3 id="meters">Meters</h3>
@@ -114,13 +114,12 @@
});
};
- function updateTimers(timers) {
- console.log("yup");
- $("#timersVal").html(function() {
+ function createTable(metric, name) {
+ $("#" + name + "Val").html(function() {
var tables = "";
- $.each(timers, function(name, stats) {
+ $.each(metric, function(clazz, stats) {
- var table = "<strong>Reporting class:</strong> " + name + "<br>";
+ var table = "<strong>Reporting class:</strong> " + clazz + "<br>";
table += "<table class=\"table table-striped\"><tbody>";
$.each(stats, function(key, value) {
table += "<tr>";
@@ -140,7 +139,7 @@
};
function updateOthers(metrics) {
- $.each(["counters", "histograms", "meters"], function(i, key) {
+ $.each(["counters", "meters"], function(i, key) {
if(! $.isEmptyObject(metrics[key])) {
$("#" + key + "Val").html(JSON.stringify(metrics[key], null, 2));
}
@@ -151,7 +150,8 @@
$.get("/status/metrics", function(metrics) {
updateGauges(metrics.gauges);
updateBars(metrics.gauges);
- if(! $.isEmptyObject(metrics.timers)) updateTimers(metrics.timers);
+ if(! $.isEmptyObject(metrics.timers)) createTable(metrics.timers, "timers");
+ if(! $.isEmptyObject(metrics.histograms)) createTable(metrics.histograms, "histograms");
updateOthers(metrics);
});
};
http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/65c83582/exec/java-exec/src/main/resources/rest/profile/list.ftl
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/resources/rest/profile/list.ftl b/exec/java-exec/src/main/resources/rest/profile/list.ftl
index 61d3466..76f9a07 100644
--- a/exec/java-exec/src/main/resources/rest/profile/list.ftl
+++ b/exec/java-exec/src/main/resources/rest/profile/list.ftl
@@ -17,29 +17,38 @@
<a href="/queries">back</a><br/>
<div class="page-header">
</div>
- <h3>Running Queries</h3>
- <div class="table-responsive">
- <table class="table table-hover">
- <thead>
- <td>Time</td>
- <td>Query</td>
- </thead>
- <tbody>
- <#list model.getRunningQueries() as query>
- <tr>
- <td>${query.getValue()}</td>
- <td>
- <a href="/profiles/${query.getKey()}">
- <div style="height:100%;width:100%">
- ${query.getKey()}
- </div>
- </a>
- </td>
- </tr>
- </#list>
- </tbody>
- </table>
- </div>
+ <#if (model.getRunningQueries()?size > 0) >
+ <h3>Running Queries</h3>
+ <div class="table-responsive">
+ <table class="table table-hover">
+ <thead>
+ <td>Time</td>
+ <td>Query</td>
+ </thead>
+ <tbody>
+ <#list model.getRunningQueries() as query>
+ <tr>
+ <td>${query.getTime()}</td>
+ <td>
+ <a href="/profiles/${query.getQueryId()}">
+ <div style="height:100%;width:100%">
+ ${query.getQueryId()}
+ </div>
+ </a>
+ </td>
+ </tr>
+ </#list>
+ </tbody>
+ </table>
+ </div>
+ <div class="page-header">
+ </div>
+ <#else>
+ <div id="message" class="alert alert-info alert-dismissable">
+ <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
+ <strong>No running queries.</strong>
+ </div>
+ </#if>
<h3>Completed Queries</h3>
<div class="table-responsive">
<table class="table table-hover">
@@ -48,13 +57,13 @@
<td>Query</td>
</thead>
<tbody>
- <#list model.getOldQueries() as query>
+ <#list model.getFinishedQueries() as query>
<tr>
- <td>${query.getValue()}</td>
+ <td>${query.getTime()}</td>
<td>
- <a href="/profiles/${query.getKey()}">
+ <a href="/profiles/${query.getQueryId()}">
<div style="height:100%;width:100%">
- ${query.getKey()}
+ ${query.getQueryId()}
</div>
</a>
</td>
http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/65c83582/exec/java-exec/src/main/resources/rest/profile/profile.ftl
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/resources/rest/profile/profile.ftl b/exec/java-exec/src/main/resources/rest/profile/profile.ftl
index 94b8aa1..8abb316 100644
--- a/exec/java-exec/src/main/resources/rest/profile/profile.ftl
+++ b/exec/java-exec/src/main/resources/rest/profile/profile.ftl
@@ -11,16 +11,21 @@
<#include "*/generic.ftl">
<#macro page_head>
+<link href="/www/style.css" rel="stylesheet">
+
+<script src="http://d3js.org/d3.v3.min.js"></script>
+<script src="http://cpettitt.github.io/project/dagre-d3/latest/dagre-d3.js"></script>
+<script src="/www/graph.js"></script>
</#macro>
<#macro page_body>
<a href="/queries">back</a><br/>
<div class="page-header">
</div>
- <h2>Query</h2>
+ <h3>Query</h3>
<form role="form" action="/query" method="POST">
<div class="form-group">
- <textarea class="form-control" id="query" name="query">${model.getProfile().query}</textarea>
+ <textarea class="form-control" id="query" name="query" style="font-family: Courier;">${model.getProfile().query}</textarea>
</div>
<div class="form-group">
<div class="radio-inline">
@@ -45,17 +50,29 @@
<button type="submit" class="btn btn-default">Re-run query</button>
</form>
<div class="page-header">
- <h2>Physical Plan</h2>
</div>
- <p><pre>${model.profile.plan}</pre></p>
+ <h3>Visualized Plan</h3>
+ <button id="renderbutton" class="btn btn-default">Generate</button>
+ <svg id="svg-canvas" style="margin: auto; display: block;">
+ <g transform="translate(20, 20)"/>
+ </svg>
+ <div class="page-header">
+ </div>
+ <h3>Physical Plan</h3>
+ <p><pre>${model.profile.plan}</pre></p>
<div class="page-header">
- <h2>Profile Summary</h2>
</div>
- <p>${model.toString()}</p>
+ <h3>Profile Summary</h3>
+ <p>${model.toString()}</p>
<div class="page-header">
- <h2>Complete Profile</h2>
</div>
- <p><pre>${model.profile.toString()}</pre></p>
+ <div class="span4 collapse-group">
+ <a class="btn btn-default" data-toggle="collapse" data-target="#viewdetails">View complete profile</a>
+ <br> <br>
+ <pre class="collapse" id="viewdetails">${model.profile.toString()}</pre>
+ </div>
+ <div class="page-header">
+ </div> <br>
</#macro>
<@page_html/>
http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/65c83582/exec/java-exec/src/main/resources/rest/query/query.ftl
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/resources/rest/query/query.ftl b/exec/java-exec/src/main/resources/rest/query/query.ftl
index 32e18de..5033aca 100644
--- a/exec/java-exec/src/main/resources/rest/query/query.ftl
+++ b/exec/java-exec/src/main/resources/rest/query/query.ftl
@@ -17,6 +17,10 @@
<a href="/queries">back</a><br/>
<div class="page-header">
</div>
+ <div id="message" class="alert alert-info alert-dismissable" style="font-family: Courier;">
+ <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
+ Sample SQL query: <strong>SELECT * FROM cp.`employee.json` LIMIT 20</strong>
+ </div>
<form role="form" action="/query" method="POST">
<div class="form-group">
<label for="queryType">Query Type</label>
@@ -41,7 +45,7 @@
</div>
<div class="form-group">
<label for="query">Query</label>
- <textarea class="form-control" id="query" rows="5" name="query"></textarea>
+ <textarea class="form-control" id="query" rows="5" name="query" style="font-family: Courier;"></textarea>
</div>
<button type="submit" class="btn btn-default">Submit</button>
</form>
http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/65c83582/exec/java-exec/src/main/resources/rest/query/result.ftl
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/resources/rest/query/result.ftl b/exec/java-exec/src/main/resources/rest/query/result.ftl
index a3cb0ce..0d60da8 100644
--- a/exec/java-exec/src/main/resources/rest/query/result.ftl
+++ b/exec/java-exec/src/main/resources/rest/query/result.ftl
@@ -15,8 +15,7 @@
<link rel="stylesheet" type="text/css" href="//cdn.datatables.net/plug-ins/be7019ee387/integration/jqueryui/dataTables.jqueryui.css">
<script type="text/javascript" language="javascript" src="//code.jquery.com/jquery-1.10.2.min.js"></script>
- <script type="text/javascript" language="javascript" src="//cdn.datatables.net/1.10.0/js/jquery.dataTables.min.js"></script>
- <script type="text/javascript" language="javascript" src="//cdn.datatables.net/plug-ins/be7019ee387/integration/jqueryui/dataTables.jqueryui.js"></script>
+ <script type="text/javascript" language="javascript" src="//cdn.datatables.net/1.10.0/js/jquery.dataTables.js"></script>
<link rel="stylesheet" type="text/css" href="//cdn.datatables.net/colvis/1.1.0/css/dataTables.colVis.css">
<script type="text/javascript" language="javascript" src="//cdn.datatables.net/colvis/1.1.0/js/dataTables.colVis.min.js"></script>
@@ -26,35 +25,32 @@
<a href="/queries">back</a><br/>
<div class="page-header">
</div>
- <h2>Result</h2>
- <div style="width=100%; overflow: auto;">
- <table id="relation" class="table table-striped table-bordered table-condensed" style="table-layout: auto; width=100%;">
- <#assign rows = model[0]>
+ <div>
+ <table id="result" class="table table-striped table-bordered table-condensed" style="table-layout: auto; width=100%;">
<thead>
<tr>
- <#list rows as row>
- <th>${row}</th>
+ <#list model.getColumnNames() as value>
+ <th>${value}</th>
</#list>
</tr>
</thead>
<tbody>
- <#list model as rows>
- <#if (rows_index > 0)>
- <tr>
- <#list rows as row>
- <td>${row}</td>
- </#list>
- </tr>
- </#if>
+ <#list model.getRecords() as record>
+ <tr>
+ <#list record as value>
+ <td>${value}</td>
+ </#list>
+ </tr>
</#list>
</tbody>
</table>
</div>
<script charset="utf-8">
$(document).ready(function() {
- $('#relation').dataTable( {
+ $('#result').dataTable( {
"scrollX" : true,
- "dom": '<Clfrtip>'
+ "dom": '<"H"lCfr>t<"F"ip>',
+ "jQueryUI" : true
} );
} );
</script>
http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/65c83582/exec/java-exec/src/main/resources/rest/storage/list.ftl
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/resources/rest/storage/list.ftl b/exec/java-exec/src/main/resources/rest/storage/list.ftl
index 226bd2b..ef97561 100644
--- a/exec/java-exec/src/main/resources/rest/storage/list.ftl
+++ b/exec/java-exec/src/main/resources/rest/storage/list.ftl
@@ -17,58 +17,72 @@
<a href="/queries">back</a><br/>
<div class="page-header">
</div>
- <h3>Registered Storage Plugins</h3>
+ <h4>Enabled Storage Plugins</h4>
<div class="table-responsive">
<table class="table">
<tbody>
- <tr><th colspan="2" style="border:none;">Enabled Plugins</th></tr>
<#list model as plugin>
- <#if plugin.enabled == true>
- <tr>
- <td style="border:none;">
- ${plugin.name}
- </td>
- <td style="border:none;">
- <a class="btn btn-default" href="/storage/${plugin.name}/config/update">Update</a>
- <a class="btn btn-default" href="/storage/${plugin.name}/config/enable/false">Disable</a>
- </td>
- </tr>
- </#if>
+ <#if plugin.enabled() == true>
+ <tr>
+ <td style="border:none; width:200px;">
+ ${plugin.getName()}
+ </td>
+ <td style="border:none;">
+ <a class="btn btn-primary" href="/storage/${plugin.getName()}">Update</a>
+ <a class="btn btn-default" onclick="doEnable('${plugin.getName()}', false)">Disable</a>
+ </td>
+ </tr>
+ </#if>
</#list>
- <tr><th colspan="2" style="border:none;">Disabled Plugins</th></tr>
+ </tbody>
+ </table>
+ </div>
+ <div class="page-header">
+ </div>
+ <h4>Disabled Storage Plugins</h4>
+ <div class="table-responsive">
+ <table class="table">
+ <tbody>
<#list model as plugin>
- <#if plugin.enabled == false>
- <tr>
- <td style="border:none;">
- ${plugin.name}
- </td>
- <td style="border:none;">
- <a class="btn btn-default" href="/storage/${plugin.name}/config/update">Update</a>
- <a class="btn btn-default" href="/storage/${plugin.name}/config/enable/true">Enable</a>
- </td>
- </tr>
- </#if>
+ <#if plugin.enabled() == false>
+ <tr>
+ <td style="border:none; width:200px;">
+ ${plugin.getName()}
+ </td>
+ <td style="border:none;">
+ <a class="btn btn-primary" href="/storage/${plugin.getName()}">Update</a>
+ <a class="btn btn-primary" onclick="doEnable('${plugin.getName()}', true)">Enable</a>
+ </td>
+ </tr>
+ </#if>
</#list>
</tbody>
</table>
</div>
+ <div class="page-header">
+ </div>
<div>
- <h4>Create new storage configuration</h4>
+ <h4>New Storage Plugin</h4>
<form class="form-inline" id="newStorage" role="form" action="/" method="GET">
<div class="form-group">
<input type="text" class="form-control" id="storageName" placeholder="Storage Name">
</div>
- <script>
- function doSubmit() {
- var name = document.getElementById("storageName");
- var form = document.getElementById("newStorage");
- form.action = "/storage/" + name.value + "/config/update";
- form.submit();
- }
- </script>
- <button type="submit" class="btn btn-default" onclick="javascript:doSubmit();">Create</button>
+ <button type="submit" class="btn btn-default" onclick="doSubmit()">Create</button>
</form>
</div>
+ <script>
+ function doSubmit() {
+ var name = document.getElementById("storageName");
+ var form = document.getElementById("newStorage");
+ form.action = "/storage/" + name.value;
+ form.submit();
+ };
+ function doEnable(name, flag) {
+ $.get("/storage/" + name + "/enable/" + flag, function(data) {
+ location.reload();
+ });
+ };
+ </script>
</#macro>
<@page_html/>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/65c83582/exec/java-exec/src/main/resources/rest/storage/update.ftl
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/resources/rest/storage/update.ftl b/exec/java-exec/src/main/resources/rest/storage/update.ftl
index 3ba375d..1b3316a 100644
--- a/exec/java-exec/src/main/resources/rest/storage/update.ftl
+++ b/exec/java-exec/src/main/resources/rest/storage/update.ftl
@@ -11,6 +11,7 @@
<#include "*/generic.ftl">
<#macro page_head>
+ <script src="http://malsup.github.com/jquery.form.js"></script>
</#macro>
<#macro page_body>
@@ -18,24 +19,53 @@
<div class="page-header">
</div>
<h3>Configuration</h3>
- <form role="form" action="/storage/config/update" method="POST">
- <input type="hidden" name="name" value="${model.name}" />
+ <form id="updateForm" role="form" action="/storage/${model.getName()}" method="POST">
+ <input type="hidden" name="name" value="${model.getName()}" />
<div class="form-group">
- <textarea class="form-control" id="config" rows="20" cols="50" name="config" style="font-family: Courier;">${model.config}</textarea>
+ <textarea class="form-control" id="config" rows="20" cols="50" name="config" style="font-family: Courier;">
+ </textarea>
</div>
<a class="btn btn-default" href="/storage">Back</a>
- <button class="btn btn-default" type="submit">
- <#if model.exists >Update<#else>Create</#if>
+ <button class="btn btn-default" type="submit" onclick="doUpdate();">
+ <#if model.exists()>Update<#else>Create</#if>
</button>
- <#if model.enabled>
- <a class="btn btn-default" href="/storage/${model.name}/config/enable/false">Disable</a>
- <#else>
- <a class="btn btn-default" href="/storage/${model.name}/config/enable/true">Enable</a>
- </#if>
- <#if model.exists>
- <a class="btn btn-danger" href="/storage/${model.name}/config/delete">Delete</a>
+ <#if model.exists()>
+ <#if model.enabled()>
+ <a id="enabled" class="btn btn-default">Disable</a>
+ <#else>
+ <a id="enabled" class="btn btn-primary">Enable</a>
+ </#if>
+ <a id="del" class="btn btn-danger" onclick="deleteFunction()">Delete</a>
</#if>
</form>
+ <br>
+ <div id="message" class="hidden alert alert-info">
+ </div>
+ <script>
+ $.get("/storage/${model.getName()}.json", function(data) {
+ $("#config").val(JSON.stringify(data.config, null, 2));
+ });
+ $("#enabled").click(function() {
+ $.get("/storage/${model.getName()}/enable/<#if model.enabled()>false<#else>true</#if>", function(data) {
+ $("#message").removeClass("hidden").text(data.result).alert();
+ setTimeout(function() { location.reload(); }, 800);
+ });
+ });
+ function doUpdate() {
+ $("#updateForm").ajaxForm(function(data) {
+ $("#message").removeClass("hidden").text(data.result).alert();
+ setTimeout(function() { location.reload(); }, 800);
+ });
+ };
+ function deleteFunction() {
+ var temp = confirm("Are you sure?");
+ if (temp == true) {
+ $.get("/storage/${model.getName()}/delete", function(data) {
+ window.location.href = "/storage";
+ });
+ }
+ };
+ </script>
</#macro>
<@page_html/>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/65c83582/exec/java-exec/src/main/resources/rest/www/graph.js
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/resources/rest/www/graph.js b/exec/java-exec/src/main/resources/rest/www/graph.js
new file mode 100644
index 0000000..0173da5
--- /dev/null
+++ b/exec/java-exec/src/main/resources/rest/www/graph.js
@@ -0,0 +1,82 @@
+/*
+ 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. */
+
+
+$(window).load(function () {
+ document.getElementById("renderbutton").addEventListener("click", function () {
+ this.remove();
+ var queryid = window.location.href.split("/").splice(-1);
+ $.ajax({
+ type: "GET",
+ dataType: "json",
+ url: "/profiles/" + queryid + ".json",
+ success: function (profile) {
+ var colors = ["red", "green", "blue", "orange", "yellow", "pink", "purple", "gray"];
+
+ var plan = $.map(profile.plan.trim().split("\n"), function (s) {
+ return [/^([0-9-]+)( *)([a-zA-Z]*)/.exec(s).slice(1)];
+ });
+
+ // nodes
+ var g = new dagreD3.Digraph();
+ for (var i = 0; i < plan.length; i++) {
+ g.addNode(plan[i][0], {
+ label: plan[i][2],
+ fragment: parseInt(plan[i][0].split("-")[0])
+ });
+ }
+
+ // edges
+ var st = [plan[0]];
+ for (var i = 1; i < plan.length; i++) {
+ var top = st.pop();
+ while (top[1].length >= plan[i][1].length)
+ top = st.pop();
+
+ g.addEdge(null, plan[i][0], top[0]);
+
+ if (plan[i][1].length != top[1].length)
+ st.push(top);
+ if (plan[i][1].length >= top[1].length)
+ st.push(plan[i]);
+ }
+
+ // rendering
+ var renderer = new dagreD3.Renderer();
+ renderer.zoom(function () {return function (graph, root) {}});
+ var oldDrawNodes = renderer.drawNodes();
+ renderer.drawNodes(function(graph, root) {
+ var svgNodes = oldDrawNodes(graph, root);
+ svgNodes.each(function(u) {
+ d3.select(this).style("fill", colors[graph.node(u).fragment % colors.length]);
+ });
+ return svgNodes;
+ });
+
+ // page placement
+ var layout = dagreD3.layout()
+ .nodeSep(20);//.rankDir("LR");
+ var layout = renderer.layout(layout).run(g, d3.select("svg g"));
+
+ //var layout = renderer.run(g, d3.select("svg g"));
+ d3.select("svg")
+ .attr("width", layout.graph().width + 40)
+ .attr("height", layout.graph().height + 40);
+ },
+ error: function (x, y, z) {
+ console.log(x);
+ console.log(y);
+ console.log(z);
+ }
+ });
+ }, false);
+});
http://git-wip-us.apache.org/repos/asf/incubator-drill/blob/65c83582/exec/java-exec/src/main/resources/rest/www/style.css
----------------------------------------------------------------------
diff --git a/exec/java-exec/src/main/resources/rest/www/style.css b/exec/java-exec/src/main/resources/rest/www/style.css
new file mode 100644
index 0000000..46ff811
--- /dev/null
+++ b/exec/java-exec/src/main/resources/rest/www/style.css
@@ -0,0 +1,27 @@
+/*
+ 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. */
+
+
+.node rect {
+ stroke: #000;
+ stroke-width: 1px;
+}
+
+.node text {
+ fill: #000;
+}
+
+.edgePath path {
+ stroke: #333;
+ stroke-width: 1.5px;
+ fill: none;
+}