Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
19 changes: 19 additions & 0 deletions core/src/main/scala/org/apache/spark/SparkContext.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2249,6 +2249,25 @@ class SparkContext(config: SparkConf) extends Logging {
dagScheduler.cancelStage(stageId, None)
}

/**
* Kill a given task. It will be retried.
*
* @param taskId the task ID to kill
*/
def killTask(taskId: Long): Unit = {
killTask(taskId, "cancelled")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"killed by user via SparkContext.killTask"? These things can be hard to debug when they're unexpected and "cancelled" isn't very helpful

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also why not make this a default argument below and then have just one method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only issue here is that the UI is not great at rendering long strings (it tends to cut them off). I'd prefer to keep it something concise for now.

}

/**
* Kill a given task. It will be retried.
*
* @param taskId the task ID to kill
* @param reason the reason for killing the task, which should be a short string
*/
def killTask(taskId: Long, reason: String): Unit = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm i don't think we should automatically retry just by providing a reason. Perhaps this

def killTask(taskId: Long, reason: String): Unit

def killTaskAndRetry(taskId: Long, reason: String): Unit

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thing for the lower level dag scheduler api.

Copy link
Contributor Author

@ericl ericl Mar 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it turns out there's not a good reason to not retry. The task will get retried anyways eventually unless the stage is cancelled. The previous code seems to be just a performance optimization to not call reviveOffers for speculative task completions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ok. for some reason i read it as killTask(long) is kill without retry, and killTask(long, string) is with.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about calling it "killAndRescheduleTask" then? Otherwise kill is a little misleading -- since where we use it elsewhere (to kill a stage) it implies no retry

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am unclear about the expectations from the api.

  • What is the expectation when a task is being killed.
    • Is it specifically for the task being referenced; or all attempts of the task ?
    • If latter, do we discard already succeeded tasks ?
    • "killAndRescheduleTask" implies it will be rescheduled - which might not occur in case this was a speculative task (or already completed) : would be good to clarify.
  • Is this expected to be exposed via the UI ?
    • How is it to be leveraged (if not via UI) ?
  • How to get to taskId - via SparkListener.onTaskStart ?
    • Any other means ? (IIRC no other way to get at it, but good to clarify for api user)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given Mridul's points maybe killTaskAttempt is a better name? IMO specifying "attempt" in the name makes it sound less permanent than killTask (which to me sounds like it won't be retried)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the expectation when a task is being killed.
Is it specifically for the task being referenced; or all attempts of the task ?

The current task attempt (which is uniquely identifier by the task id). I updated the docs as suggested here.

"killAndRescheduleTask" implies it will be rescheduled - which might not occur in case this was a speculative task (or already completed) : would be good to clarify.

Went with killTaskAttempt.

Is this expected to be exposed via the UI ?
How is it to be leveraged (if not via UI) ?

For now, you can look at the Spark UI, find the task ID, and call killTaskAttempt on it. It would be nice to have this as a button on the executor page in a follow-up. You can also have a listener that kills tasks as suggested.

dagScheduler.killTask(taskId, reason)
}

/**
* Clean a closure to make it ready to serialized and send to tasks
* (removes unreferenced variables in $outer's, updates REPL variables)
Expand Down
7 changes: 5 additions & 2 deletions core/src/main/scala/org/apache/spark/TaskEndReason.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ sealed trait TaskFailedReason extends TaskEndReason {
* on was killed.
*/
def countTowardsTaskFailures: Boolean = true

/** Whether this task should be retried by the scheduler. */
def shouldRetry: Boolean = false
}

/**
Expand Down Expand Up @@ -212,8 +215,8 @@ case object TaskResultLost extends TaskFailedReason {
* Task was killed intentionally and needs to be rescheduled.
*/
@DeveloperApi
case object TaskKilled extends TaskFailedReason {
override def toErrorString: String = "TaskKilled (killed intentionally)"
case class TaskKilled(reason: String, override val shouldRetry: Boolean) extends TaskFailedReason {
Copy link
Contributor

@JoshRosen JoshRosen Mar 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a little weird for shouldRetry to be part of this interface: whether a killed task should be retried is already handled by existing scheduling logic and it's a little confusing to be setting it on a per-task basis rather than, say, having different events which are used to distinguish between speculative tasks being killed and jobs being canceled/aborted. Basically, it's confusing to me because this isn't the source-of-truth on whether we can/will re-try and its purpose here is therefore a bit unclear to me.

As discussed offline, I think that we may be able to update the logic in TaskSchedulerImpl.handleFailedTask (the only place which reads this field) in order to eliminate the need for this field in this class.

override def toErrorString: String = s"TaskKilled ($reason)"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this was part of DeveloperApi, what is the impact of making this change ?
In JsonProtocol, in mocked user code ?

If it does introduce backward incompatible changes, is there a way to mitigate this ?
Perhaps make TaskKilled a class with apply/unapply/serde/toString (essentially all that case class provides) and case object with apply with default reason = null (and logged when used as deprecated) ?

Copy link
Contributor Author

@ericl ericl Mar 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unfortunately not backwards compatible. I've looked into this, and the issue seems to be that we are converting a case object into a case class. If TaskKilled was a case class to start with, compatibility might have been possible (e.g. implement equals() ignoring reason), but as is you would break scala match statements and possibly other things depending on the user code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is unfortunate, but looks like it cant be helped if we need this feature.
Probably something to keep in mind with future use of case objects !

Thx for clarifying.

override def countTowardsTaskFailures: Boolean = false
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,11 @@ private[spark] class CoarseGrainedExecutorBackend(
executor.launchTask(this, taskDesc)
}

case KillTask(taskId, _, interruptThread) =>
case KillTask(taskId, _, interruptThread, reason, retryTask) =>
if (executor == null) {
exitExecutor(1, "Received KillTask command but executor was null")
} else {
executor.killTask(taskId, interruptThread)
executor.killTask(taskId, interruptThread, reason, retryTask)
}

case StopExecutor =>
Expand Down
45 changes: 32 additions & 13 deletions core/src/main/scala/org/apache/spark/executor/Executor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ private[spark] class Executor(
threadPool.execute(tr)
}

def killTask(taskId: Long, interruptThread: Boolean): Unit = {
def killTask(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this fits in one line?

  def killTask(taskId: Long, interruptThread: Boolean, reason: String): Unit = {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

taskId: Long, interruptThread: Boolean, reason: String, shouldRetry: Boolean): Unit = {
val taskRunner = runningTasks.get(taskId)
if (taskRunner != null) {
if (taskReaperEnabled) {
Expand All @@ -168,7 +169,9 @@ private[spark] class Executor(
case Some(existingReaper) => interruptThread && !existingReaper.interruptThread
}
if (shouldCreateReaper) {
val taskReaper = new TaskReaper(taskRunner, interruptThread = interruptThread)
val taskReaper = new TaskReaper(
taskRunner, interruptThread = interruptThread, reason = reason,
shouldRetry = shouldRetry)
taskReaperForTask(taskId) = taskReaper
Some(taskReaper)
} else {
Expand All @@ -178,7 +181,8 @@ private[spark] class Executor(
// Execute the TaskReaper from outside of the synchronized block.
maybeNewTaskReaper.foreach(taskReaperPool.execute)
} else {
taskRunner.kill(interruptThread = interruptThread)
taskRunner.kill(
interruptThread = interruptThread, reason = reason, shouldRetry = shouldRetry)
}
}
}
Expand All @@ -189,8 +193,9 @@ private[spark] class Executor(
* tasks instead of taking the JVM down.
* @param interruptThread whether to interrupt the task thread
*/
def killAllTasks(interruptThread: Boolean) : Unit = {
runningTasks.keys().asScala.foreach(t => killTask(t, interruptThread = interruptThread))
def killAllTasks(interruptThread: Boolean, reason: String) : Unit = {
runningTasks.keys().asScala.foreach(t =>
killTask(t, interruptThread = interruptThread, reason = reason, shouldRetry = false))
}

def stop(): Unit = {
Expand Down Expand Up @@ -220,6 +225,12 @@ private[spark] class Executor(
/** Whether this task has been killed. */
@volatile private var killed = false

/** The reason this task was killed. */
@volatile private var killReason: String = null

/** Whether to retry this killed task. */
@volatile private var retryIfKilled: Boolean = false

@volatile private var threadId: Long = -1

def getThreadId: Long = threadId
Expand All @@ -239,8 +250,10 @@ private[spark] class Executor(
*/
@volatile var task: Task[Any] = _

def kill(interruptThread: Boolean): Unit = {
logInfo(s"Executor is trying to kill $taskName (TID $taskId)")
def kill(interruptThread: Boolean, reason: String, shouldRetry: Boolean): Unit = {
logInfo(s"Executor is trying to kill $taskName (TID $taskId), reason: $reason")
retryIfKilled = shouldRetry
killReason = reason
killed = true
if (task != null) {
synchronized {
Expand Down Expand Up @@ -427,14 +440,17 @@ private[spark] class Executor(
execBackend.statusUpdate(taskId, TaskState.FAILED, ser.serialize(reason))

case _: TaskKilledException =>
logInfo(s"Executor killed $taskName (TID $taskId)")
logInfo(s"Executor killed $taskName (TID $taskId), reason: $killReason")
setTaskFinishedAndClearInterruptStatus()
execBackend.statusUpdate(taskId, TaskState.KILLED, ser.serialize(TaskKilled))
execBackend.statusUpdate(
taskId, TaskState.KILLED, ser.serialize(TaskKilled(killReason, retryIfKilled)))

case _: InterruptedException if task.killed =>
logInfo(s"Executor interrupted and killed $taskName (TID $taskId)")
logInfo(
s"Executor interrupted and preempted $taskName (TID $taskId), reason: $killReason")
setTaskFinishedAndClearInterruptStatus()
execBackend.statusUpdate(taskId, TaskState.KILLED, ser.serialize(TaskKilled))
execBackend.statusUpdate(
taskId, TaskState.KILLED, ser.serialize(TaskKilled(killReason, retryIfKilled)))

case CausedBy(cDE: CommitDeniedException) =>
val reason = cDE.toTaskFailedReason
Expand Down Expand Up @@ -512,7 +528,9 @@ private[spark] class Executor(
*/
private class TaskReaper(
taskRunner: TaskRunner,
val interruptThread: Boolean)
val interruptThread: Boolean,
val reason: String,
val shouldRetry: Boolean)
extends Runnable {

private[this] val taskId: Long = taskRunner.taskId
Expand All @@ -533,7 +551,8 @@ private[spark] class Executor(
// Only attempt to kill the task once. If interruptThread = false then a second kill
// attempt would be a no-op and if interruptThread = true then it may not be safe or
// effective to interrupt multiple times:
taskRunner.kill(interruptThread = interruptThread)
taskRunner.kill(
interruptThread = interruptThread, reason = reason, shouldRetry = shouldRetry)
// Monitor the killed task until it exits. The synchronization logic here is complicated
// because we don't want to synchronize on the taskRunner while possibly taking a thread
// dump, but we also need to be careful to avoid races between checking whether the task
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,13 @@ class DAGScheduler(
eventProcessLoop.post(StageCancelled(stageId, reason))
}

/**
* Kill a given task. It will be retried.
*/
def killTask(taskId: Long, reason: String): Unit = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to the public api, we should separate retry from reason...

taskScheduler.killTask(taskId, interruptThread = true, reason, shouldRetry = true)
}

/**
* Resubmit any failed stages. Ordinarily called after a small amount of time has passed since
* the last fetch failure.
Expand Down Expand Up @@ -1345,7 +1352,7 @@ class DAGScheduler(
case TaskResultLost =>
// Do nothing here; the TaskScheduler handles these failures and resubmits the task.

case _: ExecutorLostFailure | TaskKilled | UnknownReason =>
case _: ExecutorLostFailure | _: TaskKilled | UnknownReason =>
// Unrecognized failure - also do nothing. If the task fails repeatedly, the TaskScheduler
// will abort the job.
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,20 @@ private[spark] trait SchedulerBackend {
def reviveOffers(): Unit
def defaultParallelism(): Int

def killTask(taskId: Long, executorId: String, interruptThread: Boolean): Unit =
/**
* Requests that an executor kills a running task.
*
* @param taskId Id of the task.
* @param executorId Id of the executor the task is running on.
* @param interruptThread Whether the executor should interrupt the task thread.
* @param reason The reason for the task kill.
* @param shouldRetry Whether the scheduler should retry the task.
*/
def killTask(
taskId: Long, executorId: String, interruptThread: Boolean, reason: String,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please follow the convention defined in the "Indentation" section at http://spark.apache.org/contributing.html for long parameter lists. This happens in a bunch of methods in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

shouldRetry: Boolean): Unit =
throw new UnsupportedOperationException

def isReady(): Boolean = true

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ private[spark] trait TaskScheduler {
// Cancel a stage.
def cancelTasks(stageId: Int, interruptThread: Boolean): Unit

// Kill a task.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment isn't very helpful if it just adds "a" to the method name :). Can you remove this? Also as above "killAndReschedule" would be a better name here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

def killTask(taskId: Long, interruptThread: Boolean, reason: String, shouldRetry: Boolean): Unit

// Set the DAG scheduler for upcalls. This is guaranteed to be set before submitTasks is called.
def setDAGScheduler(dagScheduler: DAGScheduler): Unit

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,14 +239,23 @@ private[spark] class TaskSchedulerImpl private[scheduler](
// simply abort the stage.
tsm.runningTasksSet.foreach { tid =>
val execId = taskIdToExecutorId(tid)
backend.killTask(tid, execId, interruptThread)
backend.killTask(
tid, execId, interruptThread, reason = "stage cancelled", shouldRetry = false)
}
tsm.abort("Stage %s cancelled".format(stageId))
logInfo("Stage %d was cancelled".format(stageId))
}
}
}

override def killTask(
taskId: Long, interruptThread: Boolean, reason: String, shouldRetry: Boolean): Unit = {
logInfo(s"Killing task ($reason): $taskId")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit but can you make this s"Killing task $taskId ($reason)"? This is somewhat more consistent with task-level logging elsewhere

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

val execId = taskIdToExecutorId.getOrElse(
taskId, throw new IllegalArgumentException("Task not found: " + taskId))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similarly how about s"Cannot kill task $taskId because it no task with that ID was found."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also it's kind of ugly that this throws an exception (seems like it could be an unhappy surprise to the user that their SparkContext threw an exception / died). How about instead changing the killTaskAttempt calls to return a boolean that's True if the task was successfully killed (and the returning false here)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

backend.killTask(taskId, execId, interruptThread, reason, shouldRetry)
}

/**
* Called to indicate that all task attempts (including speculated tasks) associated with the
* given TaskSetManager have completed, so state associated with the TaskSetManager should be
Expand Down Expand Up @@ -467,7 +476,7 @@ private[spark] class TaskSchedulerImpl private[scheduler](
taskState: TaskState,
reason: TaskFailedReason): Unit = synchronized {
taskSetManager.handleFailedTask(tid, taskState, reason)
if (!taskSetManager.isZombie && taskState != TaskState.KILLED) {
if (!taskSetManager.isZombie && reason.shouldRetry) {
// Need to revive offers again now that the task set manager state has been updated to
// reflect failed tasks that need to be re-run.
backend.reviveOffers()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,9 @@ private[spark] class TaskSetManager(
logInfo(s"Killing attempt ${attemptInfo.attemptNumber} for task ${attemptInfo.id} " +
s"in stage ${taskSet.id} (TID ${attemptInfo.taskId}) on ${attemptInfo.host} " +
s"as the attempt ${info.attemptNumber} succeeded on ${info.host}")
sched.backend.killTask(attemptInfo.taskId, attemptInfo.executorId, true)
sched.backend.killTask(
attemptInfo.taskId, attemptInfo.executorId, interruptThread = true,
reason = "another attempt succeeded", shouldRetry = false)
}
if (!successful(index)) {
tasksSuccessful += 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ private[spark] object CoarseGrainedClusterMessages {
// Driver to executors
case class LaunchTask(data: SerializableBuffer) extends CoarseGrainedClusterMessage

case class KillTask(taskId: Long, executor: String, interruptThread: Boolean)
case class KillTask(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this also seems to fit in one line?

  case class KillTask(taskId: Long, executor: String, interruptThread: Boolean, reason: String)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

taskId: Long, executor: String, interruptThread: Boolean, reason: String,
shouldRetry: Boolean)
extends CoarseGrainedClusterMessage

case class KillExecutorsOnHost(host: String)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,11 @@ class CoarseGrainedSchedulerBackend(scheduler: TaskSchedulerImpl, val rpcEnv: Rp
case ReviveOffers =>
makeOffers()

case KillTask(taskId, executorId, interruptThread) =>
case KillTask(taskId, executorId, interruptThread, reason, shouldRetry) =>
executorDataMap.get(executorId) match {
case Some(executorInfo) =>
executorInfo.executorEndpoint.send(KillTask(taskId, executorId, interruptThread))
executorInfo.executorEndpoint.send(
KillTask(taskId, executorId, interruptThread, reason, shouldRetry))
case None =>
// Ignoring the task kill since the executor is not registered.
logWarning(s"Attempted to kill task $taskId for unknown executor $executorId.")
Expand Down Expand Up @@ -414,8 +415,10 @@ class CoarseGrainedSchedulerBackend(scheduler: TaskSchedulerImpl, val rpcEnv: Rp
driverEndpoint.send(ReviveOffers)
}

override def killTask(taskId: Long, executorId: String, interruptThread: Boolean) {
driverEndpoint.send(KillTask(taskId, executorId, interruptThread))
override def killTask(
taskId: Long, executorId: String, interruptThread: Boolean, reason: String,
shouldRetry: Boolean) {
driverEndpoint.send(KillTask(taskId, executorId, interruptThread, reason, shouldRetry))
}

override def defaultParallelism(): Int = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ private case class ReviveOffers()

private case class StatusUpdate(taskId: Long, state: TaskState, serializedData: ByteBuffer)

private case class KillTask(taskId: Long, interruptThread: Boolean)
private case class KillTask(
taskId: Long, interruptThread: Boolean, reason: String, shouldRetry: Boolean)

private case class StopExecutor()

Expand Down Expand Up @@ -70,8 +71,8 @@ private[spark] class LocalEndpoint(
reviveOffers()
}

case KillTask(taskId, interruptThread) =>
executor.killTask(taskId, interruptThread)
case KillTask(taskId, interruptThread, reason, shouldRetry) =>
executor.killTask(taskId, interruptThread, reason, shouldRetry)
}

override def receiveAndReply(context: RpcCallContext): PartialFunction[Any, Unit] = {
Expand Down Expand Up @@ -143,8 +144,10 @@ private[spark] class LocalSchedulerBackend(
override def defaultParallelism(): Int =
scheduler.conf.getInt("spark.default.parallelism", totalCores)

override def killTask(taskId: Long, executorId: String, interruptThread: Boolean) {
localEndpoint.send(KillTask(taskId, interruptThread))
override def killTask(
taskId: Long, executorId: String, interruptThread: Boolean, reason: String,
shouldRetry: Boolean) {
localEndpoint.send(KillTask(taskId, interruptThread, reason, shouldRetry))
}

override def statusUpdate(taskId: Long, state: TaskState, serializedData: ByteBuffer) {
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/org/apache/spark/ui/UIUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ private[spark] object UIUtils extends Logging {
completed: Int,
failed: Int,
skipped: Int,
killed: Int,
killed: Map[String, Int],
total: Int): Seq[Node] = {
val completeWidth = "width: %s%%".format((completed.toDouble/total)*100)
// started + completed can be > total when there are speculative tasks
Expand All @@ -354,7 +354,7 @@ private[spark] object UIUtils extends Logging {
{completed}/{total}
{ if (failed > 0) s"($failed failed)" }
{ if (skipped > 0) s"($skipped skipped)" }
{ if (killed > 0) s"($killed killed)" }
{ killed.map { case (reason, count) => s"($count killed: $reason)" } }
</span>
<div class="bar bar-completed" style={completeWidth}></div>
<div class="bar bar-running" style={startWidth}></div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@ private[ui] class ExecutorTable(stageId: Int, stageAttemptId: Int, parent: Stage
</td>
<td>{executorIdToAddress.getOrElse(k, "CANNOT FIND ADDRESS")}</td>
<td sorttable_customkey={v.taskTime.toString}>{UIUtils.formatDuration(v.taskTime)}</td>
<td>{v.failedTasks + v.succeededTasks + v.killedTasks}</td>
<td>{v.failedTasks + v.succeededTasks + v.killedTasks.map(_._2).sum}</td>
<td>{v.failedTasks}</td>
<td>{v.killedTasks}</td>
<td>{v.killedTasks.map(_._2).sum}</td>
<td>{v.succeededTasks}</td>
{if (stageData.hasInput) {
<td sorttable_customkey={v.inputBytes.toString}>
Expand Down
Loading