1use client::telemetry;
2use gpui::{App, AppContext as _, SemanticVersion, Task, Window};
3use human_bytes::human_bytes;
4use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
5use serde::Serialize;
6use std::{env, fmt::Display};
7use sysinfo::{MemoryRefreshKind, RefreshKind, System};
8
9#[derive(Clone, Debug, Serialize)]
10pub struct SystemSpecs {
11 app_version: String,
12 release_channel: &'static str,
13 os_name: String,
14 os_version: String,
15 memory: u64,
16 architecture: &'static str,
17 commit_sha: Option<String>,
18 bundle_type: Option<String>,
19 gpu_specs: Option<String>,
20}
21
22impl SystemSpecs {
23 pub fn new(window: &mut Window, cx: &mut App) -> Task<Self> {
24 let app_version = AppVersion::global(cx).to_string();
25 let release_channel = ReleaseChannel::global(cx);
26 let os_name = telemetry::os_name();
27 let system = System::new_with_specifics(
28 RefreshKind::new().with_memory(MemoryRefreshKind::everything()),
29 );
30 let memory = system.total_memory();
31 let architecture = env::consts::ARCH;
32 let commit_sha = match release_channel {
33 ReleaseChannel::Dev | ReleaseChannel::Nightly => {
34 AppCommitSha::try_global(cx).map(|sha| sha.full())
35 }
36 _ => None,
37 };
38 let bundle_type = bundle_type();
39
40 let gpu_specs = window.gpu_specs().map(|specs| {
41 format!(
42 "{} || {} || {}",
43 specs.device_name, specs.driver_name, specs.driver_info
44 )
45 });
46
47 cx.background_spawn(async move {
48 let os_version = telemetry::os_version();
49 SystemSpecs {
50 app_version,
51 release_channel: release_channel.display_name(),
52 bundle_type,
53 os_name,
54 os_version,
55 memory,
56 architecture,
57 commit_sha,
58 gpu_specs,
59 }
60 })
61 }
62
63 pub fn new_stateless(
64 app_version: SemanticVersion,
65 app_commit_sha: Option<AppCommitSha>,
66 release_channel: ReleaseChannel,
67 ) -> Self {
68 let os_name = telemetry::os_name();
69 let os_version = telemetry::os_version();
70 let system = System::new_with_specifics(
71 RefreshKind::new().with_memory(MemoryRefreshKind::everything()),
72 );
73 let memory = system.total_memory();
74 let architecture = env::consts::ARCH;
75 let commit_sha = match release_channel {
76 ReleaseChannel::Dev | ReleaseChannel::Nightly => app_commit_sha.map(|sha| sha.full()),
77 _ => None,
78 };
79 let bundle_type = bundle_type();
80
81 Self {
82 app_version: app_version.to_string(),
83 release_channel: release_channel.display_name(),
84 os_name,
85 os_version,
86 memory,
87 architecture,
88 commit_sha,
89 bundle_type,
90 gpu_specs: try_determine_available_gpus(),
91 }
92 }
93}
94
95impl Display for SystemSpecs {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 let os_information = format!("OS: {} {}", self.os_name, self.os_version);
98 let app_version_information = format!(
99 "Zed: v{} ({}) {}{}",
100 self.app_version,
101 match &self.commit_sha {
102 Some(commit_sha) => format!("{} {}", self.release_channel, commit_sha),
103 None => self.release_channel.to_string(),
104 },
105 if let Some(bundle_type) = &self.bundle_type {
106 format!("({bundle_type})")
107 } else {
108 "".to_string()
109 },
110 if cfg!(debug_assertions) {
111 "(Taylor's Version)"
112 } else {
113 ""
114 },
115 );
116 let system_specs = [
117 app_version_information,
118 os_information,
119 format!("Memory: {}", human_bytes(self.memory as f64)),
120 format!("Architecture: {}", self.architecture),
121 ]
122 .into_iter()
123 .chain(
124 self.gpu_specs
125 .as_ref()
126 .map(|specs| format!("GPU: {}", specs)),
127 )
128 .collect::<Vec<String>>()
129 .join("\n");
130
131 write!(f, "{system_specs}")
132 }
133}
134
135fn try_determine_available_gpus() -> Option<String> {
136 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
137 {
138 std::process::Command::new("vulkaninfo")
139 .args(&["--summary"])
140 .output()
141 .ok()
142 .map(|output| {
143 [
144 "<details><summary>`vulkaninfo --summary` output</summary>",
145 "",
146 "```",
147 String::from_utf8_lossy(&output.stdout).as_ref(),
148 "```",
149 "</details>",
150 ]
151 .join("\n")
152 })
153 .or(Some("Failed to run `vulkaninfo --summary`".to_string()))
154 }
155 #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
156 {
157 None
158 }
159}
160
161/// Returns value of `ZED_BUNDLE_TYPE` set at compiletime or else at runtime.
162///
163/// The compiletime value is used by flatpak since it doesn't seem to have a way to provide a
164/// runtime environment variable.
165///
166/// The runtime value is used by snap since the Zed snaps use release binaries directly, and so
167/// cannot have this baked in.
168fn bundle_type() -> Option<String> {
169 option_env!("ZED_BUNDLE_TYPE")
170 .map(|bundle_type| bundle_type.to_string())
171 .or_else(|| env::var("ZED_BUNDLE_TYPE").ok())
172}