From eeaadc098f189121d840849d4833dec4398364cb Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Thu, 21 Aug 2025 18:59:42 -0500 Subject: [PATCH] Add GPU info to Sentry crashes (#36624) Closes #ISSUE Adds system GPU collection to crash reporting. Currently this is Linux only. The system GPUs are determined by reading the `/sys/class/drm` directory structure, rather than using the exisiting `gpui::Window::gpu_specs()` method in order to gather more information, and so that the GPU context is not dependent on Vulkan context initialization (i.e. we still get GPU info when Zed fails to start because Vulkan failed to initialize). Unfortunately, the `blade` APIs do not support querying which GPU _will_ be used, so we do not know which GPU was attempted to be used when Vulkan context initialization fails, however, when Vulkan initialization succeeds, we send a message to the crash handler containing the result of `gpui::Window::gpu_specs()` to include the "Active" gpu in any crash report that may occur Release Notes: - N/A *or* Added/Fixed/Improved ... --- Cargo.lock | 31 ++++- Cargo.toml | 5 + crates/client/src/telemetry.rs | 2 +- crates/crashes/Cargo.toml | 2 + crates/crashes/src/crashes.rs | 26 +++- crates/feedback/Cargo.toml | 6 +- crates/feedback/src/feedback.rs | 6 +- crates/gpui/src/gpui.rs | 2 +- crates/system_specs/Cargo.toml | 28 ++++ crates/system_specs/LICENSE-GPL | 1 + .../src/system_specs.rs | 122 +++++++++++++++++- crates/workspace/Cargo.toml | 2 +- crates/zed/Cargo.toml | 2 + crates/zed/src/main.rs | 4 +- crates/zed/src/reliability.rs | 93 ++++++++++++- crates/zed/src/zed.rs | 12 +- 16 files changed, 315 insertions(+), 29 deletions(-) create mode 100644 crates/system_specs/Cargo.toml create mode 120000 crates/system_specs/LICENSE-GPL rename crates/{feedback => system_specs}/src/system_specs.rs (59%) diff --git a/Cargo.lock b/Cargo.lock index 61f6f42498978ce0075f95cf98b99fe6b7c0ae73..2b3d7b26917988616ef128648919ea129da2bd3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4050,6 +4050,7 @@ dependencies = [ name = "crashes" version = "0.1.0" dependencies = [ + "bincode", "crash-handler", "log", "mach2 0.5.0", @@ -4059,6 +4060,7 @@ dependencies = [ "serde", "serde_json", "smol", + "system_specs", "workspace-hack", ] @@ -5738,14 +5740,10 @@ dependencies = [ name = "feedback" version = "0.1.0" dependencies = [ - "client", "editor", "gpui", - "human_bytes", "menu", - "release_channel", - "serde", - "sysinfo", + "system_specs", "ui", "urlencoding", "util", @@ -11634,6 +11632,12 @@ dependencies = [ "hmac", ] +[[package]] +name = "pciid-parser" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0008e816fcdaf229cdd540e9b6ca2dc4a10d65c31624abb546c6420a02846e61" + [[package]] name = "pem" version = "3.0.5" @@ -16154,6 +16158,21 @@ dependencies = [ "winx", ] +[[package]] +name = "system_specs" +version = "0.1.0" +dependencies = [ + "anyhow", + "client", + "gpui", + "human_bytes", + "pciid-parser", + "release_channel", + "serde", + "sysinfo", + "workspace-hack", +] + [[package]] name = "tab_switcher" version = "0.1.0" @@ -20413,6 +20432,7 @@ dependencies = [ "auto_update", "auto_update_ui", "backtrace", + "bincode", "breadcrumbs", "call", "channel", @@ -20511,6 +20531,7 @@ dependencies = [ "supermaven", "svg_preview", "sysinfo", + "system_specs", "tab_switcher", "task", "tasks_ui", diff --git a/Cargo.toml b/Cargo.toml index b13795e1e191e45e01b45a79287c56eaf397d9fb..84de9b30adddac8a46366fd4760e243cc7ceab90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -155,6 +155,7 @@ members = [ "crates/streaming_diff", "crates/sum_tree", "crates/supermaven", + "crates/system_specs", "crates/supermaven_api", "crates/svg_preview", "crates/tab_switcher", @@ -381,6 +382,7 @@ streaming_diff = { path = "crates/streaming_diff" } sum_tree = { path = "crates/sum_tree" } supermaven = { path = "crates/supermaven" } supermaven_api = { path = "crates/supermaven_api" } +system_specs = { path = "crates/system_specs" } tab_switcher = { path = "crates/tab_switcher" } task = { path = "crates/task" } tasks_ui = { path = "crates/tasks_ui" } @@ -450,6 +452,7 @@ aws-sdk-bedrockruntime = { version = "1.80.0", features = [ aws-smithy-runtime-api = { version = "1.7.4", features = ["http-1x", "client"] } aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] } base64 = "0.22" +bincode = "1.2.1" bitflags = "2.6.0" blade-graphics = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" } blade-macros = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" } @@ -493,6 +496,7 @@ handlebars = "4.3" heck = "0.5" heed = { version = "0.21.0", features = ["read-txn-no-tls"] } hex = "0.4.3" +human_bytes = "0.4.1" html5ever = "0.27.0" http = "1.1" http-body = "1.0" @@ -532,6 +536,7 @@ palette = { version = "0.7.5", default-features = false, features = ["std"] } parking_lot = "0.12.1" partial-json-fixer = "0.5.3" parse_int = "0.9" +pciid-parser = "0.8.0" pathdiff = "0.2" pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" } pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" } diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index f3142a0af667d2022dfddd1c0f3f7b309e803d46..a5c1532c7563ab4bcb5f8826dcc18f3d52daf222 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -76,7 +76,7 @@ static ZED_CLIENT_CHECKSUM_SEED: LazyLock>> = LazyLock::new(|| { pub static MINIDUMP_ENDPOINT: LazyLock> = LazyLock::new(|| { option_env!("ZED_MINIDUMP_ENDPOINT") - .map(|s| s.to_owned()) + .map(str::to_string) .or_else(|| env::var("ZED_MINIDUMP_ENDPOINT").ok()) }); diff --git a/crates/crashes/Cargo.toml b/crates/crashes/Cargo.toml index f12913d1cbda34219430d8aba8e56431a056da59..370f0bb5f6b1088e0a5af90e786b937283072e73 100644 --- a/crates/crashes/Cargo.toml +++ b/crates/crashes/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace = true license = "GPL-3.0-or-later" [dependencies] +bincode.workspace = true crash-handler.workspace = true log.workspace = true minidumper.workspace = true @@ -14,6 +15,7 @@ release_channel.workspace = true smol.workspace = true serde.workspace = true serde_json.workspace = true +system_specs.workspace = true workspace-hack.workspace = true [target.'cfg(target_os = "macos")'.dependencies] diff --git a/crates/crashes/src/crashes.rs b/crates/crashes/src/crashes.rs index b1afc5ae454d30c9c8ee283fce5e03b942fb7c70..f7bc96bff93afff70970e1a5f9488048005ec4b3 100644 --- a/crates/crashes/src/crashes.rs +++ b/crates/crashes/src/crashes.rs @@ -127,6 +127,7 @@ unsafe fn suspend_all_other_threads() { pub struct CrashServer { initialization_params: OnceLock, panic_info: OnceLock, + active_gpu: OnceLock, has_connection: Arc, } @@ -135,6 +136,8 @@ pub struct CrashInfo { pub init: InitCrashHandler, pub panic: Option, pub minidump_error: Option, + pub gpus: Vec, + pub active_gpu: Option, } #[derive(Debug, Deserialize, Serialize, Clone)] @@ -143,7 +146,6 @@ pub struct InitCrashHandler { pub zed_version: String, pub release_channel: String, pub commit_sha: String, - // pub gpu: String, } #[derive(Deserialize, Serialize, Debug, Clone)] @@ -178,6 +180,18 @@ impl minidumper::ServerHandler for CrashServer { Err(e) => Some(format!("{e:?}")), }; + #[cfg(not(any(target_os = "linux", target_os = "freebsd")))] + let gpus = vec![]; + + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + let gpus = match system_specs::read_gpu_info_from_sys_class_drm() { + Ok(gpus) => gpus, + Err(err) => { + log::warn!("Failed to collect GPU information for crash report: {err}"); + vec![] + } + }; + let crash_info = CrashInfo { init: self .initialization_params @@ -186,6 +200,8 @@ impl minidumper::ServerHandler for CrashServer { .clone(), panic: self.panic_info.get().cloned(), minidump_error, + active_gpu: self.active_gpu.get().cloned(), + gpus, }; let crash_data_path = paths::logs_dir() @@ -211,6 +227,13 @@ impl minidumper::ServerHandler for CrashServer { serde_json::from_slice::(&buffer).expect("invalid panic data"); self.panic_info.set(panic_data).expect("already panicked"); } + 3 => { + let gpu_specs: system_specs::GpuSpecs = + bincode::deserialize(&buffer).expect("gpu specs"); + self.active_gpu + .set(gpu_specs) + .expect("already set active gpu"); + } _ => { panic!("invalid message kind"); } @@ -287,6 +310,7 @@ pub fn crash_server(socket: &Path) { initialization_params: OnceLock::new(), panic_info: OnceLock::new(), has_connection, + active_gpu: OnceLock::new(), }), &shutdown, Some(CRASH_HANDLER_PING_TIMEOUT), diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index 3a2c1fd7131ef7b5d7b07b8ec036fff4f1bba621..db872f7a15035c5012d42680c2d812d3486c6a89 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -15,13 +15,9 @@ path = "src/feedback.rs" test-support = [] [dependencies] -client.workspace = true gpui.workspace = true -human_bytes = "0.4.1" menu.workspace = true -release_channel.workspace = true -serde.workspace = true -sysinfo.workspace = true +system_specs.workspace = true ui.workspace = true urlencoding.workspace = true util.workspace = true diff --git a/crates/feedback/src/feedback.rs b/crates/feedback/src/feedback.rs index 40c2707d34c9f5ab50bdb51c8b82183be2106285..3822dd7ba38ac8131df4f391b8b0a5c05978fe8d 100644 --- a/crates/feedback/src/feedback.rs +++ b/crates/feedback/src/feedback.rs @@ -1,18 +1,14 @@ use gpui::{App, ClipboardItem, PromptLevel, actions}; -use system_specs::SystemSpecs; +use system_specs::{CopySystemSpecsIntoClipboard, SystemSpecs}; use util::ResultExt; use workspace::Workspace; use zed_actions::feedback::FileBugReport; pub mod feedback_modal; -pub mod system_specs; - actions!( zed, [ - /// Copies system specifications to the clipboard for bug reports. - CopySystemSpecsIntoClipboard, /// Opens email client to send feedback to Zed support. EmailZed, /// Opens the Zed repository on GitHub. diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index 5e4b5fe6e9d221a2915e1f8234eb01e14c177a46..0f5b98df39712157845b56d9c167f59d3f8831ab 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -352,7 +352,7 @@ impl Flatten for Result { } /// Information about the GPU GPUI is running on. -#[derive(Default, Debug)] +#[derive(Default, Debug, serde::Serialize, serde::Deserialize, Clone)] pub struct GpuSpecs { /// Whether the GPU is really a fake (like `llvmpipe`) running on the CPU. pub is_software_emulated: bool, diff --git a/crates/system_specs/Cargo.toml b/crates/system_specs/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..8ef1b581ae21632c4894b132c9c52f617e016e7f --- /dev/null +++ b/crates/system_specs/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "system_specs" +version = "0.1.0" +edition.workspace = true +publish.workspace = true +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/system_specs.rs" + +[features] +default = [] + +[dependencies] +anyhow.workspace = true +client.workspace = true +gpui.workspace = true +human_bytes.workspace = true +release_channel.workspace = true +serde.workspace = true +sysinfo.workspace = true +workspace-hack.workspace = true + +[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies] +pciid-parser.workspace = true diff --git a/crates/system_specs/LICENSE-GPL b/crates/system_specs/LICENSE-GPL new file mode 120000 index 0000000000000000000000000000000000000000..89e542f750cd3860a0598eff0dc34b56d7336dc4 --- /dev/null +++ b/crates/system_specs/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/feedback/src/system_specs.rs b/crates/system_specs/src/system_specs.rs similarity index 59% rename from crates/feedback/src/system_specs.rs rename to crates/system_specs/src/system_specs.rs index 87642ab9294b3dd4faa32f368056a136f4b79e18..731d335232cd5f7c2b9190e458aae9f1b96722b6 100644 --- a/crates/feedback/src/system_specs.rs +++ b/crates/system_specs/src/system_specs.rs @@ -1,11 +1,22 @@ +//! # system_specs + use client::telemetry; -use gpui::{App, AppContext as _, SemanticVersion, Task, Window}; +pub use gpui::GpuSpecs; +use gpui::{App, AppContext as _, SemanticVersion, Task, Window, actions}; use human_bytes::human_bytes; use release_channel::{AppCommitSha, AppVersion, ReleaseChannel}; use serde::Serialize; use std::{env, fmt::Display}; use sysinfo::{MemoryRefreshKind, RefreshKind, System}; +actions!( + zed, + [ + /// Copies system specifications to the clipboard for bug reports. + CopySystemSpecsIntoClipboard, + ] +); + #[derive(Clone, Debug, Serialize)] pub struct SystemSpecs { app_version: String, @@ -158,6 +169,115 @@ fn try_determine_available_gpus() -> Option { } } +#[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize, Clone)] +pub struct GpuInfo { + pub device_name: Option, + pub device_pci_id: u16, + pub vendor_name: Option, + pub vendor_pci_id: u16, + pub driver_version: Option, + pub driver_name: Option, +} + +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +pub fn read_gpu_info_from_sys_class_drm() -> anyhow::Result> { + use anyhow::Context as _; + use pciid_parser; + let dir_iter = std::fs::read_dir("/sys/class/drm").context("Failed to read /sys/class/drm")?; + let mut pci_addresses = vec![]; + let mut gpus = Vec::::new(); + let pci_db = pciid_parser::Database::read().ok(); + for entry in dir_iter { + let Ok(entry) = entry else { + continue; + }; + + let device_path = entry.path().join("device"); + let Some(pci_address) = device_path.read_link().ok().and_then(|pci_address| { + pci_address + .file_name() + .and_then(std::ffi::OsStr::to_str) + .map(str::trim) + .map(str::to_string) + }) else { + continue; + }; + let Ok(device_pci_id) = read_pci_id_from_path(device_path.join("device")) else { + continue; + }; + let Ok(vendor_pci_id) = read_pci_id_from_path(device_path.join("vendor")) else { + continue; + }; + let driver_name = std::fs::read_link(device_path.join("driver")) + .ok() + .and_then(|driver_link| { + driver_link + .file_name() + .and_then(std::ffi::OsStr::to_str) + .map(str::trim) + .map(str::to_string) + }); + let driver_version = driver_name + .as_ref() + .and_then(|driver_name| { + std::fs::read_to_string(format!("/sys/module/{driver_name}/version")).ok() + }) + .as_deref() + .map(str::trim) + .map(str::to_string); + + let already_found = gpus + .iter() + .zip(&pci_addresses) + .any(|(gpu, gpu_pci_address)| { + gpu_pci_address == &pci_address + && gpu.driver_version == driver_version + && gpu.driver_name == driver_name + }); + + if already_found { + continue; + } + + let vendor = pci_db + .as_ref() + .and_then(|db| db.vendors.get(&vendor_pci_id)); + let vendor_name = vendor.map(|vendor| vendor.name.clone()); + let device_name = vendor + .and_then(|vendor| vendor.devices.get(&device_pci_id)) + .map(|device| device.name.clone()); + + gpus.push(GpuInfo { + device_name, + device_pci_id, + vendor_name, + vendor_pci_id, + driver_version, + driver_name, + }); + pci_addresses.push(pci_address); + } + + Ok(gpus) +} + +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +fn read_pci_id_from_path(path: impl AsRef) -> anyhow::Result { + use anyhow::Context as _; + let id = std::fs::read_to_string(path)?; + let id = id + .trim() + .strip_prefix("0x") + .context("Not a device ID") + .context(id.clone())?; + anyhow::ensure!( + id.len() == 4, + "Not a device id, expected 4 digits, found {}", + id.len() + ); + u16::from_str_radix(id, 16).context("Failed to parse device ID") +} + /// Returns value of `ZED_BUNDLE_TYPE` set at compiletime or else at runtime. /// /// The compiletime value is used by flatpak since it doesn't seem to have a way to provide a diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index e1bda7ad3621f308a5b1a9f2f465394e5200c583..570657ba8f9ee8edb540df6a1bb4c8637cff64ac 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -29,7 +29,7 @@ test-support = [ any_vec.workspace = true anyhow.workspace = true async-recursion.workspace = true -bincode = "1.2.1" +bincode.workspace = true call.workspace = true client.workspace = true clock.workspace = true diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index ac4cd721244ab9abb5e008eb9d6fe32430793b90..c61e23f0a1a4dec7f56c8fe418f2cd0097db07ca 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -33,6 +33,7 @@ audio.workspace = true auto_update.workspace = true auto_update_ui.workspace = true backtrace = "0.3" +bincode.workspace = true breadcrumbs.workspace = true call.workspace = true channel.workspace = true @@ -60,6 +61,7 @@ extensions_ui.workspace = true feature_flags.workspace = true feedback.workspace = true file_finder.workspace = true +system_specs.workspace = true fs.workspace = true futures.workspace = true git.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 7ab76b71dee308570ba96d0e2039fcd4c6d16efb..8beefd58912a703097cabc0d2260b9c7ad5258d6 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -16,7 +16,7 @@ use extension_host::ExtensionStore; use fs::{Fs, RealFs}; use futures::{StreamExt, channel::oneshot, future}; use git::GitHostingProviderRegistry; -use gpui::{App, AppContext as _, Application, AsyncApp, Focusable as _, UpdateGlobal as _}; +use gpui::{App, AppContext, Application, AsyncApp, Focusable as _, UpdateGlobal as _}; use gpui_tokio::Tokio; use http_client::{Url, read_proxy_from_env}; @@ -240,7 +240,7 @@ pub fn main() { option_env!("ZED_COMMIT_SHA").map(|commit_sha| AppCommitSha::new(commit_sha.to_string())); if args.system_specs { - let system_specs = feedback::system_specs::SystemSpecs::new_stateless( + let system_specs = system_specs::SystemSpecs::new_stateless( app_version, app_commit_sha, *release_channel::RELEASE_CHANNEL, diff --git a/crates/zed/src/reliability.rs b/crates/zed/src/reliability.rs index e9acaa588dab8c19aac4c56e8714ecd5242532bb..ac06f1fd9f4d9842eb34a67abeef2432074ecc91 100644 --- a/crates/zed/src/reliability.rs +++ b/crates/zed/src/reliability.rs @@ -89,7 +89,9 @@ pub fn init_panic_hook( }, backtrace, ); - std::process::exit(-1); + if MINIDUMP_ENDPOINT.is_none() { + std::process::exit(-1); + } } let main_module_base_address = get_main_module_base_address(); @@ -148,7 +150,9 @@ pub fn init_panic_hook( } zlog::flush(); - if !is_pty && let Some(panic_data_json) = serde_json::to_string(&panic_data).log_err() { + if (!is_pty || MINIDUMP_ENDPOINT.is_some()) + && let Some(panic_data_json) = serde_json::to_string(&panic_data).log_err() + { let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string(); let panic_file_path = paths::logs_dir().join(format!("zed-{timestamp}.panic")); let panic_file = fs::OpenOptions::new() @@ -614,10 +618,9 @@ async fn upload_minidump( let mut panic_message = "".to_owned(); if let Some(panic_info) = metadata.panic.as_ref() { panic_message = panic_info.message.clone(); - form = form.text("sentry[logentry][formatted]", panic_info.message.clone()); - form = form.text("span", panic_info.span.clone()); - // TODO: add gpu-context, feature-flag-context, and more of device-context like gpu - // name, screen resolution, available ram, device model, etc + form = form + .text("sentry[logentry][formatted]", panic_info.message.clone()) + .text("span", panic_info.span.clone()); } if let Some(minidump_error) = metadata.minidump_error.clone() { form = form.text("minidump_error", minidump_error); @@ -633,6 +636,63 @@ async fn upload_minidump( commit_sha = metadata.init.commit_sha.clone(), ); + let gpu_count = metadata.gpus.len(); + for (index, gpu) in metadata.gpus.iter().cloned().enumerate() { + let system_specs::GpuInfo { + device_name, + device_pci_id, + vendor_name, + vendor_pci_id, + driver_version, + driver_name, + } = gpu; + let num = if gpu_count == 1 && metadata.active_gpu.is_none() { + String::new() + } else { + index.to_string() + }; + let name = format!("gpu{num}"); + let root = format!("sentry[contexts][{name}]"); + form = form + .text( + format!("{root}[Description]"), + "A GPU found on the users system. May or may not be the GPU Zed is running on", + ) + .text(format!("{root}[type]"), "gpu") + .text(format!("{root}[name]"), device_name.unwrap_or(name)) + .text(format!("{root}[id]"), format!("{:#06x}", device_pci_id)) + .text( + format!("{root}[vendor_id]"), + format!("{:#06x}", vendor_pci_id), + ) + .text_if_some(format!("{root}[vendor_name]"), vendor_name) + .text_if_some(format!("{root}[driver_version]"), driver_version) + .text_if_some(format!("{root}[driver_name]"), driver_name); + } + if let Some(active_gpu) = metadata.active_gpu.clone() { + form = form + .text( + "sentry[contexts][Active_GPU][Description]", + "The GPU Zed is running on", + ) + .text("sentry[contexts][Active_GPU][type]", "gpu") + .text("sentry[contexts][Active_GPU][name]", active_gpu.device_name) + .text( + "sentry[contexts][Active_GPU][driver_version]", + active_gpu.driver_info, + ) + .text( + "sentry[contexts][Active_GPU][driver_name]", + active_gpu.driver_name, + ) + .text( + "sentry[contexts][Active_GPU][is_software_emulated]", + active_gpu.is_software_emulated.to_string(), + ); + } + + // TODO: feature-flag-context, and more of device-context like screen resolution, available ram, device model, etc + let mut response_text = String::new(); let mut response = http.send_multipart_form(endpoint, form).await?; response @@ -646,6 +706,27 @@ async fn upload_minidump( Ok(()) } +trait FormExt { + fn text_if_some( + self, + label: impl Into>, + value: Option>>, + ) -> Self; +} + +impl FormExt for Form { + fn text_if_some( + self, + label: impl Into>, + value: Option>>, + ) -> Self { + match value { + Some(value) => self.text(label.into(), value.into()), + None => self, + } + } +} + async fn upload_panic( http: &Arc, panic_report_url: &Url, diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 3b5f99f9bda24d03db9791cfec16a97ca3de2345..638e1dca0e261dcb7d66c7ac2b8df9ed9ac78ff9 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -344,7 +344,17 @@ pub fn initialize_workspace( if let Some(specs) = window.gpu_specs() { log::info!("Using GPU: {:?}", specs); - show_software_emulation_warning_if_needed(specs, window, cx); + show_software_emulation_warning_if_needed(specs.clone(), window, cx); + if let Some((crash_server, message)) = crashes::CRASH_HANDLER + .get() + .zip(bincode::serialize(&specs).ok()) + && let Err(err) = crash_server.send_message(3, message) + { + log::warn!( + "Failed to store active gpu info for crash reporting: {}", + err + ); + } } let edit_prediction_menu_handle = PopoverMenuHandle::default();