Skip to content

[jb] check maxHeapSize of IDE server and guide users to set vmoptions in .gitpod.yml #11002

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

Closed
wants to merge 1 commit into from
Closed
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
4 changes: 4 additions & 0 deletions components/ide/jetbrains/backend-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ tasks {
useJUnitPlatform()
}

runIde {
jvmArgs = listOf("-Xmx2096m")
}

runPluginVerifier {
ideVersions.set(properties("pluginVerifierIdeVersions").split(',').map(String::trim).filter(String::isNotEmpty))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ platformType=IU
platformDownloadSources=true
# Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
# Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22
platformPlugins=Git4Idea, org.jetbrains.plugins.terminal, com.jetbrains.codeWithMe
platformPlugins=Git4Idea, org.jetbrains.plugins.terminal, com.jetbrains.codeWithMe, org.jetbrains.plugins.yaml
# Opt-out flag for bundling Kotlin standard library.
# See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details.
kotlin.stdlib.default.dependency=false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.

package io.gitpod.jetbrains.remote

import com.intellij.diagnostic.VMOptions
import com.intellij.ide.BrowserUtil
import com.intellij.ide.actions.OpenFileAction
import com.intellij.notification.NotificationAction
import com.intellij.notification.NotificationGroupManager
import com.intellij.notification.NotificationType
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.StartupActivity
import com.intellij.openapi.util.BuildNumber
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.PsiManager
import com.intellij.util.application
import io.gitpod.jetbrains.remote.utils.GitpodConfig
import io.gitpod.jetbrains.remote.utils.GitpodConfig.YamlKey.jetbrains
import io.gitpod.jetbrains.remote.utils.GitpodConfig.YamlKey.vmOptions
import io.gitpod.jetbrains.remote.utils.GitpodConfig.defaultXmxOption
import io.gitpod.jetbrains.remote.utils.GitpodConfig.gitpodYamlFile
import io.gitpod.jetbrains.remote.utils.GitpodConfig.gitpodYamlReferenceLink
import org.jetbrains.yaml.YAMLFileType
import org.jetbrains.yaml.YAMLUtil
import org.jetbrains.yaml.psi.YAMLFile
import org.jetbrains.yaml.psi.YAMLKeyValue
import java.nio.file.Paths

class GitpodStartupActivity : StartupActivity.Background {
Copy link
Member

Choose a reason for hiding this comment

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

We should perform validation whenever content of .gitpod.yml is changing even if dirty. Not only once. Should not it be rather local inspection?


override fun runActivity(project: Project) {
application.invokeLater { checkMemoryOption(project) }
}

private fun checkMemoryOption(project: Project) {
val productCode = BuildNumber.currentVersion().productCode
val productName = GitpodConfig.getJetBrainsProductName(productCode) ?: return
// it's ok for .gitpod.yml to not exist
var vmOptions = emptyList<String>()
getGitpodYamlVirtualFile(project)?.let {
val vmOptionsKeyValue = getGitpodYamlVMOptionsPsiElement(project, it, productName)
vmOptions = vmOptionsKeyValue?.valueText?.split("\\s".toRegex()) ?: emptyList()
}
// if there is no -Xmx option from .gitpod.yml, compare runtime maxHeapSize with default value
var xmxVmOptions = vmOptions.filter { it.startsWith("-Xmx") }
if (xmxVmOptions.isEmpty()) {
xmxVmOptions = listOf(defaultXmxOption)
}
// the rightmost -Xmx option is the one to take effect (after deduplication)
val finalXmx = xmxVmOptions.last()
// shift right 20 (xmxInBytes >> 20) to convert to MiB
val finalXmxValueInMiB = VMOptions.parseMemoryOption(finalXmx.substringAfter("-Xmx")).shr(20)
val runtimeXmxValueInMiB = Runtime.getRuntime().maxMemory().shr(20)
if (finalXmxValueInMiB != runtimeXmxValueInMiB) {
showEditVMOptionsNotification(project, runtimeXmxValueInMiB, finalXmxValueInMiB, productName)
}
}

private fun getGitpodYamlVirtualFile(project: Project): VirtualFile? {
val basePath = project.basePath ?: return null
return VfsUtil.findFile(Paths.get(basePath, gitpodYamlFile), true)
}

private fun getGitpodYamlVMOptionsPsiElement(
project: Project,
virtualFile: VirtualFile,
productName: String
): YAMLKeyValue? {
val psiFile = PsiManager.getInstance(project).findFile(virtualFile) as? YAMLFile ?: return null
return YAMLUtil.getQualifiedKeyInFile(psiFile, jetbrains, productName, vmOptions)
}

private fun showEditVMOptionsNotification(project: Project, runtimeXmxMiB: Long, configXmxMiB: Long, productName: String) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we need notificaiotns. They are too loud. Can we use inspections instead? https://plugins.jetbrains.com/docs/intellij/code-inspections.html

val notificationGroup = NotificationGroupManager.getInstance().getNotificationGroup("Gitpod Notifications")
val title = "Gitpod memory settings"
val message = """
|Current maxHeapSize <code>-Xmx${runtimeXmxMiB}m</code> is not matched to configured <code>-Xmx${configXmxMiB}m</code>.<br/>
Copy link
Member

Choose a reason for hiding this comment

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

There should be a warning inspection:
IDE's max heap size (-Xmx) is {runtimeValue}M, but not configured in .gitpod.yml - if it is missing in .gitpod.yml
IDE's max heap size (-Xmx) is {runtimeValue}M, but {configuredXmx}M in .gitpod.yml - if it is defined in .gitpod.yml

There should be quick fixes:

  • Add -Xmx{runtimeValue}M to .gitpod.yml (both cases) - It should automatically create .gitpod.yml if necessary and sections or update vmoptions string
  • Apply default -Xmx{defaultValue} (if not in .gitpod.yml) - it should apply default value using VMOptions api
  • Apply -Xmx{defaultValue} configured in .gitpod.yml (if not in .gitpod.yml) - it should apply configured value using VMOptions api

|Set vmoptions in .gitpod.yml
""".trimMargin()
val notification = notificationGroup.createNotification(title, message, NotificationType.WARNING)
// edit or create .gitpod.yaml
val virtualFile = getGitpodYamlVirtualFile(project)
val primaryAction = if (virtualFile != null) {
editGitpodYamlAction(project, virtualFile, productName)
} else {
createGitpodYamlAction(project, productName, runtimeXmxMiB)
}
notification.addAction(primaryAction)
// show gitpod.yml reference
val helpAction = NotificationAction.createSimple("More info") {
BrowserUtil.browse(gitpodYamlReferenceLink)
}
notification.addAction(helpAction)
notification.notify(project)
}

private fun editGitpodYamlAction(project: Project, gitpodYaml: VirtualFile, productName: String): NotificationAction {
return NotificationAction.createSimple("Edit .gitpod.yml") {
OpenFileAction.openFile(gitpodYaml, project)
val vmOptionsKeyValue = getGitpodYamlVMOptionsPsiElement(project, gitpodYaml, productName)
// navigate caret to "vmoptions" if exist
vmOptionsKeyValue?.navigate(true)
Copy link
Member

Choose a reason for hiding this comment

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

Cannot we automatically update it, instead of asking a user to edit?

}
}

private fun createGitpodYamlAction(project: Project, productName: String, runtimeXmxMiB: Long): NotificationAction {
return NotificationAction.createSimple("Create .gitpod.yml") {
application.runWriteAction {
val psiFile = PsiFileFactory.getInstance(project).createFileFromText(
gitpodYamlFile,
YAMLFileType.YML,
GitpodConfig.YamlTemplate.buildVMOptions(productName, runtimeXmxMiB)
)
project.basePath?.let { basePath ->
LocalFileSystem.getInstance().findFileByPath(basePath)?.let { dir ->
PsiManager.getInstance(project).findDirectory(dir)?.add(psiFile)
// refresh VFS and open created .gitpod.yml in editor
getGitpodYamlVirtualFile(project)?.let { OpenFileAction.openFile(it, project) }
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.

package io.gitpod.jetbrains.remote.utils

/**
* Constants and util functions for Gitpod config spec
*/
object GitpodConfig {

const val gitpodYamlFile = ".gitpod.yml"
const val defaultXmxOption = "-Xmx2048m"
Copy link
Member

Choose a reason for hiding this comment

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

I think we need to provide as env var from go status endpoint where we set it.

const val gitpodYamlReferenceLink = "https://www.gitpod.io/docs/references/gitpod-yml#jetbrainsproductvmoptions"

object YamlKey {
const val jetbrains = "jetbrains"
const val vmOptions = "vmoptions"
}

object YamlTemplate {

fun buildVMOptions(productName: String, xmxValueMiB: Long): String {
return """
|jetbrains:
| $productName:
| vmoptions: "-Xmx${xmxValueMiB}m"
""".trimMargin()
}
}

/**
* map JetBrains IDE productCode to YAML key for .gitpod.yml
*/
fun getJetBrainsProductName(productCode: String): String? {
return when (productCode) {
"IC" -> "intellij"
"IU" -> "intellij"
"PS" -> "phpstorm"
"PY" -> "pycharm"
"GO" -> "goland"
else -> null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
<plugin id="Git4Idea"/>
<plugin id="org.jetbrains.plugins.terminal"/>
<plugin id="com.jetbrains.codeWithMe"/>
<plugin id="org.jetbrains.plugins.yaml"/>
</dependencies>

<extensions defaultExtensionNs="com.intellij">
<postStartupActivity implementation="io.gitpod.jetbrains.remote.GitpodStartupActivity"/>
<applicationService serviceImplementation="io.gitpod.jetbrains.remote.services.HeartbeatService" preload="true"/>
<applicationService serviceImplementation="io.gitpod.jetbrains.remote.GitpodManager" preload="true"/>
<notificationGroup id="Gitpod Notifications" displayType="BALLOON" isLogByDefault="false" />
Expand Down