All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.spark.ui.jobs.JobPage.scala Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.spark.ui.jobs

import java.util.Locale
import javax.servlet.http.HttpServletRequest

import scala.collection.mutable.{Buffer, ListBuffer}
import scala.xml.{Node, NodeSeq, Unparsed, Utility}

import org.apache.commons.lang3.StringEscapeUtils

import org.apache.spark.JobExecutionStatus
import org.apache.spark.scheduler._
import org.apache.spark.status.AppStatusStore
import org.apache.spark.status.api.v1
import org.apache.spark.ui._

/** Page showing statistics and stage list for a given job */
private[ui] class JobPage(parent: JobsTab, store: AppStatusStore) extends WebUIPage("job") {

  private val STAGES_LEGEND =
    
Completed Failed Active
.toString.filter(_ != '\n') private val EXECUTORS_LEGEND =
Added Removed
.toString.filter(_ != '\n') private def makeStageEvent(stageInfos: Seq[v1.StageData]): Seq[String] = { stageInfos.map { stage => val stageId = stage.stageId val attemptId = stage.attemptId val name = stage.name val status = stage.status.toString.toLowerCase(Locale.ROOT) val submissionTime = stage.submissionTime.get.getTime() val completionTime = stage.completionTime.map(_.getTime()) .getOrElse(System.currentTimeMillis()) // The timeline library treats contents as HTML, so we have to escape them. We need to add // extra layers of escaping in order to embed this in a Javascript string literal. val escapedName = Utility.escape(name) val jsEscapedName = StringEscapeUtils.escapeEcmaScript(escapedName) s""" |{ | 'className': 'stage job-timeline-object ${status}', | 'group': 'stages', | 'start': new Date(${submissionTime}), | 'end': new Date(${completionTime}), | 'content': '
Completed: ${UIUtils.formatDate(completionTime)}""" } else { "" } }">' + | '${jsEscapedName} (Stage ${stageId}.${attemptId})
', |} """.stripMargin } } def makeExecutorEvent(executors: Seq[v1.ExecutorSummary]): Seq[String] = { val events = ListBuffer[String]() executors.foreach { e => val addedEvent = s""" |{ | 'className': 'executor added', | 'group': 'executors', | 'start': new Date(${e.addTime.getTime()}), | 'content': '
Executor ${e.id} added
' |} """.stripMargin events += addedEvent e.removeTime.foreach { removeTime => val removedEvent = s""" |{ | 'className': 'executor removed', | 'group': 'executors', | 'start': new Date(${removeTime.getTime()}), | 'content': '
Reason: ${reason.replace("\n", " ")}""" }.getOrElse("") }"' + | 'data-html="true">Executor ${e.id} removed
' |} """.stripMargin events += removedEvent } } events.toSeq } private def makeTimeline( stages: Seq[v1.StageData], executors: Seq[v1.ExecutorSummary], appStartTime: Long): Seq[Node] = { val stageEventJsonAsStrSeq = makeStageEvent(stages) val executorsJsonAsStrSeq = makeExecutorEvent(executors) val groupJsonArrayAsStr = s""" |[ | { | 'id': 'executors', | 'content': '
Executors
${EXECUTORS_LEGEND}', | }, | { | 'id': 'stages', | 'content': '
Stages
${STAGES_LEGEND}', | } |] """.stripMargin val eventArrayAsStr = (stageEventJsonAsStrSeq ++ executorsJsonAsStrSeq).mkString("[", ",", "]") Event Timeline ++ ++ } def render(request: HttpServletRequest): Seq[Node] = { // stripXSS is called first to remove suspicious characters used in XSS attacks val parameterId = UIUtils.stripXSS(request.getParameter("id")) require(parameterId != null && parameterId.nonEmpty, "Missing id parameter") val jobId = parameterId.toInt val jobData = store.asOption(store.job(jobId)).getOrElse { val content =

No information to display for job {jobId}

return UIUtils.headerSparkPage( request, s"Details for Job $jobId", content, parent) } val isComplete = jobData.status != JobExecutionStatus.RUNNING val stages = jobData.stageIds.map { stageId => // This could be empty if the listener hasn't received information about the // stage or if the stage information has been garbage collected store.asOption(store.lastStageAttempt(stageId)).getOrElse { new v1.StageData( v1.StageStatus.PENDING, stageId, 0, 0, 0, 0, 0, 0, 0, 0L, 0L, None, None, None, None, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, "Unknown", None, "Unknown", null, Nil, Nil, None, None, Map()) } } val activeStages = Buffer[v1.StageData]() val completedStages = Buffer[v1.StageData]() // If the job is completed, then any pending stages are displayed as "skipped": val pendingOrSkippedStages = Buffer[v1.StageData]() val failedStages = Buffer[v1.StageData]() for (stage <- stages) { if (stage.submissionTime.isEmpty) { pendingOrSkippedStages += stage } else if (stage.completionTime.isDefined) { if (stage.status == v1.StageStatus.FAILED) { failedStages += stage } else { completedStages += stage } } else { activeStages += stage } } val basePath = "jobs/job" val pendingOrSkippedTableId = if (isComplete) { "pending" } else { "skipped" } val activeStagesTable = new StageTableBase(store, request, activeStages, "active", "activeStage", parent.basePath, basePath, parent.isFairScheduler, killEnabled = parent.killEnabled, isFailedStage = false) val pendingOrSkippedStagesTable = new StageTableBase(store, request, pendingOrSkippedStages, pendingOrSkippedTableId, "pendingStage", parent.basePath, basePath, parent.isFairScheduler, killEnabled = false, isFailedStage = false) val completedStagesTable = new StageTableBase(store, request, completedStages, "completed", "completedStage", parent.basePath, basePath, parent.isFairScheduler, killEnabled = false, isFailedStage = false) val failedStagesTable = new StageTableBase(store, request, failedStages, "failed", "failedStage", parent.basePath, basePath, parent.isFairScheduler, killEnabled = false, isFailedStage = true) val shouldShowActiveStages = activeStages.nonEmpty val shouldShowPendingStages = !isComplete && pendingOrSkippedStages.nonEmpty val shouldShowCompletedStages = completedStages.nonEmpty val shouldShowSkippedStages = isComplete && pendingOrSkippedStages.nonEmpty val shouldShowFailedStages = failedStages.nonEmpty val summary: NodeSeq =
  • Status: {jobData.status}
  • { if (jobData.jobGroup.isDefined) {
  • Job Group: {jobData.jobGroup.get}
  • } } { if (shouldShowActiveStages) {
  • Active Stages: {activeStages.size}
  • } } { if (shouldShowPendingStages) {
  • Pending Stages: {pendingOrSkippedStages.size}
  • } } { if (shouldShowCompletedStages) {
  • Completed Stages: {completedStages.size}
  • } } { if (shouldShowSkippedStages) {
  • Skipped Stages: {pendingOrSkippedStages.size}
  • } } { if (shouldShowFailedStages) {
  • Failed Stages: {failedStages.size}
  • } }
var content = summary val appStartTime = store.applicationInfo().attempts.head.startTime.getTime() content ++= makeTimeline(activeStages ++ completedStages ++ failedStages, store.executorList(false), appStartTime) val operationGraphContent = store.asOption(store.operationGraphForJob(jobId)) match { case Some(operationGraph) => UIUtils.showDagVizForJob(jobId, operationGraph) case None =>

No DAG visualization information to display for job {jobId}

} content ++= operationGraphContent if (shouldShowActiveStages) { content ++=

Active Stages ({activeStages.size})

++
{activeStagesTable.toNodeSeq}
} if (shouldShowPendingStages) { content ++=

Pending Stages ({pendingOrSkippedStages.size})

++
{pendingOrSkippedStagesTable.toNodeSeq}
} if (shouldShowCompletedStages) { content ++=

Completed Stages ({completedStages.size})

++
{completedStagesTable.toNodeSeq}
} if (shouldShowSkippedStages) { content ++=

Skipped Stages ({pendingOrSkippedStages.size})

++
{pendingOrSkippedStagesTable.toNodeSeq}
} if (shouldShowFailedStages) { content ++=

Failed Stages ({failedStages.size})

++
{failedStagesTable.toNodeSeq}
} UIUtils.headerSparkPage( request, s"Details for Job $jobId", content, parent, showVisualization = true) } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy