mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 00:11:45 +03:00
Add shortcuts to start and stop the backend profiling (#8358)
close #8329 Changelog: - add: `cmd`+`shift`+`,` and `cmd`+`shift`+`.` shortcuts to start and stop the backend profiling. Profiling data is stored on disk.
This commit is contained in:
parent
87dfb57f53
commit
4b3ba78b52
@ -159,6 +159,14 @@ trait API {
|
||||
#[MethodInput=RecomputeInput, rpc_name="executionContext/recompute"]
|
||||
fn recompute(&self, context_id: ContextId, invalidated_expressions: InvalidatedExpressions, execution_environment: Option<ExecutionEnvironment>) -> ();
|
||||
|
||||
/// Start the profiling of the language server.
|
||||
#[MethodInput=ProfilingStartInput, rpc_name="profiling/start"]
|
||||
fn profiling_start(&self, memory_snapshot: Option<bool>) -> ();
|
||||
|
||||
/// Stop the profiling of the language server.
|
||||
#[MethodInput=ProfilingStopInput, rpc_name="profiling/stop"]
|
||||
fn profiling_stop(&self) -> ();
|
||||
|
||||
/// Obtain the full suggestions database.
|
||||
#[MethodInput=GetSuggestionsDatabaseInput, rpc_name="search/getSuggestionsDatabase"]
|
||||
fn get_suggestions_database(&self) -> response::GetSuggestionDatabase;
|
||||
|
@ -129,6 +129,8 @@ broken and require further investigation.
|
||||
|
||||
| Shortcut | Action |
|
||||
| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| <kbd>ctrl</kbd> + <kbd>alt</kbd> + <kbd>,</kbd> | Start the language server profiling. |
|
||||
| <kbd>ctrl</kbd> + <kbd>alt</kbd> + <kbd>.</kbd> | Stop the language server profiling and save the collected data. |
|
||||
| <kbd>ctrl</kbd> + <kbd>shift</kbd> + <kbd>x</kbd> | Force reloading file in the backend. May fix some issues with synchronization if they appear. |
|
||||
| <kbd>ctrl</kbd> + <kbd>shift</kbd> + <kbd>d</kbd> | Toggle Debug Mode. All actions below are only possible when it is activated. |
|
||||
| <kbd>ctrl</kbd> + <kbd>alt</kbd> + <kbd>shift</kbd> + <kbd>i</kbd> | Open the developer console. |
|
||||
|
@ -395,6 +395,18 @@ impl Handle {
|
||||
}
|
||||
}
|
||||
|
||||
/// Command to start gathering the profiling info in the connected language server.
|
||||
pub async fn start_language_server_profiling(&self) -> FallibleResult {
|
||||
self.execution_ctx.start_profiling().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Command to stop gathering the profiling info in the connected language server.
|
||||
pub async fn stop_language_server_profiling(&self) -> FallibleResult {
|
||||
self.execution_ctx.stop_profiling().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the current call stack frames.
|
||||
pub fn call_stack(&self) -> Vec<LocalCall> {
|
||||
self.execution_ctx.stack_items().collect()
|
||||
|
@ -568,6 +568,14 @@ pub trait API: Debug {
|
||||
#[allow(clippy::needless_lifetimes)] // Note: Needless lifetimes
|
||||
fn restart<'a>(&'a self) -> BoxFuture<'a, FallibleResult>;
|
||||
|
||||
/// Start the profiling of the language server.
|
||||
#[allow(clippy::needless_lifetimes)] // Note: Needless lifetimes
|
||||
fn start_profiling<'a>(&'a self) -> BoxFuture<'a, FallibleResult>;
|
||||
|
||||
/// Stop the profiling of the language server.
|
||||
#[allow(clippy::needless_lifetimes)] // Note: Needless lifetimes
|
||||
fn stop_profiling<'a>(&'a self) -> BoxFuture<'a, FallibleResult>;
|
||||
|
||||
/// Adjust method pointers after the project rename action.
|
||||
fn rename_method_pointers(&self, old_project_name: String, new_project_name: String);
|
||||
|
||||
|
@ -298,6 +298,14 @@ impl model::execution_context::API for ExecutionContext {
|
||||
futures::future::ready(Ok(())).boxed_local()
|
||||
}
|
||||
|
||||
fn start_profiling(&self) -> BoxFuture<FallibleResult> {
|
||||
futures::future::ready(Ok(())).boxed_local()
|
||||
}
|
||||
|
||||
fn stop_profiling(&self) -> BoxFuture<FallibleResult> {
|
||||
futures::future::ready(Ok(())).boxed_local()
|
||||
}
|
||||
|
||||
fn rename_method_pointers(&self, old_project_name: String, new_project_name: String) {
|
||||
let update_method_pointer = |method_pointer: &mut MethodPointer| {
|
||||
let module = method_pointer.module.replacen(&old_project_name, &new_project_name, 1);
|
||||
|
@ -328,6 +328,23 @@ impl model::execution_context::API for ExecutionContext {
|
||||
.boxed_local()
|
||||
}
|
||||
|
||||
fn start_profiling(&self) -> BoxFuture<FallibleResult> {
|
||||
async move {
|
||||
let memory_snapshot = Some(true);
|
||||
self.language_server.client.profiling_start(&memory_snapshot).await?;
|
||||
Ok(())
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
|
||||
fn stop_profiling(&self) -> BoxFuture<FallibleResult> {
|
||||
async move {
|
||||
self.language_server.client.profiling_stop().await?;
|
||||
Ok(())
|
||||
}
|
||||
.boxed_local()
|
||||
}
|
||||
|
||||
fn rename_method_pointers(&self, old_project_name: String, new_project_name: String) {
|
||||
self.model.rename_method_pointers(old_project_name, new_project_name);
|
||||
}
|
||||
|
@ -278,6 +278,30 @@ impl Model {
|
||||
})
|
||||
}
|
||||
|
||||
fn start_language_server_profiling(&self) {
|
||||
let controller = self.graph_controller.clone_ref();
|
||||
let status_notifications = self.controller.status_notifications.clone_ref();
|
||||
executor::global::spawn(async move {
|
||||
if let Err(err) = controller.start_language_server_profiling().await {
|
||||
error!("Error starting the language server profiling: {err}");
|
||||
} else {
|
||||
status_notifications.publish_event("Backend profiling started.");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn stop_language_server_profiling(&self) {
|
||||
let controller = self.graph_controller.clone_ref();
|
||||
let status_notifications = self.controller.status_notifications.clone_ref();
|
||||
executor::global::spawn(async move {
|
||||
status_notifications.publish_event("Stopping backend profiling ...");
|
||||
if let Err(err) = controller.stop_language_server_profiling().await {
|
||||
error!("Error stopping the language server profiling: {err}");
|
||||
}
|
||||
status_notifications.publish_event("Backend profiling stopped.");
|
||||
})
|
||||
}
|
||||
|
||||
/// Prepare a list of projects to display in the Open Project dialog.
|
||||
fn project_list_opened(&self, project_list_ready: frp::Source<()>) {
|
||||
let controller = self.ide_controller.clone_ref();
|
||||
@ -463,6 +487,9 @@ impl Project {
|
||||
eval graph_view.execution_environment((env) model.execution_environment_changed(*env));
|
||||
eval_ graph_view.execution_environment_play_button_pressed( model.trigger_clean_live_execution());
|
||||
|
||||
eval_ view.start_language_server_profiling(model.start_language_server_profiling());
|
||||
eval_ view.stop_language_server_profiling(model.stop_language_server_profiling());
|
||||
|
||||
eval view.current_shortcut ((shortcut) model.handled_shortcut_changed(shortcut));
|
||||
}
|
||||
|
||||
|
@ -151,6 +151,10 @@ ensogl::define_endpoints! {
|
||||
accept_searcher_input(),
|
||||
/// Dump the suggestion database in JSON to the console.
|
||||
dump_suggestion_database(),
|
||||
/// Start the language server profiling
|
||||
start_language_server_profiling(),
|
||||
/// Stop the language server profiling
|
||||
stop_language_server_profiling(),
|
||||
}
|
||||
|
||||
Output {
|
||||
@ -812,6 +816,8 @@ impl application::View for View {
|
||||
(Press, "debug_mode", "ctrl shift enter", "debug_push_breadcrumb"),
|
||||
(Press, "debug_mode", "ctrl shift b", "debug_pop_breadcrumb"),
|
||||
(Press, "debug_mode", "ctrl shift u", "dump_suggestion_database"),
|
||||
(Press, "", "cmd alt ,", "start_language_server_profiling"),
|
||||
(Press, "", "cmd alt .", "stop_language_server_profiling"),
|
||||
]
|
||||
.iter()
|
||||
.map(|(a, b, c, d)| Self::self_shortcut_when(*a, *c, *d, *b))
|
||||
|
@ -5111,7 +5111,10 @@ request to store the gathered data. After the profiling is started, subsequent
|
||||
#### Parameters
|
||||
|
||||
```typescript
|
||||
interface ProfilingStartParameters {}
|
||||
interface ProfilingStartParameters {
|
||||
/** Also take a memory snapshot when the profiling is stopped. */
|
||||
memorySnapshot?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
#### Result
|
||||
|
@ -6,9 +6,11 @@ object ProfilingApi {
|
||||
|
||||
case object ProfilingStart extends Method("profiling/start") {
|
||||
|
||||
implicit val hasParams: HasParams.Aux[this.type, Unused.type] =
|
||||
case class Params(memorySnapshot: Option[Boolean])
|
||||
|
||||
implicit val hasParams: HasParams.Aux[this.type, ProfilingStart.Params] =
|
||||
new HasParams[this.type] {
|
||||
type Params = Unused.type
|
||||
type Params = ProfilingStart.Params
|
||||
}
|
||||
implicit val hasResult: HasResult.Aux[this.type, Unused.type] =
|
||||
new HasResult[this.type] {
|
||||
|
@ -44,7 +44,7 @@ final class ProfilingManager(
|
||||
initialized(None)
|
||||
|
||||
private def initialized(sampler: Option[RunningSampler]): Receive = {
|
||||
case ProfilingProtocol.ProfilingStartRequest =>
|
||||
case ProfilingProtocol.ProfilingStartRequest(memorySnapshot) =>
|
||||
sampler match {
|
||||
case Some(_) =>
|
||||
sender() ! ProfilingProtocol.ProfilingStartResponse
|
||||
@ -62,13 +62,15 @@ final class ProfilingManager(
|
||||
|
||||
sender() ! ProfilingProtocol.ProfilingStartResponse
|
||||
context.become(
|
||||
initialized(Some(RunningSampler(instant, sampler, result)))
|
||||
initialized(
|
||||
Some(RunningSampler(instant, sampler, result, memorySnapshot))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case ProfilingProtocol.ProfilingStopRequest =>
|
||||
sampler match {
|
||||
case Some(RunningSampler(instant, sampler, result)) =>
|
||||
case Some(RunningSampler(instant, sampler, result, memorySnapshot)) =>
|
||||
sampler.stop()
|
||||
|
||||
Try(saveSamplerResult(result.toByteArray, instant)) match {
|
||||
@ -81,6 +83,10 @@ final class ProfilingManager(
|
||||
)
|
||||
}
|
||||
|
||||
if (memorySnapshot) {
|
||||
saveMemorySnapshot(instant)
|
||||
}
|
||||
|
||||
runtimeConnector ! RuntimeConnector.RegisterEventsMonitor(
|
||||
new NoopEventsMonitor
|
||||
)
|
||||
@ -93,20 +99,22 @@ final class ProfilingManager(
|
||||
|
||||
case ProfilingProtocol.ProfilingSnapshotRequest =>
|
||||
val instant = clock.instant()
|
||||
|
||||
Try(saveHeapDump(instant)) match {
|
||||
case Failure(exception) =>
|
||||
logger.error("Failed to save the memory snapshot.", exception)
|
||||
case Success(heapDumpPath) =>
|
||||
logger.trace(
|
||||
"Saved the memory snapshot to [{}].",
|
||||
MaskedPath(heapDumpPath)
|
||||
)
|
||||
}
|
||||
saveMemorySnapshot(instant)
|
||||
|
||||
sender() ! ProfilingProtocol.ProfilingSnapshotResponse
|
||||
}
|
||||
|
||||
private def saveMemorySnapshot(instant: Instant): Unit =
|
||||
Try(saveHeapDump(instant)) match {
|
||||
case Failure(exception) =>
|
||||
logger.error("Failed to save the memory snapshot.", exception)
|
||||
case Success(heapDumpPath) =>
|
||||
logger.trace(
|
||||
"Saved the memory snapshot to [{}].",
|
||||
MaskedPath(heapDumpPath)
|
||||
)
|
||||
}
|
||||
|
||||
private def saveSamplerResult(
|
||||
result: Array[Byte],
|
||||
instant: Instant
|
||||
@ -163,7 +171,8 @@ object ProfilingManager {
|
||||
private case class RunningSampler(
|
||||
instant: Instant,
|
||||
sampler: MethodsSampler,
|
||||
result: ByteArrayOutputStream
|
||||
result: ByteArrayOutputStream,
|
||||
memorySnapshot: Boolean
|
||||
)
|
||||
|
||||
private def createProfilingFileName(instant: Instant): String = {
|
||||
|
@ -2,8 +2,11 @@ package org.enso.languageserver.profiling
|
||||
|
||||
object ProfilingProtocol {
|
||||
|
||||
/** A request to start the profiling. */
|
||||
case object ProfilingStartRequest
|
||||
/** A request to start the profiling.
|
||||
*
|
||||
* @param memorySnapshot take memory snapshot
|
||||
*/
|
||||
case class ProfilingStartRequest(memorySnapshot: Boolean)
|
||||
|
||||
/** A response to request to start the profiling. */
|
||||
case object ProfilingStartResponse
|
||||
|
@ -3,7 +3,8 @@ package org.enso.languageserver.requesthandler.profiling
|
||||
import akka.actor.{Actor, ActorRef, Cancellable, Props}
|
||||
import com.typesafe.scalalogging.LazyLogging
|
||||
import org.enso.jsonrpc._
|
||||
import org.enso.languageserver.profiling.{ProfilingApi, ProfilingProtocol}
|
||||
import org.enso.languageserver.profiling.ProfilingApi.ProfilingStart
|
||||
import org.enso.languageserver.profiling.ProfilingProtocol
|
||||
import org.enso.languageserver.requesthandler.RequestTimeout
|
||||
import org.enso.languageserver.util.UnhandledLogging
|
||||
|
||||
@ -24,8 +25,10 @@ class ProfilingStartHandler(timeout: FiniteDuration, profilingManager: ActorRef)
|
||||
override def receive: Receive = requestStage
|
||||
|
||||
private def requestStage: Receive = {
|
||||
case Request(ProfilingApi.ProfilingStart, id, _) =>
|
||||
profilingManager ! ProfilingProtocol.ProfilingStartRequest
|
||||
case Request(ProfilingStart, id, ProfilingStart.Params(memorySnapshot)) =>
|
||||
profilingManager ! ProfilingProtocol.ProfilingStartRequest(
|
||||
memorySnapshot.getOrElse(false)
|
||||
)
|
||||
val cancellable =
|
||||
context.system.scheduler.scheduleOnce(timeout, self, RequestTimeout)
|
||||
context.become(
|
||||
@ -48,7 +51,7 @@ class ProfilingStartHandler(timeout: FiniteDuration, profilingManager: ActorRef)
|
||||
context.stop(self)
|
||||
|
||||
case ProfilingProtocol.ProfilingStartResponse =>
|
||||
replyTo ! ResponseResult(ProfilingApi.ProfilingStart, id, Unused)
|
||||
replyTo ! ResponseResult(ProfilingStart, id, Unused)
|
||||
cancellable.cancel()
|
||||
context.stop(self)
|
||||
}
|
||||
|
@ -11,12 +11,14 @@ object ProfilingJsonMessages {
|
||||
"result": null
|
||||
}"""
|
||||
|
||||
def profilingStart(reqId: Int) =
|
||||
def profilingStart(reqId: Int, memorySnapshot: Boolean = false) =
|
||||
json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "profiling/start",
|
||||
"id": $reqId,
|
||||
"params": null
|
||||
"params": {
|
||||
"memorySnapshot": $memorySnapshot
|
||||
}
|
||||
}"""
|
||||
|
||||
def profilingStop(reqId: Int) =
|
||||
|
@ -49,6 +49,44 @@ class ProfilingManagerTest extends BaseServerTest {
|
||||
Files.exists(eventsFile) shouldEqual true
|
||||
}
|
||||
|
||||
"save profiling with memory snapshot " in {
|
||||
val client = getInitialisedWsClient()
|
||||
|
||||
client.send(json.profilingStart(1, memorySnapshot = true))
|
||||
runtimeConnectorProbe.receiveN(1).head match {
|
||||
case _: RuntimeConnector.RegisterEventsMonitor =>
|
||||
// Ok
|
||||
case other =>
|
||||
fail(s"Unexpected message: $other")
|
||||
}
|
||||
client.expectJson(json.ok(1))
|
||||
|
||||
client.send(json.profilingStop(2))
|
||||
runtimeConnectorProbe.receiveN(1).head match {
|
||||
case _: RuntimeConnector.RegisterEventsMonitor =>
|
||||
// Ok
|
||||
case other =>
|
||||
fail(s"Unexpected message: $other")
|
||||
}
|
||||
client.expectJson(json.ok(2))
|
||||
|
||||
val distributionManager = getDistributionManager
|
||||
val instant = clock.instant
|
||||
val samplesFile = distributionManager.paths.profiling.resolve(
|
||||
ProfilingManager.createSamplesFileName(instant)
|
||||
)
|
||||
val eventsFile = distributionManager.paths.profiling.resolve(
|
||||
ProfilingManager.createEventsFileName(instant)
|
||||
)
|
||||
val snapshotFile = distributionManager.paths.profiling.resolve(
|
||||
ProfilingManager.createHeapDumpFileName(instant)
|
||||
)
|
||||
|
||||
Files.exists(samplesFile) shouldEqual true
|
||||
Files.exists(eventsFile) shouldEqual true
|
||||
Files.exists(snapshotFile) shouldEqual true
|
||||
}
|
||||
|
||||
"save memory snapshot" in {
|
||||
val client = getInitialisedWsClient()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user