-
Notifications
You must be signed in to change notification settings - Fork 29k
[SPARK-23501][UI] Refactor AllStagesPage in order to avoid redundant code #20663
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…redundant code
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,46 +19,22 @@ package org.apache.spark.ui.jobs | |
|
|
||
| import javax.servlet.http.HttpServletRequest | ||
|
|
||
| import scala.xml.{Node, NodeSeq} | ||
| import scala.xml.{Attribute, Elem, Node, NodeSeq, Null, Text} | ||
|
|
||
| import org.apache.spark.scheduler.Schedulable | ||
| import org.apache.spark.status.PoolData | ||
| import org.apache.spark.status.api.v1._ | ||
| import org.apache.spark.status.api.v1.StageStatus | ||
| import org.apache.spark.ui.{UIUtils, WebUIPage} | ||
|
|
||
| /** Page showing list of all ongoing and recently finished stages and pools */ | ||
| private[ui] class AllStagesPage(parent: StagesTab) extends WebUIPage("") { | ||
| private val sc = parent.sc | ||
| private lazy val allStages = parent.store.stageList(null) | ||
| private lazy val appSummary = parent.store.appSummary() | ||
| private val subPath = "stages" | ||
| private def isFairScheduler = parent.isFairScheduler | ||
|
|
||
| def render(request: HttpServletRequest): Seq[Node] = { | ||
| val allStages = parent.store.stageList(null) | ||
|
|
||
| val activeStages = allStages.filter(_.status == StageStatus.ACTIVE) | ||
| val pendingStages = allStages.filter(_.status == StageStatus.PENDING) | ||
| val skippedStages = allStages.filter(_.status == StageStatus.SKIPPED) | ||
| val completedStages = allStages.filter(_.status == StageStatus.COMPLETE) | ||
| val failedStages = allStages.filter(_.status == StageStatus.FAILED).reverse | ||
|
|
||
| val numFailedStages = failedStages.size | ||
| val subPath = "stages" | ||
|
|
||
| val activeStagesTable = | ||
| new StageTableBase(parent.store, request, activeStages, "active", "activeStage", | ||
| parent.basePath, subPath, parent.isFairScheduler, parent.killEnabled, false) | ||
| val pendingStagesTable = | ||
| new StageTableBase(parent.store, request, pendingStages, "pending", "pendingStage", | ||
| parent.basePath, subPath, parent.isFairScheduler, false, false) | ||
| val completedStagesTable = | ||
| new StageTableBase(parent.store, request, completedStages, "completed", "completedStage", | ||
| parent.basePath, subPath, parent.isFairScheduler, false, false) | ||
| val skippedStagesTable = | ||
| new StageTableBase(parent.store, request, skippedStages, "skipped", "skippedStage", | ||
| parent.basePath, subPath, parent.isFairScheduler, false, false) | ||
| val failedStagesTable = | ||
| new StageTableBase(parent.store, request, failedStages, "failed", "failedStage", | ||
| parent.basePath, subPath, parent.isFairScheduler, false, true) | ||
|
|
||
| // For now, pool information is only accessible in live UIs | ||
| val pools = sc.map(_.getAllPools).getOrElse(Seq.empty[Schedulable]).map { pool => | ||
| val uiPool = parent.store.asOption(parent.store.pool(pool.name)).getOrElse( | ||
|
|
@@ -67,66 +43,19 @@ private[ui] class AllStagesPage(parent: StagesTab) extends WebUIPage("") { | |
| }.toMap | ||
| val poolTable = new PoolTable(pools, parent) | ||
|
|
||
| val shouldShowActiveStages = activeStages.nonEmpty | ||
| val shouldShowPendingStages = pendingStages.nonEmpty | ||
| val shouldShowCompletedStages = completedStages.nonEmpty | ||
| val shouldShowSkippedStages = skippedStages.nonEmpty | ||
| val shouldShowFailedStages = failedStages.nonEmpty | ||
| val allStatuses = Seq(StageStatus.ACTIVE, StageStatus.PENDING, StageStatus.COMPLETE, | ||
| StageStatus.SKIPPED, StageStatus.FAILED) | ||
|
|
||
| val appSummary = parent.store.appSummary() | ||
| val completedStageNumStr = if (appSummary.numCompletedStages == completedStages.size) { | ||
| s"${appSummary.numCompletedStages}" | ||
| } else { | ||
| s"${appSummary.numCompletedStages}, only showing ${completedStages.size}" | ||
| } | ||
| val (summaries, tables) = allStatuses.map(summaryAndTableForStatus(_, request)).unzip | ||
|
|
||
| val summary: NodeSeq = | ||
| <div> | ||
| <ul class="unstyled"> | ||
| { | ||
| if (shouldShowActiveStages) { | ||
| <li> | ||
| <a href="#active"><strong>Active Stages:</strong></a> | ||
| {activeStages.size} | ||
| </li> | ||
| } | ||
| } | ||
| { | ||
| if (shouldShowPendingStages) { | ||
| <li> | ||
| <a href="#pending"><strong>Pending Stages:</strong></a> | ||
| {pendingStages.size} | ||
| </li> | ||
| } | ||
| } | ||
| { | ||
| if (shouldShowCompletedStages) { | ||
| <li id="completed-summary"> | ||
| <a href="#completed"><strong>Completed Stages:</strong></a> | ||
| {completedStageNumStr} | ||
| </li> | ||
| } | ||
| } | ||
| { | ||
| if (shouldShowSkippedStages) { | ||
| <li id="completed-summary"> | ||
| <a href="#skipped"><strong>Skipped Stages:</strong></a> | ||
| {skippedStages.size} | ||
| </li> | ||
| } | ||
| } | ||
| { | ||
| if (shouldShowFailedStages) { | ||
| <li> | ||
| <a href="#failed"><strong>Failed Stages:</strong></a> | ||
| {numFailedStages} | ||
| </li> | ||
| } | ||
| } | ||
| {summaries.flatten} | ||
| </ul> | ||
| </div> | ||
|
|
||
| var content = summary ++ | ||
| var content: NodeSeq = summary ++ | ||
| { | ||
| if (sc.isDefined && isFairScheduler) { | ||
| <span class="collapse-aggregated-poolTable collapse-table" | ||
|
|
@@ -143,76 +72,105 @@ private[ui] class AllStagesPage(parent: StagesTab) extends WebUIPage("") { | |
| Seq.empty[Node] | ||
| } | ||
| } | ||
| if (shouldShowActiveStages) { | ||
| content ++= | ||
| <span id="active" class="collapse-aggregated-allActiveStages collapse-table" | ||
| onClick="collapseTable('collapse-aggregated-allActiveStages', | ||
| 'aggregated-allActiveStages')"> | ||
| <h4> | ||
| <span class="collapse-table-arrow arrow-open"></span> | ||
| <a>Active Stages ({activeStages.size})</a> | ||
| </h4> | ||
| </span> ++ | ||
| <div class="aggregated-allActiveStages collapsible-table"> | ||
| {activeStagesTable.toNodeSeq} | ||
| </div> | ||
| } | ||
| if (shouldShowPendingStages) { | ||
| content ++= | ||
| <span id="pending" class="collapse-aggregated-allPendingStages collapse-table" | ||
| onClick="collapseTable('collapse-aggregated-allPendingStages', | ||
| 'aggregated-allPendingStages')"> | ||
| <h4> | ||
| <span class="collapse-table-arrow arrow-open"></span> | ||
| <a>Pending Stages ({pendingStages.size})</a> | ||
| </h4> | ||
| </span> ++ | ||
| <div class="aggregated-allPendingStages collapsible-table"> | ||
| {pendingStagesTable.toNodeSeq} | ||
| </div> | ||
|
|
||
| tables.flatten.foreach(content ++= _) | ||
|
||
|
|
||
| UIUtils.headerSparkPage("Stages for All Jobs", content, parent) | ||
| } | ||
|
|
||
| def summaryAndTableForStatus( | ||
| status: StageStatus, | ||
| request: HttpServletRequest): (Option[Elem], Option[NodeSeq]) = { | ||
| val stages = if (status == StageStatus.FAILED) { | ||
| allStages.filter(_.status == status).reverse | ||
| } else { | ||
| allStages.filter(_.status == status) | ||
| } | ||
| if (shouldShowCompletedStages) { | ||
| content ++= | ||
| <span id="completed" class="collapse-aggregated-allCompletedStages collapse-table" | ||
| onClick="collapseTable('collapse-aggregated-allCompletedStages', | ||
| 'aggregated-allCompletedStages')"> | ||
| <h4> | ||
| <span class="collapse-table-arrow arrow-open"></span> | ||
| <a>Completed Stages ({completedStageNumStr})</a> | ||
| </h4> | ||
| </span> ++ | ||
| <div class="aggregated-allCompletedStages collapsible-table"> | ||
| {completedStagesTable.toNodeSeq} | ||
| </div> | ||
|
|
||
| if (stages.isEmpty) { | ||
| (None, None) | ||
| } else { | ||
| val killEnabled = status == StageStatus.ACTIVE && parent.killEnabled | ||
| val isFailedStage = status == StageStatus.FAILED | ||
|
|
||
| val stagesTable = | ||
| new StageTableBase(parent.store, request, stages, tableHeaderID(status), stageTag(status), | ||
| parent.basePath, subPath, parent.isFairScheduler, killEnabled, isFailedStage) | ||
| val stagesSize = stages.size | ||
| (Some(summary(status, stagesSize)), Some(table(status, stagesTable, stagesSize))) | ||
| } | ||
| if (shouldShowSkippedStages) { | ||
| content ++= | ||
| <span id="skipped" class="collapse-aggregated-allSkippedStages collapse-table" | ||
| onClick="collapseTable('collapse-aggregated-allSkippedStages', | ||
| 'aggregated-allSkippedStages')"> | ||
| <h4> | ||
| <span class="collapse-table-arrow arrow-open"></span> | ||
| <a>Skipped Stages ({skippedStages.size})</a> | ||
| </h4> | ||
| </span> ++ | ||
| <div class="aggregated-allSkippedStages collapsible-table"> | ||
| {skippedStagesTable.toNodeSeq} | ||
| </div> | ||
| } | ||
|
|
||
| private def tableHeaderID(status: StageStatus): String = status match { | ||
|
||
| case StageStatus.ACTIVE => "active" | ||
| case StageStatus.COMPLETE => "completed" | ||
| case StageStatus.FAILED => "failed" | ||
| case StageStatus.PENDING => "pending" | ||
| case StageStatus.SKIPPED => "skipped" | ||
| } | ||
|
|
||
| private def stageTag(status: StageStatus): String = status match { | ||
| case StageStatus.ACTIVE => "activeStage" | ||
| case StageStatus.COMPLETE => "completedStage" | ||
| case StageStatus.FAILED => "failedStage" | ||
| case StageStatus.PENDING => "pendingStage" | ||
| case StageStatus.SKIPPED => "skippedStage" | ||
| } | ||
|
|
||
| private def headerDescription(status: StageStatus): String = status match { | ||
| case StageStatus.ACTIVE => "Active" | ||
| case StageStatus.COMPLETE => "Completed" | ||
| case StageStatus.FAILED => "Failed" | ||
| case StageStatus.PENDING => "Pending" | ||
| case StageStatus.SKIPPED => "Skipped" | ||
| } | ||
|
|
||
| private def classSuffix(status: StageStatus): String = status match { | ||
| case StageStatus.ACTIVE => "ActiveStages" | ||
| case StageStatus.COMPLETE => "CompletedStages" | ||
| case StageStatus.FAILED => "FailedStages" | ||
| case StageStatus.PENDING => "PendingStages" | ||
| case StageStatus.SKIPPED => "SkippedStages" | ||
| } | ||
|
|
||
| private def summaryContent(status: StageStatus, size: Int): String = { | ||
| if (status == StageStatus.COMPLETE | ||
| && appSummary.numCompletedStages != size) { | ||
|
||
| s"${appSummary.numCompletedStages}, only showing $size" | ||
| } else { | ||
| s"$size" | ||
| } | ||
| if (shouldShowFailedStages) { | ||
| content ++= | ||
| <span id ="failed" class="collapse-aggregated-allFailedStages collapse-table" | ||
| onClick="collapseTable('collapse-aggregated-allFailedStages', | ||
| 'aggregated-allFailedStages')"> | ||
| <h4> | ||
| <span class="collapse-table-arrow arrow-open"></span> | ||
| <a>Failed Stages ({numFailedStages})</a> | ||
| </h4> | ||
| </span> ++ | ||
| <div class="aggregated-allFailedStages collapsible-table"> | ||
| {failedStagesTable.toNodeSeq} | ||
| </div> | ||
| } | ||
|
|
||
| private def summary(status: StageStatus, size: Int): Elem = { | ||
| val summary = | ||
| <li> | ||
| <a href={s"#${tableHeaderID(status)}"}> | ||
| <strong>{headerDescription(status)} Stages:</strong> | ||
| </a> | ||
| {summaryContent(status, size)} | ||
| </li> | ||
|
|
||
| if (status == StageStatus.COMPLETE) { | ||
| summary % Attribute(None, "id", Text("completed-summary"), Null) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the previous code this was also the case for
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, I realized it while doing the refactor. It was a copy-and-paste mistake, sorry. |
||
| } else { | ||
| summary | ||
| } | ||
| UIUtils.headerSparkPage("Stages for All Jobs", content, parent) | ||
| } | ||
|
|
||
| private def table(status: StageStatus, stagesTable: StageTableBase, size: Int): NodeSeq = { | ||
| val classSuffixStatus = classSuffix(status) | ||
| <span id={tableHeaderID(status)} | ||
| class={s"collapse-aggregated-all$classSuffixStatus collapse-table"} | ||
| onClick={s"collapseTable('collapse-aggregated-all$classSuffixStatus'," + | ||
| s" 'aggregated-all$classSuffixStatus')"}> | ||
| <h4> | ||
| <span class="collapse-table-arrow arrow-open"></span> | ||
| <a>{headerDescription(status)} Stages ({summaryContent(status, size)})</a> | ||
| </h4> | ||
| </span> ++ | ||
| <div class={s"aggregated-all$classSuffixStatus collapsible-table"}> | ||
| {stagesTable.toNodeSeq} | ||
| </div> | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC the class (
AllStagesPage) is only instantiated once, and therendermethod is called for each request. So this won't really work.