|
1 | 1 | package dev.suresh.routes |
2 | 2 |
|
| 3 | +import Arguments |
| 4 | +import FlameGraph |
3 | 5 | import com.sun.management.HotSpotDiagnosticMXBean |
4 | 6 | import dev.suresh.jvmRuntimeInfo |
5 | 7 | import dev.suresh.plugins.debug |
6 | | -import dev.suresh.runOnVirtualThread |
7 | 8 | import io.ktor.http.* |
8 | | -import io.ktor.server.application.* |
9 | 9 | import io.ktor.server.http.content.* |
10 | | -import io.ktor.server.request.* |
11 | 10 | import io.ktor.server.response.* |
12 | 11 | import io.ktor.server.routing.* |
13 | 12 | import java.io.File |
14 | 13 | import java.io.PrintStream |
15 | 14 | import java.lang.management.ManagementFactory |
16 | 15 | import jdk.jfr.Configuration |
| 16 | +import jdk.jfr.FlightRecorder |
17 | 17 | import jdk.jfr.consumer.RecordingStream |
| 18 | +import jfr2flame |
18 | 19 | import kotlin.io.path.* |
19 | 20 | import kotlin.time.Duration.Companion.milliseconds |
20 | 21 | import kotlin.time.Duration.Companion.minutes |
| 22 | +import kotlin.time.Duration.Companion.seconds |
21 | 23 | import kotlin.time.toJavaDuration |
22 | 24 | import kotlinx.coroutines.sync.Mutex |
23 | 25 | import kotlinx.coroutines.sync.withLock |
24 | | -import one.converter.Arguments |
25 | | -import one.converter.FlameGraph |
26 | | -import one.converter.jfr2flame |
27 | 26 | import one.jfr.JfrReader |
28 | | -import one.profiler.AsyncProfiler |
29 | | -import one.profiler.AsyncProfilerLoader |
30 | | -import one.profiler.Events |
31 | 27 |
|
32 | 28 | private val DEBUG = ScopedValue.newInstance<Boolean>() |
33 | 29 |
|
34 | 30 | val mutex = Mutex() |
35 | 31 |
|
36 | | -val profiler: AsyncProfiler? by lazy { |
37 | | - val ap = AsyncProfilerLoader.loadOrNull() |
38 | | - ap.start(Events.CPU, 1000) |
39 | | - ap |
40 | | -} |
| 32 | +// val profiler: AsyncProfiler? by lazy { |
| 33 | +// val ap = AsyncProfilerLoader.loadOrNull() |
| 34 | +// ap.start(Events.CPU, 1000) |
| 35 | +// ap |
| 36 | +// } |
41 | 37 |
|
42 | 38 | val docRoot = Path(System.getProperty("java.io.tmpdir")) |
43 | 39 |
|
@@ -158,44 +154,57 @@ fun Route.mgmtRoutes() { |
158 | 154 | } |
159 | 155 |
|
160 | 156 | get("/profile") { |
161 | | - // Run the blocking operation on virtual thread and make sure |
162 | | - // only one profile operation is running at a time. |
| 157 | + // Make sure only one profile operation is running at a time. |
163 | 158 | when { |
164 | 159 | mutex.isLocked -> call.respondText("Profile operation is already running") |
165 | 160 | else -> |
166 | 161 | mutex.withLock { |
167 | | - runOnVirtualThread { |
168 | | - val jfrPath = createTempFile("profile", ".jfr") |
169 | | - RecordingStream(Configuration.getConfiguration("profile")).use { |
170 | | - it.setMaxSize(100 * 1024 * 1024) |
171 | | - it.setMaxAge(2.minutes.toJavaDuration()) |
172 | | - it.enable("jdk.CPULoad").withPeriod(100.milliseconds.toJavaDuration()) |
173 | | - it.enable("jdk.JavaMonitorEnter").withStackTrace() |
174 | | - it.startAsync() |
175 | | - Thread.sleep(5_000) |
176 | | - it.dump(jfrPath) |
177 | | - println("JFR file written to ${jfrPath.toAbsolutePath()}") |
| 162 | + val jfrPath = createTempFile("profile", ".jfr") |
| 163 | + val flightRecorder = FlightRecorder.getFlightRecorder() |
| 164 | + when (flightRecorder.recordings.isEmpty()) { |
| 165 | + true -> { |
| 166 | + println("Starting new JFR recording...") |
| 167 | + RecordingStream(Configuration.getConfiguration("profile")).use { |
| 168 | + it.setMaxSize(100 * 1000 * 1000) |
| 169 | + it.setMaxAge(2.minutes.toJavaDuration()) |
| 170 | + it.enable("jdk.CPULoad").withPeriod(100.milliseconds.toJavaDuration()) |
| 171 | + it.enable("jdk.JavaMonitorEnter").withStackTrace() |
| 172 | + it.startAsync() |
| 173 | + Thread.sleep(5.seconds.inWholeMilliseconds) |
| 174 | + it.dump(jfrPath) |
| 175 | + } |
178 | 176 | } |
179 | | - |
180 | | - when (call.request.queryParameters.contains("download")) { |
181 | | - true -> { |
182 | | - call.response.header( |
183 | | - HttpHeaders.ContentDisposition, |
184 | | - ContentDisposition.Attachment.withParameter( |
185 | | - ContentDisposition.Parameters.FileName, jfrPath.fileName.name) |
186 | | - .toString()) |
187 | | - call.respondFile(jfrPath.toFile()) |
| 177 | + else -> { |
| 178 | + println("Using existing JFR recording...") |
| 179 | + flightRecorder.takeSnapshot().use { |
| 180 | + if (it.size > 0) { |
| 181 | + it.maxSize = 50_000_000 |
| 182 | + it.maxAge = 2.minutes.toJavaDuration() |
| 183 | + it.dump(jfrPath) |
| 184 | + } |
188 | 185 | } |
189 | | - else -> { |
190 | | - val jfr2flame = jfr2flame(JfrReader(jfrPath.pathString), Arguments()) |
191 | | - val flameGraph = FlameGraph() |
192 | | - jfr2flame.convert(flameGraph) |
| 186 | + } |
| 187 | + } |
| 188 | + println("JFR file written to ${jfrPath.toAbsolutePath()}") |
| 189 | + |
| 190 | + when (call.request.queryParameters.contains("download")) { |
| 191 | + true -> { |
| 192 | + call.response.header( |
| 193 | + HttpHeaders.ContentDisposition, |
| 194 | + ContentDisposition.Attachment.withParameter( |
| 195 | + ContentDisposition.Parameters.FileName, jfrPath.fileName.name) |
| 196 | + .toString()) |
| 197 | + call.respondFile(jfrPath.toFile()) |
| 198 | + } |
| 199 | + else -> { |
| 200 | + val jfr2flame = jfr2flame(JfrReader(jfrPath.pathString), Arguments()) |
| 201 | + val flameGraph = FlameGraph() |
| 202 | + jfr2flame.convert(flameGraph) |
193 | 203 |
|
194 | | - call.respondOutputStream(contentType = ContentType.Text.Html) { |
195 | | - flameGraph.dump(PrintStream(this)) |
196 | | - } |
197 | | - jfrPath.deleteIfExists() |
| 204 | + call.respondOutputStream(contentType = ContentType.Text.Html) { |
| 205 | + flameGraph.dump(PrintStream(this)) |
198 | 206 | } |
| 207 | + jfrPath.deleteIfExists() |
199 | 208 | } |
200 | 209 | } |
201 | 210 | } |
|
0 commit comments