All Downloads are FREE. Search and download functionalities are using the official Maven repository.

hive-webapps.static.js.query-plan-graph.js Maven / Gradle / Ivy

The newest version!
/**
 * 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.
 */
/**
* query-plan-graph.js
*
* Display a visualization of query plan
*/

const DEFAULT_TEXT_INDENT = 2;
const MAX_ORDER_TRIES = 100;

const SUCCESS = "Success";
const RUNNING = "Running";
const FAILED = "Failure";

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, jsonStatuses, jsonStats,
  jsonLogs, 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, jsonStatuses, jsonLogs,
        displayInformationTextIndent);
    }
  });
  colorTasks(networkData.edges, networkData.nodes, jsonStatuses)
  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, jsonStatuses) {
  grayFilteredOutTasks(edges, nodes, jsonStatuses);
  showSuccessfulTasks(edges, nodes, jsonStatuses);
  showRunningTasks(nodes, jsonStatuses);
  showFailedTasks(nodes, jsonStatuses);
  }

/**
* 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 jsonStatuses.
* Set all other tasks' node & edge colors to light gray
*/
function grayFilteredOutTasks(edgesDataSet, nodesDataSet, jsonStatuses) {

  for (node in nodesDataSet.getIds()) {
    nodeId = nodesDataSet.getIds()[node];
    var taskId = "Stage-" + nodeId;
    var otherTaskId = nodesDataSet.get(nodeId).id;
    if (!Object.keys(jsonStatuses).includes(taskId)) {
      setColor(nodesDataSet, nodesDataSet.get(nodeId), LIGHT_GRAY);
      setAllNodeEdgesColor(edgesDataSet, nodeId, LIGHT_GRAY);
    }
  }
}


/**
* If a task is successful, color it green!
*/
function showSuccessfulTasks(edgesDataSet, nodesDataSet, jsonStatuses) {
  changeNodeColorByStatus(nodesDataSet, jsonStatuses, SUCCESS, GREEN)
  matchAllEdgeColors(nodesDataSet, edgesDataSet);
}

function showRunningTasks(nodesDataSet, jsonStatuses) {
  changeNodeColorByStatus(nodesDataSet, jsonStatuses, RUNNING, BLUE)
}

function showFailedTasks(nodesDataSet, jsonStatuses) {
  changeNodeColorByStatus(nodesDataSet, jsonStatuses, FAILED, PINK)
}

function changeNodeColorByStatus(nodesDataSet, jsonStatuses, statusString, color) {
  for (node in nodesDataSet.getIds()) {
    nodeId = nodesDataSet.getIds()[node];
    var taskId = "Stage-" + nodeId;
    if (Object.keys(jsonStatuses).includes(taskId)) {
      if (jsonStatuses[taskId].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);
    for (var edgeNdx = 0; edgeNdx < listOfEdges.length; edgeNdx++) {
      if (!(currentPath.includes(listOfEdges[edgeNdx].to) && currentPath.includes(listOfEdges[edgeNdx].from))) {
        listOfEdges.splice(edgeNdx, 1);
        edgeNdx--;
      }
    }
    var count = 0;
    while (currentPath.length != 0 && count < MAX_ORDER_TRIES) {
      for (var nodeNdx in currentPath) {
        var 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--;
            }
          }
        }
      }
      count++;
    }
    paths[pathNdx] = 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
    var node = currentPath[nodeIndex];
    var 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) {
    var 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
      }
    },
    interaction: {
      zoomView: false
    }
  };
  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, jsonStatuses, jsonLogs, textIndent) {
  if (document.getElementById(PROGRESS_ELEMENT)) {
    document.getElementById(PROGRESS_ELEMENT).remove();
  }
  nodeId = params.nodes;
  stageName = "Stage-" + nodeId;
  if (nodeId != "" && jsonStatuses[stageName] != null) {
    if (jsonStats[stageName] != null) {

      document.getElementById(displayStatisticsElementHead).innerHTML =
        "Stage " + nodeId + " statistics:\n" +
        "Status: " + jsonStatuses[stageName] + "\n" +
        "Logs: " + prettifyJsonString(JSON.stringify(jsonLogs, null, textIndent)) + "\n\n" +
        "MapReduce job progress:\n";
      document.getElementById(displayStatisticsElementBody).innerHTML =
        prettifyJsonString(JSON.stringify(jsonStats[stageName], null, textIndent));

      if (jsonStats[stageName][MAP_PROGRESS] != null) {
        var mapProgress = jsonStats[stageName][MAP_PROGRESS];
      }
      if (jsonStats[stageName][REDUCE_PROGRESS] != null) {
        var reduceProgress = jsonStats[stageName][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: " + jsonStatuses[stageName]+ "\n" +
        "Logs: " + prettifyJsonString(JSON.stringify(jsonLogs, null, textIndent));
      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;
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy