Skip to content

Commit 6b40982

Browse files
committed
Add thread dump SQL console
1 parent 43fb763 commit 6b40982

File tree

12 files changed

+363
-11
lines changed

12 files changed

+363
-11
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ The report contains the following sections:
3737
+ [List of missing locks](doc/report.md#-list-of-missing-locks) - locks that are not released
3838

3939

40+
## TDV SQL Console
41+
42+
Yes, you can query the thread dump using SQL.
43+
44+
+ [SQL Console](doc/console.md)
45+
4046
## TODO
4147

4248
+ [X] Stats per thread pool

app/src/main/kotlin/dev/oblac/tdv/app/LocalApp.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import java.nio.file.Path
44

55
fun main() {
66
generateThreadDumpReport(Path.of("in/Thread.print.20240216120818"))
7-
generateThreadDumpReport(Path.of("in/Thread.print.20240304090236"))
8-
generateThreadDumpReport(Path.of("in/Thread.print.20240304090109"))
9-
generateThreadDumpReport(Path.of("in/Thread.print.20240319144720"))
10-
generateThreadDumpReport(Path.of("in/preprod-Thread-20240320.dump"))
7+
// generateThreadDumpReport(Path.of("in/Thread.print.20240304090236"))
8+
// generateThreadDumpReport(Path.of("in/Thread.print.20240304090109"))
9+
// generateThreadDumpReport(Path.of("in/Thread.print.20240319144720"))
10+
// generateThreadDumpReport(Path.of("in/preprod-Thread-20240320.dump"))
1111

12-
generateThreadDumpReport(Path.of("issues/0001-threaddump.txt.gz"))
13-
generateThreadDumpReport(Path.of("issues/0005-tdump.txt.gz"))
12+
// generateThreadDumpReport(Path.of("issues/0001-threaddump.txt.gz"))
13+
// generateThreadDumpReport(Path.of("issues/0005-tdump.txt.gz"))
1414

1515
}

doc/console.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Thread SQL Console
2+
3+
You can query the thread dump using SQL.
4+
5+
![](sql.png)
6+
7+
More tables and relationships will be added.

doc/sql.png

36.9 KB
Loading

domain/src/main/kotlin/dev/oblac/tdv/domain/thread.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import kotlin.time.DurationUnit
66
@JvmInline
77
value class ThreadNo(private val value: Int) {
88
override fun toString() = value.toString()
9+
fun toInt() = value
910
}
1011

1112
@JvmInline
@@ -63,7 +64,9 @@ enum class ThreadPriority(val priority: Int) {
6364
P9(9),
6465
P10(10);
6566

66-
companion object {
67+
fun toInt(): Int = priority
68+
69+
companion object {
6770
fun of(i: Int) = ThreadPriority.entries.first { it.priority == i }
6871
fun default() = P5
6972
}

reporter/src/main/kotlin/dev/oblac/tdv/reporter/GenerateReport.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,19 @@ object GenerateReport : (ThreadDump, ThreadDumpAnalysis, String) -> Report {
1717

1818
val compiledTemplateReport = engine.getTemplate("template/report.pebble")
1919
val compiledTemplateThreads = engine.getTemplate("template/threads.pebble")
20+
val compiledTemplateConsole = engine.getTemplate("template/console.pebble")
2021
val reportName = "index.html"
2122
val reportThreadsName = "threads.html"
23+
val reportConsoleName = "console.html"
2224

2325
val context: MutableMap<String, Any> = HashMap()
2426

2527
context["reportName"] =
2628
reportName
2729
context["reportThreadsName"] =
2830
reportThreadsName
31+
context["reportConsoleName"] =
32+
reportConsoleName
2933
context["stats"] =
3034
tda.stats
3135
context["blockTree"] =
@@ -97,19 +101,22 @@ object GenerateReport : (ThreadDump, ThreadDumpAnalysis, String) -> Report {
97101

98102
val writerReport = StringWriter().also { compiledTemplateReport.evaluate(it, context) }
99103
val writerThreads = StringWriter().also { compiledTemplateThreads.evaluate(it, context) }
104+
val writerConsole = StringWriter().also { compiledTemplateConsole.evaluate(it, context) }
100105

101106
return Report(
102107
listOf(
103108
ReportFile(reportName, writerReport.toString()),
104109
ReportFile(reportThreadsName, writerThreads.toString()),
110+
ReportFile(reportConsoleName, writerConsole.toString()),
105111
ResourceFile("style.css").toReportFile(),
106112
ResourceFile("canvasjs.min.js").toReportFile(),
107113
ResourceFile("d3.v7.min.js").toReportFile(),
108114
ResourceFile("d3-flamegraph.min.css").toReportFile(),
109115
ResourceFile("d3-flamegraph.min.js").toReportFile(),
110116
ResourceFile("charts.js").toReportFile(),
111117
ResourceFile("tree.js").toReportFile(),
112-
ResourceFile("expand-collapse.svg").toReportFile()
118+
ResourceFile("expand-collapse.svg").toReportFile(),
119+
ResourceFile("alasql.js").toReportFile()
113120
)
114121
)
115122
}

reporter/src/main/kotlin/dev/oblac/tdv/reporter/stacks.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package dev.oblac.tdv.reporter
22

33
import dev.oblac.tdv.domain.AppThreadInfo
4+
import dev.oblac.tdv.domain.Daemon
45

56
data class ReportThreadStackFreq(
67
val state: String,
@@ -12,13 +13,19 @@ data class ReportThreadStack(
1213
val name: String,
1314
val threadId: String,
1415
val state: String,
16+
val priority: Int,
17+
val daemon: Boolean,
18+
val number: Int,
1519
val stackTrace: List<String>,
1620
) {
1721
companion object {
1822
fun of(threadInfo: AppThreadInfo) = ReportThreadStack(
1923
threadInfo.name.toString(),
2024
threadInfo.tid.toString(),
2125
threadInfo.state.toString(),
26+
threadInfo.priority.toInt(),
27+
threadInfo.daemon == Daemon.DAEMON,
28+
threadInfo.number.toInt(),
2229
threadInfo.stackTrace.flatMap { st ->
2330
listOf(st.toString().trim()) + st.locks.map { it.toString() }
2431
}

reporter/src/main/resources/template/alasql.js

Lines changed: 158 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
{# @pebvariable name="td" type="java.util.List<dev.oblac.tdv.reporter.ReportThreadStack>" #}
2+
{% import "./macros.pebble" %}
3+
<html lang="en">
4+
<head>
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1">
7+
<title>Thread Dump : Console</title>
8+
<link rel="stylesheet" href="style.css">
9+
<script src="alasql.js"></script>
10+
<style>
11+
body {
12+
margin-top:0;
13+
}
14+
h1 {
15+
margin:0;
16+
padding: 0;
17+
}
18+
textarea {
19+
font-family: monospace;
20+
font-size: 16px;
21+
border: 1px solid #333;
22+
padding: 10px;
23+
}
24+
25+
/* CSS */
26+
.button-3 {
27+
appearance: none;
28+
background-color: #2ea44f;
29+
border: 1px solid rgba(27, 31, 35, .15);
30+
border-radius: 6px;
31+
box-shadow: rgba(27, 31, 35, .1) 0 1px 0;
32+
box-sizing: border-box;
33+
color: #fff;
34+
cursor: pointer;
35+
display: inline-block;
36+
font-family: -apple-system,system-ui,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
37+
font-size: 14px;
38+
font-weight: 600;
39+
line-height: 20px;
40+
padding: 6px 16px;
41+
position: relative;
42+
text-align: center;
43+
text-decoration: none;
44+
user-select: none;
45+
-webkit-user-select: none;
46+
touch-action: manipulation;
47+
vertical-align: middle;
48+
white-space: nowrap;
49+
}
50+
51+
.button-3:focus:not(:focus-visible):not(.focus-visible) {
52+
box-shadow: none;
53+
outline: none;
54+
}
55+
56+
.button-3:hover {
57+
background-color: #2c974b;
58+
}
59+
60+
.button-3:focus {
61+
box-shadow: rgba(46, 164, 79, .4) 0 0 0 3px;
62+
outline: none;
63+
}
64+
65+
.button-3:disabled {
66+
background-color: #94d3a2;
67+
border-color: rgba(27, 31, 35, .1);
68+
color: rgba(255, 255, 255, .8);
69+
cursor: default;
70+
}
71+
72+
.button-3:active {
73+
background-color: #298e46;
74+
box-shadow: rgba(20, 70, 32, .2) 0 1px 0 inset;
75+
}
76+
.hint {
77+
font-size: 12px;
78+
font-family: monospace;
79+
color: #666;
80+
margin-top: 10px;
81+
text-align: left;
82+
}
83+
</style>
84+
</head>
85+
<body>
86+
<main id="content">
87+
<h1>Thread Dump SQL Console</h1>
88+
<div>🏡 <a href="{{ reportName }}">report</a></div>
89+
90+
<a id="all"></a>
91+
<textarea id="console" style="width:100%;height:100px">select * from threads</textarea>
92+
<div class="hint">
93+
THREADS: id, name, state, priority, daemon, number
94+
</div>
95+
<div style="margin: 20px;">
96+
<button class="button-3" role="button" onclick="runQuery()">RUN</button>
97+
</div>
98+
<div id="results" class="box" style="width:100%;">
99+
<i>Results will be displayed here</i>
100+
</div>
101+
</main>
102+
103+
<footer>🚀 TDV</footer>
104+
</body>
105+
<script>
106+
const threads = [
107+
{% for t in td %}
108+
{
109+
id: '{{ t.threadId }}',
110+
name: '{{ t.name }}',
111+
state: '{{ t.state }}',
112+
priority: {{ t.priority }},
113+
daemon: {{ t.daemon }},
114+
number: {{ t.number }}
115+
},
116+
{% endfor %}
117+
]
118+
window.onload = function () {
119+
initDatabase();
120+
};
121+
function initDatabase() {
122+
alasql('CREATE TABLE threads (id String, name STRING, state STRING, priority NUMBER, daemon BOOLEAN, number NUMBER)');
123+
alasql.tables.threads.data = threads;
124+
}
125+
function runQuery() {
126+
const query = document.getElementById('console').value;
127+
const results = alasql(query);
128+
const resultsDiv = document.getElementById('results');
129+
resultsDiv.innerHTML = '';
130+
if (results.length === 0) {
131+
resultsDiv.innerHTML = 'No results';
132+
return;
133+
}
134+
const table = document.createElement('table');
135+
table.classList.add('styled-table');
136+
const thead = document.createElement('thead');
137+
const header = document.createElement('tr');
138+
for (const key in results[0]) {
139+
const th = document.createElement('th');
140+
th.innerText = key;
141+
header.appendChild(th);
142+
}
143+
thead.appendChild(header);
144+
table.appendChild(thead);
145+
const tbody = document.createElement('tbody');
146+
for (const row of results) {
147+
const tr = document.createElement('tr');
148+
for (const key in row) {
149+
const td = document.createElement('td');
150+
td.innerText = row[key];
151+
tr.appendChild(td);
152+
}
153+
tbody.appendChild(tr);
154+
}
155+
table.appendChild(tbody);
156+
resultsDiv.appendChild(table);
157+
}
158+
</script>
159+
</html>

reporter/src/main/resources/template/report.pebble

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<head>
1010
<meta charset="utf-8">
1111
<meta name="viewport" content="width=device-width, initial-scale=1">
12-
<title>Thread Dump Report: {{ reportName }}</title>
12+
<title>Thread Dump Report</title>
1313
<link href="d3-flamegraph.min.css" rel="stylesheet">
1414
<link rel="stylesheet" href="style.css">
1515
</head>
@@ -27,7 +27,8 @@
2727
<a href="#cpu">CPU consuming</a>
2828
<a href="#ust">Identical stack trace</a>
2929
<a href="#missing">Missing locks</a>
30-
<a href="{{ reportThreadsName }}">All&nbsp;Threads</a>
30+
<a href="{{ reportConsoleName }}" class="special">SQL</a>
31+
<a href="{{ reportThreadsName }}" class="special">ALL</a>
3132
</header>
3233
<main id="content">
3334
<h1>ThreadDump Report</h1>

0 commit comments

Comments
 (0)