Monitor de Processos do Sistema

Construa um monitor de processos em Rust que exibe CPU, memoria e arvore de processos com interface colorida no terminal.

Entender quais processos estao consumindo recursos no sistema e uma habilidade essencial para qualquer desenvolvedor ou administrador de sistemas. Ferramentas como htop e top fazem exatamente isso, e neste projeto vamos construir nossa propria versao em Rust. O monitor de processos que vamos criar lista processos ativos, mostra consumo de CPU e memoria, e permite filtrar e ordenar os resultados.

Este projeto utiliza a crate sysinfo para acessar informacoes do sistema operacional e demonstra como construir interfaces de terminal informativas e visualmente agradaveis.

O Que Vamos Construir

  • Listagem de processos com PID, nome, CPU e memoria
  • Ordenacao por CPU, memoria ou nome
  • Filtragem por nome do processo
  • Informacoes gerais do sistema (CPU total, memoria total)
  • Atualizacao periodica em modo continuo
  • Saida formatada com cores e alinhamento

Estrutura do Projeto

process-monitor/
├── Cargo.toml
└── src/
    ├── main.rs
    ├── sistema.rs
    └── formatador.rs

Configurando o Projeto

cargo new process-monitor
cd process-monitor

Edite o Cargo.toml:

[package]
name = "process-monitor"
version = "0.1.0"
edition = "2021"

[dependencies]
sysinfo = "0.32"
clap = { version = "4.5", features = ["derive"] }
colored = "2.1"

Passo 1: Coleta de Informacoes do Sistema

O modulo de sistema encapsula toda a interacao com a crate sysinfo. Crie src/sistema.rs:

use sysinfo::{Pid, Process, System};
use std::collections::HashMap;

/// Informacoes resumidas de um processo.
#[derive(Debug, Clone)]
pub struct InfoProcesso {
    pub pid: u32,
    pub nome: String,
    pub uso_cpu: f32,
    pub memoria_mb: f64,
    pub status: String,
    pub pid_pai: Option<u32>,
}

/// Informacoes gerais do sistema.
#[derive(Debug)]
pub struct InfoSistema {
    pub nome_so: String,
    pub versao_kernel: String,
    pub memoria_total_gb: f64,
    pub memoria_usada_gb: f64,
    pub cpus: usize,
    pub uso_cpu_total: f32,
}

/// Gerenciador que coleta informacoes do sistema e processos.
pub struct Monitor {
    sistema: System,
}

impl Monitor {
    pub fn new() -> Self {
        let mut sistema = System::new_all();
        // Atualizar duas vezes para obter dados de CPU precisos
        sistema.refresh_all();
        std::thread::sleep(std::time::Duration::from_millis(200));
        sistema.refresh_all();

        Monitor { sistema }
    }

    /// Atualiza as informacoes do sistema.
    pub fn atualizar(&mut self) {
        self.sistema.refresh_all();
    }

    /// Retorna informacoes gerais do sistema.
    pub fn info_sistema(&self) -> InfoSistema {
        let uso_cpu_total = self.sistema.global_cpu_usage();

        InfoSistema {
            nome_so: System::name().unwrap_or_else(|| "Desconhecido".to_string()),
            versao_kernel: System::kernel_version()
                .unwrap_or_else(|| "Desconhecida".to_string()),
            memoria_total_gb: self.sistema.total_memory() as f64 / 1_073_741_824.0,
            memoria_usada_gb: self.sistema.used_memory() as f64 / 1_073_741_824.0,
            cpus: self.sistema.cpus().len(),
            uso_cpu_total,
        }
    }

    /// Retorna a lista de processos ativos.
    pub fn listar_processos(&self) -> Vec<InfoProcesso> {
        let processos: &HashMap<Pid, Process> = self.sistema.processes();

        processos
            .iter()
            .map(|(pid, proc_info)| {
                let pid_pai = proc_info.parent().map(|p| p.as_u32());

                InfoProcesso {
                    pid: pid.as_u32(),
                    nome: proc_info.name().to_string_lossy().to_string(),
                    uso_cpu: proc_info.cpu_usage(),
                    memoria_mb: proc_info.memory() as f64 / 1_048_576.0,
                    status: format!("{:?}", proc_info.status()),
                    pid_pai,
                }
            })
            .collect()
    }
}

O Monitor encapsula a crate sysinfo e fornece metodos simples para obter informacoes do sistema e listar processos. A primeira atualizacao requer um pequeno atraso para que os dados de uso de CPU sejam calculados corretamente.

Passo 2: Formatador de Saida

O formatador transforma os dados coletados em uma apresentacao visual agradavel. Crie src/formatador.rs:

use crate::sistema::{InfoProcesso, InfoSistema};
use colored::*;

/// Exibe o cabecalho com informacoes do sistema.
pub fn exibir_info_sistema(info: &InfoSistema) {
    // Limpar tela
    print!("\x1B[2J\x1B[1;1H");

    println!("{}", "=== Monitor de Processos ===".green().bold());
    println!(
        "SO: {} | Kernel: {} | CPUs: {}",
        info.nome_so.cyan(),
        info.versao_kernel.cyan(),
        info.cpus.to_string().cyan()
    );

    // Barra de uso de CPU
    let barra_cpu = criar_barra(info.uso_cpu_total as f64, 100.0, 30);
    println!(
        "CPU:     {} {:.1}%",
        barra_cpu,
        info.uso_cpu_total
    );

    // Barra de uso de memoria
    let porcentagem_mem = (info.memoria_usada_gb / info.memoria_total_gb) * 100.0;
    let barra_mem = criar_barra(info.memoria_usada_gb, info.memoria_total_gb, 30);
    println!(
        "Memoria: {} {:.1}/{:.1} GB ({:.1}%)",
        barra_mem,
        info.memoria_usada_gb,
        info.memoria_total_gb,
        porcentagem_mem
    );
    println!();
}

/// Cria uma barra visual de progresso.
fn criar_barra(valor: f64, maximo: f64, largura: usize) -> String {
    let porcentagem = (valor / maximo).min(1.0);
    let preenchido = (porcentagem * largura as f64) as usize;
    let vazio = largura - preenchido;

    let barra_texto = format!(
        "[{}{}]",
        "#".repeat(preenchido),
        " ".repeat(vazio)
    );

    if porcentagem > 0.8 {
        barra_texto.red().to_string()
    } else if porcentagem > 0.5 {
        barra_texto.yellow().to_string()
    } else {
        barra_texto.green().to_string()
    }
}

/// Exibe a tabela de processos formatada.
pub fn exibir_processos(processos: &[InfoProcesso], limite: usize) {
    // Cabecalho da tabela
    println!(
        "{:>7} {:<25} {:>8} {:>10} {:>10}",
        "PID".white().bold(),
        "NOME".white().bold(),
        "CPU %".white().bold(),
        "MEM (MB)".white().bold(),
        "STATUS".white().bold(),
    );
    println!("{}", "-".repeat(65).dimmed());

    // Exibir processos (limitado ao numero solicitado)
    for proc in processos.iter().take(limite) {
        let cpu_colorida = if proc.uso_cpu > 50.0 {
            format!("{:>7.1}", proc.uso_cpu).red().bold().to_string()
        } else if proc.uso_cpu > 10.0 {
            format!("{:>7.1}", proc.uso_cpu).yellow().to_string()
        } else {
            format!("{:>7.1}", proc.uso_cpu).to_string()
        };

        let mem_colorida = if proc.memoria_mb > 500.0 {
            format!("{:>9.1}", proc.memoria_mb).red().to_string()
        } else if proc.memoria_mb > 100.0 {
            format!("{:>9.1}", proc.memoria_mb).yellow().to_string()
        } else {
            format!("{:>9.1}", proc.memoria_mb).to_string()
        };

        let nome_truncado = if proc.nome.len() > 24 {
            format!("{}...", &proc.nome[..21])
        } else {
            proc.nome.clone()
        };

        println!(
            "{:>7} {:<25} {} {} {:>10}",
            proc.pid,
            nome_truncado.cyan(),
            cpu_colorida,
            mem_colorida,
            proc.status.dimmed(),
        );
    }

    println!("{}", "-".repeat(65).dimmed());
}

O formatador cria uma interface visual no terminal com barras de progresso para CPU e memoria, e uma tabela de processos com cores indicando niveis de consumo. Processos que consomem muita CPU ou memoria aparecem destacados em vermelho.

Passo 3: Juntando Tudo no main.rs

Crie src/main.rs:

mod formatador;
mod sistema;

use clap::Parser;
use sistema::Monitor;
use std::time::Duration;

/// Monitor de processos do sistema
#[derive(Parser)]
#[command(name = "process-monitor")]
#[command(about = "Monitora processos do sistema com informacoes de CPU e memoria")]
struct Argumentos {
    /// Numero maximo de processos para exibir
    #[arg(short, long, default_value = "20")]
    limite: usize,

    /// Ordenar por: cpu, memoria, nome, pid
    #[arg(short, long, default_value = "cpu")]
    ordenar: String,

    /// Filtrar processos por nome (contem o texto)
    #[arg(short, long)]
    filtrar: Option<String>,

    /// Intervalo de atualizacao em segundos (0 = executar uma vez)
    #[arg(short = 'r', long, default_value = "2")]
    intervalo: u64,
}

/// Criterio de ordenacao dos processos.
enum CriterioOrdem {
    Cpu,
    Memoria,
    Nome,
    Pid,
}

impl CriterioOrdem {
    fn from_str(s: &str) -> Self {
        match s.to_lowercase().as_str() {
            "memoria" | "mem" | "memory" => CriterioOrdem::Memoria,
            "nome" | "name" => CriterioOrdem::Nome,
            "pid" => CriterioOrdem::Pid,
            _ => CriterioOrdem::Cpu,
        }
    }
}

fn main() {
    let args = Argumentos::parse();
    let criterio = CriterioOrdem::from_str(&args.ordenar);
    let mut monitor = Monitor::new();

    loop {
        monitor.atualizar();

        // Coletar informacoes do sistema
        let info_sistema = monitor.info_sistema();
        formatador::exibir_info_sistema(&info_sistema);

        // Coletar e processar lista de processos
        let mut processos = monitor.listar_processos();

        // Aplicar filtro por nome se especificado
        if let Some(ref termo) = args.filtrar {
            let termo_lower = termo.to_lowercase();
            processos.retain(|p| p.nome.to_lowercase().contains(&termo_lower));
        }

        // Ordenar conforme criterio escolhido
        match criterio {
            CriterioOrdem::Cpu => {
                processos.sort_by(|a, b| {
                    b.uso_cpu
                        .partial_cmp(&a.uso_cpu)
                        .unwrap_or(std::cmp::Ordering::Equal)
                });
            }
            CriterioOrdem::Memoria => {
                processos.sort_by(|a, b| {
                    b.memoria_mb
                        .partial_cmp(&a.memoria_mb)
                        .unwrap_or(std::cmp::Ordering::Equal)
                });
            }
            CriterioOrdem::Nome => {
                processos.sort_by(|a, b| a.nome.to_lowercase().cmp(&b.nome.to_lowercase()));
            }
            CriterioOrdem::Pid => {
                processos.sort_by_key(|p| p.pid);
            }
        }

        // Exibir tabela de processos
        formatador::exibir_processos(&processos, args.limite);
        println!(
            "\nTotal: {} processos | Mostrando: {}",
            processos.len(),
            processos.len().min(args.limite)
        );

        // Se intervalo = 0, executar apenas uma vez
        if args.intervalo == 0 {
            break;
        }

        println!(
            "Atualizando em {}s... (Ctrl+C para sair)",
            args.intervalo
        );
        std::thread::sleep(Duration::from_secs(args.intervalo));
    }
}

O programa principal conecta todos os modulos em um loop de atualizacao que coleta dados, aplica filtros, ordena e exibe os resultados. O modo continuo atualiza a tela periodicamente, similar ao top tradicional.

Como Executar

# Compilar o projeto
cargo build --release

# Executar com configuracoes padrao (top 20 processos por CPU)
cargo run

# Mostrar os 10 processos que mais consomem memoria
cargo run -- --limite 10 --ordenar memoria

# Filtrar processos do Firefox
cargo run -- --filtrar firefox --limite 5

# Executar uma unica vez (sem atualizacao continua)
cargo run -- --intervalo 0

# Ordenar por nome com 30 processos
cargo run -- -o nome -n 30 -r 3

Saida esperada:

=== Monitor de Processos ===
SO: Ubuntu | Kernel: 6.5.0 | CPUs: 8
CPU:     [#########                     ] 28.5%
Memoria: [################              ] 6.2/16.0 GB (38.8%)

    PID NOME                       CPU %   MEM (MB)     STATUS
-----------------------------------------------------------------
   1234 firefox                     45.2      856.3    Running
   5678 code                        12.8      423.1    Running
    901 cargo                        8.5      234.7    Running
   1122 rust-analyzer                5.2      312.4    Running
   3344 gnome-shell                  3.1      189.2    Running
      1 systemd                      0.0       12.3    Running
-----------------------------------------------------------------

Total: 245 processos | Mostrando: 6
Atualizando em 2s... (Ctrl+C para sair)

Desafios para Expandir

  1. Arvore de processos: Implemente a visualizacao em arvore mostrando a hierarquia pai-filho dos processos, com indentacao visual para cada nivel.
  2. Interface interativa com ratatui: Substitua a saida estática por uma TUI interativa que permita navegar pela lista com setas, matar processos e alternar a ordenacao em tempo real.
  3. Historico de consumo: Registre o uso de CPU e memoria ao longo do tempo e exiba mini-graficos usando caracteres unicode como sparklines.
  4. Alertas de consumo: Adicione a opcao de definir limites de CPU/memoria e disparar alertas (som, notificacao de desktop ou log) quando um processo exceder o limite.
  5. Exportacao de dados: Implemente a exportacao periodica dos dados de processos em formato JSON ou CSV para analise posterior com outras ferramentas.

Veja Tambem