pub use gpui::GpuSpecs;use gpui::{App, AppContext as _, Task, Window, actions};use human_bytes::human_bytes;use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};use semver::Version;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, os_name: String, os_version: String, ) -> Task<Self> { let app_version = AppVersion::global(cx).to_string(); let release_channel = ReleaseChannel::global(cx); let system = System::new_with_specifics( RefreshKind::nothing().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 { 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: Version, app_commit_sha: Option<AppCommitSha>, release_channel: ReleaseChannel, os_name: String, os_version: String, ) -> Self { let system = System::new_with_specifics( RefreshKind::nothing().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"))] { #[allow( clippy::disallowed_methods, reason = "we are not running in an executor" )] 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())}
pub use gpui::GpuSpecs;
use gpui::{App, AppContext as _, Task, Window, actions};
use human_bytes::human_bytes;
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
use semver::Version;
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,
os_name: String,
os_version: String,
) -> Task<Self> {
let app_version = AppVersion::global(cx).to_string();
let release_channel = ReleaseChannel::global(cx);
let system = System::new_with_specifics(
RefreshKind::nothing().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 {
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: Version,
app_commit_sha: Option<AppCommitSha>,
release_channel: ReleaseChannel,
os_name: String,
os_version: String,
) -> Self {
let system = System::new_with_specifics(
RefreshKind::nothing().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"))]
{
#[allow(
clippy::disallowed_methods,
reason = "we are not running in an executor"
)]
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())
}