在這個專案中,我們建立一個全面的系統效能基準測試工具。
這個工具能夠測試 CPU、磁碟和網路的效能,
提供詳細的測試報告,幫助開發者和系統管理員了解系統的效能瓶頸。
目前鐵人賽已經接近尾聲,關於系統工具相關的學習和思考,
這已經是最後一篇,明天會考慮做整合相關的部分
cargo new system-benchmark
cd system-benchmark
cargo.toml
[package]
name = "system-benchmark"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4.5", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.40", features = ["full"] }
anyhow = "1.0"
chrono = "0.4"
num_cpus = "1.16"
rand = "0.8"
indicatif = "0.17"
colored = "2.1"
src/models.rs
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Serialize, Deserialize)]
pub struct BenchmarkResult {
pub test_name: String,
pub duration: Duration,
pub timestamp: String,
pub cpu_results: Option<CpuBenchmark>,
pub disk_results: Option<DiskBenchmark>,
pub network_results: Option<NetworkBenchmark>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CpuBenchmark {
pub single_thread_score: f64,
pub multi_thread_score: f64,
pub cores_used: usize,
pub operations_per_second: u64,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DiskBenchmark {
pub sequential_read_mbps: f64,
pub sequential_write_mbps: f64,
pub random_read_iops: f64,
pub random_write_iops: f64,
pub test_file_size_mb: usize,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct NetworkBenchmark {
pub latency_ms: f64,
pub download_mbps: f64,
pub upload_mbps: f64,
pub packet_loss_percent: f64,
pub test_host: String,
}
src/cpu_bench.rs
use anyhow::Result;
use std::time::Instant;
use crate::models::CpuBenchmark;
pub struct CpuBenchmarker {
cores: usize,
}
impl CpuBenchmarker {
pub fn new() -> Self {
Self {
cores: num_cpus::get(),
}
}
pub fn run_benchmark(&self, duration_secs: u64) -> Result<CpuBenchmark> {
println!("🔧 執行 CPU 基準測試...");
// 單執行緒測試
let single_score = self.run_single_thread_test(duration_secs)?;
// 多執行緒測試
let multi_score = self.run_multi_thread_test(duration_secs)?;
let ops_per_sec = (multi_score as u64) * 1_000_000;
Ok(CpuBenchmark {
single_thread_score: single_score,
multi_thread_score: multi_score,
cores_used: self.cores,
operations_per_second: ops_per_sec,
})
}
fn run_single_thread_test(&self, duration_secs: u64) -> Result<f64> {
let start = Instant::now();
let mut operations = 0u64;
while start.elapsed().as_secs() < duration_secs {
// 執行 CPU 密集運算:質數計算
operations += self.calculate_primes(10000) as u64;
}
let elapsed = start.elapsed().as_secs_f64();
Ok(operations as f64 / elapsed)
}
fn run_multi_thread_test(&self, duration_secs: u64) -> Result<f64> {
use std::sync::{Arc, Mutex};
use std::thread;
let operations = Arc::new(Mutex::new(0u64));
let mut handles = vec![];
let start = Instant::now();
for _ in 0..self.cores {
let ops = Arc::clone(&operations);
let duration = duration_secs;
let handle = thread::spawn(move || {
let thread_start = Instant::now();
let mut local_ops = 0u64;
while thread_start.elapsed().as_secs() < duration {
local_ops += Self::calculate_primes_static(10000) as u64;
}
let mut ops = ops.lock().unwrap();
*ops += local_ops;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let elapsed = start.elapsed().as_secs_f64();
let total_ops = *operations.lock().unwrap();
Ok(total_ops as f64 / elapsed)
}
fn calculate_primes(&self, limit: usize) -> usize {
Self::calculate_primes_static(limit)
}
fn calculate_primes_static(limit: usize) -> usize {
let mut primes = vec![true; limit];
let mut count = 0;
for i in 2..limit {
if primes[i] {
count += 1;
let mut j = i * i;
while j < limit {
primes[j] = false;
j += i;
}
}
}
count
}
}
src/disk_bench.rs
use anyhow::Result;
use rand::Rng;
use std::fs::{File, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::time::Instant;
use crate::models::DiskBenchmark;
pub struct DiskBenchmarker {
test_file_path: String,
file_size_mb: usize,
}
impl DiskBenchmarker {
pub fn new(test_file_path: String, file_size_mb: usize) -> Self {
Self {
test_file_path,
file_size_mb,
}
}
pub fn run_benchmark(&self) -> Result<DiskBenchmark> {
println!("💾 執行磁碟基準測試...");
// 順序寫入測試
let seq_write = self.test_sequential_write()?;
// 順序讀取測試
let seq_read = self.test_sequential_read()?;
// 隨機寫入測試
let rand_write = self.test_random_write()?;
// 隨機讀取測試
let rand_read = self.test_random_read()?;
// 清理測試檔案
std::fs::remove_file(&self.test_file_path).ok();
Ok(DiskBenchmark {
sequential_read_mbps: seq_read,
sequential_write_mbps: seq_write,
random_read_iops: rand_read,
random_write_iops: rand_write,
test_file_size_mb: self.file_size_mb,
})
}
fn test_sequential_write(&self) -> Result<f64> {
let mut file = File::create(&self.test_file_path)?;
let buffer = vec![0u8; 1024 * 1024]; // 1MB buffer
let start = Instant::now();
for _ in 0..self.file_size_mb {
file.write_all(&buffer)?;
}
file.sync_all()?;
let elapsed = start.elapsed().as_secs_f64();
Ok(self.file_size_mb as f64 / elapsed)
}
fn test_sequential_read(&self) -> Result<f64> {
let mut file = File::open(&self.test_file_path)?;
let mut buffer = vec![0u8; 1024 * 1024];
let start = Instant::now();
loop {
let bytes_read = file.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
}
let elapsed = start.elapsed().as_secs_f64();
Ok(self.file_size_mb as f64 / elapsed)
}
fn test_random_write(&self) -> Result<f64> {
let mut file = OpenOptions::new()
.write(true)
.open(&self.test_file_path)?;
let buffer = vec![0u8; 4096]; // 4KB blocks
let mut rng = rand::thread_rng();
let max_offset = (self.file_size_mb * 1024 * 1024) - 4096;
let operations = 1000;
let start = Instant::now();
for _ in 0..operations {
let offset = rng.gen_range(0..max_offset) as u64;
file.seek(SeekFrom::Start(offset))?;
file.write_all(&buffer)?;
}
file.sync_all()?;
let elapsed = start.elapsed().as_secs_f64();
Ok(operations as f64 / elapsed)
}
fn test_random_read(&self) -> Result<f64> {
let mut file = File::open(&self.test_file_path)?;
let mut buffer = vec![0u8; 4096];
let mut rng = rand::thread_rng();
let max_offset = (self.file_size_mb * 1024 * 1024) - 4096;
let operations = 1000;
let start = Instant::now();
for _ in 0..operations {
let offset = rng.gen_range(0..max_offset) as u64;
file.seek(SeekFrom::Start(offset))?;
file.read_exact(&mut buffer)?;
}
let elapsed = start.elapsed().as_secs_f64();
Ok(operations as f64 / elapsed)
}
}
src/network_bench.rs
use anyhow::Result;
use std::time::Instant;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::time::{sleep, Duration};
use crate::models::NetworkBenchmark;
pub struct NetworkBenchmarker {
test_host: String,
}
impl NetworkBenchmarker {
pub fn new(test_host: String) -> Self {
Self { test_host }
}
pub async fn run_benchmark(&self) -> Result<NetworkBenchmark> {
println!("🌐 執行網路基準測試...");
// 延遲測試
let latency = self.test_latency().await?;
// 下載速度測試 (模擬)
let download_speed = self.test_download_speed().await?;
// 上傳速度測試 (模擬)
let upload_speed = self.test_upload_speed().await?;
// 封包遺失測試
let packet_loss = self.test_packet_loss().await?;
Ok(NetworkBenchmark {
latency_ms: latency,
download_mbps: download_speed,
upload_mbps: upload_speed,
packet_loss_percent: packet_loss,
test_host: self.test_host.clone(),
})
}
async fn test_latency(&self) -> Result<f64> {
let mut total_latency = 0.0;
let tests = 10;
for _ in 0..tests {
let start = Instant::now();
// 嘗試連接到目標主機
match TcpStream::connect(&self.test_host).await {
Ok(_) => {
let latency = start.elapsed().as_secs_f64() * 1000.0;
total_latency += latency;
}
Err(_) => {
// 如果連接失敗,使用預設延遲
total_latency += 50.0;
}
}
sleep(Duration::from_millis(100)).await;
}
Ok(total_latency / tests as f64)
}
async fn test_download_speed(&self) -> Result<f64> {
// 這是一個簡化的模擬測試
// 實際應用中應該連接到測速伺服器
match TcpStream::connect(&self.test_host).await {
Ok(mut stream) => {
let mut buffer = vec![0u8; 8192];
let mut total_bytes = 0;
let start = Instant::now();
let test_duration = Duration::from_secs(3);
while start.elapsed() < test_duration {
match stream.read(&mut buffer).await {
Ok(n) if n > 0 => total_bytes += n,
_ => break,
}
}
let elapsed = start.elapsed().as_secs_f64();
let mbps = (total_bytes as f64 * 8.0) / (elapsed * 1_000_000.0);
Ok(mbps)
}
Err(_) => Ok(0.0),
}
}
async fn test_upload_speed(&self) -> Result<f64> {
match TcpStream::connect(&self.test_host).await {
Ok(mut stream) => {
let buffer = vec![0u8; 8192];
let mut total_bytes = 0;
let start = Instant::now();
let test_duration = Duration::from_secs(3);
while start.elapsed() < test_duration {
match stream.write(&buffer).await {
Ok(n) => total_bytes += n,
Err(_) => break,
}
}
let elapsed = start.elapsed().as_secs_f64();
let mbps = (total_bytes as f64 * 8.0) / (elapsed * 1_000_000.0);
Ok(mbps)
}
Err(_) => Ok(0.0),
}
}
async fn test_packet_loss(&self) -> Result<f64> {
let total_tests = 20;
let mut failed = 0;
for _ in 0..total_tests {
if TcpStream::connect(&self.test_host).await.is_err() {
failed += 1;
}
sleep(Duration::from_millis(50)).await;
}
Ok((failed as f64 / total_tests as f64) * 100.0)
}
}
src/reporter.rs
use anyhow::Result;
use chrono::Local;
use colored::*;
use std::fs::File;
use std::io::Write;
use crate::models::BenchmarkResult;
pub struct Reporter;
impl Reporter {
pub fn print_results(result: &BenchmarkResult) {
println!("\n{}", "═══════════════════════════════════════".blue().bold());
println!("{}", " 系統效能基準測試報告".blue().bold());
println!("{}", "═══════════════════════════════════════".blue().bold());
println!("\n📊 測試資訊:");
println!(" 測試名稱: {}", result.test_name);
println!(" 測試時間: {}", result.timestamp);
println!(" 總耗時: {:.2}秒", result.duration.as_secs_f64());
if let Some(cpu) = &result.cpu_results {
println!("\n🔧 CPU 測試結果:");
println!(" 單執行緒分數: {:.2}", cpu.single_thread_score);
println!(" 多執行緒分數: {:.2}", cpu.multi_thread_score);
println!(" 使用核心數: {}", cpu.cores_used);
println!(" 每秒運算次數: {}", cpu.operations_per_second);
let speedup = cpu.multi_thread_score / cpu.single_thread_score;
let efficiency = (speedup / cpu.cores_used as f64) * 100.0;
println!(" 多核加速比: {:.2}x", speedup);
println!(" 並行效率: {:.1}%", efficiency);
}
if let Some(disk) = &result.disk_results {
println!("\n💾 磁碟測試結果:");
println!(" 順序讀取: {:.2} MB/s", disk.sequential_read_mbps);
println!(" 順序寫入: {:.2} MB/s", disk.sequential_write_mbps);
println!(" 隨機讀取: {:.2} IOPS", disk.random_read_iops);
println!(" 隨機寫入: {:.2} IOPS", disk.random_write_iops);
println!(" 測試檔案大小: {} MB", disk.test_file_size_mb);
}
if let Some(net) = &result.network_results {
println!("\n🌐 網路測試結果:");
println!(" 測試主機: {}", net.test_host);
println!(" 延遲: {:.2} ms", net.latency_ms);
println!(" 下載速度: {:.2} Mbps", net.download_mbps);
println!(" 上傳速度: {:.2} Mbps", net.upload_mbps);
println!(" 封包遺失率: {:.2}%", net.packet_loss_percent);
}
println!("\n{}", "═══════════════════════════════════════".blue().bold());
}
pub fn save_to_file(result: &BenchmarkResult, filename: &str) -> Result<()> {
let json = serde_json::to_string_pretty(result)?;
let mut file = File::create(filename)?;
file.write_all(json.as_bytes())?;
println!("\n✅ 測試結果已儲存至: {}", filename.green());
Ok(())
}
pub fn generate_markdown_report(result: &BenchmarkResult, filename: &str) -> Result<()> {
let mut content = String::new();
content.push_str(&format!("# 系統效能基準測試報告\n\n"));
content.push_str(&format!("**測試時間:** {}\n\n", result.timestamp));
content.push_str(&format!("**測試名稱:** {}\n\n", result.test_name));
if let Some(cpu) = &result.cpu_results {
content.push_str("## CPU 測試結果\n\n");
content.push_str("| 項目 | 數值 |\n");
content.push_str("|------|------|\n");
content.push_str(&format!("| 單執行緒分數 | {:.2} |\n", cpu.single_thread_score));
content.push_str(&format!("| 多執行緒分數 | {:.2} |\n", cpu.multi_thread_score));
content.push_str(&format!("| 使用核心數 | {} |\n", cpu.cores_used));
content.push_str(&format!("| 每秒運算次數 | {} |\n\n", cpu.operations_per_second));
}
if let Some(disk) = &result.disk_results {
content.push_str("## 磁碟測試結果\n\n");
content.push_str("| 項目 | 數值 |\n");
content.push_str("|------|------|\n");
content.push_str(&format!("| 順序讀取 | {:.2} MB/s |\n", disk.sequential_read_mbps));
content.push_str(&format!("| 順序寫入 | {:.2} MB/s |\n", disk.sequential_write_mbps));
content.push_str(&format!("| 隨機讀取 | {:.2} IOPS |\n", disk.random_read_iops));
content.push_str(&format!("| 隨機寫入 | {:.2} IOPS |\n\n", disk.random_write_iops));
}
if let Some(net) = &result.network_results {
content.push_str("## 網路測試結果\n\n");
content.push_str("| 項目 | 數值 |\n");
content.push_str("|------|------|\n");
content.push_str(&format!("| 測試主機 | {} |\n", net.test_host));
content.push_str(&format!("| 延遲 | {:.2} ms |\n", net.latency_ms));
content.push_str(&format!("| 下載速度 | {:.2} Mbps |\n", net.download_mbps));
content.push_str(&format!("| 上傳速度 | {:.2} Mbps |\n", net.upload_mbps));
content.push_str(&format!("| 封包遺失率 | {:.2}% |\n\n", net.packet_loss_percent));
}
let mut file = File::create(filename)?;
file.write_all(content.as_bytes())?;
println!("✅ Markdown 報告已儲存至: {}", filename.green());
Ok(())
}
}
mod models;
mod cpu_bench;
mod disk_bench;
mod network_bench;
mod reporter;
use anyhow::Result;
use chrono::Local;
use clap::{Parser, Subcommand};
use std::time::Instant;
use models::BenchmarkResult;
use cpu_bench::CpuBenchmarker;
use disk_bench::DiskBenchmarker;
use network_bench::NetworkBenchmarker;
use reporter::Reporter;
#[derive(Parser)]
#[command(name = "system-benchmark")]
#[command(about = "系統效能基準測試工具", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// 執行 CPU 基準測試
Cpu {
/// 測試持續時間(秒)
#[arg(short, long, default_value = "5")]
duration: u64,
/// 輸出檔案名稱
#[arg(short, long)]
output: Option<String>,
},
/// 執行磁碟基準測試
Disk {
/// 測試檔案路徑
#[arg(short, long, default_value = "benchmark_test.dat")]
file: String,
/// 測試檔案大小(MB)
#[arg(short, long, default_value = "100")]
size: usize,
/// 輸出檔案名稱
#[arg(short, long)]
output: Option<String>,
},
/// 執行網路基準測試
Network {
/// 測試主機位址
#[arg(short, long, default_value = "www.google.com:80")]
host: String,
/// 輸出檔案名稱
#[arg(short, long)]
output: Option<String>,
},
/// 執行完整基準測試
Full {
/// CPU 測試持續時間(秒)
#[arg(long, default_value = "5")]
cpu_duration: u64,
/// 磁碟測試檔案大小(MB)
#[arg(long, default_value = "100")]
disk_size: usize,
/// 網路測試主機
#[arg(long, default_value = "www.google.com:80")]
network_host: String,
/// 輸出檔案名稱
#[arg(short, long)]
output: Option<String>,
/// 同時產生 Markdown 報告
#[arg(short, long)]
markdown: bool,
},
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Commands::Cpu { duration, output } => {
run_cpu_benchmark(duration, output).await?;
}
Commands::Disk { file, size, output } => {
run_disk_benchmark(file, size, output).await?;
}
Commands::Network { host, output } => {
run_network_benchmark(host, output).await?;
}
Commands::Full {
cpu_duration,
disk_size,
network_host,
output,
markdown,
} => {
run_full_benchmark(cpu_duration, disk_size, network_host, output, markdown).await?;
}
}
Ok(())
}
async fn run_cpu_benchmark(duration: u64, output: Option<String>) -> Result<()> {
let start = Instant::now();
let benchmarker = CpuBenchmarker::new();
let cpu_results = benchmarker.run_benchmark(duration)?;
let result = BenchmarkResult {
test_name: "CPU Benchmark".to_string(),
duration: start.elapsed(),
timestamp: Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
cpu_results: Some(cpu_results),
disk_results: None,
network_results: None,
};
Reporter::print_results(&result);
if let Some(filename) = output {
Reporter::save_to_file(&result, &filename)?;
}
Ok(())
}
async fn run_disk_benchmark(file: String, size: usize, output: Option<String>) -> Result<()> {
let start = Instant::now();
let benchmarker = DiskBenchmarker::new(file, size);
let disk_results = benchmarker.run_benchmark()?;
let result = BenchmarkResult {
test_name: "Disk Benchmark".to_string(),
duration: start.elapsed(),
timestamp: Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
cpu_results: None,
disk_results: Some(disk_results),
network_results: None,
};
Reporter::print_results(&result);
if let Some(filename) = output {
Reporter::save_to_file(&result, &filename)?;
}
Ok(())
}
async fn run_network_benchmark(host: String, output: Option<String>) -> Result<()> {
let start = Instant::now();
let benchmarker = NetworkBenchmarker::new(host);
let network_results = benchmarker.run_benchmark().await?;
let result = BenchmarkResult {
test_name: "Network Benchmark".to_string(),
duration: start.elapsed(),
timestamp: Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
cpu_results: None,
disk_results: None,
network_results: Some(network_results),
};
Reporter::print_results(&result);
if let Some(filename) = output {
Reporter::save_to_file(&result, &filename)?;
}
Ok(())
}
async fn run_full_benchmark(
cpu_duration: u64,
disk_size: usize,
network_host: String,
output: Option<String>,
markdown: bool,
) -> Result<()> {
println!("🚀 開始完整系統基準測試...\n");
let start = Instant::now();
// CPU 測試
let cpu_benchmarker = CpuBenchmarker::new();
let cpu_results = cpu_benchmarker.run_benchmark(cpu_duration)?;
// 磁碟測試
let disk_benchmarker = DiskBenchmarker::new("benchmark_test.dat".to_string(), disk_size);
let disk_results = disk_benchmarker.run_benchmark()?;
// 網路測試
let network_benchmarker = NetworkBenchmarker::new(network_host);
let network_results = network_benchmarker.run_benchmark().await?;
let result = BenchmarkResult {
test_name: "Full System Benchmark".to_string(),
duration: start.elapsed(),
timestamp: Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
cpu_results: Some(cpu_results),
disk_results: Some(disk_results),
network_results: Some(network_results),
};
Reporter::print_results(&result);
if let Some(filename) = output {
Reporter::save_to_file(&result, &filename)?;
if markdown {
let md_filename = filename.replace(".json", ".md");
Reporter::generate_markdown_report(&result, &md_filename)?;
}
}Ok(())
}
## 使用範例
### 1. 執行 CPU 測試
```bash
# 基本 CPU 測試
cargo run -- cpu
# 指定測試時間並儲存結果
cargo run -- cpu --duration 10 --output cpu_results.json
# 基本磁碟測試
cargo run -- disk
# 自訂測試檔案和大小
cargo run -- disk --file /tmp/test.dat --size 500 --output disk_results.json
# 基本網路測試
cargo run -- network
# 測試特定主機
cargo run -- network --host "example.com:443" --output network_results.json
# 執行所有測試
cargo run -- full
# 完整測試並產生報告
cargo run -- full --output full_results.json --markdown
# 自訂所有參數
cargo run -- full \
--cpu-duration 10 \
--disk-size 200 \
--network-host "www.google.com:80" \
--output benchmark.json \
--markdown
仿間有各種 lib 其實可以實現個人目標,也可以視為值得參考的部分