From 7a295fee9641e7b9480dd5cb520afe45039ffbe0 Mon Sep 17 00:00:00 2001 From: Denny <dennybritz@gmail.com> Date: Tue, 31 Jul 2012 08:39:24 -0700 Subject: [PATCH] Spark WebUI Implementation. --- .../spark/deploy/master/webui/index.html | 6 --- .../spark/deploy/master/webui/spark_logo.png | Bin 0 -> 14233 bytes .../spark/deploy/worker/webui/spark_logo.png | Bin 0 -> 14233 bytes .../scala/spark/deploy/DeployMessage.scala | 24 ++++++++++- .../scala/spark/deploy/master/Master.scala | 12 ++++-- .../spark/deploy/master/MasterWebUI.scala | 29 ++++++++++++- .../spark/deploy/master/WorkerInfo.scala | 7 +++- .../spark/deploy/worker/ExecutorRunner.scala | 20 ++++----- .../scala/spark/deploy/worker/Worker.scala | 25 +++++++---- .../spark/deploy/worker/WorkerWebUI.scala | 23 ++++++++++- core/src/main/twirl/common/layout.scala.html | 31 ++++++++++++++ .../twirl/masterui/executor_row.scala.html | 15 +++++++ .../twirl/masterui/executors_table.scala.html | 19 +++++++++ core/src/main/twirl/masterui/index.scala.html | 37 +++++++++++++++++ .../twirl/masterui/job_details.scala.html | 34 +++++++++++++++ .../main/twirl/masterui/job_row.scala.html | 15 +++++++ .../main/twirl/masterui/job_table.scala.html | 20 +++++++++ .../main/twirl/masterui/worker_row.scala.html | 11 +++++ .../twirl/masterui/worker_table.scala.html | 18 ++++++++ .../twirl/workerui/executor_row.scala.html | 21 ++++++++++ .../twirl/workerui/executors_table.scala.html | 18 ++++++++ core/src/main/twirl/workerui/index.scala.html | 39 ++++++++++++++++++ project/SparkBuild.scala | 3 +- project/plugins.sbt | 8 +++- 24 files changed, 399 insertions(+), 36 deletions(-) delete mode 100644 core/src/main/resources/spark/deploy/master/webui/index.html create mode 100644 core/src/main/resources/spark/deploy/master/webui/spark_logo.png create mode 100644 core/src/main/resources/spark/deploy/worker/webui/spark_logo.png create mode 100644 core/src/main/twirl/common/layout.scala.html create mode 100644 core/src/main/twirl/masterui/executor_row.scala.html create mode 100644 core/src/main/twirl/masterui/executors_table.scala.html create mode 100644 core/src/main/twirl/masterui/index.scala.html create mode 100644 core/src/main/twirl/masterui/job_details.scala.html create mode 100644 core/src/main/twirl/masterui/job_row.scala.html create mode 100644 core/src/main/twirl/masterui/job_table.scala.html create mode 100644 core/src/main/twirl/masterui/worker_row.scala.html create mode 100644 core/src/main/twirl/masterui/worker_table.scala.html create mode 100644 core/src/main/twirl/workerui/executor_row.scala.html create mode 100644 core/src/main/twirl/workerui/executors_table.scala.html create mode 100644 core/src/main/twirl/workerui/index.scala.html diff --git a/core/src/main/resources/spark/deploy/master/webui/index.html b/core/src/main/resources/spark/deploy/master/webui/index.html deleted file mode 100644 index c11101045e..0000000000 --- a/core/src/main/resources/spark/deploy/master/webui/index.html +++ /dev/null @@ -1,6 +0,0 @@ -<html> -<head><title>Hello world!</title></head> -<body> -<p>Hello world!</p> -</body> -</html> \ No newline at end of file diff --git a/core/src/main/resources/spark/deploy/master/webui/spark_logo.png b/core/src/main/resources/spark/deploy/master/webui/spark_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4b187347792a64a6aad514826227866ff5fb27a9 GIT binary patch literal 14233 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	Ikdsan+n&2}NtY1xTX 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 literal 0 HcmV?d00001 diff --git a/core/src/main/resources/spark/deploy/worker/webui/spark_logo.png b/core/src/main/resources/spark/deploy/worker/webui/spark_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4b187347792a64a6aad514826227866ff5fb27a9 GIT binary patch literal 14233 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	Ikdsan+n&2}NtY1xTX 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 literal 0 HcmV?d00001 diff --git a/core/src/main/scala/spark/deploy/DeployMessage.scala b/core/src/main/scala/spark/deploy/DeployMessage.scala index cf5e42797b..e05ca62367 100644 --- a/core/src/main/scala/spark/deploy/DeployMessage.scala +++ b/core/src/main/scala/spark/deploy/DeployMessage.scala @@ -1,12 +1,17 @@ package spark.deploy import spark.deploy.ExecutorState.ExecutorState +import spark.deploy.master.{WorkerInfo, JobInfo} +import spark.deploy.worker.ExecutorRunner +import scala.collection.immutable.List +import scala.collection.mutable.HashMap + sealed trait DeployMessage extends Serializable // Worker to Master -case class RegisterWorker(id: String, host: String, port: Int, cores: Int, memory: Int) +case class RegisterWorker(id: String, host: String, port: Int, cores: Int, memory: Int, webUiPort: Int) extends DeployMessage case class ExecutorStateChanged( @@ -44,4 +49,19 @@ case class JobKilled(message: String) // Internal message in Client -case object StopClient \ No newline at end of file +case object StopClient + +// MasterWebUI To Master + +case object RequestMasterState + +// Master to MasterWebUI + +case class MasterState(workers: List[WorkerInfo], jobs: HashMap[String, JobInfo]) + +// WorkerWebUI to Worker +case object RequestWorkerState + +// Worker to WorkerWebUI + +case class WorkerState(workerId: String, executors: List[ExecutorRunner], finishedExecutors: List[ExecutorRunner], masterUrl: String, cores: Int, memory: Int, coresUsed: Int, memoryUsed: Int) \ No newline at end of file diff --git a/core/src/main/scala/spark/deploy/master/Master.scala b/core/src/main/scala/spark/deploy/master/Master.scala index d691613b0d..4ccf3ee9d5 100644 --- a/core/src/main/scala/spark/deploy/master/Master.scala +++ b/core/src/main/scala/spark/deploy/master/Master.scala @@ -51,13 +51,13 @@ class Master(ip: String, port: Int, webUiPort: Int) extends Actor with Logging { } override def receive = { - case RegisterWorker(id, host, workerPort, cores, memory) => { + case RegisterWorker(id, host, workerPort, cores, memory, webUiPort) => { logInfo("Registering worker %s:%d with %d cores, %s RAM".format( host, workerPort, cores, Utils.memoryMegabytesToString(memory))) if (idToWorker.contains(id)) { sender ! RegisterWorkerFailed("Duplicate worker ID") } else { - addWorker(id, host, workerPort, cores, memory) + addWorker(id, host, workerPort, cores, memory, webUiPort) context.watch(sender) // This doesn't work with remote actors but helps for testing sender ! RegisteredWorker schedule() @@ -112,6 +112,10 @@ class Master(ip: String, port: Int, webUiPort: Int) extends Actor with Logging { addressToWorker.get(address).foreach(removeWorker) addressToJob.get(address).foreach(removeJob) } + + case RequestMasterState => { + sender ! MasterState(workers.toList, idToJob.clone) + } } /** @@ -143,8 +147,8 @@ class Master(ip: String, port: Int, webUiPort: Int) extends Actor with Logging { exec.job.actor ! ExecutorAdded(exec.id, worker.id, worker.host, exec.cores, exec.memory) } - def addWorker(id: String, host: String, port: Int, cores: Int, memory: Int): WorkerInfo = { - val worker = new WorkerInfo(id, host, port, cores, memory, sender) + def addWorker(id: String, host: String, port: Int, cores: Int, memory: Int, webUiPort: Int): WorkerInfo = { + val worker = new WorkerInfo(id, host, port, cores, memory, sender, webUiPort) workers += worker idToWorker(worker.id) = worker actorToWorker(sender) = worker diff --git a/core/src/main/scala/spark/deploy/master/MasterWebUI.scala b/core/src/main/scala/spark/deploy/master/MasterWebUI.scala index b0c871dd7b..5ee4d7730d 100644 --- a/core/src/main/scala/spark/deploy/master/MasterWebUI.scala +++ b/core/src/main/scala/spark/deploy/master/MasterWebUI.scala @@ -1,7 +1,14 @@ package spark.deploy.master import akka.actor.{ActorRef, ActorSystem} +import akka.dispatch.Await +import akka.pattern.ask +import akka.util.Timeout +import akka.util.duration._ import cc.spray.Directives +import cc.spray.directives._ +import cc.spray.typeconversion.TwirlSupport._ +import spark.deploy._ class MasterWebUI(val actorSystem: ActorSystem, master: ActorRef) extends Directives { val RESOURCE_DIR = "spark/deploy/master/webui" @@ -9,9 +16,29 @@ class MasterWebUI(val actorSystem: ActorSystem, master: ActorRef) extends Direct val handler = { get { path("") { - getFromResource(RESOURCE_DIR + "/index.html") + completeWith { + val masterState = getMasterState() + // Render the HTML + masterui.html.index.render(masterState.jobs.values.toList, masterState.workers) + } + } ~ + path("job") { + parameter("jobId") { jobId => + completeWith { + val masterState = getMasterState + masterui.html.job_details.render(masterState.jobs(jobId)) + } + } } ~ getFromResourceDirectory(RESOURCE_DIR) } } + + // Requests the current state from the Master and waits for the response + def getMasterState() : MasterState = { + implicit val timeout = Timeout(1 seconds) + val future = master ? RequestMasterState + return Await.result(future, timeout.duration).asInstanceOf[MasterState] + } + } diff --git a/core/src/main/scala/spark/deploy/master/WorkerInfo.scala b/core/src/main/scala/spark/deploy/master/WorkerInfo.scala index af0be108ea..59474a0945 100644 --- a/core/src/main/scala/spark/deploy/master/WorkerInfo.scala +++ b/core/src/main/scala/spark/deploy/master/WorkerInfo.scala @@ -9,7 +9,8 @@ class WorkerInfo( val port: Int, val cores: Int, val memory: Int, - val actor: ActorRef) { + val actor: ActorRef, + val webUiPort: Int) { var executors = new mutable.HashMap[String, ExecutorInfo] // fullId => info @@ -32,4 +33,8 @@ class WorkerInfo( memoryUsed -= exec.memory } } + + def webUiAddress : String = { + "http://" + this.host + ":" + this.webUiPort + } } diff --git a/core/src/main/scala/spark/deploy/worker/ExecutorRunner.scala b/core/src/main/scala/spark/deploy/worker/ExecutorRunner.scala index ecd558546b..3e24380810 100644 --- a/core/src/main/scala/spark/deploy/worker/ExecutorRunner.scala +++ b/core/src/main/scala/spark/deploy/worker/ExecutorRunner.scala @@ -14,16 +14,16 @@ import spark.deploy.ExecutorStateChanged * Manages the execution of one executor process. */ class ExecutorRunner( - jobId: String, - execId: Int, - jobDesc: JobDescription, - cores: Int, - memory: Int, - worker: ActorRef, - workerId: String, - hostname: String, - sparkHome: File, - workDir: File) + val jobId: String, + val execId: Int, + val jobDesc: JobDescription, + val cores: Int, + val memory: Int, + val worker: ActorRef, + val workerId: String, + val hostname: String, + val sparkHome: File, + val workDir: File) extends Logging { val fullId = jobId + "/" + execId diff --git a/core/src/main/scala/spark/deploy/worker/Worker.scala b/core/src/main/scala/spark/deploy/worker/Worker.scala index 19ffc1e401..fc496fdd97 100644 --- a/core/src/main/scala/spark/deploy/worker/Worker.scala +++ b/core/src/main/scala/spark/deploy/worker/Worker.scala @@ -27,7 +27,7 @@ class Worker(ip: String, port: Int, webUiPort: Int, cores: Int, memory: Int, mas var sparkHome: File = null var workDir: File = null val executors = new HashMap[String, ExecutorRunner] - val finishedExecutors = new ArrayBuffer[String] + val finishedExecutors = new HashMap[String, ExecutorRunner] var coresUsed = 0 var memoryUsed = 0 @@ -67,7 +67,7 @@ class Worker(ip: String, port: Int, webUiPort: Int, cores: Int, memory: Int, mas val akkaUrl = "akka://spark@%s:%s/user/Master".format(masterHost, masterPort) try { master = context.actorFor(akkaUrl) - master ! RegisterWorker(workerId, ip, port, cores, memory) + master ! RegisterWorker(workerId, ip, port, cores, memory, webUiPort) context.system.eventStream.subscribe(self, classOf[RemoteClientLifeCycleEvent]) context.watch(master) // Doesn't work with remote actors, but useful for testing } catch { @@ -108,25 +108,34 @@ class Worker(ip: String, port: Int, webUiPort: Int, cores: Int, memory: Int, mas jobId, execId, jobDesc, cores_, memory_, self, workerId, ip, sparkHome, workDir) executors(jobId + "/" + execId) = manager manager.start() + coresUsed += cores_ + memoryUsed += memory_ master ! ExecutorStateChanged(jobId, execId, ExecutorState.LOADING, None) case ExecutorStateChanged(jobId, execId, state, message) => master ! ExecutorStateChanged(jobId, execId, state, message) + val fullId = jobId + "/" + execId if (ExecutorState.isFinished(state)) { - logInfo("Executor " + jobId + "/" + execId + " finished with state " + state) - executors -= jobId + "/" + execId - finishedExecutors += jobId + "/" + execId + val executor = executors(fullId) + logInfo("Executor " + fullId + " finished with state " + state) + finishedExecutors(fullId) = executor + executors -= fullId + coresUsed -= executor.cores + memoryUsed -= executor.memory } case KillExecutor(jobId, execId) => val fullId = jobId + "/" + execId + val executor = executors(fullId) logInfo("Asked to kill executor " + fullId) - executors(jobId + "/" + execId).kill() - executors -= fullId - finishedExecutors += fullId + executor.kill() case Terminated(_) | RemoteClientDisconnected(_, _) | RemoteClientShutdown(_, _) => masterDisconnected() + + case RequestWorkerState => { + sender ! WorkerState(workerId, executors.values.toList, finishedExecutors.values.toList, masterUrl, cores, memory, coresUsed, memoryUsed) + } } def masterDisconnected() { diff --git a/core/src/main/scala/spark/deploy/worker/WorkerWebUI.scala b/core/src/main/scala/spark/deploy/worker/WorkerWebUI.scala index efd3822e61..47760f463d 100644 --- a/core/src/main/scala/spark/deploy/worker/WorkerWebUI.scala +++ b/core/src/main/scala/spark/deploy/worker/WorkerWebUI.scala @@ -1,7 +1,13 @@ package spark.deploy.worker import akka.actor.{ActorRef, ActorSystem} +import akka.dispatch.Await +import akka.pattern.ask +import akka.util.Timeout +import akka.util.duration._ import cc.spray.Directives +import cc.spray.typeconversion.TwirlSupport._ +import spark.deploy.{WorkerState, RequestWorkerState} class WorkerWebUI(val actorSystem: ActorSystem, worker: ActorRef) extends Directives { val RESOURCE_DIR = "spark/deploy/worker/webui" @@ -9,9 +15,24 @@ class WorkerWebUI(val actorSystem: ActorSystem, worker: ActorRef) extends Direct val handler = { get { path("") { - getFromResource(RESOURCE_DIR + "/index.html") + completeWith{ + workerui.html.index(getWorkerState()) + } + } ~ + path("log") { + parameters("jobId", "executorId", "logType") { (jobId, executorId, logType) => + getFromFileName("work/" + jobId + "/" + executorId + "/" + logType) + } } ~ getFromResourceDirectory(RESOURCE_DIR) } } + + // Requests the current state from the Master and waits for the response + def getWorkerState() : WorkerState = { + implicit val timeout = Timeout(1 seconds) + val future = worker ? RequestWorkerState + return Await.result(future, timeout.duration).asInstanceOf[WorkerState] + } + } diff --git a/core/src/main/twirl/common/layout.scala.html b/core/src/main/twirl/common/layout.scala.html new file mode 100644 index 0000000000..e6bb21969a --- /dev/null +++ b/core/src/main/twirl/common/layout.scala.html @@ -0,0 +1,31 @@ +@(title: String)(content: Html) + +<!DOCTYPE html> +<html> + + <head> + <meta http-equiv="Content-type" content="text/html; charset=utf-8"> + <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.0.4/css/bootstrap.min.css" type="text/css"> + <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.0.4/css/bootstrap-responsive.min.css" type="text/css"> + <title>Spark WebUI</title> + </head> + + <body> + <div class="container"> + + <!-- HEADER --> + <div class="row"> + <div class="span12"> + <img src="spark_logo.png"> + <h1 style="vertical-align: bottom; margin-bottom: 10px; margin-left: 30px; display: inline-block;"> @title </h1> + </div> + </div> + + <hr/> + + @content + + </div> + </body> + +</html> \ No newline at end of file diff --git a/core/src/main/twirl/masterui/executor_row.scala.html b/core/src/main/twirl/masterui/executor_row.scala.html new file mode 100644 index 0000000000..784d692fc2 --- /dev/null +++ b/core/src/main/twirl/masterui/executor_row.scala.html @@ -0,0 +1,15 @@ +@(executor: spark.deploy.master.ExecutorInfo) + +<tr> + <td>@executor.id</td> + <td> + <a href="@executor.worker.webUiAddress">@executor.worker.id</href> + </td> + <td>@executor.cores</td> + <td>@executor.memory</td> + <td>@executor.state</td> + <td> + <a href="@(executor.worker.webUiAddress)/log?jobId=@(executor.job.id)&executorId=@(executor.id)&logType=stdout">stdout</a> + <a href="@(executor.worker.webUiAddress)/log?jobId=@(executor.job.id)&executorId=@(executor.id)&logType=stderr">stderr</a> + </td> +</tr> \ No newline at end of file diff --git a/core/src/main/twirl/masterui/executors_table.scala.html b/core/src/main/twirl/masterui/executors_table.scala.html new file mode 100644 index 0000000000..cafc42c80e --- /dev/null +++ b/core/src/main/twirl/masterui/executors_table.scala.html @@ -0,0 +1,19 @@ +@(executors: List[spark.deploy.master.ExecutorInfo]) + +<table class="table table-bordered table-striped table-condensed"> + <thead> + <tr> + <th>ExecutorID</th> + <th>Worker</th> + <th>Cores</th> + <th>Memory</th> + <th>State</th> + <th>Logs</th> + </tr> + </thead> + <tbody> + @for(e <- executors) { + @executor_row(e) + } + </tbody> +</table> \ No newline at end of file diff --git a/core/src/main/twirl/masterui/index.scala.html b/core/src/main/twirl/masterui/index.scala.html new file mode 100644 index 0000000000..ddf6163765 --- /dev/null +++ b/core/src/main/twirl/masterui/index.scala.html @@ -0,0 +1,37 @@ +@(jobs: List[spark.deploy.master.JobInfo], workers: List[spark.deploy.master.WorkerInfo]) +@import spark.deploy.master._ + +@common.html.layout(title = "Master WebUI") { + + <!-- Cluster Summary (Workers) --> + <div class="row"> + <div class="span12"> + <h3> Cluster Summary </h3> + <br/> + @worker_table(workers) + </div> + </div> + + <hr/> + + <!-- Job Summary (Running) --> + <div class="row"> + <div class="span12"> + <h3> Running Jobs </h3> + <br/> + @job_table(jobs.filter(j => j.state == JobState.WAITING || j.state == JobState.RUNNING)) + </div> + </div> + + <hr/> + + <!-- Job Summary (Completed) --> + <div class="row"> + <div class="span12"> + <h3> Completed Jobs </h3> + <br/> + @job_table(jobs.filter(j => j.state == JobState.FINISHED || j.state == JobState.FAILED)) + </div> + </div> + +} \ No newline at end of file diff --git a/core/src/main/twirl/masterui/job_details.scala.html b/core/src/main/twirl/masterui/job_details.scala.html new file mode 100644 index 0000000000..a1fa4ab1ac --- /dev/null +++ b/core/src/main/twirl/masterui/job_details.scala.html @@ -0,0 +1,34 @@ +@(job: spark.deploy.master.JobInfo) + +@common.html.layout(title = "Job Details") { + + <!-- Job Details --> + <div class="row"> + <div class="span12"> + <ul class="unstyled"> + <li><strong>ID:</strong> @job.id</li> + <li><strong>Description:</strong> @job.desc.name</li> + <li><strong>User:</strong> @job.desc.user</li> + <li><strong>Cores:</strong> @job.desc.cores</li> + <li><strong>Memory per Slave:</strong> @job.desc.memoryPerSlave</li> + <li><strong>Submit Date:</strong> @job.submitDate</li> + <li><strong>State:</strong> @job.state</li> + <li><strong>Cores Granted:</strong> @job.coresGranted</li> + <li><strong>Cores Left:</strong> @job.coresLeft</li> + <li><strong>Command:</strong> @job.desc.command</li> + </ul> + </div> + </div> + + <hr/> + + <!-- Executors --> + <div class="row"> + <div class="span12"> + <h3> Executor Summary </h3> + <br/> + @executors_table(job.executors.values.toList) + </div> + </div> + +} \ No newline at end of file diff --git a/core/src/main/twirl/masterui/job_row.scala.html b/core/src/main/twirl/masterui/job_row.scala.html new file mode 100644 index 0000000000..1d0d1650c0 --- /dev/null +++ b/core/src/main/twirl/masterui/job_row.scala.html @@ -0,0 +1,15 @@ +@(job: spark.deploy.master.JobInfo) + +<tr> + <td> + <a href="job?jobId=@(job.id)">@job.id</a> + </td> + <td>@job.desc</td> + <td> + @job.coresGranted Granted, @job.coresLeft Left + </td> + <td>@job.desc.memoryPerSlave</td> + <td>@job.submitDate</td> + <td>@job.desc.user</td> + <td>@job.state.toString()</td> +</tr> \ No newline at end of file diff --git a/core/src/main/twirl/masterui/job_table.scala.html b/core/src/main/twirl/masterui/job_table.scala.html new file mode 100644 index 0000000000..b3b1e4d472 --- /dev/null +++ b/core/src/main/twirl/masterui/job_table.scala.html @@ -0,0 +1,20 @@ +@(jobs: List[spark.deploy.master.JobInfo]) + +<table class="table table-bordered table-striped table-condensed"> + <thead> + <tr> + <th>JobID</th> + <th>Description</th> + <th>Cores</th> + <th>Memory per Slave</th> + <th>Submit Date</th> + <th>User</th> + <th>State</th> + </tr> + </thead> + <tbody> + @for(j <- jobs) { + @job_row(j) + } + </tbody> +</table> \ No newline at end of file diff --git a/core/src/main/twirl/masterui/worker_row.scala.html b/core/src/main/twirl/masterui/worker_row.scala.html new file mode 100644 index 0000000000..6c8aaaae60 --- /dev/null +++ b/core/src/main/twirl/masterui/worker_row.scala.html @@ -0,0 +1,11 @@ +@(worker: spark.deploy.master.WorkerInfo) + +<tr> + <td> + <a href="http://@worker.host:@worker.webUiPort">@worker.id</href> + </td> + <td>@worker.host</td> + <td>@worker.port</td> + <td>@worker.cores (@worker.coresUsed Used)</td> + <td>@worker.memory (@worker.memoryUsed Used)</td> +</tr> \ No newline at end of file diff --git a/core/src/main/twirl/masterui/worker_table.scala.html b/core/src/main/twirl/masterui/worker_table.scala.html new file mode 100644 index 0000000000..201af5383a --- /dev/null +++ b/core/src/main/twirl/masterui/worker_table.scala.html @@ -0,0 +1,18 @@ +@(workers: List[spark.deploy.master.WorkerInfo]) + +<table class="table table-bordered table-striped table-condensed"> + <thead> + <tr> + <th>ID</th> + <th>Host</th> + <th>Port</th> + <th>Cores</th> + <th>Memory</th> + </tr> + </thead> + <tbody> + @for(w <- workers) { + @worker_row(w) + } + </tbody> +</table> \ No newline at end of file diff --git a/core/src/main/twirl/workerui/executor_row.scala.html b/core/src/main/twirl/workerui/executor_row.scala.html new file mode 100644 index 0000000000..db3d33d74e --- /dev/null +++ b/core/src/main/twirl/workerui/executor_row.scala.html @@ -0,0 +1,21 @@ +@(executor: spark.deploy.worker.ExecutorRunner) + +<tr> + <td>@executor.execId</td> + <td>@executor.cores</td> + <td>@executor.memory</td> + <td> + <ul class="unstyled"> + <li><strong>ID:</strong> @executor.jobId</li> + <li><strong>Name:</strong> @executor.jobDesc.name</li> + <li><strong>User:</strong> @executor.jobDesc.user</li> + <li><strong>Cores:</strong> @executor.jobDesc.cores </li> + <li><strong>Memory per Slave:</strong> @executor.jobDesc.memoryPerSlave</li> + <li><strong>Command:</strong> @executor.jobDesc.command</li> + </ul> + </td> + <td> + <a href="log?jobId=@(executor.jobId)&executorId=@(executor.execId)&logType=stdout">stdout</a> + <a href="log?jobId=@(executor.jobId)&executorId=@(executor.execId)&logType=stderr">stderr</a> + </td> +</tr> \ No newline at end of file diff --git a/core/src/main/twirl/workerui/executors_table.scala.html b/core/src/main/twirl/workerui/executors_table.scala.html new file mode 100644 index 0000000000..c8d51cc9f4 --- /dev/null +++ b/core/src/main/twirl/workerui/executors_table.scala.html @@ -0,0 +1,18 @@ +@(executors: List[spark.deploy.worker.ExecutorRunner]) + +<table class="table table-bordered table-striped table-condensed"> + <thead> + <tr> + <th>ExecutorID</th> + <th>Cores</th> + <th>Memory</th> + <th>Job Details</th> + <th>Logs</th> + </tr> + </thead> + <tbody> + @for(e <- executors) { + @executor_row(e) + } + </tbody> +</table> \ No newline at end of file diff --git a/core/src/main/twirl/workerui/index.scala.html b/core/src/main/twirl/workerui/index.scala.html new file mode 100644 index 0000000000..a70760f25a --- /dev/null +++ b/core/src/main/twirl/workerui/index.scala.html @@ -0,0 +1,39 @@ +@(worker: spark.deploy.WorkerState) + +@common.html.layout(title = "Worker WebUI") { + + <!-- Worker Details --> + <div class="row"> + <div class="span12"> + <ul class="unstyled"> + <li><strong>ID:</strong> @worker.workerId</li> + <li><strong>Master URL:</strong> @worker.masterUrl </li> + <li><strong>Cores:</strong> @worker.cores (@worker.coresUsed Used)</li> + <li><strong>Memory:</strong> @worker.memory (@worker.memoryUsed Used)</li> + </ul> + </div> + </div> + + <hr/> + + <!-- Running Executors --> + <div class="row"> + <div class="span12"> + <h3> Running Executors </h3> + <br/> + @executors_table(worker.executors) + </div> + </div> + + <hr/> + + <!-- Finished Executors --> + <div class="row"> + <div class="span12"> + <h3> Finished Executors </h3> + <br/> + @executors_table(worker.finishedExecutors) + </div> + </div> + +} \ No newline at end of file diff --git a/project/SparkBuild.scala b/project/SparkBuild.scala index 726d490738..d1445f2ade 100644 --- a/project/SparkBuild.scala +++ b/project/SparkBuild.scala @@ -2,6 +2,7 @@ import sbt._ import Keys._ import sbtassembly.Plugin._ import AssemblyKeys._ +import twirl.sbt.TwirlPlugin._ object SparkBuild extends Build { // Hadoop version to build against. For example, "0.20.2", "0.20.205.0", or @@ -69,7 +70,7 @@ object SparkBuild extends Build { "cc.spray" % "spray-can" % "1.0-M2.1", "cc.spray" % "spray-server" % "1.0-M2.1" ) - ) ++ assemblySettings ++ extraAssemblySettings + ) ++ assemblySettings ++ extraAssemblySettings ++ Seq(Twirl.settings: _*) def replSettings = sharedSettings ++ Seq( name := "spark-repl", diff --git a/project/plugins.sbt b/project/plugins.sbt index 0e2b6d4902..896fa4834f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,9 +1,13 @@ -resolvers += Classpaths.typesafeResolver - resolvers += Resolver.url("artifactory", url("http://scalasbt.artifactoryonline.com/scalasbt/sbt-plugin-releases"))(Resolver.ivyStylePatterns) +resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/" + +resolvers += "Spray Repository" at "http://repo.spray.cc/" + addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.8.3") addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.1.0-RC1") addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.0.0") + +addSbtPlugin("cc.spray" %% "sbt-twirl" % "0.5.2") -- GitLab