summaryrefslogtreecommitdiff
path: root/cli/lsp/language_server.rs
diff options
context:
space:
mode:
authorKitson Kelly <me@kitsonkelly.com>2021-01-27 11:32:49 +1100
committerGitHub <noreply@github.com>2021-01-27 11:32:49 +1100
commite7323002d9fe5ab834754592916aba7cb842613d (patch)
tree0c7265e4a880c0defabf71ca560d54a36d055c69 /cli/lsp/language_server.rs
parentada43cc56ac2e337cc034f8052d7e5da61268c34 (diff)
feat(lsp): add performance measurements (#9209)
Diffstat (limited to 'cli/lsp/language_server.rs')
-rw-r--r--cli/lsp/language_server.rs207
1 files changed, 176 insertions, 31 deletions
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index b322438fa..b892ba8d3 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -31,6 +31,7 @@ use super::diagnostics;
use super::diagnostics::DiagnosticCollection;
use super::diagnostics::DiagnosticSource;
use super::documents::DocumentCache;
+use super::performance::Performance;
use super::sources;
use super::sources::Sources;
use super::text;
@@ -54,14 +55,15 @@ pub struct StateSnapshot {
struct Inner {
assets: HashMap<ModuleSpecifier, Option<AssetDocument>>,
client: Client,
- ts_server: TsServer,
config: Config,
- documents: DocumentCache,
- sources: Sources,
diagnostics: DiagnosticCollection,
+ documents: DocumentCache,
maybe_config_uri: Option<Url>,
maybe_import_map: Option<ImportMap>,
maybe_import_map_uri: Option<Url>,
+ performance: Performance,
+ sources: Sources,
+ ts_server: TsServer,
}
impl LanguageServer {
@@ -81,14 +83,15 @@ impl Inner {
Self {
assets: Default::default(),
client,
- ts_server: TsServer::new(),
config: Default::default(),
- documents: Default::default(),
- sources,
diagnostics: Default::default(),
+ documents: Default::default(),
maybe_config_uri: Default::default(),
maybe_import_map: Default::default(),
maybe_import_map_uri: Default::default(),
+ performance: Default::default(),
+ sources,
+ ts_server: TsServer::new(),
}
}
@@ -103,7 +106,8 @@ impl Inner {
&self,
specifier: ModuleSpecifier,
) -> Result<LineIndex, AnyError> {
- if specifier.as_url().scheme() == "asset" {
+ let mark = self.performance.mark("get_line_index");
+ let result = if specifier.as_url().scheme() == "asset" {
let maybe_asset = self.assets.get(&specifier).cloned();
if let Some(maybe_asset) = maybe_asset {
if let Some(asset) = maybe_asset {
@@ -128,7 +132,9 @@ impl Inner {
Ok(line_index)
} else {
Err(anyhow!("Unable to find line index for: {}", specifier))
- }
+ };
+ self.performance.measure(mark);
+ result
}
/// Only searches already cached assets and documents for a line index. If
@@ -137,7 +143,8 @@ impl Inner {
&self,
specifier: &ModuleSpecifier,
) -> Option<LineIndex> {
- if specifier.as_url().scheme() == "asset" {
+ let mark = self.performance.mark("get_line_index_sync");
+ let maybe_line_index = if specifier.as_url().scheme() == "asset" {
if let Some(Some(asset)) = self.assets.get(specifier) {
Some(asset.line_index.clone())
} else {
@@ -150,7 +157,9 @@ impl Inner {
} else {
self.sources.get_line_index(specifier)
}
- }
+ };
+ self.performance.measure(mark);
+ maybe_line_index
}
async fn prepare_diagnostics(&mut self) -> Result<(), AnyError> {
@@ -162,6 +171,7 @@ impl Inner {
let lint = async {
let mut diagnostics = None;
if lint_enabled {
+ let mark = self.performance.mark("prepare_diagnostics_lint");
diagnostics = Some(
diagnostics::generate_lint_diagnostics(
self.snapshot(),
@@ -169,6 +179,7 @@ impl Inner {
)
.await,
);
+ self.performance.measure(mark);
};
Ok::<_, AnyError>(diagnostics)
};
@@ -176,6 +187,7 @@ impl Inner {
let ts = async {
let mut diagnostics = None;
if enabled {
+ let mark = self.performance.mark("prepare_diagnostics_ts");
diagnostics = Some(
diagnostics::generate_ts_diagnostics(
self.snapshot(),
@@ -184,6 +196,7 @@ impl Inner {
)
.await?,
);
+ self.performance.measure(mark);
};
Ok::<_, AnyError>(diagnostics)
};
@@ -191,6 +204,7 @@ impl Inner {
let deps = async {
let mut diagnostics = None;
if enabled {
+ let mark = self.performance.mark("prepare_diagnostics_deps");
diagnostics = Some(
diagnostics::generate_dependency_diagnostics(
self.snapshot(),
@@ -198,6 +212,7 @@ impl Inner {
)
.await?,
);
+ self.performance.measure(mark);
};
Ok::<_, AnyError>(diagnostics)
};
@@ -249,6 +264,7 @@ impl Inner {
}
async fn publish_diagnostics(&mut self) -> Result<(), AnyError> {
+ let mark = self.performance.mark("publish_diagnostics");
let (maybe_changes, diagnostics_collection) = {
let diagnostics_collection = &mut self.diagnostics;
let maybe_changes = diagnostics_collection.take_changes();
@@ -291,6 +307,7 @@ impl Inner {
}
}
+ self.performance.measure(mark);
Ok(())
}
@@ -302,7 +319,8 @@ impl Inner {
}
}
- async fn update_import_map(&mut self) -> Result<(), AnyError> {
+ pub async fn update_import_map(&mut self) -> Result<(), AnyError> {
+ let mark = self.performance.mark("update_import_map");
let (maybe_import_map, maybe_root_uri) = {
let config = &self.config;
(config.settings.import_map.clone(), config.root_uri.clone())
@@ -344,10 +362,12 @@ impl Inner {
} else {
self.maybe_import_map = None;
}
+ self.performance.measure(mark);
Ok(())
}
async fn update_tsconfig(&mut self) -> Result<(), AnyError> {
+ let mark = self.performance.mark("update_tsconfig");
let mut tsconfig = TsConfig::new(json!({
"allowJs": true,
"experimentalDecorators": true,
@@ -413,6 +433,7 @@ impl Inner {
.ts_server
.request(self.snapshot(), tsc::RequestMethod::Configure(tsconfig))
.await?;
+ self.performance.measure(mark);
Ok(())
}
}
@@ -513,6 +534,7 @@ impl Inner {
}
async fn did_open(&mut self, params: DidOpenTextDocumentParams) {
+ let mark = self.performance.mark("did_open");
if params.text_document.uri.scheme() == "deno" {
// we can ignore virtual text documents opening, as they don't need to
// be tracked in memory, as they are static assets that won't change
@@ -532,6 +554,7 @@ impl Inner {
error!("{}", err);
}
+ self.performance.measure(mark);
// TODO(@kitsonk): how to better lazily do this?
if let Err(err) = self.prepare_diagnostics().await {
error!("{}", err);
@@ -539,6 +562,7 @@ impl Inner {
}
async fn did_change(&mut self, params: DidChangeTextDocumentParams) {
+ let mark = self.performance.mark("did_change");
let specifier = utils::normalize_url(params.text_document.uri);
if let Err(err) = self.documents.change(
&specifier,
@@ -554,6 +578,7 @@ impl Inner {
error!("{}", err);
}
+ self.performance.measure(mark);
// TODO(@kitsonk): how to better lazily do this?
if let Err(err) = self.prepare_diagnostics().await {
error!("{}", err);
@@ -561,6 +586,7 @@ impl Inner {
}
async fn did_close(&mut self, params: DidCloseTextDocumentParams) {
+ let mark = self.performance.mark("did_close");
if params.text_document.uri.scheme() == "deno" {
// we can ignore virtual text documents opening, as they don't need to
// be tracked in memory, as they are static assets that won't change
@@ -574,6 +600,7 @@ impl Inner {
if let Err(err) = self.prepare_diagnostics().await {
error!("{}", err);
}
+ self.performance.measure(mark);
}
async fn did_save(&self, _params: DidSaveTextDocumentParams) {
@@ -584,6 +611,7 @@ impl Inner {
&mut self,
params: DidChangeConfigurationParams,
) {
+ let mark = self.performance.mark("did_change_configuration");
let config = if self.config.client_capabilities.workspace_configuration {
self
.client
@@ -625,12 +653,14 @@ impl Inner {
} else {
error!("received empty extension settings from the client");
}
+ self.performance.measure(mark);
}
async fn did_change_watched_files(
&mut self,
params: DidChangeWatchedFilesParams,
) {
+ let mark = self.performance.mark("did_change_watched_files");
// if the current import map has changed, we need to reload it
if let Some(import_map_uri) = &self.maybe_import_map_uri {
if params.changes.iter().any(|fe| *import_map_uri == fe.uri) {
@@ -653,12 +683,14 @@ impl Inner {
}
}
}
+ self.performance.measure(mark);
}
async fn formatting(
&self,
params: DocumentFormattingParams,
) -> LspResult<Option<Vec<TextEdit>>> {
+ let mark = self.performance.mark("formatting");
let specifier = utils::normalize_url(params.text_document.uri.clone());
let file_text = self
.documents
@@ -697,6 +729,7 @@ impl Inner {
.await
.unwrap();
+ self.performance.measure(mark);
if let Some(text_edits) = text_edits {
if text_edits.is_empty() {
Ok(None)
@@ -713,6 +746,7 @@ impl Inner {
if !self.enabled() {
return Ok(None);
}
+ let mark = self.performance.mark("hover");
let specifier = utils::normalize_url(
params.text_document_position_params.text_document.uri,
);
@@ -736,8 +770,10 @@ impl Inner {
serde_json::from_value(res).unwrap();
if let Some(quick_info) = maybe_quick_info {
let hover = quick_info.to_hover(&line_index);
+ self.performance.measure(mark);
Ok(Some(hover))
} else {
+ self.performance.measure(mark);
Ok(None)
}
}
@@ -749,6 +785,7 @@ impl Inner {
if !self.enabled() {
return Ok(None);
}
+ let mark = self.performance.mark("document_highlight");
let specifier = utils::normalize_url(
params.text_document_position_params.text_document.uri,
);
@@ -774,14 +811,15 @@ impl Inner {
serde_json::from_value(res).unwrap();
if let Some(document_highlights) = maybe_document_highlights {
- Ok(Some(
- document_highlights
- .into_iter()
- .map(|dh| dh.to_highlight(&line_index))
- .flatten()
- .collect(),
- ))
+ let result = document_highlights
+ .into_iter()
+ .map(|dh| dh.to_highlight(&line_index))
+ .flatten()
+ .collect();
+ self.performance.measure(mark);
+ Ok(Some(result))
} else {
+ self.performance.measure(mark);
Ok(None)
}
}
@@ -793,6 +831,7 @@ impl Inner {
if !self.enabled() {
return Ok(None);
}
+ let mark = self.performance.mark("references");
let specifier =
utils::normalize_url(params.text_document_position.text_document.uri);
let line_index =
@@ -829,8 +868,10 @@ impl Inner {
results.push(reference.to_location(&line_index));
}
+ self.performance.measure(mark);
Ok(Some(results))
} else {
+ self.performance.measure(mark);
Ok(None)
}
}
@@ -842,6 +883,7 @@ impl Inner {
if !self.enabled() {
return Ok(None);
}
+ let mark = self.performance.mark("goto_definition");
let specifier = utils::normalize_url(
params.text_document_position_params.text_document.uri,
);
@@ -865,12 +907,13 @@ impl Inner {
serde_json::from_value(res).unwrap();
if let Some(definition) = maybe_definition {
- Ok(
- definition
- .to_definition(&line_index, |s| self.get_line_index(s))
- .await,
- )
+ let results = definition
+ .to_definition(&line_index, |s| self.get_line_index(s))
+ .await;
+ self.performance.measure(mark);
+ Ok(results)
} else {
+ self.performance.measure(mark);
Ok(None)
}
}
@@ -882,6 +925,7 @@ impl Inner {
if !self.enabled() {
return Ok(None);
}
+ let mark = self.performance.mark("completion");
let specifier =
utils::normalize_url(params.text_document_position.text_document.uri);
// TODO(lucacasonato): handle error correctly
@@ -910,8 +954,11 @@ impl Inner {
serde_json::from_value(res).unwrap();
if let Some(completions) = maybe_completion_info {
- Ok(Some(completions.into_completion_response(&line_index)))
+ let results = completions.into_completion_response(&line_index);
+ self.performance.measure(mark);
+ Ok(Some(results))
} else {
+ self.performance.measure(mark);
Ok(None)
}
}
@@ -923,6 +970,7 @@ impl Inner {
if !self.enabled() {
return Ok(None);
}
+ let mark = self.performance.mark("goto_implementation");
let specifier = utils::normalize_url(
params.text_document_position_params.text_document.uri,
);
@@ -971,8 +1019,10 @@ impl Inner {
results.push(link);
}
}
+ self.performance.measure(mark);
Ok(Some(GotoDefinitionResponse::Link(results)))
} else {
+ self.performance.measure(mark);
Ok(None)
}
}
@@ -984,6 +1034,7 @@ impl Inner {
if !self.enabled() {
return Ok(None);
}
+ let mark = self.performance.mark("goto_implementation");
let specifier =
utils::normalize_url(params.text_document_position.text_document.uri);
@@ -1039,8 +1090,10 @@ impl Inner {
error!("Failed to get workspace edits: {:#?}", err);
LspError::internal_error()
})?;
+ self.performance.measure(mark);
Ok(Some(workspace_edits))
} else {
+ self.performance.measure(mark);
Ok(None)
}
}
@@ -1061,6 +1114,7 @@ impl Inner {
Some(Err(err)) => Err(LspError::invalid_params(err.to_string())),
None => Err(LspError::invalid_params("Missing parameters")),
},
+ "deno/performance" => self.get_performance(),
"deno/virtualTextDocument" => match params.map(serde_json::from_value) {
Some(Ok(params)) => Ok(Some(
serde_json::to_value(self.virtual_text_document(params).await?)
@@ -1206,6 +1260,7 @@ struct VirtualTextDocumentParams {
impl Inner {
async fn cache(&mut self, params: CacheParams) -> LspResult<bool> {
+ let mark = self.performance.mark("cache");
let specifier = utils::normalize_url(params.text_document.uri);
let maybe_import_map = self.maybe_import_map.clone();
sources::cache(specifier.clone(), maybe_import_map)
@@ -1221,24 +1276,40 @@ impl Inner {
error!("{}", err);
LspError::internal_error()
})?;
+ self.performance.measure(mark);
Ok(true)
}
+ fn get_performance(&self) -> LspResult<Option<Value>> {
+ let averages = self.performance.averages();
+ Ok(Some(json!({ "averages": averages })))
+ }
+
async fn virtual_text_document(
&self,
params: VirtualTextDocumentParams,
) -> LspResult<Option<String>> {
+ let mark = self.performance.mark("virtual_text_document");
let specifier = utils::normalize_url(params.text_document.uri);
let url = specifier.as_url();
let contents = if url.as_str() == "deno:/status.md" {
- Some(format!(
+ let mut contents = String::new();
+
+ contents.push_str(&format!(
r#"# Deno Language Server Status
- Documents in memory: {}
-
- "#,
+"#,
self.documents.len()
- ))
+ ));
+ contents.push_str("\n## Performance\n\n");
+ for average in self.performance.averages() {
+ contents.push_str(&format!(
+ " - {}: {}ms ({})\n",
+ average.name, average.average_duration, average.count
+ ));
+ }
+ Some(contents)
} else {
match url.scheme() {
"asset" => {
@@ -1273,6 +1344,7 @@ impl Inner {
}
}
};
+ self.performance.measure(mark);
Ok(contents)
}
}
@@ -1280,6 +1352,7 @@ impl Inner {
#[cfg(test)]
mod tests {
use super::*;
+ use crate::lsp::performance::PerformanceAverage;
use lspower::jsonrpc;
use lspower::ExitedError;
use lspower::LspService;
@@ -1288,19 +1361,25 @@ mod tests {
use std::time::Instant;
use tower_test::mock::Spawn;
- enum LspResponse {
+ enum LspResponse<V>
+ where
+ V: FnOnce(Value),
+ {
None,
RequestAny,
Request(u64, Value),
+ RequestAssert(V),
}
+ type LspTestHarnessRequest = (&'static str, LspResponse<fn(Value)>);
+
struct LspTestHarness {
- requests: Vec<(&'static str, LspResponse)>,
+ requests: Vec<LspTestHarnessRequest>,
service: Spawn<LspService>,
}
impl LspTestHarness {
- pub fn new(requests: Vec<(&'static str, LspResponse)>) -> Self {
+ pub fn new(requests: Vec<LspTestHarnessRequest>) -> Self {
let (service, _) = LspService::new(LanguageServer::new);
let service = Spawn::new(service);
Self { requests, service }
@@ -1330,6 +1409,10 @@ mod tests {
),
_ => panic!("unexpected result: {:?}", result),
},
+ LspResponse::RequestAssert(assert) => match result {
+ Some(jsonrpc::Outgoing::Response(resp)) => assert(json!(resp)),
+ _ => panic!("unexpected result: {:?}", result),
+ },
},
Err(err) => panic!("Error result: {}", err),
}
@@ -1609,6 +1692,11 @@ mod tests {
("initialized_notification.json", LspResponse::None),
("did_open_notification_large.json", LspResponse::None),
("did_change_notification_large.json", LspResponse::None),
+ ("did_change_notification_large_02.json", LspResponse::None),
+ ("did_change_notification_large_03.json", LspResponse::None),
+ ("hover_request_large_01.json", LspResponse::RequestAny),
+ ("hover_request_large_02.json", LspResponse::RequestAny),
+ ("hover_request_large_03.json", LspResponse::RequestAny),
(
"shutdown_request.json",
LspResponse::Request(3, json!(null)),
@@ -1676,4 +1764,61 @@ mod tests {
]);
harness.run().await;
}
+
+ #[derive(Deserialize)]
+ struct PerformanceAverages {
+ averages: Vec<PerformanceAverage>,
+ }
+ #[derive(Deserialize)]
+ struct PerformanceResponse {
+ result: PerformanceAverages,
+ }
+
+ #[tokio::test]
+ async fn test_deno_performance_request() {
+ let mut harness = LspTestHarness::new(vec![
+ ("initialize_request.json", LspResponse::RequestAny),
+ ("initialized_notification.json", LspResponse::None),
+ ("did_open_notification.json", LspResponse::None),
+ (
+ "hover_request.json",
+ LspResponse::Request(
+ 2,
+ json!({
+ "contents": [
+ {
+ "language": "typescript",
+ "value": "const Deno.args: string[]"
+ },
+ "Returns the script arguments to the program. If for example we run a\nprogram:\n\ndeno run --allow-read https://deno.land/std/examples/cat.ts /etc/passwd\n\nThen `Deno.args` will contain:\n\n[ \"/etc/passwd\" ]"
+ ],
+ "range": {
+ "start": {
+ "line": 0,
+ "character": 17
+ },
+ "end": {
+ "line": 0,
+ "character": 21
+ }
+ }
+ }),
+ ),
+ ),
+ (
+ "performance_request.json",
+ LspResponse::RequestAssert(|value| {
+ let resp: PerformanceResponse =
+ serde_json::from_value(value).unwrap();
+ assert_eq!(resp.result.averages.len(), 9);
+ }),
+ ),
+ (
+ "shutdown_request.json",
+ LspResponse::Request(3, json!(null)),
+ ),
+ ("exit_notification.json", LspResponse::None),
+ ]);
+ harness.run().await;
+ }
}