Rust は Stack Overflow の開発者調査で連続多年「最も愛されているプログラミング言語」のトップに位置しており、その核心的な強みは以下の通りです:
- ゼロコスト抽象化: 高水準な言語機能を使っても実行時のパフォーマンス劣化が発生しない
- メモリ安全性: コンパイル時にデータ競合やメモリリークを検出できる
- クロスプラットフォームコンパイル: 単一のソースコードから Linux、macOS、Windows 用のネイティブバイナリを生成
- 優れた CLI エコシステム:
clap、tokio、serdeなどの crate によって CLI 開発が大幅に効率化
本記事では、ゼロから sysmgr という名前の Linux サーバー管理ツールを構築し、以下の機能モジュールを実装します:
- システム情報収集(CPU、メモリ、ディスク、ネットワーク)
- プロセス管理(一覧表示、終了、監視)
- ログ分析(フィルタリング、集計、アラート)
- 自動化運用(定期タスク、一括操作)
1. プロジェクトの初期化と依存関係の設定
1.1 プロジェクトの作成
BASH
cargo new sysmgr
cd sysmgr
1.2 Cargo.toml の設定
TOML
[package]
name = "sysmgr"
version = "0.1.0"
edition = "2021"
[dependencies]
# CLI フレームワーク
clap = { version = "4.5", features = ["derive"] }
# 非同期ランタイム
tokio = { version = "1.40", features = ["full"] }
# システム情報
sysinfo = "0.31"
# シリアライゼーション
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# ターミナル UI
crossterm = "0.28"
# 正規表現
regex = "1.10"
# 日時処理
chrono = "0.4"
# ログ
tracing = "0.1"
tracing-subscriber = "0.3"
# エラーハンドリング
anyhow = "1.0"
thiserror = "1.0"
1.3 プロジェクト構成
sysmgr/
├── Cargo.toml
├── src/
│ ├── main.rs # エントリーポイントとコマンドルーティング
│ ├── cli.rs # CLI 引数の定義
│ ├── commands/
│ │ ├── mod.rs
│ │ ├── info.rs # システム情報
│ │ ├── process.rs # プロセス管理
│ │ ├── log.rs # ログ分析
│ │ └── cron.rs # 自動化タスク
│ └── utils/
│ ├── mod.rs
│ └── output.rs # 出力フォーマット
2. CLI フレームワークの構築
2.1 コマンド構造の定義
clap の derive マクロを使用してコマンドを定義します:
RUST
// src/cli.rs
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "sysmgr")]
#[command(about = "Linux サーバー管理ツール", version)]
pub struct Cli {
/// 詳細出力を有効にする
#[arg(short, long, global = true)]
pub verbose: bool,
/// 出力フォーマット
#[arg(short, long, default_value = "table", global = true)]
pub format: OutputFormat,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
/// システム情報を表示
Info {
/// 情報タイプを指定
#[arg(short, long)]
section: Option<InfoSection>,
},
/// プロセス管理
Proc {
#[command(subcommand)]
action: ProcAction,
},
/// ログ分析
Log {
/// ログファイルのパス
#[arg(short, long)]
file: String,
/// フィルタリングキーワード
#[arg(short, long)]
keyword: Option<String>,
/// エラーレベルの集計
#[arg(short, long)]
stats: bool,
},
/// 自動化タスク
Cron {
#[command(subcommand)]
action: CronAction,
},
}
#[derive(Clone, Debug, clap::ValueEnum)]
pub enum InfoSection {
Cpu,
Memory,
Disk,
Network,
All,
}
#[derive(Clone, Debug, clap::ValueEnum)]
pub enum OutputFormat {
Table,
Json,
Csv,
}
#[derive(Subcommand)]
pub enum ProcAction {
/// プロセスを一覧表示
List {
/// 名前でフィルタリング
#[arg(short, long)]
name: Option<String>,
/// CPU 使用率でソート
#[arg(long)]
sort_by_cpu: bool,
},
/// プロセスを終了
Kill {
/// プロセス ID
pid: u32,
/// 強制終了
#[arg(short, long)]
force: bool,
},
/// プロセスを監視
Watch {
/// プロセス名
name: String,
/// 更新間隔(秒)
#[arg(short, long, default_value = "2")]
interval: u64,
},
}
#[derive(Subcommand)]
pub enum CronAction {
/// 定期タスクを一覧表示
List,
/// 定期タスクを追加
Add {
/// Cron 式
schedule: String,
/// 実行するコマンド
command: String,
},
/// 定期タスクを削除
Remove {
/// タスク ID
id: u32,
},
}
2.2 メインエントリーポイント
RUST
// src/main.rs
mod cli;
mod commands;
mod utils;
use anyhow::Result;
use clap::Parser;
use cli::{Cli, Commands};
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
// ログの初期化
if cli.verbose {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.init();
}
match cli.command {
Commands::Info { section } => {
commands::info::run(section, &cli.format).await?;
}
Commands::Proc { action } => {
commands::process::run(action, &cli.format).await?;
}
Commands::Log { file, keyword, stats } => {
commands::log::run(&file, keyword.as_deref(), stats, &cli.format).await?;
}
Commands::Cron { action } => {
commands::cron::run(action, &cli.format).await?;
}
}
Ok(())
}
3. システム情報モジュール
3.1 システム情報収集の実装
RUST
// src/commands/info.rs
use crate::cli::{InfoSection, OutputFormat};
use anyhow::Result;
use sysinfo::System;
use serde::Serialize;
#[derive(Serialize)]
struct SystemInfo {
hostname: String,
os: String,
kernel: String,
uptime: u64,
cpu_count: usize,
cpu_usage: f32,
total_memory: u64,
used_memory: u64,
total_swap: u64,
used_swap: u64,
}
pub async fn run(section: Option<InfoSection>, format: &OutputFormat) -> Result<()> {
let mut sys = System::new_all();
sys.refresh_all();
let section = section.unwrap_or(InfoSection::All);
match section {
InfoSection::Cpu => print_cpu_info(&sys, format),
InfoSection::Memory => print_memory_info(&sys, format),
InfoSection::Disk => print_disk_info(format),
InfoSection::Network => print_network_info(format),
InfoSection::All => {
print_cpu_info(&sys, format);
println!();
print_memory_info(&sys, format);
println!();
print_disk_info(format);
println!();
print_network_info(format);
}
}
Ok(())
}
fn print_cpu_info(sys: &System, format: &OutputFormat) {
let cpu_count = sys.cpus().len();
let cpu_usage: f32 = sys.cpus().iter().map(|c| c.cpu_usage()).sum::<f32>() / cpu_count as f32;
match format {
OutputFormat::Json => {
let data = serde_json::json!({
"cpu_count": cpu_count,
"cpu_usage_percent": cpu_usage,
"cpu_brand": sys.cpus().first().map(|c| c.brand()).unwrap_or("Unknown"),
});
println!("{}", serde_json::to_string_pretty(&data).unwrap());
}
_ => {
println!("=== CPU 情報 ===");
println!("コア数: {}", cpu_count);
println!("使用率: {:.1}%", cpu_usage);
if let Some(cpu) = sys.cpus().first() {
println!("モデル: {}", cpu.brand());
}
}
}
}
fn print_memory_info(sys: &System, format: &OutputFormat) {
let total = sys.total_memory();
let used = sys.used_memory();
let total_swap = sys.total_swap();
let used_swap = sys.used_swap();
match format {
OutputFormat::Json => {
let data = serde_json::json!({
"total_memory_mb": total / 1024 / 1024,
"used_memory_mb": used / 1024 / 1024,
"memory_usage_percent": (used as f64 / total as f64) * 100.0,
"total_swap_mb": total_swap / 1024 / 1024,
"used_swap_mb": used_swap / 1024 / 1024,
});
println!("{}", serde_json::to_string_pretty(&data).unwrap());
}
_ => {
println!("=== メモリ情報 ===");
println!("総メモリ: {} MB", total / 1024 / 1024);
println!("使用済み: {} MB ({:.1}%)", used / 1024 / 1024, (used as f64 / total as f64) * 100.0);
println!("総スワップ: {} MB", total_swap / 1024 / 1024);
println!("スワップ使用量: {} MB", used_swap / 1024 / 1024);
}
}
}
fn print_disk_info(format: &OutputFormat) {
use sysinfo::Disks;
let disks = Disks::new_with_refreshed_list();
match format {
OutputFormat::Json => {
let data: Vec<_> = disks.iter().map(|d| {
serde_json::json!({
"mount_point": d.mount_point().to_string_lossy(),
"filesystem": d.file_system().to_string_lossy(),
"total_gb": d.total_space() / 1024 / 1024 / 1024,
"available_gb": d.available_space() / 1024 / 1024 / 1024,
})
}).collect();
println!("{}", serde_json::to_string_pretty(&data).unwrap());
}
_ => {
println!("=== ディスク情報 ===");
println!("{:<20} {:<10} {:<10} {:<10}", "マウントポイント", "ファイルシステム", "総容量", "空き容量");
for disk in disks.iter() {
println!("{:<20} {:<10} {:<8} GB {:<6} GB",
disk.mount_point().to_string_lossy(),
disk.file_system().to_string_lossy(),
disk.total_space() / 1024 / 1024 / 1024,
disk.available_space() / 1024 / 1024 / 1024);
}
}
}
}
fn print_network_info(format: &OutputFormat) {
use sysinfo::Networks;
let networks = Networks::new_with_refreshed_list();
match format {
OutputFormat::Json => {
let data: Vec<_> = networks.iter().map(|(name, data)| {
serde_json::json!({
"interface": name,
"received_mb": data.total_received() / 1024 / 1024,
"transmitted_mb": data.total_transmitted() / 1024 / 1024,
})
}).collect();
println!("{}", serde_json::to_string_pretty(&data).unwrap());
}
_ => {
println!("=== ネットワーク情報 ===");
println!("{:<15} {:<15} {:<15}", "インターフェース", "受信", "送信");
for (name, data) in networks.iter() {
println!("{:<15} {:<10} MB {:<10} MB",
name,
data.total_received() / 1024 / 1024,
data.total_transmitted() / 1024 / 1024);
}
}
}
}
3.2 システム情報モジュールのテスト
BASH
# コンパイル
cargo build --release
# すべてのシステム情報を表示
./target/release/sysmgr info
# CPU 情報のみ表示
./target/release/sysmgr info --section cpu
# JSON フォーマットで出力
./target/release/sysmgr info --format json
# メモリ情報を JSON で表示
./target/release/sysmgr info --section memory --format json
4. プロセス管理モジュール
4.1 プロセス一覧の実装
RUST
// src/commands/process.rs
use crate::cli::{ProcAction, OutputFormat};
use anyhow::Result;
use sysinfo::{System, Process, Pid};
use serde::Serialize;
#[derive(Serialize)]
struct ProcessInfo {
pid: u32,
name: String,
cpu_usage: f32,
memory_mb: u64,
status: String,
user: String,
}
pub async fn run(action: ProcAction, format: &OutputFormat) -> Result<()> {
match action {
ProcAction::List { name, sort_by_cpu } => {
list_processes(name, sort_by_cpu, format).await?;
}
ProcAction::Kill { pid, force } => {
kill_process(pid, force).await?;
}
ProcAction::Watch { name, interval } => {
watch_process(name, interval).await?;
}
}
Ok(())
}
async fn list_processes(
name_filter: Option<String>,
sort_by_cpu: bool,
format: &OutputFormat,
) -> Result<()> {
let mut sys = System::new_all();
sys.refresh_all();
let mut processes: Vec<ProcessInfo> = sys.processes().iter().map(|(pid, proc_)| {
ProcessInfo {
pid: pid.as_u32(),
name: proc_.name().to_string_lossy().to_string(),
cpu_usage: proc_.cpu_usage(),
memory_mb: proc_.memory() / 1024 / 1024,
status: format!("{:?}", proc_.status()),
user: proc_.user_id().map(|u| u.to_string()).unwrap_or_default(),
}
}).collect();
// フィルタリング
if let Some(name) = name_filter {
processes.retain(|p| p.name.to_lowercase().contains(&name.to_lowercase()));
}
// ソート
if sort_by_cpu {
processes.sort_by(|a, b| b.cpu_usage.partial_cmp(&a.cpu_usage).unwrap());
} else {
processes.sort_by_key(|p| p.pid);
}
// 出力
match format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&processes)?);
}
OutputFormat::Csv => {
println!("PID,NAME,CPU%,MEMORY_MB,STATUS,USER");
for p in &processes {
println!("{},{},{:.1},{},{},{}", p.pid, p.name, p.cpu_usage, p.memory_mb, p.status, p.user);
}
}
OutputFormat::Table => {
println!("{:<8} {:<25} {:<8} {:<10} {:<10} {}", "PID", "名前", "CPU%", "メモリ(MB)", "状態", "ユーザー");
println!("{}", "-".repeat(80));
for p in processes.iter().take(50) {
println!("{:<8} {:<25} {:<8.1} {:<10} {:<10} {}",
p.pid, truncate(&p.name, 24), p.cpu_usage, p.memory_mb, p.status, p.user);
}
println!("\n合計 {} 個のプロセス", processes.len());
}
}
Ok(())
}
fn truncate(s: &str, max_len: usize) -> String {
if s.len() > max_len {
format!("{}...", &s[..max_len - 3])
} else {
s.to_string()
}
}
async fn kill_process(pid: u32, force: bool) -> Result<()> {
let mut sys = System::new();
sys.refresh_processes(sysinfo::ProcessesToUpdate::All, true);
let pid = Pid::from(pid as usize);
if let Some(process) = sys.process(pid) {
if force {
process.kill();
println!("プロセス {} ({}) を強制終了しました", pid, process.name().to_string_lossy());
} else {
// SIGTERM を送信
#[cfg(unix)]
{
use std::process::Command;
Command::new("kill").arg(pid.to_string()).output()?;
println!("プロセス {} ({}) に TERM シグナルを送信しました", pid, process.name().to_string_lossy());
}
}
} else {
println!("プロセス {} が見つかりません", pid);
}
Ok(())
}
async fn watch_process(name: String, interval: u64) -> Result<()> {
use std::time::Duration;
println!("プロセスを監視中: {} ({} 秒ごとに更新、Ctrl+C で終了)", name, interval);
loop {
let mut sys = System::new();
sys.refresh_processes(sysinfo::ProcessesToUpdate::All, true);
let matched: Vec<_> = sys.processes().iter()
.filter(|(_, p)| p.name().to_string_lossy().to_lowercase().contains(&name.to_lowercase()))
.collect();
// 画面をクリア
print!("\x1B[2J\x1B[1;1H");
println!("=== プロセス監視: {} ===", name);
println!("時刻: {}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S"));
println!();
if matched.is_empty() {
println!("'{}' に一致するプロセスが見つかりません", name);
} else {
println!("{:<8} {:<25} {:<8} {:<10}", "PID", "名前", "CPU%", "メモリ(MB)");
println!("{}", "-".repeat(55));
for (pid, proc_) in &matched {
println!("{:<8} {:<25} {:<8.1} {:<10}",
pid, proc_.name().to_string_lossy(),
proc_.cpu_usage(),
proc_.memory() / 1024 / 1024);
}
}
tokio::time::sleep(Duration::from_secs(interval)).await;
}
}
4.2 プロセス管理のテスト
BASH
# すべてのプロセスを一覧表示
./target/release/sysmgr proc list
# CPU 使用率でソート
./target/release/sysmgr proc list --sort-by-cpu
# 特定のプロセスをフィルタリング
./target/release/sysmgr proc list --name nginx
# JSON フォーマットで出力
./target/release/sysmgr proc list --format json
# プロセスを終了
./target/release/sysmgr proc kill 1234
# 強制終了
./target/release/sysmgr proc kill 1234 --force
# プロセスを監視
./target/release/sysmgr proc watch nginx --interval 5
5. ログ分析モジュール
5.1 ログ分析の実装
RUST
// src/commands/log.rs
use crate::cli::OutputFormat;
use anyhow::Result;
use regex::Regex;
use serde::Serialize;
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader};
#[derive(Serialize)]
struct LogStats {
total_lines: usize,
error_count: usize,
warn_count: usize,
info_count: usize,
debug_count: usize,
errors_per_hour: HashMap<String, usize>,
}
pub async fn run(
file: &str,
keyword: Option<&str>,
stats: bool,
format: &OutputFormat,
) -> Result<()> {
let file = File::open(file)?;
let reader = BufReader::new(file);
if stats {
analyze_stats(reader, format)?;
} else if let Some(kw) = keyword {
filter_lines(reader, kw, format)?;
} else {
println!("--keyword または --stats パラメータを指定してください");
}
Ok(())
}
fn analyze_stats(reader: BufReader<File>, format: &OutputFormat) -> Result<()> {
let mut stats = LogStats {
total_lines: 0,
error_count: 0,
warn_count: 0,
info_count: 0,
debug_count: 0,
errors_per_hour: HashMap::new(),
};
// 正規表現:タイムスタンプとログレベルにマッチ
let re = Regex::new(r"(?P<timestamp>\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}).*(?P<level>ERROR|WARN|INFO|DEBUG)")?;
for line in reader.lines() {
let line = line?;
stats.total_lines += 1;
if let Some(caps) = re.captures(&line) {
let level = caps.name("level").unwrap().as_str();
let timestamp = caps.name("timestamp").unwrap().as_str();
match level {
"ERROR" => {
stats.error_count += 1;
// 時間を抽出
if let Some(hour) = timestamp.get(11..13) {
*stats.errors_per_hour.entry(hour.to_string()).or_insert(0) += 1;
}
}
"WARN" => stats.warn_count += 1,
"INFO" => stats.info_count += 1,
"DEBUG" => stats.debug_count += 1,
_ => {}
}
}
}
match format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&stats)?);
}
_ => {
println!("=== ログ統計 ===");
println!("総行数: {}", stats.total_lines);
println!("ERROR: {}", stats.error_count);
println!("WARN: {}", stats.warn_count);
println!("INFO: {}", stats.info_count);
println!("DEBUG: {}", stats.debug_count);
if !stats.errors_per_hour.is_empty() {
println!("\n=== 時間別エラー分布 ===");
let mut hours: Vec<_> = stats.errors_per_hour.iter().collect();
hours.sort_by_key(|(h, _)| h.clone());
for (hour, count) in hours {
println!("{}:00 - {} 件のエラー", hour, count);
}
}
}
}
Ok(())
}
fn filter_lines(reader: BufReader<File>, keyword: &str, format: &OutputFormat) -> Result<()> {
let re = Regex::new(&format!("(?i){}", regex::escape(keyword)))?;
let mut count = 0;
for line in reader.lines() {
let line = line?;
if re.is_match(&line) {
count += 1;
match format {
OutputFormat::Json => {
println!("{}", serde_json::json!({"line": count, "content": line}));
}
_ => {
println!("{}", line);
}
}
}
}
eprintln!("\n合計 {} 行が一致しました", count);
Ok(())
}
5.2 ログ分析のテスト
BASH
# ログの統計
./target/release/sysmgr log --file /var/log/syslog --stats
# JSON フォーマットで統計
./target/release/sysmgr log --file /var/log/syslog --stats --format json
# キーワードでフィルタリング
./target/release/sysmgr log --file /var/log/syslog --keyword "error"
# フィルタリングして JSON で出力
./target/release/sysmgr log --file /var/log/syslog --keyword "failed" --format json
6. 自動化タスクモジュール
6.1 Cron 管理の実装
RUST
// src/commands/cron.rs
use crate::cli::{CronAction, OutputFormat};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
#[derive(Serialize, Deserialize, Debug)]
struct CronJob {
id: u32,
schedule: String,
command: String,
enabled: bool,
}
#[derive(Serialize, Deserialize, Debug, Default)]
struct CronConfig {
jobs: Vec<CronJob>,
}
fn config_path() -> PathBuf {
let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
PathBuf::from(home).join(".sysmgr").join("cron.json")
}
fn load_config() -> Result<CronConfig> {
let path = config_path();
if !path.exists() {
return Ok(CronConfig::default());
}
let content = fs::read_to_string(path)?;
Ok(serde_json::from_str(&content)?)
}
fn save_config(config: &CronConfig) -> Result<()> {
let path = config_path();
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(path, serde_json::to_string_pretty(config)?)?;
Ok(())
}
pub async fn run(action: CronAction, format: &OutputFormat) -> Result<()> {
match action {
CronAction::List => {
let config = load_config()?;
match format {
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(&config.jobs)?);
}
_ => {
println!("{:<5} {:<20} {:<40} {:<8}", "ID", "スケジュール", "コマンド", "状態");
println!("{}", "-".repeat(75));
for job in &config.jobs {
let status = if job.enabled { "有効" } else { "無効" };
println!("{:<5} {:<20} {:<40} {:<8}", job.id, job.schedule, job.command, status);
}
}
}
}
CronAction::Add { schedule, command } => {
let mut config = load_config()?;
let new_id = config.jobs.iter().map(|j| j.id).max().unwrap_or(0) + 1;
config.jobs.push(CronJob {
id: new_id,
schedule,
command,
enabled: true,
});
save_config(&config)?;
println!("タスク ID {} を追加しました", new_id);
}
CronAction::Remove { id } => {
let mut config = load_config()?;
let initial_len = config.jobs.len();
config.jobs.retain(|j| j.id != id);
if config.jobs.len() == initial_len {
println!("タスク ID {} が見つかりません", id);
} else {
save_config(&config)?;
println!("タスク ID {} を削除しました", id);
}
}
}
Ok(())
}
6.2 自動化タスクのテスト
BASH
# 定期タスクを追加
./target/release/sysmgr cron add "0 */6 * * *" "/usr/bin/backup.sh"
# すべてのタスクを一覧表示
./target/release/sysmgr cron list
# JSON フォーマットで一覧表示
./target/release/sysmgr cron list --format json
# タスクを削除
./target/release/sysmgr cron remove 1
7. 高度な機能
7.1 クロスプラットフォームコンパイル
BASH
# Linux x86_64 用にコンパイル
cargo build --release --target x86_64-unknown-linux-gnu
# Linux ARM64 用にコンパイル
cargo build --release --target aarch64-unknown-linux-gnu
# macOS 用にコンパイル
cargo build --release --target x86_64-apple-darwin
# Windows 用にコンパイル
cargo build --release --target x86_64-pc-windows-gnu
7.2 静的リンク(glibc バージョン問題を回避)
BASH
# musl を使用して静的リンク
rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl
# 生成されたバイナリは任意の Linux ディストリビューションで動作します
ls -lh target/x86_64-unknown-linux-musl/release/sysmgr
7.3 パッケージングと配布
BASH
# cargo-deb を使用して Debian パッケージを作成
cargo install cargo-deb
cargo deb
# cargo-generate-rpm を使用して RPM パッケージを作成
cargo install cargo-generate-rpm
cargo generate-rpm
# tar.gz 配布パッケージを生成
tar -czvf sysmgr-0.1.0-linux-x86_64.tar.gz \
-C target/release sysmgr
8. パフォーマンス最適化
8.1 コンパイルの最適化
TOML
# Cargo.toml
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
panic = "abort"
strip = true
8.2 ランタイムの最適化
- デフォルトアロケータの代わりに
mimallocを使用
RUST
// src/main.rs
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
- 大量のログを処理するための非同期 I/O
RUST
use tokio::io::AsyncBufReadExt;
async fn process_large_log(path: &str) -> Result<()> {
let file = tokio::fs::File::open(path).await?;
let reader = tokio::io::BufReader::new(file);
let mut lines = reader.lines();
while let Some(line) = lines.next_line().await? {
// 各行を処理
}
Ok(())
}
9. まとめ
本記事では、Rust を使用して機能豊富な Linux サーバー管理 CLI ツールを構築しました。以下の内容を含んでいます:
- CLI フレームワークの構築:
clapを使用して明確なコマンド構造を定義 - システム情報収集: CPU、メモリ、ディスク、ネットワークのリアルタイム監視
- プロセス管理: プロセスの一覧表示、フィルタリング、終了、監視
- ログ分析: フィルタリング、集計、エラー分布分析
- 自動化タスク: Cron タスクの CRUD 管理
- クロスプラットフォームコンパイル: 単一のソースコードから複数プラットフォームのバイナリを生成
- パフォーマンス最適化: 静的リンク、LTO、mimalloc など
Rust の強みはサーバー管理ツールの開発において遺憾なく発揮されます:
- 安全性: コンパイル時にデータ競合やメモリ問題を回避
- パフォーマンス: C/C++ と同等の実行効率
- 移植性: クロスコンパイルで異なるアーキテクチャに簡単にデプロイ
- エコシステム: 豊富な crate により開発を加速
関連リソース: - sysinfo ドキュメント - clap ドキュメント - tokio ドキュメント - Rust CLI アプリケーションガイド