Cap number of saved hang traces at 3 (#47674)

Cole Miller created

Prevents unbounded grown of the `hang_traces` directory.

Release Notes:

- Fixed excessive disk space usage from the `hang_traces` directory.

Change summary

crates/zed/src/reliability.rs | 44 +++++++++++++++++++++++++++++++++++++
1 file changed, 44 insertions(+)

Detailed changes

crates/zed/src/reliability.rs 🔗

@@ -16,6 +16,8 @@ use util::ResultExt;
 
 use crate::STARTUP_TIME;
 
+const MAX_HANG_TRACES: usize = 3;
+
 pub fn init(client: Arc<Client>, cx: &mut App) {
     monitor_hangs(cx);
 
@@ -82,6 +84,8 @@ fn monitor_hangs(cx: &App) {
         .spawn({
             let background_executor = background_executor.clone();
             async move {
+                cleanup_old_hang_traces();
+
                 let mut hang_time = None;
 
                 let mut hanging = false;
@@ -115,6 +119,27 @@ fn monitor_hangs(cx: &App) {
         .detach();
 }
 
+fn cleanup_old_hang_traces() {
+    if let Ok(entries) = std::fs::read_dir(paths::hang_traces_dir()) {
+        let mut files: Vec<_> = entries
+            .filter_map(|entry| entry.ok())
+            .filter(|entry| {
+                entry
+                    .path()
+                    .extension()
+                    .is_some_and(|ext| ext == "miniprof")
+            })
+            .collect();
+
+        if files.len() > MAX_HANG_TRACES {
+            files.sort_by_key(|entry| entry.file_name());
+            for entry in files.iter().take(files.len() - MAX_HANG_TRACES) {
+                std::fs::remove_file(entry.path()).log_err();
+            }
+        }
+    }
+}
+
 fn save_hang_trace(
     main_thread_id: ThreadId,
     background_executor: &gpui::BackgroundExecutor,
@@ -144,6 +169,25 @@ fn save_hang_trace(
         return;
     };
 
+    if let Ok(entries) = std::fs::read_dir(paths::hang_traces_dir()) {
+        let mut files: Vec<_> = entries
+            .filter_map(|entry| entry.ok())
+            .filter(|entry| {
+                entry
+                    .path()
+                    .extension()
+                    .is_some_and(|ext| ext == "miniprof")
+            })
+            .collect();
+
+        if files.len() >= MAX_HANG_TRACES {
+            files.sort_by_key(|entry| entry.file_name());
+            for entry in files.iter().take(files.len() - (MAX_HANG_TRACES - 1)) {
+                std::fs::remove_file(entry.path()).log_err();
+            }
+        }
+    }
+
     std::fs::write(&trace_path, timings)
         .context("hang trace file writing")
         .log_err();