Skip to content
This repository was archived by the owner on Jan 11, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ The Java Thread Dump Analyzer is licensed under
the copyright belongs to Spotify AB.

## TODO
* List top RUNNING methods before everything else. I have a thread
dump taken on a system that was swapping basically getting the GC
stuck. With a list of top RUNNING methods it should be possible to
see this from the analysis.
* For top running methods belonging to only one thread, make a link
from the top list method to the thread executing it.

* Add a Clear button next to the Analyze button.

* Say "... threads with no stack" for threads with no stack, rather
than "... threads with this stack".

* Make the Thread class parse held locks, waited-for locks and
waited-for condition variables from the thread dump.
Expand Down Expand Up @@ -120,3 +123,8 @@ obvious if we need to add something.
* Move styling into CSS.

* Turn the Ignored Lines section at the bottom into an HTML table.

* List top RUNNING methods before everything else. I have a thread
dump taken on a system that was swapping basically getting the GC
stuck. With a list of top RUNNING methods it should be possible to
see this from the analysis.
76 changes: 40 additions & 36 deletions analyze.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,13 @@ function analyzeTextfield() { // jshint ignore: line
var text = document.getElementById("TEXTAREA").value;

var analyzer = new Analyzer(text);
setOutputHtml(analyzer.toHtml());
setHtml("OUTPUT", analyzer.toHtml());

var ignores = analyzer.toIgnoresHtml();
if (ignores.length > 0) {
setIgnoredHtml(ignores);
} else {
var ignoredDiv = document.getElementById('IGNORED_DIV');
ignoredDiv.style.display = 'none';
}
setHtml("IGNORED", ignores);

var running = analyzer.toRunningHtml();
setHtml("RUNNING", running);
}

function htmlEscape(unescaped) {
Expand All @@ -41,20 +39,12 @@ function htmlEscape(unescaped) {
return escaped;
}

function setOutputHtml(html) {
var output = document.getElementById("OUTPUT");
output.innerHTML = html;
function setHtml(name, html) {
var destination = document.getElementById(name);
destination.innerHTML = html;

var outputDiv = document.getElementById('OUTPUT_DIV');
outputDiv.style.display = 'inline';
}

function setIgnoredHtml(html) {
var ignoredTable = document.getElementById("IGNORED");
ignoredTable.innerHTML = html;

var ignoredDiv = document.getElementById('IGNORED_DIV');
ignoredDiv.style.display = 'inline';
var div = document.getElementById(name + '_DIV');
div.style.display = (html.length > 0) ? 'inline' : 'none';
}

// Extracts a substring from a string.
Expand Down Expand Up @@ -92,7 +82,7 @@ function Thread(line) {
var match = line.match(THREAD_STATE);
if (match !== null) {
this.threadState = match[1];
this.running = (this.threadState === "RUNNING") && (this.state === 'running');
this.running = (this.threadState === "RUNNABLE") && (this.state === 'runnable');
return true;
}

Expand Down Expand Up @@ -196,13 +186,28 @@ function StringCounter() {
var string = "";
var countedStrings = this.getStrings();
for (var i = 0; i < countedStrings.length; i++) {
if (string.length > 0) {
string += '\n';
}
string += countedStrings[i].count +
" " + countedStrings[i].string +
'\n';
" " + countedStrings[i].string;
}
return string;
};

this.toHtml = function() {
var html = "";
var countedStrings = this.getStrings();
for (var i = 0; i < countedStrings.length; i++) {
html += '<tr><td class="right-align">' +
countedStrings[i].count +
'</td><td class="raw">' +
htmlEscape(countedStrings[i].string) +
"</td></tr>\n";
}
return html;
};

this._stringsToCounts = {};
}

Expand Down Expand Up @@ -353,6 +358,10 @@ function Analyzer(text) {
};

this.toHtml = function() {
if (this.threads.length === 0) {
return "";
}

var threadsAndStacks = this._toThreadsAndStacks();

var asHtml = "";
Expand Down Expand Up @@ -385,26 +394,21 @@ function Analyzer(text) {
};

this.toIgnoresString = function() {
return this._ignores.toString();
return this._ignores.toString() + '\n';
};

this.toIgnoresHtml = function() {
var html = "";
var countedIgnores = this._ignores.getStrings();
for (var i = 0; i < countedIgnores.length; i++) {
html += '<tr><td class="right-align">' +
countedIgnores[i].count +
'</td><td class="raw">' +
" " + htmlEscape(countedIgnores[i].string) +
"</td></tr>\n";
}
return html;
return this._ignores.toHtml();
};

this.toRunningString = function() {
return this._getCountedRunningMethods().toString();
};

this.toRunningHtml = function() {
return this._getCountedRunningMethods().toHtml();
};

this._getCountedRunningMethods = function() {
var countedRunning = new StringCounter();
for (var i = 0; i < this.threads.length; i++) {
Expand All @@ -417,11 +421,11 @@ function Analyzer(text) {
continue;
}

var runningMethod = thread.frames[0].replace(/^\s+at /, '');
var runningMethod = thread.frames[0].replace(/^\s+at\s+/, '');
countedRunning.addString(runningMethod);
}

return countedRunning.getStrings();
return countedRunning;
};

this.threads = [];
Expand Down
10 changes: 9 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ <h1>Java Thread Dump Analyzer</h1>
<p>This page is client-side only, and no data will leave your
computer when you click Analyze.</p>

<div id="RUNNING_DIV" style="display: none;">
<hr/>
<h2>Top Running Methods</h2>
<table id="RUNNING"
summary="Top methods from all threads that are running">
</table>
</div>

<div id="OUTPUT_DIV" style="display: none;">
<hr/>
<div id="OUTPUT"></div>
Expand All @@ -44,7 +52,7 @@ <h1>Java Thread Dump Analyzer</h1>

<div id="IGNORED_DIV" style="display: none;">
<hr/>
<h2>Ignored input lines</h2>
<h2>Ignored Input Lines</h2>
<p><a href="https://github.com/spotify/threaddump-analyzer/blob/gh-pages/README.md#todo">
See TODO list for which of these there are plans for already:</a></p>
<table id="IGNORED" class="ignored"
Expand Down
5 changes: 3 additions & 2 deletions test.html
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,8 @@
1 Full thread dump Java HotSpot(TM) 64-Bit Server VM (20.65-b04-466.1 mixed mode):
1 JNI global references: 8210
</pre>
<pre id="sample-running" style="display: none;">
</pre>
<pre id="sample-running" style="display: none;">2 java.net.PlainSocketImpl.socketAccept(Native Method)
1 java.lang.UNIXProcess.waitForProcessExit(Native Method)
1 sun.nio.ch.KQueueArrayWrapper.kevent0(Native Method)</pre>
</body>
</html>
28 changes: 20 additions & 8 deletions tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,28 +82,28 @@ QUnit.test( "sleeping thread", function(assert) {
});

// Our definition of a "running" thread is one that is both in
// Thread.State running and free text state running.
// Thread.State RUNNABLE and free text state "runnable".
QUnit.test("thread.running", function(assert) {
var thread;

thread = new Thread('"thread" prio=10 running');
thread.addStackLine(" java.lang.Thread.State: RUNNING");
thread = new Thread('"thread" prio=10 runnable');
thread.addStackLine(" java.lang.Thread.State: RUNNABLE");
assert.ok(thread.running);

thread = new Thread('"thread" prio=10 not running');
thread.addStackLine(" java.lang.Thread.State: RUNNING");
thread = new Thread('"thread" prio=10 not runnable');
thread.addStackLine(" java.lang.Thread.State: RUNNABLE");
assert.ok(!thread.running);

thread = new Thread('"thread" prio=10 running');
thread = new Thread('"thread" prio=10 runnable');
thread.addStackLine(" java.lang.Thread.State: TERMINATED");
assert.ok(!thread.running);

thread = new Thread('"thread" prio=10 not running');
thread = new Thread('"thread" prio=10 not runnable');
thread.addStackLine(" java.lang.Thread.State: TERMINATED");
assert.ok(!thread.running);

// Thread without Thread.State
thread = new Thread('"thread" prio=10 running');
thread = new Thread('"thread" prio=10 runnable');
assert.ok(!thread.running);
});

Expand Down Expand Up @@ -239,9 +239,13 @@ QUnit.test("extract regex from string", function(assert) {
QUnit.test("identical string counter", function(assert) {
var counter = new StringCounter();
assert.deepEqual(counter.getStrings().length, 0);
assert.equal(counter.toString(), "");

counter.addString("hej");
assert.deepEqual(counter.getStrings(), [{count:1, string:"hej"}]);
assert.deepEqual(counter.toString().split('\n'), [
"1 hej"
]);

counter.addString("nej");
counter.addString("nej");
Expand All @@ -250,6 +254,10 @@ QUnit.test("identical string counter", function(assert) {
{count:2, string:"nej"},
{count:1, string:"hej"}
]);
assert.deepEqual(counter.toString().split('\n'), [
"2 nej",
"1 hej"
]);

counter.addString("hej");
counter.addString("hej");
Expand All @@ -258,4 +266,8 @@ QUnit.test("identical string counter", function(assert) {
{count:3, string:"hej"},
{count:2, string:"nej"}
]);
assert.deepEqual(counter.toString().split('\n'), [
"3 hej",
"2 nej"
]);
});