diff --git common/src/java/org/apache/hadoop/hive/common/LogUtils.java common/src/java/org/apache/hadoop/hive/common/LogUtils.java index 0a3e0c7201..d97cb53a62 100644 --- common/src/java/org/apache/hadoop/hive/common/LogUtils.java +++ common/src/java/org/apache/hadoop/hive/common/LogUtils.java @@ -29,6 +29,8 @@ import org.apache.hadoop.hive.conf.HiveConf.ConfVars; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.appender.FileAppender; +import org.apache.logging.log4j.core.appender.RollingFileAppender; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.routing.RoutingAppender; @@ -232,6 +234,19 @@ public static void unregisterLoggingContext() { MDC.clear(); } + public static String getLogFilePath() { + String logFilePath = null; + for (Appender appender : ((org.apache.logging.log4j.core.Logger) + LogManager.getRootLogger()).getAppenders().values()) { + if (appender instanceof FileAppender) { + logFilePath = ((FileAppender) appender).getFileName(); + } else if (appender instanceof RollingFileAppender) { + logFilePath = ((RollingFileAppender) appender).getFileName(); + } + } + return logFilePath; + } + /** * Stop the subordinate appender for the operation log so it will not leak a file descriptor. * @param routingAppenderName the name of the RoutingAppender diff --git common/src/java/org/apache/hadoop/hive/conf/HiveConf.java common/src/java/org/apache/hadoop/hive/conf/HiveConf.java index 056f2d7834..4dafe1760d 100644 --- common/src/java/org/apache/hadoop/hive/conf/HiveConf.java +++ common/src/java/org/apache/hadoop/hive/conf/HiveConf.java @@ -2363,6 +2363,16 @@ private static void populateLlapDaemonVarsSet(Set llapDaemonVarsSetLocal "the value of hive.server2.webui.host or the correct host name."), HIVE_SERVER2_WEBUI_MAX_HISTORIC_QUERIES("hive.server2.webui.max.historic.queries", 25, "The maximum number of past queries to show in HiverSever2 WebUI."), + HIVE_SERVER2_WEBUI_SHOW_GRAPH("hive.server2.webui.show.graph", false, + "Set this to true to to display query plan as a graph instead of text in the WebUI. " + + "Only works with hive.log.explain.output set to true."), + HIVE_SERVER2_WEBUI_SHOW_STATS("hive.server2.webui.show.stats", false, + "Set this to true to to display statistics for MapReduce tasks in the WebUI. " + + "Only works when hive.server2.webui.show.graph and hive.log.explain.output set to true."), + HIVE_SERVER2_WEBUI_MAX_GRAPH_SIZE("hive.server2.webui.max.graph.size", 25, + "Max number of stages graph can display. If number of stages exceeds this, no query" + + "plan will be shown. Only works when hive.server2.webui.show.graph and " + + "hive.log.explain.output set to true."), // Tez session settings HIVE_SERVER2_TEZ_DEFAULT_QUEUES("hive.server2.tez.default.queues", "", diff --git itests/hive-unit/src/test/java/org/apache/hive/service/cli/session/TestQueryDisplay.java itests/hive-unit/src/test/java/org/apache/hive/service/cli/session/TestQueryDisplay.java index 155c65dd26..da01a2947d 100644 --- itests/hive-unit/src/test/java/org/apache/hive/service/cli/session/TestQueryDisplay.java +++ itests/hive-unit/src/test/java/org/apache/hive/service/cli/session/TestQueryDisplay.java @@ -172,7 +172,8 @@ private void verifyDDLHtml(String stmt, String opHandle) throws Exception { StringWriter sw = new StringWriter(); QueryInfo queryInfo = sessionManager.getOperationManager().getQueryInfo( opHandle); - new QueryProfileTmpl().render(sw, queryInfo); + HiveConf hiveConf = sessionManager.getOperationManager().getHiveConf(); + new QueryProfileTmpl().render(sw, queryInfo, hiveConf); String html = sw.toString(); Assert.assertTrue(html.contains(stmt)); diff --git ql/src/java/org/apache/hadoop/hive/ql/Driver.java ql/src/java/org/apache/hadoop/hive/ql/Driver.java index 4e7c80f184..ed81d1ed05 100644 --- ql/src/java/org/apache/hadoop/hive/ql/Driver.java +++ ql/src/java/org/apache/hadoop/hive/ql/Driver.java @@ -124,6 +124,7 @@ import org.apache.hive.common.util.ShutdownHookManager; +import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -779,12 +780,22 @@ private String getExplainOutput(BaseSemanticAnalyzer sem, QueryPlan plan, PrintStream ps = new PrintStream(baos); try { List> rootTasks = sem.getAllRootTasks(); - task.getJSONPlan(ps, rootTasks, sem.getFetchTask(), false, true, true); - ret = baos.toString(); + if (conf.getBoolVar(ConfVars.HIVE_SERVER2_WEBUI_SHOW_GRAPH)) { + JSONObject jsonPlan = task.getJSONPlan( + null, rootTasks, sem.getFetchTask(), true, true, true); + if (jsonPlan.getJSONObject("STAGE DEPENDENCIES").length() < + conf.getIntVar(ConfVars.HIVE_SERVER2_WEBUI_MAX_GRAPH_SIZE)) { + ret = jsonPlan.toString(); + } else { + ret = null; + } + } else { + task.getJSONPlan(ps, rootTasks, sem.getFetchTask(), false, true, true); + ret = baos.toString(); + } } catch (Exception e) { LOG.warn("Exception generating explain output: " + e, e); } - return ret; } diff --git ql/src/java/org/apache/hadoop/hive/ql/MapRedStats.java ql/src/java/org/apache/hadoop/hive/ql/MapRedStats.java index 4b6051485e..fc336459f2 100644 --- ql/src/java/org/apache/hadoop/hive/ql/MapRedStats.java +++ ql/src/java/org/apache/hadoop/hive/ql/MapRedStats.java @@ -25,18 +25,19 @@ * MapRedStats. * * A data structure to keep one mapreduce's stats: - * number of mappers, number of reducers, accumulative CPU time and whether it - * succeeds. + * number of mappers, number of reducers, accumulative CPU time, whether it + * succeeds, and log file path. * */ public class MapRedStats { - int numMap; - int numReduce; - long cpuMSec; - Counters counters = null; - boolean success; + private int numMap; + private int numReduce; + private long cpuMSec; + private Counters counters = null; + private boolean success; - String jobId; + private String jobId; + private String logFilePath; public MapRedStats(int numMap, int numReduce, long cpuMSec, boolean ifSuccess, String jobId) { this.numMap = numMap; @@ -94,6 +95,14 @@ public void setJobId(String jobId) { this.jobId = jobId; } + public String getLogFilePath() { + return logFilePath; + } + + public void setLogFilePath(String logFilePath) { + this.logFilePath = logFilePath; + } + public String getTaskNumbers() { StringBuilder sb = new StringBuilder(); if (numMap > 0) { diff --git ql/src/java/org/apache/hadoop/hive/ql/QueryDisplay.java ql/src/java/org/apache/hadoop/hive/ql/QueryDisplay.java index 5bf22107dc..d89a8835aa 100644 --- ql/src/java/org/apache/hadoop/hive/ql/QueryDisplay.java +++ ql/src/java/org/apache/hadoop/hive/ql/QueryDisplay.java @@ -22,12 +22,16 @@ import org.apache.hadoop.hive.ql.exec.TaskResult; import org.apache.hadoop.hive.ql.plan.api.StageType; +import java.io.IOException; import java.io.Serializable; import java.util.*; +import org.apache.hadoop.mapred.Counters; +import org.apache.hadoop.mapred.RunningJob; import org.codehaus.jackson.annotate.JsonIgnoreProperties; import org.codehaus.jackson.annotate.JsonWriteNullProperties; import org.codehaus.jackson.annotate.JsonIgnore; +import org.json.JSONObject; /** * Some limited query information to save for WebUI. @@ -55,6 +59,13 @@ tasks.get(tTask.getId()).updateStatus(tTask); } + public synchronized void updateTaskStatistics(MapRedStats mapRedStats, + RunningJob rj, String taskId) throws IOException { + if (tasks.containsKey(taskId)) { + tasks.get(taskId).updateMapRedStatsJson(mapRedStats, rj); + } + } + //Inner classes public enum Phase { COMPILATION, @@ -80,6 +91,7 @@ private boolean requireLock; private boolean retryIfFail; private String statusMessage; + private JSONObject statsJSON; // required for jackson public TaskDisplay() { @@ -104,6 +116,58 @@ public synchronized String getStatus() { } } + private void updateMapRedStatsJson(MapRedStats stats, RunningJob rj) throws IOException { + if (statsJSON == null) { + statsJSON = new JSONObject(); + } + if (stats != null) { + if (stats.getLogFilePath() != null) { + statsJSON.put("Log file located at", stats.getLogFilePath()); + } + if (stats.getNumMap() >= 0 && stats.getNumReduce() >= 0) { + statsJSON.put("Number of Mappers", stats.getNumMap()); + statsJSON.put("Number of Reducers", stats.getNumReduce()); + } + if (stats.getCounters() != null) { + statsJSON.put("Counters", getCountersJson(stats.getCounters())); + } + } + if (rj != null) { + statsJSON.put("Job Id", rj.getID().toString()); + statsJSON.put("Job File", rj.getJobFile()); + statsJSON.put("Tracking URL", rj.getTrackingURL()); + statsJSON.put("Map Progress (%)", Math.round(rj.mapProgress() * 100)); + statsJSON.put("Reduce Progress (%)", Math.round(rj.reduceProgress() * 100)); + statsJSON.put("Cleanup Progress (%)", Math.round(rj.cleanupProgress() * 100)); + statsJSON.put("Setup Progress (%)", Math.round(rj.setupProgress() * 100)); + statsJSON.put("Complete", rj.isComplete()); + statsJSON.put("Successful", rj.isSuccessful()); + } + } + + public synchronized String getStatsJsonString() { + if (statsJSON != null) { + return statsJSON.toString(); + } + return null; + } + + private JSONObject getCountersJson(Counters ctrs) { + JSONObject countersJson = new JSONObject(); + Iterator iterator = ctrs.iterator(); + while(iterator.hasNext()) { + Counters.Group group = (Counters.Group)iterator.next(); + Iterator groupIterator = group.iterator(); + JSONObject groupJson = new JSONObject(); + while(groupIterator.hasNext()) { + Counters.Counter counter = (Counters.Counter)groupIterator.next(); + groupJson.put(counter.getDisplayName(), counter.getCounter()); + } + countersJson.put(group.getDisplayName(), groupJson); + } + return countersJson; + } + public synchronized Long getElapsedTime() { if (endTime == null) { if (beginTime == null) { @@ -207,7 +271,7 @@ public synchronized String getQueryString() { } public synchronized String getExplainPlan() { - return explainPlan == null ? "SET hive.log.explain.output TO true TO VIEW PLANS" : explainPlan; + return returnStringOrUnknown(explainPlan); } public synchronized void setExplainPlan(String explainPlan) { diff --git ql/src/java/org/apache/hadoop/hive/ql/exec/mr/HadoopJobExecHelper.java ql/src/java/org/apache/hadoop/hive/ql/exec/mr/HadoopJobExecHelper.java index 3c0719717c..6b7ef1bb92 100644 --- ql/src/java/org/apache/hadoop/hive/ql/exec/mr/HadoopJobExecHelper.java +++ ql/src/java/org/apache/hadoop/hive/ql/exec/mr/HadoopJobExecHelper.java @@ -30,6 +30,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; +import org.apache.hadoop.hive.common.LogUtils; import org.apache.hadoop.hive.conf.HiveConf; import org.apache.hadoop.hive.conf.HiveConf.ConfVars; import org.apache.hadoop.hive.ql.Context; @@ -55,11 +56,6 @@ import org.apache.hadoop.mapred.TaskCompletionEvent; import org.apache.hadoop.mapred.TaskReport; import org.apache.hive.common.util.ShutdownHookManager; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.core.Appender; -import org.apache.logging.log4j.core.Logger; -import org.apache.logging.log4j.core.appender.FileAppender; -import org.apache.logging.log4j.core.appender.RollingFileAppender; import org.slf4j.LoggerFactory; public class HadoopJobExecHelper { @@ -238,6 +234,11 @@ private MapRedStats progress(ExecDriverTaskHandle th) throws IOException, LockEx List clientStatPublishers = getClientStatPublishers(); final boolean localMode = ShimLoader.getHadoopShims().isLocalMode(job); + MapRedStats mapRedStats = new MapRedStats( + numMap, numReduce, cpuMsec, false, rj.getID().toString()); + mapRedStats.setLogFilePath(LogUtils.getLogFilePath()); + updateMapRedTaskWebUIStatistics(mapRedStats, rj); + while (!rj.isComplete()) { if (th.getContext() != null) { th.getContext().checkHeartbeaterLockException(); @@ -313,6 +314,11 @@ private MapRedStats progress(ExecDriverTaskHandle th) throws IOException, LockEx Counters ctrs = th.getCounters(); + mapRedStats.setCounters(ctrs); + mapRedStats.setNumMap(numMap); + mapRedStats.setNumReduce(numReduce); + updateMapRedTaskWebUIStatistics(mapRedStats, rj); + if (fatal = checkFatalErrors(ctrs, errMsg)) { console.printError("[Fatal Error] " + errMsg.toString() + ". Killing the job."); rj.killJob(); @@ -418,8 +424,10 @@ private MapRedStats progress(ExecDriverTaskHandle th) throws IOException, LockEx } } - MapRedStats mapRedStats = new MapRedStats(numMap, numReduce, cpuMsec, success, rj.getID().toString()); + mapRedStats.setSuccess(success); mapRedStats.setCounters(ctrs); + mapRedStats.setCpuMSec(cpuMsec); + updateMapRedTaskWebUIStatistics(mapRedStats, rj); // update based on the final value of the counters updateCounters(ctrs, rj); @@ -432,6 +440,12 @@ private MapRedStats progress(ExecDriverTaskHandle th) throws IOException, LockEx return mapRedStats; } + private void updateMapRedTaskWebUIStatistics(MapRedStats mapRedStats, RunningJob rj) { + if (task instanceof MapRedTask) { + ((MapRedTask) task).updateWebUiStats(mapRedStats, rj); + } + } + private String getId() { return this.task.getId(); @@ -501,13 +515,7 @@ public void localJobDebugger(int exitVal, String taskId) { sb.append("Logs:\n"); console.printError(sb.toString()); - for (Appender appender : ((Logger) LogManager.getRootLogger()).getAppenders().values()) { - if (appender instanceof FileAppender) { - console.printError(((FileAppender) appender).getFileName()); - } else if (appender instanceof RollingFileAppender) { - console.printError(((RollingFileAppender) appender).getFileName()); - } - } + console.printError(LogUtils.getLogFilePath()); } public int progressLocal(Process runningJob, String taskId) { diff --git ql/src/java/org/apache/hadoop/hive/ql/exec/mr/MapRedTask.java ql/src/java/org/apache/hadoop/hive/ql/exec/mr/MapRedTask.java index 1bd4db7805..ca5cca2a71 100644 --- ql/src/java/org/apache/hadoop/hive/ql/exec/mr/MapRedTask.java +++ ql/src/java/org/apache/hadoop/hive/ql/exec/mr/MapRedTask.java @@ -39,6 +39,7 @@ import org.apache.hadoop.hive.conf.HiveConf.ConfVars; import org.apache.hadoop.hive.ql.Context; import org.apache.hadoop.hive.ql.DriverContext; +import org.apache.hadoop.hive.ql.MapRedStats; import org.apache.hadoop.hive.ql.exec.Operator; import org.apache.hadoop.hive.ql.exec.SerializationUtilities; import org.apache.hadoop.hive.ql.exec.Utilities; @@ -51,6 +52,7 @@ import org.apache.hadoop.hive.shims.ShimLoader; import org.apache.hive.common.util.HiveStringUtils; import org.apache.hive.common.util.StreamPrinter; +import org.apache.hadoop.mapred.RunningJob; /** * Extension of ExecDriver: @@ -498,6 +500,19 @@ public static String isEligibleForLocalMode(HiveConf conf, return null; } + public void updateWebUiStats(MapRedStats mapRedStats, RunningJob rj) { + if (queryDisplay != null && + conf.getBoolVar(HiveConf.ConfVars.HIVE_SERVER2_WEBUI_SHOW_STATS) && + conf.getBoolVar(HiveConf.ConfVars.HIVE_LOG_EXPLAIN_OUTPUT) && + conf.getBoolVar(HiveConf.ConfVars.HIVE_SERVER2_WEBUI_SHOW_GRAPH)) { + try { + queryDisplay.updateTaskStatistics(mapRedStats, rj, getId()); + } catch (IOException e) { + LOG.error(org.apache.hadoop.util.StringUtils.stringifyException(e)); + } + } + } + @Override public void shutdown() { super.shutdown(); diff --git service/src/jamon/org/apache/hive/tmpl/QueryProfileTmpl.jamon service/src/jamon/org/apache/hive/tmpl/QueryProfileTmpl.jamon index 5e2d68c4a4..a4333d3bb9 100644 --- service/src/jamon/org/apache/hive/tmpl/QueryProfileTmpl.jamon +++ service/src/jamon/org/apache/hive/tmpl/QueryProfileTmpl.jamon @@ -18,12 +18,18 @@ limitations under the License. <%args> QueryInfo queryInfo; +HiveConf hiveConf; <%import> java.util.*; org.apache.hadoop.hive.ql.QueryDisplay; org.apache.hadoop.hive.ql.QueryInfo; +org.apache.hadoop.hive.conf.HiveConf; +<%class> +private boolean showGraph = false; +private boolean showStats = false; + @@ -38,6 +44,22 @@ org.apache.hadoop.hive.ql.QueryInfo; + + + + + <%if hiveConf.getBoolVar(HiveConf.ConfVars.HIVE_SERVER2_WEBUI_SHOW_GRAPH) && + hiveConf.getBoolVar(HiveConf.ConfVars.HIVE_LOG_EXPLAIN_OUTPUT) %> + <%java showGraph = true; %> + <%if hiveConf.getBoolVar(HiveConf.ConfVars.HIVE_SERVER2_WEBUI_SHOW_STATS) %> + <%java showStats = true; %> + + + <%if showGraph %> + + + + @@ -204,13 +226,55 @@ org.apache.hadoop.hive.ql.QueryInfo;
Explain plan
-
-        <% queryInfo.getQueryDisplay() == null ? "Unknown" : queryInfo.getQueryDisplay().getExplainPlan() %>
-        
+ <%if showGraph %> + <%if queryInfo.getQueryDisplay() != null && queryInfo.getQueryDisplay().getExplainPlan() != null %> +
+ <%if showStats %> +
+
+

+                
+
+
+
+
+ <%else> +

+            
+            
+          <%else>
+            
Query information not available
+ + <%else> +
+            <% (queryInfo.getQueryDisplay() == null || queryInfo.getQueryDisplay().getExplainPlan() == null) ? "Unknown" : queryInfo.getQueryDisplay().getExplainPlan() %>
+          
+
- +<%if showGraph %> + + <%def perfLogging> <%args> @@ -295,8 +359,5 @@ org.apache.hadoop.hive.ql.QueryInfo; - - - diff --git service/src/java/org/apache/hive/service/servlet/QueryProfileServlet.java service/src/java/org/apache/hive/service/servlet/QueryProfileServlet.java index 27a3b1423a..fb0edeca16 100644 --- service/src/java/org/apache/hive/service/servlet/QueryProfileServlet.java +++ service/src/java/org/apache/hive/service/servlet/QueryProfileServlet.java @@ -17,6 +17,7 @@ */ package org.apache.hive.service.servlet; +import org.apache.hadoop.hive.conf.HiveConf; import org.apache.hadoop.hive.ql.QueryInfo; import org.apache.hive.service.cli.operation.OperationManager; import org.apache.hive.service.cli.session.SessionManager; @@ -31,7 +32,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; - /** * Renders a query page */ @@ -47,11 +47,12 @@ public void doGet(HttpServletRequest request, HttpServletResponse response) (SessionManager)ctx.getAttribute("hive.sm"); OperationManager opManager = sessionManager.getOperationManager(); QueryInfo queryInfo = opManager.getQueryInfo(opId); + HiveConf hiveConf = opManager.getHiveConf(); if (queryInfo == null) { LOG.debug("No display object found for operation {} ", opId); return; } - new QueryProfileTmpl().render(response.getWriter(), queryInfo); + new QueryProfileTmpl().render(response.getWriter(), queryInfo, hiveConf); } } diff --git service/src/resources/hive-webapps/static/css/query-plan-graph.css service/src/resources/hive-webapps/static/css/query-plan-graph.css new file mode 100644 index 0000000000..c36826ec5f --- /dev/null +++ service/src/resources/hive-webapps/static/css/query-plan-graph.css @@ -0,0 +1,5 @@ +#plan-visualization { + width: 100%; + min-height: 300px; + border: 1px solid grey; + } \ No newline at end of file diff --git service/src/resources/hive-webapps/static/js/query-plan-graph.js service/src/resources/hive-webapps/static/js/query-plan-graph.js new file mode 100644 index 0000000000..6b41a66b51 --- /dev/null +++ service/src/resources/hive-webapps/static/js/query-plan-graph.js @@ -0,0 +1,510 @@ +/** +* query-plan-graph.js +* +* Display a visualization of query plan +*/ + +const DEFAULT_TEXT_INDENT = 2; + +const SUCCESS = "Success"; +const RUNNING = "Running"; +const FAILED = "Failure"; + +const STATISTICS = "statistics"; +const MAP_PROGRESS = "Map Progress (%)"; +const REDUCE_PROGRESS = "Reduce Progress (%)"; +const NUMBER_MAPPERS = "Number of Mappers"; +const NUMBER_REDUCERS = "Number of Reducers"; +const PROGRESS_ELEMENT = "progress-bar-element"; +const LONGEST_PATH_FROM_ROOT = "longestPathFromRoot"; +const NODE_ID = "nodeId"; + +const BLUE = { + border:'#2B7CE9', + background : '#D2E5FF', + highlight : { + border : '#2B7CE9', + background : '#D2E5FF' + } +} +const GREEN = { + border : '#009900', + background : '#99E699', + highlight : { + border : '#009900', + background : '#99E699' + } +} +const PINK = 'pink'; +const LIGHT_GRAY = 'rgba(200,200,200,0.5)'; + + +/** +* Displays query plan as a graph as well as information about a selected stage. +*/ +function visualizeJsonPlan(displayGraphElement, displayStagePlanElement, displayStatisticsElement, + displayStatisticsElementHead, displayStatisticsElementBody, jsonPlan, jsonStats, + displayInformationTextIndent = DEFAULT_TEXT_INDENT) { + + setUpInstructions(displayStagePlanElement, displayStatisticsElementHead); + networkData = getNodesAndEdges(jsonPlan["STAGE DEPENDENCIES"]); + network = createNetwork(networkData.nodes, networkData.edges, displayGraphElement); + network.on('click', function (params) { + displayStagePlan(params, displayStagePlanElement, jsonPlan, displayInformationTextIndent); + if (document.getElementById(displayStatisticsElement) != null) { + displayStatistics(params, displayStatisticsElement, displayStatisticsElementHead, + displayStatisticsElementBody, jsonStats, displayInformationTextIndent); + } + }); + colorTasks(networkData.edges, networkData.nodes, jsonStats) + return network; +} + + +function setUpInstructions(displayStagePlanElement, displayStatisticsElementHead) { + document.getElementById(displayStagePlanElement).innerHTML = + "Click on a stage to view its plan"; + if (document.getElementById(displayStatisticsElementHead) != null) { + document.getElementById(displayStatisticsElementHead).innerHTML = + "Click on a colored-in stage to view stats"; + } +} + +function colorTasks(edges, nodes, jsonStats) { + grayFilteredOutTasks(edges, nodes, jsonStats); + showSuccessfulTasks(edges, nodes, jsonStats); + showRunningTasks(nodes, jsonStats); + showFailedTasks(nodes, jsonStats); + } + +/** +* Set color of node or edge +*/ +function setColor(dataSet, itemToChange, newColor) { + itemToChange.color = newColor; + dataSet.update(itemToChange); +} + + +/** +* Colors all edges connected to the node. +*/ +function setAllNodeEdgesColor(edgesDataSet, nodeId, newColor) { + var theseEdges = edgesDataSet.get({ + filter: function(item) { + return (item.to == nodeId || item.from == nodeId); + }}); + for (var i = theseEdges.length - 1; i >= 0; i--) { + setColor(edgesDataSet, theseEdges[i], newColor); + } +} + + +/** +* If two nodes are the same color, change the color of their connecting edge to match +*/ +function matchAllEdgeColors(nodesDataSet, edgesDataSet) { + for (node in nodesDataSet.getIds()) { + nodeId = nodesDataSet.getIds()[node]; + var toEdges = edgesDataSet.get({ + filter: function (item) { + return item.to == nodeId; + }}); + for (var i = toEdges.length - 1; i >= 0; i--) { + if (nodesDataSet.get(nodeId).color == nodesDataSet.get(parseInt(toEdges[i].from)).color) { + setColor(edgesDataSet, toEdges[i], nodesDataSet.get(nodeId).color.border); + } + } + } +} + + +/** +* If a task is not filtered out by conditional statements, it will be in jsonStats. +* Set all other tasks' node & edge colors to light gray +*/ +function grayFilteredOutTasks(edgesDataSet, nodesDataSet, jsonStats) { + + for (node in nodesDataSet.getIds()) { + nodeId = nodesDataSet.getIds()[node]; + var taskId = "Stage-" + nodeId; + var otherTaskId = nodesDataSet.get(nodeId).id; + if (!Object.keys(jsonStats).includes(taskId)) { + setColor(nodesDataSet, nodesDataSet.get(nodeId), LIGHT_GRAY); + setAllNodeEdgesColor(edgesDataSet, nodeId, LIGHT_GRAY); + } + } +} + + +/** +* If a task is successful, color it blue! +*/ +function showSuccessfulTasks(edgesDataSet, nodesDataSet, jsonStats) { + changeNodeColorByStatus(nodesDataSet, jsonStats, SUCCESS, GREEN) + matchAllEdgeColors(nodesDataSet, edgesDataSet); +} + +function showRunningTasks(nodesDataSet, jsonStats) { + changeNodeColorByStatus(nodesDataSet, jsonStats, RUNNING, BLUE) +} + +function showFailedTasks(nodesDataSet, jsonStats) { + changeNodeColorByStatus(nodesDataSet, jsonStats, FAILED, PINK) +} + +function changeNodeColorByStatus(nodesDataSet, jsonStats, statusString, color) { + for (node in nodesDataSet.getIds()) { + nodeId = nodesDataSet.getIds()[node]; + var taskId = "Stage-" + nodeId; + if (Object.keys(jsonStats).includes(taskId)) { + if (jsonStats[taskId]['status'].search(statusString) != -1) { + setColor(nodesDataSet, nodesDataSet.get(nodeId), color); + } + } + } +} + + +/** +* Removes all non-number characters from string, casts to integer +* @param {Object} stageDependencies - json object containing node and edge information +* returns {nodes, edges} - javascript arrayObjects containing node, edge info readable by vis.js +*/ +function getNodesAndEdges(stageDependencies) { + var nodes = []; + var edges = []; + var paths = []; + var childNodes = []; + var nodeIndex = 0; + var edgeIndex = 0; + var newNode = {}; + var newEdge = {}; + var nodeId = 0; + var newLabel = ""; + for (stage in stageDependencies) { + newNode = {}; + nodeId = getStageNumber(stage); + newNode['id'] = nodeId; + newLabel = nodeId + " - " + stageDependencies[stage]["TASK TYPE"]; + if (stageDependencies[stage]["ROOT STAGE"] == "TRUE") { + newLabel += " - ROOT"; + //create new path + paths.push([nodeId]); + } + else { + childNodes.push(nodeId); + } + newNode['label'] = newLabel; + if (stageDependencies[stage]["CONDITIONAL CHILD TASKS"] != null) { + newNode['shape'] = 'text'; + } + nodes[nodeIndex] = newNode; + edgeIndex = linkStages("DEPENDENT STAGES", stageDependencies, stage, nodeId, edgeIndex, edges); + edgeIndex = linkStages("CONDITIONAL CHILD TASKS", stageDependencies, stage, nodeId, edgeIndex, + edges, true); + + nodeIndex++; + } + + for (var i = paths.length - 1; i >= 0; i--) { + for (ndx in childNodes) { + paths[i].push(childNodes[ndx]); + } + } + assignNodeLevel(paths, nodes, edges); + + var nodesDataSet = new vis.DataSet(nodes); + var edgesDataSet = new vis.DataSet(edges); + return { + nodes: nodesDataSet, + edges: edgesDataSet + } +} + + +/** +* Make each node's level in hierarchy longest path from any root +*/ +function assignNodeLevel(paths, nodes, edges) { + + orderPathsByEdgeDirection(paths, edges); + for (pathNdx in paths) { + prepareMap(paths[pathNdx]); + assignLongestPathFromRoot(paths[pathNdx], edges) + } + assignLevelByLongestPathFromAllRoots(paths, nodes); +} + + +function orderPathsByEdgeDirection(paths, edges) { + for (pathNdx in paths) { + var currentPath = paths[pathNdx]; + var orderedPath = [] + var listOfEdges = edges.slice(0); + while (currentPath.length != 0) { + for (nodeNdx in currentPath) { + node = currentPath[nodeNdx]; + var edgesIn = 0; + for (edgeNdx in listOfEdges) { + if (listOfEdges[edgeNdx].to == node) { + edgesIn++; + break; + } + } + if (edgesIn == 0) { + orderedPath.push(node); + currentPath.splice(nodeNdx, 1); + for (var edgeNdx = 0; edgeNdx < listOfEdges.length; edgeNdx++) { + if (listOfEdges[edgeNdx].from == node || listOfEdges[edgeNdx].to == node) { + listOfEdges.splice(edgeNdx, 1); + edgeNdx--; + } + } + } + } + } + } + paths.splice(currentPath); + paths.push(orderedPath); +} + + +function prepareMap(currentPath) { + for (currentPathNdx in currentPath) { + nodeId = currentPath[currentPathNdx]; + currentPath[currentPathNdx] = {}; + currentPath[currentPathNdx][NODE_ID] = nodeId; + if (currentPathNdx == 0) { + currentPath[currentPathNdx][LONGEST_PATH_FROM_ROOT] = 0; + } + else { + currentPath[currentPathNdx][LONGEST_PATH_FROM_ROOT] = -Infinity; + } + } +} + + +function assignLongestPathFromRoot(currentPath, edges) { + for (var nodeIndex = 1; nodeIndex <= currentPath.length - 1; nodeIndex++) { //skips index 0, root + node = currentPath[nodeIndex]; + nodeId = node[NODE_ID]; + for (var prevNodeIndex = 0; prevNodeIndex < nodeIndex; prevNodeIndex++) { + var prevNode = currentPath[prevNodeIndex]; + for (edgeNdx in edges) { + if (edges[edgeNdx].to == nodeId && edges[edgeNdx].from == prevNode.nodeId && + currentPath[nodeIndex][LONGEST_PATH_FROM_ROOT] <= prevNode[LONGEST_PATH_FROM_ROOT]) { + currentPath[nodeIndex][LONGEST_PATH_FROM_ROOT] = prevNode[LONGEST_PATH_FROM_ROOT] + 1; + } + } + } + } +} + + +function assignLevelByLongestPathFromAllRoots(paths, nodes) { + for (nodeNdx in nodes) { + nodeId = nodes[nodeNdx].id; + var longestPath = 0; + for (pathNdx in paths) { + currentPath = paths[pathNdx]; + for (pathNodeNdx in currentPath) { + if (currentPath[pathNodeNdx][NODE_ID] == nodeId && + currentPath[pathNodeNdx][LONGEST_PATH_FROM_ROOT] > longestPath) { + longestPath = currentPath[pathNodeNdx][LONGEST_PATH_FROM_ROOT]; + } + } + } + nodes[nodeNdx]['level'] = longestPath; + } +} + +/** +* Creates a vis.js hierarchical network +* @param {nodes} - vis DataSet, node info +* @param {edges} - vis DataSet, edge info +* @param {documentElement} - where to place the network +*/ +function createNetwork(nodes, edges, documentElement) { + var data = { + nodes: nodes, + edges: edges + }; + var container = document.getElementById(documentElement); + var options = { + layout: { + hierarchical: { + direction: 'LR', + sortMethod: 'directed', + levelSeparation: 150, + parentCentralization: true + } + }, + edges: { + smooth: true, + arrows: {to : true } + }, + physics: { + hierarchicalRepulsion: { + nodeDistance: 150 + } + } + }; + var network = new vis.Network(container, data, options); + return network; +} + + +/** +* Removes all non-number characters from string, casts to integer +* @param {string} - string to mine the number from +* returns {int} - the integer needed from the string +*/ +function getStageNumber(string) { + return parseInt(string.replace( /^\D+/g, '')); +} + +function prettifyJsonString(jsonString) { + return jsonString.replace(/{/g, "").replace(/}/g, "").replace(/,/g, ""); +} + + +function addDashedEdge(parent, child, edgeIndex, edges) { + addEdge(parent, child, edgeIndex, edges); + edges[edgeIndex]['dashes'] = 'true'; +} + + +function addEdge(parent, child, edgeIndex, edges) { + newEdge = {}; + newEdge['from'] = parent; + newEdge['to'] = child; + newEdge['color'] = 'gray'; + edges[edgeIndex] = newEdge; +} + + +/** +* Add edge information +* returns {int} edgeIndex - where we are in the list of edges +*/ +function linkStages(linkType, data, stage, nodeId, edgeIndex, edges, dashes=false) { + if (data[stage][linkType] != null) { + linkedStages = data[stage][linkType].split(","); + for (index in linkedStages) { + if (dashes == true) { + addDashedEdge(nodeId, getStageNumber(linkedStages[index]), edgeIndex, edges); + } + else { + addEdge(getStageNumber(linkedStages[index]), nodeId, edgeIndex, edges); + } + edgeIndex ++ + } + } + return edgeIndex; +} + + +function displayStagePlan(params, displayStagePlanElement, jsonPlan, textIndent) { + nodeId = params.nodes; + stageName = "Stage-" + nodeId; + if (nodeId != "") { + document.getElementById(displayStagePlanElement).innerHTML = + 'Stage ' + nodeId + " plan:\n" + + prettifyJsonString(JSON.stringify(jsonPlan['STAGE PLANS'][stageName], null, textIndent)); + } + //show nothing if no node is selected + else { + document.getElementById(displayStagePlanElement).innerHTML = + "Click on a stage to view its plan"; + } +} + + +function displayStatistics(params, displayStatisticsElement, displayStatisticsElementHead, + displayStatisticsElementBody, jsonStats, textIndent) { + if (document.getElementById(PROGRESS_ELEMENT)) { + document.getElementById(PROGRESS_ELEMENT).remove(); + } + nodeId = params.nodes; + stageName = "Stage-" + nodeId; + if (nodeId != "" && jsonStats[stageName] != null) { + if (jsonStats[stageName][STATISTICS] != null && + jsonStats[stageName][STATISTICS] != '') { + + document.getElementById(displayStatisticsElementHead).innerHTML = + "Stage " + nodeId + " statistics:\n" + + "Status: " + jsonStats[stageName]['status'] + "\n\n" + + "MapReduce job progress:\n"; + document.getElementById(displayStatisticsElementBody).innerHTML = + prettifyJsonString(JSON.stringify(jsonStats[stageName][STATISTICS], null, textIndent)); + + var numMappers = jsonStats[stageName][STATISTICS][NUMBER_MAPPERS]; + if (numMappers != null && numMappers > 0) { + var mapProgress = jsonStats[stageName][STATISTICS][MAP_PROGRESS]; + } + var numReducers = jsonStats[stageName][STATISTICS][NUMBER_REDUCERS]; + if (numReducers != null && numReducers > 0) { + var reduceProgress = jsonStats[stageName][STATISTICS][REDUCE_PROGRESS]; + } + var progressBar = getMainProgressBar(mapProgress, reduceProgress, PROGRESS_ELEMENT); + if (progressBar != null) { + document.getElementById(displayStatisticsElement).insertBefore( + progressBar, document.getElementById(displayStatisticsElementBody)); + } + } + else { // stage without statistics + document.getElementById(displayStatisticsElementHead).innerHTML = + "Stage " + nodeId + " statistics:\n" + + "Status: " + jsonStats[stageName]['status']; + document.getElementById(displayStatisticsElementBody).innerHTML = ''; + } + } + //show nothing if no node is selected or selected node isn't executed + else { + document.getElementById(displayStatisticsElementHead).innerHTML = + "Click on a colored-in stage to view stats"; + document.getElementById(displayStatisticsElementBody).innerHTML = ''; + } +} + + +function getMainProgressBar(mapProgress, reduceProgress, elementId) { + var progressBar = document.createElement("div"); + progressBar.className = "progress"; + progressBar.id = elementId; + var numChildProgressBars = 2; + if (mapProgress == null || reduceProgress == null) { + numChildProgressBars = 1; + } + if (mapProgress != null) { + progressBar.appendChild(getSubProgressBar("map", mapProgress, progressBar, + numChildProgressBars)); + } + if (reduceProgress != null) { + progressBar.appendChild(getSubProgressBar("reduce", reduceProgress, progressBar, + numChildProgressBars, 'green')); + } + if (mapProgress == null && reduceProgress == null) { + progressBar = null; + } + return progressBar; +} + + +function getSubProgressBar(type, progress, mainProgressBar, numChildProgressBars, color) { + var new_progress_bar = document.createElement("div"); + new_progress_bar.id = "progress-" + type; + var className = "progress-bar"; + if (color == "green") { + className += " progress-bar-success" + } + new_progress_bar.className = className; + new_progress_bar.role = "progressbar"; + new_progress_bar['aria-valuenow']= "0"; + new_progress_bar['aria-valuemin']= progress / numChildProgressBars; + new_progress_bar['aria-valuemax']="100"; + new_progress_bar.style="width: " + progress / numChildProgressBars + "%; min-width: 2em;"; + new_progress_bar.innerHTML = progress + "% " + type; + return new_progress_bar; +} \ No newline at end of file diff --git service/src/resources/hive-webapps/static/js/vis.min.js service/src/resources/hive-webapps/static/js/vis.min.js new file mode 100644 index 0000000000..7c48489df7 --- /dev/null +++ service/src/resources/hive-webapps/static/js/vis.min.js @@ -0,0 +1,46 @@ +/** + * vis.js + * https://github.com/almende/vis + * + * A dynamic, browser-based visualization library. + * + * @version 4.20.0 + * @date 2017-05-21 + * + * @license + * Copyright (C) 2011-2017 Almende B.V, http://almende.com + * + * Vis.js is dual licensed under both + * + * * The Apache 2.0 License + * http://www.apache.org/licenses/LICENSE-2.0 + * + * and + * + * * The MIT License + * http://opensource.org/licenses/MIT + * + * Vis.js may be distributed under either license. + */ +"use strict";!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.vis=e():t.vis=e()}(this,function(){return function(t){function e(o){if(i[o])return i[o].exports;var n=i[o]={exports:{},id:o,loaded:!1};return t[o].call(n.exports,n,n.exports,e),n.loaded=!0,n.exports}var i={};return e.m=t,e.c=i,e.p="",e(0)}([function(t,e,i){var o=i(1);o.extend(e,i(87)),o.extend(e,i(116)),o.extend(e,i(158))},function(t,e,i){function o(t){return t&&t.__esModule?t:{default:t}}var n=i(2),s=o(n),r=i(55),a=o(r),h=i(58),d=o(h),l=i(62),u=o(l),c=i(82),p=i(86);e.isNumber=function(t){return t instanceof Number||"number"==typeof t},e.recursiveDOMDelete=function(t){if(t)for(;!0===t.hasChildNodes();)e.recursiveDOMDelete(t.firstChild),t.removeChild(t.firstChild)},e.giveRange=function(t,e,i,o){if(e==t)return.5;var n=1/(e-t);return Math.max(0,(o-t)*n)},e.isString=function(t){return t instanceof String||"string"==typeof t},e.isDate=function(t){if(t instanceof Date)return!0;if(e.isString(t)){if(f.exec(t))return!0;if(!isNaN(Date.parse(t)))return!0}return!1},e.randomUUID=function(){return p.v4()},e.assignAllKeys=function(t,e){for(var i in t)t.hasOwnProperty(i)&&"object"!==(0,u.default)(t[i])&&(t[i]=e)},e.fillIfDefined=function(t,i){var o=arguments.length>2&&void 0!==arguments[2]&&arguments[2];for(var n in t)void 0!==i[n]&&("object"!==(0,u.default)(i[n])?void 0!==i[n]&&null!==i[n]||void 0===t[n]||!0!==o?t[n]=i[n]:delete t[n]:"object"===(0,u.default)(t[n])&&e.fillIfDefined(t[n],i[n],o))},e.protoExtend=function(t,e){for(var i=1;i3&&void 0!==arguments[3]&&arguments[3];if(Array.isArray(o))throw new TypeError("Arrays are not supported by deepExtend");for(var s=2;s3&&void 0!==arguments[3]&&arguments[3];if(Array.isArray(o))throw new TypeError("Arrays are not supported by deepExtend");for(var s in o)if(o.hasOwnProperty(s)&&-1==t.indexOf(s))if(o[s]&&o[s].constructor===Object)void 0===i[s]&&(i[s]={}),i[s].constructor===Object?e.deepExtend(i[s],o[s]):null===o[s]&&void 0!==i[s]&&!0===n?delete i[s]:i[s]=o[s];else if(Array.isArray(o[s])){i[s]=[];for(var r=0;r=0&&(e="DOMMouseScroll"),t.addEventListener(e,i,o)):t.attachEvent("on"+e,i)},e.removeEventListener=function(t,e,i,o){t.removeEventListener?(void 0===o&&(o=!1),"mousewheel"===e&&navigator.userAgent.indexOf("Firefox")>=0&&(e="DOMMouseScroll"),t.removeEventListener(e,i,o)):t.detachEvent("on"+e,i)},e.preventDefault=function(t){t||(t=window.event),t.preventDefault?t.preventDefault():t.returnValue=!1},e.getTarget=function(t){t||(t=window.event);var e;return t.target?e=t.target:t.srcElement&&(e=t.srcElement),void 0!=e.nodeType&&3==e.nodeType&&(e=e.parentNode),e},e.hasParent=function(t,e){for(var i=t;i;){if(i===e)return!0;i=i.parentNode}return!1},e.option={},e.option.asBoolean=function(t,e){return"function"==typeof t&&(t=t()),null!=t?0!=t:e||null},e.option.asNumber=function(t,e){return"function"==typeof t&&(t=t()),null!=t?Number(t)||e||null:e||null},e.option.asString=function(t,e){return"function"==typeof t&&(t=t()),null!=t?String(t):e||null},e.option.asSize=function(t,i){return"function"==typeof t&&(t=t()),e.isString(t)?t:e.isNumber(t)?t+"px":i||null},e.option.asElement=function(t,e){return"function"==typeof t&&(t=t()),t||e||null},e.hexToRGB=function(t){var e=/^#?([a-f\d])([a-f\d])([a-f\d])$/i;t=t.replace(e,function(t,e,i,o){return e+e+i+i+o+o});var i=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(t);return i?{r:parseInt(i[1],16),g:parseInt(i[2],16),b:parseInt(i[3],16)}:null},e.overrideOpacity=function(t,i){if(-1!=t.indexOf("rgba"))return t;if(-1!=t.indexOf("rgb")){var o=t.substr(t.indexOf("(")+1).replace(")","").split(",");return"rgba("+o[0]+","+o[1]+","+o[2]+","+i+")"}var o=e.hexToRGB(t);return null==o?t:"rgba("+o.r+","+o.g+","+o.b+","+i+")"},e.RGBToHex=function(t,e,i){return"#"+((1<<24)+(t<<16)+(e<<8)+i).toString(16).slice(1)},e.parseColor=function(t){var i;if(!0===e.isString(t)){if(!0===e.isValidRGB(t)){var o=t.substr(4).substr(0,t.length-5).split(",").map(function(t){return parseInt(t)});t=e.RGBToHex(o[0],o[1],o[2])}if(!0===e.isValidHex(t)){var n=e.hexToHSV(t),s={h:n.h,s:.8*n.s,v:Math.min(1,1.02*n.v)},r={h:n.h,s:Math.min(1,1.25*n.s),v:.8*n.v},a=e.HSVToHex(r.h,r.s,r.v),h=e.HSVToHex(s.h,s.s,s.v);i={background:t,border:a,highlight:{background:h,border:a},hover:{background:h,border:a}}}else i={background:t,border:t,highlight:{background:t,border:t},hover:{background:t,border:t}}}else i={},i.background=t.background||void 0,i.border=t.border||void 0,e.isString(t.highlight)?i.highlight={border:t.highlight,background:t.highlight}:(i.highlight={},i.highlight.background=t.highlight&&t.highlight.background||void 0,i.highlight.border=t.highlight&&t.highlight.border||void 0),e.isString(t.hover)?i.hover={border:t.hover,background:t.hover}:(i.hover={},i.hover.background=t.hover&&t.hover.background||void 0,i.hover.border=t.hover&&t.hover.border||void 0);return i},e.RGBToHSV=function(t,e,i){t/=255,e/=255,i/=255;var o=Math.min(t,Math.min(e,i)),n=Math.max(t,Math.max(e,i));if(o==n)return{h:0,s:0,v:o};var s=t==o?e-i:i==o?t-e:i-t;return{h:60*((t==o?3:i==o?1:5)-s/(n-o))/360,s:(n-o)/n,v:n}};var m={split:function(t){var e={};return t.split(";").forEach(function(t){if(""!=t.trim()){var i=t.split(":"),o=i[0].trim(),n=i[1].trim();e[o]=n}}),e},join:function(t){return(0,d.default)(t).map(function(e){return e+": "+t[e]}).join("; ")}};e.addCssText=function(t,i){var o=m.split(t.style.cssText),n=m.split(i),s=e.extend(o,n);t.style.cssText=m.join(s)},e.removeCssText=function(t,e){var i=m.split(t.style.cssText),o=m.split(e);for(var n in o)o.hasOwnProperty(n)&&delete i[n];t.style.cssText=m.join(i)},e.HSVToRGB=function(t,e,i){var o,n,s,r=Math.floor(6*t),a=6*t-r,h=i*(1-e),d=i*(1-a*e),l=i*(1-(1-a)*e);switch(r%6){case 0:o=i,n=l,s=h;break;case 1:o=d,n=i,s=h;break;case 2:o=h,n=i,s=l;break;case 3:o=h,n=d,s=i;break;case 4:o=l,n=h,s=i;break;case 5:o=i,n=h,s=d}return{r:Math.floor(255*o),g:Math.floor(255*n),b:Math.floor(255*s)}},e.HSVToHex=function(t,i,o){var n=e.HSVToRGB(t,i,o);return e.RGBToHex(n.r,n.g,n.b)},e.hexToHSV=function(t){var i=e.hexToRGB(t);return e.RGBToHSV(i.r,i.g,i.b)},e.isValidHex=function(t){return/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(t)},e.isValidRGB=function(t){return t=t.replace(" ",""),/rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/i.test(t)},e.isValidRGBA=function(t){return t=t.replace(" ",""),/rgba\((\d{1,3}),(\d{1,3}),(\d{1,3}),(.{1,3})\)/i.test(t)},e.selectiveBridgeObject=function(t,i){if("object"==(void 0===i?"undefined":(0,u.default)(i))){for(var o=(0,a.default)(i),n=0;n0&&e(o,t[n-1])<0;n--)t[n]=t[n-1];t[n]=o}return t},e.mergeOptions=function(t,e,i){var o=(arguments.length>3&&void 0!==arguments[3]&&arguments[3],arguments.length>4&&void 0!==arguments[4]?arguments[4]:{});if(null===e[i])t[i]=(0,a.default)(o[i]);else if(void 0!==e[i])if("boolean"==typeof e[i])t[i].enabled=e[i];else{void 0===e[i].enabled&&(t[i].enabled=!0);for(var n in e[i])e[i].hasOwnProperty(n)&&(t[i][n]=e[i][n])}},e.binarySearchCustom=function(t,e,i,o){for(var n=0,s=0,r=t.length-1;s<=r&&n<1e4;){var a=Math.floor((s+r)/2),h=t[a],d=void 0===o?h[i]:h[i][o],l=e(d);if(0==l)return a;-1==l?s=a+1:r=a-1,n++}return-1},e.binarySearchValue=function(t,e,i,o,n){for(var s,r,a,h,d=0,l=0,u=t.length-1,n=void 0!=n?n:function(t,e){return t==e?0:t0)return"before"==o?Math.max(0,h-1):h;if(n(r,e)<0&&n(a,e)>0)return"before"==o?h:Math.min(t.length-1,h+1);n(r,e)<0?l=h+1:u=h-1,d++}return-1},e.easingFunctions={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return t*(2-t)},easeInOutQuad:function(t){return t<.5?2*t*t:(4-2*t)*t-1},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return--t*t*t+1},easeInOutCubic:function(t){return t<.5?4*t*t*t:(t-1)*(2*t-2)*(2*t-2)+1},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return 1- --t*t*t*t},easeInOutQuart:function(t){return t<.5?8*t*t*t*t:1-8*--t*t*t*t},easeInQuint:function(t){return t*t*t*t*t},easeOutQuint:function(t){return 1+--t*t*t*t*t},easeInOutQuint:function(t){return t<.5?16*t*t*t*t*t:1+16*--t*t*t*t*t}},e.getScrollBarWidth=function(){var t=document.createElement("p");t.style.width="100%",t.style.height="200px";var e=document.createElement("div");e.style.position="absolute",e.style.top="0px",e.style.left="0px",e.style.visibility="hidden",e.style.width="200px",e.style.height="150px",e.style.overflow="hidden",e.appendChild(t),document.body.appendChild(e);var i=t.offsetWidth;e.style.overflow="scroll";var o=t.offsetWidth;return i==o&&(o=e.clientWidth),document.body.removeChild(e),i-o},e.topMost=function(t,e){var i=void 0;Array.isArray(e)||(e=[e]);var o=!0,n=!1,r=void 0;try{for(var a,h=(0,s.default)(t);!(o=(a=h.next()).done);o=!0){var d=a.value;if(d){i=d[e[0]];for(var l=1;l=t.length?(this._t=void 0,n(1)):"keys"==e?n(0,i):"values"==e?n(0,t[i]):n(0,[i,t[i]])},"values"),s.Arguments=s.Array,o("keys"),o("values"),o("entries")},function(t,e){t.exports=function(){}},function(t,e){t.exports=function(t,e){return{value:e,done:!!t}}},function(t,e){t.exports={}},function(t,e,i){var o=i(10),n=i(12);t.exports=function(t){return o(n(t))}},function(t,e,i){var o=i(11);t.exports=Object("z").propertyIsEnumerable(0)?Object:function(t){return"String"==o(t)?t.split(""):Object(t)}},function(t,e){var i={}.toString;t.exports=function(t){return i.call(t).slice(8,-1)}},function(t,e){t.exports=function(t){if(void 0==t)throw TypeError("Can't call method on "+t);return t}},function(t,e,i){var o=i(14),n=i(15),s=i(30),r=i(20),a=i(31),h=i(8),d=i(32),l=i(46),u=i(48),c=i(47)("iterator"),p=!([].keys&&"next"in[].keys()),f=function(){return this};t.exports=function(t,e,i,m,v,g,y){d(i,e,m);var b,_,w,x=function(t){if(!p&&t in D)return D[t];switch(t){case"keys":case"values":return function(){return new i(this,t)}}return function(){return new i(this,t)}},k=e+" Iterator",M="values"==v,S=!1,D=t.prototype,O=D[c]||D["@@iterator"]||v&&D[v],C=O||x(v),T=v?M?x("entries"):C:void 0,E="Array"==e?D.entries||O:O;if(E&&(w=u(E.call(new t)))!==Object.prototype&&(l(w,k,!0),o||a(w,c)||r(w,c,f)),M&&O&&"values"!==O.name&&(S=!0,C=function(){return O.call(this)}),o&&!y||!p&&!S&&D[c]||r(D,c,C),h[e]=C,h[k]=f,v)if(b={values:M?C:x("values"),keys:g?C:x("keys"),entries:T},y)for(_ in b)_ in D||s(D,_,b[_]);else n(n.P+n.F*(p||S),e,b);return b}},function(t,e){t.exports=!0},function(t,e,i){var o=i(16),n=i(17),s=i(18),r=i(20),a=function(t,e,i){var h,d,l,u=t&a.F,c=t&a.G,p=t&a.S,f=t&a.P,m=t&a.B,v=t&a.W,g=c?n:n[e]||(n[e]={}),y=g.prototype,b=c?o:p?o[e]:(o[e]||{}).prototype;c&&(i=e);for(h in i)(d=!u&&b&&void 0!==b[h])&&h in g||(l=d?b[h]:i[h],g[h]=c&&"function"!=typeof b[h]?i[h]:m&&d?s(l,o):v&&b[h]==l?function(t){var e=function(e,i,o){if(this instanceof t){switch(arguments.length){case 0:return new t;case 1:return new t(e);case 2:return new t(e,i)}return new t(e,i,o)}return t.apply(this,arguments)};return e.prototype=t.prototype,e}(l):f&&"function"==typeof l?s(Function.call,l):l,f&&((g.virtual||(g.virtual={}))[h]=l,t&a.R&&y&&!y[h]&&r(y,h,l)))};a.F=1,a.G=2,a.S=4,a.P=8,a.B=16,a.W=32,a.U=64,a.R=128,t.exports=a},function(t,e){var i=t.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=i)},function(t,e){var i=t.exports={version:"2.4.0"};"number"==typeof __e&&(__e=i)},function(t,e,i){var o=i(19);t.exports=function(t,e,i){if(o(t),void 0===e)return t;switch(i){case 1:return function(i){return t.call(e,i)};case 2:return function(i,o){return t.call(e,i,o)};case 3:return function(i,o,n){return t.call(e,i,o,n)}}return function(){return t.apply(e,arguments)}}},function(t,e){t.exports=function(t){if("function"!=typeof t)throw TypeError(t+" is not a function!");return t}},function(t,e,i){var o=i(21),n=i(29);t.exports=i(25)?function(t,e,i){return o.f(t,e,n(1,i))}:function(t,e,i){return t[e]=i,t}},function(t,e,i){var o=i(22),n=i(24),s=i(28),r=Object.defineProperty;e.f=i(25)?Object.defineProperty:function(t,e,i){if(o(t),e=s(e,!0),o(i),n)try{return r(t,e,i)}catch(t){}if("get"in i||"set"in i)throw TypeError("Accessors not supported!");return"value"in i&&(t[e]=i.value),t}},function(t,e,i){var o=i(23);t.exports=function(t){if(!o(t))throw TypeError(t+" is not an object!");return t}},function(t,e){t.exports=function(t){return"object"==typeof t?null!==t:"function"==typeof t}},function(t,e,i){t.exports=!i(25)&&!i(26)(function(){return 7!=Object.defineProperty(i(27)("div"),"a",{get:function(){return 7}}).a})},function(t,e,i){t.exports=!i(26)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(t,e){t.exports=function(t){try{return!!t()}catch(t){return!0}}},function(t,e,i){var o=i(23),n=i(16).document,s=o(n)&&o(n.createElement);t.exports=function(t){return s?n.createElement(t):{}}},function(t,e,i){var o=i(23);t.exports=function(t,e){if(!o(t))return t;var i,n;if(e&&"function"==typeof(i=t.toString)&&!o(n=i.call(t)))return n;if("function"==typeof(i=t.valueOf)&&!o(n=i.call(t)))return n;if(!e&&"function"==typeof(i=t.toString)&&!o(n=i.call(t)))return n;throw TypeError("Can't convert object to primitive value")}},function(t,e){t.exports=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}}},function(t,e,i){t.exports=i(20)},function(t,e){var i={}.hasOwnProperty;t.exports=function(t,e){return i.call(t,e)}},function(t,e,i){var o=i(33),n=i(29),s=i(46),r={};i(20)(r,i(47)("iterator"),function(){return this}),t.exports=function(t,e,i){t.prototype=o(r,{next:n(1,i)}),s(t,e+" Iterator")}},function(t,e,i){var o=i(22),n=i(34),s=i(44),r=i(41)("IE_PROTO"),a=function(){},h=function(){var t,e=i(27)("iframe"),o=s.length;for(e.style.display="none",i(45).appendChild(e),e.src="javascript:",t=e.contentWindow.document,t.open(),t.write("