Skip to content

Commit faa6b30

Browse files
akosyakovroboquat
authored andcommitted
jb: push backend memory metrics to prometheus
1 parent 52e4221 commit faa6b30

File tree

4 files changed

+96
-7
lines changed

4 files changed

+96
-7
lines changed

components/ide/jetbrains/backend-plugin/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ dependencies {
3939
type = "jar"
4040
}
4141
}
42+
implementation("io.prometheus:simpleclient_pushgateway:0.15.0")
4243
compileOnly("javax.websocket:javax.websocket-api:1.1")
4344
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.18.1")
4445
testImplementation(kotlin("test"))

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

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ import io.grpc.ManagedChannel
2727
import io.grpc.ManagedChannelBuilder
2828
import io.grpc.stub.ClientCallStreamObserver
2929
import io.grpc.stub.ClientResponseObserver
30+
import io.prometheus.client.CollectorRegistry
31+
import io.prometheus.client.Gauge
32+
import io.prometheus.client.exporter.PushGateway
3033
import kotlinx.coroutines.GlobalScope
3134
import kotlinx.coroutines.delay
3235
import kotlinx.coroutines.future.await
@@ -52,13 +55,48 @@ class GitpodManager : Disposable {
5255
}
5356

5457
val devMode = System.getenv("JB_DEV").toBoolean()
58+
private val backendKind = System.getenv("JETBRAINS_GITPOD_BACKEND_KIND") ?: "unknown"
59+
private val backendQualifier = System.getenv("JETBRAINS_BACKEND_QUALIFIER") ?: "unknown"
5560

5661
private val lifetime = Lifetime.Eternal.createNested()
5762

5863
override fun dispose() {
5964
lifetime.terminate()
6065
}
6166

67+
init {
68+
val monitoringJob = GlobalScope.launch {
69+
if (application.isHeadlessEnvironment) {
70+
return@launch
71+
}
72+
val pg = PushGateway("localhost:22999")
73+
val registry = CollectorRegistry()
74+
val allocatedGauge = Gauge.build()
75+
.name("gitpod_jb_backend_memory_max_bytes")
76+
.help("Total allocated memory of JB backend in bytes.")
77+
.labelNames("product", "qualifier")
78+
.register(registry)
79+
val usedGauge = Gauge.build()
80+
.name("gitpod_jb_backend_memory_used_bytes")
81+
.help("Used memory of JB backend in bytes.")
82+
.labelNames("product", "qualifier")
83+
.register(registry)
84+
while(isActive) {
85+
val totalMemory = Runtime.getRuntime().totalMemory()
86+
allocatedGauge.labels(backendKind, backendQualifier).set(totalMemory.toDouble())
87+
val usedMemory = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
88+
usedGauge.labels(backendKind, backendQualifier).set(usedMemory.toDouble())
89+
try {
90+
pg.push(registry, "jb_backend")
91+
} catch (t: Throwable) {
92+
thisLogger().error("gitpod: failed to push monitoring metrics:", t)
93+
}
94+
delay(5000)
95+
}
96+
}
97+
lifetime.onTerminationOrNow { monitoringJob.cancel() }
98+
}
99+
62100
init {
63101
GlobalScope.launch {
64102
if (application.isHeadlessEnvironment) {
@@ -269,4 +307,4 @@ class GitpodManager : Disposable {
269307
serverJob.cancel()
270308
}
271309
}
272-
}
310+
}

components/ide/jetbrains/image/status/main.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func main() {
8888
if err != nil {
8989
log.WithError(err).Error("failed to configure backend Xmx")
9090
}
91-
go run(wsInfo)
91+
go run(wsInfo, alias)
9292

9393
http.HandleFunc("/joinLink", func(w http.ResponseWriter, r *http.Request) {
9494
backendPort := r.URL.Query().Get("backendPort")
@@ -224,11 +224,12 @@ func resolveWorkspaceInfo(ctx context.Context) (*supervisor.ContentStatusRespons
224224
return nil, nil, errors.New("failed with attempt 10 times")
225225
}
226226

227-
func run(wsInfo *supervisor.WorkspaceInfoResponse) {
227+
func run(wsInfo *supervisor.WorkspaceInfoResponse, alias string) {
228228
var args []string
229229
args = append(args, "run")
230230
args = append(args, wsInfo.GetCheckoutLocation())
231231
cmd := remoteDevServerCmd(args)
232+
cmd.Env = append(cmd.Env, "JETBRAINS_GITPOD_BACKEND_KIND="+alias)
232233
workspaceUrl, err := url.Parse(wsInfo.WorkspaceUrl)
233234
if err == nil {
234235
cmd.Env = append(cmd.Env, "JETBRAINS_GITPOD_WORKSPACE_HOST="+workspaceUrl.Hostname())

dev/ide/profile-workspace.js

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
const { execSync } = require("child_process");
88
const { promises } = require("fs");
9+
const fetch = require("node-fetch");
910
const path = require("path");
1011

1112
/**
@@ -26,13 +27,41 @@ const q90 = (arr) => {
2627

2728
(async () => {
2829
let wsPodName;
30+
let creationTimestamp;
2931
while (!wsPodName) {
30-
wsPodName = execSync("kubectl get pod -l component=workspace -o=custom-columns=:metadata.name", {
31-
encoding: "utf8",
32-
}).trim();
32+
const segments = execSync(
33+
"kubectl get pod -l component=workspace -o=custom-columns=:metadata.name,:metadata.creationTimestamp",
34+
{
35+
encoding: "utf8",
36+
},
37+
)
38+
.trim()
39+
.split(/\s+/);
40+
wsPodName = segments[0];
41+
creationTimestamp = new Date(segments[1]);
3342
await new Promise((r) => setTimeout(r, 1000));
3443
}
3544
console.log(wsPodName);
45+
console.log(creationTimestamp);
46+
47+
const query = async (prefix) => {
48+
const age = ((new Date().getTime() - creationTimestamp.getTime()) / 1000).toFixed(0);
49+
const response = await fetch.default(
50+
encodeURI(`http://localhost:9090/api/v1/query?query=${prefix}{pod="${wsPodName}"}[${age}s])/(1024*1024)`),
51+
{
52+
method: "GET",
53+
},
54+
);
55+
if (!response.ok) {
56+
console.error(`${prefix}: ${response.statusText} (${response.status})`);
57+
return "N/A";
58+
}
59+
/**
60+
* @type {{data: {result: { value: [number, string] }[]}}}
61+
*/
62+
const body = await response.json();
63+
return Number(body.data.result[0].value[1]).toFixed(2);
64+
};
3665

3766
const perfLogPath = path.resolve(__dirname, "perf.log");
3867
console.log(perfLogPath);
@@ -65,6 +94,7 @@ const q90 = (arr) => {
6594
} catch (e) {
6695
console.error(e);
6796
}
97+
6898
if (top) {
6999
cores.push(top.cpu.used);
70100
sumCores += top.cpu.used;
@@ -77,13 +107,32 @@ const q90 = (arr) => {
77107
avgMemory = sumMemory / measurements;
78108
maxMemory = Math.max(maxMemory, mem);
79109

110+
const [avgMax, maxMax, q90Max, avgUsed, maxUsed, q90user] = (
111+
await Promise.allSettled([
112+
query("avg_over_time(gitpod_jb_backend_memory_max_bytes"),
113+
query("max_over_time(gitpod_jb_backend_memory_max_bytes"),
114+
query("quantile_over_time(0.9, gitpod_jb_backend_memory_max_bytes"),
115+
query("avg_over_time(gitpod_jb_backend_memory_used_bytes"),
116+
query("max_over_time(gitpod_jb_backend_memory_used_bytes"),
117+
query("quantile_over_time(0.9, gitpod_jb_backend_memory_used_bytes"),
118+
])
119+
).map((v) => {
120+
if (v.status === "fulfilled") {
121+
return v.value;
122+
}
123+
console.error(v.reason);
124+
return "N/A";
125+
});
126+
80127
await promises.appendFile(
81128
perfLogPath,
82129
`${((new Date().getTime() - start) / 1000).toFixed(2)}s, cpu(m) ${top.cpu.used.toFixed(
83130
2,
84131
)}/${avgCores.toFixed(2)}/${maxCores.toFixed(2)}/${q90(cores).toFixed(2)}, memory(Mi) ${mem.toFixed(
85132
2,
86-
)}/${avgMemory.toFixed(2)}/${maxMemory.toFixed(2)}/${q90(mems).toFixed(2)}\n`,
133+
)}/${avgMemory.toFixed(2)}/${maxMemory.toFixed(2)}/${q90(mems).toFixed(
134+
2,
135+
)}, allocated(M) ${avgMax}/${maxMax}/${q90Max}, used(M) ${avgUsed}/${maxUsed}/${q90user}\n`,
87136
);
88137
}
89138
await new Promise((r) => setTimeout(r, 1000));

0 commit comments

Comments
 (0)