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 ...
This commit is contained in:
Ben Kunkle 2025-08-21 18:59:42 -05:00 committed by GitHub
parent 18fe68d991
commit eeaadc098f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 315 additions and 29 deletions

31
Cargo.lock generated
View file

@ -4050,6 +4050,7 @@ dependencies = [
name = "crashes" name = "crashes"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bincode",
"crash-handler", "crash-handler",
"log", "log",
"mach2 0.5.0", "mach2 0.5.0",
@ -4059,6 +4060,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"smol", "smol",
"system_specs",
"workspace-hack", "workspace-hack",
] ]
@ -5738,14 +5740,10 @@ dependencies = [
name = "feedback" name = "feedback"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"client",
"editor", "editor",
"gpui", "gpui",
"human_bytes",
"menu", "menu",
"release_channel", "system_specs",
"serde",
"sysinfo",
"ui", "ui",
"urlencoding", "urlencoding",
"util", "util",
@ -11634,6 +11632,12 @@ dependencies = [
"hmac", "hmac",
] ]
[[package]]
name = "pciid-parser"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0008e816fcdaf229cdd540e9b6ca2dc4a10d65c31624abb546c6420a02846e61"
[[package]] [[package]]
name = "pem" name = "pem"
version = "3.0.5" version = "3.0.5"
@ -16154,6 +16158,21 @@ dependencies = [
"winx", "winx",
] ]
[[package]]
name = "system_specs"
version = "0.1.0"
dependencies = [
"anyhow",
"client",
"gpui",
"human_bytes",
"pciid-parser",
"release_channel",
"serde",
"sysinfo",
"workspace-hack",
]
[[package]] [[package]]
name = "tab_switcher" name = "tab_switcher"
version = "0.1.0" version = "0.1.0"
@ -20413,6 +20432,7 @@ dependencies = [
"auto_update", "auto_update",
"auto_update_ui", "auto_update_ui",
"backtrace", "backtrace",
"bincode",
"breadcrumbs", "breadcrumbs",
"call", "call",
"channel", "channel",
@ -20511,6 +20531,7 @@ dependencies = [
"supermaven", "supermaven",
"svg_preview", "svg_preview",
"sysinfo", "sysinfo",
"system_specs",
"tab_switcher", "tab_switcher",
"task", "task",
"tasks_ui", "tasks_ui",

View file

@ -155,6 +155,7 @@ members = [
"crates/streaming_diff", "crates/streaming_diff",
"crates/sum_tree", "crates/sum_tree",
"crates/supermaven", "crates/supermaven",
"crates/system_specs",
"crates/supermaven_api", "crates/supermaven_api",
"crates/svg_preview", "crates/svg_preview",
"crates/tab_switcher", "crates/tab_switcher",
@ -381,6 +382,7 @@ streaming_diff = { path = "crates/streaming_diff" }
sum_tree = { path = "crates/sum_tree" } sum_tree = { path = "crates/sum_tree" }
supermaven = { path = "crates/supermaven" } supermaven = { path = "crates/supermaven" }
supermaven_api = { path = "crates/supermaven_api" } supermaven_api = { path = "crates/supermaven_api" }
system_specs = { path = "crates/system_specs" }
tab_switcher = { path = "crates/tab_switcher" } tab_switcher = { path = "crates/tab_switcher" }
task = { path = "crates/task" } task = { path = "crates/task" }
tasks_ui = { path = "crates/tasks_ui" } 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-runtime-api = { version = "1.7.4", features = ["http-1x", "client"] }
aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] } aws-smithy-types = { version = "1.3.0", features = ["http-body-1-x"] }
base64 = "0.22" base64 = "0.22"
bincode = "1.2.1"
bitflags = "2.6.0" bitflags = "2.6.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" } blade-graphics = { git = "https://github.com/kvark/blade", rev = "e0ec4e720957edd51b945b64dd85605ea54bcfe5" }
blade-macros = { 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" heck = "0.5"
heed = { version = "0.21.0", features = ["read-txn-no-tls"] } heed = { version = "0.21.0", features = ["read-txn-no-tls"] }
hex = "0.4.3" hex = "0.4.3"
human_bytes = "0.4.1"
html5ever = "0.27.0" html5ever = "0.27.0"
http = "1.1" http = "1.1"
http-body = "1.0" http-body = "1.0"
@ -532,6 +536,7 @@ palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1" parking_lot = "0.12.1"
partial-json-fixer = "0.5.3" partial-json-fixer = "0.5.3"
parse_int = "0.9" parse_int = "0.9"
pciid-parser = "0.8.0"
pathdiff = "0.2" pathdiff = "0.2"
pet = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" } 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" } pet-conda = { git = "https://github.com/microsoft/python-environment-tools.git", rev = "845945b830297a50de0e24020b980a65e4820559" }

View file

@ -76,7 +76,7 @@ static ZED_CLIENT_CHECKSUM_SEED: LazyLock<Option<Vec<u8>>> = LazyLock::new(|| {
pub static MINIDUMP_ENDPOINT: LazyLock<Option<String>> = LazyLock::new(|| { pub static MINIDUMP_ENDPOINT: LazyLock<Option<String>> = LazyLock::new(|| {
option_env!("ZED_MINIDUMP_ENDPOINT") option_env!("ZED_MINIDUMP_ENDPOINT")
.map(|s| s.to_owned()) .map(str::to_string)
.or_else(|| env::var("ZED_MINIDUMP_ENDPOINT").ok()) .or_else(|| env::var("ZED_MINIDUMP_ENDPOINT").ok())
}); });

View file

@ -6,6 +6,7 @@ edition.workspace = true
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
[dependencies] [dependencies]
bincode.workspace = true
crash-handler.workspace = true crash-handler.workspace = true
log.workspace = true log.workspace = true
minidumper.workspace = true minidumper.workspace = true
@ -14,6 +15,7 @@ release_channel.workspace = true
smol.workspace = true smol.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
system_specs.workspace = true
workspace-hack.workspace = true workspace-hack.workspace = true
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]

View file

@ -127,6 +127,7 @@ unsafe fn suspend_all_other_threads() {
pub struct CrashServer { pub struct CrashServer {
initialization_params: OnceLock<InitCrashHandler>, initialization_params: OnceLock<InitCrashHandler>,
panic_info: OnceLock<CrashPanic>, panic_info: OnceLock<CrashPanic>,
active_gpu: OnceLock<system_specs::GpuSpecs>,
has_connection: Arc<AtomicBool>, has_connection: Arc<AtomicBool>,
} }
@ -135,6 +136,8 @@ pub struct CrashInfo {
pub init: InitCrashHandler, pub init: InitCrashHandler,
pub panic: Option<CrashPanic>, pub panic: Option<CrashPanic>,
pub minidump_error: Option<String>, pub minidump_error: Option<String>,
pub gpus: Vec<system_specs::GpuInfo>,
pub active_gpu: Option<system_specs::GpuSpecs>,
} }
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
@ -143,7 +146,6 @@ pub struct InitCrashHandler {
pub zed_version: String, pub zed_version: String,
pub release_channel: String, pub release_channel: String,
pub commit_sha: String, pub commit_sha: String,
// pub gpu: String,
} }
#[derive(Deserialize, Serialize, Debug, Clone)] #[derive(Deserialize, Serialize, Debug, Clone)]
@ -178,6 +180,18 @@ impl minidumper::ServerHandler for CrashServer {
Err(e) => Some(format!("{e:?}")), 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 { let crash_info = CrashInfo {
init: self init: self
.initialization_params .initialization_params
@ -186,6 +200,8 @@ impl minidumper::ServerHandler for CrashServer {
.clone(), .clone(),
panic: self.panic_info.get().cloned(), panic: self.panic_info.get().cloned(),
minidump_error, minidump_error,
active_gpu: self.active_gpu.get().cloned(),
gpus,
}; };
let crash_data_path = paths::logs_dir() let crash_data_path = paths::logs_dir()
@ -211,6 +227,13 @@ impl minidumper::ServerHandler for CrashServer {
serde_json::from_slice::<CrashPanic>(&buffer).expect("invalid panic data"); serde_json::from_slice::<CrashPanic>(&buffer).expect("invalid panic data");
self.panic_info.set(panic_data).expect("already panicked"); 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"); panic!("invalid message kind");
} }
@ -287,6 +310,7 @@ pub fn crash_server(socket: &Path) {
initialization_params: OnceLock::new(), initialization_params: OnceLock::new(),
panic_info: OnceLock::new(), panic_info: OnceLock::new(),
has_connection, has_connection,
active_gpu: OnceLock::new(),
}), }),
&shutdown, &shutdown,
Some(CRASH_HANDLER_PING_TIMEOUT), Some(CRASH_HANDLER_PING_TIMEOUT),

View file

@ -15,13 +15,9 @@ path = "src/feedback.rs"
test-support = [] test-support = []
[dependencies] [dependencies]
client.workspace = true
gpui.workspace = true gpui.workspace = true
human_bytes = "0.4.1"
menu.workspace = true menu.workspace = true
release_channel.workspace = true system_specs.workspace = true
serde.workspace = true
sysinfo.workspace = true
ui.workspace = true ui.workspace = true
urlencoding.workspace = true urlencoding.workspace = true
util.workspace = true util.workspace = true

View file

@ -1,18 +1,14 @@
use gpui::{App, ClipboardItem, PromptLevel, actions}; use gpui::{App, ClipboardItem, PromptLevel, actions};
use system_specs::SystemSpecs; use system_specs::{CopySystemSpecsIntoClipboard, SystemSpecs};
use util::ResultExt; use util::ResultExt;
use workspace::Workspace; use workspace::Workspace;
use zed_actions::feedback::FileBugReport; use zed_actions::feedback::FileBugReport;
pub mod feedback_modal; pub mod feedback_modal;
pub mod system_specs;
actions!( actions!(
zed, zed,
[ [
/// Copies system specifications to the clipboard for bug reports.
CopySystemSpecsIntoClipboard,
/// Opens email client to send feedback to Zed support. /// Opens email client to send feedback to Zed support.
EmailZed, EmailZed,
/// Opens the Zed repository on GitHub. /// Opens the Zed repository on GitHub.

View file

@ -352,7 +352,7 @@ impl<T> Flatten<T> for Result<T> {
} }
/// Information about the GPU GPUI is running on. /// Information about the GPU GPUI is running on.
#[derive(Default, Debug)] #[derive(Default, Debug, serde::Serialize, serde::Deserialize, Clone)]
pub struct GpuSpecs { pub struct GpuSpecs {
/// Whether the GPU is really a fake (like `llvmpipe`) running on the CPU. /// Whether the GPU is really a fake (like `llvmpipe`) running on the CPU.
pub is_software_emulated: bool, pub is_software_emulated: bool,

View file

@ -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

View file

@ -0,0 +1 @@
../../LICENSE-GPL

View file

@ -1,11 +1,22 @@
//! # system_specs
use client::telemetry; 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 human_bytes::human_bytes;
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel}; use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
use serde::Serialize; use serde::Serialize;
use std::{env, fmt::Display}; use std::{env, fmt::Display};
use sysinfo::{MemoryRefreshKind, RefreshKind, System}; use sysinfo::{MemoryRefreshKind, RefreshKind, System};
actions!(
zed,
[
/// Copies system specifications to the clipboard for bug reports.
CopySystemSpecsIntoClipboard,
]
);
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
pub struct SystemSpecs { pub struct SystemSpecs {
app_version: String, app_version: String,
@ -158,6 +169,115 @@ fn try_determine_available_gpus() -> Option<String> {
} }
} }
#[derive(Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize, Clone)]
pub struct GpuInfo {
pub device_name: Option<String>,
pub device_pci_id: u16,
pub vendor_name: Option<String>,
pub vendor_pci_id: u16,
pub driver_version: Option<String>,
pub driver_name: Option<String>,
}
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
pub fn read_gpu_info_from_sys_class_drm() -> anyhow::Result<Vec<GpuInfo>> {
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::<GpuInfo>::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<std::path::Path>) -> anyhow::Result<u16> {
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. /// 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 /// The compiletime value is used by flatpak since it doesn't seem to have a way to provide a

View file

@ -29,7 +29,7 @@ test-support = [
any_vec.workspace = true any_vec.workspace = true
anyhow.workspace = true anyhow.workspace = true
async-recursion.workspace = true async-recursion.workspace = true
bincode = "1.2.1" bincode.workspace = true
call.workspace = true call.workspace = true
client.workspace = true client.workspace = true
clock.workspace = true clock.workspace = true

View file

@ -33,6 +33,7 @@ audio.workspace = true
auto_update.workspace = true auto_update.workspace = true
auto_update_ui.workspace = true auto_update_ui.workspace = true
backtrace = "0.3" backtrace = "0.3"
bincode.workspace = true
breadcrumbs.workspace = true breadcrumbs.workspace = true
call.workspace = true call.workspace = true
channel.workspace = true channel.workspace = true
@ -60,6 +61,7 @@ extensions_ui.workspace = true
feature_flags.workspace = true feature_flags.workspace = true
feedback.workspace = true feedback.workspace = true
file_finder.workspace = true file_finder.workspace = true
system_specs.workspace = true
fs.workspace = true fs.workspace = true
futures.workspace = true futures.workspace = true
git.workspace = true git.workspace = true

View file

@ -16,7 +16,7 @@ use extension_host::ExtensionStore;
use fs::{Fs, RealFs}; use fs::{Fs, RealFs};
use futures::{StreamExt, channel::oneshot, future}; use futures::{StreamExt, channel::oneshot, future};
use git::GitHostingProviderRegistry; 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 gpui_tokio::Tokio;
use http_client::{Url, read_proxy_from_env}; 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())); option_env!("ZED_COMMIT_SHA").map(|commit_sha| AppCommitSha::new(commit_sha.to_string()));
if args.system_specs { if args.system_specs {
let system_specs = feedback::system_specs::SystemSpecs::new_stateless( let system_specs = system_specs::SystemSpecs::new_stateless(
app_version, app_version,
app_commit_sha, app_commit_sha,
*release_channel::RELEASE_CHANNEL, *release_channel::RELEASE_CHANNEL,

View file

@ -89,7 +89,9 @@ pub fn init_panic_hook(
}, },
backtrace, backtrace,
); );
std::process::exit(-1); if MINIDUMP_ENDPOINT.is_none() {
std::process::exit(-1);
}
} }
let main_module_base_address = get_main_module_base_address(); let main_module_base_address = get_main_module_base_address();
@ -148,7 +150,9 @@ pub fn init_panic_hook(
} }
zlog::flush(); 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 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_path = paths::logs_dir().join(format!("zed-{timestamp}.panic"));
let panic_file = fs::OpenOptions::new() let panic_file = fs::OpenOptions::new()
@ -614,10 +618,9 @@ async fn upload_minidump(
let mut panic_message = "".to_owned(); let mut panic_message = "".to_owned();
if let Some(panic_info) = metadata.panic.as_ref() { if let Some(panic_info) = metadata.panic.as_ref() {
panic_message = panic_info.message.clone(); panic_message = panic_info.message.clone();
form = form.text("sentry[logentry][formatted]", panic_info.message.clone()); form = form
form = form.text("span", panic_info.span.clone()); .text("sentry[logentry][formatted]", panic_info.message.clone())
// TODO: add gpu-context, feature-flag-context, and more of device-context like gpu .text("span", panic_info.span.clone());
// name, screen resolution, available ram, device model, etc
} }
if let Some(minidump_error) = metadata.minidump_error.clone() { if let Some(minidump_error) = metadata.minidump_error.clone() {
form = form.text("minidump_error", minidump_error); form = form.text("minidump_error", minidump_error);
@ -633,6 +636,63 @@ async fn upload_minidump(
commit_sha = metadata.init.commit_sha.clone(), 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_text = String::new();
let mut response = http.send_multipart_form(endpoint, form).await?; let mut response = http.send_multipart_form(endpoint, form).await?;
response response
@ -646,6 +706,27 @@ async fn upload_minidump(
Ok(()) Ok(())
} }
trait FormExt {
fn text_if_some(
self,
label: impl Into<std::borrow::Cow<'static, str>>,
value: Option<impl Into<std::borrow::Cow<'static, str>>>,
) -> Self;
}
impl FormExt for Form {
fn text_if_some(
self,
label: impl Into<std::borrow::Cow<'static, str>>,
value: Option<impl Into<std::borrow::Cow<'static, str>>>,
) -> Self {
match value {
Some(value) => self.text(label.into(), value.into()),
None => self,
}
}
}
async fn upload_panic( async fn upload_panic(
http: &Arc<HttpClientWithUrl>, http: &Arc<HttpClientWithUrl>,
panic_report_url: &Url, panic_report_url: &Url,

View file

@ -344,7 +344,17 @@ pub fn initialize_workspace(
if let Some(specs) = window.gpu_specs() { if let Some(specs) = window.gpu_specs() {
log::info!("Using 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(); let edit_prediction_menu_handle = PopoverMenuHandle::default();