Este script é um ótimo ponto de partida para aprender conceitos de shell scripting. Eu criei para ajudar a debugar um possível leak de memória no Strawberry Music Player, um player de música multiplataforma escrito em Qt e C++, que utiliza o gStreamer como um de seus backends. Identifiquei esse problema de crescimento de consumo de memória RAM e precisava saber em intervalos pormenorizadamente o valor do consumo de RAM enquanto eu debugava. A idéia desse script é printar em intervalos definidos a quantidade em MB de RAM consumida pelo processo determinado, no caso por default, “strawberry”. Como um bonus ele reinicia o processo se atingir um threshold superior em porcentagem definida também pela variável respectiva ($MEM_THRESHOLD).
Vamos comentar seus truques e analisar o código!
Truques e Técnicas:
- Valores Padrão: O script fornece valores padrão para argumentos, tornando-o flexível. $1, $2, $3 seriam os tres primeiros parametros passados na linha de comando para o script: caso nao sejam passados existem valores padrão: strawberry, para o nome do processo, 30 segundos, para o intervalo de iteração e 20, no caso os vinte porcento do total de RAM do sistema.
- Tratamento de Erros: O script verifica se há instâncias duplicadas para evitar conflitos, e se recusa a iniciar mais de uma instância de si mesmo.
- Execução em Segundo Plano: O processo reiniciado é jogado para segundo plano, permitindo que o script continue o monitoramento, sem bloquear o fluxo do script.
- bc para Cálculos: O script aproveita o bc para realizar cálculos dentro do ambiente shell. Isso acontece porquê shell não reconhece nada além de strings e números inteiros: um ponto-flutuante é uma string, para o shell. Mas não para a calculadora bc! Fica a dica.
- Detalhe sobre prevenir scripts duplicados: O script verifica se outra instância já está em execução usando pgrep. Tem uma razão, não utilizar ps. Verifique depois o manual de pgrep e suas opções;
- Se outra instância for encontrada (pela contagem de wc -l), o script sai com uma mensagem de erro.
Este script demonstra vários conceitos de shell scripting. Ao compreendê-lo e modificá-lo, você pode aprender a automatizar várias tarefas em seu sistema.
#!/bin/bash
# Set process to monitor mem usage
STRAWGLER=${1:-"strawberry"};
# Set loop interval (in seconds)
INTERVAL=${2:-30};
# Set memory threshold (in percentage)
MEM_THRESHOLD=${3:-20};
export LC_ALL=C;
strawgler_main() {
# Loop continuously, restart process if reaches threshold!
while true; do
# Get total RAM in Megabytes
TOTAL_MEM=$(free -m | awk '/Mem:/{print $2}')
# Get memory in MB used by the monitored process
STRAWGLER_MEM=$(ps -eO rss,fname | grep ${STRAWGLER}$ | awk '{sum+=$2} END {print sum/1024}')
# Calculate memory usage percentage
MEM_USAGE=$(echo "scale=2; 100 * $STRAWGLER_MEM / $TOTAL_MEM" | bc)
echo "${STRAWGLER_MEM} MB (${MEM_USAGE}%)";
###notify-send -i ${STRAWGLER} -a ${STRAWGLER} "${STRAWGLER_MEM} MB ($MEM_USAGE%)";
# Check if memory usage exceeds threshold
if [[ $(echo "$MEM_USAGE >= $MEM_THRESHOLD" | bc -l) -eq 1 ]]; then
echo "${STRAWGLER} memory usage ($MEM_USAGE%) exceeded threshold. Restarting..."
notify-send -i ${STRAWGLER} -a ${STRAWGLER} "${STRAWGLER} mem: ($MEM_USAGE%)" "process Exceeded threshold. Restarting!";
# Send HUP signal to gracefully restart process
killall -HUP ${STRAWGLER};
# Wait to allow shutdown
sleep 1;
# then Start "${STRAWGLER}" in background
${STRAWGLER} &
fi
# Wait some time before checking again
sleep ${INTERVAL};
done
}
## Check if there is an instance already running
if [ $( pgrep -x "$( basename $0 )" | grep -v $$ | wc -l ) -gt 1 ]; then
echo "Tried to spawn more than one script, ¡there can be only one!";
exit 1;
else
strawgler_main ${1};
fi
Shebang (#! /bin/bash):
- Esta linha indica ao sistema que use o interpretador bash para executar o script.
Variáveis:
- $STRAWGLER: Esta variável armazena o nome do processo a ser monitorado. O padrão é “strawberry” se nenhum argumento for fornecido ao executar o script (por exemplo, ./script.sh).
- $INTERVAL: Esta variável define o intervalo do loop em segundos entre verificações. O padrão é 30 segundos.
- $MEM_THRESHOLD: Esta variável define o limite de uso de memória em porcentagem. O padrão é 20%.
LC_ALL=C: Isso força o script a usar o locale C, garantindo comportamento consistente independente do locale padrão do sistema, assim se espera ponto ao invés de vírgulas, para separar pontos decimais, por exemplo.
Função strawgler_main:
- Esta função representa o loop principal do script.
Looping (while true):
- O script entra em loop contínuo usando while true. Isso garante que o script continue monitorando o processo.
Cálculo de Uso de Memória:
free -m
- Este comando recupera a memória total do sistema em Megabytes.
ps -eO rss,fname
- Este comando lista todos os processos com seu tamanho do conjunto residente (RSS) e nome do arquivo.
grep ${STRAWGLER}$
- Filtra a saída para obter informações apenas para o processo chamado “strawberry” (ou o nome armazenado em $STRAWGLER, passado como o primeiro parâmetro $1 ).
awk '{sum+=$2} END {print sum/1024}'
- Processa a saída filtrada:
sum+=$2
- Adiciona o uso de memória RSS de cada processo correspondente a uma variável sum.
END {print sum/1024}
- Após processar todas as linhas, imprime o uso total de memória em Megabytes. O awk por si só é uma linguagem de programação!
- bc: Este comando realiza cálculos matemáticos e garante a representação correta dos valores fracionários.
echo "scale=2; 100 * $STRAWGLER_MEM / $TOTAL_MEM"
- Esta linha prepara o cálculo para bc. Define a escala para 2 casas decimais e calcula a porcentagem de uso de memória.
Verificação do Limite de Memória:
if [[ $(echo "$MEM_USAGE >= $MEM_THRESHOLD" | bc -l) -eq 1 ]]; then
- Esta instrução condicional complexa verifica se o uso de memória ($MEM_USAGE) é maior ou igual ao limite ($MEM_THRESHOLD).bc -l: Informa ao bc para usar a biblioteca matemática para operadores de comparação como >=.-eq 1: Verifica se o resultado da comparação é verdadeiro (1).
Reinício do Processo (se o limite for excedido):
- O script envia uma notificação usando notify-send (descomentado no código).
killall -HUP ${STRAWGLER}
- Envia um sinal HUP para todos os processos nomeados por $STRAWGLER. O sinal HUP geralmente aciona uma reinicialização amigável do processo.
- sleep 1: Permite uma breve pausa para o processo encerrar.
${STRAWGLER} &
- Inicia o processo nomeado por $STRAWGLER em segundo plano.
O script utiliza o comando pgrep para verificar se outra instância já está em execução, em vez de ps. Existem algumas razões para essa escolha:
Eficiência:
- O pgrep é geralmente mais rápido e eficiente que o ps para encontrar processos específicos, especialmente quando se lida com um grande número de processos. Isso se deve ao fato de que o pgrep usa técnicas otimizadas para pesquisa de processos, enquanto o ps pode ler todos os processos em execução no sistema, o que pode ser um processo mais lento e pesado.
Precisão:
- O pgrep é projetado para identificar processos com base em seus nomes ou IDs, enquanto o ps oferece uma gama mais ampla de opções de pesquisa, o que pode levar a resultados mais imprecisos no contexto deste script. O pgrep é mais preciso para encontrar a instância específica do script em execução.
Simplicidade:
- O pgrep possui uma sintaxe mais simples e direta para encontrar processos por nome, tornando-o mais fácil de usar e ler no script. O ps, por outro lado, possui diversas opções e flags que podem tornar a construção do comando de pesquisa mais complexa.
Embora o ps possa ser usado para encontrar scripts duplicados, sua desvantagem em termos de eficiência e precisão o torna menos adequado para essa tarefa específica.
A escolha do pgrep demonstra uma boa prática de selecionar a ferramenta mais adequada para cada tarefa, priorizando eficiência, precisão e simplicidade no código.
E com isso eu automatizei a tarefa de me reportar a RAM utilizada, gerar logs centralizados, etc. não tendo que me preocupar com movimentações manuais para saber a informação que eu precisava, e com certeza isso colaborou para eu ter solucionado o problema de forma mais ágil e menos pentelha.
Obviamente esse script serve como safe-guard contra qualquer memory leak em qualquer aplicativo, impedindo exaustão de recursos, mas entendam ele antes de usar, cada caso tem um uso diferente. O meu script manda um killall -HUP em caso de atingir o threshold, mas pq não executar outra coisa mediante esse trigger?
Por hj chega! VLW!