Coverage Summary for Class: Profiling (dev.suresh)
Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
Profiling |
0%
(0/9)
|
|
0%
(0/8)
|
0%
(0/59)
|
Profiling$flameGraph-VtjQ1oo$$inlined$runOnVirtualThread$1 |
0%
(0/1)
|
|
Profiling$heapdump$$inlined$runOnVirtualThread$1 |
0%
(0/1)
|
|
Profiling$jfrSnapshot$2$3 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/4)
|
Profiling$jfrSnapshot-KLykuaI$$inlined$runOnVirtualThread$1 |
0%
(0/1)
|
|
Profiling$threaddump$$inlined$runOnVirtualThread$1 |
0%
(0/1)
|
|
Total |
0%
(0/14)
|
|
0%
(0/9)
|
0%
(0/63)
|
@file:Suppress("DuplicatedCode")
package dev.suresh
import Arguments
import FlameGraph
import com.sun.management.HotSpotDiagnosticMXBean
import io.github.oshai.kotlinlogging.KotlinLogging
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.lang.management.ManagementFactory
import java.nio.charset.StandardCharsets
import java.nio.file.Path
import jdk.jfr.Configuration
import jdk.jfr.FlightRecorder
import jdk.jfr.consumer.RecordingStream
import jdk.management.VirtualThreadSchedulerMXBean
import jfr2flame
import kotlin.io.path.createTempFile
import kotlin.io.path.deleteIfExists
import kotlin.io.path.pathString
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
import one.jfr.JfrReader
object Profiling {
private val log = KotlinLogging.logger {}
const val diagnosticObjName = "com.sun.management:type=HotSpotDiagnostic"
val virtualThreadMxBean by lazy {
ManagementFactory.getPlatformMXBean(VirtualThreadSchedulerMXBean::class.java)
}
suspend fun threaddump(): Path = runOnVirtualThread {
val server = ManagementFactory.getPlatformMBeanServer()
val hotspot =
ManagementFactory.newPlatformMXBeanProxy(
server, diagnosticObjName, HotSpotDiagnosticMXBean::class.java)
val heapDumpPath = createTempFile("heapdump", ".hprof")
heapDumpPath.deleteIfExists()
// hotspot.dumpThreads(ThreadDumpFormat.valueOf())
hotspot.dumpHeap(heapDumpPath.pathString, true)
heapDumpPath
}
suspend fun heapdump(): Path = runOnVirtualThread {
val server = ManagementFactory.getPlatformMBeanServer()
val hotspot =
ManagementFactory.newPlatformMXBeanProxy(
server, diagnosticObjName, HotSpotDiagnosticMXBean::class.java)
val heapDumpPath = createTempFile("heapdump", ".hprof")
heapDumpPath.deleteIfExists()
hotspot.dumpHeap(heapDumpPath.pathString, true)
heapDumpPath
}
suspend fun jfrSnapshot(maxAge: Duration = 2.minutes, maxSizeBytes: Long = 100_000_000): Path =
runOnVirtualThread {
val jfrPath = createTempFile("profile", ".jfr")
val flightRecorder = FlightRecorder.getFlightRecorder()
when {
flightRecorder.recordings.isEmpty() ->
RecordingStream(Configuration.getConfiguration("profile")).use {
it.setMaxSize(maxSizeBytes)
it.setMaxAge(maxAge.toJavaDuration())
it.enable("jdk.CPULoad").withPeriod(100.milliseconds.toJavaDuration())
it.enable("jdk.JavaMonitorEnter").withStackTrace()
it.startAsync()
Thread.sleep(5.seconds.toJavaDuration())
it.dump(jfrPath)
}
else ->
flightRecorder.takeSnapshot().use {
if (it.size > 0) {
it.maxSize = maxSizeBytes
it.maxAge = maxAge.toJavaDuration()
it.dump(jfrPath)
}
}
}
log.info { "JFR file written to ${jfrPath.toAbsolutePath()}" }
jfrPath
}
suspend fun flameGraph(maxAge: Duration = 2.minutes): String = runOnVirtualThread {
val jfrPath = jfrSnapshot(maxAge = maxAge, maxSizeBytes = 100_000_000)
JfrReader(jfrPath.pathString).use {
val jfr2flame = jfr2flame(it, Arguments())
val flameGraph = FlameGraph()
jfr2flame.convert(flameGraph)
val bos = ByteArrayOutputStream()
flameGraph.dump(PrintStream(bos))
jfrPath.deleteIfExists()
bos.toString(StandardCharsets.UTF_8)
}
}
}