今天我們要用 Rust 打造一個即時的系統監控器,能夠顯示 CPU 使用率、記憶體使用情況和磁碟空間資訊
仿間中也有很多像這樣的東西,其中最著名的就是 htop
,畢竟在學習 rust 製作專案的過程中,我建議這樣的東西
可以自己嘗試做做看。
[package]
name = "system_monitor"
version = "0.1.0"
edition = "2021"
[dependencies]
sysinfo = "0.29"
crossterm = "0.27"
tokio = { version = "1.0", features = ["full"] }
chrono = "0.4"
anyhow = "1.0"
這裡簡單介紹一下個別依賴他們的作用
systeminfo
-> 這個可以獲取系統的資訊,像是 CPU, 磁碟,記憶體corssterm
-> 這個可以用於跨平台的終端控制tokio
-> 可以進行異步操作的東西,這東西很常見,應該不用特別解說XDchrono
-> 日期時間處理相關元件(這個也很常用到)anyhow
-> 錯誤處理
use anyhow::Result;
use chrono::Local;
use crossterm::{
cursor,
event::{self, Event, KeyCode},
execute,
style::{Color, Print, ResetColor, SetForegroundColor},
terminal::{self, ClearType},
};
use std::io::{self, Write};
use std::time::Duration;
use sysinfo::{CpuExt, DiskExt, System, SystemExt};
use tokio::time;
pub struct SystemMonitor {
system: System,
}
impl SystemMonitor {
pub fn new() -> Self {
let mut system = System::new_all();
system.refresh_all();
Self { system }
}
pub fn refresh(&mut self) {
self.system.refresh_all();
// 等待一小段時間讓 CPU 使用率計算更準確
std::thread::sleep(Duration::from_millis(100));
self.system.refresh_cpu();
}
pub fn get_cpu_usage(&self) -> f32 {
self.system.global_cpu_info().cpu_usage()
}
pub fn get_memory_info(&self) -> (u64, u64, u64) {
let total = self.system.total_memory();
let used = self.system.used_memory();
let available = total - used;
(total, used, available)
}
pub fn get_disk_info(&self) -> Vec<(String, u64, u64)> {
self.system
.disks()
.iter()
.map(|disk| {
let name = disk.mount_point().to_string_lossy().to_string();
let total = disk.total_space();
let available = disk.available_space();
(name, total, available)
})
.collect()
}
pub fn get_cpu_count(&self) -> usize {
self.system.cpus().len()
}
}
pub struct Display {
stdout: io::Stdout,
}
impl Display {
pub fn new() -> Result<Self> {
let mut stdout = io::stdout();
terminal::enable_raw_mode()?;
execute!(stdout, terminal::Clear(ClearType::All))?;
Ok(Self { stdout })
}
pub fn clear_screen(&mut self) -> Result<()> {
execute!(
self.stdout,
terminal::Clear(ClearType::All),
cursor::MoveTo(0, 0)
)?;
Ok(())
}
pub fn print_header(&mut self) -> Result<()> {
let now = Local::now().format("%Y-%m-%d %H:%M:%S");
execute!(
self.stdout,
SetForegroundColor(Color::Cyan),
Print("=".repeat(60)),
Print("\n"),
Print(format!(" 系統監控器 - {}\n", now)),
Print("=".repeat(60)),
Print("\n\n"),
ResetColor
)?;
Ok(())
}
pub fn print_cpu_info(&mut self, usage: f32, cpu_count: usize) -> Result<()> {
let color = if usage > 80.0 {
Color::Red
} else if usage > 60.0 {
Color::Yellow
} else {
Color::Green
};
execute!(
self.stdout,
SetForegroundColor(Color::White),
Print("CPU 資訊:\n"),
ResetColor
)?;
execute!(
self.stdout,
Print(format!(" 核心數量: {}\n", cpu_count)),
Print(" 使用率: "),
SetForegroundColor(color),
Print(format!("{:.1}%", usage)),
ResetColor,
Print("\n"),
Print(" 圖表: "),
)?;
// CPU 使用率圖表
let bar_length = 30;
let filled = (usage / 100.0 * bar_length as f32) as usize;
execute!(self.stdout, Print("["))?;
for i in 0..bar_length {
if i < filled {
execute!(self.stdout, SetForegroundColor(color), Print("█"), ResetColor)?;
} else {
execute!(self.stdout, Print("░"))?;
}
}
execute!(self.stdout, Print("]\n\n"))?;
Ok(())
}
pub fn print_memory_info(&mut self, total: u64, used: u64, available: u64) -> Result<()> {
let usage_percent = (used as f64 / total as f64) * 100.0;
let color = if usage_percent > 80.0 {
Color::Red
} else if usage_percent > 60.0 {
Color::Yellow
} else {
Color::Green
};
execute!(
self.stdout,
SetForegroundColor(Color::White),
Print("記憶體資訊:\n"),
ResetColor
)?;
execute!(
self.stdout,
Print(format!(" 總容量: {:.2} GB\n", self.bytes_to_gb(total))),
Print(format!(" 已使用: {:.2} GB\n", self.bytes_to_gb(used))),
Print(format!(" 可用量: {:.2} GB\n", self.bytes_to_gb(available))),
Print(" 使用率: "),
SetForegroundColor(color),
Print(format!("{:.1}%", usage_percent)),
ResetColor,
Print("\n"),
Print(" 圖表: "),
)?;
// 記憶體使用率圖表
let bar_length = 30;
let filled = (usage_percent / 100.0 * bar_length as f64) as usize;
execute!(self.stdout, Print("["))?;
for i in 0..bar_length {
if i < filled {
execute!(self.stdout, SetForegroundColor(color), Print("█"), ResetColor)?;
} else {
execute!(self.stdout, Print("░"))?;
}
}
execute!(self.stdout, Print("]\n\n"))?;
Ok(())
}
pub fn print_disk_info(&mut self, disks: &[(String, u64, u64)]) -> Result<()> {
execute!(
self.stdout,
SetForegroundColor(Color::White),
Print("磁碟資訊:\n"),
ResetColor
)?;
for (name, total, available) in disks {
if *total == 0 {
continue;
}
let used = total - available;
let usage_percent = (used as f64 / *total as f64) * 100.0;
let color = if usage_percent > 90.0 {
Color::Red
} else if usage_percent > 75.0 {
Color::Yellow
} else {
Color::Green
};
execute!(
self.stdout,
Print(format!(" 掛載點: {}\n", name)),
Print(format!(" 總容量: {:.2} GB\n", self.bytes_to_gb(*total))),
Print(format!(" 可用量: {:.2} GB\n", self.bytes_to_gb(*available))),
Print(" 使用率: "),
SetForegroundColor(color),
Print(format!("{:.1}%", usage_percent)),
ResetColor,
Print("\n"),
Print(" 圖表: "),
)?;
// 磁碟使用率圖表
let bar_length = 30;
let filled = (usage_percent / 100.0 * bar_length as f64) as usize;
execute!(self.stdout, Print("["))?;
for i in 0..bar_length {
if i < filled {
execute!(self.stdout, SetForegroundColor(color), Print("█"), ResetColor)?;
} else {
execute!(self.stdout, Print("░"))?;
}
}
execute!(self.stdout, Print("]\n\n"))?;
}
Ok(())
}
pub fn print_footer(&mut self) -> Result<()> {
execute!(
self.stdout,
SetForegroundColor(Color::DarkGrey),
Print("按 'q' 或 'Ctrl+C' 退出程式 | 每 2 秒自動刷新\n"),
ResetColor
)?;
self.stdout.flush()?;
Ok(())
}
fn bytes_to_gb(&self, bytes: u64) -> f64 {
bytes as f64 / (1024.0 * 1024.0 * 1024.0)
}
}
impl Drop for Display {
fn drop(&mut self) {
let _ = terminal::disable_raw_mode();
let _ = execute!(self.stdout, terminal::Clear(ClearType::All));
}
}
#[tokio::main]
async fn main() -> Result<()> {
let mut monitor = SystemMonitor::new();
let mut display = Display::new()?;
println!("正在初始化系統監控器...");
time::sleep(Duration::from_secs(1)).await;
loop {
// 檢查是否有按鍵輸入
if event::poll(Duration::from_millis(100))? {
if let Event::Key(key_event) = event::read()? {
match key_event.code {
KeyCode::Char('q') | KeyCode::Esc => break,
KeyCode::Char('c') if key_event.modifiers.contains(event::KeyModifiers::CONTROL) => break,
_ => {}
}
}
}
// 刷新系統資訊
monitor.refresh();
// 清除螢幕並顯示資訊
display.clear_screen()?;
display.print_header()?;
// 顯示 CPU 資訊
let cpu_usage = monitor.get_cpu_usage();
let cpu_count = monitor.get_cpu_count();
display.print_cpu_info(cpu_usage, cpu_count)?;
// 顯示記憶體資訊
let (total_mem, used_mem, available_mem) = monitor.get_memory_info();
display.print_memory_info(total_mem, used_mem, available_mem)?;
// 顯示磁碟資訊
let disk_info = monitor.get_disk_info();
display.print_disk_info(&disk_info)?;
display.print_footer()?;
// 等待 2 秒後刷新
time::sleep(Duration::from_secs(2)).await;
}
println!("\n感謝使用系統監控器!");
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_system_monitor_creation() {
let monitor = SystemMonitor::new();
assert!(monitor.get_cpu_count() > 0);
}
#[test]
fn test_memory_info() {
let monitor = SystemMonitor::new();
let (total, used, available) = monitor.get_memory_info();
assert!(total > 0);
assert!(used <= total);
assert!(available <= total);
assert_eq!(used + available, total);
}
#[test]
fn test_disk_info() {
let monitor = SystemMonitor::new();
let disks = monitor.get_disk_info();
assert!(!disks.is_empty());
for (name, total, available) in disks {
assert!(!name.is_empty());
if total > 0 {
assert!(available <= total);
}
}
}
}
pub struct Display {
stdout: io::Stdout,
}
彩色輸出支援
進度條視覺化
清晰的資訊排版
即時刷新功能
let bar_length = 30;
let filled = (usage / 100.0 * bar_length as f32) as usize;
for i in 0..bar_length {
if i < filled {
execute!(self.stdout, SetForegroundColor(color), Print("█"), ResetColor)?;
} else {
execute!(self.stdout, Print("░"))?;
}
}
綠色:正常使用率(< 60%)
黃色:中等使用率(60-80%)
紅色:高使用率(> 80%)
============================================================
系統監控器 - 2025-09-21 14:30:25
============================================================
CPU 資訊:
核心數量: 8
使用率: 23.5%
圖表: [███████░░░░░░░░░░░░░░░░░░░░░░░░]
記憶體資訊:
總容量: 16.00 GB
已使用: 8.45 GB
可用量: 7.55 GB
使用率: 52.8%
圖表: [████████████████░░░░░░░░░░░░░░]
磁碟資訊:
掛載點: /
總容量: 500.00 GB
可用量: 123.45 GB
使用率: 75.3%
圖表: [██████████████████████░░░░░░░░]
按 'q' 或 'Ctrl+C' 退出程式 | 每 2 秒自動刷新
nice~~