Rust a été classé premier dans l'enquête Stack Overflow sur les développeurs comme « langage de programmation le plus apprécié » pendant plusieurs années consécutives, avec ses avantages principaux :

  • Abstractions à coût zéro : Les fonctionnalités de langage de haut niveau n'entraînent aucune perte de performance à l'exécution
  • Sécurité mémoire : Détection au moment de la compilation des courses de données et des fuites de mémoire
  • Compilation multiplateforme : Un seul code source compile en binaires natifs pour Linux, macOS, Windows
  • Excellent écosystème CLI : Les crates comme clap, tokio, serde rendent le développement CLI deux fois plus efficace avec la moitié de l'effort

Cet article vous guide de zéro pour construire un outil de gestion de serveurs Linux nommé sysmgr, couvrant les modules fonctionnels suivants :

  1. Collecte d'informations système (CPU, mémoire, disque, réseau)
  2. Gestion des processus (listage, terminaison, surveillance)
  3. Analyse de journaux (filtrage, statistiques, alertes)
  4. Opérations d'automatisation (tâches planifiées, opérations par lots)

1. Initialisation du projet et configuration des dépendances

1.1 Création du projet

BASH
cargo new sysmgr
cd sysmgr

1.2 Configuration de Cargo.toml

TOML
[package]
name = "sysmgr"
version = "0.1.0"
edition = "2021"

[dependencies]
# Framework CLI
clap = { version = "4.5", features = ["derive"] }
# Runtime asynchrone
tokio = { version = "1.40", features = ["full"] }
# Informations système
sysinfo = "0.31"
# Sérialisation
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# UI terminal
crossterm = "0.28"
# Expressions régulières
regex = "1.10"
# Gestion du temps
chrono = "0.4"
# Journalisation
tracing = "0.1"
tracing-subscriber = "0.3"
# Gestion des erreurs
anyhow = "1.0"
thiserror = "1.0"

1.3 Structure du projet

sysmgr/
├── Cargo.toml
├── src/
│   ├── main.rs          # Point d'entrée et routage des commandes
│   ├── cli.rs           # Définitions des arguments CLI
│   ├── commands/
│   │   ├── mod.rs
│   │   ├── info.rs      # Informations système
│   │   ├── process.rs   # Gestion des processus
│   │   ├── log.rs       # Analyse de journaux
│   │   └── cron.rs      # Tâches d'automatisation
│   └── utils/
│       ├── mod.rs
│       └── output.rs    # Formatage de sortie

2. Construction du framework CLI

2.1 Définition de la structure des commandes

Utilisation des macros derive de clap pour définir les commandes :

RUST
// src/cli.rs
use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(name = "sysmgr")]
#[command(about = "Outil de gestion de serveurs Linux", version)]
pub struct Cli {
    /// Activer la sortie détaillée
    #[arg(short, long, global = true)]
    pub verbose: bool,

    /// Format de sortie
    #[arg(short, long, default_value = "table", global = true)]
    pub format: OutputFormat,

    #[command(subcommand)]
    pub command: Commands,
}

#[derive(Subcommand)]
pub enum Commands {
    /// Afficher les informations système
    Info {
        /// Spécifier le type d'information
        #[arg(short, long)]
        section: Option<InfoSection>,
    },
    /// Gestion des processus
    Proc {
        #[command(subcommand)]
        action: ProcAction,
    },
    /// Analyse de journaux
    Log {
        /// Chemin du fichier journal
        #[arg(short, long)]
        file: String,
        /// Mot-clé de filtrage
        #[arg(short, long)]
        keyword: Option<String>,
        /// Calculer les statistiques d'erreurs
        #[arg(short, long)]
        stats: bool,
    },
    /// Tâches d'automatisation
    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 {
    /// Lister les processus
    List {
        /// Filtrer par nom
        #[arg(short, long)]
        name: Option<String>,
        /// Trier par utilisation CPU
        #[arg(long)]
        sort_by_cpu: bool,
    },
    /// Terminer le processus
    Kill {
        /// ID du processus
        pid: u32,
        /// Terminaison forcée
        #[arg(short, long)]
        force: bool,
    },
    /// Surveiller le processus
    Watch {
        /// Nom du processus
        name: String,
        /// Intervalle de rafraîchissement (secondes)
        #[arg(short, long, default_value = "2")]
        interval: u64,
    },
}

#[derive(Subcommand)]
pub enum CronAction {
    /// Lister les tâches planifiées
    List,
    /// Ajouter une tâche planifiée
    Add {
        /// Expression Cron
        schedule: String,
        /// Commande à exécuter
        command: String,
    },
    /// Supprimer une tâche planifiée
    Remove {
        /// ID de la tâche
        id: u32,
    },
}

2.2 Point d'entrée principal

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();

    // Initialiser la journalisation
    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. Module d'informations système

3.1 Implémentation de la collecte d'informations système

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!("=== Informations CPU ===");
            println!("Nombre de cœurs : {}", cpu_count);
            println!("Utilisation : {:.1}%", cpu_usage);
            if let Some(cpu) = sys.cpus().first() {
                println!("Modèle : {}", 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!("=== Informations mémoire ===");
            println!("Mémoire totale : {} MB", total / 1024 / 1024);
            println!("Utilisée : {} MB ({:.1}%)", used / 1024 / 1024, (used as f64 / total as f64) * 100.0);
            println!("Swap total : {} MB", total_swap / 1024 / 1024);
            println!("Swap utilisé : {} 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!("=== Informations disque ===");
            println!("{:<20} {:<10} {:<10} {:<10}", "Point de montage", "Système de fichiers", "Total", "Disponible");
            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!("=== Informations réseau ===");
            println!("{:<15} {:<15} {:<15}", "Interface", "Reçu", "Transmis");
            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 Test du module d'informations système

BASH
# Compiler
cargo build --release

# Voir toutes les informations système
./target/release/sysmgr info

# Voir uniquement les informations CPU
./target/release/sysmgr info --section cpu

# Sortie au format JSON
./target/release/sysmgr info --format json

# Voir les informations mémoire (JSON)
./target/release/sysmgr info --section memory --format json

4. Module de gestion des processus

4.1 Implémentation du listage des processus

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();

    // Filtrer
    if let Some(name) = name_filter {
        processes.retain(|p| p.name.to_lowercase().contains(&name.to_lowercase()));
    }

    // Trier
    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);
    }

    // Sortie
    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", "Nom", "CPU%", "Mémoire(MB)", "Statut", "Utilisateur");
            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!("\nTotal {} processus", 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!("Processus {} terminé de force ({})", pid, process.name().to_string_lossy());
        } else {
            // Envoyer SIGTERM
            #[cfg(unix)]
            {
                use std::process::Command;
                Command::new("kill").arg(pid.to_string()).output()?;
                println!("Signal TERM envoyé au processus {} ({})", pid, process.name().to_string_lossy());
            }
        }
    } else {
        println!("Processus {} non trouvé", pid);
    }

    Ok(())
}

async fn watch_process(name: String, interval: u64) -> Result<()> {
    use std::time::Duration;

    println!("Surveillance du processus : {} (rafraîchissement toutes les {} secondes, Ctrl+C pour arrêter)", 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();

        // Effacer l'écran
        print!("\x1B[2J\x1B[1;1H");
        println!("=== Moniteur de processus : {} ===", name);
        println!("Heure : {}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S"));
        println!();

        if matched.is_empty() {
            println!("Aucun processus correspondant à '{}' trouvé", name);
        } else {
            println!("{:<8} {:<25} {:<8} {:<10}", "PID", "Nom", "CPU%", "Mémoire(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 Test de la gestion des processus

BASH
# Lister tous les processus
./target/release/sysmgr proc list

# Trier par utilisation CPU
./target/release/sysmgr proc list --sort-by-cpu

# Filtrer un processus spécifique
./target/release/sysmgr proc list --name nginx

# Sortie au format JSON
./target/release/sysmgr proc list --format json

# Terminer un processus
./target/release/sysmgr proc kill 1234

# Terminer de force
./target/release/sysmgr proc kill 1234 --force

# Surveiller un processus
./target/release/sysmgr proc watch nginx --interval 5

5. Module d'analyse de journaux

5.1 Implémentation de l'analyse de journaux

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!("Veuillez spécifier l'argument --keyword ou --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(),
    };

    // Regex : correspondance des horodatages et niveaux de journal
    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;
                    // Extraire l'heure
                    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!("=== Statistiques de journal ===");
            println!("Lignes totales : {}", 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=== Distribution des erreurs par heure ===");
                let mut hours: Vec<_> = stats.errors_per_hour.iter().collect();
                hours.sort_by_key(|(h, _)| h.clone());
                for (hour, count) in hours {
                    println!("{}:00 - {} erreurs", 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{} lignes correspondantes trouvées", count);
    Ok(())
}

5.2 Test de l'analyse de journaux

BASH
# Analyser le journal
./target/release/sysmgr log --file /var/log/syslog --stats

# Statistiques au format JSON
./target/release/sysmgr log --file /var/log/syslog --stats --format json

# Filtrer par mot-clé
./target/release/sysmgr log --file /var/log/syslog --keyword "error"

# Filtrer et sortir en JSON
./target/release/sysmgr log --file /var/log/syslog --keyword "failed" --format json

6. Module de tâches d'automatisation

6.1 Implémentation de la gestion 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", "Planification", "Commande", "Statut");
                    println!("{}", "-".repeat(75));
                    for job in &config.jobs {
                        let status = if job.enabled { "Activé" } else { "Désactivé" };
                        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!("Tâche ID {} ajoutée", 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 de tâche non trouvé : {}", id);
            } else {
                save_config(&config)?;
                println!("Tâche ID {} supprimée", id);
            }
        }
    }
    Ok(())
}

6.2 Test des tâches d'automatisation

BASH
# Ajouter une tâche planifiée
./target/release/sysmgr cron add "0 */6 * * *" "/usr/bin/backup.sh"

# Lister toutes les tâches
./target/release/sysmgr cron list

# Liste au format JSON
./target/release/sysmgr cron list --format json

# Supprimer une tâche
./target/release/sysmgr cron remove 1

7. Fonctionnalités avancées

7.1 Compilation multiplateforme

BASH
# Compiler pour Linux x86_64
cargo build --release --target x86_64-unknown-linux-gnu

# Compiler pour Linux ARM64
cargo build --release --target aarch64-unknown-linux-gnu

# Compiler pour macOS
cargo build --release --target x86_64-apple-darwin

# Compiler pour Windows
cargo build --release --target x86_64-pc-windows-gnu

7.2 Liaison statique (éviter les problèmes de version de glibc)

BASH
# Utiliser musl pour la liaison statique
rustup target add x86_64-unknown-linux-musl
cargo build --release --target x86_64-unknown-linux-musl

# Le binaire généré peut s'exécuter sur n'importe quelle distribution Linux
ls -lh target/x86_64-unknown-linux-musl/release/sysmgr

7.3 Empaquetage et distribution

BASH
# Empaqueter en paquet Debian avec cargo-deb
cargo install cargo-deb
cargo deb

# Empaqueter en RPM avec cargo-generate-rpm
cargo install cargo-generate-rpm
cargo generate-rpm

# Générer un paquet de distribution tar.gz
tar -czvf sysmgr-0.1.0-linux-x86_64.tar.gz \
    -C target/release sysmgr

8. Optimisation des performances

8.1 Optimisation de la compilation

TOML
# Cargo.toml
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
panic = "abort"
strip = true

8.2 Optimisation du runtime

  • Utiliser mimalloc au lieu de l'allocateur par défaut
RUST
// src/main.rs
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
  • E/S asynchrones pour le traitement de grands journaux
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? {
        // Traiter chaque ligne
    }
    Ok(())
}

9. Conclusion

Cet article vous a guidé dans la construction d'un outil CLI de gestion de serveurs Linux entièrement fonctionnel en utilisant Rust, couvrant :

  1. Construction du framework CLI : Utilisation de clap pour définir une structure de commande claire
  2. Collecte d'informations système : Surveillance en temps réel du CPU, de la mémoire, du disque, du réseau
  3. Gestion des processus : Lister, filtrer, terminer, surveiller les processus
  4. Analyse de journaux : Filtrage, statistiques, analyse de distribution des erreurs
  5. Tâches d'automatisation : Gestion CRUD des tâches Cron
  6. Compilation multiplateforme : Un seul code source compilant en binaires multiplateformes
  7. Optimisation des performances : Liaison statique, LTO, mimalloc, etc.

Les avantages de Rust dans le développement d'outils de gestion de serveurs sont pleinement démontrés :

  • Sécurité : Prévention au moment de la compilation des courses de données et des problèmes de mémoire
  • Performance : Efficacité d'exécution comparable à C/C++
  • Portabilité : Cross-compilation facile et déploiement sur différentes architectures
  • Écosystème : Crates riches accélérant le développement

Ressources associées : - Documentation sysinfo - Documentation clap - Documentation tokio - Guide d'application CLI Rust