Skip to content

Commit c11e664

Browse files
committed
chore: profiling changes
1 parent 8f2d838 commit c11e664

File tree

18 files changed

+251
-125
lines changed

18 files changed

+251
-125
lines changed

backend/data/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ kotlin.sourceSets {
1212
exclude(group = "org.jetbrains.pty4j", module = "purejavacomm")
1313
}
1414
implementation(fileTree("lib") { include("*.jar") })
15+
// implementation(libs.graal.polyglot)
16+
// implementation(libs.graal.wasm)
1517
}
1618

1719
kotlin.srcDir("src/main/kotlin")
1820
resources.srcDir("src/main/resources")
1921
}
2022

2123
jvmTest {
22-
dependencies {}
23-
2424
kotlin.srcDir("src/test/kotlin")
2525
resources.srcDir("src/test/resources")
2626
}

backend/data/src/main/kotlin/dev/suresh/App.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,26 @@ fun ssh() {
5959
println("PTY child process terminated with exit code $result")
6060
ptyProcess.destroyForcibly()
6161
}
62+
63+
// fun wasm() {
64+
// val source =
65+
// Source.newBuilder(
66+
// "wasm",
67+
// """
68+
// (module
69+
// (func (export "add") (param i32 i32) (result i32)
70+
// local.get 0
71+
// local.get 1
72+
// i32.add)
73+
// )
74+
// """
75+
// .trimIndent(),
76+
// "test")
77+
// .build()
78+
// Context.newBuilder("wasm").build().use { ctx ->
79+
// val wasm = ctx.eval(source)
80+
// val add = wasm.getMember("add")
81+
// val result = add.execute(10, 20)
82+
// println(result)
83+
// }
84+
// }

backend/jvm/build.gradle.kts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ tasks {
113113
dependencies {
114114
implementation(projects.shared)
115115
implementation(projects.backend.data)
116+
implementation(projects.backend.profiling)
116117
// Server dependencies
117118
implementation(libs.ktor.server.core)
118119
implementation(libs.ktor.server.netty)
@@ -133,6 +134,7 @@ dependencies {
133134
implementation(libs.ktor.server.resources)
134135
implementation(libs.ktor.server.auth)
135136
implementation(libs.ktor.server.auth.jwt)
137+
implementation(libs.ktor.server.websockets)
136138
implementation(libs.ktor.serialization.json)
137139

138140
// Client dependencies
@@ -159,19 +161,11 @@ dependencies {
159161
implementation(libs.kotlinx.html)
160162
implementation(kotlinw("css"))
161163
implementation(libs.ktor.server.html)
162-
// constraints {
163-
// implementation(libs.kotlinx.html.get().module.toString()) {
164-
// version { strictly(libs.kotlinx.html.get().version.toString()) }
165-
// because("Ktor Issue!")
166-
// }
167-
// }
168164

169165
// Monitoring
170166
implementation(libs.ktor.cohort.core)
171167
implementation(libs.ktor.cohort.hikari)
172168
implementation(libs.micrometer.prometheus)
173-
implementation(libs.ap.converter)
174-
// implementation(libs.ap.loader.all)
175169

176170
// Logging
177171
implementation(libs.logback.classic)
@@ -190,4 +184,11 @@ dependencies {
190184

191185
// Specify the classifier using variantOf
192186
// implementation(variantOf(libs.lwjgl.opengl) { classifier("natives-linux") })
187+
188+
// constraints {
189+
// implementation(libs.kotlinx.html.get().module.toString()) {
190+
// version { strictly(libs.kotlinx.html.get().version.toString()) }
191+
// because("Ktor Issue!")
192+
// }
193+
// }
193194
}

backend/jvm/src/main/kotlin/dev/suresh/App.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,15 @@ import dev.suresh.plugins.configureHTTP
66
import dev.suresh.plugins.configureSecurity
77
import dev.suresh.plugins.configureSerialization
88
import dev.suresh.plugins.errorRoutes
9-
import dev.suresh.routes.adminRoutes
10-
import dev.suresh.routes.jvmFeatures
11-
import dev.suresh.routes.mgmtRoutes
12-
import dev.suresh.routes.webApp
9+
import dev.suresh.routes.*
1310
import io.ktor.server.application.*
1411
import io.ktor.server.netty.*
1512
import io.ktor.server.routing.*
1613
import io.ktor.util.logging.*
1714

1815
fun main(args: Array<String>) =
1916
try {
20-
SysConfig.initSysProperty()
17+
SysConfig.initSysProperties()
2118
println("Starting App ${BuildConfig.version}...")
2219
EngineMain.main(args)
2320
} catch (e: Throwable) {
@@ -33,7 +30,7 @@ fun Application.module() {
3330
routing {
3431
adminRoutes()
3532
webApp()
36-
jvmFeatures()
33+
services()
3734
mgmtRoutes()
3835
}
3936
// CoroutineScope(coroutineContext).launch {}

backend/jvm/src/main/kotlin/dev/suresh/config/SystemConfig.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package dev.suresh.config
22

33
import io.ktor.server.config.*
4+
import kotlin.io.path.Path
5+
import kotlin.io.path.exists
46
import kotlin.reflect.full.withNullability
57
import kotlin.reflect.typeOf
68
import kotlin.time.Duration
@@ -9,18 +11,19 @@ data object SysConfig {
911

1012
// Logback log directory
1113
val LOG_DIR by lazy {
12-
val os = System.getProperty("os.name")
13-
when {
14-
os.startsWith("Mac", ignoreCase = true) -> System.getProperty("user.dir")
15-
else -> System.getenv("LOG_DIR").orEmpty().ifBlank { "/log" }
14+
System.getenv("LOG_DIR").orEmpty().ifBlank {
15+
when {
16+
Path("/log").exists() -> "/log"
17+
else -> System.getProperty("user.dir")
18+
}
1619
}
1720
}
1821

1922
/**
2023
* Initializes the system properties required for the application to run. This should be invoked
2124
* before the Engine main() method is called.
2225
*/
23-
fun initSysProperty() {
26+
fun initSysProperties() {
2427
System.setProperty("jdk.tls.maxCertificateChainLength", "15")
2528
System.setProperty("jdk.includeInExceptions", "hostInfo")
2629
System.setProperty("LOG_DIR", LOG_DIR)

backend/jvm/src/main/kotlin/dev/suresh/plugins/Http.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import io.ktor.server.plugins.forwardedheaders.*
1313
import io.ktor.server.plugins.partialcontent.*
1414
import io.ktor.server.request.*
1515
import io.ktor.server.routing.*
16+
import io.ktor.server.websocket.*
17+
import kotlin.time.Duration.Companion.seconds
18+
import kotlin.time.toJavaDuration
1619
import org.slf4j.event.Level
1720

1821
fun Application.configureHTTP() {
@@ -49,6 +52,13 @@ fun Application.configureHTTP() {
4952
filter { it.isApiRoute }
5053
mdc("remoteHost") { call -> call.request.origin.remoteHost }
5154
}
55+
56+
install(WebSockets) {
57+
pingPeriod = 15.seconds.toJavaDuration()
58+
timeout = 15.seconds.toJavaDuration()
59+
maxFrameSize = Long.MAX_VALUE
60+
masking = false
61+
}
5262
}
5363

5464
val ApplicationCall.debug

backend/jvm/src/main/kotlin/dev/suresh/routes/Mgmt.kt

Lines changed: 49 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,25 @@
11
package dev.suresh.routes
22

3-
import Arguments
4-
import FlameGraph
5-
import com.sun.management.HotSpotDiagnosticMXBean
3+
import dev.suresh.Profiling
64
import dev.suresh.jvmRuntimeInfo
75
import dev.suresh.plugins.debug
86
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.*
911
import io.ktor.server.http.content.*
12+
import io.ktor.server.plugins.*
1013
import io.ktor.server.response.*
1114
import io.ktor.server.routing.*
15+
import io.ktor.server.websocket.*
16+
import io.ktor.websocket.*
17+
import io.ktor.websocket.Frame.*
1218
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
1920
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
2421
import kotlinx.coroutines.sync.Mutex
2522
import kotlinx.coroutines.sync.withLock
26-
import one.jfr.JfrReader
2723

2824
private val DEBUG = ScopedValue.newInstance<Boolean>()
2925

@@ -147,79 +143,63 @@ fun Route.mgmtRoutes() {
147143
}
148144

149145
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")
152148
else ->
153149
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-
180150
when (call.request.queryParameters.contains("download")) {
181151
true -> {
152+
val jfrPath = Profiling.jfrSnapshot()
182153
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())
187156
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-
}
197157
jfrPath.deleteIfExists()
198158
}
159+
else ->
160+
call.respondText(contentType = ContentType.Text.Html) { Profiling.flameGraph() }
199161
}
200162
}
201163
}
202164
}
203165

204166
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()
215168
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())
220171
call.respondFile(heapDumpPath.toFile())
221172
heapDumpPath.deleteIfExists()
222173
}
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+
}
223203
}
224204

225205
// val profiler: AsyncProfiler? by lazy {

backend/jvm/src/main/kotlin/dev/suresh/routes/JvmFeature.kt renamed to backend/jvm/src/main/kotlin/dev/suresh/routes/Service.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package dev.suresh.routes
22

3+
import dev.suresh.JFR
34
import dev.suresh.lang.FFM
4-
import dev.suresh.lang.JFR
55
import dev.suresh.lang.VThread
66
import dev.suresh.log.RespLogger
77
import io.github.oshai.kotlinlogging.KLogger
@@ -14,7 +14,7 @@ import java.io.Writer
1414

1515
private val logger = KotlinLogging.logger {}
1616

17-
fun Route.jvmFeatures() {
17+
fun Route.services() {
1818
get("/ffm") {
1919
call.respondLogStream {
2020
FFM.memoryLayout()
@@ -35,7 +35,7 @@ fun Route.jvmFeatures() {
3535
}
3636

3737
suspend fun ApplicationCall.respondLogStream(
38-
contentType: ContentType = ContentType.Text.Plain,
38+
contentType: ContentType = ContentType.Text.EventStream,
3939
block: suspend context(KLogger) Writer.() -> Unit
4040
) {
4141
respondTextWriter(contentType = contentType) {

0 commit comments

Comments
 (0)