@@ -15,6 +15,7 @@ import com.intellij.openapi.extensions.PluginId
15
15
import com.intellij.remoteDev.util.onTerminationOrNow
16
16
import com.intellij.util.application
17
17
import com.jetbrains.rd.util.lifetime.Lifetime
18
+ import com.sun.management.GarbageCollectionNotificationInfo
18
19
import git4idea.config.GitVcsApplicationSettings
19
20
import io.gitpod.gitpodprotocol.api.GitpodClient
20
21
import io.gitpod.gitpodprotocol.api.GitpodServerLauncher
@@ -28,6 +29,7 @@ import io.grpc.ManagedChannelBuilder
28
29
import io.grpc.stub.ClientCallStreamObserver
29
30
import io.grpc.stub.ClientResponseObserver
30
31
import io.prometheus.client.CollectorRegistry
32
+ import io.prometheus.client.Counter
31
33
import io.prometheus.client.Gauge
32
34
import io.prometheus.client.exporter.PushGateway
33
35
import kotlinx.coroutines.GlobalScope
@@ -37,13 +39,20 @@ import kotlinx.coroutines.guava.asDeferred
37
39
import kotlinx.coroutines.isActive
38
40
import kotlinx.coroutines.launch
39
41
import org.jetbrains.ide.BuiltInServerManager
42
+ import java.lang.management.ManagementFactory
40
43
import java.net.URI
41
44
import java.net.http.HttpClient
42
45
import java.net.http.HttpRequest
43
46
import java.net.http.HttpResponse
44
47
import java.time.Duration
45
48
import java.util.concurrent.CancellationException
46
49
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
47
56
import javax.websocket.DeploymentException
48
57
49
58
@Service
@@ -64,13 +73,65 @@ class GitpodManager : Disposable {
64
73
lifetime.terminate()
65
74
}
66
75
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
+
67
127
init {
68
- val monitoringJob = GlobalScope .launch {
128
+ val monitoringJob = GlobalScope .launch {
69
129
if (application.isHeadlessEnvironment) {
70
130
return @launch
71
131
}
72
132
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)
74
135
val allocatedGauge = Gauge .build()
75
136
.name(" gitpod_jb_backend_memory_max_bytes" )
76
137
.help(" Total allocated memory of JB backend in bytes." )
@@ -81,7 +142,8 @@ class GitpodManager : Disposable {
81
142
.help(" Used memory of JB backend in bytes." )
82
143
.labelNames(" product" , " qualifier" )
83
144
.register(registry)
84
- while (isActive) {
145
+
146
+ while (isActive) {
85
147
val totalMemory = Runtime .getRuntime().totalMemory()
86
148
allocatedGauge.labels(backendKind, backendQualifier).set(totalMemory.toDouble())
87
149
val usedMemory = (Runtime .getRuntime().totalMemory() - Runtime .getRuntime().freeMemory())
@@ -108,7 +170,7 @@ class GitpodManager : Disposable {
108
170
.connectTimeout(Duration .ofSeconds(5 ))
109
171
.build()
110
172
val httpRequest = HttpRequest .newBuilder()
111
- .uri(URI .create(" http://localhost:24000/gatewayLink?backendPort=${ backendPort} " ))
173
+ .uri(URI .create(" http://localhost:24000/gatewayLink?backendPort=$backendPort " ))
112
174
.GET ()
113
175
.build()
114
176
val response =
@@ -260,14 +322,14 @@ class GitpodManager : Disposable {
260
322
tokenResponse.token
261
323
)
262
324
} finally {
263
- Thread .currentThread().contextClassLoader = originalClassLoader;
325
+ Thread .currentThread().contextClassLoader = originalClassLoader
264
326
}
265
327
}
266
328
267
329
val minReconnectionDelay = 2 * 1000L
268
330
val maxReconnectionDelay = 30 * 1000L
269
- val reconnectionDelayGrowFactor = 1.5 ;
270
- var reconnectionDelay = minReconnectionDelay;
331
+ val reconnectionDelayGrowFactor = 1.5
332
+ var reconnectionDelay = minReconnectionDelay
271
333
val gitpodHost = info.gitpodApi.host
272
334
var closeReason: Any = " cancelled"
273
335
try {
0 commit comments