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 a896f7f054e27..35ef14e5aaf1a 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 @@ -168,3 +168,15 @@ tr.corresponding-item-hover>td, tr.corresponding-item-hover>th { span.expand-application-timeline, span.expand-job-timeline { cursor: pointer; } + +.control-panel input+span { + cursor: pointer; +} + +.vis.timeline .item.range .content { + position: unset; +} + +.vis.timeline .item .tooltip-inner { + max-width: unset !important; +} 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 80b008e7c3263..e4a891d47f035 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 @@ -35,27 +35,34 @@ function drawApplicationTimeline(groupArray, eventObjArray, startTime) { applicationTimeline.setItems(items); setupZoomable("#application-timeline-zoom-lock", applicationTimeline); - - $(".item.range.job.application-timeline-object").each(function() { - var getJobId = function(baseElem) { - var jobIdText = $($(baseElem).find(".application-timeline-content")[0]).text(); - var jobId = jobIdText.match("\\(Job (\\d+)\\)")[1]; - return jobId; - }; - - $(this).click(function() { - window.location.href = "job/?id=" + getJobId(this); + setupExecutorEventAction(); + + function setupJobEventAction() { + $(".item.range.job.application-timeline-object").each(function() { + var getJobId = function(baseElem) { + var jobIdText = $($(baseElem).find(".application-timeline-content")[0]).text(); + var jobId = jobIdText.match("\\(Job (\\d+)\\)")[1]; + return jobId; + }; + + $(this).click(function() { + window.location.href = "job/?id=" + getJobId(this); + }); + + $(this).hover( + function() { + $("#job-" + getJobId(this)).addClass("corresponding-item-hover"); + $($(this).find("div.application-timeline-content")[0]).tooltip("show"); + }, + function() { + $("#job-" + getJobId(this)).removeClass("corresponding-item-hover"); + $($(this).find("div.application-timeline-content")[0]).tooltip("hide"); + } + ); }); + } - $(this).hover( - function() { - $("#job-" + getJobId(this)).addClass("corresponding-item-hover"); - }, - function() { - $("#job-" + getJobId(this)).removeClass("corresponding-item-hover"); - } - ); - }); + setupJobEventAction(); $("span.expand-application-timeline").click(function() { $("#application-timeline").toggleClass('collapsed'); @@ -86,36 +93,43 @@ function drawJobTimeline(groupArray, eventObjArray, startTime) { jobTimeline.setItems(items); setupZoomable("#job-timeline-zoom-lock", jobTimeline); + setupExecutorEventAction(); - $(".item.range.stage.job-timeline-object").each(function() { - var getStageIdAndAttempt = function(baseElem) { - var stageIdText = $($(baseElem).find(".job-timeline-content")[0]).text(); - var stageIdAndAttempt = stageIdText.match("\\(Stage (\\d+\\.\\d+)\\)")[1].split("."); - return stageIdAndAttempt; - }; - - $(this).click(function() { - var idAndAttempt = getStageIdAndAttempt(this); - var id = idAndAttempt[0]; - var attempt = idAndAttempt[1]; - window.location.href = "../../stages/stage/?id=" + id + "&attempt=" + attempt; - }); + function setupStageEventAction() { + $(".item.range.stage.job-timeline-object").each(function() { + var getStageIdAndAttempt = function(baseElem) { + var stageIdText = $($(baseElem).find(".job-timeline-content")[0]).text(); + var stageIdAndAttempt = stageIdText.match("\\(Stage (\\d+\\.\\d+)\\)")[1].split("."); + return stageIdAndAttempt; + }; - $(this).hover( - function() { + $(this).click(function() { var idAndAttempt = getStageIdAndAttempt(this); var id = idAndAttempt[0]; var attempt = idAndAttempt[1]; - $("#stage-" + id + "-" + attempt).addClass("corresponding-item-hover"); - }, - function() { - var idAndAttempt = getStageIdAndAttempt(this); - var id = idAndAttempt[0]; - var attempt = idAndAttempt[1]; - $("#stage-" + id + "-" + attempt).removeClass("corresponding-item-hover"); - } - ); - }); + window.location.href = "../../stages/stage/?id=" + id + "&attempt=" + attempt; + }); + + $(this).hover( + function() { + var idAndAttempt = getStageIdAndAttempt(this); + var id = idAndAttempt[0]; + var attempt = idAndAttempt[1]; + $("#stage-" + id + "-" + attempt).addClass("corresponding-item-hover"); + $($(this).find("div.job-timeline-content")[0]).tooltip("show"); + }, + function() { + var idAndAttempt = getStageIdAndAttempt(this); + var id = idAndAttempt[0]; + var attempt = idAndAttempt[1]; + $("#stage-" + id + "-" + attempt).removeClass("corresponding-item-hover"); + $($(this).find("div.job-timeline-content")[0]).tooltip("hide"); + } + ); + }); + } + + setupStageEventAction(); $("span.expand-job-timeline").click(function() { $("#job-timeline").toggleClass('collapsed'); @@ -126,6 +140,19 @@ function drawJobTimeline(groupArray, eventObjArray, startTime) { }); } +function setupExecutorEventAction() { + $(".item.box.executor").each(function () { + $(this).hover( + function() { + $($(this).find(".executor-event-content")[0]).tooltip("show"); + }, + function() { + $($(this).find(".executor-event-content")[0]).tooltip("hide"); + } + ); + }); +} + function setupZoomable(id, timeline) { $(id + '>input[type="checkbox"]').click(function() { if (this.checked) { 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 123e376668a33..a7ea12b1655fe 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 @@ -88,17 +88,18 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") { | 'group': 'jobs', | 'start': new Date(${submissionTime}), | 'end': new Date(${completionTime}), - | 'content': '
' + - | '${displayJobDescription} (Job ${jobId})
', - | 'title': '${displayJobDescription} (Job ${jobId})\\nStatus: ${status}\\n' + - | 'Submission Time: ${UIUtils.formatDate(new Date(submissionTime))}' + - | '${ - if (status != JobExecutionStatus.RUNNING) { - s"""\\nCompletion Time: ${UIUtils.formatDate(new Date(completionTime))}""" - } else { - "" - } - }' + | 'content': '
Completion Time: ${UIUtils.formatDate(new Date(completionTime))}""" + } else { + "" + } + }">' + + | '${displayJobDescription} (Job ${jobId})
' |} """.stripMargin jobEventJsonAsStr @@ -109,38 +110,44 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") { val events = ListBuffer[String]() executorUIDatas.foreach { case (executorId, event) => - val addedEvent = - s""" - |{ - | 'className': 'executor added', - | 'group': 'executors', - | 'start': new Date(${event.startTime}), - | 'content': '
Executor ${executorId} added
', - | 'title': 'Added at ${UIUtils.formatDate(new Date(event.startTime))}' - |} - """.stripMargin - events += addedEvent - - if (event.finishTime.isDefined) { - val removedEvent = + val addedEvent = s""" + |{ + | 'className': 'executor added', + | 'group': 'executors', + | 'start': new Date(${event.startTime}), + | 'content': '
Executor ${executorId} added
' + |} + """.stripMargin + events += addedEvent + + if (event.finishTime.isDefined) { + val removedEvent = + s""" |{ | 'className': 'executor removed', | 'group': 'executors', | 'start': new Date(${event.finishTime.get}), - | 'content': '
Executor ${executorId} removed
', - | 'title': 'Removed at ${UIUtils.formatDate(new Date(event.finishTime.get))}' + + | 'content': '
Reason: ${event.finishReason.get}""" } else { "" } - }' + }"' + + | 'data-html="true">Executor ${executorId} removed
' |} """.stripMargin - events += removedEvent - } + events += removedEvent + } } events.toSeq } 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 e06cbfaadbd3a..dd968e124738e 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 @@ -48,10 +48,10 @@ private[ui] class JobPage(parent: JobsTab) extends WebUIPage("job") {
- Executor Added + Added - Executor Removed + Removed
.toString.filter(_ != '\n') private def makeStageEvent(stageInfos: Seq[StageInfo]): Seq[String] = { @@ -69,17 +69,19 @@ private[ui] class JobPage(parent: JobsTab) extends WebUIPage("job") { | 'group': 'stages', | 'start': new Date(${submissionTime}), | 'end': new Date(${completionTime}), - | 'content': '
' + + | 'content': '
Completion Time: ${UIUtils.formatDate(new Date(completionTime))}""" + } else { + "" + } + }">' + | '${name} (Stage ${stageId}.${attemptId})
', - | 'title': '${name} (Stage ${stageId}.${attemptId})\\nStatus: ${status.toUpperCase}\\n' + - | 'Submission Time: ${UIUtils.formatDate(new Date(submissionTime))}' + - | '${ - if (status != "running") { - s"""\\nCompletion Time: ${UIUtils.formatDate(new Date(completionTime))}""" - } else { - "" - } - }' |} """.stripMargin } @@ -89,38 +91,44 @@ private[ui] class JobPage(parent: JobsTab) extends WebUIPage("job") { val events = ListBuffer[String]() executorUIDatas.foreach { case (executorId, event) => - val addedEvent = - s""" - |{ - | 'className': 'executor added', - | 'group': 'executors', - | 'start': new Date(${event.startTime}), - | 'content': '
Executor ${executorId} added
', - | 'title': 'Added at ${UIUtils.formatDate(new Date(event.startTime))}' - |} - """.stripMargin - events += addedEvent - - if (event.finishTime.isDefined) { - val removedEvent = + val addedEvent = s""" + |{ + | 'className': 'executor added', + | 'group': 'executors', + | 'start': new Date(${event.startTime}), + | 'content': '
Executor ${executorId} added
' + |} + """.stripMargin + events += addedEvent + + if (event.finishTime.isDefined) { + val removedEvent = + s""" |{ | 'className': 'executor removed', | 'group': 'executors', | 'start': new Date(${event.finishTime.get}), - | 'content': '
Executor ${executorId} removed
', - | 'title': 'Removed at ${UIUtils.formatDate(new Date(event.finishTime.get))}' + + | 'content': '
Reason: ${event.finishReason.get}""" } else { "" } - }' + }"' + + | 'data-html="true">Executor ${executorId} removed
' |} """.stripMargin - events += removedEvent - } + events += removedEvent + } } events.toSeq } @@ -128,7 +136,7 @@ private[ui] class JobPage(parent: JobsTab) extends WebUIPage("job") { private def makeTimeline( stages: Seq[StageInfo], executors: HashMap[String, ExecutorUIData], - jobStartTime: Long): Seq[Node] = { + appStartTime: Long): Seq[Node] = { val stageEventJsonAsStrSeq = makeStageEvent(stages) val executorsJsonAsStrSeq = makeExecutorEvent(executors) @@ -163,7 +171,7 @@ private[ui] class JobPage(parent: JobsTab) extends WebUIPage("job") {
++ } @@ -293,10 +301,10 @@ private[ui] class JobPage(parent: JobsTab) extends WebUIPage("job") { var content = summary - val jobStartTime = jobData.submissionTime.get + val appStartTime = listener.startTime val executorListener = parent.executorListener content ++= makeTimeline(activeStages ++ completedStages ++ failedStages, - executorListener.executorIdToData, jobStartTime) + executorListener.executorIdToData, appStartTime) if (shouldShowActiveStages) { content ++=

Active Stages ({activeStages.size})

++