Add memory and cpu events (#3080)

Joseph T. Lyons created

Release Notes:

- N/A

Change summary

Cargo.lock                     |  5 +-
Cargo.toml                     |  1 
crates/client/Cargo.toml       |  9 +++--
crates/client/src/telemetry.rs | 55 +++++++++++++++++++++++++++++++++++
crates/feedback/Cargo.toml     |  2 
crates/zed/src/main.rs         |  2 
6 files changed, 65 insertions(+), 9 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1415,6 +1415,7 @@ dependencies = [
  "settings",
  "smol",
  "sum_tree",
+ "sysinfo",
  "tempfile",
  "text",
  "thiserror",
@@ -7607,9 +7608,9 @@ dependencies = [
 
 [[package]]
 name = "sysinfo"
-version = "0.27.8"
+version = "0.29.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a902e9050fca0a5d6877550b769abd2bd1ce8c04634b941dbe2809735e1a1e33"
+checksum = "0a18d114d420ada3a891e6bc8e96a2023402203296a47cdd65083377dad18ba5"
 dependencies = [
  "cfg-if 1.0.0",
  "core-foundation-sys 0.8.3",

Cargo.toml 🔗

@@ -111,6 +111,7 @@ serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
 serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] }
 smallvec = { version = "1.6", features = ["union"] }
 smol = { version = "1.2" }
+sysinfo = "0.29.10"
 tempdir = { version = "0.3.7" }
 thiserror = { version = "1.0.29" }
 time = { version = "0.3", features = ["serde", "serde-well-known"] }

crates/client/Cargo.toml 🔗

@@ -33,15 +33,16 @@ parking_lot.workspace = true
 postage.workspace = true
 rand.workspace = true
 schemars.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
 smol.workspace = true
+sysinfo.workspace = true
+tempfile = "3"
 thiserror.workspace = true
 time.workspace = true
 tiny_http = "0.8"
-uuid = { version = "1.1.2", features = ["v4"] }
 url = "2.2"
-serde.workspace = true
-serde_derive.workspace = true
-tempfile = "3"
+uuid = { version = "1.1.2", features = ["v4"] }
 
 [dev-dependencies]
 collections = { path = "../collections", features = ["test-support"] }

crates/client/src/telemetry.rs 🔗

@@ -4,6 +4,7 @@ use lazy_static::lazy_static;
 use parking_lot::Mutex;
 use serde::Serialize;
 use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration};
+use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt};
 use tempfile::NamedTempFile;
 use util::http::HttpClient;
 use util::{channel::ReleaseChannel, TryFutureExt};
@@ -88,6 +89,16 @@ pub enum ClickhouseEvent {
         kind: AssistantKind,
         model: &'static str,
     },
+    Cpu {
+        usage_as_percent: f32,
+        core_count: u32,
+    },
+    Memory {
+        memory_in_bytes: u64,
+        virtual_memory_in_bytes: u64,
+        start_time_in_seconds: u64,
+        run_time_in_seconds: u64,
+    },
 }
 
 #[cfg(debug_assertions)]
@@ -136,7 +147,7 @@ impl Telemetry {
         Some(self.state.lock().log_file.as_ref()?.path().to_path_buf())
     }
 
-    pub fn start(self: &Arc<Self>, installation_id: Option<String>) {
+    pub fn start(self: &Arc<Self>, installation_id: Option<String>, cx: &mut AppContext) {
         let mut state = self.state.lock();
         state.installation_id = installation_id.map(|id| id.into());
         let has_clickhouse_events = !state.clickhouse_events_queue.is_empty();
@@ -145,6 +156,48 @@ impl Telemetry {
         if has_clickhouse_events {
             self.flush_clickhouse_events();
         }
+
+        let this = self.clone();
+        cx.spawn(|mut cx| async move {
+            let mut system = System::new_all();
+            system.refresh_all();
+
+            loop {
+                // Waiting some amount of time before the first query is important to get a reasonable value
+                // https://docs.rs/sysinfo/0.29.10/sysinfo/trait.ProcessExt.html#tymethod.cpu_usage
+                const DURATION_BETWEEN_SYSTEM_EVENTS: Duration = Duration::from_secs(60);
+                smol::Timer::after(DURATION_BETWEEN_SYSTEM_EVENTS).await;
+
+                let telemetry_settings = cx.update(|cx| *settings::get::<TelemetrySettings>(cx));
+
+                system.refresh_memory();
+                system.refresh_processes();
+
+                let current_process = Pid::from_u32(std::process::id());
+                let Some(process) = system.processes().get(&current_process) else {
+                    let process = current_process;
+                    log::error!("Failed to find own process {process:?} in system process table");
+                    // TODO: Fire an error telemetry event
+                    return;
+                };
+
+                let memory_event = ClickhouseEvent::Memory {
+                    memory_in_bytes: process.memory(),
+                    virtual_memory_in_bytes: process.virtual_memory(),
+                    start_time_in_seconds: process.start_time(),
+                    run_time_in_seconds: process.run_time(),
+                };
+
+                let cpu_event = ClickhouseEvent::Cpu {
+                    usage_as_percent: process.cpu_usage(),
+                    core_count: system.cpus().len() as u32,
+                };
+
+                this.report_clickhouse_event(memory_event, telemetry_settings);
+                this.report_clickhouse_event(cpu_event, telemetry_settings);
+            }
+        })
+        .detach();
     }
 
     pub fn set_authenticated_user_info(

crates/feedback/Cargo.toml 🔗

@@ -33,7 +33,7 @@ lazy_static.workspace = true
 postage.workspace = true
 serde.workspace = true
 serde_derive.workspace = true
-sysinfo = "0.27.1"
+sysinfo.workspace = true
 tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
 urlencoding = "2.1.2"
 

crates/zed/src/main.rs 🔗

@@ -177,7 +177,7 @@ fn main() {
         })
         .detach();
 
-        client.telemetry().start(installation_id);
+        client.telemetry().start(installation_id, cx);
 
         let app_state = Arc::new(AppState {
             languages,