Skip to content

[SPARK-20242][Web UI] Add spark.ui.stopDelay #17551

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion core/src/main/resources/org/apache/spark/ui/static/webui.css
Original file line number Diff line number Diff line change
Expand Up @@ -250,4 +250,9 @@ a.expandbutton {

.table-cell-width-limited td {
max-width: 600px;
}
}

.stop-delayed {
color: red;
margin-bottom: 1em;
}
57 changes: 52 additions & 5 deletions core/src/main/scala/org/apache/spark/ui/SparkUI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,22 @@
package org.apache.spark.ui

import java.util.{Date, ServiceLoader}
import java.util.concurrent.atomic.AtomicReference

import scala.collection.JavaConverters._

import org.apache.spark.{SecurityManager, SparkConf, SparkContext}
import org.apache.spark.internal.Logging
import org.apache.spark.scheduler._
import org.apache.spark.status.api.v1.{ApiRootResource, ApplicationAttemptInfo, ApplicationInfo,
UIRoot}
import org.apache.spark.status.api.v1.{ApiRootResource, ApplicationAttemptInfo, ApplicationInfo, UIRoot}
import org.apache.spark.storage.StorageStatusListener
import org.apache.spark.ui.JettyUtils._
import org.apache.spark.ui.env.{EnvironmentListener, EnvironmentTab}
import org.apache.spark.ui.exec.{ExecutorsListener, ExecutorsTab}
import org.apache.spark.ui.jobs.{JobProgressListener, JobsTab, StagesTab}
import org.apache.spark.ui.scope.RDDOperationGraphListener
import org.apache.spark.ui.storage.{StorageListener, StorageTab}
import org.apache.spark.util.Utils
import org.apache.spark.util.{SignalUtils, Utils}

/**
* Top level user interface for a Spark application.
Expand All @@ -58,6 +58,9 @@ private[spark] class SparkUI private (

val killEnabled = sc.map(_.conf.getBoolean("spark.ui.killEnabled", true)).getOrElse(false)

private val stopDelayThread = new AtomicReference[Thread]()
private val stopDelaySeconds = conf.getOption("spark.ui.stopDelay").map(Utils.timeStringAsSeconds)

var appId: String = _

private var streamingJobProgressListener: Option[SparkListener] = None
Expand All @@ -80,6 +83,9 @@ private[spark] class SparkUI private (
attachHandler(createRedirectHandler(
"/stages/stage/kill", "/stages/", stagesTab.handleKillRequest,
httpMethods = Set("GET", "POST")))
attachHandler(createRedirectHandler(
"/proceed-with-ui-stop", "/jobs/", _ => proceedWithStop("web UI action"),
httpMethods = Set("GET", "POST")))
}
initialize()

Expand All @@ -93,10 +99,51 @@ private[spark] class SparkUI private (
appId = id
}

def activeStopDelay: Option[Long] =
stopDelaySeconds.filter(_ => stopDelayThread.get() != null)

/** Stop the server behind this web interface. Only valid after bind(). */
override def stop() {
super.stop()
logInfo(s"Stopped Spark web UI at $webUrl")

def reallyStop(): Unit = {
super.stop()
logInfo(s"Stopped Spark web UI at $webUrl")
}

stopDelaySeconds match {
case Some(stopDelaySeconds) =>
if (stopDelayThread.compareAndSet(null, Thread.currentThread())) {
delayStop(stopDelaySeconds)
reallyStop()
}
case None =>
reallyStop()
}
}

private def delayStop(stopDelaySeconds: Long): Unit = {

SignalUtils.register("INT") {
proceedWithStop(s"received SIGINT")
true
}

logInfo(s"Delaying stop of Spark web UI at $webUrl " +
s"for $stopDelaySeconds seconds as set in spark.ui.stopDelay. " +
s"Stopping can be resumed immediately in the Spark UI, or by sending SIGINT.")

try Thread.sleep(1000 * stopDelaySeconds)
catch { case _: InterruptedException => }

stopDelayThread.set(null)
Thread.interrupted()
}

def proceedWithStop(reason: String): Unit = {
Option(stopDelayThread.get).foreach { stopThread =>
logInfo(s"Proceeding to stop Spark web UI ($reason)")
stopThread.interrupt()
}
}

def getSparkUI(appId: String): Option[SparkUI] = {
Expand Down
18 changes: 18 additions & 0 deletions core/src/main/scala/org/apache/spark/ui/jobs/AllJobsPage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,24 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") {

val summary: NodeSeq =
<div>
{
parent.activeStopDelay match {
case None =>
case Some(delay) =>
val confirm =
s"if (window.confirm('Are you sure you allow the Spark UI to stop?')) " +
"{ this.parentNode.submit(); return true; } else { return false; }"
val action = s"${parent.basePath}/proceed-with-ui-stop"
<div class="stop-delayed">
<strong>The Spark UI has been asked to stop, but that is being delayed for
{delay}s.
<form action={action} method="POST" style="display:inline">
<a href="#" onclick={confirm}>(stop now)</a>
</form>
</strong>
</div>
}
}
<ul class="unstyled">
<li>
<strong>User:</strong>
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/scala/org/apache/spark/ui/jobs/JobsTab.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ private[ui] class JobsTab(parent: SparkUI) extends SparkUITab(parent, "jobs") {
val executorListener = parent.executorsListener
val operationGraphListener = parent.operationGraphListener

def activeStopDelay: Option[Long] =
parent.activeStopDelay

def isFairScheduler: Boolean =
jobProgresslistener.schedulingMode == Some(SchedulingMode.FAIR)

Expand Down
7 changes: 7 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,13 @@ Apart from these, the following properties are also available, and may be useful
finished.
</td>
</tr>
<tr>
<td><code>spark.ui.stopDelay</code></td>
<td>(none)</td>
<td>
A period of time to delay the stopping of the Web UI, so that it can be inspected after the application finishes.
</td>
</tr>
<tr>
<td><code>spark.ui.enabled</code></td>
<td>true</td>
Expand Down