Skip to content

Commit c0aca09

Browse files
committed
Add WebUITableBuilder to clean up table building code.
This significantly simplifies / abstracts the web UI's table construction code, which seems to account for the majority of the UI code. I haven't converted all tables to use this yet; this commit just provides the basic framework and a few example usages in the master web UI.
1 parent 4c589ca commit c0aca09

File tree

2 files changed

+291
-73
lines changed

2 files changed

+291
-73
lines changed

core/src/main/scala/org/apache/spark/deploy/master/ui/MasterPage.scala

Lines changed: 59 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ package org.apache.spark.deploy.master.ui
2020
import javax.servlet.http.HttpServletRequest
2121

2222
import scala.concurrent.Await
23-
import scala.xml.Node
23+
import scala.xml.{Text, Node}
2424

2525
import akka.pattern.ask
2626
import org.json4s.JValue
2727

2828
import org.apache.spark.deploy.JsonProtocol
2929
import org.apache.spark.deploy.DeployMessages.{MasterStateResponse, RequestMasterState}
30-
import org.apache.spark.deploy.master.{ApplicationInfo, DriverInfo, WorkerInfo}
31-
import org.apache.spark.ui.{WebUIPage, UIUtils}
30+
import org.apache.spark.deploy.master.{WorkerInfo, ApplicationInfo, DriverInfo}
31+
import org.apache.spark.ui.{UITable, UITableBuilder, WebUIPage, UIUtils}
3232
import org.apache.spark.util.Utils
3333

3434
private[spark] class MasterPage(parent: MasterWebUI) extends WebUIPage("") {
@@ -41,32 +41,71 @@ private[spark] class MasterPage(parent: MasterWebUI) extends WebUIPage("") {
4141
JsonProtocol.writeMasterState(state)
4242
}
4343

44+
private val workerTable: UITable[WorkerInfo] = {
45+
val builder = new UITableBuilder[WorkerInfo]()
46+
import builder._
47+
customCol("ID") { worker =>
48+
<a href={worker.webUiAddress}>{worker.id}</a>
49+
}
50+
col("Address") { worker => s"${worker.host}:${worker.port}"}
51+
col("State") { _.state.toString }
52+
intCol("Cores", formatter = c => s"$c Used") { _.coresUsed }
53+
customCol("Memory",
54+
sortKey = Some({worker: WorkerInfo => s"${worker.memory}:${worker.memoryUsed}"})) { worker =>
55+
Text(Utils.megabytesToString(worker.memory)) ++
56+
Text(Utils.megabytesToString(worker.memoryUsed))
57+
}
58+
build
59+
}
60+
61+
private val appTable: UITable[ApplicationInfo] = {
62+
val builder = new UITableBuilder[ApplicationInfo]()
63+
import builder._
64+
customCol("ID") { app =>
65+
<a href={"app?appId=" + app.id}>{app.id}</a>
66+
}
67+
col("Name") { _.id }
68+
intCol("Cores") { _.coresGranted }
69+
memCol("Memory per Node") { _.desc.memoryPerSlave }
70+
dateCol("Submitted Time") { _.submitDate }
71+
col("User") { _.desc.user }
72+
col("State") { _.state.toString }
73+
durationCol("Duration") { _.duration }
74+
build
75+
}
76+
77+
private val driverTable: UITable[DriverInfo] = {
78+
val builder = new UITableBuilder[DriverInfo]()
79+
import builder._
80+
col("ID") { _.id }
81+
dateCol("Submitted Time") { _.submitDate }
82+
customCol("Worker") { driver =>
83+
driver.worker.map(w => <a href={w.webUiAddress}>{w.id.toString}</a>).getOrElse(Text("None"))
84+
}
85+
col("State") { _.state.toString }
86+
intCol("Cores") { _.desc.cores }
87+
memCol("Memory") { _.desc.mem.toLong }
88+
col("Main Class") { _.desc.command.arguments(1) }
89+
build
90+
}
91+
4492
/** Index view listing applications and executors */
4593
def render(request: HttpServletRequest): Seq[Node] = {
4694
val stateFuture = (master ? RequestMasterState)(timeout).mapTo[MasterStateResponse]
4795
val state = Await.result(stateFuture, timeout)
4896

49-
val workerHeaders = Seq("Id", "Address", "State", "Cores", "Memory")
50-
val workers = state.workers.sortBy(_.id)
51-
val workerTable = UIUtils.listingTable(workerHeaders, workerRow, workers)
97+
val allWorkersTable = workerTable.render(state.workers.sortBy(_.id))
5298

53-
val appHeaders = Seq("ID", "Name", "Cores", "Memory per Node", "Submitted Time", "User",
54-
"State", "Duration")
55-
val activeApps = state.activeApps.sortBy(_.startTime).reverse
56-
val activeAppsTable = UIUtils.listingTable(appHeaders, appRow, activeApps)
57-
val completedApps = state.completedApps.sortBy(_.endTime).reverse
58-
val completedAppsTable = UIUtils.listingTable(appHeaders, appRow, completedApps)
99+
val activeAppsTable = appTable.render(state.activeApps.sortBy(_.startTime).reverse)
100+
val completedAppsTable = appTable.render(state.completedApps.sortBy(_.endTime).reverse)
59101

60-
val driverHeaders = Seq("ID", "Submitted Time", "Worker", "State", "Cores", "Memory",
61-
"Main Class")
62-
val activeDrivers = state.activeDrivers.sortBy(_.startTime).reverse
63-
val activeDriversTable = UIUtils.listingTable(driverHeaders, driverRow, activeDrivers)
64-
val completedDrivers = state.completedDrivers.sortBy(_.startTime).reverse
65-
val completedDriversTable = UIUtils.listingTable(driverHeaders, driverRow, completedDrivers)
102+
val activeDriversTable = driverTable.render(state.activeDrivers.sortBy(_.startTime).reverse)
103+
val completedDriversTable =
104+
driverTable.render(state.completedDrivers.sortBy(_.startTime).reverse)
66105

67106
// For now we only show driver information if the user has submitted drivers to the cluster.
68107
// This is until we integrate the notion of drivers and applications in the UI.
69-
def hasDrivers = activeDrivers.length > 0 || completedDrivers.length > 0
108+
def hasDrivers = state.activeDrivers.length > 0 || state.completedDrivers.length > 0
70109

71110
val content =
72111
<div class="row-fluid">
@@ -93,7 +132,7 @@ private[spark] class MasterPage(parent: MasterWebUI) extends WebUIPage("") {
93132
<div class="row-fluid">
94133
<div class="span12">
95134
<h4> Workers </h4>
96-
{workerTable}
135+
{allWorkersTable}
97136
</div>
98137
</div>
99138

@@ -138,57 +177,4 @@ private[spark] class MasterPage(parent: MasterWebUI) extends WebUIPage("") {
138177

139178
UIUtils.basicSparkPage(content, "Spark Master at " + state.uri)
140179
}
141-
142-
private def workerRow(worker: WorkerInfo): Seq[Node] = {
143-
<tr>
144-
<td>
145-
<a href={worker.webUiAddress}>{worker.id}</a>
146-
</td>
147-
<td>{worker.host}:{worker.port}</td>
148-
<td>{worker.state}</td>
149-
<td>{worker.cores} ({worker.coresUsed} Used)</td>
150-
<td sorttable_customkey={"%s.%s".format(worker.memory, worker.memoryUsed)}>
151-
{Utils.megabytesToString(worker.memory)}
152-
({Utils.megabytesToString(worker.memoryUsed)} Used)
153-
</td>
154-
</tr>
155-
}
156-
157-
private def appRow(app: ApplicationInfo): Seq[Node] = {
158-
<tr>
159-
<td>
160-
<a href={"app?appId=" + app.id}>{app.id}</a>
161-
</td>
162-
<td>
163-
<a href={app.desc.appUiUrl}>{app.desc.name}</a>
164-
</td>
165-
<td>
166-
{app.coresGranted}
167-
</td>
168-
<td sorttable_customkey={app.desc.memoryPerSlave.toString}>
169-
{Utils.megabytesToString(app.desc.memoryPerSlave)}
170-
</td>
171-
<td>{UIUtils.formatDate(app.submitDate)}</td>
172-
<td>{app.desc.user}</td>
173-
<td>{app.state.toString}</td>
174-
<td>{UIUtils.formatDuration(app.duration)}</td>
175-
</tr>
176-
}
177-
178-
private def driverRow(driver: DriverInfo): Seq[Node] = {
179-
<tr>
180-
<td>{driver.id} </td>
181-
<td>{driver.submitDate}</td>
182-
<td>{driver.worker.map(w => <a href={w.webUiAddress}>{w.id.toString}</a>).getOrElse("None")}
183-
</td>
184-
<td>{driver.state}</td>
185-
<td sorttable_customkey={driver.desc.cores.toString}>
186-
{driver.desc.cores}
187-
</td>
188-
<td sorttable_customkey={driver.desc.mem.toString}>
189-
{Utils.megabytesToString(driver.desc.mem.toLong)}
190-
</td>
191-
<td>{driver.desc.command.arguments(1)}</td>
192-
</tr>
193-
}
194180
}

0 commit comments

Comments
 (0)