Skip to content

Commit d5c9f50

Browse files
committed
[jb]: monitor low memory notifications and gc overhead
1 parent 25caa87 commit d5c9f50

File tree

2 files changed

+81
-7
lines changed

2 files changed

+81
-7
lines changed

components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodClientProjectSessionTracker.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import com.intellij.openapi.diagnostic.thisLogger
1717
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
1818
import com.intellij.openapi.fileEditor.FileEditorManagerListener
1919
import com.intellij.openapi.fileTypes.LanguageFileType
20+
import com.intellij.openapi.util.LowMemoryWatcher
2021
import com.intellij.remoteDev.util.onTerminationOrNow
2122
import com.intellij.util.application
2223
import com.jetbrains.rd.util.lifetime.Lifetime
@@ -58,6 +59,17 @@ class GitpodClientProjectSessionTracker(
5859
}
5960
}
6061

62+
init {
63+
val lowMemoryWatcher = ClientId.withClientId(session.clientId) {
64+
LowMemoryWatcher.register {
65+
manager.incLowMemoryCounter()
66+
}
67+
}
68+
lifetime.onTerminationOrNow {
69+
lowMemoryWatcher.stop()
70+
}
71+
}
72+
6173
private fun isExposedServedPort(port: Status.PortsStatus?) : Boolean {
6274
if (port === null) {
6375
return false

components/ide/jetbrains/backend-plugin/src/main/kotlin/io/gitpod/jetbrains/remote/GitpodManager.kt

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import com.intellij.openapi.extensions.PluginId
1515
import com.intellij.remoteDev.util.onTerminationOrNow
1616
import com.intellij.util.application
1717
import com.jetbrains.rd.util.lifetime.Lifetime
18+
import com.sun.management.GarbageCollectionNotificationInfo
1819
import git4idea.config.GitVcsApplicationSettings
1920
import io.gitpod.gitpodprotocol.api.GitpodClient
2021
import io.gitpod.gitpodprotocol.api.GitpodServerLauncher
@@ -28,6 +29,7 @@ import io.grpc.ManagedChannelBuilder
2829
import io.grpc.stub.ClientCallStreamObserver
2930
import io.grpc.stub.ClientResponseObserver
3031
import io.prometheus.client.CollectorRegistry
32+
import io.prometheus.client.Counter
3133
import io.prometheus.client.Gauge
3234
import io.prometheus.client.exporter.PushGateway
3335
import kotlinx.coroutines.GlobalScope
@@ -37,13 +39,20 @@ import kotlinx.coroutines.guava.asDeferred
3739
import kotlinx.coroutines.isActive
3840
import kotlinx.coroutines.launch
3941
import org.jetbrains.ide.BuiltInServerManager
42+
import java.lang.management.ManagementFactory
4043
import java.net.URI
4144
import java.net.http.HttpClient
4245
import java.net.http.HttpRequest
4346
import java.net.http.HttpResponse
4447
import java.time.Duration
4548
import java.util.concurrent.CancellationException
4649
import java.util.concurrent.CompletableFuture
50+
import java.util.concurrent.TimeUnit
51+
import javax.management.ListenerNotFoundException
52+
import javax.management.Notification
53+
import javax.management.NotificationEmitter
54+
import javax.management.NotificationListener
55+
import javax.management.openmbean.CompositeData
4756
import javax.websocket.DeploymentException
4857

4958
@Service
@@ -64,13 +73,65 @@ class GitpodManager : Disposable {
6473
lifetime.terminate()
6574
}
6675

76+
77+
private val registry = CollectorRegistry()
78+
79+
private val lowMemoryCounter = Counter.build()
80+
.name("gitpod_jb_backend_low_memory_counter")
81+
.help("Low memory notification counter")
82+
.labelNames("product", "qualifier")
83+
.register(registry)
84+
fun incLowMemoryCounter() {
85+
lowMemoryCounter.labels(backendKind, backendQualifier).inc()
86+
}
87+
88+
private val gcOverheadGauge = Gauge.build()
89+
.name("gitpod_jb_backend_gc_overhead")
90+
.help("An approximation of the percent of CPU time used by GC activities in the range [0..1]")
91+
.labelNames("product", "qualifier")
92+
.register(registry)
93+
init {
94+
val startTime = System.nanoTime()
95+
var gcPauseTime = 0L
96+
val listener = object : NotificationListener {
97+
override fun handleNotification(notification: Notification, ref: Any?) {
98+
val notificationInfo = GarbageCollectionNotificationInfo.from(notification.userData as CompositeData)
99+
if ("No GC" == notificationInfo.gcCause || "Shenandoah Cycles" == notificationInfo.gcName || "ZGC Cycles" == notificationInfo.gcName) {
100+
// see https://github.com/micrometer-metrics/micrometer/blob/b617100e92f55584f3207a29c8e0338d73042b73/micrometer-core/src/main/java/io/micrometer/core/instrument/binder/jvm/JvmMemory.java#L37
101+
return
102+
}
103+
gcPauseTime += notificationInfo.gcInfo.duration
104+
val totalTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
105+
val gcOverhead = gcPauseTime / totalTime.toDouble()
106+
gcOverheadGauge.labels(backendKind, backendQualifier).set(gcOverhead)
107+
}
108+
}
109+
for (gcBean in ManagementFactory.getGarbageCollectorMXBeans()) {
110+
if (gcBean !is NotificationEmitter) {
111+
continue
112+
}
113+
gcBean.addNotificationListener(listener,
114+
{ notification: Notification ->
115+
(notification.type == GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION)
116+
}, null
117+
)
118+
lifetime.onTerminationOrNow {
119+
try {
120+
gcBean.removeNotificationListener(listener)
121+
} catch (ignore: ListenerNotFoundException) {
122+
}
123+
}
124+
}
125+
}
126+
67127
init {
68-
val monitoringJob = GlobalScope.launch {
128+
val monitoringJob = GlobalScope.launch {
69129
if (application.isHeadlessEnvironment) {
70130
return@launch
71131
}
72132
val pg = PushGateway("localhost:22999")
73-
val registry = CollectorRegistry()
133+
// prod: count(max_over_time(gitpod_jb_backend_memory_used_bytes/gitpod_jb_backend_memory_max_bytes)[5m:] > 0.9)
134+
// prev: count(max_over_time(gitpod_jb_backend_memory_used_bytes[5m:])/max_over_time(gitpod_jb_backend_memory_max_bytes[5m:]) > 0.8)
74135
val allocatedGauge = Gauge.build()
75136
.name("gitpod_jb_backend_memory_max_bytes")
76137
.help("Total allocated memory of JB backend in bytes.")
@@ -81,7 +142,8 @@ class GitpodManager : Disposable {
81142
.help("Used memory of JB backend in bytes.")
82143
.labelNames("product", "qualifier")
83144
.register(registry)
84-
while(isActive) {
145+
146+
while (isActive) {
85147
val totalMemory = Runtime.getRuntime().totalMemory()
86148
allocatedGauge.labels(backendKind, backendQualifier).set(totalMemory.toDouble())
87149
val usedMemory = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
@@ -108,7 +170,7 @@ class GitpodManager : Disposable {
108170
.connectTimeout(Duration.ofSeconds(5))
109171
.build()
110172
val httpRequest = HttpRequest.newBuilder()
111-
.uri(URI.create("http://localhost:24000/gatewayLink?backendPort=${backendPort}"))
173+
.uri(URI.create("http://localhost:24000/gatewayLink?backendPort=$backendPort"))
112174
.GET()
113175
.build()
114176
val response =
@@ -260,14 +322,14 @@ class GitpodManager : Disposable {
260322
tokenResponse.token
261323
)
262324
} finally {
263-
Thread.currentThread().contextClassLoader = originalClassLoader;
325+
Thread.currentThread().contextClassLoader = originalClassLoader
264326
}
265327
}
266328

267329
val minReconnectionDelay = 2 * 1000L
268330
val maxReconnectionDelay = 30 * 1000L
269-
val reconnectionDelayGrowFactor = 1.5;
270-
var reconnectionDelay = minReconnectionDelay;
331+
val reconnectionDelayGrowFactor = 1.5
332+
var reconnectionDelay = minReconnectionDelay
271333
val gitpodHost = info.gitpodApi.host
272334
var closeReason: Any = "cancelled"
273335
try {

0 commit comments

Comments
 (0)