Skip to content

Commit adb6415

Browse files
kayousterhoutJoshRosen
authored andcommitted
[SPARK-4016] Allow user to show/hide UI metrics.
This commit adds a set of checkboxes to the stage detail page that the user can use to show additional task metrics, including the GC time, result serialization time, result fetch time, and scheduler delay. All of these metrics are now hidden by default. This allows advanced users to look at more detailed metrics, without distracting the average user. This change also cleans up the stage detail page so that metrics are shown in the same order in the summary table as in the task table, and updates the metrics in both tables such that they contain the same set of metrics. The ability to remember a user's preferences for which metrics should be shown has been filed as SPARK-4024. Here's what the stage detail page looks like by default: ![image](https://cloud.githubusercontent.com/assets/1108612/4744322/3ebe319e-5a2f-11e4-891f-c792be79caa2.png) and once a user clicks "Show additional metrics" (note that all the metrics get checked by default): ![image](https://cloud.githubusercontent.com/assets/1108612/4744332/51e5abda-5a2f-11e4-8994-d0d3705ee05d.png) cc shivaram andrewor14 Author: Kay Ousterhout <[email protected]> Closes #2867 from kayousterhout/SPARK-checkboxes and squashes the following commits: 6015913 [Kay Ousterhout] Added comment 08dee73 [Kay Ousterhout] Josh's usability comments 0940d61 [Kay Ousterhout] Style updates based on Andrew's review ef05ccd [Kay Ousterhout] Added tooltips d7cfaaf [Kay Ousterhout] Made list of add'l metrics collapsible. 70c1fb5 [Kay Ousterhout] [SPARK-4016] Allow user to show/hide UI metrics.
1 parent acd4ac7 commit adb6415

File tree

7 files changed

+350
-95
lines changed

7 files changed

+350
-95
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
/* Register functions to show/hide columns based on checkboxes. These need
19+
* to be registered after the page loads. */
20+
$(function() {
21+
$("span.expand-additional-metrics").click(function(){
22+
// Expand the list of additional metrics.
23+
var additionalMetricsDiv = $(this).parent().find('.additional-metrics');
24+
$(additionalMetricsDiv).toggleClass('collapsed');
25+
26+
// Switch the class of the arrow from open to closed.
27+
$(this).find('.expand-additional-metrics-arrow').toggleClass('arrow-open');
28+
$(this).find('.expand-additional-metrics-arrow').toggleClass('arrow-closed');
29+
30+
// If clicking caused the metrics to expand, automatically check all options for additional
31+
// metrics (don't trigger a click when collapsing metrics, because it leads to weird
32+
// toggling behavior).
33+
if (!$(additionalMetricsDiv).hasClass('collapsed')) {
34+
$(this).parent().find('input:checkbox:not(:checked)').trigger('click');
35+
}
36+
});
37+
38+
$("input:checkbox:not(:checked)").each(function() {
39+
var column = "table ." + $(this).attr("name");
40+
$(column).hide();
41+
});
42+
43+
$("input:checkbox").click(function() {
44+
var column = "table ." + $(this).attr("name");
45+
$(column).toggle();
46+
stripeTables();
47+
});
48+
49+
// Trigger a click on the checkbox if a user clicks the label next to it.
50+
$("span.additional-metric-title").click(function() {
51+
$(this).parent().find('input:checkbox').trigger('click');
52+
});
53+
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
/* Adds background colors to stripe table rows. This is necessary (instead of using css or the
19+
* table striping provided by bootstrap) to appropriately stripe tables with hidden rows. */
20+
function stripeTables() {
21+
$("table.table-striped-custom").each(function() {
22+
$(this).find("tr:not(:hidden)").each(function (index) {
23+
if (index % 2 == 1) {
24+
$(this).css("background-color", "#f9f9f9");
25+
} else {
26+
$(this).css("background-color", "#ffffff");
27+
}
28+
});
29+
});
30+
}
31+
32+
/* Stripe all tables after pages finish loading. */
33+
$(function() {
34+
stripeTables();
35+
});

core/src/main/resources/org/apache/spark/ui/static/webui.css

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,37 @@ pre {
120120
border: none;
121121
}
122122

123+
span.expand-additional-metrics {
124+
cursor: pointer;
125+
}
126+
127+
span.additional-metric-title {
128+
cursor: pointer;
129+
}
130+
131+
.additional-metrics.collapsed {
132+
display: none;
133+
}
134+
123135
.tooltip {
124136
font-weight: normal;
125137
}
126138

139+
.arrow-open {
140+
width: 0;
141+
height: 0;
142+
border-left: 5px solid transparent;
143+
border-right: 5px solid transparent;
144+
border-top: 5px solid black;
145+
float: left;
146+
margin-top: 6px;
147+
}
148+
149+
.arrow-closed {
150+
width: 0;
151+
height: 0;
152+
border-top: 5px solid transparent;
153+
border-bottom: 5px solid transparent;
154+
border-left: 5px solid black;
155+
display: inline-block;
156+
}

core/src/main/scala/org/apache/spark/ui/ToolTips.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,16 @@ private[spark] object ToolTips {
3131
val SHUFFLE_READ =
3232
"""Bytes read from remote executors. Typically less than shuffle write bytes
3333
because this does not include shuffle data read locally."""
34+
35+
val GETTING_RESULT_TIME =
36+
"""Time that the driver spends fetching task results from workers. If this is large, consider
37+
decreasing the amount of data returned from each task."""
38+
39+
val RESULT_SERIALIZATION_TIME =
40+
"""Time spent serializing the task result on the executor before sending it back to the
41+
driver."""
42+
43+
val GC_TIME =
44+
"""Time that the executor spent paused for Java garbage collection while the task was
45+
running."""
3446
}

core/src/main/scala/org/apache/spark/ui/UIUtils.scala

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ package org.apache.spark.ui
2020
import java.text.SimpleDateFormat
2121
import java.util.{Locale, Date}
2222

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

2525
import org.apache.spark.Logging
2626

2727
/** Utility functions for generating XML pages with spark content. */
2828
private[spark] object UIUtils extends Logging {
29-
val TABLE_CLASS = "table table-bordered table-striped table-condensed sortable"
29+
val TABLE_CLASS = "table table-bordered table-striped-custom table-condensed sortable"
3030

3131
// SimpleDateFormat is not thread-safe. Don't expose it to avoid improper use.
3232
private val dateFormat = new ThreadLocal[SimpleDateFormat]() {
@@ -160,6 +160,8 @@ private[spark] object UIUtils extends Logging {
160160
<script src={prependBaseUri("/static/jquery-1.11.1.min.js")}></script>
161161
<script src={prependBaseUri("/static/bootstrap-tooltip.js")}></script>
162162
<script src={prependBaseUri("/static/initialize-tooltips.js")}></script>
163+
<script src={prependBaseUri("/static/table.js")}></script>
164+
<script src={prependBaseUri("/static/additional-metrics.js")}></script>
163165
}
164166

165167
/** Returns a spark page with correctly formatted headers */
@@ -240,28 +242,38 @@ private[spark] object UIUtils extends Logging {
240242
generateDataRow: T => Seq[Node],
241243
data: Iterable[T],
242244
fixedWidth: Boolean = false,
243-
id: Option[String] = None): Seq[Node] = {
245+
id: Option[String] = None,
246+
headerClasses: Seq[String] = Seq.empty): Seq[Node] = {
244247

245248
var listingTableClass = TABLE_CLASS
246249
if (fixedWidth) {
247250
listingTableClass += " table-fixed"
248251
}
249252
val colWidth = 100.toDouble / headers.size
250253
val colWidthAttr = if (fixedWidth) colWidth + "%" else ""
251-
val headerRow: Seq[Node] = {
252-
// if none of the headers have "\n" in them
253-
if (headers.forall(!_.contains("\n"))) {
254-
// represent header as simple text
255-
headers.map(h => <th width={colWidthAttr}>{h}</th>)
254+
255+
def getClass(index: Int): String = {
256+
if (index < headerClasses.size) {
257+
headerClasses(index)
256258
} else {
257-
// represent header text as list while respecting "\n"
258-
headers.map { case h =>
259-
<th width={colWidthAttr}>
260-
<ul class ="unstyled">
261-
{ h.split("\n").map { case t => <li> {t} </li> } }
262-
</ul>
263-
</th>
264-
}
259+
""
260+
}
261+
}
262+
263+
val newlinesInHeader = headers.exists(_.contains("\n"))
264+
def getHeaderContent(header: String): Seq[Node] = {
265+
if (newlinesInHeader) {
266+
<ul class="unstyled">
267+
{ header.split("\n").map { case t => <li> {t} </li> } }
268+
</ul>
269+
} else {
270+
Text(header)
271+
}
272+
}
273+
274+
val headerRow: Seq[Node] = {
275+
headers.view.zipWithIndex.map { x =>
276+
<th width={colWidthAttr} class={getClass(x._2)}>{getHeaderContent(x._1)}</th>
265277
}
266278
}
267279
<table class={listingTableClass} id={id.map(Text.apply)}>

0 commit comments

Comments
 (0)