From aafc2db23ca6d0379be583d699720deae07b5b2d Mon Sep 17 00:00:00 2001 From: Manodnya Bhoite Date: Tue, 16 Sep 2025 13:51:04 -0700 Subject: [PATCH 1/2] feat: add cpu profiler logs --- .../services/amazonq/lsp/AmazonQLspService.kt | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt index 5bc04abbb85..87b566f1663 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt @@ -31,6 +31,7 @@ import com.intellij.util.net.ssl.CertificateManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred import kotlinx.coroutines.Job +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.async import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.delay @@ -125,6 +126,17 @@ internal class LSPProcessListener : ProcessListener { override fun processTerminated(event: ProcessEvent) { LOG.info { "LSP process terminated with exit code ${event.exitCode}" } + + // Check for CPU profile file after process termination + val processId = ProcessHandle.current().pid() + val profileDir = System.getProperty("java.io.tmpdir").trimEnd('/') + val profilePath = "$profileDir/node-profile-$processId.cpuprofile" + if (Files.exists(Path.of(profilePath))) { + LOG.info { "CPU profile file created: $profilePath" } + } else { + LOG.warn { "CPU profile file not found at: $profilePath" } + } + try { this.outputStreamWriter.close() this.outputStream.close() @@ -463,8 +475,17 @@ private class AmazonQServerInstance(private val project: Project, private val cs val nodePath = getNodeRuntimePath(artifact.resolve(node)) val emptyFile = Files.createTempFile("empty", null).toAbsolutePath().toString() + val processId = ProcessHandle.current().pid() + val profileDir = System.getProperty("java.io.tmpdir").trimEnd('/') + val profilePath = "node-profile-$processId" + LOG.info { "Node.js CPU profile will be saved to: $profileDir $profilePath" } + val cmd = NodeExePatcher.patch(nodePath) .withParameters( + "--cpu-prof", + "--cpu-prof-dir=$profileDir", + "--cpu-prof-name=$profilePath", + // "--cpu-prof-name=node-profile-$processId.cpuprofile", LspSettings.getInstance().getArtifactPath() ?: artifact.resolve("aws-lsp-codewhisperer.js").toString(), "--stdio", "--set-credentials-encryption-key", @@ -721,18 +742,28 @@ private class AmazonQServerInstance(private val project: Project, private val cs // otherwise can deadlock waiting for frozen flare process cs.launch { languageServer.apply { - withTimeout(2000) { + withTimeout(5000) { shutdown().await() exit() } } }.asCompletableFuture() - .get(3000, TimeUnit.MILLISECONDS) + .get(6000, TimeUnit.MILLISECONDS) + } catch (e: CancellationException) { + // Expected during shutdown, proceed with cleanup + launcherHandler.process.destroy() + Thread.sleep(500) } catch (e: Exception) { LOG.warn(e) { "LSP shutdown failed" } - launcherHandler.killProcess() + launcherHandler.process.destroy() + Thread.sleep(500) + } finally { + if (!launcherHandler.isProcessTerminated) { + launcherHandler.killProcess() + } } } else if (!launcherHandler.isProcessTerminated) { + Thread.sleep(500) launcherHandler.killProcess() } } From 445fcf0d6135ea44eb505ad8c7516a4b8784575c Mon Sep 17 00:00:00 2001 From: Manodnya Bhoite Date: Mon, 6 Oct 2025 17:11:57 -0700 Subject: [PATCH 2/2] add flag --- .../settings/CodeWhispererConfigurable.kt | 8 +++ .../services/amazonq/lsp/AmazonQLspService.kt | 50 ++++++++++--------- .../jetbrains/settings/LspSettings.kt | 7 +++ 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererConfigurable.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererConfigurable.kt index 8a9e9824914..ddc9c440cfb 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererConfigurable.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/settings/CodeWhispererConfigurable.kt @@ -111,6 +111,14 @@ class CodeWhispererConfigurable(private val project: Project) : .resizableColumn() .align(Align.FILL) } + row { + checkBox("Enable CPU profiling") + .bindSelected( + { LspSettings.getInstance().isCpuProfilingEnabled() }, + { LspSettings.getInstance().setCpuProfilingEnabled(it) } + ) + .comment("Enable CPU profiling for the LSP server to help diagnose performance issues") + } } group(message("aws.settings.codewhisperer.group.general")) { diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt index 87b566f1663..700b86ddb06 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt @@ -28,10 +28,10 @@ import com.intellij.util.io.DigestUtil import com.intellij.util.net.HttpConfigurable import com.intellij.util.net.JdkProxyProvider import com.intellij.util.net.ssl.CertificateManager +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred import kotlinx.coroutines.Job -import kotlinx.coroutines.CancellationException import kotlinx.coroutines.async import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.delay @@ -126,17 +126,19 @@ internal class LSPProcessListener : ProcessListener { override fun processTerminated(event: ProcessEvent) { LOG.info { "LSP process terminated with exit code ${event.exitCode}" } - - // Check for CPU profile file after process termination - val processId = ProcessHandle.current().pid() - val profileDir = System.getProperty("java.io.tmpdir").trimEnd('/') - val profilePath = "$profileDir/node-profile-$processId.cpuprofile" - if (Files.exists(Path.of(profilePath))) { - LOG.info { "CPU profile file created: $profilePath" } - } else { - LOG.warn { "CPU profile file not found at: $profilePath" } + + // Check for CPU profile file after process termination only if profiling was enabled + if (LspSettings.getInstance().isCpuProfilingEnabled()) { + val processId = ProcessHandle.current().pid() + val profileDir = System.getProperty("java.io.tmpdir").trimEnd('/') + val profilePath = "$profileDir/node-profile-$processId.cpuprofile" + if (Files.exists(Path.of(profilePath))) { + LOG.info { "CPU profile file created: $profilePath" } + } else { + LOG.warn { "CPU profile file not found at: $profilePath" } + } } - + try { this.outputStreamWriter.close() this.outputStream.close() @@ -475,20 +477,22 @@ private class AmazonQServerInstance(private val project: Project, private val cs val nodePath = getNodeRuntimePath(artifact.resolve(node)) val emptyFile = Files.createTempFile("empty", null).toAbsolutePath().toString() - val processId = ProcessHandle.current().pid() - val profileDir = System.getProperty("java.io.tmpdir").trimEnd('/') - val profilePath = "node-profile-$processId" - LOG.info { "Node.js CPU profile will be saved to: $profileDir $profilePath" } - val cmd = NodeExePatcher.patch(nodePath) .withParameters( - "--cpu-prof", - "--cpu-prof-dir=$profileDir", - "--cpu-prof-name=$profilePath", - // "--cpu-prof-name=node-profile-$processId.cpuprofile", - LspSettings.getInstance().getArtifactPath() ?: artifact.resolve("aws-lsp-codewhisperer.js").toString(), - "--stdio", - "--set-credentials-encryption-key", + buildList { + if (LspSettings.getInstance().isCpuProfilingEnabled()) { + val processId = ProcessHandle.current().pid() + val profileDir = System.getProperty("java.io.tmpdir").trimEnd('/') + val profilePath = "node-profile-$processId.cpuprofile" + LOG.info { "Node.js CPU profile will be saved to: $profileDir $profilePath" } + add("--cpu-prof") + add("--cpu-prof-dir=$profileDir") + add("--cpu-prof-name=$profilePath") + } + add(LspSettings.getInstance().getArtifactPath() ?: artifact.resolve("aws-lsp-codewhisperer.js").toString()) + add("--stdio") + add("--set-credentials-encryption-key") + } ).withEnvironment( buildMap { extraCaCerts?.let { diff --git a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/LspSettings.kt b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/LspSettings.kt index bc199de4917..28b1a66b9f6 100644 --- a/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/LspSettings.kt +++ b/plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/settings/LspSettings.kt @@ -27,6 +27,8 @@ class LspSettings : PersistentStateComponent { fun getNodeRuntimePath() = state.nodeRuntimePath + fun isCpuProfilingEnabled() = state.cpuProfilingEnabled + fun setArtifactPath(artifactPath: String?) { state.artifactPath = artifactPath.nullize(nullizeSpaces = true) } @@ -35,6 +37,10 @@ class LspSettings : PersistentStateComponent { state.nodeRuntimePath = nodeRuntimePath.nullize(nullizeSpaces = true) } + fun setCpuProfilingEnabled(enabled: Boolean) { + state.cpuProfilingEnabled = enabled + } + companion object { fun getInstance(): LspSettings = service() } @@ -43,4 +49,5 @@ class LspSettings : PersistentStateComponent { class LspConfiguration : BaseState() { var artifactPath by string() var nodeRuntimePath by string() + var cpuProfilingEnabled by property(false) }