From f9bfe28ccef6cad9f1ccb5dbabccd08666937137 Mon Sep 17 00:00:00 2001 From: Rival Abdrakhmanov Date: Sat, 23 Dec 2023 19:29:19 +0100 Subject: [PATCH] Refactoring --- .../aspire/run/AspireHostProgramRunner.kt | 6 +- .../services/AspireServiceContributor.kt | 3 +- .../aspire/services/AspireServiceManager.kt | 72 --------- .../services/SessionServiceViewDescriptor.kt | 5 + .../AspireSessionHostLifecycleListener.kt | 18 --- .../sessionHost/AspireSessionHostManager.kt | 140 ++++++++++++++++++ .../sessionHost/AspireSessionHostRunner.kt | 120 +++++---------- src/main/resources/META-INF/plugin.xml | 5 - .../messages/AspireBundle.properties | 3 +- 9 files changed, 191 insertions(+), 181 deletions(-) delete mode 100644 src/main/kotlin/me/rafaelldi/aspire/services/AspireServiceManager.kt delete mode 100644 src/main/kotlin/me/rafaelldi/aspire/sessionHost/AspireSessionHostLifecycleListener.kt create mode 100644 src/main/kotlin/me/rafaelldi/aspire/sessionHost/AspireSessionHostManager.kt diff --git a/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostProgramRunner.kt b/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostProgramRunner.kt index 798bb85b..9467c20f 100644 --- a/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostProgramRunner.kt +++ b/src/main/kotlin/me/rafaelldi/aspire/run/AspireHostProgramRunner.kt @@ -16,7 +16,7 @@ import com.jetbrains.rider.debugger.DotNetProgramRunner import com.jetbrains.rider.run.DotNetProcessRunProfileState import com.jetbrains.rider.util.NetUtils import me.rafaelldi.aspire.sessionHost.AspireSessionHostConfig -import me.rafaelldi.aspire.sessionHost.AspireSessionHostRunner +import me.rafaelldi.aspire.sessionHost.AspireSessionHostManager import org.jetbrains.concurrency.Promise import org.jetbrains.concurrency.asPromise @@ -60,7 +60,7 @@ class AspireHostProgramRunner : DotNetProgramRunner() { val sessionHostLifetime = environment.project.lifetime.createNested() - val sessionHostRunner = AspireSessionHostRunner.getInstance() + val sessionHostManager = AspireSessionHostManager.getInstance(environment.project) val openTelemetryPort = NetUtils.findFreePort(77800) val config = AspireSessionHostConfig( debugSessionToken, @@ -74,7 +74,7 @@ class AspireHostProgramRunner : DotNetProgramRunner() { LOG.trace("Aspire session host config: $config") val sessionHostPromise = sessionHostLifetime.startOnUiAsync { - sessionHostRunner.runSessionHost(environment.project, config, sessionHostLifetime) + sessionHostManager.runSessionHost(config, sessionHostLifetime) }.asPromise() return sessionHostPromise.then { diff --git a/src/main/kotlin/me/rafaelldi/aspire/services/AspireServiceContributor.kt b/src/main/kotlin/me/rafaelldi/aspire/services/AspireServiceContributor.kt index 954f182c..b4c92865 100644 --- a/src/main/kotlin/me/rafaelldi/aspire/services/AspireServiceContributor.kt +++ b/src/main/kotlin/me/rafaelldi/aspire/services/AspireServiceContributor.kt @@ -4,6 +4,7 @@ import com.intellij.execution.services.ServiceViewContributor import com.intellij.execution.services.SimpleServiceViewDescriptor import com.intellij.openapi.project.Project import me.rafaelldi.aspire.AspireIcons +import me.rafaelldi.aspire.sessionHost.AspireSessionHostManager import me.rafaelldi.aspire.settings.AspireSettings class AspireServiceContributor : ServiceViewContributor { @@ -13,7 +14,7 @@ class AspireServiceContributor : ServiceViewContributor { val showServices = AspireSettings.getInstance().showServices return if (showServices) - AspireServiceManager.getInstance(project) + AspireSessionHostManager.getInstance(project) .getSessionHosts() .map { AspireHostServiceContributor(it) } .toMutableList() diff --git a/src/main/kotlin/me/rafaelldi/aspire/services/AspireServiceManager.kt b/src/main/kotlin/me/rafaelldi/aspire/services/AspireServiceManager.kt deleted file mode 100644 index baf56def..00000000 --- a/src/main/kotlin/me/rafaelldi/aspire/services/AspireServiceManager.kt +++ /dev/null @@ -1,72 +0,0 @@ -package me.rafaelldi.aspire.services - -import com.intellij.execution.services.ServiceEventListener -import com.intellij.openapi.components.Service -import com.intellij.openapi.components.service -import com.intellij.openapi.project.Project -import com.jetbrains.rd.util.addUnique -import com.jetbrains.rd.util.lifetime.Lifetime -import me.rafaelldi.aspire.generated.AspireSessionHostModel -import me.rafaelldi.aspire.sessionHost.AspireSessionHostConfig -import me.rafaelldi.aspire.sessionHost.AspireSessionHostLifecycleListener -import java.util.concurrent.ConcurrentHashMap - -@Service(Service.Level.PROJECT) -class AspireServiceManager(project: Project) { - companion object { - fun getInstance(project: Project) = project.service() - } - - private val sessionHosts = ConcurrentHashMap() - private val notifier = project.messageBus.syncPublisher(ServiceEventListener.TOPIC) - - fun addSessionHost( - sessionHostConfig: AspireSessionHostConfig, - sessionHostModel: AspireSessionHostModel, - sessionHostLifetime: Lifetime - ) { - val hostDate = SessionHostServiceData( - sessionHostConfig.id, - sessionHostConfig.hostName, - sessionHostConfig.dashboardUrl, - sessionHostModel - ) - sessionHosts.addUnique(sessionHostLifetime, sessionHostConfig.id, hostDate) - - sessionHostModel.sessions.view(sessionHostLifetime) { sessionLifetime, _, _ -> - sessionLifetime.bracketIfAlive( - { - val event = ServiceEventListener.ServiceEvent.createResetEvent(AspireServiceContributor::class.java) - notifier.handle(event) - }, - { - val event = ServiceEventListener.ServiceEvent.createResetEvent(AspireServiceContributor::class.java) - notifier.handle(event) - } - ) - } - - sessionHostLifetime.bracketIfAlive( - { - val event = ServiceEventListener.ServiceEvent.createResetEvent(AspireServiceContributor::class.java) - notifier.handle(event) - }, - { - val event = ServiceEventListener.ServiceEvent.createResetEvent(AspireServiceContributor::class.java) - notifier.handle(event) - } - ) - } - - fun getSessionHosts(): List = sessionHosts.values.toList() - - class SessionHostLifecycleListener(val project: Project) : AspireSessionHostLifecycleListener { - override fun sessionHostStarted( - sessionHostConfig: AspireSessionHostConfig, - sessionHostModel: AspireSessionHostModel, - sessionHostLifetime: Lifetime - ) { - getInstance(project).addSessionHost(sessionHostConfig, sessionHostModel, sessionHostLifetime) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/me/rafaelldi/aspire/services/SessionServiceViewDescriptor.kt b/src/main/kotlin/me/rafaelldi/aspire/services/SessionServiceViewDescriptor.kt index c8ddb384..3f2c3d3b 100644 --- a/src/main/kotlin/me/rafaelldi/aspire/services/SessionServiceViewDescriptor.kt +++ b/src/main/kotlin/me/rafaelldi/aspire/services/SessionServiceViewDescriptor.kt @@ -4,10 +4,12 @@ import com.intellij.execution.services.ServiceViewDescriptor import com.intellij.ide.projectView.PresentationData import com.intellij.ui.SimpleTextAttributes import com.intellij.ui.components.JBTabbedPane +import com.intellij.ui.dsl.builder.panel import icons.RiderIcons import me.rafaelldi.aspire.AspireBundle import me.rafaelldi.aspire.services.components.EnvironmentVariablePanel import me.rafaelldi.aspire.services.components.SessionDashboardPanel +import me.rafaelldi.aspire.settings.AspireSettings import java.awt.BorderLayout import javax.swing.JPanel import kotlin.io.path.Path @@ -26,6 +28,9 @@ class SessionServiceViewDescriptor(private val sessionData: SessionServiceData) val tabs = JBTabbedPane() tabs.addTab(AspireBundle.getMessage("service.tab.Information"), SessionDashboardPanel(sessionData)) tabs.addTab(AspireBundle.getMessage("service.tab.EnvironmentVariables"), EnvironmentVariablePanel(sessionData)) + if (AspireSettings.getInstance().collectTelemetry) { + tabs.addTab(AspireBundle.getMessage("service.tab.Metrics"), panel { }) + } return JPanel(BorderLayout()).apply { add(tabs, BorderLayout.CENTER) } diff --git a/src/main/kotlin/me/rafaelldi/aspire/sessionHost/AspireSessionHostLifecycleListener.kt b/src/main/kotlin/me/rafaelldi/aspire/sessionHost/AspireSessionHostLifecycleListener.kt deleted file mode 100644 index 630b25bb..00000000 --- a/src/main/kotlin/me/rafaelldi/aspire/sessionHost/AspireSessionHostLifecycleListener.kt +++ /dev/null @@ -1,18 +0,0 @@ -package me.rafaelldi.aspire.sessionHost - -import com.intellij.util.messages.Topic -import com.jetbrains.rd.util.lifetime.Lifetime -import me.rafaelldi.aspire.generated.AspireSessionHostModel - -interface AspireSessionHostLifecycleListener { - companion object { - @Topic.ProjectLevel - val TOPIC = Topic.create("Host Lifecycle Listener", AspireSessionHostLifecycleListener::class.java) - } - - fun sessionHostStarted( - sessionHostConfig: AspireSessionHostConfig, - sessionHostModel: AspireSessionHostModel, - sessionHostLifetime: Lifetime - ) -} \ No newline at end of file diff --git a/src/main/kotlin/me/rafaelldi/aspire/sessionHost/AspireSessionHostManager.kt b/src/main/kotlin/me/rafaelldi/aspire/sessionHost/AspireSessionHostManager.kt new file mode 100644 index 00000000..459bfca8 --- /dev/null +++ b/src/main/kotlin/me/rafaelldi/aspire/sessionHost/AspireSessionHostManager.kt @@ -0,0 +1,140 @@ +package me.rafaelldi.aspire.sessionHost + +import com.intellij.execution.services.ServiceEventListener +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.rd.util.launchOnUi +import com.intellij.openapi.rd.util.withUiContext +import com.jetbrains.rd.util.addUnique +import com.jetbrains.rd.util.lifetime.Lifetime +import com.jetbrains.rd.util.lifetime.LifetimeDefinition +import com.jetbrains.rd.util.lifetime.isNotAlive +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.consumeAsFlow +import me.rafaelldi.aspire.generated.* +import me.rafaelldi.aspire.services.AspireServiceContributor +import me.rafaelldi.aspire.services.SessionHostServiceData +import java.util.concurrent.ConcurrentHashMap + +@Service(Service.Level.PROJECT) +class AspireSessionHostManager(private val project: Project) { + companion object { + fun getInstance(project: Project) = project.service() + + private val LOG = logger() + } + + private val sessionHosts = ConcurrentHashMap() + private val notifier = project.messageBus.syncPublisher(ServiceEventListener.TOPIC) + + fun getSessionHosts(): List = sessionHosts.values.toList() + + suspend fun runSessionHost( + sessionHostConfig: AspireSessionHostConfig, + sessionHostLifetime: LifetimeDefinition + ) { + LOG.info("Starting Aspire session host: $sessionHostConfig") + + if (sessionHostLifetime.isNotAlive) { + LOG.warn("Unable to start Aspire host because lifetime is not alive") + return + } + + LOG.trace("Starting new session hosts with runner") + val sessionHostRunner = AspireSessionHostRunner.getInstance(project) + val sessionHostModel = sessionHostRunner.runSessionHost( + sessionHostConfig, + sessionHostLifetime, + ::subscribe + ) + + val sessionHostData = SessionHostServiceData( + sessionHostConfig.id, + sessionHostConfig.hostName, + sessionHostConfig.dashboardUrl, + sessionHostModel + ) + LOG.trace("Adding new session host data $sessionHostData") + sessionHosts.addUnique(sessionHostLifetime, sessionHostConfig.id, sessionHostData) + + sessionHostLifetime.bracketIfAlive( + { + val event = ServiceEventListener.ServiceEvent.createResetEvent(AspireServiceContributor::class.java) + notifier.handle(event) + }, + { + val event = ServiceEventListener.ServiceEvent.createResetEvent(AspireServiceContributor::class.java) + notifier.handle(event) + } + ) + } + + private suspend fun subscribe( + sessionHostConfig: AspireSessionHostConfig, + sessionHostModel: AspireSessionHostModel, + sessionHostLifetime: Lifetime + ) { + val sessionEvents = Channel(Channel.UNLIMITED) + sessionHostLifetime.launchOnUi { + sessionEvents.consumeAsFlow().collect { + when (it) { + is AspireSessionStarted -> { + LOG.trace("Aspire session started (${it.id}, ${it.pid})") + sessionHostModel.processStarted.fire(ProcessStarted(it.id, it.pid)) + } + + is AspireSessionTerminated -> { + LOG.trace("Aspire session terminated (${it.id}, ${it.exitCode})") + sessionHostModel.processTerminated.fire(ProcessTerminated(it.id, it.exitCode)) + } + + is AspireSessionLogReceived -> { + LOG.trace("Aspire session log received (${it.id}, ${it.isStdErr}, ${it.message})") + sessionHostModel.logReceived.fire(LogReceived(it.id, it.isStdErr, it.message)) + } + } + } + } + + withUiContext { + sessionHostModel.sessions.view(sessionHostLifetime) { sessionLifetime, sessionId, sessionModel -> + LOG.info("New session added $sessionId, $sessionModel") + sessionAdded(sessionId, sessionModel, sessionLifetime, sessionEvents, sessionHostConfig) + } + } + } + + private fun sessionAdded( + sessionId: String, + sessionModel: SessionModel, + sessionLifetime: Lifetime, + sessionEvents: Channel, + sessionHostConfig: AspireSessionHostConfig + ) { + val runner = AspireSessionRunner.getInstance(project) + runner.runSession( + AspireSessionRunner.RunSessionCommand( + sessionId, + sessionModel, + sessionLifetime, + sessionEvents, + sessionHostConfig.hostName, + sessionHostConfig.isDebug, + sessionHostConfig.openTelemetryPort + ) + ) + + sessionLifetime.bracketIfAlive( + { + val event = ServiceEventListener.ServiceEvent.createResetEvent(AspireServiceContributor::class.java) + notifier.handle(event) + }, + { + val event = ServiceEventListener.ServiceEvent.createResetEvent(AspireServiceContributor::class.java) + notifier.handle(event) + } + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/me/rafaelldi/aspire/sessionHost/AspireSessionHostRunner.kt b/src/main/kotlin/me/rafaelldi/aspire/sessionHost/AspireSessionHostRunner.kt index a416fd36..92566fd9 100644 --- a/src/main/kotlin/me/rafaelldi/aspire/sessionHost/AspireSessionHostRunner.kt +++ b/src/main/kotlin/me/rafaelldi/aspire/sessionHost/AspireSessionHostRunner.kt @@ -13,34 +13,32 @@ import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.extensions.PluginId import com.intellij.openapi.project.Project import com.intellij.openapi.rd.createNestedDisposable -import com.intellij.openapi.rd.util.launchOnUi import com.intellij.openapi.rd.util.withUiContext import com.intellij.openapi.util.Key import com.jetbrains.rd.framework.* import com.jetbrains.rd.util.lifetime.Lifetime import com.jetbrains.rd.util.lifetime.LifetimeDefinition -import com.jetbrains.rd.util.lifetime.isNotAlive import com.jetbrains.rdclient.protocol.RdDispatcher import com.jetbrains.rider.runtime.RiderDotNetActiveRuntimeHost -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.consumeAsFlow -import me.rafaelldi.aspire.generated.* +import com.jetbrains.rider.runtime.dotNetCore.DotNetCoreRuntime +import me.rafaelldi.aspire.generated.AspireSessionHostModel +import me.rafaelldi.aspire.generated.aspireSessionHostModel import me.rafaelldi.aspire.util.decodeAnsiCommandsToString import java.nio.charset.StandardCharsets import java.nio.file.Path import kotlin.io.path.div -@Service -class AspireSessionHostRunner { +@Service(Service.Level.PROJECT) +class AspireSessionHostRunner(private val project: Project) { companion object { - fun getInstance() = service() + fun getInstance(project: Project) = project.service() private val LOG = logger() private const val ASPNETCORE_URLS = "ASPNETCORE_URLS" private const val RIDER_OTEL_PORT = "RIDER_OTEL_PORT" - private const val RIDER_PARENT_PROCESS_PID = "RIDER_PARENT_PROCESS_PID" private const val RIDER_RD_PORT = "RIDER_RD_PORT" + private const val RIDER_PARENT_PROCESS_PID = "RIDER_PARENT_PROCESS_PID" private const val DOTNET_OTLP_ENDPOINT_URL = "DOTNET_OTLP_ENDPOINT_URL" } @@ -53,39 +51,25 @@ class AspireSessionHostRunner { } suspend fun runSessionHost( - project: Project, sessionHostConfig: AspireSessionHostConfig, - sessionHostLifetime: LifetimeDefinition - ) { + sessionHostLifetime: LifetimeDefinition, + subscribeToProtocol: suspend ( + sessionHostConfig: AspireSessionHostConfig, + sessionHostModel: AspireSessionHostModel, + sessionHostLifetime: Lifetime + ) -> Unit + ): AspireSessionHostModel { LOG.info("Starting Aspire session host: $sessionHostConfig") - if (sessionHostLifetime.isNotAlive) { - LOG.warn("Unable to start Aspire host because lifetime is not alive") - return - } - val dotnet = RiderDotNetActiveRuntimeHost.getInstance(project).dotNetCoreRuntime.value ?: throw CantRunException("Cannot find active .NET runtime") val protocol = startProtocol(sessionHostLifetime) - subscribe(sessionHostConfig, protocol.aspireSessionHostModel, sessionHostLifetime, project) + subscribeToProtocol(sessionHostConfig, protocol.aspireSessionHostModel, sessionHostLifetime) - val commandLine = GeneralCommandLine() - .withExePath(dotnet.cliExePath) - .withCharset(StandardCharsets.UTF_8) - .withParameters(hostAssemblyPath.toString()) - .withEnvironment( - buildMap { - put(ASPNETCORE_URLS, "http://localhost:${sessionHostConfig.debugSessionPort}/") - put(RIDER_OTEL_PORT, sessionHostConfig.openTelemetryPort.toString()) - put(RIDER_RD_PORT, "${protocol.wire.serverPort}") - put(RIDER_PARENT_PROCESS_PID, ProcessHandle.current().pid().toString()) - if (sessionHostConfig.openTelemetryProtocolUrl != null) - put(DOTNET_OTLP_ENDPOINT_URL, sessionHostConfig.openTelemetryProtocolUrl) - } - ) + val commandLine = getCommandLine(dotnet, sessionHostConfig, protocol.wire.serverPort) LOG.trace("Host command line: ${commandLine.commandLineString}") - val processHandler = KillableColoredProcessHandler.Silent(commandLine) + val processHandler = KillableColoredProcessHandler(commandLine) sessionHostLifetime.onTermination { if (!processHandler.isProcessTerminating && !processHandler.isProcessTerminated) { LOG.trace("Killing Aspire host process") @@ -109,11 +93,11 @@ class AspireSessionHostRunner { } } }, sessionHostLifetime.createNestedDisposable()) + processHandler.startNotify() - LOG.trace("Aspire session host started") + LOG.info("Aspire session host started") - project.messageBus.syncPublisher(AspireSessionHostLifecycleListener.TOPIC) - .sessionHostStarted(sessionHostConfig, protocol.aspireSessionHostModel, sessionHostLifetime) + return protocol.aspireSessionHostModel } private suspend fun startProtocol(lifetime: Lifetime) = withUiContext { @@ -130,51 +114,25 @@ class AspireSessionHostRunner { return@withUiContext protocol } - private suspend fun subscribe( - hostConfig: AspireSessionHostConfig, - hostModel: AspireSessionHostModel, - hostLifetime: Lifetime, - project: Project - ) { - val sessionEvents = Channel(Channel.UNLIMITED) - - hostLifetime.launchOnUi { - sessionEvents.consumeAsFlow().collect { - when (it) { - is AspireSessionStarted -> { - LOG.trace("Aspire session started (${it.id}, ${it.pid})") - hostModel.processStarted.fire(ProcessStarted(it.id, it.pid)) - } - - is AspireSessionTerminated -> { - LOG.trace("Aspire session terminated (${it.id}, ${it.exitCode})") - hostModel.processTerminated.fire(ProcessTerminated(it.id, it.exitCode)) - } - - is AspireSessionLogReceived -> { - LOG.trace("Aspire session log received (${it.id}, ${it.isStdErr}, ${it.message})") - hostModel.logReceived.fire(LogReceived(it.id, it.isStdErr, it.message)) - } + private fun getCommandLine( + dotnet: DotNetCoreRuntime, + sessionHostConfig: AspireSessionHostConfig, + rdPort: Int + ): GeneralCommandLine { + val commandLine = GeneralCommandLine() + .withExePath(dotnet.cliExePath) + .withCharset(StandardCharsets.UTF_8) + .withParameters(hostAssemblyPath.toString()) + .withEnvironment( + buildMap { + put(ASPNETCORE_URLS, "http://localhost:${sessionHostConfig.debugSessionPort}/") + put(RIDER_OTEL_PORT, sessionHostConfig.openTelemetryPort.toString()) + put(RIDER_RD_PORT, "$rdPort") + put(RIDER_PARENT_PROCESS_PID, ProcessHandle.current().pid().toString()) + if (sessionHostConfig.openTelemetryProtocolUrl != null) + put(DOTNET_OTLP_ENDPOINT_URL, sessionHostConfig.openTelemetryProtocolUrl) } - } - } - - withUiContext { - hostModel.sessions.view(hostLifetime) { sessionLifetime, sessionId, sessionModel -> - LOG.info("New session added $sessionId, $sessionModel") - val runner = AspireSessionRunner.getInstance(project) - runner.runSession( - AspireSessionRunner.RunSessionCommand( - sessionId, - sessionModel, - sessionLifetime, - sessionEvents, - hostConfig.hostName, - hostConfig.isDebug, - hostConfig.openTelemetryPort - ) - ) - } - } + ) + return commandLine } } \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 814ce45d..54ad5381 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -35,11 +35,6 @@ - - - -