Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SPARK-9516][UI] Improvement of Thread Dump Page #7910

Closed
wants to merge 14 commits into from
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ sorttable = {
for (var i=0; i<table.tBodies[0].rows.length; i++) {
text = sorttable.getInnerText(table.tBodies[0].rows[i].cells[column]);
if (text != '') {
if (text.match(/^-?[�$�]?[\d,.]+%?$/)) {
if (text.match(/^-?[£$¤]?[\d,.]+%?$/)) {
return sorttable.sort_numeric;
}
// check for a date: dd/mm/yyyy or dd/mm/yy
Expand Down Expand Up @@ -266,8 +266,8 @@ sorttable = {
return aa-bb;
},
sort_alpha: function(a,b) {
if (a[0]==b[0]) return 0;
if (a[0]<b[0]) return -1;
if (a[0].toLowerCase()==b[0].toLowerCase()) return 0;
if (a[0].toLowerCase()<b[0].toLowerCase()) return -1;
return 1;
},
sort_ddmm: function(a,b) {
Expand Down
72 changes: 72 additions & 0 deletions core/src/main/resources/org/apache/spark/ui/static/table.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,75 @@ function stripeSummaryTable() {
}
});
}

function toggleThreadStackTrace(threadId, forceAdd) {
var stackTrace = $("#" + threadId + "_stacktrace")
if (stackTrace.length == 0) {
var stackTraceText = $('#' + threadId + "_td_stacktrace").html()
var threadCell = $("#thread_" + threadId + "_tr")
threadCell.after("<tr id=\"" + threadId +"_stacktrace\" class=\"accordion-body\"><td colspan=\"3\"><pre>" +
stackTraceText + "</pre></td></tr>")
} else {
if (!forceAdd) {
stackTrace.remove()
}
}
}

// expandOrCollapse - true: expand, false: collapse
function expandOrCollapseAllThreadStackTrace(expandOrCollapse, toggleButton) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so I am totally unqualified to be judging javascript code, so feel free to reject this suggestion -- but to me it kinda seems like this really two different functions based on expandOrCollapse. The callsite always statically knows the value of that param, so it doesn't help to put them together. Might be cleaner to separate them out? Also it seems like its never called with true, false, so then you could eliminate that option.

(But again, totally up to your judgement, I have an incomplete understanding of what is going on here ...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm...good suggestion, it looks fishy to me too....

if (expandOrCollapse) {
$('.accordion-heading').each(function() {
//get thread ID
if (!$(this).hasClass("hidden")) {
var trId = $(this).attr('id').match(/thread_([0-9]+)_tr/m)[1]
toggleThreadStackTrace(trId, true)
}
})
if (toggleButton) {
$('.expandbutton').toggleClass('hidden')
}
} else {
$('.accordion-body').each(function() {
$(this).remove()
})
if (toggleButton) {
$('.expandbutton').toggleClass('hidden');
}
}
}

// inOrOut - true: over, false: out
function onMouseOverAndOut(threadId) {
$("#" + threadId + "_td_id").toggleClass("threaddump-td-mouseover");
$("#" + threadId + "_td_name").toggleClass("threaddump-td-mouseover");
$("#" + threadId + "_td_state").toggleClass("threaddump-td-mouseover");
}

function onSearchStringChange() {
var searchString = $('#search').val()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a toLowerCase here -- I tried searching for "RUNNABLE" first and it didn't find anything.

//remove the stacktrace
expandOrCollapseAllThreadStackTrace(false, false)
if (searchString.length == 0) {
$('tr').each(function() {
$(this).removeClass('hidden')
})
} else {
$('tr').each(function(){
if($(this).attr('id') && $(this).attr('id').match(/thread_[0-9]+_tr/) ) {
var children = $(this).children()
var found = false
for (i = 0; i < children.length; i++) {
if (children.eq(i).text().toLowerCase().indexOf(searchString) >= 0) {
found = true
}
}
if (found) {
$(this).removeClass('hidden')
} else {
$(this).addClass('hidden')
}
}
});
}
}
10 changes: 4 additions & 6 deletions core/src/main/resources/org/apache/spark/ui/static/webui.css
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,8 @@ a.expandbutton {
cursor: pointer;
}

.executor-thread {
background: #E6E6E6;
}

.non-executor-thread {
background: #FAFAFA;
.threaddump-td-mouseover {
background-color: #49535a !important;
color: white;
cursor:pointer;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.apache.spark.ui.exec

import java.net.URLDecoder
import java.util.regex.Pattern
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think this is unused now

import javax.servlet.http.HttpServletRequest

import scala.util.Try
Expand Down Expand Up @@ -49,7 +50,7 @@ private[ui] class ExecutorThreadDumpPage(parent: ExecutorsTab) extends WebUIPage
val maybeThreadDump = sc.get.getExecutorThreadDump(executorId)

val content = maybeThreadDump.map { threadDump =>
val dumpRows = threadDump.sortWith {
val dumpRows = threadDump.sortWith {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: old indentation was correct

case (threadTrace1, threadTrace2) => {
val v1 = if (threadTrace1.threadName.contains("Executor task launch")) 1 else 0
val v2 = if (threadTrace2.threadName.contains("Executor task launch")) 1 else 0
Expand All @@ -60,44 +61,49 @@ private[ui] class ExecutorThreadDumpPage(parent: ExecutorsTab) extends WebUIPage
}
}
}.map { thread =>
val threadName = thread.threadName
val className = "accordion-heading " + {
if (threadName.contains("Executor task launch")) {
"executor-thread"
} else {
"non-executor-thread"
}
}
<div class="accordion-group">
<div class={className} onclick="$(this).next().toggleClass('hidden')">
<a class="accordion-toggle">
Thread {thread.threadId}: {threadName} ({thread.threadState})
</a>
</div>
<div class="accordion-body hidden">
<div class="accordion-inner">
<pre>{thread.stackTrace}</pre>
val threadId = thread.threadId
<tr id={s"thread_${threadId}_tr"} class="accordion-heading"
onclick={s"toggleThreadStackTrace($threadId, false)"}
onmouseover={s"onMouseOverAndOut($threadId)"}
onmouseout={s"onMouseOverAndOut($threadId)"}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're losing the highlighting of the task threads here. A user could search for the thread name, but I don't think users will know that is what they should search for.

I can't think of a clean UI treatment to keep this, so I'm OK removing it, but I just wanted to call it out in case others have ideas / opinions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is just OK, since when the user opens the thread dump page, the task threads are always listed as the top rows of the table,

<td id={s"${threadId}_td_id"}>{threadId}</td>
<td id={s"${threadId}_td_name"}>{thread.threadName}</td>
<td id={s"${threadId}_td_state"}>{thread.threadState}</td>
<td id={s"${threadId}_td_stacktrace"} class="hidden">{thread.stackTrace}</td>
</tr>
}

<div class="row-fluid">
<p>Updated at {UIUtils.formatDate(time)}</p>
{
// scalastyle:off
<p><a class="expandbutton" onClick="expandOrCollapseAllThreadStackTrace(true, true)">
Expand All
</a></p>
<p><a class="expandbutton hidden" onClick="expandOrCollapseAllThreadStackTrace(false, true)">
Collapse All
</a></p>
<div class="form-inline">
<div class="bs-example" data-example-id="simple-form-inline">
<div class="form-group">
<div class="input-group">
Search: <input type="text" class="form-control" id="search" oninput="onSearchStringChange()"></input>
</div>
</div>
</div>
</div>
<p></p>
// scalastyle:on
}

<div class="row-fluid">
<p>Updated at {UIUtils.formatDate(time)}</p>
{
// scalastyle:off
<p><a class="expandbutton"
onClick="$('.accordion-body').removeClass('hidden'); $('.expandbutton').toggleClass('hidden')">
Expand All
</a></p>
<p><a class="expandbutton hidden"
onClick="$('.accordion-body').addClass('hidden'); $('.expandbutton').toggleClass('hidden')">
Collapse All
</a></p>
// scalastyle:on
}
<div class="accordion">{dumpRows}</div>
</div>
<table class={UIUtils.TABLE_CLASS_STRIPED + " accordion-group" + " sortable"}>
<thead>
<th onClick="expandOrCollapseAllThreadStackTrace(false, false)">Thread ID</th>
<th onClick="expandOrCollapseAllThreadStackTrace(false, false)">Thread Name</th>
<th onClick="expandOrCollapseAllThreadStackTrace(false, false)">Thread State</th>
</thead>
<tbody>{dumpRows}</tbody>
</table>
</div>
}.getOrElse(Text("Error fetching thread dump"))
UIUtils.headerSparkPage(s"Thread dump for executor $executorId", content, parent)
}
Expand Down