mirror of
https://github.com/denoland/deno.git
synced 2024-11-27 16:10:57 -05:00
446 lines
13 KiB
Rust
446 lines
13 KiB
Rust
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
|
|
use deno_core::serde::Deserialize;
|
|
use deno_core::serde_json;
|
|
use deno_core::serde_json::json;
|
|
use deno_core::serde_json::Value;
|
|
use lsp_types::Uri;
|
|
use std::collections::HashMap;
|
|
use std::path::Path;
|
|
use std::str::FromStr;
|
|
use std::time::Duration;
|
|
use test_util::lsp::LspClientBuilder;
|
|
use test_util::PathRef;
|
|
use tower_lsp::lsp_types as lsp;
|
|
|
|
static FIXTURE_CODE_LENS_TS: &str = include_str!("testdata/code_lens.ts");
|
|
static FIXTURE_DB_TS: &str = include_str!("testdata/db.ts");
|
|
static FIXTURE_DB_MESSAGES: &[u8] = include_bytes!("testdata/db_messages.json");
|
|
static FIXTURE_DECO_APPS: &[u8] =
|
|
include_bytes!("testdata/deco_apps_requests.json");
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
enum FixtureType {
|
|
#[serde(rename = "action")]
|
|
Action,
|
|
#[serde(rename = "change")]
|
|
Change,
|
|
#[serde(rename = "completion")]
|
|
Completion,
|
|
#[serde(rename = "highlight")]
|
|
Highlight,
|
|
#[serde(rename = "hover")]
|
|
Hover,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct FixtureMessage {
|
|
#[serde(rename = "type")]
|
|
fixture_type: FixtureType,
|
|
params: Value,
|
|
}
|
|
|
|
/// replaces the root directory in the URIs of the requests
|
|
/// with the given root path
|
|
fn patch_uris<'a>(
|
|
reqs: impl IntoIterator<Item = &'a mut tower_lsp::jsonrpc::Request>,
|
|
root: &PathRef,
|
|
) {
|
|
for req in reqs {
|
|
let mut params = req.params().unwrap().clone();
|
|
let new_req = if let Some(doc) = params.get_mut("textDocument") {
|
|
if let Some(uri_val) = doc.get_mut("uri") {
|
|
let uri = uri_val.as_str().unwrap();
|
|
*uri_val =
|
|
Value::from(uri.replace(
|
|
"file:///",
|
|
&format!("file://{}/", root.to_string_lossy()),
|
|
));
|
|
}
|
|
let builder = tower_lsp::jsonrpc::Request::build(req.method().to_owned());
|
|
let builder = if let Some(id) = req.id() {
|
|
builder.id(id.clone())
|
|
} else {
|
|
builder
|
|
};
|
|
|
|
Some(builder.params(params).finish())
|
|
} else {
|
|
None
|
|
};
|
|
|
|
if let Some(new_req) = new_req {
|
|
*req = new_req;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn bench_deco_apps_edits(deno_exe: &Path) -> Duration {
|
|
let mut requests: Vec<tower_lsp::jsonrpc::Request> =
|
|
serde_json::from_slice(FIXTURE_DECO_APPS).unwrap();
|
|
let apps =
|
|
test_util::root_path().join("cli/bench/testdata/lsp_benchdata/apps");
|
|
|
|
// it's a bit wasteful to do this for every run, but it's the easiest with the way things
|
|
// are currently structured
|
|
patch_uris(&mut requests, &apps);
|
|
|
|
let mut client = LspClientBuilder::new()
|
|
.use_diagnostic_sync(false)
|
|
.set_root_dir(apps.clone())
|
|
.deno_exe(deno_exe)
|
|
.build();
|
|
client.initialize(|c| {
|
|
c.set_workspace_folders(vec![lsp_types::WorkspaceFolder {
|
|
uri: apps.uri_dir(),
|
|
name: "apps".to_string(),
|
|
}]);
|
|
c.set_deno_enable(true);
|
|
c.set_unstable(true);
|
|
c.set_preload_limit(1000);
|
|
c.set_config(apps.join("deno.json").as_path().to_string_lossy());
|
|
});
|
|
|
|
let start = std::time::Instant::now();
|
|
|
|
let mut reqs = 0;
|
|
for req in requests {
|
|
if req.id().is_none() {
|
|
client.write_notification(req.method(), req.params());
|
|
} else {
|
|
reqs += 1;
|
|
client.write_jsonrpc(req.method(), req.params());
|
|
}
|
|
}
|
|
for _ in 0..reqs {
|
|
let _ = client.read_latest_response();
|
|
}
|
|
|
|
let end = start.elapsed();
|
|
|
|
// part of the motivation of including this benchmark is to see how we perform
|
|
// with a fairly large number of documents in memory.
|
|
// make sure that's the case
|
|
let res = client.write_request(
|
|
"deno/virtualTextDocument",
|
|
json!({
|
|
"textDocument": {
|
|
"uri": "deno:/status.md"
|
|
}
|
|
}),
|
|
);
|
|
let re = lazy_regex::regex!(r"Documents in memory: (\d+)");
|
|
let res = res.as_str().unwrap().to_string();
|
|
assert!(res.starts_with("# Deno Language Server Status"));
|
|
let captures = re.captures(&res).unwrap();
|
|
let count = captures.get(1).unwrap().as_str().parse::<usize>().unwrap();
|
|
assert!(count > 1000, "count: {}", count);
|
|
|
|
client.shutdown();
|
|
|
|
end
|
|
}
|
|
|
|
/// A benchmark that opens a 8000+ line TypeScript document, adds a function to
|
|
/// the end of the document and does a level of hovering and gets quick fix
|
|
/// code actions.
|
|
fn bench_big_file_edits(deno_exe: &Path) -> Duration {
|
|
let mut client = LspClientBuilder::new()
|
|
.use_diagnostic_sync(false)
|
|
.deno_exe(deno_exe)
|
|
.build();
|
|
client.initialize_default();
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "deno/didRefreshDenoConfigurationTree");
|
|
client.change_configuration(json!({ "deno": { "enable": true } }));
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "deno/didRefreshDenoConfigurationTree");
|
|
|
|
client.write_notification(
|
|
"textDocument/didOpen",
|
|
json!({
|
|
"textDocument": {
|
|
"uri": "file:///testdata/db.ts",
|
|
"languageId": "typescript",
|
|
"version": 1,
|
|
"text": FIXTURE_DB_TS
|
|
}
|
|
}),
|
|
);
|
|
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "textDocument/publishDiagnostics");
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "textDocument/publishDiagnostics");
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "textDocument/publishDiagnostics");
|
|
|
|
let messages: Vec<FixtureMessage> =
|
|
serde_json::from_slice(FIXTURE_DB_MESSAGES).unwrap();
|
|
|
|
for msg in messages {
|
|
match msg.fixture_type {
|
|
FixtureType::Action => {
|
|
client.write_request("textDocument/codeAction", msg.params);
|
|
}
|
|
FixtureType::Change => {
|
|
client.write_notification("textDocument/didChange", msg.params);
|
|
}
|
|
FixtureType::Completion => {
|
|
client.write_request("textDocument/completion", msg.params);
|
|
}
|
|
FixtureType::Highlight => {
|
|
client.write_request("textDocument/documentHighlight", msg.params);
|
|
}
|
|
FixtureType::Hover => {
|
|
client.write_request("textDocument/hover", msg.params);
|
|
}
|
|
}
|
|
}
|
|
|
|
client.write_request("shutdown", json!(null));
|
|
client.write_notification("exit", json!(null));
|
|
|
|
client.duration()
|
|
}
|
|
|
|
fn bench_code_lens(deno_exe: &Path) -> Duration {
|
|
let mut client = LspClientBuilder::new()
|
|
.use_diagnostic_sync(false)
|
|
.deno_exe(deno_exe)
|
|
.build();
|
|
client.initialize_default();
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "deno/didRefreshDenoConfigurationTree");
|
|
client.change_configuration(json!({ "deno": {
|
|
"enable": true,
|
|
"codeLens": {
|
|
"implementations": true,
|
|
"references": true,
|
|
"test": true,
|
|
},
|
|
} }));
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "deno/didRefreshDenoConfigurationTree");
|
|
|
|
client.write_notification(
|
|
"textDocument/didOpen",
|
|
json!({
|
|
"textDocument": {
|
|
"uri": "file:///testdata/code_lens.ts",
|
|
"languageId": "typescript",
|
|
"version": 1,
|
|
"text": FIXTURE_CODE_LENS_TS
|
|
}
|
|
}),
|
|
);
|
|
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "textDocument/publishDiagnostics");
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "textDocument/publishDiagnostics");
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "textDocument/publishDiagnostics");
|
|
|
|
let res = client.write_request_with_res_as::<Vec<lsp::CodeLens>>(
|
|
"textDocument/codeLens",
|
|
json!({
|
|
"textDocument": {
|
|
"uri": "file:///testdata/code_lens.ts"
|
|
}
|
|
}),
|
|
);
|
|
assert!(!res.is_empty());
|
|
|
|
for code_lens in res {
|
|
client.write_request("codeLens/resolve", code_lens);
|
|
}
|
|
|
|
client.duration()
|
|
}
|
|
|
|
fn bench_find_replace(deno_exe: &Path) -> Duration {
|
|
let mut client = LspClientBuilder::new()
|
|
.use_diagnostic_sync(false)
|
|
.deno_exe(deno_exe)
|
|
.build();
|
|
client.initialize_default();
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "deno/didRefreshDenoConfigurationTree");
|
|
client.change_configuration(json!({ "deno": { "enable": true } }));
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "deno/didRefreshDenoConfigurationTree");
|
|
|
|
for i in 0..10 {
|
|
client.write_notification(
|
|
"textDocument/didOpen",
|
|
json!({
|
|
"textDocument": {
|
|
"uri": format!("file:///a/file_{i}.ts"),
|
|
"languageId": "typescript",
|
|
"version": 1,
|
|
"text": "console.log(\"000\");\n"
|
|
}
|
|
}),
|
|
);
|
|
}
|
|
|
|
for _ in 0..3 {
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "textDocument/publishDiagnostics");
|
|
}
|
|
|
|
for i in 0..10 {
|
|
let file_name = format!("file:///a/file_{i}.ts");
|
|
client.write_notification(
|
|
"textDocument/didChange",
|
|
lsp::DidChangeTextDocumentParams {
|
|
text_document: lsp::VersionedTextDocumentIdentifier {
|
|
uri: Uri::from_str(&file_name).unwrap(),
|
|
version: 2,
|
|
},
|
|
content_changes: vec![lsp::TextDocumentContentChangeEvent {
|
|
range: Some(lsp::Range {
|
|
start: lsp::Position {
|
|
line: 0,
|
|
character: 13,
|
|
},
|
|
end: lsp::Position {
|
|
line: 0,
|
|
character: 16,
|
|
},
|
|
}),
|
|
range_length: None,
|
|
text: "111".to_string(),
|
|
}],
|
|
},
|
|
);
|
|
}
|
|
|
|
for i in 0..10 {
|
|
let file_name = format!("file:///a/file_{i}.ts");
|
|
client.write_request(
|
|
"textDocument/formatting",
|
|
lsp::DocumentFormattingParams {
|
|
text_document: lsp::TextDocumentIdentifier {
|
|
uri: Uri::from_str(&file_name).unwrap(),
|
|
},
|
|
options: lsp::FormattingOptions {
|
|
tab_size: 2,
|
|
insert_spaces: true,
|
|
..Default::default()
|
|
},
|
|
work_done_progress_params: Default::default(),
|
|
},
|
|
);
|
|
}
|
|
|
|
for _ in 0..3 {
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "textDocument/publishDiagnostics");
|
|
}
|
|
|
|
client.write_request("shutdown", json!(null));
|
|
client.write_notification("exit", json!(null));
|
|
|
|
client.duration()
|
|
}
|
|
|
|
/// A test that starts up the LSP, opens a single line document, and exits.
|
|
fn bench_startup_shutdown(deno_exe: &Path) -> Duration {
|
|
let mut client = LspClientBuilder::new()
|
|
.use_diagnostic_sync(false)
|
|
.deno_exe(deno_exe)
|
|
.build();
|
|
client.initialize_default();
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "deno/didRefreshDenoConfigurationTree");
|
|
client.change_configuration(json!({ "deno": { "enable": true } }));
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "deno/didRefreshDenoConfigurationTree");
|
|
|
|
client.write_notification(
|
|
"textDocument/didOpen",
|
|
json!({
|
|
"textDocument": {
|
|
"uri": "file:///a/file.ts",
|
|
"languageId": "typescript",
|
|
"version": 1,
|
|
"text": "console.log(Deno.args);\n"
|
|
}
|
|
}),
|
|
);
|
|
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "textDocument/publishDiagnostics");
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "textDocument/publishDiagnostics");
|
|
let (method, _): (String, Option<Value>) = client.read_notification();
|
|
assert_eq!(method, "textDocument/publishDiagnostics");
|
|
|
|
client.write_request("shutdown", json!(null));
|
|
|
|
client.write_notification("exit", json!(null));
|
|
|
|
client.duration()
|
|
}
|
|
|
|
/// Generate benchmarks for the LSP server.
|
|
pub fn benchmarks(deno_exe: &Path) -> HashMap<String, i64> {
|
|
println!("-> Start benchmarking lsp");
|
|
let mut exec_times = HashMap::new();
|
|
|
|
println!(" - Simple Startup/Shutdown ");
|
|
let mut times = Vec::new();
|
|
for _ in 0..10 {
|
|
times.push(bench_startup_shutdown(deno_exe));
|
|
}
|
|
let mean =
|
|
(times.iter().sum::<Duration>() / times.len() as u32).as_millis() as i64;
|
|
println!(" ({} runs, mean: {}ms)", times.len(), mean);
|
|
exec_times.insert("startup_shutdown".to_string(), mean);
|
|
|
|
println!(" - Big Document/Several Edits ");
|
|
let mut times = Vec::new();
|
|
for _ in 0..5 {
|
|
times.push(bench_big_file_edits(deno_exe));
|
|
}
|
|
let mean =
|
|
(times.iter().sum::<Duration>() / times.len() as u32).as_millis() as i64;
|
|
println!(" ({} runs, mean: {}ms)", times.len(), mean);
|
|
exec_times.insert("big_file_edits".to_string(), mean);
|
|
|
|
println!(" - Find/Replace");
|
|
let mut times = Vec::new();
|
|
for _ in 0..10 {
|
|
times.push(bench_find_replace(deno_exe));
|
|
}
|
|
let mean =
|
|
(times.iter().sum::<Duration>() / times.len() as u32).as_millis() as i64;
|
|
println!(" ({} runs, mean: {}ms)", times.len(), mean);
|
|
exec_times.insert("find_replace".to_string(), mean);
|
|
|
|
println!(" - Code Lens");
|
|
let mut times = Vec::new();
|
|
for _ in 0..10 {
|
|
times.push(bench_code_lens(deno_exe));
|
|
}
|
|
let mean =
|
|
(times.iter().sum::<Duration>() / times.len() as u32).as_millis() as i64;
|
|
println!(" ({} runs, mean: {}ms)", times.len(), mean);
|
|
exec_times.insert("code_lens".to_string(), mean);
|
|
|
|
println!(" - deco-cx/apps Multiple Edits + Navigation");
|
|
let mut times = Vec::new();
|
|
for _ in 0..5 {
|
|
times.push(bench_deco_apps_edits(deno_exe));
|
|
}
|
|
let mean =
|
|
(times.iter().sum::<Duration>() / times.len() as u32).as_millis() as i64;
|
|
println!(" ({} runs, mean: {}ms)", times.len(), mean);
|
|
exec_times.insert("deco_apps_edits_nav".to_string(), mean);
|
|
|
|
println!("<- End benchmarking lsp");
|
|
|
|
exec_times
|
|
}
|