mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 17:03:32 +03:00
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:
parent
891f064a6a
commit
c402d9a900
@ -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
|
||||
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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 = ()
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user