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
- Arvore de processos: Implemente a visualizacao em arvore mostrando a hierarquia pai-filho dos processos, com indentacao visual para cada nivel.
- 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.
- Historico de consumo: Registre o uso de CPU e memoria ao longo do tempo e exiba mini-graficos usando caracteres unicode como
sparklines. - 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.
- Exportacao de dados: Implemente a exportacao periodica dos dados de processos em formato JSON ou CSV para analise posterior com outras ferramentas.