Cargo.lock 🔗
@@ -9207,6 +9207,7 @@ dependencies = [
"semver",
"serde_json",
"settings",
+ "sysinfo 0.37.2",
"theme",
"tree-sitter",
"ui",
John Tur created
Closes https://github.com/zed-industries/zed/issues/32712
<img width="575" height="131" alt="image"
src="https://github.com/user-attachments/assets/aaad583c-277c-4a84-b2ce-a18b8b38bc0e"
/>
Release Notes:
- The language servers menu now shows the memory used by each language
server.
Cargo.lock | 1
crates/language_tools/Cargo.toml | 1
crates/language_tools/src/lsp_button.rs | 158 ++++++++++++++++++++++--
crates/language_tools/src/lsp_log_view.rs | 1
crates/lsp/src/lsp.rs | 5
crates/project/src/lsp_store.rs | 4
6 files changed, 153 insertions(+), 17 deletions(-)
@@ -9207,6 +9207,7 @@ dependencies = [
"semver",
"serde_json",
"settings",
+ "sysinfo 0.37.2",
"theme",
"tree-sitter",
"ui",
@@ -30,6 +30,7 @@ serde_json.workspace = true
settings.workspace = true
theme.workspace = true
tree-sitter.workspace = true
+sysinfo.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true
@@ -1,10 +1,13 @@
use std::{
+ cell::RefCell,
collections::{BTreeMap, HashMap},
path::{Path, PathBuf},
rc::Rc,
- time::Duration,
+ time::{Duration, Instant},
};
+use sysinfo::{Pid, ProcessRefreshKind, RefreshKind, System};
+
use client::proto;
use collections::HashSet;
use editor::{Editor, EditorEvent};
@@ -41,13 +44,93 @@ pub struct LspButton {
_subscriptions: Vec<Subscription>,
}
-#[derive(Debug)]
struct LanguageServerState {
items: Vec<LspMenuItem>,
workspace: WeakEntity<Workspace>,
lsp_store: WeakEntity<LspStore>,
active_editor: Option<ActiveEditor>,
language_servers: LanguageServers,
+ process_memory_cache: Rc<RefCell<ProcessMemoryCache>>,
+}
+
+impl std::fmt::Debug for LanguageServerState {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("LanguageServerState")
+ .field("items", &self.items)
+ .field("workspace", &self.workspace)
+ .field("lsp_store", &self.lsp_store)
+ .field("active_editor", &self.active_editor)
+ .field("language_servers", &self.language_servers)
+ .finish_non_exhaustive()
+ }
+}
+
+const PROCESS_MEMORY_CACHE_DURATION: Duration = Duration::from_secs(5);
+
+struct ProcessMemoryCache {
+ system: System,
+ memory_usage: HashMap<u32, u64>,
+ last_refresh: Option<Instant>,
+}
+
+impl ProcessMemoryCache {
+ fn new() -> Self {
+ Self {
+ system: System::new(),
+ memory_usage: HashMap::new(),
+ last_refresh: None,
+ }
+ }
+
+ fn get_memory_usage(&mut self, process_id: u32) -> u64 {
+ let cache_expired = self
+ .last_refresh
+ .map(|last| last.elapsed() >= PROCESS_MEMORY_CACHE_DURATION)
+ .unwrap_or(true);
+
+ if cache_expired {
+ let refresh_kind =
+ RefreshKind::nothing().with_processes(ProcessRefreshKind::nothing().with_memory());
+ self.system.refresh_specifics(refresh_kind);
+ self.memory_usage.clear();
+ self.last_refresh = Some(Instant::now());
+ }
+
+ if let Some(&memory) = self.memory_usage.get(&process_id) {
+ return memory;
+ }
+
+ let root_pid = Pid::from_u32(process_id);
+
+ let parent_map: HashMap<Pid, Pid> = self
+ .system
+ .processes()
+ .iter()
+ .filter_map(|(&pid, process)| Some((pid, process.parent()?)))
+ .collect();
+
+ let total_memory = self
+ .system
+ .processes()
+ .iter()
+ .filter(|(pid, _)| self.is_descendant_of(**pid, root_pid, &parent_map))
+ .map(|(_, process)| process.memory())
+ .sum();
+
+ self.memory_usage.insert(process_id, total_memory);
+ total_memory
+ }
+
+ fn is_descendant_of(&self, pid: Pid, root_pid: Pid, parent_map: &HashMap<Pid, Pid>) -> bool {
+ let mut current = pid;
+ while current != root_pid {
+ match parent_map.get(¤t) {
+ Some(&parent) => current = parent,
+ None => return false,
+ }
+ }
+ true
+ }
}
struct ActiveEditor {
@@ -143,6 +226,7 @@ impl LanguageServerState {
(
status.server_version.clone(),
status.binary.as_ref().map(|b| b.path.clone()),
+ status.process_id,
),
)
})
@@ -150,6 +234,8 @@ impl LanguageServerState {
})
.unwrap_or_default();
+ let process_memory_cache = self.process_memory_cache.clone();
+
let mut first_button_encountered = false;
for item in &self.items {
if let LspMenuItem::ToggleServersButton { restart } = item {
@@ -274,16 +360,17 @@ impl LanguageServerState {
.or_else(|| server_info.binary_status.as_ref()?.message.as_ref())
.cloned();
- let (server_version, binary_path) = server_metadata
+ let (server_version, binary_path, process_id) = server_metadata
.get(&server_info.id)
- .map(|(version, path)| {
+ .map(|(version, path, process_id)| {
(
version.clone(),
path.as_ref()
.map(|p| SharedString::from(p.compact().to_string_lossy().to_string())),
+ *process_id,
)
})
- .unwrap_or((None, None));
+ .unwrap_or((None, None, None));
let truncated_message = message.as_ref().and_then(|message| {
message
@@ -293,17 +380,6 @@ impl LanguageServerState {
.next()
});
- let metadata_label = match (&server_version, &truncated_message) {
- (None, None) => None,
- (Some(version), None) => Some(SharedString::from(format!("v{}", version.as_ref()))),
- (None, Some(message)) => Some(message.clone()),
- (Some(version), Some(message)) => Some(SharedString::from(format!(
- "v{}\n\n{}",
- version.as_ref(),
- message.as_ref()
- ))),
- };
-
let submenu_server_name = server_info.name.clone();
let submenu_server_info = server_info.clone();
@@ -319,6 +395,7 @@ impl LanguageServerState {
let lsp_store = self.lsp_store.clone();
let state = cx.entity().downgrade();
let can_stop = submenu_server_info.can_stop();
+ let process_memory_cache = process_memory_cache.clone();
move |menu, _window, _cx| {
let mut submenu = menu;
@@ -509,9 +586,55 @@ impl LanguageServerState {
}
submenu = submenu.separator().custom_row({
- let metadata_label = metadata_label.clone();
let binary_path = binary_path.clone();
+ let server_version = server_version.clone();
+ let truncated_message = truncated_message.clone();
+ let process_memory_cache = process_memory_cache.clone();
move |_, _| {
+ let memory_usage = process_id.map(|pid| {
+ process_memory_cache.borrow_mut().get_memory_usage(pid)
+ });
+
+ let memory_label = memory_usage.map(|bytes| {
+ if bytes >= 1024 * 1024 * 1024 {
+ format!(
+ "{:.1} GB",
+ bytes as f64 / (1024.0 * 1024.0 * 1024.0)
+ )
+ } else {
+ format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0))
+ }
+ });
+
+ let metadata_label =
+ match (&server_version, &memory_label, &truncated_message) {
+ (None, None, None) => None,
+ (Some(version), None, None) => {
+ Some(format!("v{}", version.as_ref()))
+ }
+ (None, Some(memory), None) => Some(memory.clone()),
+ (Some(version), Some(memory), None) => {
+ Some(format!("v{} • {}", version.as_ref(), memory))
+ }
+ (None, None, Some(message)) => Some(message.to_string()),
+ (Some(version), None, Some(message)) => Some(format!(
+ "v{}\n\n{}",
+ version.as_ref(),
+ message.as_ref()
+ )),
+ (None, Some(memory), Some(message)) => {
+ Some(format!("{}\n\n{}", memory, message.as_ref()))
+ }
+ (Some(version), Some(memory), Some(message)) => {
+ Some(format!(
+ "v{} • {}\n\n{}",
+ version.as_ref(),
+ memory,
+ message.as_ref()
+ ))
+ }
+ };
+
h_flex()
.id("metadata-container")
.ml_neg_1()
@@ -744,6 +867,7 @@ impl LspButton {
lsp_store: lsp_store.downgrade(),
active_editor: None,
language_servers,
+ process_memory_cache: Rc::new(RefCell::new(ProcessMemoryCache::new())),
});
let mut lsp_button = Self {
@@ -1349,6 +1349,7 @@ impl ServerInfo {
binary: Some(server.binary().clone()),
configuration: Some(server.configuration().clone()),
workspace_folders: server.workspace_folders(),
+ process_id: server.process_id(),
},
}
}
@@ -1208,6 +1208,11 @@ impl LanguageServer {
self.server_id
}
+ /// Get the process ID of the running language server, if available.
+ pub fn process_id(&self) -> Option<u32> {
+ self.server.lock().as_ref().map(|child| child.id())
+ }
+
/// Language server's binary information.
pub fn binary(&self) -> &LanguageServerBinary {
&self.binary
@@ -3908,6 +3908,7 @@ pub struct LanguageServerStatus {
pub binary: Option<LanguageServerBinary>,
pub configuration: Option<Value>,
pub workspace_folders: BTreeSet<Uri>,
+ pub process_id: Option<u32>,
}
#[derive(Clone, Debug)]
@@ -8487,6 +8488,7 @@ impl LspStore {
binary: None,
configuration: None,
workspace_folders: BTreeSet::new(),
+ process_id: None,
},
)
})
@@ -9554,6 +9556,7 @@ impl LspStore {
binary: None,
configuration: None,
workspace_folders: BTreeSet::new(),
+ process_id: None,
},
);
cx.emit(LspStoreEvent::LanguageServerAdded(
@@ -11610,6 +11613,7 @@ impl LspStore {
binary: Some(language_server.binary().clone()),
configuration: Some(language_server.configuration().clone()),
workspace_folders: language_server.workspace_folders(),
+ process_id: language_server.process_id(),
},
);