Skip to content

Commit 4c8f114

Browse files
tgravescsgengliangwang
authored andcommitted
[SPARK-27489][WEBUI] UI updates to show executor resource information
### What changes were proposed in this pull request? We are adding other resource type support to the executors and Spark. We should show the resource information for each executor on the UI Executors page. This also adds a toggle button to show the resources column. It is off by default. ![executorui1](https://user-images.githubusercontent.com/4563792/63891432-c815b580-c9aa-11e9-9f41-62975649efbc.png) ![Screenshot from 2019-08-28 14-56-26](https://user-images.githubusercontent.com/4563792/63891516-fd220800-c9aa-11e9-9fe4-89fcdca37306.png) ### Why are the changes needed? to show user what resources the executors have. Like Gpus, fpgas, etc ### Does this PR introduce any user-facing change? Yes introduces UI and rest api changes to show the resources ### How was this patch tested? Unit tests and manual UI tests on yarn and standalone modes. Closes #25613 from tgravescs/SPARK-27489-gpu-ui-latest. Authored-by: Thomas Graves <tgraves@nvidia.com> Signed-off-by: Gengliang Wang <gengliang.wang@databricks.com>
1 parent 56f2887 commit 4c8f114

29 files changed

Lines changed: 394 additions & 82 deletions

core/src/main/resources/org/apache/spark/ui/static/executorspage-template.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ <h4 class="title-table">Executors</h4>
9292
Off Heap Storage Memory</span></th>
9393
<th><span data-toggle="tooltip" data-placement="top" title="Disk Used">Disk Used</span></th>
9494
<th><span data-toggle="tooltip" data-placement="top" title="Cores">Cores</span></th>
95+
<th><span data-toggle="tooltip" data-placement="top" title="Resources">Resources</span></th>
9596
<th><span data-toggle="tooltip" data-placement="top" title="Active Tasks">Active Tasks</span></th>
9697
<th><span data-toggle="tooltip" data-placement="top" title="Failed Tasks">Failed Tasks</span></th>
9798
<th><span data-toggle="tooltip" data-placement="top" title="Complete Tasks">Complete Tasks</span></th>

core/src/main/resources/org/apache/spark/ui/static/executorspage.js

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@ function formatStatus(status, type, row) {
3939
return "Dead"
4040
}
4141

42+
function formatResourceCells(resources) {
43+
var result = ""
44+
var count = 0
45+
$.each(resources, function (name, resInfo) {
46+
if (count > 0) {
47+
result += ", "
48+
}
49+
result += name + ': [' + resInfo.addresses.join(", ") + ']'
50+
count += 1
51+
});
52+
return result
53+
}
54+
4255
jQuery.extend(jQuery.fn.dataTableExt.oSort, {
4356
"title-numeric-pre": function (a) {
4457
var x = a.match(/title="*(-?[0-9\.]+)/)[1];
@@ -106,7 +119,7 @@ function totalDurationColor(totalGCTime, totalDuration) {
106119
}
107120

108121
var sumOptionalColumns = [3, 4];
109-
var execOptionalColumns = [5, 6];
122+
var execOptionalColumns = [5, 6, 9];
110123
var execDataTable;
111124
var sumDataTable;
112125

@@ -401,6 +414,7 @@ $(document).ready(function () {
401414
},
402415
{data: 'diskUsed', render: formatBytes},
403416
{data: 'totalCores'},
417+
{name: 'resourcesCol', data: 'resources', render: formatResourceCells, orderable: false},
404418
{
405419
data: 'activeTasks',
406420
"fnCreatedCell": function (nTd, sData, oData, iRow, iCol) {
@@ -446,7 +460,8 @@ $(document).ready(function () {
446460
"order": [[0, "asc"]],
447461
"columnDefs": [
448462
{"visible": false, "targets": 5},
449-
{"visible": false, "targets": 6}
463+
{"visible": false, "targets": 6},
464+
{"visible": false, "targets": 9}
450465
]
451466
};
452467

@@ -553,6 +568,7 @@ $(document).ready(function () {
553568
"<div><input type='checkbox' class='toggle-vis' id='select-all-box'>Select All</div>" +
554569
"<div id='on_heap_memory' class='on-heap-memory-checkbox-div'><input type='checkbox' class='toggle-vis' data-sum-col-idx='3' data-exec-col-idx='5'>On Heap Memory</div>" +
555570
"<div id='off_heap_memory' class='off-heap-memory-checkbox-div'><input type='checkbox' class='toggle-vis' data-sum-col-idx='4' data-exec-col-idx='6'>Off Heap Memory</div>" +
571+
"<div id='extra_resources' class='resources-checkbox-div'><input type='checkbox' class='toggle-vis' data-sum-col-idx='' data-exec-col-idx='9'>Resources</div>" +
556572
"</div>");
557573

558574
reselectCheckboxesBasedOnTaskTableState();
@@ -584,8 +600,10 @@ $(document).ready(function () {
584600
var execCol = execDataTable.column(execColIdx);
585601
execCol.visible(!execCol.visible());
586602
var sumColIdx = thisBox.attr("data-sum-col-idx");
587-
var sumCol = sumDataTable.column(sumColIdx);
588-
sumCol.visible(!sumCol.visible());
603+
if (sumColIdx) {
604+
var sumCol = sumDataTable.column(sumColIdx);
605+
sumCol.visible(!sumCol.visible());
606+
}
589607
}
590608
});
591609

core/src/main/scala/org/apache/spark/deploy/history/HistoryAppStatusStore.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ private[spark] class HistoryAppStatusStore(
7272
source.totalGCTime, source.totalInputBytes, source.totalShuffleRead,
7373
source.totalShuffleWrite, source.isBlacklisted, source.maxMemory, source.addTime,
7474
source.removeTime, source.removeReason, newExecutorLogs, source.memoryMetrics,
75-
source.blacklistedInStages, source.peakMemoryMetrics, source.attributes)
75+
source.blacklistedInStages, source.peakMemoryMetrics, source.attributes, source.resources)
7676
}
7777

7878
}

core/src/main/scala/org/apache/spark/deploy/master/WorkerInfo.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@ import org.apache.spark.rpc.RpcEndpointRef
2626
import org.apache.spark.util.Utils
2727

2828
private[spark] case class WorkerResourceInfo(name: String, addresses: Seq[String])
29-
extends ResourceAllocator(name, addresses) {
29+
extends ResourceAllocator {
30+
31+
override protected def resourceName = this.name
32+
override protected def resourceAddresses = this.addresses
3033

3134
def acquire(amount: Int): ResourceInformation = {
3235
val allocated = availableAddrs.take(amount)
3336
acquire(allocated)
34-
new ResourceInformation(name, allocated.toArray)
37+
new ResourceInformation(resourceName, allocated.toArray)
3538
}
3639
}
3740

core/src/main/scala/org/apache/spark/resource/ResourceAllocator.scala

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,20 @@ import org.apache.spark.SparkException
2323
import org.apache.spark.util.collection.OpenHashMap
2424

2525
/**
26-
* Class used to help executor/worker allocate resources
27-
* Please note that this class is intended to be used in a single thread.
28-
* @param name Resource name, e.g. gpu/fpga
29-
* @param addresses Resource addresses provided by the executor/worker
26+
* Trait used to help executor/worker allocate resources.
27+
* Please note that this is intended to be used in a single thread.
3028
*/
31-
class ResourceAllocator(name: String, addresses: Seq[String]) extends Serializable {
29+
trait ResourceAllocator {
30+
31+
protected def resourceName: String
32+
protected def resourceAddresses: Seq[String]
33+
3234
/**
3335
* Map from an address to its availability, the value `true` means the address is available,
3436
* while value `false` means the address is assigned.
3537
* TODO Use [[OpenHashMap]] instead to gain better performance.
3638
*/
37-
private val addressAvailabilityMap = mutable.HashMap(addresses.map(_ -> true): _*)
39+
private lazy val addressAvailabilityMap = mutable.HashMap(resourceAddresses.map(_ -> true): _*)
3840

3941
/**
4042
* Sequence of currently available resource addresses.
@@ -59,15 +61,15 @@ class ResourceAllocator(name: String, addresses: Seq[String]) extends Serializab
5961
def acquire(addrs: Seq[String]): Unit = {
6062
addrs.foreach { address =>
6163
if (!addressAvailabilityMap.contains(address)) {
62-
throw new SparkException(s"Try to acquire an address that doesn't exist. $name address " +
63-
s"$address doesn't exist.")
64+
throw new SparkException(s"Try to acquire an address that doesn't exist. $resourceName " +
65+
s"address $address doesn't exist.")
6466
}
6567
val isAvailable = addressAvailabilityMap(address)
6668
if (isAvailable) {
6769
addressAvailabilityMap(address) = false
6870
} else {
69-
throw new SparkException(s"Try to acquire an address that is not available. $name " +
70-
s"address $address is not available.")
71+
throw new SparkException("Try to acquire an address that is not available. " +
72+
s"$resourceName address $address is not available.")
7173
}
7274
}
7375
}
@@ -80,14 +82,14 @@ class ResourceAllocator(name: String, addresses: Seq[String]) extends Serializab
8082
def release(addrs: Seq[String]): Unit = {
8183
addrs.foreach { address =>
8284
if (!addressAvailabilityMap.contains(address)) {
83-
throw new SparkException(s"Try to release an address that doesn't exist. $name address " +
84-
s"$address doesn't exist.")
85+
throw new SparkException(s"Try to release an address that doesn't exist. $resourceName " +
86+
s"address $address doesn't exist.")
8587
}
8688
val isAvailable = addressAvailabilityMap(address)
8789
if (!isAvailable) {
8890
addressAvailabilityMap(address) = true
8991
} else {
90-
throw new SparkException(s"Try to release an address that is not assigned. $name " +
92+
throw new SparkException(s"Try to release an address that is not assigned. $resourceName " +
9193
s"address $address is not assigned.")
9294
}
9395
}

core/src/main/scala/org/apache/spark/resource/ResourceInformation.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ private[spark] object ResourceInformation {
7474
s"Here is a correct example: $exampleJson.", e)
7575
}
7676
}
77+
78+
def parseJson(json: JValue): ResourceInformation = {
79+
implicit val formats = DefaultFormats
80+
try {
81+
json.extract[ResourceInformationJson].toResourceInformation
82+
} catch {
83+
case NonFatal(e) =>
84+
throw new SparkException(s"Error parsing JSON into ResourceInformation:\n$json\n", e)
85+
}
86+
}
7787
}
7888

7989
/** A case class to simplify JSON serialization of [[ResourceInformation]]. */

core/src/main/scala/org/apache/spark/scheduler/ExecutorResourceInfo.scala

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
package org.apache.spark.scheduler
1919

20-
import org.apache.spark.resource.ResourceAllocator
20+
import org.apache.spark.resource.{ResourceAllocator, ResourceInformation}
2121

2222
/**
2323
* Class to hold information about a type of Resource on an Executor. This information is managed
@@ -27,4 +27,8 @@ import org.apache.spark.resource.ResourceAllocator
2727
* @param addresses Resource addresses provided by the executor
2828
*/
2929
private[spark] class ExecutorResourceInfo(name: String, addresses: Seq[String])
30-
extends ResourceAllocator(name, addresses)
30+
extends ResourceInformation(name, addresses.toArray) with ResourceAllocator {
31+
32+
override protected def resourceName = this.name
33+
override protected def resourceAddresses = this.addresses
34+
}

core/src/main/scala/org/apache/spark/scheduler/cluster/ExecutorData.scala

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ import org.apache.spark.scheduler.ExecutorResourceInfo
3131
* @param resourcesInfo The information of the currently available resources on the executor
3232
*/
3333
private[cluster] class ExecutorData(
34-
val executorEndpoint: RpcEndpointRef,
35-
val executorAddress: RpcAddress,
36-
override val executorHost: String,
37-
var freeCores: Int,
38-
override val totalCores: Int,
39-
override val logUrlMap: Map[String, String],
40-
override val attributes: Map[String, String],
41-
val resourcesInfo: Map[String, ExecutorResourceInfo]
42-
) extends ExecutorInfo(executorHost, totalCores, logUrlMap, attributes)
34+
val executorEndpoint: RpcEndpointRef,
35+
val executorAddress: RpcAddress,
36+
override val executorHost: String,
37+
var freeCores: Int,
38+
override val totalCores: Int,
39+
override val logUrlMap: Map[String, String],
40+
override val attributes: Map[String, String],
41+
override val resourcesInfo: Map[String, ExecutorResourceInfo]
42+
) extends ExecutorInfo(executorHost, totalCores, logUrlMap, attributes, resourcesInfo)

core/src/main/scala/org/apache/spark/scheduler/cluster/ExecutorInfo.scala

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.apache.spark.scheduler.cluster
1818

1919
import org.apache.spark.annotation.DeveloperApi
20+
import org.apache.spark.resource.ResourceInformation
2021

2122
/**
2223
* :: DeveloperApi ::
@@ -27,10 +28,19 @@ class ExecutorInfo(
2728
val executorHost: String,
2829
val totalCores: Int,
2930
val logUrlMap: Map[String, String],
30-
val attributes: Map[String, String]) {
31+
val attributes: Map[String, String],
32+
val resourcesInfo: Map[String, ResourceInformation]) {
3133

3234
def this(executorHost: String, totalCores: Int, logUrlMap: Map[String, String]) = {
33-
this(executorHost, totalCores, logUrlMap, Map.empty)
35+
this(executorHost, totalCores, logUrlMap, Map.empty, Map.empty)
36+
}
37+
38+
def this(
39+
executorHost: String,
40+
totalCores: Int,
41+
logUrlMap: Map[String, String],
42+
attributes: Map[String, String]) = {
43+
this(executorHost, totalCores, logUrlMap, attributes, Map.empty)
3444
}
3545

3646
def canEqual(other: Any): Boolean = other.isInstanceOf[ExecutorInfo]
@@ -41,12 +51,13 @@ class ExecutorInfo(
4151
executorHost == that.executorHost &&
4252
totalCores == that.totalCores &&
4353
logUrlMap == that.logUrlMap &&
44-
attributes == that.attributes
54+
attributes == that.attributes &&
55+
resourcesInfo == that.resourcesInfo
4556
case _ => false
4657
}
4758

4859
override def hashCode(): Int = {
49-
val state = Seq(executorHost, totalCores, logUrlMap, attributes)
60+
val state = Seq(executorHost, totalCores, logUrlMap, attributes, resourcesInfo)
5061
state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
5162
}
5263
}

core/src/main/scala/org/apache/spark/status/AppStatusListener.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ private[spark] class AppStatusListener(
199199
exec.totalCores = event.executorInfo.totalCores
200200
exec.maxTasks = event.executorInfo.totalCores / coresPerTask
201201
exec.executorLogs = event.executorInfo.logUrlMap
202+
exec.resources = event.executorInfo.resourcesInfo
202203
exec.attributes = event.executorInfo.attributes
203204
liveUpdate(exec, System.nanoTime())
204205
}

0 commit comments

Comments
 (0)