Skip to content

[jb] configure vmoptions for intellij backend server #10175

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion components/ide/jetbrains/image/leeway.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ RUN apk add --no-cache --upgrade curl gzip tar unzip
RUN curl -sSLo backend.tar.gz "$JETBRAINS_BACKEND_URL" && tar -xf backend.tar.gz --strip-components=1 && rm backend.tar.gz
COPY --chown=33333:33333 components-ide-jetbrains-backend-plugin--plugin/build/distributions/gitpod-remote-0.0.1.zip /workdir
RUN unzip gitpod-remote-0.0.1.zip -d plugins/ && rm gitpod-remote-0.0.1.zip
RUN printf '\n-Dgtw.disable.exit.dialog=true\n' >> $(find /workdir/bin/ -name "*64.vmoptions")
# enable shared indexes by default
RUN printf '\nshared.indexes.download.auto.consent=true' >> "/workdir/bin/idea.properties"

Expand Down
68 changes: 57 additions & 11 deletions components/ide/jetbrains/image/status/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ func main() {
}
}

err = configureXmx(alias)
err = configureVMOptions(alias)
if err != nil {
log.WithError(err).Error("failed to configure backend Xmx")
log.WithError(err).Error("failed to configure vmoptions")
}
go run(wsInfo, alias)

Expand Down Expand Up @@ -307,19 +307,65 @@ func handleSignal(projectPath string) {
log.Info("asked IDE to terminate")
}

func configureXmx(alias string) error {
xmx := os.Getenv(strings.ToUpper(alias) + "_XMX")
if xmx == "" {
return nil
func configureVMOptions(alias string) error {
idePrefix := alias
if alias == "intellij" {
idePrefix = "idea"
}
launcherPath := "/ide-desktop/backend/plugins/remote-dev-server/bin/launcher.sh"
content, err := ioutil.ReadFile(launcherPath)
// [idea64|goland64|pycharm64|phpstorm64].vmoptions
path := fmt.Sprintf("/ide-desktop/backend/bin/%s64.vmoptions", idePrefix)
content, err := ioutil.ReadFile(path)
if err != nil {
return err
}
// by default remote dev already set -Xmx2048m, see /ide-desktop/backend/plugins/remote-dev-server/bin/launcher.sh
newContent := strings.Replace(string(content), "-Xmx2048m", "-Xmx"+xmx, 1)
return ioutil.WriteFile(launcherPath, []byte(newContent), 0)
newContent := updateVMOptions(alias, string(content))
return ioutil.WriteFile(path, []byte(newContent), 0)
}

// deduplicateVMOption append new VMOptions onto old VMOptions and remove any duplicated leftmost options
func deduplicateVMOption(oldLines []string, newLines []string, predicate func(l, r string) bool) []string {
var result []string
var merged = append(oldLines, newLines...)
for i, left := range merged {
for _, right := range merged[i+1:] {
if predicate(left, right) {
left = ""
break
}
}
if left != "" {
result = append(result, left)
}
}
return result
}

func updateVMOptions(alias string, content string) string {
// inspired by how intellij platform merge the VMOptions
// https://github.com/JetBrains/intellij-community/blob/master/platform/platform-impl/src/com/intellij/openapi/application/ConfigImportHelper.java#L1115
filterFunc := func(l, r string) bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why it is not part of deduplicateVMOption?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make it easier to have finer control over the type of VMOptions to dedup. Currently, only memory-related VMOptions (-Xmx / -Xms, etc.) are handled, however, if we need to deal with VMOptions such as GC-related by another feature toggle (e.g. a separate env var). Putting all filter logic into deduplicateVMOption is a bit messy.

It's similar to updateVMOptions (https://github.com/JetBrains/intellij-community/blob/master/platform/platform-impl/src/com/intellij/openapi/application/ConfigImportHelper.java#L1068) from the Intellij codebase

isEqual := l == r
isXmx := strings.HasPrefix(l, "-Xmx") && strings.HasPrefix(r, "-Xmx")
isXms := strings.HasPrefix(l, "-Xms") && strings.HasPrefix(r, "-Xms")
isXss := strings.HasPrefix(l, "-Xss") && strings.HasPrefix(r, "-Xss")
isXXOptions := strings.HasPrefix(l, "-XX:") && strings.HasPrefix(r, "-XX:") &&
strings.Split(l, "=")[0] == strings.Split(r, "=")[0]
return isEqual || isXmx || isXms || isXss || isXXOptions
}
// original vmoptions (inherited from $JETBRAINS_IDE_HOME/bin/idea64.vmoptions)
ideaVMOptionsLines := strings.Fields(content)
// Gitpod's default customization
gitpodVMOptions := []string{"-Dgtw.disable.exit.dialog=true"}
vmoptions := deduplicateVMOption(ideaVMOptionsLines, gitpodVMOptions, filterFunc)

// user-defined vmoptions
userVMOptionsVar := os.Getenv(strings.ToUpper(alias) + "_VMOPTIONS")
userVMOptions := strings.Fields(userVMOptionsVar)
if len(userVMOptions) > 0 {
vmoptions = deduplicateVMOption(vmoptions, userVMOptions, filterFunc)
}
// vmoptions file should end with a newline
return strings.Join(vmoptions, "\n") + "\n"
}

/**
Expand Down
43 changes: 43 additions & 0 deletions components/ide/jetbrains/image/status/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
package main

import (
"strings"
"testing"

protocol "github.com/gitpod-io/gitpod/gitpod-protocol"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)

func TestGetProductConfig(t *testing.T) {
Expand All @@ -23,3 +25,44 @@ func TestGetProductConfig(t *testing.T) {
t.Errorf("unexpected output (-want +got):\n%s", diff)
}
}

func TestUpdateVMOptions(t *testing.T) {
tests := []struct {
Desc string
Alias string
EnvVar map[string]string
Src string
Expectation string
}{
{"goland64.vmoptions", "goland", nil, "-Xms128m\n-Xmx750m\n-Dsun.tools.attach.tmp.only=true", "-Xms128m\n-Xmx750m\n-Dsun.tools.attach.tmp.only=true\n-Dgtw.disable.exit.dialog=true"},
{"idea64.vmoptions", "intellij", nil, "-Xms128m\n-Xmx750m\n-Dsun.tools.attach.tmp.only=true", "-Xms128m\n-Xmx750m\n-Dsun.tools.attach.tmp.only=true\n-Dgtw.disable.exit.dialog=true"},
{"idea64.vmoptions (INTELLIJ_VMOPTIONS env set)", "intellij", map[string]string{"INTELLIJ_VMOPTIONS": "-Xmx2048m"}, "-Xms128m\n-Xmx750m\n-Dsun.tools.attach.tmp.only=true", "-Xms128m\n-Xmx2048m\n-Dsun.tools.attach.tmp.only=true\n-Dgtw.disable.exit.dialog=true"},
{"idea64.vmoptions (INTELLIJ_VMOPTIONS env set)", "intellij", map[string]string{"INTELLIJ_VMOPTIONS": "-Xmx4096m"}, "-Xms128m\n-Xmx2g\n-Dsun.tools.attach.tmp.only=true", "-Xms128m\n-Xmx4096m\n-Dsun.tools.attach.tmp.only=true\n-Dgtw.disable.exit.dialog=true"},
{"idea64.vmoptions (INTELLIJ_VMOPTIONS env set)", "intellij", map[string]string{"INTELLIJ_VMOPTIONS": "-Xmx4096m -XX:MaxRAMPercentage=75"}, "-Xms128m\n-Xmx2g\n-Dsun.tools.attach.tmp.only=true", "-Xms128m\n-Xmx4096m\n-XX:MaxRAMPercentage=75\n-Dsun.tools.attach.tmp.only=true\n-Dgtw.disable.exit.dialog=true"},
{"goland64.vmoptions (GOLAND_VMOPTIONS env set with conflicting options)", "goland", map[string]string{"GOLAND_VMOPTIONS": "-ea -XX:+IgnoreUnrecognizedVMOptions -XX:MaxRAMPercentage=75 -XX:MaxRAMPercentage=50"}, "-Xms128m\n-Xmx2g\n-Dsun.tools.attach.tmp.only=true", "-Xms128m\n-Xmx2g\n-Dsun.tools.attach.tmp.only=true\n-Dgtw.disable.exit.dialog=true\n-ea\n-XX:+IgnoreUnrecognizedVMOptions\n-XX:MaxRAMPercentage=50"},
}
for _, test := range tests {
for v := range test.EnvVar {
t.Setenv(v, test.EnvVar[v])
}
// compare vmoptions string content equality (i.e. split into slices and compare ignore order)
lessFunc := func(a, b string) bool { return a < b }

t.Run(test.Desc, func(t *testing.T) {
actual := updateVMOptions(test.Alias, test.Src)
if diff := cmp.Diff(strings.Fields(test.Expectation), strings.Fields(actual), cmpopts.SortSlices(lessFunc)); diff != "" {
t.Errorf("unexpected output (-want +got):\n%s", diff)
}
})

t.Run("updateVMOptions multiple time should be stable", func(t *testing.T) {
actual := test.Src
for i := 0; i < 5; i++ {
actual = updateVMOptions(test.Alias, actual)
if diff := cmp.Diff(strings.Fields(test.Expectation), strings.Fields(actual), cmpopts.SortSlices(lessFunc)); diff != "" {
t.Errorf("unexpected output (-want +got):\n%s", diff)
}
}
})
}
}