|
1 | 1 | package dev.suresh.routes |
2 | 2 |
|
3 | | -import Arguments |
4 | | -import FlameGraph |
5 | | -import com.sun.management.HotSpotDiagnosticMXBean |
| 3 | +import dev.suresh.Profiling |
6 | 4 | import dev.suresh.jvmRuntimeInfo |
7 | 5 | import dev.suresh.plugins.debug |
8 | 6 | import io.ktor.http.* |
| 7 | +import io.ktor.http.ContentDisposition.Companion.Attachment |
| 8 | +import io.ktor.http.ContentDisposition.Parameters.FileName |
| 9 | +import io.ktor.http.HttpHeaders.ContentDisposition |
| 10 | +import io.ktor.server.application.* |
9 | 11 | import io.ktor.server.http.content.* |
| 12 | +import io.ktor.server.plugins.* |
10 | 13 | import io.ktor.server.response.* |
11 | 14 | import io.ktor.server.routing.* |
| 15 | +import io.ktor.server.websocket.* |
| 16 | +import io.ktor.websocket.* |
| 17 | +import io.ktor.websocket.Frame.* |
12 | 18 | import java.io.File |
13 | | -import java.io.PrintStream |
14 | | -import java.lang.management.ManagementFactory |
15 | | -import jdk.jfr.Configuration |
16 | | -import jdk.jfr.FlightRecorder |
17 | | -import jdk.jfr.consumer.RecordingStream |
18 | | -import jfr2flame |
| 19 | +import java.util.concurrent.ConcurrentHashMap |
19 | 20 | import kotlin.io.path.* |
20 | | -import kotlin.time.Duration.Companion.milliseconds |
21 | | -import kotlin.time.Duration.Companion.minutes |
22 | | -import kotlin.time.Duration.Companion.seconds |
23 | | -import kotlin.time.toJavaDuration |
24 | 21 | import kotlinx.coroutines.sync.Mutex |
25 | 22 | import kotlinx.coroutines.sync.withLock |
26 | | -import one.jfr.JfrReader |
27 | 23 |
|
28 | 24 | private val DEBUG = ScopedValue.newInstance<Boolean>() |
29 | 25 |
|
@@ -147,79 +143,63 @@ fun Route.mgmtRoutes() { |
147 | 143 | } |
148 | 144 |
|
149 | 145 | get("/profile") { |
150 | | - when { |
151 | | - mutex.isLocked -> call.respondText("Profile operation is already running") |
| 146 | + when (mutex.isLocked) { |
| 147 | + true -> call.respondText("Profile operation is already running") |
152 | 148 | else -> |
153 | 149 | mutex.withLock { |
154 | | - val jfrPath = createTempFile("profile", ".jfr") |
155 | | - val flightRecorder = FlightRecorder.getFlightRecorder() |
156 | | - when (flightRecorder.recordings.isEmpty()) { |
157 | | - true -> |
158 | | - RecordingStream(Configuration.getConfiguration("profile")).use { |
159 | | - it.setMaxSize(100 * 1000 * 1000) |
160 | | - it.setMaxAge(2.minutes.toJavaDuration()) |
161 | | - it.enable("jdk.CPULoad").withPeriod(100.milliseconds.toJavaDuration()) |
162 | | - it.enable("jdk.JavaMonitorEnter").withStackTrace() |
163 | | - it.startAsync() |
164 | | - Thread.sleep(5.seconds.inWholeMilliseconds) |
165 | | - it.dump(jfrPath) |
166 | | - } |
167 | | - else -> |
168 | | - flightRecorder.takeSnapshot().use { |
169 | | - if (it.size > 0) { |
170 | | - it.maxSize = 50_000_000 |
171 | | - it.maxAge = 2.minutes.toJavaDuration() |
172 | | - it.dump(jfrPath) |
173 | | - } |
174 | | - } |
175 | | - } |
176 | | - |
177 | | - println("JFR file written to ${jfrPath.toAbsolutePath()}") |
178 | | - // RecordingFile.readAllEvents(jfrPath).isNotEmpty() |
179 | | - |
180 | 150 | when (call.request.queryParameters.contains("download")) { |
181 | 151 | true -> { |
| 152 | + val jfrPath = Profiling.jfrSnapshot() |
182 | 153 | call.response.header( |
183 | | - HttpHeaders.ContentDisposition, |
184 | | - ContentDisposition.Attachment.withParameter( |
185 | | - ContentDisposition.Parameters.FileName, jfrPath.fileName.name) |
186 | | - .toString()) |
| 154 | + ContentDisposition, |
| 155 | + Attachment.withParameter(FileName, jfrPath.fileName.name).toString()) |
187 | 156 | call.respondFile(jfrPath.toFile()) |
188 | | - } |
189 | | - else -> { |
190 | | - val jfr2flame = jfr2flame(JfrReader(jfrPath.pathString), Arguments()) |
191 | | - val flameGraph = FlameGraph() |
192 | | - jfr2flame.convert(flameGraph) |
193 | | - |
194 | | - call.respondOutputStream(contentType = ContentType.Text.Html) { |
195 | | - flameGraph.dump(PrintStream(this)) |
196 | | - } |
197 | 157 | jfrPath.deleteIfExists() |
198 | 158 | } |
| 159 | + else -> |
| 160 | + call.respondText(contentType = ContentType.Text.Html) { Profiling.flameGraph() } |
199 | 161 | } |
200 | 162 | } |
201 | 163 | } |
202 | 164 | } |
203 | 165 |
|
204 | 166 | get("/heapdump") { |
205 | | - val server = ManagementFactory.getPlatformMBeanServer() |
206 | | - val hotspot = |
207 | | - ManagementFactory.newPlatformMXBeanProxy( |
208 | | - server, |
209 | | - "com.sun.management:type=HotSpotDiagnostic", |
210 | | - HotSpotDiagnosticMXBean::class.java) |
211 | | - |
212 | | - val heapDumpPath = createTempFile("heapdump", ".hprof") |
213 | | - heapDumpPath.deleteIfExists() |
214 | | - hotspot.dumpHeap(heapDumpPath.pathString, true) |
| 167 | + val heapDumpPath = Profiling.heapdump() |
215 | 168 | call.response.header( |
216 | | - HttpHeaders.ContentDisposition, |
217 | | - ContentDisposition.Attachment.withParameter( |
218 | | - ContentDisposition.Parameters.FileName, heapDumpPath.fileName.name) |
219 | | - .toString()) |
| 169 | + ContentDisposition, |
| 170 | + Attachment.withParameter(FileName, heapDumpPath.fileName.name).toString()) |
220 | 171 | call.respondFile(heapDumpPath.toFile()) |
221 | 172 | heapDumpPath.deleteIfExists() |
222 | 173 | } |
| 174 | + |
| 175 | + webSocketRaw("/term") { |
| 176 | + val ip = call.request.origin.remoteHost |
| 177 | + application.log.info("Got WebSocket connection from $ip") |
| 178 | + send("Connected to server using WebSocket: $ip") |
| 179 | + send("Type 'hi' to proceed") |
| 180 | + |
| 181 | + // create concurrent hashset |
| 182 | + val conn = ConcurrentHashMap.newKeySet<Frame>() |
| 183 | + for (frame in incoming) { |
| 184 | + when (frame) { |
| 185 | + is Text -> { |
| 186 | + val text = frame.readText() |
| 187 | + application.log.info("Received $text") |
| 188 | + when (text.lowercase()) { |
| 189 | + "hi" -> send("Hello, $ip!") |
| 190 | + "bye" -> { |
| 191 | + send("Goodbye, $ip. Closing from client!") |
| 192 | + close(CloseReason(CloseReason.Codes.NORMAL, "Client said BYE")) |
| 193 | + } |
| 194 | + else -> send("Sorry, I don't understand") |
| 195 | + } |
| 196 | + } |
| 197 | + is Binary -> application.log.info("Binary frame ${frame.data.decodeToString()}") |
| 198 | + is Close -> application.log.info("Connection closed from Server") |
| 199 | + else -> application.log.info("Unknown frame ${frame.frameType}") |
| 200 | + } |
| 201 | + } |
| 202 | + } |
223 | 203 | } |
224 | 204 |
|
225 | 205 | // val profiler: AsyncProfiler? by lazy { |
|
0 commit comments