diff --git a/common/src/java/org/apache/hadoop/hive/ql/log/PerfLogger.java b/common/src/java/org/apache/hadoop/hive/ql/log/PerfLogger.java index d4194cf..8fa5cbf 100644 --- a/common/src/java/org/apache/hadoop/hive/ql/log/PerfLogger.java +++ b/common/src/java/org/apache/hadoop/hive/ql/log/PerfLogger.java @@ -18,6 +18,7 @@ package org.apache.hadoop.hive.ql.log; +import com.google.common.collect.ImmutableMap; import org.apache.hadoop.hive.common.metrics.common.Metrics; import org.apache.hadoop.hive.common.metrics.common.MetricsFactory; import org.apache.hadoop.hive.conf.HiveConf; @@ -222,4 +223,12 @@ private void endMetrics(String method) { LOG.warn("Error recording metrics", e); } } + + public ImmutableMap getStartTimes() { + return ImmutableMap.copyOf(startTimes); + } + + public ImmutableMap getEndTimes() { + return ImmutableMap.copyOf(endTimes); + } } diff --git a/common/src/java/org/apache/hive/http/HttpServer.java b/common/src/java/org/apache/hive/http/HttpServer.java index 9e23b11..b8836de 100644 --- a/common/src/java/org/apache/hive/http/HttpServer.java +++ b/common/src/java/org/apache/hive/http/HttpServer.java @@ -435,7 +435,7 @@ String getWebAppsPath(String appName) throws FileNotFoundException { * @param pathSpec The path spec for the servlet * @param clazz The servlet class */ - void addServlet(String name, String pathSpec, + public void addServlet(String name, String pathSpec, Class clazz) { ServletHolder holder = new ServletHolder(clazz); if (name != null) { diff --git a/pom.xml b/pom.xml index 802d3d4..844ee15 100644 --- a/pom.xml +++ b/pom.xml @@ -138,6 +138,8 @@ 2.4.2 5.5.23 + 2.3.4 + 2.3.1 0.3.2 3.0.0.v201112011016 5.5.1 @@ -723,6 +725,11 @@ + + org.jamon + jamon-runtime + ${jamon-runtime.version} + @@ -1062,6 +1069,11 @@ + + org.jamon + jamon-maven-plugin + ${jamon.plugin.version} + diff --git a/ql/src/java/org/apache/hadoop/hive/ql/Driver.java b/ql/src/java/org/apache/hadoop/hive/ql/Driver.java index 4c89812..553da89 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/Driver.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/Driver.java @@ -36,6 +36,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import org.apache.commons.lang.StringUtils; import org.apache.hadoop.mapreduce.MRJobConfig; @@ -53,7 +54,6 @@ import org.apache.hadoop.hive.ql.exec.ConditionalTask; import org.apache.hadoop.hive.ql.exec.ExplainTask; import org.apache.hadoop.hive.ql.exec.FetchTask; -import org.apache.hadoop.hive.ql.exec.Operator; import org.apache.hadoop.hive.ql.exec.TableScanOperator; import org.apache.hadoop.hive.ql.exec.Task; import org.apache.hadoop.hive.ql.exec.TaskFactory; @@ -159,7 +159,7 @@ private String operationId; // For WebUI. Kept alive after queryPlan is freed. - private String savedQueryString; + private final QueryDisplay queryDisplay = new QueryDisplay(); private boolean checkConcurrency() { boolean supportConcurrency = conf.getBoolVar(HiveConf.ConfVars.HIVE_SUPPORT_CONCURRENCY); @@ -387,7 +387,6 @@ public int compile(String command, boolean resetTaskIds) { } catch (Exception e) { LOG.warn("WARNING! Query command could not be redacted." + e); } - this.savedQueryString = queryStr; //holder for parent command type/string when executing reentrant queries QueryState queryState = new QueryState(); @@ -409,6 +408,10 @@ public int compile(String command, boolean resetTaskIds) { conf.setVar(HiveConf.ConfVars.HIVEQUERYID, queryId); } + //save some info for webUI for use after plan is freed + this.queryDisplay.setQueryStr(queryStr); + this.queryDisplay.setQueryId(queryId); + LOG.info("Compiling command(queryId=" + queryId + "): " + queryStr); SessionState.get().setupQueryCurrentTimestamp(); @@ -519,11 +522,19 @@ public void run() { } } - if (conf.getBoolVar(ConfVars.HIVE_LOG_EXPLAIN_OUTPUT)) { + if (conf.getBoolVar(ConfVars.HIVE_LOG_EXPLAIN_OUTPUT) || + (conf.getIntVar(ConfVars.HIVE_SERVER2_WEBUI_PORT) != 0 && + conf.getIntVar(ConfVars.HIVE_SERVER2_WEBUI_MAX_HISTORIC_QUERIES) > 0)) { String explainOutput = getExplainOutput(sem, plan, tree); if (explainOutput != null) { - LOG.info("EXPLAIN output for queryid " + queryId + " : " + if (conf.getBoolVar(ConfVars.HIVE_LOG_EXPLAIN_OUTPUT)) { + LOG.info("EXPLAIN output for queryid " + queryId + " : " + explainOutput); + } + if (conf.getIntVar(ConfVars.HIVE_SERVER2_WEBUI_PORT) != 0 && + conf.getIntVar(ConfVars.HIVE_SERVER2_WEBUI_MAX_HISTORIC_QUERIES) > 0) { + queryDisplay.setExplainPlan(explainOutput); + } } } return 0; @@ -549,18 +560,20 @@ public void run() { // since it exceeds valid range of shell return values } finally { double duration = perfLogger.PerfLogEnd(CLASS_NAME, PerfLogger.COMPILE)/1000.00; - dumpMetaCallTimingWithoutEx("compilation"); + ImmutableMap compileHMSTimings = dumpMetaCallTimingWithoutEx("compilation"); + queryDisplay.setCompileHMSTimings(compileHMSTimings); restoreSession(queryState); LOG.info("Completed compiling command(queryId=" + queryId + "); Time taken: " + duration + " seconds"); } } - private void dumpMetaCallTimingWithoutEx(String phase) { + private ImmutableMap dumpMetaCallTimingWithoutEx(String phase) { try { - Hive.get().dumpAndClearMetaCallTiming(phase); + return Hive.get().dumpAndClearMetaCallTiming(phase); } catch (HiveException he) { LOG.warn("Caught exception attempting to write metadata call information " + he, he); } + return null; } /** @@ -1339,6 +1352,8 @@ else if(plan.getOperation() == HiveOperation.ROLLBACK) { } perfLogger.PerfLogEnd(CLASS_NAME, PerfLogger.DRIVER_RUN); + queryDisplay.setStartTimes(perfLogger.getStartTimes()); + queryDisplay.setEndTimes(perfLogger.getEndTimes()); // Take all the driver run hooks and post-execute them. try { @@ -1410,6 +1425,7 @@ private boolean requiresLock() { } private CommandProcessorResponse createProcessorResponse(int ret) { + queryDisplay.setErrorMessage(errorMessage); return new CommandProcessorResponse(ret, errorMessage, SQLState, downstreamError); } @@ -1540,6 +1556,7 @@ public int execute() throws CommandNeedRetryException { // Launch upto maxthreads tasks Task task; while ((task = driverCxt.getRunnable(maxthreads)) != null) { + queryDisplay.newTask(task); TaskRunner runner = launchTask(task, queryId, noName, jobname, jobs, driverCxt); if (!runner.isRunning()) { break; @@ -1552,6 +1569,7 @@ public int execute() throws CommandNeedRetryException { continue; } hookContext.addCompleteTask(tskRun); + queryDisplay.taskFinished(tskRun.getTask().getId(), tskRun.getTaskResult()); Task tsk = tskRun.getTask(); TaskResult result = tskRun.getTaskResult(); @@ -1690,9 +1708,11 @@ public int execute() throws CommandNeedRetryException { if (noName) { conf.set(MRJobConfig.JOB_NAME, ""); } - dumpMetaCallTimingWithoutEx("execution"); double duration = perfLogger.PerfLogEnd(CLASS_NAME, PerfLogger.DRIVER_EXECUTE)/1000.00; + ImmutableMap executionHMSTimings = dumpMetaCallTimingWithoutEx("execution"); + queryDisplay.setExecuteHMSTimings(executionHMSTimings); + Map stats = SessionState.get().getMapRedStats(); if (stats != null && !stats.isEmpty()) { long totalCpu = 0; @@ -1948,8 +1968,8 @@ public String getErrorMsg() { } - public String getQueryString() { - return savedQueryString == null ? "Unknown" : savedQueryString; + public QueryDisplay getQueryDisplay() { + return queryDisplay; } /** @@ -1959,5 +1979,4 @@ public String getQueryString() { public void setOperationId(String opId) { this.operationId = opId; } - } diff --git a/ql/src/java/org/apache/hadoop/hive/ql/QueryDisplay.java b/ql/src/java/org/apache/hadoop/hive/ql/QueryDisplay.java new file mode 100644 index 0000000..7e3a014 --- /dev/null +++ b/ql/src/java/org/apache/hadoop/hive/ql/QueryDisplay.java @@ -0,0 +1,222 @@ +/** + * 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.hadoop.hive.ql; + +import com.google.common.collect.ImmutableMap; +import org.apache.hadoop.hive.ql.exec.Task; +import org.apache.hadoop.hive.ql.exec.TaskResult; +import org.apache.hadoop.hive.ql.plan.api.StageType; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Some limited query information to save for WebUI. + * + * The class is synchronized, as WebUI may access information about a running query. + */ +public class QueryDisplay { + private String queryStr; + private String explainPlan; + private String errorMessage; + private String queryId; + private ImmutableMap compileHMSTimings; + private ImmutableMap executeHMSTimings; + private ImmutableMap startTimes; + private ImmutableMap endTimes; + + final LinkedHashMap tasks = new LinkedHashMap(); + + public static class TaskInfo { + private Integer returnVal; + private String errorMsg; + private long endTime; + + final long beginTime; + final String taskId; + final StageType taskType; + final String name; + final boolean requireLock; + final boolean retryIfFail; + + public TaskInfo (Task task) { + beginTime = System.currentTimeMillis(); + taskId = task.getId(); + taskType = task.getType(); + name = task.getName(); + requireLock = task.requireLock(); + retryIfFail = task.ifRetryCmdWhenFail(); + } + + public synchronized void updateTask(Integer returnVal, String errorMsg, long endTime) { + this.returnVal = returnVal; + this.errorMsg = errorMsg; + this.endTime = endTime; + } + + public synchronized String getStatus() { + if (returnVal == null) { + return "Running"; + } else if (returnVal == 0) { + return "Success, ReturnVal 0"; + } else { + return "Failure, ReturnVal " + String.valueOf(returnVal); + } + } + + public synchronized long getElapsedTime() { + if (endTime == 0) { + return System.currentTimeMillis() - beginTime; + } else { + return endTime - beginTime; + } + } + + public synchronized String getErrorMsg() { + return errorMsg; + } + + public synchronized long getEndTime() { + return endTime; + } + + public synchronized long getBeginTime() { + return beginTime; + } + + public synchronized String getTaskId() { + return taskId; + } + + public synchronized StageType getTaskType() { + return taskType; + } + + public synchronized String getName() { + return name; + } + + public synchronized boolean isRequireLock() { + return requireLock; + } + + public synchronized boolean isRetryIfFail() { + return retryIfFail; + } + } + + public synchronized void newTask(Task task) { + tasks.put(task.getId(), new TaskInfo(task)); + } + + public synchronized void taskFinished(String taskId, TaskResult result) { + TaskInfo taskInfo = tasks.get(taskId); + if (taskInfo != null) { + taskInfo.returnVal = result.getExitVal(); + if (result.getTaskError() != null) { + taskInfo.errorMsg = result.getTaskError().toString(); + } + taskInfo.endTime = System.currentTimeMillis(); + } + } + + public List getTaskInfos() { + List taskInfos = new ArrayList(); + synchronized(this) { + taskInfos.addAll(tasks.values()); + } + return taskInfos; + } + + public synchronized void setQueryStr(String queryStr) { + this.queryStr = queryStr; + } + + public synchronized String getQueryString() { + return returnStringOrUnknown(queryStr); + } + + public synchronized String getExplainPlan() { + return returnStringOrUnknown(explainPlan); + } + + public synchronized void setExplainPlan(String explainPlan) { + this.explainPlan = explainPlan; + } + + public synchronized Map getCompileHMSTimings() { + return compileHMSTimings; + } + + public synchronized void setCompileHMSTimings(ImmutableMap compileHMSTimings) { + this.compileHMSTimings = compileHMSTimings; + } + + public synchronized Map getExecuteHMSTimings() { + return executeHMSTimings; + } + + public synchronized void setExecuteHMSTimings(ImmutableMap executeHMSTimings) { + this.executeHMSTimings = executeHMSTimings; + } + + public synchronized void setStartTimes(ImmutableMap startTimes) { + this.startTimes = startTimes; + } + + public synchronized void setEndTimes(ImmutableMap endTimes) { + this.endTimes = endTimes; + } + + public synchronized Map getTimes() { + Map times = new HashMap<>(); + if (endTimes != null && startTimes != null) { + for (String timeKey : endTimes.keySet()) { + Long endTime = endTimes.get(timeKey); + Long startTime = startTimes.get(timeKey); + if (startTime != null) { + times.put(timeKey, endTime - startTime); + } + } + } + return times; + } + + public synchronized String getErrorMessage() { + return errorMessage; + } + + public synchronized void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public synchronized String getQueryId() { + return returnStringOrUnknown(queryId); + } + + public synchronized void setQueryId(String queryId) { + this.queryId = queryId; + } + + private String returnStringOrUnknown(String s) { + return s == null ? "Unknown" : s; + } +} diff --git a/ql/src/java/org/apache/hadoop/hive/ql/metadata/Hive.java b/ql/src/java/org/apache/hadoop/hive/ql/metadata/Hive.java index 0bab769..4e574f7 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/metadata/Hive.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/metadata/Hive.java @@ -44,6 +44,7 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import com.google.common.collect.ImmutableMap; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; @@ -3400,7 +3401,7 @@ public void clearMetaCallTiming() { metaCallTimeMap.clear(); } - public void dumpAndClearMetaCallTiming(String phase) { + public ImmutableMap dumpAndClearMetaCallTiming(String phase) { boolean phaseInfoLogged = false; if (LOG.isDebugEnabled()) { phaseInfoLogged = logDumpPhase(phase); @@ -3420,7 +3421,10 @@ public void dumpAndClearMetaCallTiming(String phase) { } } } + + ImmutableMap result = ImmutableMap.copyOf(metaCallTimeMap); metaCallTimeMap.clear(); + return result; } private boolean logDumpPhase(String phase) { diff --git a/service/pom.xml b/service/pom.xml index b2e3a84..e3f61d0 100644 --- a/service/pom.xml +++ b/service/pom.xml @@ -128,7 +128,13 @@ ${hadoop.version} true - + + org.jamon + jamon-runtime + ${jamon-runtime.version} + + + org.apache.hive hive-exec @@ -219,7 +225,48 @@ - + + org.jamon + jamon-maven-plugin + + + generate-sources + + translate + + + src/jamon + target/generated-jamon + + + + + + + org.apache.maven.plugins + maven-eclipse-plugin + + + org.jamon.project.jamonnature + + + org.jamon.project.templateBuilder + org.eclipse.jdt.core.javabuilder + org.jamon.project.markerUpdater + + + + .settings/org.jamon.prefs + # now + eclipse.preferences.version=1 + templateSourceDir=src/main/jamon + templateOutputDir=target/generated-jamon + + + + + + org.apache.maven.plugins maven-jar-plugin diff --git a/service/src/jamon/org/apache/hive/tmpl/QueryProfileTmpl.jamon b/service/src/jamon/org/apache/hive/tmpl/QueryProfileTmpl.jamon new file mode 100644 index 0000000..5a241fd --- /dev/null +++ b/service/src/jamon/org/apache/hive/tmpl/QueryProfileTmpl.jamon @@ -0,0 +1,281 @@ +<%doc> + +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. + +<%args> +SQLOperationDisplay sod; + +<%import> +java.util.*; +org.apache.hadoop.hive.ql.QueryDisplay; +org.apache.hive.service.cli.operation.SQLOperationDisplay; + + + + + + + HiveServer2 + + + + + + + + + + + + + + <%if sod == null %> +
+

Query not found. It may have been deleted, increase hive.server2.webui.max.historic.queries + to retain more historic query information.

+
+ <%else> + + +
+
+ +
+
+ + +
+ +
+
+ <& baseProfile; sod = sod &> +
+
+ <& stages; sod = sod &> +
+
+ <& queryPlan; sod = sod &> +
+
+ <& perfLogging; sod = sod &> +
+
+
+ + + +
+
+ + + + + + +<%def baseProfile> +<%args> + SQLOperationDisplay sod; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <%if sod.getQueryDisplay() != null && sod.getQueryDisplay().getErrorMessage() != null %> + + + + + +
User Name<% sod.getUserName() %>
Query String<% sod.getQueryDisplay() == null ? "Unknown" : sod.getQueryDisplay().getQueryString() %>
Query Id<% sod.getQueryDisplay() == null ? "Unknown" : sod.getQueryDisplay().getQueryId() %>
Execution Engine<% sod.getExecutionEngine() %> +
State<% sod.getState() %>
Begin Time<% new Date(sod.getBeginTime()) %>
Elapsed Time (s)<% sod.getElapsedTime()/1000 %>
End Time<% sod.getEndTime() == null ? "In Progress" : new Date(sod.getEndTime()) %>
Error<% sod.getEndTime() == null ? "In Progress" : new Date(sod.getEndTime()) %>
+ + +<%def stages> +<%args> + SQLOperationDisplay sod; + + + + + + + + + + + + + + <%if sod.getQueryDisplay() != null && sod.getQueryDisplay().getTaskInfos() != null %> + <%for QueryDisplay.TaskInfo taskInfo : sod.getQueryDisplay().getTaskInfos() %> + + + + + + + + + + + +
Stage IdStage NameStatusBegin TimeEnd TimeElapsed Time (s)Requires LockRetry If Fail
<% taskInfo.getTaskId() + ":" + taskInfo.getTaskType() %><% taskInfo.getName() %><% taskInfo.getStatus() %><% new Date(taskInfo.getBeginTime()) %> + <% taskInfo.getEndTime() == 0 ? "" : new Date(taskInfo.getEndTime()) %><% taskInfo.getElapsedTime()/1000 %> (s) <% taskInfo.isRequireLock() %><% taskInfo.isRetryIfFail() %>
+ + + +<%def queryPlan> +<%args> + SQLOperationDisplay sod; + +
+
Explain plan
+
+
+        <% sod.getQueryDisplay() == null ? "Unknown" : sod.getQueryDisplay().getExplainPlan() %>
+        
+
+
+ + + +<%def perfLogging> +<%args> + SQLOperationDisplay sod; + +
+

Compile-time metadata operations

+ + + + + + + <%if sod.getQueryDisplay() != null && sod.getQueryDisplay().getCompileHMSTimings() != null %> + <%for Map.Entry time : sod.getQueryDisplay().getCompileHMSTimings().entrySet() %> + + + + + + +
Call NameTime (ms)
<% time.getKey() %><% time.getValue() %>
+
+ +
+

Execution-time metadata operations

+ + + + + + + <%if sod.getQueryDisplay() != null && sod.getQueryDisplay().getExecuteHMSTimings() != null %> + <%for Map.Entry time : sod.getQueryDisplay().getExecuteHMSTimings().entrySet() %> + + + + + + +
Call NameTime (ms)
<% time.getKey() %><% time.getValue() %>
+
+ +
+

Perf-Logger

+ + + + + + + <%if sod.getQueryDisplay() != null && sod.getQueryDisplay().getTimes() != null %> + <%for Map.Entry time : sod.getQueryDisplay().getTimes().entrySet() %> + + + + + + +
Call NameTime (ms)
<% time.getKey() %><% time.getValue() %>
+
+ + + + + + + + + \ No newline at end of file diff --git a/service/src/java/org/apache/hive/service/cli/operation/Operation.java b/service/src/java/org/apache/hive/service/cli/operation/Operation.java index 0c263cf..8340202 100644 --- a/service/src/java/org/apache/hive/service/cli/operation/Operation.java +++ b/service/src/java/org/apache/hive/service/cli/operation/Operation.java @@ -157,6 +157,7 @@ protected final OperationState setState(OperationState newState) throws HiveSQLE state.validateTransition(newState); this.state = newState; setMetrics(state); + onNewState(state); this.lastAccessTime = System.currentTimeMillis(); return this.state; } @@ -420,4 +421,7 @@ public long getBeginTime() { protected OperationState getState() { return state; } + + protected void onNewState(OperationState state) { + } } diff --git a/service/src/java/org/apache/hive/service/cli/operation/OperationManager.java b/service/src/java/org/apache/hive/service/cli/operation/OperationManager.java index f1ce6f6..9f3bca2 100644 --- a/service/src/java/org/apache/hive/service/cli/operation/OperationManager.java +++ b/service/src/java/org/apache/hive/service/cli/operation/OperationManager.java @@ -22,7 +22,9 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -65,8 +67,10 @@ private final ConcurrentHashMap handleToOperation = new ConcurrentHashMap(); - //for displaying historical queries on WebUI - private Queue historicSqlOperations; + //Following fields for displaying queries on WebUI + private Object webuiLock = new Object(); + private SQLOperationDisplayCache historicSqlOperations; + private Map liveSqlOperations = new LinkedHashMap(); public OperationManager() { super(OperationManager.class.getSimpleName()); @@ -82,7 +86,7 @@ public synchronized void init(HiveConf hiveConf) { } if ((hiveConf.getIntVar(HiveConf.ConfVars.HIVE_SERVER2_WEBUI_PORT) != 0) && hiveConf.getIntVar(HiveConf.ConfVars.HIVE_SERVER2_WEBUI_MAX_HISTORIC_QUERIES) > 0) { - historicSqlOperations = EvictingQueue.create( + historicSqlOperations = new SQLOperationDisplayCache( hiveConf.getIntVar(ConfVars.HIVE_SERVER2_WEBUI_MAX_HISTORIC_QUERIES)); } super.init(hiveConf); @@ -186,7 +190,11 @@ private Operation removeTimedOutOperation(OperationHandle operationHandle) { Operation operation = handleToOperation.get(operationHandle); if (operation != null && operation.isTimedOut(System.currentTimeMillis())) { handleToOperation.remove(operationHandle, operation); - cacheOldOperationInfo(operation); + synchronized (webuiLock) { + String opKey = operationHandle.getHandleIdentifier().toString(); + SQLOperationDisplay display = liveSqlOperations.remove(opKey); + historicSqlOperations.put(opKey, display); + } return operation; } return null; @@ -194,11 +202,21 @@ private Operation removeTimedOutOperation(OperationHandle operationHandle) { private void addOperation(Operation operation) { handleToOperation.put(operation.getHandle(), operation); + if (operation instanceof SQLOperation) { + synchronized (webuiLock) { + liveSqlOperations.put(operation.getHandle().getHandleIdentifier().toString(), + ((SQLOperation) operation).getSQLOperationDisplay()); + } + } } private Operation removeOperation(OperationHandle opHandle) { Operation result = handleToOperation.remove(opHandle); - cacheOldOperationInfo(result); + synchronized (webuiLock) { + String opKey = opHandle.getHandleIdentifier().toString(); + SQLOperationDisplay display = liveSqlOperations.remove(opKey); + historicSqlOperations.put(opKey, display); + } return result; } @@ -329,35 +347,40 @@ public OperationLog getOperationLogByThread() { return removed; } - //Cache a number of historical operation info, at max number of - //hive.server2.webui.max.historic.queries. - private void cacheOldOperationInfo(Operation oldOperation) { - if ((getHiveConf().getIntVar(HiveConf.ConfVars.HIVE_SERVER2_WEBUI_PORT) != 0) && - getHiveConf().getIntVar(HiveConf.ConfVars.HIVE_SERVER2_WEBUI_MAX_HISTORIC_QUERIES) > 0) { - if (oldOperation instanceof SQLOperation) { - SQLOperation query = (SQLOperation) oldOperation; - SQLOperationInfo queryInfo = query.getSQLOperationInfo(); - if (queryInfo != null) { - synchronized (historicSqlOperations) { - historicSqlOperations.add(queryInfo); - } - } - } + /** + * @return displays representing a number of historical SQLOperations, at max number of + * hive.server2.webui.max.historic.queries + */ + public List getHistoricalSQLOperations() { + List result = new LinkedList<>(); + synchronized (webuiLock) { + result.addAll(historicSqlOperations.values()); } + return result; } /** - * @return a number of historical SQLOperation info, at max number of - * hive.server2.webui.max.historic.queries + * @return displays representing live SQLOperations */ - public List getHistoricalSQLOpInfo() { - List result = new LinkedList<>(); - synchronized (historicSqlOperations) { - Iterator opIterator = historicSqlOperations.iterator(); - while (opIterator.hasNext()) { - result.add(opIterator.next()); - } + public List getLiveSqlOperations() { + List result = new LinkedList<>(); + synchronized (webuiLock) { + result.addAll(liveSqlOperations.values()); } return result; } + + /** + * @param handle handle of SQLOperation. + * @return display representing a particular SQLOperation. + */ + public SQLOperationDisplay getSQLOperationDisplay(String handle) { + synchronized (webuiLock) { + SQLOperationDisplay result = liveSqlOperations.get(handle); + if (result != null) { + return result; + } + return historicSqlOperations.get(handle); + } + } } diff --git a/service/src/java/org/apache/hive/service/cli/operation/SQLOperation.java b/service/src/java/org/apache/hive/service/cli/operation/SQLOperation.java index 01b1d3d..3fbbb70 100644 --- a/service/src/java/org/apache/hive/service/cli/operation/SQLOperation.java +++ b/service/src/java/org/apache/hive/service/cli/operation/SQLOperation.java @@ -34,8 +34,6 @@ import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.CharEncoding; import org.apache.hadoop.hive.conf.HiveConf; -import org.apache.hadoop.hive.conf.HiveVariableSource; -import org.apache.hadoop.hive.conf.VariableSubstitution; import org.apache.hadoop.hive.metastore.api.FieldSchema; import org.apache.hadoop.hive.metastore.api.Schema; import org.apache.hadoop.hive.ql.CommandNeedRetryException; @@ -79,11 +77,20 @@ private SerDe serde = null; private boolean fetchStarted = false; + //Display for WebUI. + private SQLOperationDisplay sqlOpDisplay; + + public SQLOperation(HiveSession parentSession, String statement, Map confOverlay, boolean runInBackground) { // TODO: call setRemoteUser in ExecuteStatementOperation or higher. super(parentSession, statement, confOverlay, runInBackground); setupSessionIO(parentSession.getSessionState()); + try { + sqlOpDisplay = new SQLOperationDisplay(this); + } catch (HiveSQLException e) { + LOG.warn("Error calcluating SQL Operation Display for webui", e); + } } private void setupSessionIO(SessionState sessionState) { @@ -111,6 +118,7 @@ public void prepare(HiveConf sqlOperationConf) throws HiveSQLException { try { driver = new Driver(sqlOperationConf, getParentSession().getUserName()); + sqlOpDisplay.setQueryDisplay(driver.getQueryDisplay()); // set the operation handle information in Driver, so that thrift API users // can use the operation handle they receive, to lookup query information in @@ -162,10 +170,6 @@ public void prepare(HiveConf sqlOperationConf) throws HiveSQLException { } } - public String getQueryStr() { - return driver == null ? "Unknown" : driver.getQueryString(); - } - private void runQuery(HiveConf sqlOperationConf) throws HiveSQLException { try { // In Hive server mode, we are not able to retry in the FetchTask @@ -485,18 +489,17 @@ public HiveConf getConfigForOperation() throws HiveSQLException { /** * Get summary information of this SQLOperation for display in WebUI. */ - public SQLOperationInfo getSQLOperationInfo() { - try { - return new SQLOperationInfo( - getParentSession().getUserName(), - driver.getQueryString(), - getConfigForOperation().getVar(HiveConf.ConfVars.HIVE_EXECUTION_ENGINE), - getState(), - (int) (System.currentTimeMillis() - getBeginTime()) / 1000, - System.currentTimeMillis()); - } catch (HiveSQLException e) { - LOG.warn("Error calcluating SQL Operation Info for webui", e); + public SQLOperationDisplay getSQLOperationDisplay() { + return sqlOpDisplay; + } + + @Override + protected void onNewState(OperationState state) { + if (state == OperationState.CLOSED) { + sqlOpDisplay.closed(); + } else { + //CLOSED state not interesting, state before (FINISHED, ERROR) is. + sqlOpDisplay.updateState(state); } - return null; } } diff --git a/service/src/java/org/apache/hive/service/cli/operation/SQLOperationDisplay.java b/service/src/java/org/apache/hive/service/cli/operation/SQLOperationDisplay.java new file mode 100644 index 0000000..ac5f40e --- /dev/null +++ b/service/src/java/org/apache/hive/service/cli/operation/SQLOperationDisplay.java @@ -0,0 +1,100 @@ +/** + * 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.hive.service.cli.operation; + +import org.apache.hadoop.hive.conf.HiveConf; +import org.apache.hadoop.hive.ql.QueryDisplay; +import org.apache.hive.service.cli.HiveSQLException; +import org.apache.hive.service.cli.OperationState; + +/** + * Used to display some info in the HS2 WebUI. + */ +public class SQLOperationDisplay { + public String userName; + public QueryDisplay queryDisplay; + public String executionEngine; + public OperationState state; + public long beginTime; + public Long endTime; + public String operationId; + + public SQLOperationDisplay(SQLOperation sqlOperation) throws HiveSQLException { + this.state = sqlOperation.getState(); + this.userName = sqlOperation.getParentSession().getUserName(); + this.executionEngine = sqlOperation.getConfigForOperation().getVar(HiveConf.ConfVars.HIVE_EXECUTION_ENGINE); + this.beginTime = System.currentTimeMillis(); + this.operationId = sqlOperation.getHandle().getHandleIdentifier().toString(); + } + + public synchronized long getElapsedTime() { + if (isRunning()) { + return System.currentTimeMillis() - beginTime; + } else { + return endTime - beginTime; + } + } + + public synchronized void queryFinished() { + this.endTime = System.currentTimeMillis(); + } + + public synchronized boolean isRunning() { + return endTime == null; + } + + public synchronized QueryDisplay getQueryDisplay() { + return queryDisplay; + } + + public synchronized void setQueryDisplay(QueryDisplay queryDisplay) { + this.queryDisplay = queryDisplay; + } + + public synchronized String getUserName() { + return userName; + } + + public synchronized String getExecutionEngine() { + return executionEngine; + } + + public synchronized OperationState getState() { + return state; + } + + public synchronized long getBeginTime() { + return beginTime; + } + + public synchronized Long getEndTime() { + return endTime; + } + + public synchronized void updateState(OperationState state) { + this.state = state; + } + + public synchronized String getOperationId() { + return operationId; + } + + public synchronized void closed() { + this.endTime = System.currentTimeMillis(); + } +} diff --git a/service/src/java/org/apache/hive/service/cli/operation/SQLOperationDisplayCache.java b/service/src/java/org/apache/hive/service/cli/operation/SQLOperationDisplayCache.java new file mode 100644 index 0000000..0ac1c12 --- /dev/null +++ b/service/src/java/org/apache/hive/service/cli/operation/SQLOperationDisplayCache.java @@ -0,0 +1,38 @@ +/** + * 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.hive.service.cli.operation; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Cache some SQLOperation information for WebUI + */ +public class SQLOperationDisplayCache extends LinkedHashMap { + + private final int capacity; + + public SQLOperationDisplayCache(int capacity) { + super(capacity + 1, 1.1f, false); + this.capacity = capacity; + } + + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > capacity; + } +} diff --git a/service/src/java/org/apache/hive/service/cli/operation/SQLOperationInfo.java b/service/src/java/org/apache/hive/service/cli/operation/SQLOperationInfo.java deleted file mode 100644 index 179f6dd..0000000 --- a/service/src/java/org/apache/hive/service/cli/operation/SQLOperationInfo.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * 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.hive.service.cli.operation; - -import org.apache.hive.service.cli.OperationState; - -/** - * Used to display some info in the HS2 WebUI. - */ -public class SQLOperationInfo { - public String userName; - public String queryStr; - public String executionEngine; - public OperationState endState; //state before CLOSED (one of CANCELLED, FINISHED, ERROR) - public int elapsedTime; - public long endTime; - - public SQLOperationInfo( - String userName, - String queryStr, - String executionEngine, - OperationState endState, - int elapsedTime, - long endTime - ) { - this.userName = userName; - this.queryStr = queryStr; - this.executionEngine = executionEngine; - this.endState = endState; - this.elapsedTime = elapsedTime; - this.endTime = endTime; - } -} diff --git a/service/src/java/org/apache/hive/service/server/HiveServer2.java b/service/src/java/org/apache/hive/service/server/HiveServer2.java index 958458f..c601614 100644 --- a/service/src/java/org/apache/hive/service/server/HiveServer2.java +++ b/service/src/java/org/apache/hive/service/server/HiveServer2.java @@ -64,6 +64,7 @@ import org.apache.hive.service.cli.thrift.ThriftBinaryCLIService; import org.apache.hive.service.cli.thrift.ThriftCLIService; import org.apache.hive.service.cli.thrift.ThriftHttpCLIService; +import org.apache.hive.service.servlet.QueryProfileServlet; import org.apache.logging.log4j.util.Strings; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; @@ -180,6 +181,7 @@ public void run() { builder.setUseSPNEGO(true); } webServer = builder.build(); + webServer.addServlet("query_page", "/query_page", QueryProfileServlet.class); } } } catch (IOException ie) { diff --git a/service/src/java/org/apache/hive/service/servlet/QueryProfileServlet.java b/service/src/java/org/apache/hive/service/servlet/QueryProfileServlet.java new file mode 100644 index 0000000..74a374d --- /dev/null +++ b/service/src/java/org/apache/hive/service/servlet/QueryProfileServlet.java @@ -0,0 +1,49 @@ +/** + * 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.hive.service.servlet; + +import org.apache.hive.service.cli.operation.OperationManager; +import org.apache.hive.service.cli.operation.SQLOperationDisplay; +import org.apache.hive.service.cli.session.SessionManager; +import org.apache.hive.tmpl.QueryProfileTmpl; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Renders a query page + */ +public class QueryProfileServlet extends HttpServlet { + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String opId = (String) request.getParameter("operationId"); + ServletContext ctx = getServletContext(); + SessionManager sessionManager = + (SessionManager)ctx.getAttribute("hive.sm"); + OperationManager opManager = sessionManager.getOperationManager(); + SQLOperationDisplay sod = opManager.getSQLOperationDisplay(opId); + + new QueryProfileTmpl().render(response.getWriter(), sod); + } + +} diff --git a/service/src/resources/hive-webapps/hiveserver2/hiveserver2.jsp b/service/src/resources/hive-webapps/hiveserver2/hiveserver2.jsp index a0b5d2e..8b46550 100644 --- a/service/src/resources/hive-webapps/hiveserver2/hiveserver2.jsp +++ b/service/src/resources/hive-webapps/hiveserver2/hiveserver2.jsp @@ -24,7 +24,7 @@ import="org.apache.hive.common.util.HiveVersionInfo" import="org.apache.hive.service.cli.operation.Operation" import="org.apache.hive.service.cli.operation.SQLOperation" - import="org.apache.hive.service.cli.operation.SQLOperationInfo" + import="org.apache.hive.service.cli.operation.SQLOperationDisplay" import="org.apache.hive.service.cli.session.SessionManager" import="org.apache.hive.service.cli.session.HiveSession" import="javax.servlet.ServletContext" @@ -132,29 +132,32 @@ for (HiveSession hiveSession: hiveSessions) { Query Execution Engine State + Begin Time Elapsed Time (s) + Drilldown Link -<% -int queries = 0; -Collection operations = sessionManager.getOperations(); -for (Operation operation: operations) { - if (operation instanceof SQLOperation) { - SQLOperation query = (SQLOperation) operation; - queries++; -%> + <% + int queries = 0; + Collection operations = sessionManager.getOperationManager().getLiveSqlOperations(); + for (SQLOperationDisplay operation : operations) { + queries++; + %> - <%= query.getParentSession().getUserName() %> - <%= query.getQueryStr() %> - <%= query.getConfigForOperation().getVar(ConfVars.HIVE_EXECUTION_ENGINE) %> - <%= query.getStatus().getState() %> - <%= (currentTime - query.getBeginTime())/1000 %> + <%= operation.getUserName() %> + <%= operation.getQueryDisplay() == null ? "Unknown" : operation.getQueryDisplay().getQueryString() %> + <%= operation.getExecutionEngine() %> + <%= operation.getState() %> + <%= new Date(operation.getBeginTime()) %> + <%= operation.getElapsedTime()/1000 %> + <% String link = "/query_page?operationId=" + operation.getOperationId(); %> + >Query Drilldown + <% } -} %> - Total number of queries: <%= queries %> + Total number of queries: <%= queries %> @@ -169,33 +172,36 @@ for (Operation operation: operations) { Execution Engine State Elapsed Time (s) - End Time + End Time + Drilldown Link -<% -queries = 0; -List sqlOperations = sessionManager.getOperationManager().getHistoricalSQLOpInfo(); -for (SQLOperationInfo sqlOperation: sqlOperations) { - queries++; -%> + <% + queries = 0; + operations = sessionManager.getOperationManager().getHistoricalSQLOperations(); + for (SQLOperationDisplay operation : operations) { + queries++; + %> - <%= sqlOperation.userName %> - <%= sqlOperation.queryStr %> - <%= sqlOperation.executionEngine %> - <%= sqlOperation.endState %> - <%= sqlOperation.elapsedTime %> - <%= new Date(sqlOperation.endTime) %> + <%= operation.getUserName() %> + <%= operation.getQueryDisplay() == null ? "Unknown" : operation.getQueryDisplay().getQueryString() %> + <%= operation.getExecutionEngine() %> + <%= operation.getState() %> + <%= operation.getElapsedTime()/1000 %> + <%= operation.getEndTime() == null ? "In Progress" : new Date(operation.getEndTime()) %> + <% String link = "/query_page?operationId=" + operation.getOperationId(); %> + >Query Drilldown -<% -} +<% + } %> - Total number of queries: <%= queries %> + Total number of queries: <%= queries %> -<% +<% } %> diff --git a/service/src/resources/hive-webapps/static/js/bootstrap.js b/service/src/resources/hive-webapps/static/js/bootstrap.js new file mode 100755 index 0000000..2c64257 --- /dev/null +++ b/service/src/resources/hive-webapps/static/js/bootstrap.js @@ -0,0 +1,1999 @@ +/** +* bootstrap.js v3.0.0 by @fat and @mdo +* Copyright 2013 Twitter Inc. +* http://www.apache.org/licenses/LICENSE-2.0 +*/ +if (!jQuery) { throw new Error("Bootstrap requires jQuery") } + +/* ======================================================================== + * Bootstrap: transition.js v3.0.0 + * http://twbs.github.com/bootstrap/javascript.html#transitions + * ======================================================================== + * Copyright 2013 Twitter, Inc. + * + * Licensed 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. + * ======================================================================== */ + + ++function ($) { "use strict"; + + // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) + // ============================================================ + + function transitionEnd() { + var el = document.createElement('bootstrap') + + var transEndEventNames = { + 'WebkitTransition' : 'webkitTransitionEnd' + , 'MozTransition' : 'transitionend' + , 'OTransition' : 'oTransitionEnd otransitionend' + , 'transition' : 'transitionend' + } + + for (var name in transEndEventNames) { + if (el.style[name] !== undefined) { + return { end: transEndEventNames[name] } + } + } + } + + // http://blog.alexmaccaw.com/css-transitions + $.fn.emulateTransitionEnd = function (duration) { + var called = false, $el = this + $(this).one($.support.transition.end, function () { called = true }) + var callback = function () { if (!called) $($el).trigger($.support.transition.end) } + setTimeout(callback, duration) + return this + } + + $(function () { + $.support.transition = transitionEnd() + }) + +}(window.jQuery); + +/* ======================================================================== + * Bootstrap: alert.js v3.0.0 + * http://twbs.github.com/bootstrap/javascript.html#alerts + * ======================================================================== + * Copyright 2013 Twitter, Inc. + * + * Licensed 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. + * ======================================================================== */ + + ++function ($) { "use strict"; + + // ALERT CLASS DEFINITION + // ====================== + + var dismiss = '[data-dismiss="alert"]' + var Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.prototype.close = function (e) { + var $this = $(this) + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = $(selector) + + if (e) e.preventDefault() + + if (!$parent.length) { + $parent = $this.hasClass('alert') ? $this : $this.parent() + } + + $parent.trigger(e = $.Event('close.bs.alert')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + $parent.trigger('closed.bs.alert').remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent + .one($.support.transition.end, removeElement) + .emulateTransitionEnd(150) : + removeElement() + } + + + // ALERT PLUGIN DEFINITION + // ======================= + + var old = $.fn.alert + + $.fn.alert = function (option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.alert') + + if (!data) $this.data('bs.alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + $.fn.alert.Constructor = Alert + + + // ALERT NO CONFLICT + // ================= + + $.fn.alert.noConflict = function () { + $.fn.alert = old + return this + } + + + // ALERT DATA-API + // ============== + + $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) + +}(window.jQuery); + +/* ======================================================================== + * Bootstrap: button.js v3.0.0 + * http://twbs.github.com/bootstrap/javascript.html#buttons + * ======================================================================== + * Copyright 2013 Twitter, Inc. + * + * Licensed 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. + * ======================================================================== */ + + ++function ($) { "use strict"; + + // BUTTON PUBLIC CLASS DEFINITION + // ============================== + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Button.DEFAULTS, options) + } + + Button.DEFAULTS = { + loadingText: 'loading...' + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + var $el = this.$element + var val = $el.is('input') ? 'val' : 'html' + var data = $el.data() + + state = state + 'Text' + + if (!data.resetText) $el.data('resetText', $el[val]()) + + $el[val](data[state] || this.options[state]) + + // push to event loop to allow forms to submit + setTimeout(function () { + state == 'loadingText' ? + $el.addClass(d).attr(d, d) : + $el.removeClass(d).removeAttr(d); + }, 0) + } + + Button.prototype.toggle = function () { + var $parent = this.$element.closest('[data-toggle="buttons"]') + + if ($parent.length) { + var $input = this.$element.find('input') + .prop('checked', !this.$element.hasClass('active')) + .trigger('change') + if ($input.prop('type') === 'radio') $parent.find('.active').removeClass('active') + } + + this.$element.toggleClass('active') + } + + + // BUTTON PLUGIN DEFINITION + // ======================== + + var old = $.fn.button + + $.fn.button = function (option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.button') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.button', (data = new Button(this, options))) + + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + $.fn.button.Constructor = Button + + + // BUTTON NO CONFLICT + // ================== + + $.fn.button.noConflict = function () { + $.fn.button = old + return this + } + + + // BUTTON DATA-API + // =============== + + $(document).on('click.bs.button.data-api', '[data-toggle^=button]', function (e) { + var $btn = $(e.target) + if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') + $btn.button('toggle') + e.preventDefault() + }) + +}(window.jQuery); + +/* ======================================================================== + * Bootstrap: carousel.js v3.0.0 + * http://twbs.github.com/bootstrap/javascript.html#carousel + * ======================================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed 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. + * ======================================================================== */ + + ++function ($) { "use strict"; + + // CAROUSEL CLASS DEFINITION + // ========================= + + var Carousel = function (element, options) { + this.$element = $(element) + this.$indicators = this.$element.find('.carousel-indicators') + this.options = options + this.paused = + this.sliding = + this.interval = + this.$active = + this.$items = null + + this.options.pause == 'hover' && this.$element + .on('mouseenter', $.proxy(this.pause, this)) + .on('mouseleave', $.proxy(this.cycle, this)) + } + + Carousel.DEFAULTS = { + interval: 5000 + , pause: 'hover' + , wrap: true + } + + Carousel.prototype.cycle = function (e) { + e || (this.paused = false) + + this.interval && clearInterval(this.interval) + + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + + return this + } + + Carousel.prototype.getActiveIndex = function () { + this.$active = this.$element.find('.item.active') + this.$items = this.$active.parent().children() + + return this.$items.index(this.$active) + } + + Carousel.prototype.to = function (pos) { + var that = this + var activeIndex = this.getActiveIndex() + + if (pos > (this.$items.length - 1) || pos < 0) return + + if (this.sliding) return this.$element.one('slid', function () { that.to(pos) }) + if (activeIndex == pos) return this.pause().cycle() + + return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos])) + } + + Carousel.prototype.pause = function (e) { + e || (this.paused = true) + + if (this.$element.find('.next, .prev').length && $.support.transition.end) { + this.$element.trigger($.support.transition.end) + this.cycle(true) + } + + this.interval = clearInterval(this.interval) + + return this + } + + Carousel.prototype.next = function () { + if (this.sliding) return + return this.slide('next') + } + + Carousel.prototype.prev = function () { + if (this.sliding) return + return this.slide('prev') + } + + Carousel.prototype.slide = function (type, next) { + var $active = this.$element.find('.item.active') + var $next = next || $active[type]() + var isCycling = this.interval + var direction = type == 'next' ? 'left' : 'right' + var fallback = type == 'next' ? 'first' : 'last' + var that = this + + if (!$next.length) { + if (!this.options.wrap) return + $next = this.$element.find('.item')[fallback]() + } + + this.sliding = true + + isCycling && this.pause() + + var e = $.Event('slide.bs.carousel', { relatedTarget: $next[0], direction: direction }) + + if ($next.hasClass('active')) return + + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + this.$element.one('slid', function () { + var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()]) + $nextIndicator && $nextIndicator.addClass('active') + }) + } + + if ($.support.transition && this.$element.hasClass('slide')) { + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + $active + .one($.support.transition.end, function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { that.$element.trigger('slid') }, 0) + }) + .emulateTransitionEnd(600) + } else { + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger('slid') + } + + isCycling && this.cycle() + + return this + } + + + // CAROUSEL PLUGIN DEFINITION + // ========================== + + var old = $.fn.carousel + + $.fn.carousel = function (option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.carousel') + var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) + var action = typeof option == 'string' ? option : options.slide + + if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.pause().cycle() + }) + } + + $.fn.carousel.Constructor = Carousel + + + // CAROUSEL NO CONFLICT + // ==================== + + $.fn.carousel.noConflict = function () { + $.fn.carousel = old + return this + } + + + // CAROUSEL DATA-API + // ================= + + $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) { + var $this = $(this), href + var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 + var options = $.extend({}, $target.data(), $this.data()) + var slideIndex = $this.attr('data-slide-to') + if (slideIndex) options.interval = false + + $target.carousel(options) + + if (slideIndex = $this.attr('data-slide-to')) { + $target.data('bs.carousel').to(slideIndex) + } + + e.preventDefault() + }) + + $(window).on('load', function () { + $('[data-ride="carousel"]').each(function () { + var $carousel = $(this) + $carousel.carousel($carousel.data()) + }) + }) + +}(window.jQuery); + +/* ======================================================================== + * Bootstrap: collapse.js v3.0.0 + * http://twbs.github.com/bootstrap/javascript.html#collapse + * ======================================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed 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. + * ======================================================================== */ + + ++function ($) { "use strict"; + + // COLLAPSE PUBLIC CLASS DEFINITION + // ================================ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Collapse.DEFAULTS, options) + this.transitioning = null + + if (this.options.parent) this.$parent = $(this.options.parent) + if (this.options.toggle) this.toggle() + } + + Collapse.DEFAULTS = { + toggle: true + } + + Collapse.prototype.dimension = function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + Collapse.prototype.show = function () { + if (this.transitioning || this.$element.hasClass('in')) return + + var startEvent = $.Event('show.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var actives = this.$parent && this.$parent.find('> .panel > .in') + + if (actives && actives.length) { + var hasData = actives.data('bs.collapse') + if (hasData && hasData.transitioning) return + actives.collapse('hide') + hasData || actives.data('bs.collapse', null) + } + + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + .addClass('collapsing') + [dimension](0) + + this.transitioning = 1 + + var complete = function () { + this.$element + .removeClass('collapsing') + .addClass('in') + [dimension]('auto') + this.transitioning = 0 + this.$element.trigger('shown.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + var scrollSize = $.camelCase(['scroll', dimension].join('-')) + + this.$element + .one($.support.transition.end, $.proxy(complete, this)) + .emulateTransitionEnd(350) + [dimension](this.$element[0][scrollSize]) + } + + Collapse.prototype.hide = function () { + if (this.transitioning || !this.$element.hasClass('in')) return + + var startEvent = $.Event('hide.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var dimension = this.dimension() + + this.$element + [dimension](this.$element[dimension]()) + [0].offsetHeight + + this.$element + .addClass('collapsing') + .removeClass('collapse') + .removeClass('in') + + this.transitioning = 1 + + var complete = function () { + this.transitioning = 0 + this.$element + .trigger('hidden.bs.collapse') + .removeClass('collapsing') + .addClass('collapse') + } + + if (!$.support.transition) return complete.call(this) + + this.$element + [dimension](0) + .one($.support.transition.end, $.proxy(complete, this)) + .emulateTransitionEnd(350) + } + + Collapse.prototype.toggle = function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + + // COLLAPSE PLUGIN DEFINITION + // ========================== + + var old = $.fn.collapse + + $.fn.collapse = function (option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.collapse') + var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.collapse.Constructor = Collapse + + + // COLLAPSE NO CONFLICT + // ==================== + + $.fn.collapse.noConflict = function () { + $.fn.collapse = old + return this + } + + + // COLLAPSE DATA-API + // ================= + + $(document).on('click.bs.collapse.data-api', '[data-toggle=collapse]', function (e) { + var $this = $(this), href + var target = $this.attr('data-target') + || e.preventDefault() + || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 + var $target = $(target) + var data = $target.data('bs.collapse') + var option = data ? 'toggle' : $this.data() + var parent = $this.attr('data-parent') + var $parent = parent && $(parent) + + if (!data || !data.transitioning) { + if ($parent) $parent.find('[data-toggle=collapse][data-parent="' + parent + '"]').not($this).addClass('collapsed') + $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed') + } + + $target.collapse(option) + }) + +}(window.jQuery); + +/* ======================================================================== + * Bootstrap: dropdown.js v3.0.0 + * http://twbs.github.com/bootstrap/javascript.html#dropdowns + * ======================================================================== + * Copyright 2012 Twitter, Inc. + * + * Licensed 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. + * ======================================================================== */ + + ++function ($) { "use strict"; + + // DROPDOWN CLASS DEFINITION + // ========================= + + var backdrop = '.dropdown-backdrop' + var toggle = '[data-toggle=dropdown]' + var Dropdown = function (element) { + var $el = $(element).on('click.bs.dropdown', this.toggle) + } + + Dropdown.prototype.toggle = function (e) { + var $this = $(this) + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { + // if mobile we we use a backdrop because click events don't delegate + $('