Skip to content

Commit 25dc0b7

Browse files
committed
[jb] check maxHeapSize of IDE server and guide users to set vmoptions in .gitpod.yml
- Register a background activity (GitpodStartupActivity) for handling postStart events - Add YAML plugin dependency for parsing & navigating PSI elements (AST) in .gitpod.yml - Show warning notification when the runtime Xmx is different from the configured Xmx - Add edit/create-if-missing .gitpod.yml quick actions, and navigate caret to `vmoptions` YAML key if exists - Add help action for browsing gitpod.yml references - Set -Xmx for the `runIde` task (only affect local sandbox IDE instance with the developed plugin), which can be modified for local testing purpose
1 parent 65384db commit 25dc0b7

File tree

5 files changed

+181
-1
lines changed

5 files changed

+181
-1
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ tasks {
111111
useJUnitPlatform()
112112
}
113113

114+
runIde {
115+
jvmArgs = listOf("-Xmx2096m")
116+
}
117+
114118
runPluginVerifier {
115119
ideVersions.set(properties("pluginVerifierIdeVersions").split(',').map(String::trim).filter(String::isNotEmpty))
116120
}

components/ide/jetbrains/backend-plugin/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ platformType=IU
1010
platformDownloadSources=true
1111
# Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
1212
# Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22
13-
platformPlugins=Git4Idea, org.jetbrains.plugins.terminal, com.jetbrains.codeWithMe
13+
platformPlugins=Git4Idea, org.jetbrains.plugins.terminal, com.jetbrains.codeWithMe, org.jetbrains.plugins.yaml
1414
# Opt-out flag for bundling Kotlin standard library.
1515
# See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details.
1616
kotlin.stdlib.default.dependency=false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package io.gitpod.jetbrains.remote
6+
7+
import com.intellij.diagnostic.VMOptions
8+
import com.intellij.ide.BrowserUtil
9+
import com.intellij.ide.actions.OpenFileAction
10+
import com.intellij.notification.NotificationAction
11+
import com.intellij.notification.NotificationGroupManager
12+
import com.intellij.notification.NotificationType
13+
import com.intellij.openapi.project.Project
14+
import com.intellij.openapi.startup.StartupActivity
15+
import com.intellij.openapi.util.BuildNumber
16+
import com.intellij.openapi.vfs.LocalFileSystem
17+
import com.intellij.openapi.vfs.VfsUtil
18+
import com.intellij.openapi.vfs.VirtualFile
19+
import com.intellij.psi.PsiFileFactory
20+
import com.intellij.psi.PsiManager
21+
import com.intellij.util.application
22+
import io.gitpod.jetbrains.remote.utils.GitpodConfig
23+
import io.gitpod.jetbrains.remote.utils.GitpodConfig.YamlKey.jetbrains
24+
import io.gitpod.jetbrains.remote.utils.GitpodConfig.YamlKey.vmOptions
25+
import io.gitpod.jetbrains.remote.utils.GitpodConfig.defaultXmxOption
26+
import io.gitpod.jetbrains.remote.utils.GitpodConfig.gitpodYamlFile
27+
import io.gitpod.jetbrains.remote.utils.GitpodConfig.gitpodYamlReferenceLink
28+
import org.jetbrains.yaml.YAMLFileType
29+
import org.jetbrains.yaml.YAMLUtil
30+
import org.jetbrains.yaml.psi.YAMLFile
31+
import org.jetbrains.yaml.psi.YAMLKeyValue
32+
import java.nio.file.Paths
33+
34+
class GitpodStartupActivity : StartupActivity.Background {
35+
36+
override fun runActivity(project: Project) {
37+
application.invokeLater { checkMemoryOption(project) }
38+
}
39+
40+
private fun checkMemoryOption(project: Project) {
41+
val productCode = BuildNumber.currentVersion().productCode
42+
val productName = GitpodConfig.getJetBrainsProductName(productCode) ?: return
43+
// it's ok for .gitpod.yml to not exist
44+
var vmOptions = emptyList<String>()
45+
getGitpodYamlVirtualFile(project)?.let {
46+
val vmOptionsKeyValue = getGitpodYamlVMOptionsPsiElement(project, it, productName)
47+
vmOptions = vmOptionsKeyValue?.valueText?.split("\\s".toRegex()) ?: emptyList()
48+
}
49+
// if there is no -Xmx option from .gitpod.yml, compare runtime maxHeapSize with default value
50+
var xmxVmOptions = vmOptions.filter { it.startsWith("-Xmx") }
51+
if (xmxVmOptions.isEmpty()) {
52+
xmxVmOptions = listOf(defaultXmxOption)
53+
}
54+
// the rightmost -Xmx option is the one to take effect (after deduplication)
55+
val finalXmx = xmxVmOptions.last()
56+
// shift right 20 (xmxInBytes >> 20) to convert to MiB
57+
val finalXmxValueInMiB = VMOptions.parseMemoryOption(finalXmx.substringAfter("-Xmx")).shr(20)
58+
val runtimeXmxValueInMiB = Runtime.getRuntime().maxMemory().shr(20)
59+
if (finalXmxValueInMiB != runtimeXmxValueInMiB) {
60+
showEditVMOptionsNotification(project, runtimeXmxValueInMiB, finalXmxValueInMiB, productName)
61+
}
62+
}
63+
64+
private fun getGitpodYamlVirtualFile(project: Project): VirtualFile? {
65+
val basePath = project.basePath ?: return null
66+
return VfsUtil.findFile(Paths.get(basePath, gitpodYamlFile), true)
67+
}
68+
69+
private fun getGitpodYamlVMOptionsPsiElement(
70+
project: Project,
71+
virtualFile: VirtualFile,
72+
productName: String
73+
): YAMLKeyValue? {
74+
val psiFile = PsiManager.getInstance(project).findFile(virtualFile) as? YAMLFile ?: return null
75+
return YAMLUtil.getQualifiedKeyInFile(psiFile, jetbrains, productName, vmOptions)
76+
}
77+
78+
private fun showEditVMOptionsNotification(project: Project, runtimeXmxMiB: Long, configXmxMiB: Long, productName: String) {
79+
val notificationGroup = NotificationGroupManager.getInstance().getNotificationGroup("Gitpod Notifications")
80+
val title = "Gitpod memory settings"
81+
val message = """
82+
|Current maxHeapSize <code>-Xmx${runtimeXmxMiB}m</code> is not matched to configured <code>-Xmx${configXmxMiB}m</code>.<br/>
83+
|Set vmoptions in .gitpod.yml
84+
""".trimMargin()
85+
val notification = notificationGroup.createNotification(title, message, NotificationType.WARNING)
86+
// edit or create .gitpod.yaml
87+
val virtualFile = getGitpodYamlVirtualFile(project)
88+
val primaryAction = if (virtualFile != null) {
89+
editGitpodYamlAction(project, virtualFile, productName)
90+
} else {
91+
createGitpodYamlAction(project, productName, runtimeXmxMiB)
92+
}
93+
notification.addAction(primaryAction)
94+
// show gitpod.yml reference
95+
val helpAction = NotificationAction.createSimple("More info") {
96+
BrowserUtil.browse(gitpodYamlReferenceLink)
97+
}
98+
notification.addAction(helpAction)
99+
notification.notify(project)
100+
}
101+
102+
private fun editGitpodYamlAction(project: Project, gitpodYaml: VirtualFile, productName: String): NotificationAction {
103+
return NotificationAction.createSimple("Edit .gitpod.yml") {
104+
OpenFileAction.openFile(gitpodYaml, project)
105+
val vmOptionsKeyValue = getGitpodYamlVMOptionsPsiElement(project, gitpodYaml, productName)
106+
// navigate caret to "vmoptions" if exist
107+
vmOptionsKeyValue?.navigate(true)
108+
}
109+
}
110+
111+
private fun createGitpodYamlAction(project: Project, productName: String, runtimeXmxMiB: Long): NotificationAction {
112+
return NotificationAction.createSimple("Create .gitpod.yml") {
113+
application.runWriteAction {
114+
val psiFile = PsiFileFactory.getInstance(project).createFileFromText(
115+
gitpodYamlFile,
116+
YAMLFileType.YML,
117+
GitpodConfig.YamlTemplate.buildVMOptions(productName, runtimeXmxMiB)
118+
)
119+
project.basePath?.let { basePath ->
120+
LocalFileSystem.getInstance().findFileByPath(basePath)?.let { dir ->
121+
PsiManager.getInstance(project).findDirectory(dir)?.add(psiFile)
122+
// refresh VFS and open created .gitpod.yml in editor
123+
getGitpodYamlVirtualFile(project)?.let { OpenFileAction.openFile(it, project) }
124+
}
125+
}
126+
}
127+
}
128+
}
129+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License-AGPL.txt in the project root for license information.
4+
5+
package io.gitpod.jetbrains.remote.utils
6+
7+
/**
8+
* Constants and util functions for Gitpod spec
9+
*/
10+
object GitpodConfig {
11+
12+
const val gitpodYamlFile = ".gitpod.yml"
13+
const val defaultXmxOption = "-Xmx2048m"
14+
const val gitpodYamlReferenceLink = "https://www.gitpod.io/docs/references/gitpod-yml#jetbrainsproductvmoptions"
15+
16+
object YamlKey {
17+
const val jetbrains = "jetbrains"
18+
const val vmOptions = "vmoptions"
19+
}
20+
21+
object YamlTemplate {
22+
23+
fun buildVMOptions(productName: String, xmxValueMiB: Long): String {
24+
return """
25+
|jetbrains:
26+
| $productName:
27+
| vmoptions: "-Xmx${xmxValueMiB}m"
28+
""".trimMargin()
29+
}
30+
}
31+
32+
/**
33+
* map JetBrains IDE productCode to YAML key for .gitpod.yml
34+
*/
35+
fun getJetBrainsProductName(productCode: String): String? {
36+
return when (productCode) {
37+
"IC" -> "intellij"
38+
"IU" -> "intellij"
39+
"PS" -> "phpstorm"
40+
"PY" -> "pycharm"
41+
"GO" -> "goland"
42+
else -> null
43+
}
44+
}
45+
}

components/ide/jetbrains/backend-plugin/src/main/resources/META-INF/plugin.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
<plugin id="Git4Idea"/>
2020
<plugin id="org.jetbrains.plugins.terminal"/>
2121
<plugin id="com.jetbrains.codeWithMe"/>
22+
<plugin id="org.jetbrains.plugins.yaml"/>
2223
</dependencies>
2324

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

0 commit comments

Comments
 (0)