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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ 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.

* Make the Thread class parse held locks, waited-for locks and
waited-for condition variables from the thread dump.

Expand Down
91 changes: 55 additions & 36 deletions analyze.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ limitations under the License.

/* global document */

var EMPTY_STACK = " <empty stack>\n";
var EMPTY_STACK = " <empty stack>";

// This method is called from HTML so we need to tell JSHint it's not unused
function analyzeTextfield() { // jshint ignore: line
Expand Down Expand Up @@ -86,22 +86,16 @@ function Thread(line) {
if (line.match(FRAME) === null) {
return false;
}
this._frames.push(line);
this.frames.push(line);
return true;
};

this.toStackString = function() {
var string = "";
for (var i = 0; i < this._frames.length; i++) {
var frame = this._frames[i];
string += frame + '\n';
}

if (string === '') {
if (this.frames.length === 0) {
return EMPTY_STACK;
}

return string;
return this.frames.join('\n');
};

this.toHeaderString = function() {
Expand Down Expand Up @@ -156,28 +150,7 @@ function Thread(line) {
return undefined;
}

this._frames = [];
}

function toStackWithHeadersString(stack, threads) {
var string = '';
if (threads.length > 4) {
string += "" + threads.length + " threads with this stack:\n";
}

// Print thread headers for this stack in alphabetic order
var headers = [];
for (var k = 0; k < threads.length; k++) {
headers.push(threads[k].toHeaderString());
}
headers.sort();
for (var l = 0; l < headers.length; l++) {
string += headers[l] + '\n';
}

string += stack;

return string;
this.frames = [];
}

function StringCounter() {
Expand Down Expand Up @@ -252,7 +225,10 @@ function Analyzer(text) {
}
};

this.toString = function() {
// Returns an array [{threads:, stackFrames:,} ...]. The threads:
// field contains an array of Threads. The stackFrames contain an
// array of strings
this._toThreadsAndStacks = function() {
// Map stacks to which threads have them
var stacksToThreads = {};
for (var i = 0; i < this.threads.length; i++) {
Expand Down Expand Up @@ -301,12 +277,55 @@ function Analyzer(text) {

// Iterate over stacks and for each stack, print first all
// threads that have it, and then the stack itself.
var asString = "";
asString += "" + this.threads.length + " threads found:\n";
var threadsAndStacks = [];
for (var j = 0; j < stacks.length; j++) {
var currentStack = stacks[j];
var threads = stacksToThreads[currentStack];
asString += '\n' + toStackWithHeadersString(currentStack, threads);

threads.sort(function(a, b){
var aHeader = a.toHeaderString();
var bHeader = b.toHeaderString();
if (aHeader > bHeader) {
return 1;
}
if (aHeader < bHeader) {
return -1;
}
return 0;
});

threadsAndStacks.push({
threads: threads,
stackFrames: currentStack.split('\n')
});
}

return threadsAndStacks;
};

this.toString = function() {
var threadsAndStacks = this._toThreadsAndStacks();

var asString = "";
asString += "" + this.threads.length + " threads found:\n";
for (var i = 0; i < threadsAndStacks.length; i++) {
var currentThreadsAndStack = threadsAndStacks[i];
var stackFrames = currentThreadsAndStack.stackFrames;
var threads = currentThreadsAndStack.threads;

asString += '\n';

if (threads.length > 4) {
asString += "" + threads.length + " threads with this stack:\n";
}

for (var j = 0; j < threads.length; j++) {
asString += threads[j].toHeaderString() + '\n';
}

for (var k = 0; k < stackFrames.length; k++) {
asString += stackFrames[k] + "\n";
}
}

return asString;
Expand Down
93 changes: 76 additions & 17 deletions tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,78 @@ QUnit.test( "multiline thread name", function(assert) {

assert.equal(threads.length, 1);
var threadLines = threads[0].toString().split('\n');
assert.equal(threadLines.length, 3);
assert.equal(threadLines[0], '"line 1, line 2": runnable');
assert.equal(threadLines[1], ' <empty stack>');
assert.equal(threadLines[2], '');
assert.deepEqual(threadLines, [
'"line 1, line 2": runnable',
' <empty stack>'
]);

// Test the Analyzer's toString() method as well now that we have an Analyzer
var analysisLines = analyzer.toString().split('\n');
assert.equal(analysisLines.length, 5);
assert.equal(analysisLines[0], "1 threads found:");
assert.equal(analysisLines[1], "");
assert.equal(analysisLines[2], '"line 1, line 2": runnable');
assert.equal(analysisLines[3], ' <empty stack>');
assert.equal(analysisLines[4], "");
assert.deepEqual(analysisLines, [
"1 threads found:",
"",
'"line 1, line 2": runnable',
" <empty stack>",
""
]);
});

QUnit.test( "analyze stackless thread", function(assert) {
var threadDump = '"thread name" prio=10 tid=0x00007f16a118e000 nid=0x6e5a runnable [0x00007f18b91d0000]';
var analyzer = new Analyzer(threadDump);
var threads = analyzer.threads;
assert.equal(threads.length, 1);
var thread = threads[0];

var analysisResult = analyzer._toThreadsAndStacks();
assert.deepEqual(analysisResult, [{
threads: [thread],
stackFrames: [" <empty stack>"]
}]);
});

QUnit.test( "analyze single thread", function(assert) {
var threadDump = [
'"thread name" prio=10 tid=0x00007f16a118e000 nid=0x6e5a runnable [0x00007f18b91d0000]',
' at fluff'
].join('\n');
var analyzer = new Analyzer(threadDump);
var threads = analyzer.threads;
assert.equal(threads.length, 1);
var thread = threads[0];

var analysisResult = analyzer._toThreadsAndStacks();
assert.deepEqual(analysisResult, [{
threads: [thread],
stackFrames: [" at fluff"]
}]);
});

QUnit.test( "analyze two threads with same stack", function(assert) {
// Thread dump with zebra before aardvark
var threadDump = [
'"zebra thread" prio=10 tid=0x00007f16a118e000 nid=0x6e5a runnable [0x00007f18b91d0000]',
' at fluff',
"",
'"aardvark thread" prio=10 tid=0x00007f16a118e000 nid=0x6e5a runnable [0x00007f18b91d0000]',
' at fluff'
].join('\n');

var analyzer = new Analyzer(threadDump);

var threads = analyzer.threads;
assert.equal(threads.length, 2);
var zebra = threads[0];
assert.equal(zebra.name, "zebra thread");
var aardvark = threads[1];
assert.equal(aardvark.name, "aardvark thread");

var analysisResult = analyzer._toThreadsAndStacks();
assert.deepEqual(analysisResult, [{
// Make sure the aardvark comes before the zebra
threads: [aardvark, zebra],
stackFrames: [" at fluff"]
}]);
});

QUnit.test( "thread stack", function(assert) {
Expand All @@ -115,11 +174,11 @@ QUnit.test( "thread stack", function(assert) {
// When adding stack frames we should just ignore unsupported
// lines, and the end result should contain only supported data.
var threadLines = thread.toString().split('\n');
assert.equal(threadLines.length, 4);
assert.equal(threadLines[0], '"Thread name": sleeping');
assert.equal(threadLines[1], " at java.security.AccessController.doPrivileged(Native Method)");
assert.equal(threadLines[2], " at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:353)");
assert.equal(threadLines[3], "");
assert.deepEqual(threadLines, [
'"Thread name": sleeping',
" at java.security.AccessController.doPrivileged(Native Method)",
" at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:353)"
]);
});

function unescapeHtml(escaped) {
Expand All @@ -132,10 +191,10 @@ QUnit.test( "full dump analysis", function(assert) {
var input = document.getElementById("sample-input").innerHTML;
var expectedOutput = unescapeHtml(document.getElementById("sample-analysis").innerHTML);
var analyzer = new Analyzer(input);
assert.equal(analyzer.toString(), expectedOutput);
assert.deepEqual(analyzer.toString().split('\n'), expectedOutput.split('\n'));

var expectedIgnores = document.getElementById("sample-ignored").innerHTML;
assert.equal(analyzer.toIgnoresString(), expectedIgnores);
assert.deepEqual(analyzer.toIgnoresString().split('\n'), expectedIgnores.split('\n'));
});

QUnit.test("extract regex from string", function(assert) {
Expand Down