1use std::{collections::BTreeMap, path::PathBuf, sync::Arc};
2
3use criterion::{BatchSize, BenchmarkId, Criterion, criterion_group, criterion_main};
4use extension::{
5 ExtensionCapability, ExtensionHostProxy, ExtensionLibraryKind, ExtensionManifest,
6 LanguageServerManifestEntry, LibManifestEntry, SchemaVersion,
7 extension_builder::{CompileExtensionOptions, ExtensionBuilder},
8};
9use extension_host::wasm_host::WasmHost;
10use fs::RealFs;
11use gpui::{SemanticVersion, TestAppContext, TestDispatcher};
12use http_client::{FakeHttpClient, Response};
13use node_runtime::NodeRuntime;
14use rand::{SeedableRng, rngs::StdRng};
15use reqwest_client::ReqwestClient;
16use serde_json::json;
17use settings::SettingsStore;
18use util::test::TempTree;
19
20fn extension_benchmarks(c: &mut Criterion) {
21 let cx = init();
22 cx.update(gpui_tokio::init);
23
24 let mut group = c.benchmark_group("load");
25
26 let mut manifest = manifest();
27 let wasm_bytes = wasm_bytes(&cx, &mut manifest);
28 let manifest = Arc::new(manifest);
29 let extensions_dir = TempTree::new(json!({
30 "installed": {},
31 "work": {}
32 }));
33 let wasm_host = wasm_host(&cx, &extensions_dir);
34
35 group.bench_function(BenchmarkId::from_parameter(1), |b| {
36 b.iter_batched(
37 || wasm_bytes.clone(),
38 |wasm_bytes| {
39 let _extension = cx
40 .executor()
41 .block(wasm_host.load_extension(wasm_bytes, &manifest, &cx.to_async()))
42 .unwrap();
43 },
44 BatchSize::SmallInput,
45 );
46 });
47}
48
49fn init() -> TestAppContext {
50 const SEED: u64 = 9999;
51 let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(SEED));
52 let cx = TestAppContext::build(dispatcher, None);
53 cx.executor().allow_parking();
54 cx.update(|cx| {
55 let store = SettingsStore::test(cx);
56 cx.set_global(store);
57 release_channel::init(SemanticVersion::default(), cx);
58 });
59
60 cx
61}
62
63fn wasm_bytes(cx: &TestAppContext, manifest: &mut ExtensionManifest) -> Vec<u8> {
64 let extension_builder = extension_builder();
65 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
66 .parent()
67 .unwrap()
68 .parent()
69 .unwrap()
70 .join("extensions/test-extension");
71 cx.executor()
72 .block(extension_builder.compile_extension(
73 &path,
74 manifest,
75 CompileExtensionOptions { release: true },
76 ))
77 .unwrap();
78 std::fs::read(path.join("extension.wasm")).unwrap()
79}
80
81fn extension_builder() -> ExtensionBuilder {
82 let user_agent = format!(
83 "Zed Extension CLI/{} ({}; {})",
84 env!("CARGO_PKG_VERSION"),
85 std::env::consts::OS,
86 std::env::consts::ARCH
87 );
88 let http_client = Arc::new(ReqwestClient::user_agent(&user_agent).unwrap());
89 // Local dir so that we don't have to download it on every run
90 let build_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("benches/.build");
91 ExtensionBuilder::new(http_client, build_dir)
92}
93
94fn wasm_host(cx: &TestAppContext, extensions_dir: &TempTree) -> Arc<WasmHost> {
95 let http_client = FakeHttpClient::create(async |_| {
96 Ok(Response::builder().status(404).body("not found".into())?)
97 });
98 let extensions_dir = extensions_dir.path().canonicalize().unwrap();
99 let work_dir = extensions_dir.join("work");
100 let fs = Arc::new(RealFs::new(None, cx.executor()));
101
102 cx.update(|cx| {
103 WasmHost::new(
104 fs,
105 http_client,
106 NodeRuntime::unavailable(),
107 Arc::new(ExtensionHostProxy::new()),
108 work_dir,
109 cx,
110 )
111 })
112}
113
114fn manifest() -> ExtensionManifest {
115 ExtensionManifest {
116 id: "test-extension".into(),
117 name: "Test Extension".into(),
118 version: "0.1.0".into(),
119 schema_version: SchemaVersion(1),
120 description: Some("An extension for use in tests.".into()),
121 authors: Vec::new(),
122 repository: None,
123 themes: Default::default(),
124 icon_themes: Vec::new(),
125 lib: LibManifestEntry {
126 kind: Some(ExtensionLibraryKind::Rust),
127 version: Some(SemanticVersion::new(0, 1, 0)),
128 },
129 languages: Vec::new(),
130 grammars: BTreeMap::default(),
131 language_servers: [("gleam".into(), LanguageServerManifestEntry::default())]
132 .into_iter()
133 .collect(),
134 context_servers: BTreeMap::default(),
135 slash_commands: BTreeMap::default(),
136 snippets: None,
137 capabilities: vec![ExtensionCapability::ProcessExec(
138 extension::ProcessExecCapability {
139 command: "echo".into(),
140 args: vec!["hello!".into()],
141 },
142 )],
143 debug_adapters: Default::default(),
144 debug_locators: Default::default(),
145 }
146}
147
148criterion_group!(benches, extension_benchmarks);
149criterion_main!(benches);