1use crate::checks::ENABLED_CHECK;
2use anyhow::Context as _;
3use heim::units::{
4 frequency::{
5 gigahertz,
6 hertz,
7 },
8 Frequency,
9};
10use serenity::{
11 builder::{
12 CreateEmbed,
13 CreateEmbedFooter,
14 CreateMessage,
15 },
16 framework::standard::{
17 macros::command,
18 Args,
19 CommandResult,
20 },
21 model::{
22 colour::Colour,
23 prelude::*,
24 },
25 prelude::*,
26};
27use std::time::{
28 Duration,
29 Instant,
30};
31use systemstat::{
32 platform::common::Platform,
33 System,
34};
35use time::format_description::well_known::Rfc2822;
36use tracing::warn;
37use uom::{
38 fmt::DisplayStyle,
39 si::f32::Frequency as FrequencyF32,
40};
41
42const BYTES_IN_GB_F64: f64 = 1_000_000_000_f64;
43
44fn fmt_cpu_frequency(freq: Frequency) -> String {
45 let fmt_args = FrequencyF32::format_args(gigahertz, DisplayStyle::Abbreviation);
46 let freq = FrequencyF32::new::<hertz>(freq.get::<hertz>() as f32);
47
48 format!("{:.2}", fmt_args.with(freq))
49}
50
51async fn get_cpu_usage() -> Result<f32, heim::Error> {
52 let start = heim::cpu::usage().await?;
53 tokio::time::sleep(Duration::from_secs(1)).await;
54 let end = heim::cpu::usage().await?;
55
56 Ok((end - start).get::<heim::units::ratio::percent>())
57}
58
59#[command]
60#[description("Get System Stats")]
61#[bucket("system")]
62#[checks(Enabled)]
63async fn system(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
64 let start = Instant::now();
65
66 let profile_avatar_url = ctx.cache.current_user().avatar_url();
67
68 let sys = System::new();
70
71 let cpu_temp = match sys.cpu_temp() {
72 Ok(cpu_temp) => Some(cpu_temp),
73 Err(error) => {
74 warn!("Failed to get cpu temp: {error}");
75 None
76 }
77 };
78
79 let cache_context = pikadick_system_info::CacheContext::new();
81
82 let boot_time = match cache_context
83 .get_boot_time()
84 .context("failed to get boot time")
85 .map(time::OffsetDateTime::from)
86 .and_then(|boot_time| {
87 boot_time
88 .format(&Rfc2822)
89 .context("failed to format boot time date")
90 }) {
91 Ok(boot_time) => Some(boot_time),
92 Err(error) => {
93 warn!("{error:?}");
94 None
95 }
96 };
97
98 let uptime = match cache_context.get_uptime().context("failed to get uptime") {
99 Ok(uptime) => Some(uptime),
100 Err(error) => {
101 warn!("{error:?}");
102 None
103 }
104 };
105
106 let hostname = match cache_context
107 .get_hostname()
108 .context("failed to get hostname")
109 {
110 Ok(hostname) => Some(hostname),
111 Err(error) => {
112 warn!("{error:?}");
113 None
114 }
115 };
116
117 let architecture = match cache_context
118 .get_architecture()
119 .context("failed to get architecture")
120 {
121 Ok(architecture) => Some(
122 architecture
123 .map(|architecture| architecture.as_str())
124 .unwrap_or("unknown"),
125 ),
126 Err(error) => {
127 warn!("{error:?}");
128 None
129 }
130 };
131
132 let system_name = match cache_context
133 .get_system_name()
134 .context("failed to get system name")
135 {
136 Ok(system_name) => system_name,
137 Err(error) => {
138 warn!("{error:?}");
139 None
140 }
141 };
142
143 let system_version = match cache_context
144 .get_system_version()
145 .context("failed to get system version")
146 {
147 Ok(system_name) => Some(system_name),
148 Err(error) => {
149 warn!("{error:?}");
150 None
151 }
152 };
153
154 let total_memory = match cache_context
155 .get_total_memory()
156 .context("failed to get total memory")
157 {
158 Ok(memory) => Some(memory),
159 Err(error) => {
160 warn!("{error:?}");
161 None
162 }
163 };
164
165 let available_memory = match cache_context
166 .get_available_memory()
167 .context("failed to get available memory")
168 {
169 Ok(memory) => Some(memory),
170 Err(error) => {
171 warn!("{error:?}");
172 None
173 }
174 };
175
176 let total_swap = match cache_context
177 .get_total_swap()
178 .context("failed to get total swap")
179 {
180 Ok(memory) => Some(memory),
181 Err(error) => {
182 warn!("{error:?}");
183 None
184 }
185 };
186
187 let available_swap = match cache_context
188 .get_available_swap()
189 .context("failed to get available swap")
190 {
191 Ok(memory) => Some(memory),
192 Err(error) => {
193 warn!("{error:?}");
194 None
195 }
196 };
197
198 let cpu_frequency = match heim::cpu::frequency().await {
199 Ok(cpu_frequency) => Some(cpu_frequency),
200 Err(error) => {
201 warn!("Failed to get cpu frequency: {error}");
202 None
203 }
204 };
205
206 let cpu_logical_count = match heim::cpu::logical_count().await {
207 Ok(cpu_logical_count) => Some(cpu_logical_count),
208 Err(error) => {
209 warn!("Failed to get logical cpu count: {error}");
210 None
211 }
212 };
213
214 let cpu_physical_count = match heim::cpu::physical_count().await {
215 Ok(cpu_physical_count) => cpu_physical_count, Err(error) => {
217 warn!("Failed to get physical cpu count: {error}");
218 None
219 }
220 };
221
222 let virtualization = heim::virt::detect().await;
223
224 let cpu_usage = match get_cpu_usage().await {
225 Ok(usage) => Some(usage),
226 Err(error) => {
227 warn!("Failed to get cpu usage: {error}");
228 None
229 }
230 };
231
232 let data_retrieval_time = Instant::now() - start;
233
234 let mut embed_builder = CreateEmbed::new()
248 .title("System Status")
249 .color(Colour::from_rgb(255, 0, 0));
250 if let Some(icon) = profile_avatar_url {
251 embed_builder = embed_builder.thumbnail(icon);
252 }
253
254 if let Some(hostname) = hostname {
255 embed_builder = embed_builder.field("Hostname", hostname, true);
256 }
257
258 if let Some(system_name) = system_name {
259 embed_builder = embed_builder.field("OS", system_name, true);
260 }
261
262 if let Some(system_version) = system_version {
263 embed_builder = embed_builder.field("OS Version", system_version, true);
264 }
265
266 if let Some(architecture) = architecture {
267 embed_builder = embed_builder.field("Architecture", architecture, true);
268 }
269
270 if let Some(boot_time) = boot_time {
271 embed_builder = embed_builder.field("Boot Time", boot_time, true);
272 }
273
274 if let Some(uptime) = uptime {
275 let raw_secs = uptime.as_secs();
276
277 let days = raw_secs / (60 * 60 * 24);
278 let hours = (raw_secs % (60 * 60 * 24)) / (60 * 60);
279 let minutes = (raw_secs % (60 * 60)) / 60;
280 let seconds = raw_secs % 60;
281
282 let mut value = String::with_capacity(64);
283 if days != 0 {
284 value.push_str(itoa::Buffer::new().format(days));
285 value.push_str(" day");
286 if days > 1 {
287 value.push('s');
288 }
289 }
290
291 if hours != 0 {
292 value.push(' ');
293
294 value.push_str(itoa::Buffer::new().format(hours));
295 value.push_str(" hour");
296 if hours > 1 {
297 value.push('s');
298 }
299 }
300
301 if minutes != 0 {
302 value.push(' ');
303
304 value.push_str(itoa::Buffer::new().format(minutes));
305 value.push_str(" minute");
306 if minutes > 1 {
307 value.push('s');
308 }
309 }
310
311 if seconds != 0 {
312 value.push(' ');
313
314 value.push_str(itoa::Buffer::new().format(seconds));
315 value.push_str(" second");
316 if seconds > 1 {
317 value.push('s');
318 }
319 }
320
321 embed_builder = embed_builder.field("Uptime", value, true);
322 }
323
324 if let Some(cpu_frequency) = cpu_frequency {
326 embed_builder =
327 embed_builder.field("Cpu Freq", fmt_cpu_frequency(cpu_frequency.current()), true);
328
329 if let Some(min_cpu_frequency) = cpu_frequency.min() {
330 embed_builder =
331 embed_builder.field("Min Cpu Freq", fmt_cpu_frequency(min_cpu_frequency), true);
332 }
333
334 if let Some(max_cpu_frequency) = cpu_frequency.max() {
335 embed_builder =
336 embed_builder.field("Max Cpu Freq", fmt_cpu_frequency(max_cpu_frequency), true);
337 }
338 }
339
340 match (cpu_logical_count, cpu_physical_count) {
341 (Some(logical_count), Some(physical_count)) => {
342 embed_builder = embed_builder.field(
343 "Cpu Core Count",
344 format!("{logical_count} logical, {physical_count} physical"),
345 true,
346 );
347 }
348 (Some(logical_count), None) => {
349 embed_builder =
350 embed_builder.field("Cpu Core Count", format!("{logical_count} logical"), true);
351 }
352 (None, Some(physical_count)) => {
353 embed_builder =
354 embed_builder.field("Cpu Core Count", format!("{physical_count} physical"), true);
355 }
356 (None, None) => {}
357 }
358
359 if let (Some(total_memory), Some(available_memory)) = (total_memory, available_memory) {
360 embed_builder = embed_builder.field(
361 "Memory Usage",
362 format!(
363 "{:.2} GB / {:.2} GB",
364 (total_memory - available_memory) as f64 / BYTES_IN_GB_F64,
365 total_memory as f64 / BYTES_IN_GB_F64,
366 ),
367 true,
368 );
369 }
370
371 if let (Some(total_swap), Some(available_swap)) = (total_swap, available_swap) {
372 embed_builder = embed_builder.field(
373 "Swap Usage",
374 format!(
375 "{:.2} GB / {:.2} GB",
376 (total_swap - available_swap) as f64 / BYTES_IN_GB_F64,
377 total_swap as f64 / BYTES_IN_GB_F64,
378 ),
379 true,
380 );
381 }
382
383 let virtualization_field = match virtualization.as_ref() {
384 Some(virtualization) => virtualization.as_str(),
385 None => "None",
386 };
387 embed_builder = embed_builder.field("Virtualization", virtualization_field, true);
388
389 if let (Some(cpu_usage), Some(cpu_logical_count)) = (cpu_usage, cpu_logical_count) {
390 embed_builder = embed_builder.field(
391 "Cpu Usage",
392 format!("{:.2}%", cpu_usage / (cpu_logical_count as f32)),
393 true,
394 );
395 }
396
397 if let Some(cpu_temp) = cpu_temp {
405 embed_builder = embed_builder.field("Cpu Temp", format!("{cpu_temp} °C"), true);
406 }
407
408 let footer = CreateEmbedFooter::new(format!(
409 "Retrieved system data in {:.2} second(s)",
410 data_retrieval_time.as_secs_f32()
411 ));
412
413 embed_builder = embed_builder.footer(footer);
414
415 let message_builder = CreateMessage::new().embed(embed_builder);
416 msg.channel_id
417 .send_message(&ctx.http, message_builder)
418 .await?;
419
420 Ok(())
421}