-
Notifications
You must be signed in to change notification settings - Fork 29k
[SPARK-4411] [Web UI] Add "kill" link for jobs in the UI #15441
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 all commits
af461cc
7f52874
7a6143a
584240a
25fc0fd
ba16839
a0eee0c
d0e2083
f2519fc
999f83a
c9b2454
b1e77ba
0162c81
12bc267
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -218,7 +218,8 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") { | |
| request: HttpServletRequest, | ||
| tableHeaderId: String, | ||
| jobTag: String, | ||
| jobs: Seq[JobUIData]): Seq[Node] = { | ||
| jobs: Seq[JobUIData], | ||
| killEnabled: Boolean): Seq[Node] = { | ||
| val allParameters = request.getParameterMap.asScala.toMap | ||
| val parameterOtherTable = allParameters.filterNot(_._1.startsWith(jobTag)) | ||
| .map(para => para._1 + "=" + para._2(0)) | ||
|
|
@@ -264,6 +265,7 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") { | |
| parameterOtherTable, | ||
| parent.jobProgresslistener.stageIdToInfo, | ||
| parent.jobProgresslistener.stageIdToData, | ||
| killEnabled, | ||
| currentTime, | ||
| jobIdTitle, | ||
| pageSize = jobPageSize, | ||
|
|
@@ -290,9 +292,12 @@ private[ui] class AllJobsPage(parent: JobsTab) extends WebUIPage("") { | |
| val completedJobs = listener.completedJobs.reverse.toSeq | ||
| val failedJobs = listener.failedJobs.reverse.toSeq | ||
|
|
||
| val activeJobsTable = jobsTable(request, "active", "activeJob", activeJobs) | ||
| val completedJobsTable = jobsTable(request, "completed", "completedJob", completedJobs) | ||
| val failedJobsTable = jobsTable(request, "failed", "failedJob", failedJobs) | ||
| val activeJobsTable = | ||
| jobsTable(request, "active", "activeJob", activeJobs, killEnabled = parent.killEnabled) | ||
| val completedJobsTable = | ||
| jobsTable(request, "completed", "completedJob", completedJobs, killEnabled = false) | ||
| val failedJobsTable = | ||
| jobsTable(request, "failed", "failedJob", failedJobs, killEnabled = false) | ||
|
|
||
| val shouldShowActiveJobs = activeJobs.nonEmpty | ||
| val shouldShowCompletedJobs = completedJobs.nonEmpty | ||
|
|
@@ -483,6 +488,7 @@ private[ui] class JobPagedTable( | |
| parameterOtherTable: Iterable[String], | ||
| stageIdToInfo: HashMap[Int, StageInfo], | ||
| stageIdToData: HashMap[(Int, Int), StageUIData], | ||
| killEnabled: Boolean, | ||
| currentTime: Long, | ||
| jobIdTitle: String, | ||
| pageSize: Int, | ||
|
|
@@ -586,12 +592,30 @@ private[ui] class JobPagedTable( | |
| override def row(jobTableRow: JobTableRowData): Seq[Node] = { | ||
| val job = jobTableRow.jobData | ||
|
|
||
| val killLink = if (killEnabled) { | ||
|
Member
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. Hm, does this work? there's no 'else' so this becomes an
Member
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. Nice catch, since it's supposed to be html I'll add a |
||
| val confirm = | ||
| s"if (window.confirm('Are you sure you want to kill job ${job.jobId} ?')) " + | ||
| "{ this.parentNode.submit(); return true; } else { return false; }" | ||
| // SPARK-6846 this should be POST-only but YARN AM won't proxy POST | ||
| /* | ||
| val killLinkUri = s"$basePathUri/jobs/job/kill/" | ||
| <form action={killLinkUri} method="POST" style="display:inline"> | ||
| <input type="hidden" name="id" value={job.jobId.toString}/> | ||
| <a href="#" onclick={confirm} class="kill-link">(kill)</a> | ||
| </form> | ||
| */ | ||
| val killLinkUri = s"$basePath/jobs/job/kill/?id=${job.jobId}" | ||
| <a href={killLinkUri} onclick={confirm} class="kill-link">(kill)</a> | ||
| } else { | ||
| Seq.empty | ||
| } | ||
|
|
||
| <tr id={"job-" + job.jobId}> | ||
| <td> | ||
| {job.jobId} {job.jobGroup.map(id => s"($id)").getOrElse("")} | ||
| </td> | ||
| <td> | ||
| {jobTableRow.jobDescription} | ||
| {jobTableRow.jobDescription} {killLink} | ||
| <a href={jobTableRow.detailUrl} class="name-link">{jobTableRow.lastStageName}</a> | ||
| </td> | ||
| <td> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,8 @@ | |
|
|
||
| package org.apache.spark.ui.jobs | ||
|
|
||
| import javax.servlet.http.HttpServletRequest | ||
|
|
||
| import org.apache.spark.scheduler.SchedulingMode | ||
| import org.apache.spark.ui.{SparkUI, SparkUITab} | ||
|
|
||
|
|
@@ -35,4 +37,19 @@ private[ui] class JobsTab(parent: SparkUI) extends SparkUITab(parent, "jobs") { | |
|
|
||
| attachPage(new AllJobsPage(this)) | ||
| attachPage(new JobPage(this)) | ||
|
|
||
| def handleKillRequest(request: HttpServletRequest): Unit = { | ||
| if (killEnabled && parent.securityManager.checkModifyPermissions(request.getRemoteUser)) { | ||
| val jobId = Option(request.getParameter("id")).map(_.toInt) | ||
| jobId.foreach { id => | ||
| if (jobProgresslistener.activeJobs.contains(id)) { | ||
| sc.foreach(_.cancelJob(id)) | ||
| // Do a quick pause here to give Spark time to kill the job so it shows up as | ||
| // killed after the refresh. Note that this will block the serving thread so the | ||
| // time should be limited in duration. | ||
| Thread.sleep(100) | ||
|
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. You're doing the sleep here even if
Member
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. That was my thinking, that in the case of sc being None that this code shouldn't be able to be called. Plus by moving the sleep into the if statement it at least only run when an actual kill is attempted.
Member
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. Hm, in both this and the kill-stage endpoint I wonder why we have a 'terminate' flag at all. What would it mean to call this with terminate=false? it seems like this can be checked once, if at all, at the start of the method. Or just removed, if I'm not missing something.
Member
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. I just did some looking into the git history and thats left over from when there wasn't a separate |
||
| } | ||
| } | ||
|
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. Creating an val jobId = Option(request.getParameter("id"))
jobId.foreach { id =>
if (killFlag && jobProgresslistener.activeJobs.contains(id)) {
sc.get.cancelJob(id)
}
}
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. Similarly, there is no need for sc.foreach(_.cancelJob(id))
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. And if the Job isn't actually going to be canceled, then there is no need to delay the page refresh (which I'm not entirely happy with, but I'm not going to try to resolve that issue right now.) So... sc.foreach { sparkContext =>
sparkContext.cancelJob(id)
Thread.sleep(100)
}And we better make that val jobId = Option(request.getParameter("id")).map(_.toInt)
Member
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. I copied this code from |
||
| } | ||
| } | ||
| } | ||
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.
See #15603 which reminds me that these two tabs don't need to be members. They can be locals. You can make the change for both here.