aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/src/main/resources/org/apache/spark/ui/static/spark-dag-viz.css60
-rw-r--r--core/src/main/resources/org/apache/spark/ui/static/spark-dag-viz.js57
-rw-r--r--core/src/main/resources/org/apache/spark/ui/static/timeline-view.css128
-rw-r--r--core/src/main/resources/org/apache/spark/ui/static/timeline-view.js4
-rw-r--r--core/src/main/resources/org/apache/spark/ui/static/webui.css36
-rw-r--r--core/src/main/scala/org/apache/spark/ui/ToolTips.scala19
-rw-r--r--core/src/main/scala/org/apache/spark/ui/UIUtils.scala15
-rw-r--r--core/src/main/scala/org/apache/spark/ui/jobs/AllJobsPage.scala31
-rw-r--r--core/src/main/scala/org/apache/spark/ui/jobs/AllStagesPage.scala15
-rw-r--r--core/src/main/scala/org/apache/spark/ui/jobs/JobPage.scala23
-rw-r--r--core/src/main/scala/org/apache/spark/ui/jobs/StagePage.scala16
-rw-r--r--core/src/main/scala/org/apache/spark/ui/scope/RDDOperationGraph.scala2
12 files changed, 255 insertions, 151 deletions
diff --git a/core/src/main/resources/org/apache/spark/ui/static/spark-dag-viz.css b/core/src/main/resources/org/apache/spark/ui/static/spark-dag-viz.css
index 8481710828..18c72694f3 100644
--- a/core/src/main/resources/org/apache/spark/ui/static/spark-dag-viz.css
+++ b/core/src/main/resources/org/apache/spark/ui/static/spark-dag-viz.css
@@ -16,40 +16,51 @@
*/
#dag-viz-graph svg path {
- stroke: #444444;
+ stroke: #444;
stroke-width: 1.5px;
}
#dag-viz-graph svg g.cluster rect {
- stroke-width: 4px;
- stroke-opacity: 0.5;
+ stroke-width: 1px;
+}
+
+#dag-viz-graph svg g.node circle {
+ fill: #444;
}
-#dag-viz-graph svg g.node circle,
#dag-viz-graph svg g.node rect {
- fill: #444444;
+ fill: #C3EBFF;
+ stroke: #3EC0FF;
+ stroke-width: 1px;
+}
+
+#dag-viz-graph svg g.node.cached circle {
+ fill: #444;
}
-#dag-viz-graph svg g.node.cached circle,
#dag-viz-graph svg g.node.cached rect {
- fill: #FF0000;
+ fill: #B3F5C5;
+ stroke: #56F578;
+ stroke-width: 1px;
}
/* Job page specific styles */
#dag-viz-graph svg.job marker#marker-arrow path {
- fill: #444444;
+ fill: #333;
stroke-width: 0px;
}
#dag-viz-graph svg.job g.cluster rect {
- fill: #FFFFFF;
- stroke: #AADFFF;
+ fill: #A0DFFF;
+ stroke: #3EC0FF;
+ stroke-width: 1px;
}
#dag-viz-graph svg.job g.cluster[id*="stage"] rect {
- stroke: #FFDDEE;
- stroke-width: 6px;
+ fill: #FFFFFF;
+ stroke: #FF99AC;
+ stroke-width: 1px;
}
#dag-viz-graph svg.job g#cross-stage-edges path {
@@ -57,27 +68,36 @@
}
#dag-viz-graph svg.job g.cluster text {
- fill: #AAAAAA;
+ fill: #333;
}
/* Stage page specific styles */
#dag-viz-graph svg.stage g.cluster rect {
- fill: #F0F8FF;
- stroke: #AADFFF;
+ fill: #A0DFFF;
+ stroke: #3EC0FF;
+ stroke-width: 1px;
}
#dag-viz-graph svg.stage g.cluster[id*="stage"] rect {
fill: #FFFFFF;
- stroke: #FFDDEE;
- stroke-width: 6px;
+ stroke: #FFA6B6;
+ stroke-width: 1px;
}
#dag-viz-graph svg.stage g.node g.label text tspan {
- fill: #FFFFFF;
+ fill: #333;
}
#dag-viz-graph svg.stage g.cluster text {
- fill: #444444;
- font-weight: bold;
+ fill: #333;
+}
+
+#dag-viz-graph a, #dag-viz-graph a:hover {
+ text-decoration: none;
+}
+
+#dag-viz-graph .label {
+ font-weight: normal;
+ text-shadow: none;
}
diff --git a/core/src/main/resources/org/apache/spark/ui/static/spark-dag-viz.js b/core/src/main/resources/org/apache/spark/ui/static/spark-dag-viz.js
index a0e3e914c2..764dd2cfcd 100644
--- a/core/src/main/resources/org/apache/spark/ui/static/spark-dag-viz.js
+++ b/core/src/main/resources/org/apache/spark/ui/static/spark-dag-viz.js
@@ -52,9 +52,9 @@
*/
var VizConstants = {
- svgMarginX: 20,
- svgMarginY: 20,
- stageSep: 50,
+ svgMarginX: 16,
+ svgMarginY: 16,
+ stageSep: 40,
graphPrefix: "graph_",
nodePrefix: "node_",
stagePrefix: "stage_",
@@ -63,14 +63,16 @@ var VizConstants = {
};
var JobPageVizConstants = {
- clusterLabelSize: 11,
- stageClusterLabelSize: 14
-}
+ clusterLabelSize: 12,
+ stageClusterLabelSize: 14,
+ rankSep: 40
+};
var StagePageVizConstants = {
clusterLabelSize: 14,
- stageClusterLabelSize: 18
-}
+ stageClusterLabelSize: 14,
+ rankSep: 40
+};
/*
* Show or hide the RDD DAG visualization.
@@ -149,11 +151,11 @@ function renderDagVizForStage(svgContainer) {
var dot = metadata.select(".dot-file").text();
var containerId = VizConstants.graphPrefix + metadata.attr("stage-id");
var container = svgContainer.append("g").attr("id", containerId);
- renderDot(dot, container);
+ renderDot(dot, container, StagePageVizConstants.rankSep);
- // Round corners on RDDs
+ // Round corners on rectangles
svgContainer
- .selectAll("g.node rect")
+ .selectAll("rect")
.attr("rx", "5")
.attr("ry", "5");
}
@@ -207,7 +209,13 @@ function renderDagVizForJob(svgContainer) {
}
// Actually render the stage
- renderDot(dot, container);
+ renderDot(dot, container, JobPageVizConstants.rankSep);
+
+ // Round corners on rectangles
+ container
+ .selectAll("rect")
+ .attr("rx", "4")
+ .attr("ry", "4");
// If there are any incoming edges into this graph, keep track of them to render
// them separately later. Note that we cannot draw them now because we need to
@@ -223,12 +231,13 @@ function renderDagVizForJob(svgContainer) {
}
/* Render the dot file as an SVG in the given container. */
-function renderDot(dot, container) {
+function renderDot(dot, container, rankSep) {
var escaped_dot = dot
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, "\"");
var g = graphlibDot.read(escaped_dot);
+ g.graph().rankSep = rankSep;
var renderer = new dagreD3.render();
renderer(container, g);
}
@@ -248,12 +257,13 @@ function metadataContainer() { return d3.select("#dag-viz-metadata"); }
* In general, the clustering support for dagre-d3 is quite limited at this point.
*/
function drawClusterLabels(svgContainer, forJob) {
+ var clusterLabelSize, stageClusterLabelSize;
if (forJob) {
- var clusterLabelSize = JobPageVizConstants.clusterLabelSize;
- var stageClusterLabelSize = JobPageVizConstants.stageClusterLabelSize;
+ clusterLabelSize = JobPageVizConstants.clusterLabelSize;
+ stageClusterLabelSize = JobPageVizConstants.stageClusterLabelSize;
} else {
- var clusterLabelSize = StagePageVizConstants.clusterLabelSize;
- var stageClusterLabelSize = StagePageVizConstants.stageClusterLabelSize;
+ clusterLabelSize = StagePageVizConstants.clusterLabelSize;
+ stageClusterLabelSize = StagePageVizConstants.stageClusterLabelSize;
}
svgContainer.selectAll("g.cluster").each(function() {
var cluster = d3.select(this);
@@ -283,7 +293,7 @@ function drawClusterLabel(d3cluster, fontSize) {
.attr("x", labelX)
.attr("y", labelY)
.attr("text-anchor", "end")
- .style("font-size", fontSize)
+ .style("font-size", fontSize + "px")
.text(labelText);
}
@@ -303,12 +313,12 @@ function resizeSvg(svg) {
}));
var endX = VizConstants.svgMarginX +
toFloat(d3.max(allClusters, function(e) {
- var t = d3.select(e)
+ var t = d3.select(e);
return getAbsolutePosition(t).x + toFloat(t.attr("width"));
}));
var endY = VizConstants.svgMarginY +
toFloat(d3.max(allClusters, function(e) {
- var t = d3.select(e)
+ var t = d3.select(e);
return getAbsolutePosition(t).y + toFloat(t.attr("height"));
}));
var width = endX - startX;
@@ -338,7 +348,7 @@ function drawCrossStageEdges(edges, svgContainer) {
if (!dagreD3Marker.empty()) {
svgContainer
.append(function() { return dagreD3Marker.node().cloneNode(true); })
- .attr("id", "marker-arrow")
+ .attr("id", "marker-arrow");
svgContainer.selectAll("g > path").attr("marker-end", "url(#marker-arrow)");
svgContainer.selectAll("g.edgePaths def").remove(); // We no longer need these
}
@@ -394,12 +404,13 @@ function connectRDDs(fromRDDId, toRDDId, edgesContainer, svgContainer) {
toPos.x += delta;
}
+ var points;
if (fromPos.y == toPos.y) {
// If they are on the same rank, curve the middle part of the edge
// upward a little to avoid interference with things in between
// e.g. _______
// _____/ \_____
- var points = [
+ points = [
[fromPos.x, fromPos.y],
[fromPos.x + (toPos.x - fromPos.x) * 0.2, fromPos.y],
[fromPos.x + (toPos.x - fromPos.x) * 0.3, fromPos.y - 20],
@@ -413,7 +424,7 @@ function connectRDDs(fromRDDId, toRDDId, edgesContainer, svgContainer) {
// /
// |
// _____/
- var points = [
+ points = [
[fromPos.x, fromPos.y],
[fromPos.x + (toPos.x - fromPos.x) * 0.4, fromPos.y],
[fromPos.x + (toPos.x - fromPos.x) * 0.6, toPos.y],
diff --git a/core/src/main/resources/org/apache/spark/ui/static/timeline-view.css b/core/src/main/resources/org/apache/spark/ui/static/timeline-view.css
index 35ef14e5aa..d40de70422 100644
--- a/core/src/main/resources/org/apache/spark/ui/static/timeline-view.css
+++ b/core/src/main/resources/org/apache/spark/ui/static/timeline-view.css
@@ -23,6 +23,10 @@ div#application-timeline, div#job-timeline {
margin-top: 5px;
}
+.vis.timeline {
+ line-height: 14px;
+}
+
.vis.timeline div.content {
width: 100%;
}
@@ -32,48 +36,55 @@ div#application-timeline, div#job-timeline {
}
.vis.timeline .item.stage.succeeded {
- background-color: #D5DDF6;
+ background-color: #A0DFFF;
+ border-color: #3EC0FF;
}
.vis.timeline .item.stage.succeeded.selected {
- background-color: #D5DDF6;
- border-color: #97B0F8;
- z-index: auto;
+ background-color: #A0DFFF;
+ border-color: #3EC0FF;
+ z-index: auto;
}
.legend-area rect.completed-stage-legend {
- fill: #D5DDF6;
- stroke: #97B0F8;
+ fill: #A0DFFF;
+ stroke: #3EC0FF;
}
.vis.timeline .item.stage.failed {
- background-color: #FF5475;
+ background-color: #FFA1B0;
+ border-color: #FF4D6D;
}
.vis.timeline .item.stage.failed.selected {
- background-color: #FF5475;
- border-color: #97B0F8;
- z-index: auto;
+ background-color: #FFA1B0;
+ border-color: #FF4D6D;
+ z-index: auto;
}
.legend-area rect.failed-stage-legend {
- fill: #FF5475;
- stroke: #97B0F8;
+ fill: #FFA1B0;
+ stroke: #FF4D6D;
}
.vis.timeline .item.stage.running {
- background-color: #FDFFCA;
+ background-color: #A2FCC0;
+ border-color: #36F572;
}
.vis.timeline .item.stage.running.selected {
- background-color: #FDFFCA;
- border-color: #97B0F8;
- z-index: auto;
+ background-color: #A2FCC0;
+ border-color: #36F572;
+ z-index: auto;
}
.legend-area rect.active-stage-legend {
- fill: #FDFFCA;
- stroke: #97B0F8;
+ fill: #A2FCC0;
+ stroke: #36F572;
+}
+
+.vis.timeline .foreground {
+ cursor: move;
}
.vis.timeline .item.job {
@@ -81,76 +92,81 @@ div#application-timeline, div#job-timeline {
}
.vis.timeline .item.job.succeeded {
- background-color: #D5DDF6;
+ background-color: #A0DFFF;
+ border-color: #3EC0FF;
}
.vis.timeline .item.job.succeeded.selected {
- background-color: #D5DDF6;
- border-color: #97B0F8;
- z-index: auto;
+ background-color: #A0DFFF;
+ border-color: #3EC0FF;
+ z-index: auto;
}
.legend-area rect.succeeded-job-legend {
- fill: #D5DDF6;
- stroke: #97B0F8;
+ fill: #A0DFFF;
+ stroke: #3EC0FF;
}
.vis.timeline .item.job.failed {
- background-color: #FF5475;
+ background-color: #FFA1B0;
+ border-color: #FF4D6D;
}
.vis.timeline .item.job.failed.selected {
- background-color: #FF5475;
- border-color: #97B0F8;
- z-index: auto;
+ background-color: #FFA1B0;
+ border-color: #FF4D6D;
+ z-index: auto;
}
.legend-area rect.failed-job-legend {
- fill: #FF5475;
- stroke: #97B0F8;
+ fill: #FFA1B0;
+ stroke: #FF4D6D;
}
.vis.timeline .item.job.running {
- background-color: #FDFFCA;
+ background-color: #A2FCC0;
+ border-color: #36F572;
}
.vis.timeline .item.job.running.selected {
- background-color: #FDFFCA;
- border-color: #97B0F8;
- z-index: auto;
+ background-color: #A2FCC0;
+ border-color: #36F572;
+ z-index: auto;
}
.legend-area rect.running-job-legend {
- fill: #FDFFCA;
- stroke: #97B0F8;
+ fill: #A2FCC0;
+ stroke: #36F572;
}
.vis.timeline .item.executor.added {
- background-color: #D5DDF6;
+ background-color: #A0DFFF;
+ border-color: #3EC0FF;
}
.legend-area rect.executor-added-legend {
- fill: #D5DDF6;
- stroke: #97B0F8;
+ fill: #A0DFFF;
+ stroke: #3EC0FF;
}
.vis.timeline .item.executor.removed {
- background-color: #EBCA59;
+ background-color: #FFA1B0;
+ border-color: #FF4D6D;
}
.legend-area rect.executor-removed-legend {
- fill: #EBCA59;
- stroke: #97B0F8;
+ fill: #FFA1B0;
+ stroke: #FF4D6D;
}
.vis.timeline .item.executor.selected {
- border-color: #FFC200;
- background-color: #FFF785;
+ background-color: #A2FCC0;
+ border-color: #36F572;
z-index: 2;
}
-tr.corresponding-item-hover>td, tr.corresponding-item-hover>th {
- background-color: #FFE1FA !important;
+tr.corresponding-item-hover > td, tr.corresponding-item-hover > th {
+ background-color: #D6FFE4 !important;
}
#application-timeline.collapsed {
@@ -165,11 +181,15 @@ tr.corresponding-item-hover>td, tr.corresponding-item-hover>th {
margin-bottom: 5px;
}
+.control-panel input[type="checkbox"] {
+ margin: 0;
+}
+
span.expand-application-timeline, span.expand-job-timeline {
cursor: pointer;
}
-.control-panel input+span {
+.control-panel input + span {
cursor: pointer;
}
@@ -180,3 +200,17 @@ span.expand-application-timeline, span.expand-job-timeline {
.vis.timeline .item .tooltip-inner {
max-width: unset !important;
}
+
+.vispanel.center {
+ font-size: 12px;
+ line-height: 12px;
+}
+
+.legend-area text {
+ fill: #4D4D4D;
+}
+
+.additional-metrics ul {
+ list-style: none;
+ margin-left: 15px;
+}
diff --git a/core/src/main/resources/org/apache/spark/ui/static/timeline-view.js b/core/src/main/resources/org/apache/spark/ui/static/timeline-view.js
index e4a891d47f..48fbb33b11 100644
--- a/core/src/main/resources/org/apache/spark/ui/static/timeline-view.js
+++ b/core/src/main/resources/org/apache/spark/ui/static/timeline-view.js
@@ -156,9 +156,9 @@ function setupExecutorEventAction() {
function setupZoomable(id, timeline) {
$(id + '>input[type="checkbox"]').click(function() {
if (this.checked) {
- timeline.setOptions({zoomable: false});
- } else {
timeline.setOptions({zoomable: true});
+ } else {
+ timeline.setOptions({zoomable: false});
}
});
diff --git a/core/src/main/resources/org/apache/spark/ui/static/webui.css b/core/src/main/resources/org/apache/spark/ui/static/webui.css
index 669ad48937..e7c1d475d4 100644
--- a/core/src/main/resources/org/apache/spark/ui/static/webui.css
+++ b/core/src/main/resources/org/apache/spark/ui/static/webui.css
@@ -106,14 +106,18 @@ span.rest-uri {
}
pre {
- font-size: 0.8em;
+ font-size: 12px;
+ line-height: 18px;
+ padding: 6px;
+ margin: 0;
+ border-radius: 3px;
}
.stage-details {
max-height: 100px;
overflow-y: auto;
margin: 0;
- transition: max-height 0.5s ease-out, padding 0.5s ease-out;
+ transition: max-height 0.25s ease-out, padding 0.25s ease-out;
}
.stage-details.collapsed {
@@ -135,7 +139,7 @@ pre {
max-height: 300px;
overflow-y: auto;
margin: 0;
- transition: max-height 0.5s ease-out, padding 0.5s ease-out;
+ transition: max-height 0.25s ease-out, padding 0.25s ease-out;
}
.stacktrace-details.collapsed {
@@ -158,7 +162,7 @@ span.additional-metric-title {
}
.tooltip {
- font-weight: normal;
+ font-weight: normal;
}
.arrow-open {
@@ -166,9 +170,9 @@ span.additional-metric-title {
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
- border-top: 5px solid black;
- float: left;
- margin-top: 6px;
+ border-top: 5px solid #08c;
+ display: inline-block;
+ margin-bottom: 2px;
}
.arrow-closed {
@@ -176,8 +180,10 @@ span.additional-metric-title {
height: 0;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
- border-left: 5px solid black;
+ border-left: 5px solid #08c;
display: inline-block;
+ margin-left: 2px;
+ margin-right: 3px;
}
.version {
@@ -196,3 +202,17 @@ span.additional-metric-title {
.serialization_time, .getting_result_time {
display: none;
}
+
+.accordion-inner {
+ background: #f5f5f5;
+}
+
+.accordion-inner pre {
+ border: 0;
+ padding: 0;
+ background: none;
+}
+
+a.expandbutton {
+ cursor: pointer;
+}
diff --git a/core/src/main/scala/org/apache/spark/ui/ToolTips.scala b/core/src/main/scala/org/apache/spark/ui/ToolTips.scala
index 24f3236456..063e2a1f8b 100644
--- a/core/src/main/scala/org/apache/spark/ui/ToolTips.scala
+++ b/core/src/main/scala/org/apache/spark/ui/ToolTips.scala
@@ -57,4 +57,23 @@ private[spark] object ToolTips {
val GC_TIME =
"""Time that the executor spent paused for Java garbage collection while the task was
running."""
+
+ val JOB_TIMELINE =
+ """Shows when jobs started and ended and when executors joined or left. Drag to scroll.
+ Click Enable Zooming and use mouse wheel to zoom in/out."""
+
+ val STAGE_TIMELINE =
+ """Shows when stages started and ended and when executors joined or left. Drag to scroll.
+ Click Enable Zooming and use mouse wheel to zoom in/out."""
+
+ val JOB_DAG =
+ """Shows a graph of stages executed for this job, each of which can contain
+ multiple RDD operations (e.g. map() and filter()), and of RDDs inside each operation
+ (shown as dots)."""
+
+ val STAGE_DAG =
+ """Shows a graph of RDD operations in this stage, and RDDs inside each one. A stage can run
+ multiple operations (e.g. two map() functions) if they can be pipelined. Some operations
+ also create multiple RDDs internally. Cached RDDs are shown in green.
+ """
}
diff --git a/core/src/main/scala/org/apache/spark/ui/UIUtils.scala b/core/src/main/scala/org/apache/spark/ui/UIUtils.scala
index 97eed13c2d..6a0f5c5d16 100644
--- a/core/src/main/scala/org/apache/spark/ui/UIUtils.scala
+++ b/core/src/main/scala/org/apache/spark/ui/UIUtils.scala
@@ -156,10 +156,10 @@ private[spark] object UIUtils extends Logging {
def commonHeaderNodes: Seq[Node] = {
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
- <link rel="stylesheet" href={prependBaseUri("/static/bootstrap.min.css")} type="text/css" />
- <link rel="stylesheet" href={prependBaseUri("/static/webui.css")} type="text/css" />
- <link rel="stylesheet" href={prependBaseUri("/static/vis.min.css")} type="text/css" />
- <link rel="stylesheet" href={prependBaseUri("/static/timeline-view.css")} type="text/css" />
+ <link rel="stylesheet" href={prependBaseUri("/static/bootstrap.min.css")} type="text/css"/>
+ <link rel="stylesheet" href={prependBaseUri("/static/vis.min.css")} type="text/css"/>
+ <link rel="stylesheet" href={prependBaseUri("/static/webui.css")} type="text/css"/>
+ <link rel="stylesheet" href={prependBaseUri("/static/timeline-view.css")} type="text/css"/>
<script src={prependBaseUri("/static/sorttable.js")} ></script>
<script src={prependBaseUri("/static/jquery-1.11.1.min.js")}></script>
<script src={prependBaseUri("/static/vis.min.js")}></script>
@@ -250,7 +250,7 @@ private[spark] object UIUtils extends Logging {
<h3 style="vertical-align: middle; display: inline-block;">
<a style="text-decoration: none" href={prependBaseUri("/")}>
<img src={prependBaseUri("/static/spark-logo-77x50px-hd.png")} />
- <span class="version"
+ <span class="version"
style="margin-right: 15px;">{org.apache.spark.SPARK_VERSION}</span>
</a>
{title}
@@ -350,7 +350,10 @@ private[spark] object UIUtils extends Logging {
<div>
<span class="expand-dag-viz" onclick={s"toggleDagViz($forJob);"}>
<span class="expand-dag-viz-arrow arrow-closed"></span>
- <strong>DAG visualization</strong>
+ <a data-toggle="tooltip" title={if (forJob) ToolTips.JOB_DAG else ToolTips.STAGE_DAG}
+ data-placement="right">
+ DAG Visualization
+ </a>
</span>
<div id="dag-viz-graph"></div>
<div id="dag-viz-metadata">
diff --git a/core/src/main/scala/org/apache/spark/ui/jobs/AllJobsPage.scala b/core/src/main/scala/org/apache/spark/ui/jobs/AllJobsPage.scala
index 09323d1d80..e010ebef3b 100644
--- a/core/src/main/scala/org/apache/spark/ui/jobs/AllJobsPage.scala
+++ b/core/src/main/scala/org/apache/spark/ui/jobs/AllJobsPage.scala
@@ -18,12 +18,12 @@
package org.apache.spark.ui.jobs
import scala.collection.mutable.{HashMap, ListBuffer}
-import scala.xml.{Node, NodeSeq, Unparsed}
+import scala.xml.{Node, NodeSeq, Unparsed, Utility}
import java.util.Date
import javax.servlet.http.HttpServletRequest
-import org.apache.spark.ui.{UIUtils, WebUIPage}
+import org.apache.spark.ui.{ToolTips, UIUtils, WebUIPage}
import org.apache.spark.ui.jobs.UIData.{ExecutorUIData, JobUIData}
import org.apache.spark.JobExecutionStatus
@@ -81,6 +81,9 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") {
case JobExecutionStatus.RUNNING => "running"
}
+ // The timeline library treats contents as HTML, so we have to escape them; for the
+ // data-title attribute string we have to escape them twice since that's in a string.
+ val escapedDesc = Utility.escape(displayJobDescription)
val jobEventJsonAsStr =
s"""
|{
@@ -90,16 +93,17 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") {
| 'end': new Date(${completionTime}),
| 'content': '<div class="application-timeline-content"' +
| 'data-html="true" data-placement="top" data-toggle="tooltip"' +
- | 'data-title="${displayJobDescription} (Job ${jobId})<br>Status: ${status}<br>' +
- | 'Submission Time: ${UIUtils.formatDate(new Date(submissionTime))}' +
+ | 'data-title="${Utility.escape(escapedDesc)} (Job ${jobId})<br>' +
+ | 'Status: ${status}<br>' +
+ | 'Submitted: ${UIUtils.formatDate(new Date(submissionTime))}' +
| '${
if (status != JobExecutionStatus.RUNNING) {
- s"""<br>Completion Time: ${UIUtils.formatDate(new Date(completionTime))}"""
+ s"""<br>Completed: ${UIUtils.formatDate(new Date(completionTime))}"""
} else {
""
}
}">' +
- | '${displayJobDescription} (Job ${jobId})</div>'
+ | '${escapedDesc} (Job ${jobId})</div>'
|}
""".stripMargin
jobEventJsonAsStr
@@ -179,13 +183,15 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") {
<span class="expand-application-timeline">
<span class="expand-application-timeline-arrow arrow-closed"></span>
- <strong>Event timeline</strong>
+ <a data-toggle="tooltip" title={ToolTips.JOB_TIMELINE} data-placement="right">
+ Event Timeline
+ </a>
</span> ++
<div id="application-timeline" class="collapsed">
<div class="control-panel">
<div id="application-timeline-zoom-lock">
- <input type="checkbox" checked="checked"></input>
- <span>Zoom Lock</span>
+ <input type="checkbox"></input>
+ <span>Enable zooming</span>
</div>
</div>
</div> ++
@@ -283,7 +289,7 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") {
{if (parent.sc.isDefined) {
// Total duration is not meaningful unless the UI is live
<li>
- <strong>Total Duration: </strong>
+ <strong>Total Uptime: </strong>
{UIUtils.formatDuration(System.currentTimeMillis() - startTime)}
</li>
}}
@@ -336,9 +342,8 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") {
failedJobsTable
}
- val helpText = """A job is triggered by an action, like "count()" or "saveAsTextFile()".""" +
- " Click on a job's title to see information about the stages of tasks associated with" +
- " the job."
+ val helpText = """A job is triggered by an action, like count() or saveAsTextFile().""" +
+ " Click on a job to see information about the stages of tasks inside it."
UIUtils.headerSparkPage("Spark Jobs", content, parent, helpText = Some(helpText))
}
diff --git a/core/src/main/scala/org/apache/spark/ui/jobs/AllStagesPage.scala b/core/src/main/scala/org/apache/spark/ui/jobs/AllStagesPage.scala
index a37f739ab9..5e52942b64 100644
--- a/core/src/main/scala/org/apache/spark/ui/jobs/AllStagesPage.scala
+++ b/core/src/main/scala/org/apache/spark/ui/jobs/AllStagesPage.scala
@@ -74,19 +74,6 @@ private[ui] class AllStagesPage(parent: StagesTab) extends WebUIPage("") {
<div>
<ul class="unstyled">
{
- if (sc.isDefined) {
- // Total duration is not meaningful unless the UI is live
- <li>
- <strong>Total Duration: </strong>
- {UIUtils.formatDuration(now - sc.get.startTime)}
- </li>
- }
- }
- <li>
- <strong>Scheduling Mode: </strong>
- {listener.schedulingMode.map(_.toString).getOrElse("Unknown")}
- </li>
- {
if (shouldShowActiveStages) {
<li>
<a href="#active"><strong>Active Stages:</strong></a>
@@ -145,7 +132,7 @@ private[ui] class AllStagesPage(parent: StagesTab) extends WebUIPage("") {
content ++= <h4 id ="failed">Failed Stages ({numFailedStages})</h4> ++
failedStagesTable.toNodeSeq
}
- UIUtils.headerSparkPage("Spark Stages (for all jobs)", content, parent)
+ UIUtils.headerSparkPage("Stages for All Jobs", content, parent)
}
}
}
diff --git a/core/src/main/scala/org/apache/spark/ui/jobs/JobPage.scala b/core/src/main/scala/org/apache/spark/ui/jobs/JobPage.scala
index 7163217e1f..2cad0a7969 100644
--- a/core/src/main/scala/org/apache/spark/ui/jobs/JobPage.scala
+++ b/core/src/main/scala/org/apache/spark/ui/jobs/JobPage.scala
@@ -20,13 +20,13 @@ package org.apache.spark.ui.jobs
import java.util.Date
import scala.collection.mutable.{Buffer, HashMap, ListBuffer}
-import scala.xml.{NodeSeq, Node, Unparsed}
+import scala.xml.{NodeSeq, Node, Unparsed, Utility}
import javax.servlet.http.HttpServletRequest
import org.apache.spark.JobExecutionStatus
import org.apache.spark.scheduler.StageInfo
-import org.apache.spark.ui.{UIUtils, WebUIPage}
+import org.apache.spark.ui.{ToolTips, UIUtils, WebUIPage}
import org.apache.spark.ui.jobs.UIData.ExecutorUIData
/** Page showing statistics and stage list for a given job */
@@ -64,6 +64,9 @@ private[ui] class JobPage(parent: JobsTab) extends WebUIPage("job") {
val submissionTime = stage.submissionTime.get
val completionTime = stage.completionTime.getOrElse(System.currentTimeMillis())
+ // The timeline library treats contents as HTML, so we have to escape them; for the
+ // data-title attribute string we have to escape them twice since that's in a string.
+ val escapedName = Utility.escape(name)
s"""
|{
| 'className': 'stage job-timeline-object ${status}',
@@ -72,17 +75,17 @@ private[ui] class JobPage(parent: JobsTab) extends WebUIPage("job") {
| 'end': new Date(${completionTime}),
| 'content': '<div class="job-timeline-content" data-toggle="tooltip"' +
| 'data-placement="top" data-html="true"' +
- | 'data-title="${name} (Stage ${stageId}.${attemptId})<br>' +
+ | 'data-title="${Utility.escape(escapedName)} (Stage ${stageId}.${attemptId})<br>' +
| 'Status: ${status.toUpperCase}<br>' +
- | 'Submission Time: ${UIUtils.formatDate(new Date(submissionTime))}' +
+ | 'Submitted: ${UIUtils.formatDate(new Date(submissionTime))}' +
| '${
if (status != "running") {
- s"""<br>Completion Time: ${UIUtils.formatDate(new Date(completionTime))}"""
+ s"""<br>Completed: ${UIUtils.formatDate(new Date(completionTime))}"""
} else {
""
}
}">' +
- | '${name} (Stage ${stageId}.${attemptId})</div>',
+ | '${escapedName} (Stage ${stageId}.${attemptId})</div>',
|}
""".stripMargin
}
@@ -161,13 +164,15 @@ private[ui] class JobPage(parent: JobsTab) extends WebUIPage("job") {
<span class="expand-job-timeline">
<span class="expand-job-timeline-arrow arrow-closed"></span>
- <strong>Event timeline</strong>
+ <a data-toggle="tooltip" title={ToolTips.STAGE_TIMELINE} data-placement="right">
+ Event Timeline
+ </a>
</span> ++
<div id="job-timeline" class="collapsed">
<div class="control-panel">
<div id="job-timeline-zoom-lock">
- <input type="checkbox" checked="checked"></input>
- <span>Zoom Lock</span>
+ <input type="checkbox"></input>
+ <span>Enable zooming</span>
</div>
</div>
</div> ++
diff --git a/core/src/main/scala/org/apache/spark/ui/jobs/StagePage.scala b/core/src/main/scala/org/apache/spark/ui/jobs/StagePage.scala
index b01fad8e45..8f7b1c2f09 100644
--- a/core/src/main/scala/org/apache/spark/ui/jobs/StagePage.scala
+++ b/core/src/main/scala/org/apache/spark/ui/jobs/StagePage.scala
@@ -81,7 +81,7 @@ private[ui] class StagePage(parent: StagesTab) extends WebUIPage("stage") {
<div>
<ul class="unstyled">
<li>
- <strong>Total task time across all tasks: </strong>
+ <strong>Total Time Across All Tasks: </strong>
{UIUtils.formatDuration(stageData.executorRunTime)}
</li>
{if (stageData.hasInput) {
@@ -98,25 +98,25 @@ private[ui] class StagePage(parent: StagesTab) extends WebUIPage("stage") {
}}
{if (stageData.hasShuffleRead) {
<li>
- <strong>Shuffle read: </strong>
+ <strong>Shuffle Read: </strong>
{s"${Utils.bytesToString(stageData.shuffleReadTotalBytes)} / " +
s"${stageData.shuffleReadRecords}"}
</li>
}}
{if (stageData.hasShuffleWrite) {
<li>
- <strong>Shuffle write: </strong>
+ <strong>Shuffle Write: </strong>
{s"${Utils.bytesToString(stageData.shuffleWriteBytes)} / " +
s"${stageData.shuffleWriteRecords}"}
</li>
}}
{if (stageData.hasBytesSpilled) {
<li>
- <strong>Shuffle spill (memory): </strong>
+ <strong>Shuffle Spill (Memory): </strong>
{Utils.bytesToString(stageData.memoryBytesSpilled)}
</li>
<li>
- <strong>Shuffle spill (disk): </strong>
+ <strong>Shuffle Spill (Disk): </strong>
{Utils.bytesToString(stageData.diskBytesSpilled)}
</li>
}}
@@ -127,10 +127,10 @@ private[ui] class StagePage(parent: StagesTab) extends WebUIPage("stage") {
<div>
<span class="expand-additional-metrics">
<span class="expand-additional-metrics-arrow arrow-closed"></span>
- <strong>Show additional metrics</strong>
+ <a>Show Additional Metrics</a>
</span>
<div class="additional-metrics collapsed">
- <ul style="list-style-type:none">
+ <ul>
<li>
<input type="checkbox" id="select-all-metrics"/>
<span class="additional-metric-title"><em>(De)select All</em></span>
@@ -457,9 +457,9 @@ private[ui] class StagePage(parent: StagesTab) extends WebUIPage("stage") {
val content =
summary ++
- showAdditionalMetrics ++
dagViz ++
maybeExpandDagViz ++
+ showAdditionalMetrics ++
<h4>Summary Metrics for {numCompleted} Completed Tasks</h4> ++
<div>{summaryTable.getOrElse("No tasks have reported metrics yet.")}</div> ++
<h4>Aggregated Metrics by Executor</h4> ++ executorTable.toNodeSeq ++
diff --git a/core/src/main/scala/org/apache/spark/ui/scope/RDDOperationGraph.scala b/core/src/main/scala/org/apache/spark/ui/scope/RDDOperationGraph.scala
index 2b2db9e62b..c7045c98c8 100644
--- a/core/src/main/scala/org/apache/spark/ui/scope/RDDOperationGraph.scala
+++ b/core/src/main/scala/org/apache/spark/ui/scope/RDDOperationGraph.scala
@@ -182,7 +182,7 @@ private[ui] object RDDOperationGraph extends Logging {
if (forJob) {
s"""${node.id} [label="$label" shape="circle" padding="5" labelStyle="font-size: 0"]"""
} else {
- s"""${node.id} [label="$label" padding="5" labelStyle="font-size: 10"]"""
+ s"""${node.id} [label="$label" padding="5" labelStyle="font-size: 12px"]"""
}
}