Update profiling CLI arguments (#3461)

This commit is contained in:
Dmitry Bushev 2022-05-24 16:01:26 +03:00 committed by GitHub
parent 9a188344a8
commit f9d2964e83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 499 additions and 175 deletions

View File

@ -235,6 +235,7 @@
[3444]: https://github.com/enso-org/enso/pull/3444
[3453]: https://github.com/enso-org/enso/pull/3453
[3454]: https://github.com/enso-org/enso/pull/3454
[3461]: https://github.com/enso-org/enso/pull/3461
[3465]: https://github.com/enso-org/enso/pull/3465
# Enso 2.0.0-alpha.18 (2021-10-12)

View File

@ -905,9 +905,10 @@ lazy val `json-rpc-server` = project
libraryDependencies ++= akka ++ akkaTest,
libraryDependencies ++= circe,
libraryDependencies ++= Seq(
"io.circe" %% "circe-literal" % circeVersion,
akkaTestkit % Test,
"org.scalatest" %% "scalatest" % scalatestVersion % Test
"io.circe" %% "circe-literal" % circeVersion,
"com.typesafe.scala-logging" %% "scala-logging" % scalaLoggingVersion,
akkaTestkit % Test,
"org.scalatest" %% "scalatest" % scalatestVersion % Test
)
)

View File

@ -15,9 +15,10 @@ import org.enso.languageserver.runtime.RuntimeKiller.{
ShutDownRuntime
}
import org.enso.loggingservice.LogLevel
import org.enso.profiling.{FileSampler, MethodsSampler, NoopSampler}
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
import scala.concurrent.{Await, ExecutionContextExecutor, Future}
/** A lifecycle component used to start and stop a Language Server.
*
@ -31,11 +32,14 @@ class LanguageServerComponent(config: LanguageServerConfig, logLevel: LogLevel)
@volatile
private var maybeServerCtx: Option[ServerContext] = None
implicit private val ec = config.computeExecutionContext
implicit private val ec: ExecutionContextExecutor =
config.computeExecutionContext
/** @inheritdoc */
override def start(): Future[ComponentStarted.type] = {
logger.info("Starting Language Server...")
val sampler = startSampling(config)
logger.debug(s"Started ${sampler.getClass.getName}.")
val module = new MainModule(config, logLevel)
val bindJsonServer =
for {
@ -51,7 +55,8 @@ class LanguageServerComponent(config: LanguageServerConfig, logLevel: LogLevel)
jsonBinding <- bindJsonServer
binaryBinding <- bindBinaryServer
_ <- Future {
maybeServerCtx = Some(ServerContext(module, jsonBinding, binaryBinding))
maybeServerCtx =
Some(ServerContext(sampler, module, jsonBinding, binaryBinding))
}
_ <- Future {
logger.info(
@ -62,14 +67,29 @@ class LanguageServerComponent(config: LanguageServerConfig, logLevel: LogLevel)
} yield ComponentStarted
}
/** Start the application sampling. */
private def startSampling(config: LanguageServerConfig): MethodsSampler = {
val sampler = config.profilingConfig.profilingPath match {
case Some(path) =>
new FileSampler(path.toFile)
case None =>
NoopSampler()
}
sampler.start()
config.profilingConfig.profilingTime.foreach(sampler.stop(_))
sampler
}
/** @inheritdoc */
override def stop(): Future[ComponentStopped.type] =
maybeServerCtx match {
case None =>
Future.failed(new Exception("Server isn't running"))
Future.successful(ComponentStopped)
case Some(serverContext) =>
for {
_ <- stopSampling(serverContext)
_ <- terminateTruffle(serverContext)
_ <- terminateAkka(serverContext)
_ <- releaseResources(serverContext)
@ -77,6 +97,9 @@ class LanguageServerComponent(config: LanguageServerConfig, logLevel: LogLevel)
} yield ComponentStopped
}
private def stopSampling(serverContext: ServerContext): Future[Unit] =
Future(serverContext.sampler.stop()).recover(logError)
private def releaseResources(serverContext: ServerContext): Future[Unit] =
for {
_ <- Future(serverContext.mainModule.close()).recover(logError)
@ -101,7 +124,7 @@ class LanguageServerComponent(config: LanguageServerConfig, logLevel: LogLevel)
}
private def terminateTruffle(serverContext: ServerContext): Future[Unit] = {
implicit val askTimeout = Timeout(12.seconds)
implicit val askTimeout: Timeout = Timeout(12.seconds)
val killFiber =
(serverContext.mainModule.runtimeKiller ? ShutDownRuntime)
.mapTo[RuntimeShutdownResult]
@ -129,11 +152,13 @@ object LanguageServerComponent {
/** A running server context.
*
* @param sampler a sampler gathering the application performance statistics
* @param mainModule a main module containing all components of the server
* @param jsonBinding a http binding for rpc protocol
* @param binaryBinding a http binding for data protocol
*/
case class ServerContext(
sampler: MethodsSampler,
mainModule: MainModule,
jsonBinding: Http.ServerBinding,
binaryBinding: Http.ServerBinding

View File

@ -2,7 +2,7 @@ package org.enso.languageserver.boot
import java.util.UUID
import scala.concurrent.ExecutionContext
import scala.concurrent.{ExecutionContext, ExecutionContextExecutor}
/** The config of the running Language Server instance.
*
@ -11,7 +11,7 @@ import scala.concurrent.ExecutionContext
* @param dataPort a data port that the server listen to
* @param contentRootUuid an id of content root
* @param contentRootPath a path to the content root
* @param isProfilingEnabled is the application profiling enabled
* @param profilingConfig an application profiling configuration
*/
case class LanguageServerConfig(
interface: String,
@ -19,7 +19,7 @@ case class LanguageServerConfig(
dataPort: Int,
contentRootUuid: UUID,
contentRootPath: String,
isProfilingEnabled: Boolean,
name: String = "language-server",
computeExecutionContext: ExecutionContext = ExecutionContext.global
profilingConfig: ProfilingConfig,
name: String = "language-server",
computeExecutionContext: ExecutionContextExecutor = ExecutionContext.global
)

View File

@ -44,7 +44,6 @@ import org.enso.lockmanager.server.LockManagerService
import org.enso.logger.masking.Masking
import org.enso.loggingservice.{JavaLoggingLogHandler, LogLevel}
import org.enso.polyglot.{RuntimeOptions, RuntimeServerInfo}
import org.enso.profiling.{NoopSampler, TempFileSampler}
import org.enso.searcher.sql.{SqlDatabase, SqlSuggestionsRepo, SqlVersionsRepo}
import org.enso.text.{ContentBasedVersioning, Sha3_224VersionCalculator}
import org.graalvm.polyglot.Context
@ -56,6 +55,7 @@ import java.net.URI
import java.time.Clock
import scala.concurrent.duration._
import scala.util.{Failure, Success}
/** A main module containing all components of the server.
*
@ -83,7 +83,8 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {
FileManagerConfig(timeout = 3.seconds),
PathWatcherConfig(),
ExecutionContextConfig(),
directoriesConfig
directoriesConfig,
serverConfig.profilingConfig
)
log.trace("Created Language Server config [{}].", languageServerConfig)
@ -148,8 +149,20 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {
)
val runtimeEventsMonitor =
if (logLevel == LogLevel.Trace) ApiEventsMonitor()
else new NoopEventsMonitor
languageServerConfig.profiling.runtimeEventsLogPath match {
case Some(path) =>
ApiEventsMonitor(path) match {
case Success(monitor) =>
monitor
case Failure(exception) =>
log.error(
s"Failed to create runtime events monitor for $path ($exception)."
)
new NoopEventsMonitor
}
case None =>
new NoopEventsMonitor
}
log.trace(
s"Started runtime events monitor ${runtimeEventsMonitor.getClass.getName}."
)
@ -228,12 +241,7 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {
languageServerConfig,
RuntimeFailureMapper(contentRootManagerWrapper),
runtimeConnector,
sessionRouter,
if (serverConfig.isProfilingEnabled) {
val s = TempFileSampler("context-registry")
JavaLoggingLogHandler.registerLogFile(s.getSiblingFile(".log"))
s
} else NoopSampler()
sessionRouter
),
"context-registry"
)

View File

@ -0,0 +1,18 @@
package org.enso.languageserver.boot
import java.nio.file.Path
import scala.concurrent.duration.FiniteDuration
/** Application profiling configuration.
*
* @param runtimeEventsLogPath the path to the runtime events log file
* @param profilingPath the path to the profiling output file
* @param profilingTime limit the profiling duration, as an infinite profiling
* duration may cause out-of-memory errors.
*/
case class ProfilingConfig(
runtimeEventsLogPath: Option[Path] = None,
profilingPath: Option[Path] = None,
profilingTime: Option[FiniteDuration] = None
)

View File

@ -1,10 +1,12 @@
package org.enso.languageserver.data
import org.enso.languageserver.boot.ProfilingConfig
import org.enso.languageserver.filemanager.ContentRootWithFile
import org.enso.logger.masking.{MaskingUtils, ToLogString}
import java.io.File
import java.nio.file.Files
import scala.concurrent.duration._
/** Configuration of the path watcher.
@ -122,13 +124,15 @@ object ProjectDirectoriesConfig {
* @param pathWatcher the path watcher config
* @param executionContext the executionContext config
* @param directories the configuration of internal directories
* @param profiling the profiling configuration
*/
case class Config(
projectContentRoot: ContentRootWithFile,
fileManager: FileManagerConfig,
pathWatcher: PathWatcherConfig,
executionContext: ExecutionContextConfig,
directories: ProjectDirectoriesConfig
directories: ProjectDirectoriesConfig,
profiling: ProfilingConfig
) extends ToLogString {
/** @inheritdoc */

View File

@ -8,6 +8,8 @@ import java.nio.charset.StandardCharsets
import java.nio.file.{Files, Path, StandardOpenOption}
import java.time.Clock
import scala.util.Try
/** Gather messages between the language server and the runtime and write them
* to the provided file in CSV format.
*
@ -61,12 +63,16 @@ final class ApiEventsMonitor(path: Path, clock: Clock) extends EventsMonitor {
}
object ApiEventsMonitor {
/** Create default instance of [[ApiEventsMonitor]]. */
def apply(): ApiEventsMonitor =
new ApiEventsMonitor(
Files.createTempFile("enso-api-events-", ".csv"),
Clock.systemUTC()
)
/** Create default instance of [[ApiEventsMonitor]].
*
* @param path the path to the events log file
* @return an instance of [[ApiEventsMonitor]]
*/
def apply(path: Path): Try[ApiEventsMonitor] = Try {
Files.deleteIfExists(path)
Files.createFile(path)
new ApiEventsMonitor(path, Clock.systemUTC())
}
/** Direction of the message. */
sealed trait Direction

View File

@ -13,7 +13,6 @@ import org.enso.languageserver.util.UnhandledLogging
import org.enso.logger.akka.ActorMessageLogging
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.polyglot.runtime.Runtime.Api.ContextId
import org.enso.profiling.MethodsSampler
import org.enso.searcher.SuggestionsRepo
import java.util.UUID
@ -59,15 +58,13 @@ import scala.concurrent.duration._
* @param runtimeFailureMapper mapper for runtime failures
* @param runtime reference to the [[RuntimeConnector]]
* @param sessionRouter the session router
* @param sampler the methods sampler
*/
final class ContextRegistry(
repo: SuggestionsRepo[Future],
config: Config,
runtimeFailureMapper: RuntimeFailureMapper,
runtime: ActorRef,
sessionRouter: ActorRef,
sampler: MethodsSampler
sessionRouter: ActorRef
) extends Actor
with LazyLogging
with ActorMessageLogging
@ -75,7 +72,8 @@ final class ContextRegistry(
import ContextRegistryProtocol._
private val timeout: FiniteDuration = config.executionContext.requestTimeout
private val timeout: FiniteDuration =
config.executionContext.requestTimeout
override def preStart(): Unit = {
context.system.eventStream
@ -110,11 +108,9 @@ final class ContextRegistry(
.foreach(_ ! update)
case update: Api.ExecutionFailed =>
sampler.stop(6.seconds)(context.dispatcher)
store.getListener(update.contextId).foreach(_ ! update)
case update: Api.ExecutionComplete =>
sampler.stop(6.seconds)(context.dispatcher)
store.getListener(update.contextId).foreach(_ ! update)
case update: Api.ExecutionUpdate =>
@ -166,7 +162,6 @@ final class ContextRegistry(
}
case PushContextRequest(client, contextId, stackItem) =>
sampler.start()
if (store.hasContext(client.clientId, contextId)) {
val item = getRuntimeStackItem(stackItem)
val handler =
@ -400,15 +395,13 @@ object ContextRegistry {
* @param runtimeFailureMapper mapper for runtime failures
* @param runtime reference to the [[RuntimeConnector]]
* @param sessionRouter the session router
* @param sampler the methods sampler
*/
def props(
repo: SuggestionsRepo[Future],
config: Config,
runtimeFailureMapper: RuntimeFailureMapper,
runtime: ActorRef,
sessionRouter: ActorRef,
sampler: MethodsSampler
sessionRouter: ActorRef
): Props =
Props(
new ContextRegistry(
@ -416,8 +409,7 @@ object ContextRegistry {
config,
runtimeFailureMapper,
runtime,
sessionRouter,
sampler
sessionRouter
)
)
}

View File

@ -3,6 +3,7 @@ package org.enso.languageserver.boot.resource
import akka.actor.ActorSystem
import akka.testkit._
import org.apache.commons.io.FileUtils
import org.enso.languageserver.boot.ProfilingConfig
import org.enso.languageserver.data._
import org.enso.languageserver.event.InitializedEvent
import org.enso.languageserver.filemanager.{ContentRoot, ContentRootWithFile}
@ -20,6 +21,7 @@ import org.sqlite.SQLiteException
import java.nio.file.{Files, StandardOpenOption}
import java.util.UUID
import scala.concurrent.Await
import scala.concurrent.duration._
@ -210,7 +212,8 @@ class RepoInitializationSpec
FileManagerConfig(timeout = 3.seconds.dilated),
PathWatcherConfig(),
ExecutionContextConfig(requestTimeout = 3.seconds.dilated),
ProjectDirectoriesConfig.initialize(root.file)
ProjectDirectoriesConfig.initialize(root.file),
ProfilingConfig()
)
}

View File

@ -3,6 +3,7 @@ package org.enso.languageserver.filemanager
import akka.actor.{ActorRef, ActorSystem}
import akka.testkit.{TestDuration, TestKit, TestProbe}
import org.apache.commons.lang3.SystemUtils
import org.enso.languageserver.boot.ProfilingConfig
import org.enso.languageserver.data._
import org.enso.languageserver.filemanager.ContentRootManagerProtocol.{
ContentRootsAddedNotification,
@ -19,6 +20,7 @@ import org.scalatest.{Inside, OptionValues}
import java.io.File
import java.nio.file.{Path => JPath}
import java.util.UUID
import scala.concurrent.duration.DurationInt
class ContentRootManagerSpec
@ -46,7 +48,8 @@ class ContentRootManagerSpec
FileManagerConfig(timeout = 3.seconds.dilated),
PathWatcherConfig(),
ExecutionContextConfig(requestTimeout = 3.seconds.dilated),
ProjectDirectoriesConfig.initialize(root.file)
ProjectDirectoriesConfig.initialize(root.file),
ProfilingConfig()
)
rootActor = system.actorOf(ContentRootManagerActor.props(config))
rootManager = new ContentRootManagerWrapper(config, rootActor)

View File

@ -3,6 +3,7 @@ package org.enso.languageserver.runtime
import akka.actor.{ActorRef, ActorSystem}
import akka.testkit.{ImplicitSender, TestKit, TestProbe}
import org.apache.commons.io.FileUtils
import org.enso.languageserver.boot.ProfilingConfig
import org.enso.languageserver.data._
import org.enso.languageserver.event.InitializedEvent
import org.enso.languageserver.filemanager.{
@ -29,6 +30,7 @@ import org.scalatest.wordspec.AnyWordSpecLike
import java.nio.file.Files
import java.util.UUID
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
import scala.util.{Failure, Success}
@ -453,7 +455,8 @@ class ContextEventsListenerSpec
FileManagerConfig(timeout = 3.seconds),
PathWatcherConfig(),
ExecutionContextConfig(requestTimeout = 3.seconds),
ProjectDirectoriesConfig.initialize(root.file)
ProjectDirectoriesConfig.initialize(root.file),
ProfilingConfig()
)
}

View File

@ -4,6 +4,7 @@ import akka.actor.{ActorRef, ActorSystem}
import akka.testkit.{ImplicitSender, TestKit, TestProbe}
import org.apache.commons.io.FileUtils
import org.enso.docs.generator.DocsGenerator
import org.enso.languageserver.boot.ProfilingConfig
import org.enso.languageserver.capability.CapabilityProtocol.{
AcquireCapability,
CapabilityAcquired
@ -1107,7 +1108,8 @@ class SuggestionsHandlerSpec
FileManagerConfig(timeout = 3.seconds),
PathWatcherConfig(),
ExecutionContextConfig(requestTimeout = 3.seconds),
ProjectDirectoriesConfig.initialize(root.file)
ProjectDirectoriesConfig.initialize(root.file),
ProfilingConfig()
)
}

View File

@ -6,6 +6,7 @@ import akka.actor.{ActorRef, Props}
import akka.http.scaladsl.model.RemoteAddress
import com.google.flatbuffers.FlatBufferBuilder
import org.apache.commons.io.FileUtils
import org.enso.languageserver.boot.ProfilingConfig
import org.enso.languageserver.data.{
Config,
ExecutionContextConfig,
@ -45,7 +46,8 @@ class BaseBinaryServerTest extends BinaryServerTestKit {
FileManagerConfig(timeout = 3.seconds),
PathWatcherConfig(),
ExecutionContextConfig(requestTimeout = 3.seconds),
ProjectDirectoriesConfig.initialize(testContentRoot.file)
ProjectDirectoriesConfig.initialize(testContentRoot.file),
ProfilingConfig()
)
sys.addShutdownHook(FileUtils.deleteQuietly(testContentRoot.file))

View File

@ -12,6 +12,7 @@ import org.enso.editions.{EditionResolver, Editions}
import org.enso.jsonrpc.test.JsonRpcServerTestKit
import org.enso.jsonrpc.{ClientControllerFactory, Protocol}
import org.enso.languageserver.TestClock
import org.enso.languageserver.boot.ProfilingConfig
import org.enso.languageserver.boot.resource.{
DirectoriesInitialization,
RepoInitialization,
@ -41,7 +42,6 @@ import org.enso.loggingservice.LogLevel
import org.enso.pkg.PackageManager
import org.enso.polyglot.data.TypeGraph
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.profiling.NoopSampler
import org.enso.runtimeversionmanager.test.{
FakeEnvironment,
TestableThreadSafeFileLockManager
@ -93,7 +93,8 @@ class BaseServerTest
FileManagerConfig(timeout = 3.seconds),
PathWatcherConfig(),
ExecutionContextConfig(requestTimeout = 3.seconds),
ProjectDirectoriesConfig(testContentRoot.file)
ProjectDirectoriesConfig(testContentRoot.file),
ProfilingConfig()
)
override def protocol: Protocol = JsonRpc.protocol
@ -189,8 +190,7 @@ class BaseServerTest
config,
RuntimeFailureMapper(contentRootManagerWrapper),
runtimeConnectorProbe.ref,
sessionRouter,
NoopSampler()
sessionRouter
)
)

View File

@ -1,19 +1,20 @@
package org.enso.languageserver.websocket.json
import io.circe.literal._
import io.circe.parser.parse
import org.apache.commons.io.FileUtils
import org.bouncycastle.util.encoders.Hex
import org.enso.languageserver.boot.ProfilingConfig
import org.enso.languageserver.data._
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.testkit.RetrySpec
import java.io.File
import java.nio.file.attribute.BasicFileAttributes
import java.nio.file.{Files, Paths}
import java.security.MessageDigest
import java.util.UUID
import io.circe.literal._
import io.circe.parser.parse
import org.apache.commons.io.FileUtils
import org.bouncycastle.util.encoders.Hex
import org.enso.languageserver.data._
import org.enso.polyglot.runtime.Runtime.Api
import org.enso.testkit.RetrySpec
import scala.concurrent.duration._
class FileManagerTest extends BaseServerTest with RetrySpec {
@ -26,7 +27,8 @@ class FileManagerTest extends BaseServerTest with RetrySpec {
FileManagerConfig(timeout = 3.seconds),
PathWatcherConfig(),
ExecutionContextConfig(requestTimeout = 3.seconds),
ProjectDirectoriesConfig.initialize(testContentRoot.file)
ProjectDirectoriesConfig.initialize(testContentRoot.file),
ProfilingConfig()
)
}

View File

@ -679,7 +679,8 @@ object LauncherApplication {
logLevel,
connectLogger,
globalCLIOptions.colorMode,
!disableLogMasking
!disableLogMasking,
None
)
initializeApp()

View File

@ -6,7 +6,9 @@ import org.enso.languageserver.boot.{
}
import org.enso.loggingservice.LogLevel
import scala.concurrent.Await
import java.util.concurrent.Semaphore
import scala.concurrent.{Await, ExecutionContext, Future}
import scala.concurrent.duration._
import scala.io.StdIn
@ -14,6 +16,8 @@ import scala.io.StdIn
*/
object LanguageServerApp {
private val semaphore = new Semaphore(1)
/** Runs a Language Server
*
* @param config the application config
@ -27,7 +31,7 @@ object LanguageServerApp {
): Unit = {
val server = new LanguageServerComponent(config, logLevel)
Runtime.getRuntime.addShutdownHook(new Thread(() => {
Await.result(server.stop(), 40.seconds)
stop(server)(config.computeExecutionContext)
}))
Await.result(server.start(), 1.minute)
if (deamonize) {
@ -37,7 +41,31 @@ object LanguageServerApp {
}
} else {
StdIn.readLine()
stop(server)(config.computeExecutionContext)
}
}
/** Stops the language server.
*
* @param server the language server component
* @param ec the execution context
*/
private def stop(
server: LanguageServerComponent
)(implicit ec: ExecutionContext): Unit = {
Await.ready(synchronize(server.stop()), 40.seconds)
}
/** Makes sure that the calls to the provided future are synchronized. */
private def synchronize[A](
fut: => Future[A]
)(implicit ec: ExecutionContext): Future[A] = {
val task = for {
_ <- Future { semaphore.acquire() }
result <- fut
} yield result
task.onComplete(_ => semaphore.release())
task
}
}

View File

@ -7,7 +7,7 @@ import com.typesafe.scalalogging.Logger
import org.apache.commons.cli.{Option => CliOption, _}
import org.enso.editions.DefaultEdition
import org.enso.languageserver.boot
import org.enso.languageserver.boot.LanguageServerConfig
import org.enso.languageserver.boot.{LanguageServerConfig, ProfilingConfig}
import org.enso.libraryupload.LibraryUploader.UploadFailedError
import org.enso.loggingservice.LogLevel
import org.enso.pkg.{Contact, PackageManager, Template}
@ -16,14 +16,14 @@ import org.enso.version.VersionDescription
import org.graalvm.polyglot.PolyglotException
import java.io.File
import java.nio.file.Path
import java.util.UUID
import java.nio.file.{Path, Paths}
import java.util.{Collections, UUID}
import scala.Console.err
import scala.concurrent.duration._
import scala.jdk.CollectionConverters._
import scala.util.{Failure, Success, Try}
import scala.util.control.NonFatal
import java.util.Collections
import scala.util.{Failure, Success, Try}
/** The main CLI entry point class. */
object Main {
@ -40,7 +40,10 @@ object Main {
private val DOCS_OPTION = "docs"
private val PREINSTALL_OPTION = "preinstall-dependencies"
private val LANGUAGE_SERVER_OPTION = "server"
private val LANGUAGE_SERVER_PROFILING = "server-profiling"
private val LANGUAGE_SERVER_PROFILING_PATH = "server-profiling-path"
private val LANGUAGE_SERVER_PROFILING_TIME = "server-profiling-time"
private val LANGUAGE_SERVER_PROFILING_EVENTS_LOG_PATH =
"server-profiling-events-log-path"
private val DAEMONIZE_OPTION = "daemon"
private val INTERFACE_OPTION = "interface"
private val RPC_PORT_OPTION = "rpc-port"
@ -153,11 +156,26 @@ object Main {
.longOpt(LANGUAGE_SERVER_OPTION)
.desc("Runs Language Server")
.build()
val lsProfilingOption = CliOption.builder
.longOpt(LANGUAGE_SERVER_PROFILING)
.desc(
"Enables the Language Server profiling. The output is written to system temp directory."
)
val lsProfilingPathOption = CliOption.builder
.hasArg(true)
.numberOfArgs(1)
.argName("file")
.longOpt(LANGUAGE_SERVER_PROFILING_PATH)
.desc("The path to the Language Server profiling file.")
.build()
val lsProfilingTimeOption = CliOption.builder
.hasArg(true)
.numberOfArgs(1)
.argName("seconds")
.longOpt(LANGUAGE_SERVER_PROFILING_TIME)
.desc("The duration in seconds limiting the profiling time.")
.build()
val lsProfilingEventsLogPathOption = CliOption.builder
.hasArg(true)
.numberOfArgs(1)
.argName("file")
.longOpt(LANGUAGE_SERVER_PROFILING_EVENTS_LOG_PATH)
.desc("The path to the runtime events log file.")
.build()
val deamonizeOption = CliOption.builder
.longOpt(DAEMONIZE_OPTION)
@ -335,7 +353,9 @@ object Main {
.addOption(newProjectAuthorNameOpt)
.addOption(newProjectAuthorEmailOpt)
.addOption(lsOption)
.addOption(lsProfilingOption)
.addOption(lsProfilingPathOption)
.addOption(lsProfilingTimeOption)
.addOption(lsProfilingEventsLogPathOption)
.addOption(deamonizeOption)
.addOption(interfaceOption)
.addOption(rpcPortOption)
@ -823,14 +843,29 @@ object Main {
dataPort <- Either
.catchNonFatal(dataPortStr.toInt)
.leftMap(_ => "Port must be integer")
profilingEnabled = line.hasOption(LANGUAGE_SERVER_PROFILING)
profilingPathStr =
Option(line.getOptionValue(LANGUAGE_SERVER_PROFILING_PATH))
profilingPath <- Either
.catchNonFatal(profilingPathStr.map(Paths.get(_)))
.leftMap(_ => "Profiling path is invalid")
profilingTimeStr = Option(
line.getOptionValue(LANGUAGE_SERVER_PROFILING_TIME)
)
profilingTime <- Either
.catchNonFatal(profilingTimeStr.map(_.toInt.seconds))
.leftMap(_ => "Profiling time should be an integer")
profilingEventsLogPathStr =
Option(line.getOptionValue(LANGUAGE_SERVER_PROFILING_EVENTS_LOG_PATH))
profilingEventsLogPath <- Either
.catchNonFatal(profilingEventsLogPathStr.map(Paths.get(_)))
.leftMap(_ => "Profiling events log path is invalid")
} yield boot.LanguageServerConfig(
interface,
rpcPort,
dataPort,
rootId,
rootPath,
profilingEnabled
ProfilingConfig(profilingEventsLogPath, profilingPath, profilingTime)
)
/** Prints the version of the Enso executable.

View File

@ -1,7 +1,6 @@
package org.enso.jsonrpc
import java.util.UUID
import akka.NotUsed
import akka.actor.{ActorRef, ActorSystem, Props}
import akka.http.scaladsl.Http
@ -10,6 +9,7 @@ import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.stream.scaladsl.{Flow, Sink, Source}
import akka.stream.{Materializer, OverflowStrategy}
import com.typesafe.scalalogging.LazyLogging
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future}
@ -31,7 +31,7 @@ class JsonRpcServer(
)(
implicit val system: ActorSystem,
implicit val materializer: Materializer
) {
) extends LazyLogging {
implicit val ec: ExecutionContext = system.dispatcher
@ -55,6 +55,9 @@ class JsonRpcServer(
_.toStrict(config.lazyMessageTimeout)
.map(msg => MessageHandler.WebMessage(msg.text))
)
.wireTap { webMessage =>
logger.trace(s"Received text message: ${webMessage.message}.")
}
.to(
Sink.actorRef[MessageHandler.WebMessage](
messageHandler,
@ -78,6 +81,9 @@ class JsonRpcServer(
NotUsed
}
.map((outMsg: MessageHandler.WebMessage) => TextMessage(outMsg.message))
.wireTap { textMessage =>
logger.trace(s"Sent text message ${textMessage.text}.")
}
Flow.fromSinkAndSource(incomingMessages, outgoingMessages)
}

View File

@ -2,9 +2,7 @@ package org.enso.loggingservice
import org.enso.loggingservice.internal.{InternalLogMessage, LoggerConnection}
import java.io.{File, FileWriter, Writer}
import java.nio.charset.StandardCharsets
import java.util.logging.{Handler, Level, LogRecord, XMLFormatter}
import java.util.logging.{Handler, Level, LogRecord}
/** A [[Handler]] implementation that allows to use the logging service as a
* backend for [[java.util.logging]].
@ -27,12 +25,6 @@ class JavaLoggingLogHandler(
exception = Option(record.getThrown)
)
connection.send(message)
val w = JavaLoggingLogHandler.log;
if (w != null) {
val f = new XMLFormatter()
val out = f.format(record)
w.write(out);
}
}
}
@ -81,17 +73,4 @@ object JavaLoggingLogHandler {
case LogLevel.Debug => Level.FINE
case LogLevel.Trace => Level.ALL
}
var log: Writer = null
def registerLogFile(file: File): Unit = {
if (this.log != null) {
this.log.close()
}
val w = new FileWriter(file, StandardCharsets.UTF_8, false)
w.write(
"<?xml version='1.0' encoding='UTF-8'?><uigestures version='1.0'>\n"
)
this.log = w
}
}

View File

@ -132,4 +132,16 @@ object LogLevel {
case Debug => akka.event.Logging.DebugLevel
case Trace => akka.event.Logging.DebugLevel
}
/** Converts our internal [[LogLevel]] to the corresponding instance of
* Java log level.
*/
def toJava(logLevel: LogLevel): java.util.logging.Level = logLevel match {
case Off => java.util.logging.Level.OFF
case Error => java.util.logging.Level.SEVERE
case Warning => java.util.logging.Level.WARNING
case Info => java.util.logging.Level.INFO
case Debug => java.util.logging.Level.FINER
case Trace => java.util.logging.Level.FINEST
}
}

View File

@ -1,16 +1,11 @@
package org.enso.loggingservice
import java.nio.file.Path
import akka.http.scaladsl.model.Uri
import com.typesafe.scalalogging.Logger
import org.enso.logger.masking.Masking
import org.enso.loggingservice.printers.{
FileOutputPrinter,
Printer,
StderrPrinter,
StderrPrinterWithColors
}
import org.enso.loggingservice.printers._
import java.nio.file.Path
import scala.concurrent.duration.DurationInt
import scala.concurrent.{Await, ExecutionContext, Future, Promise}
@ -54,7 +49,8 @@ abstract class LoggingServiceSetupHelper(implicit
logLevel: Option[LogLevel],
connectToExternalLogger: Option[Uri],
colorMode: ColorMode,
logMasking: Boolean
logMasking: Boolean,
profilingLog: Option[Path]
): Unit = {
val actualLogLevel = logLevel.getOrElse(defaultLogLevel)
Masking.setup(logMasking)
@ -62,7 +58,7 @@ abstract class LoggingServiceSetupHelper(implicit
case Some(uri) =>
setupLoggingConnection(uri, actualLogLevel)
case None =>
setupLoggingServer(actualLogLevel, colorMode)
setupLoggingServer(actualLogLevel, colorMode, profilingLog)
}
}
@ -114,7 +110,8 @@ abstract class LoggingServiceSetupHelper(implicit
private def setupLoggingServer(
logLevel: LogLevel,
colorMode: ColorMode
colorMode: ColorMode,
profilingLog: Option[Path]
): Unit = {
val printExceptionsInStderr =
implicitly[Ordering[LogLevel]].compare(logLevel, LogLevel.Debug) >= 0
@ -132,10 +129,11 @@ abstract class LoggingServiceSetupHelper(implicit
suffix = logFileSuffix,
printExceptions = true
)
val profilingPrinterOpt = profilingLog.map(new FileXmlPrinter(_))
Seq(
stderrPrinter(colorMode, printExceptionsInStderr),
filePrinter
)
) ++ profilingPrinterOpt
} catch {
case NonFatal(error) =>
logger.error(

View File

@ -70,7 +70,7 @@ object FileOutputPrinter {
def create(
logDirectory: Path,
suffix: String,
printExceptions: Boolean = true
printExceptions: Boolean
): FileOutputPrinter =
new FileOutputPrinter(logDirectory, suffix, printExceptions)
}

View File

@ -0,0 +1,61 @@
package org.enso.loggingservice.printers
import org.enso.loggingservice.LogLevel
import org.enso.loggingservice.internal.protocol.WSLogMessage
import java.io.PrintWriter
import java.nio.file.{Files, Path, StandardOpenOption}
import java.util.logging.{LogRecord, XMLFormatter}
/** Creates a new file in [[logPath]] and writes incoming log messages to
* this file in XML format.
*
* @param logPath the file path to log
*/
class FileXmlPrinter(logPath: Path) extends Printer {
private val writer = initializeWriter()
private val formatter = new XMLFormatter()
/** @inheritdoc */
override def print(message: WSLogMessage): Unit = {
val lines = formatter.format(toLogRecord(message))
writer.print(lines)
}
/** @inheritdoc */
override def shutdown(): Unit = {
writer.flush()
writer.close()
}
/** Opens the log file for writing. */
private def initializeWriter(): PrintWriter = {
Option(logPath.getParent).foreach(Files.createDirectories(_))
val writer = new PrintWriter(
Files.newBufferedWriter(
logPath,
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING,
StandardOpenOption.WRITE
)
)
writer.println(FileXmlPrinter.Header)
writer
}
/** Converts [[WSLogMessage]] to java [[LogRecord]]. */
private def toLogRecord(wsLogMessage: WSLogMessage): LogRecord = {
val record =
new LogRecord(LogLevel.toJava(wsLogMessage.level), wsLogMessage.message)
record.setInstant(wsLogMessage.timestamp)
record.setLoggerName(wsLogMessage.group)
record
}
}
object FileXmlPrinter {
private val Header: String =
"<?xml version='1.0' encoding='UTF-8'?><uigestures version='1.0'>"
}

View File

@ -3,10 +3,9 @@ package org.enso.profiling
import org.netbeans.modules.sampler.Sampler
import java.io.{DataOutputStream, File, FileOutputStream}
import java.nio.file.Files
import java.util.concurrent.{CompletableFuture, Executor, Executors}
import scala.concurrent.duration.Duration
import scala.concurrent.duration.FiniteDuration
import scala.util.Using
/** Gathers application performance statistics that can be visualised in Java
@ -14,16 +13,11 @@ import scala.util.Using
*
* @param output the output stream to write the collected statistics to
*/
final class TempFileSampler(output: File) extends MethodsSampler {
final class FileSampler(output: File) extends MethodsSampler {
private val sampler: Sampler = Sampler.createSampler(getClass.getSimpleName)
private var samplingStarted: Boolean = false
def getSiblingFile(ext: String): File = {
val newName = output.getName.replace(".npss", ext)
new File(output.getParent, newName)
}
/** @inheritdoc */
def start(): Unit =
this.synchronized {
@ -45,7 +39,7 @@ final class TempFileSampler(output: File) extends MethodsSampler {
}
/** @inheritdoc */
def stop(delay: Duration)(implicit ec: Executor): Unit =
def stop(delay: FiniteDuration)(implicit ec: Executor): Unit =
this.synchronized {
val executor = Executors.newSingleThreadScheduledExecutor()
@ -58,22 +52,4 @@ final class TempFileSampler(output: File) extends MethodsSampler {
)
.whenComplete((_, _) => executor.shutdown())
}
/** @return `true` if the sampling is started. */
def isSamplingStarted: Boolean =
this.samplingStarted
}
object TempFileSampler {
/** Create an instance of [[MethodsSampler]] that writes the data to the
* temporary `.npss` file with the provided prefix.
*
* @param prefix the prefix of the temp file.
* @return the [[MethodsSampler]] instance
*/
def apply(prefix: String): TempFileSampler = {
val path = Files.createTempFile(s"$prefix-", ".npss")
Files.deleteIfExists(path)
new TempFileSampler(path.toFile)
}
}

View File

@ -2,7 +2,7 @@ package org.enso.profiling
import java.util.concurrent.Executor
import scala.concurrent.duration.Duration
import scala.concurrent.duration.FiniteDuration
/** Sampler gathers the application performance statistics. */
trait MethodsSampler {
@ -19,5 +19,5 @@ trait MethodsSampler {
* @param delay the duration to wait before stopping
* @param ec the execution context
*/
def stop(delay: Duration)(implicit ec: Executor): Unit
def stop(delay: FiniteDuration)(implicit ec: Executor): Unit
}

View File

@ -2,7 +2,7 @@ package org.enso.profiling
import java.util.concurrent.Executor
import scala.concurrent.duration.Duration
import scala.concurrent.duration.FiniteDuration
/** Sampler that does nothing. */
final class NoopSampler extends MethodsSampler {
@ -14,7 +14,7 @@ final class NoopSampler extends MethodsSampler {
override def stop(): Unit = ()
/** @inheritdoc */
override def stop(delay: Duration)(implicit ec: Executor): Unit = ()
override def stop(delay: FiniteDuration)(implicit ec: Executor): Unit = ()
}
object NoopSampler {

View File

@ -7,12 +7,14 @@ import scala.util.Try
object Cli {
val JSON_OPTION = "json"
val HELP_OPTION = "help"
val NO_LOG_MASKING = "no-log-masking"
val VERBOSE_OPTION = "verbose"
val VERSION_OPTION = "version"
val ENABLE_PROFILING = "profiling"
val JSON_OPTION = "json"
val HELP_OPTION = "help"
val NO_LOG_MASKING = "no-log-masking"
val VERBOSE_OPTION = "verbose"
val VERSION_OPTION = "version"
val PROFILING_PATH = "profiling-path"
val PROFILING_TIME = "profiling-time"
val PROFILING_EVENTS_LOG_PATH = "profiling-events-log-path"
object option {
@ -47,9 +49,30 @@ object Cli {
)
.build()
val enableProfiling: cli.Option = cli.Option.builder
.longOpt(ENABLE_PROFILING)
.desc("Enables the application profiling.")
val profilingPath: cli.Option = cli.Option.builder
.hasArg(true)
.numberOfArgs(1)
.argName("file")
.longOpt(PROFILING_PATH)
.desc("The path to profiling file. Enables the application profiling.")
.build()
val profilingTime: cli.Option = cli.Option.builder
.hasArg(true)
.numberOfArgs(1)
.argName("seconds")
.longOpt(PROFILING_TIME)
.desc("The duration in seconds limiting the application profiling time.")
.build()
val profilingEventsLogPath: cli.Option = cli.Option.builder
.hasArg(true)
.numberOfArgs(1)
.argName("file")
.longOpt(PROFILING_EVENTS_LOG_PATH)
.desc(
"The path to the runtime events log file. Enables the runtime events logging."
)
.build()
}
@ -60,7 +83,9 @@ object Cli {
.addOption(option.version)
.addOption(option.json)
.addOption(option.noLogMasking)
.addOption(option.enableProfiling)
.addOption(option.profilingPath)
.addOption(option.profilingTime)
.addOption(option.profilingEventsLogPath)
/** Parse the command line options. */
def parse(args: Array[String]): Either[String, cli.CommandLine] = {

View File

@ -23,6 +23,7 @@ import zio.console._
import zio.interop.catz.core._
import java.io.IOException
import java.nio.file.{FileAlreadyExistsException, Files, Path, Paths}
import java.util.concurrent.ScheduledThreadPoolExecutor
import scala.concurrent.duration._
@ -120,23 +121,99 @@ object ProjectManager extends App with LazyLogging {
}
}
/** Parses and validates the command line arguments.
*
* @param options the command line arguments
*/
def parseOpts(
options: CommandLine
): ZIO[ZEnv, Throwable, ProjectManagerOptions] = {
val parseProfilingPath = ZIO
.effect {
Option(options.getOptionValue(Cli.PROFILING_PATH))
.map(Paths.get(_).toAbsolutePath)
}
.flatMap {
case pathOpt @ Some(path) =>
ZIO.ifM(ZIO.effect(Files.isDirectory(path)))(
onTrue = putStrLnErr(
s"Error: ${Cli.PROFILING_PATH} is a directory: $path"
) *>
ZIO.fail(new FileAlreadyExistsException(path.toString)),
onFalse = ZIO.succeed(pathOpt)
)
case None =>
ZIO.succeed(None)
}
.catchAll { err =>
putStrLnErr(s"Invalid ${Cli.PROFILING_PATH} argument.") *> ZIO.fail(err)
}
val parseProfilingTime = ZIO
.effect {
Option(options.getOptionValue(Cli.PROFILING_TIME))
.map(_.toInt.seconds)
}
.catchAll { err =>
putStrLnErr(s"Invalid ${Cli.PROFILING_TIME} argument.") *> ZIO.fail(err)
}
val parseProfilingEventsLogPath = ZIO
.effect {
Option(options.getOptionValue(Cli.PROFILING_EVENTS_LOG_PATH))
.map(Paths.get(_).toAbsolutePath)
}
.flatMap {
case pathOpt @ Some(path) =>
ZIO.ifM(ZIO.effect(Files.isDirectory(path)))(
onTrue = putStrLnErr(
s"Error: ${Cli.PROFILING_EVENTS_LOG_PATH} is a directory: $path"
) *>
ZIO.fail(new FileAlreadyExistsException(path.toString)),
onFalse = ZIO.succeed(pathOpt)
)
case None =>
ZIO.succeed(None)
}
.catchAll { err =>
putStrLnErr(s"Invalid ${Cli.PROFILING_EVENTS_LOG_PATH} argument.") *>
ZIO.fail(err)
}
for {
profilingEventsLogPath <- parseProfilingEventsLogPath
profilingPath <- parseProfilingPath
profilingTime <- parseProfilingTime
} yield ProjectManagerOptions(
profilingEventsLogPath,
profilingPath,
profilingTime
)
}
/** The main function of the application, which will be passed the command-line
* arguments to the program and has to return an `IO` with the errors fully handled.
*/
def runOpts(options: CommandLine): ZIO[ZEnv, IOException, ExitCode] = {
def runOpts(options: CommandLine): ZIO[ZEnv, Throwable, ExitCode] = {
if (options.hasOption(Cli.HELP_OPTION)) {
ZIO.effectTotal(Cli.printHelp()) *>
ZIO.succeed(SuccessExitCode)
} else if (options.hasOption(Cli.VERSION_OPTION)) {
displayVersion(options.hasOption(Cli.JSON_OPTION))
} else {
val verbosity = options.getOptions.count(_ == Cli.option.verbose)
val logMasking = !options.hasOption(Cli.NO_LOG_MASKING)
val profilingEnabled = options.hasOption(Cli.ENABLE_PROFILING)
val verbosity = options.getOptions.count(_ == Cli.option.verbose)
val logMasking = !options.hasOption(Cli.NO_LOG_MASKING)
logger.info("Starting Project Manager...")
for {
logLevel <- setupLogging(verbosity, logMasking)
procConf = MainProcessConfig(logLevel, profilingEnabled)
opts <- parseOpts(options)
profilingLog = opts.profilingPath.map(getSiblingFile(_, ".log"))
logLevel <- setupLogging(verbosity, logMasking, profilingLog)
procConf = MainProcessConfig(
logLevel,
opts.profilingRuntimeEventsLog,
opts.profilingPath,
opts.profilingTime
)
exitCode <- mainProcess(procConf).fold(
th => {
logger.error("Main process execution failed.", th)
@ -150,7 +227,8 @@ object ProjectManager extends App with LazyLogging {
private def setupLogging(
verbosityLevel: Int,
logMasking: Boolean
logMasking: Boolean,
profilingLog: Option[Path]
): ZIO[Console, IOException, LogLevel] = {
val level = verbosityLevel match {
case 0 => LogLevel.Info
@ -164,7 +242,7 @@ object ProjectManager extends App with LazyLogging {
ZIO
.effect {
Logging.setup(Some(level), None, colorMode, logMasking)
Logging.setup(Some(level), None, colorMode, logMasking, profilingLog)
}
.catchAll { exception =>
putStrLnErr(s"Failed to setup the logger: $exception")
@ -203,4 +281,12 @@ object ProjectManager extends App with LazyLogging {
)
}
private def getSiblingFile(file: Path, ext: String): Path = {
val fileName = file.getFileName.toString
val extensionIndex = fileName.lastIndexOf(".")
val newName =
if (extensionIndex > 0) fileName.substring(0, extensionIndex) + ext
else fileName + ext
file.getParent.resolve(newName)
}
}

View File

@ -0,0 +1,17 @@
package org.enso.projectmanager.boot
import java.nio.file.Path
import scala.concurrent.duration.FiniteDuration
/** The runtime options.
*
* @param profilingRuntimeEventsLog the path to the runtime events log file
* @param profilingPath the path to the profiling output file
* @param profilingTime the time limiting the profiling duration
*/
case class ProjectManagerOptions(
profilingRuntimeEventsLog: Option[Path],
profilingPath: Option[Path],
profilingTime: Option[FiniteDuration]
)

View File

@ -3,6 +3,7 @@ package org.enso.projectmanager.boot
import org.enso.loggingservice.LogLevel
import java.io.File
import java.nio.file.Path
import scala.concurrent.duration.FiniteDuration
@ -12,11 +13,15 @@ object configuration {
* main project manager process.
*
* @param logLevel the logging level
* @param profilingEnabled if the profiling is enabled
* @param profilingEventsLogPath the path to the runtime events log file
* @param profilingPath the path to the profiling out file
* @param profilingTime the time limiting the profiling duration
*/
case class MainProcessConfig(
logLevel: LogLevel,
profilingEnabled: Boolean
profilingEventsLogPath: Option[Path],
profilingPath: Option[Path],
profilingTime: Option[FiniteDuration]
)
/** A configuration object for properties of the Project Manager.

View File

@ -95,8 +95,23 @@ object ExecutorWithUnlimitedPool extends LanguageServerExecutor {
environment = distributionConfiguration.environment,
loggerConnection = descriptor.deferredLoggingServiceEndpoint
)
val profilingPathArguments =
descriptor.profilingPath.toSeq
.flatMap(path => Seq("--server-profiling-path", path.toString))
val profilingTimeArguments =
descriptor.profilingTime.toSeq
.flatMap(time =>
Seq("--server-profiling-time", time.toSeconds.toString)
)
val profilingEventsLogPathArguments =
descriptor.profilingEventsLogPath.toSeq
.flatMap(path =>
Seq("--server-profiling-events-log-path", path.toString)
)
val additionalArguments =
Option.when(descriptor.profilingEnabled)("--server-profiling").toSeq
profilingPathArguments ++
profilingTimeArguments ++
profilingEventsLogPathArguments
val runSettings = runner
.startLanguageServer(
options = options,

View File

@ -79,7 +79,9 @@ class LanguageServerController(
engineVersion = engineVersion,
jvmSettings = distributionConfiguration.defaultJVMSettings,
discardOutput = distributionConfiguration.shouldDiscardChildOutput,
profilingEnabled = processConfig.profilingEnabled,
profilingEventsLogPath = processConfig.profilingEventsLogPath,
profilingPath = processConfig.profilingPath,
profilingTime = processConfig.profilingTime,
deferredLoggingServiceEndpoint = loggingServiceDescriptor.getEndpoint
)

View File

@ -1,14 +1,16 @@
package org.enso.projectmanager.infrastructure.languageserver
import java.util.UUID
import akka.http.scaladsl.model.Uri
import nl.gn0s1s.bump.SemVer
import org.enso.projectmanager.boot.configuration.NetworkConfig
import org.enso.projectmanager.versionmanagement.DistributionConfiguration
import org.enso.runtimeversionmanager.runner.JVMSettings
import java.nio.file.Path
import java.util.UUID
import scala.concurrent.Future
import scala.concurrent.duration.FiniteDuration
/** A descriptor specifying options related to starting a Language Server.
*
@ -23,7 +25,9 @@ import scala.concurrent.Future
* @param jvmSettings settings to use for the JVM that will host the engine
* @param discardOutput specifies if the process output should be discarded or
* printed to parent's streams
* @param profilingEnabled enables the language server profiling
* @param profilingEventsLogPath the path to the runtime events log file
* @param profilingPath the language server profiling file path
* @param profilingTime the time limiting the profiling duration
* @param deferredLoggingServiceEndpoint a future that is completed once the
* logging service has been fully set-up;
* if the child component should connect
@ -39,6 +43,8 @@ case class LanguageServerDescriptor(
engineVersion: SemVer,
jvmSettings: JVMSettings,
discardOutput: Boolean,
profilingEnabled: Boolean,
profilingEventsLogPath: Option[Path],
profilingPath: Option[Path],
profilingTime: Option[FiniteDuration],
deferredLoggingServiceEndpoint: Future[Option[Uri]]
)

View File

@ -71,7 +71,7 @@ class BaseServerSpec extends JsonRpcServerTestKit with BeforeAndAfterAll {
val debugLogs: Boolean = false
/** Enables the application profiling. */
val profilingEnabled: Boolean = false
val profilingPath: Option[Path] = None
/** Tests can override this to allow child process output to be displayed. */
val debugChildLogs: Boolean = false
@ -87,8 +87,10 @@ class BaseServerSpec extends JsonRpcServerTestKit with BeforeAndAfterAll {
val processConfig: MainProcessConfig =
MainProcessConfig(
logLevel = if (debugLogs) LogLevel.Trace else LogLevel.Off,
profilingEnabled = profilingEnabled
logLevel = if (debugLogs) LogLevel.Trace else LogLevel.Off,
profilingPath = profilingPath,
profilingTime = None,
profilingEventsLogPath = None
)
val testClock =