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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

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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

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