Implement Events Monitor (#3392)

PR adds a monitor that handles messages between the language server and the runtime and dumps them as a CSV file `/tmp/enso-api-events-*********.csv`

```
UTC timestamp,Direction,Request Id,Message class
```

# Important Notes
⚠️ Monitor is enabled when the log level is set to trace. You should pass `-vv` (very verbose) option to the backend when starting IDE
```
enso -- -vv
```
This commit is contained in:
Dmitry Bushev 2022-04-12 18:17:47 +03:00 committed by GitHub
parent 891f064a6a
commit c402d9a900
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 123 additions and 7 deletions

View File

@ -152,6 +152,7 @@
[3379]: https://github.com/enso-org/enso/pull/3379
[3381]: https://github.com/enso-org/enso/pull/3381
[3383]: https://github.com/enso-org/enso/pull/3383
[3392]: https://github.com/enso-org/enso/pull/3392
#### Enso Compiler

View File

@ -20,7 +20,8 @@ import org.enso.languageserver.libraries._
import org.enso.languageserver.monitoring.{
HealthCheckEndpoint,
IdlenessEndpoint,
IdlenessMonitor
IdlenessMonitor,
NoopEventsMonitor
}
import org.enso.languageserver.protocol.binary.{
BinaryConnectionControllerFactory,
@ -145,9 +146,16 @@ class MainModule(serverConfig: LanguageServerConfig, logLevel: LogLevel) {
"lock-manager-service"
)
val runtimeEventsMonitor =
if (logLevel == LogLevel.Trace) ApiEventsMonitor()
else new NoopEventsMonitor
log.trace(
s"Started runtime events monitor ${runtimeEventsMonitor.getClass.getName}."
)
lazy val runtimeConnector =
system.actorOf(
RuntimeConnector.props(lockManagerService),
RuntimeConnector.props(lockManagerService, runtimeEventsMonitor),
"runtime-connector"
)

View File

@ -0,0 +1,20 @@
package org.enso.languageserver.monitoring
/** Diagnostic tool that processes event messages. Used for debugging or
* performance review.
*/
trait EventsMonitor {
/** Process the event message.
*
* @param event the event message
*/
def registerEvent(event: Any): Unit
}
/** Events monitor that does nothing. */
final class NoopEventsMonitor extends EventsMonitor {
/** @inheritdoc */
override def registerEvent(event: Any): Unit = ()
}

View File

@ -0,0 +1,77 @@
package org.enso.languageserver.runtime
import org.enso.languageserver.monitoring.EventsMonitor
import org.enso.polyglot.runtime.Runtime.ApiEnvelope
import org.enso.polyglot.runtime.Runtime.Api
import java.nio.charset.StandardCharsets
import java.nio.file.{Files, Path, StandardOpenOption}
import java.time.Clock
/** Gather messages between the language server and the runtime and write them
* to the provided file in CSV format.
*
* @param path the path where to write the events
* @param clock the system clock
*/
final class ApiEventsMonitor(path: Path, clock: Clock) extends EventsMonitor {
import ApiEventsMonitor.Direction
/** @inheritdoc */
override def registerEvent(event: Any): Unit =
event match {
case envelope: ApiEnvelope =>
registerApiEvent(envelope)
case RuntimeConnector.MessageFromRuntime(envelope) =>
registerApiEvent(envelope)
case _ =>
}
def registerApiEvent(event: ApiEnvelope): Unit =
event match {
case Api.Request(requestId, payload) =>
Files.write(
path,
entry(Direction.Request, requestId, payload.getClass),
StandardOpenOption.APPEND,
StandardOpenOption.SYNC
)
case Api.Response(correlationId, payload) =>
Files.write(
path,
entry(Direction.Response, correlationId, payload.getClass),
StandardOpenOption.APPEND,
StandardOpenOption.SYNC
)
}
private def entry(
direction: Direction,
requestId: Option[Api.RequestId],
payload: Class[_]
): Array[Byte] = {
val requestIdEntry = requestId.fold("")(_.toString)
val payloadEntry = payload.getSimpleName
val timeEntry = clock.instant()
s"$timeEntry,$direction,$requestIdEntry,$payloadEntry${System.lineSeparator()}"
.getBytes(StandardCharsets.UTF_8)
}
}
object ApiEventsMonitor {
/** Create default instance of [[ApiEventsMonitor]]. */
def apply(): ApiEventsMonitor =
new ApiEventsMonitor(
Files.createTempFile("enso-api-events-", ".csv"),
Clock.systemUTC()
)
/** Direction of the message. */
sealed trait Direction
object Direction {
case object Request extends Direction
case object Response extends Direction
}
}

View File

@ -2,6 +2,7 @@ package org.enso.languageserver.runtime
import akka.actor.{Actor, ActorRef, Props, Stash}
import com.typesafe.scalalogging.LazyLogging
import org.enso.languageserver.monitoring.EventsMonitor
import org.enso.languageserver.runtime.RuntimeConnector.{
Destroy,
MessageFromRuntime
@ -15,8 +16,10 @@ import org.graalvm.polyglot.io.MessageEndpoint
import java.nio.ByteBuffer
/** An actor managing a connection to Enso's runtime server. */
class RuntimeConnector(handlers: Map[Class[_], ActorRef])
extends Actor
class RuntimeConnector(
handlers: Map[Class[_], ActorRef],
eventsMonitor: EventsMonitor
) extends Actor
with LazyLogging
with UnhandledLogging
with Stash {
@ -36,6 +39,11 @@ class RuntimeConnector(handlers: Map[Class[_], ActorRef])
case _ => stash()
}
def registerEvent: PartialFunction[Any, Any] = { case event =>
eventsMonitor.registerEvent(event)
event
}
/** Performs communication between runtime and language server.
*
* Requests and responses can be sent in both directions and this Actor's
@ -62,7 +70,7 @@ class RuntimeConnector(handlers: Map[Class[_], ActorRef])
def initialized(
engine: MessageEndpoint,
senders: Map[Runtime.Api.RequestId, ActorRef]
): Receive = {
): Receive = registerEvent.andThen {
case Destroy => context.stop(self)
case msg: Runtime.ApiEnvelope =>
@ -120,13 +128,15 @@ object RuntimeConnector {
/** Helper for creating instances of the [[RuntimeConnector]] actor.
*
* @param lockManagerService a reference to the lock manager service actor
* @param monitor events monitor that handles messages between the language
* server and the runtime
* @return a [[Props]] instance for the newly created actor.
*/
def props(lockManagerService: ActorRef): Props = {
def props(lockManagerService: ActorRef, monitor: EventsMonitor): Props = {
val lockRequests =
LockManagerService.handledRequestTypes.map(_ -> lockManagerService)
val handlers: Map[Class[_], ActorRef] = Map.from(lockRequests)
Props(new RuntimeConnector(handlers))
Props(new RuntimeConnector(handlers, monitor))
}
/** Endpoint implementation used to handle connections with the runtime.