Skip to content

Commit 71281fa

Browse files
author
Andrew Or
committed
Embed the viz in the UI in a toggleable manner
1 parent 8dd5af2 commit 71281fa

File tree

6 files changed

+61
-21
lines changed

6 files changed

+61
-21
lines changed

core/src/main/resources/org/apache/spark/ui/static/spark-stage-viz.js

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,31 @@
1515
* limitations under the License.
1616
*/
1717

18+
var stageVizIsRendered = false
19+
20+
/*
21+
* Render or remove the stage visualization on the UI.
22+
* This assumes that the visualization is stored in the "#viz-graph" element.
23+
*/
24+
function toggleStageViz() {
25+
$(".expand-visualization-arrow").toggleClass('arrow-closed');
26+
$(".expand-visualization-arrow").toggleClass('arrow-open');
27+
var shouldRender = $(".expand-visualization-arrow").hasClass("arrow-open");
28+
if (shouldRender) {
29+
// If the viz is already rendered, just show it
30+
if (stageVizIsRendered) {
31+
$("#viz-graph").show();
32+
} else {
33+
renderStageViz();
34+
stageVizIsRendered = true;
35+
}
36+
} else {
37+
// Instead of emptying the element once and for all, cache it for use
38+
// again later in case we want to expand the visualization again
39+
$("#viz-graph").hide();
40+
}
41+
}
42+
1843
/*
1944
* Render a DAG that describes the RDDs for a given stage.
2045
*
@@ -27,6 +52,14 @@
2752
*/
2853
function renderStageViz() {
2954

55+
// If there is not a dot file to render, report error
56+
if (d3.select("#viz-dot-file").empty()) {
57+
d3.select("#viz-graph")
58+
.append("div")
59+
.text("No visualization information available for this stage.");
60+
return;
61+
}
62+
3063
// Parse the dot file and render it in an SVG
3164
var dot = d3.select("#viz-dot-file").text();
3265
var escaped_dot = dot
@@ -35,7 +68,7 @@ function renderStageViz() {
3568
.replace(/"/g, "\"");
3669
var g = graphlibDot.read(escaped_dot);
3770
var render = new dagreD3.render();
38-
var svg = d3.select("#viz-graph");
71+
var svg = d3.select("#viz-graph").append("svg");
3972
svg.call(render, g);
4073

4174
// Set the appropriate SVG dimensions to ensure that all elements are displayed
@@ -88,7 +121,6 @@ function renderStageViz() {
88121
var endY = toFloat(svg.style("height")) + svgMargin;
89122
var newViewBox = startX + " " + startY + " " + endX + " " + endY;
90123
svg.attr("viewBox", newViewBox);
91-
92124
}
93125

94126
/* Helper method to convert attributes to numeric values. */

core/src/main/resources/org/apache/spark/ui/static/webui.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ pre {
145145
border: none;
146146
}
147147

148-
span.expand-additional-metrics {
148+
span.expand-additional-metrics, span.expand-visualization {
149149
cursor: pointer;
150150
}
151151

core/src/main/scala/org/apache/spark/ui/SparkUI.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ private[spark] object SparkUI {
147147
val storageStatusListener = new StorageStatusListener
148148
val executorsListener = new ExecutorsListener(storageStatusListener)
149149
val storageListener = new StorageListener(storageStatusListener)
150-
val visualizationListener = new VisualizationListener
150+
val visualizationListener = new VisualizationListener(conf)
151151

152152
listenerBus.addListener(environmentListener)
153153
listenerBus.addListener(storageStatusListener)

core/src/main/scala/org/apache/spark/ui/jobs/StagePage.scala

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,29 @@ private[ui] class StagePage(parent: StagesTab) extends WebUIPage("stage") {
3737
private val vizListener = parent.vizListener
3838

3939
/**
40-
* Return a DOM element that contains an RDD DAG visualization for this stage.
41-
* If there is no visualization information for this stage, return an empty element.
40+
* Return a DOM element containing the "Show Visualization" toggle that, if enabled,
41+
* renders the visualization for the stage. If there is no visualization information
42+
* available for this stage, an appropriate message is displayed to the user.
4243
*/
43-
private def renderViz(stageId: Int): Seq[Node] = {
44+
private def showVizElement(stageId: Int): Seq[Node] = {
4445
val graph = vizListener.getVizGraph(stageId)
45-
if (graph.isEmpty) {
46-
Seq.empty
47-
} else {
48-
<div id="viz-dot-file" style="display:none">
49-
{VizGraph.makeDotFile(graph.get)}
50-
</div>
51-
<svg id="viz-graph"></svg>
52-
<script type="text/javascript">renderStageViz()</script>
53-
}
46+
<div>
47+
<span class="expand-visualization" onclick="render();">
48+
<span class="expand-visualization-arrow arrow-closed"></span>
49+
<strong>Show Visualization</strong>
50+
</span>
51+
<div id="viz-graph"></div>
52+
<script type="text/javascript">
53+
{Unparsed(s"function render() { toggleStageViz(); }")}
54+
</script>
55+
{
56+
if (graph.isDefined) {
57+
<div id="viz-dot-file" style="display:none">
58+
{VizGraph.makeDotFile(graph.get)}
59+
</div>
60+
}
61+
}
62+
</div>
5463
}
5564

5665
def render(request: HttpServletRequest): Seq[Node] = {
@@ -75,8 +84,6 @@ private[ui] class StagePage(parent: StagesTab) extends WebUIPage("stage") {
7584
s"Details for Stage $stageId (Attempt $stageAttemptId)", content, parent)
7685
}
7786

78-
val viz: Seq[Node] = renderViz(stageId)
79-
8087
val stageData = stageDataOption.get
8188
val tasks = stageData.taskData.values.toSeq.sortBy(_.taskInfo.launchTime)
8289

@@ -453,9 +460,9 @@ private[ui] class StagePage(parent: StagesTab) extends WebUIPage("stage") {
453460
if (accumulables.size > 0) { <h4>Accumulators</h4> ++ accumulableTable } else Seq()
454461

455462
val content =
456-
viz ++
457463
summary ++
458464
showAdditionalMetrics ++
465+
showVizElement(stageId) ++
459466
<h4>Summary Metrics for {numCompleted} Completed Tasks</h4> ++
460467
<div>{summaryTable.getOrElse("No tasks have reported metrics yet.")}</div> ++
461468
<h4>Aggregated Metrics by Executor</h4> ++ executorTable.toNodeSeq ++

core/src/main/scala/org/apache/spark/ui/viz/VisualizationListener.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ package org.apache.spark.ui.viz
1919

2020
import scala.collection.mutable
2121

22+
import org.apache.spark.SparkConf
2223
import org.apache.spark.scheduler._
2324
import org.apache.spark.ui.SparkUI
2425

2526
/**
2627
* A SparkListener that constructs a graph of the RDD DAG for each stage.
2728
* This graph will be used for rendering visualization in the UI later.
2829
*/
29-
private[ui] class VisualizationListener extends SparkListener {
30+
private[ui] class VisualizationListener(conf: SparkConf) extends SparkListener {
3031

3132
// A list of stage IDs to track the order in which stages are inserted
3233
private val stageIds = new mutable.ArrayBuffer[Int]

core/src/main/scala/org/apache/spark/ui/viz/VizGraph.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ private[ui] object VizGraph {
7171

7272
// Populate nodes, edges, and scopes
7373
rddInfos.foreach { rdd =>
74-
val node = nodes.getOrElseUpdate(rdd.id, VizNode(rdd.id, rdd.name, rdd.isCached))
74+
val node = nodes.getOrElseUpdate(rdd.id, VizNode(rdd.id, rdd.name))
7575
edges ++= rdd.parentIds.map { parentId => VizEdge(parentId, rdd.id) }
7676

7777
if (rdd.scope == null) {

0 commit comments

Comments
 (0)