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:
parent
18fe68d991
commit
eeaadc098f
16 changed files with 315 additions and 29 deletions
292
crates/system_specs/src/system_specs.rs
Normal file
292
crates/system_specs/src/system_specs.rs
Normal file
|
@ -0,0 +1,292 @@
|
|||
//! # system_specs
|
||||
|
||||
use client::telemetry;
|
||||
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,
|
||||
release_channel: &'static str,
|
||||
os_name: String,
|
||||
os_version: String,
|
||||
memory: u64,
|
||||
architecture: &'static str,
|
||||
commit_sha: Option<String>,
|
||||
bundle_type: Option<String>,
|
||||
gpu_specs: Option<String>,
|
||||
}
|
||||
|
||||
impl SystemSpecs {
|
||||
pub fn new(window: &mut Window, cx: &mut App) -> Task<Self> {
|
||||
let app_version = AppVersion::global(cx).to_string();
|
||||
let release_channel = ReleaseChannel::global(cx);
|
||||
let os_name = telemetry::os_name();
|
||||
let system = System::new_with_specifics(
|
||||
RefreshKind::new().with_memory(MemoryRefreshKind::everything()),
|
||||
);
|
||||
let memory = system.total_memory();
|
||||
let architecture = env::consts::ARCH;
|
||||
let commit_sha = match release_channel {
|
||||
ReleaseChannel::Dev | ReleaseChannel::Nightly => {
|
||||
AppCommitSha::try_global(cx).map(|sha| sha.full())
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let bundle_type = bundle_type();
|
||||
|
||||
let gpu_specs = window.gpu_specs().map(|specs| {
|
||||
format!(
|
||||
"{} || {} || {}",
|
||||
specs.device_name, specs.driver_name, specs.driver_info
|
||||
)
|
||||
});
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let os_version = telemetry::os_version();
|
||||
SystemSpecs {
|
||||
app_version,
|
||||
release_channel: release_channel.display_name(),
|
||||
bundle_type,
|
||||
os_name,
|
||||
os_version,
|
||||
memory,
|
||||
architecture,
|
||||
commit_sha,
|
||||
gpu_specs,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_stateless(
|
||||
app_version: SemanticVersion,
|
||||
app_commit_sha: Option<AppCommitSha>,
|
||||
release_channel: ReleaseChannel,
|
||||
) -> Self {
|
||||
let os_name = telemetry::os_name();
|
||||
let os_version = telemetry::os_version();
|
||||
let system = System::new_with_specifics(
|
||||
RefreshKind::new().with_memory(MemoryRefreshKind::everything()),
|
||||
);
|
||||
let memory = system.total_memory();
|
||||
let architecture = env::consts::ARCH;
|
||||
let commit_sha = match release_channel {
|
||||
ReleaseChannel::Dev | ReleaseChannel::Nightly => app_commit_sha.map(|sha| sha.full()),
|
||||
_ => None,
|
||||
};
|
||||
let bundle_type = bundle_type();
|
||||
|
||||
Self {
|
||||
app_version: app_version.to_string(),
|
||||
release_channel: release_channel.display_name(),
|
||||
os_name,
|
||||
os_version,
|
||||
memory,
|
||||
architecture,
|
||||
commit_sha,
|
||||
bundle_type,
|
||||
gpu_specs: try_determine_available_gpus(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SystemSpecs {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let os_information = format!("OS: {} {}", self.os_name, self.os_version);
|
||||
let app_version_information = format!(
|
||||
"Zed: v{} ({}) {}{}",
|
||||
self.app_version,
|
||||
match &self.commit_sha {
|
||||
Some(commit_sha) => format!("{} {}", self.release_channel, commit_sha),
|
||||
None => self.release_channel.to_string(),
|
||||
},
|
||||
if let Some(bundle_type) = &self.bundle_type {
|
||||
format!("({bundle_type})")
|
||||
} else {
|
||||
"".to_string()
|
||||
},
|
||||
if cfg!(debug_assertions) {
|
||||
"(Taylor's Version)"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
);
|
||||
let system_specs = [
|
||||
app_version_information,
|
||||
os_information,
|
||||
format!("Memory: {}", human_bytes(self.memory as f64)),
|
||||
format!("Architecture: {}", self.architecture),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(
|
||||
self.gpu_specs
|
||||
.as_ref()
|
||||
.map(|specs| format!("GPU: {}", specs)),
|
||||
)
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
write!(f, "{system_specs}")
|
||||
}
|
||||
}
|
||||
|
||||
fn try_determine_available_gpus() -> Option<String> {
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
{
|
||||
std::process::Command::new("vulkaninfo")
|
||||
.args(&["--summary"])
|
||||
.output()
|
||||
.ok()
|
||||
.map(|output| {
|
||||
[
|
||||
"<details><summary>`vulkaninfo --summary` output</summary>",
|
||||
"",
|
||||
"```",
|
||||
String::from_utf8_lossy(&output.stdout).as_ref(),
|
||||
"```",
|
||||
"</details>",
|
||||
]
|
||||
.join("\n")
|
||||
})
|
||||
.or(Some("Failed to run `vulkaninfo --summary`".to_string()))
|
||||
}
|
||||
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[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.
|
||||
///
|
||||
/// The compiletime value is used by flatpak since it doesn't seem to have a way to provide a
|
||||
/// runtime environment variable.
|
||||
///
|
||||
/// The runtime value is used by snap since the Zed snaps use release binaries directly, and so
|
||||
/// cannot have this baked in.
|
||||
fn bundle_type() -> Option<String> {
|
||||
option_env!("ZED_BUNDLE_TYPE")
|
||||
.map(|bundle_type| bundle_type.to_string())
|
||||
.or_else(|| env::var("ZED_BUNDLE_TYPE").ok())
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue