diff options
author | Matei Zaharia <matei@databricks.com> | 2015-05-08 14:41:42 -0400 |
---|---|---|
committer | Matei Zaharia <matei@databricks.com> | 2015-05-08 14:41:42 -0400 |
commit | a1ec08f7edc8d956afcfbb92d10b26b7619486e8 (patch) | |
tree | 9b1ce3161ea21e205592ca7ba3c07667b78d2941 /core/src/main/scala/org | |
parent | 35d6a99cbe3f67da5d56888e63baf9bc69f3de91 (diff) | |
download | spark-a1ec08f7edc8d956afcfbb92d10b26b7619486e8.tar.gz spark-a1ec08f7edc8d956afcfbb92d10b26b7619486e8.tar.bz2 spark-a1ec08f7edc8d956afcfbb92d10b26b7619486e8.zip |
[SPARK-7298] Harmonize style of new visualizations
- Colors on the timeline now match the rest of the UI
- The expandable buttons to show timeline view, DAG, etc are now more visible
- Timeline text is smaller
- DAG visualization text and colors are more consistent throughout
- Fix some JavaScript style issues
- Various small fixes throughout (e.g. inconsistent capitalization, some confusing names, HTML escaping, etc)
Author: Matei Zaharia <matei@databricks.com>
Closes #5942 from mateiz/ui and squashes the following commits:
def38d0 [Matei Zaharia] Add some tooltips
4c5a364 [Matei Zaharia] Reduce stage and rank separation slightly
43dcbe3 [Matei Zaharia] Some updates to DAG
fac734a [Matei Zaharia] tweaks
6a6705d [Matei Zaharia] More fixes
67629f5 [Matei Zaharia] Various small tweaks
Diffstat (limited to 'core/src/main/scala/org')
7 files changed, 70 insertions, 51 deletions
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"]""" } } |