9 Observações sobre seleção de processos nos itens proc.mem e proc.num

Processos modificando sua própria linha de comando

Alguns programas utilizam sua própria linha de comando como uma forma de apresentar sua atividade atual. Um usuário pode ver a atividade através dos comandos ps e top. Exemplos destes programas incluem PostgreSQL, Sendmail, Zabbix.

Vamos ver um exemplo no Linux. Digamos que vamos monitorar a quantidade de processos do Zabbix Agent.

O comando ps mostra os processos de interesse desta forma

$ ps -fu zabbix
       UID        PID  PPID  C STIME TTY          TIME CMD
       ...
       zabbix    6318     1  0 12:01 ?        00:00:00 sbin/zabbix_agentd -c /home/zabbix/ZBXNEXT-1078/zabbix_agentd.conf
       zabbix    6319  6318  0 12:01 ?        00:00:01 sbin/zabbix_agentd: collector [idle 1 sec]                          
       zabbix    6320  6318  0 12:01 ?        00:00:00 sbin/zabbix_agentd: listener #1 [waiting for connection]            
       zabbix    6321  6318  0 12:01 ?        00:00:00 sbin/zabbix_agentd: listener #2 [waiting for connection]            
       zabbix    6322  6318  0 12:01 ?        00:00:00 sbin/zabbix_agentd: listener #3 [waiting for connection]            
       zabbix    6323  6318  0 12:01 ?        00:00:00 sbin/zabbix_agentd: active checks #1 [idle 1 sec]                   
       ...

Podemos contar estes processos através de seu nome e do usuário:

$ zabbix_get -s localhost -k 'proc.num[zabbix_agentd,zabbix]'
       6

Vamos modificar o executável zabbix_agentd para zabbix_agentd_30 e reiniciá-lo.

ps agora mostra

$ ps -fu zabbix
       UID        PID  PPID  C STIME TTY          TIME CMD
       ...
       zabbix    6715     1  0 12:53 ?        00:00:00 sbin/zabbix_agentd_30 -c /home/zabbix/ZBXNEXT-1078/zabbix_agentd.conf
       zabbix    6716  6715  0 12:53 ?        00:00:00 sbin/zabbix_agentd_30: collector [idle 1 sec]                          
       zabbix    6717  6715  0 12:53 ?        00:00:00 sbin/zabbix_agentd_30: listener #1 [waiting for connection]            
       zabbix    6718  6715  0 12:53 ?        00:00:00 sbin/zabbix_agentd_30: listener #2 [waiting for connection]            
       zabbix    6719  6715  0 12:53 ?        00:00:00 sbin/zabbix_agentd_30: listener #3 [waiting for connection]            
       zabbix    6720  6715  0 12:53 ?        00:00:00 sbin/zabbix_agentd_30: active checks #1 [idle 1 sec]                   
       ...

Agora selecionando os processos pelo nome e pelo usuário irá retornar um valor incorreto:

$ zabbix_get -s localhost -k 'proc.num[zabbix_agentd_30,zabbix]'
       1

Por que o simples renomear de um executável fez com que tivéssemos um resultado diferente ?

O Zabbix Agent começa com a verificação pelo nome do processos. O arquivo aberto é /proc/<pid>/status e a linha Name é verificada. No nosso caso as linhas Name são:

$ grep Name /proc/{6715,6716,6717,6718,6719,6720}/status
       /proc/6715/status:Name:   zabbix_agentd_3
       /proc/6716/status:Name:   zabbix_agentd_3
       /proc/6717/status:Name:   zabbix_agentd_3
       /proc/6718/status:Name:   zabbix_agentd_3
       /proc/6719/status:Name:   zabbix_agentd_3
       /proc/6720/status:Name:   zabbix_agentd_3

O nome do processo no arquivo de status é truncado em 15 characteres.

Um resultado similar pode ser visto com o comando ps:

$ ps -u zabbix
         PID TTY          TIME CMD
       ...
        6715 ?        00:00:00 zabbix_agentd_3
        6716 ?        00:00:01 zabbix_agentd_3
        6717 ?        00:00:00 zabbix_agentd_3
        6718 ?        00:00:00 zabbix_agentd_3
        6719 ?        00:00:00 zabbix_agentd_3
        6720 ?        00:00:00 zabbix_agentd_3
        ...

Obviamente, isso não é igual ao nosso valor de parâmetro utilizado em proc.num[] name (zabbix_agentd_30). Quando a verificação do nome do processo a partir do arquivo de status falha o Zabbix Agent modifica para o arquivo /proc/<pid>/cmdline.

Agora o agente vê a o arquivo "cmdline" o que pode ser entendido como o funcionamento de um comando

$ for i in 6715 6716 6717 6718 6719 6720; do cat /proc/$i/cmdline | awk '{gsub(/\x0/,"<NUL>"); print};'; done
       sbin/zabbix_agentd_30<NUL>-c<NUL>/home/zabbix/ZBXNEXT-1078/zabbix_agentd.conf<NUL>
       sbin/zabbix_agentd_30: collector [idle 1 sec]<NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL>...
       sbin/zabbix_agentd_30: listener #1 [waiting for connection]<NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL>...
       sbin/zabbix_agentd_30: listener #2 [waiting for connection]<NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL>...
       sbin/zabbix_agentd_30: listener #3 [waiting for connection]<NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL>...
       sbin/zabbix_agentd_30: active checks #1 [idle 1 sec]<NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL><NUL>...

Os arquivos /proc/<pid>/cmdline em nosso caso contêm bytes invisíveis, não apresentáveis e nulos, utilizados para terminar strings na linguagem C. Os bytes nulos estão sendo apresentados como "<NUL>" aqui neste exemplo.

O agente verifica "cmdline" buscando o processo principal e encontra um zabbix_agentd_30, que corresponde com o valor informado no parâmetro name (zabbix_agentd_30). Logo, o processo principal é contado pelo item proc.num[zabbix_agentd_30,zabbix].

Quando vai verificar o próximo processo o agente encontra zabbix_agentd_30: collector [idle 1 sec] no arquivo cmdline e isso não confere com o valor proposto (zabbix_agentd_30). Logo, apenas o processo principal, que não modifica a sua própria linha de comando, será contabilizado. Os outros processos do agente serão ignorados.

Este exemplo mostro o que parâmetro name não pode ser utilizado neste caso para os itens proc.mem[] e proc.num[].

Usando o comando cmdline com uma expressão recular apropriada, obteremos o resultado correto:

$ zabbix_get -s localhost -k 'proc.num[,zabbix,,zabbix_agentd_30[ :]]'
       6

Cuidado ao utilizar os itens proc.mem[] e proc.num[] para monitorar programas que modificam sua própria linha de comando.

Antes de utilizar os parâmetros name e cmdline com os itens proc.mem[] e proc.num[], pode ser uma boa prática testar o resultado destes usando o comando ps.

Threads do kernel Linux

Threads não podem ser selecionadas com o parâmetro cmdline nos itens proc.mem[] e proc.num[]

Vamos ver um exemplo das threads do kernel:

$ ps -ef| grep kthreadd
       root         2     0  0 09:33 ?        00:00:00 [kthreadd]

Ela pode ser selecionada através do parâmetro name:

$ zabbix_get -s localhost -k 'proc.num[kthreadd,root]'
       1

Mas selecionado pelo parâmetro cmdline não irá funcionar:

$ zabbix_get -s localhost -k 'proc.num[,root,,kthreadd]'
       0

A razão disso é que o agente utiliza a expressão regular definida no parametro cmdline e aplica seu conteúdo ao conteúdo do processo /proc/<pid>/cmdline. Mas para as threads do kernel os arquivos /proc/<pid>/cmdline estarão vazios. Logo, o parâmetro cmdline nunca irá funcionar para elas.

Contando threads com os itens proc.mem[] e proc.num[]

As threads do kernel Linux são contadas pelo item proc.num[] mas não pelo proc.mem[]. Por exemplo:

$ ps -ef | grep kthreadd
       root         2     0  0 09:51 ?        00:00:00 [kthreadd]
       
       $ zabbix_get -s localhost -k 'proc.num[kthreadd]'
       1
       
       $ zabbix_get -s localhost -k 'proc.mem[kthreadd]'
       ZBX_NOTSUPPORTED: Cannot get amount of "VmSize" memory.

Mas o que acontece se existir um processo com o mesmo nome de uma thread do kernel? Pode ocorrer algo similar a isso:

$ ps -ef | grep kthreadd
       root         2     0  0 09:51 ?        00:00:00 [kthreadd]
       zabbix    9611  6133  0 17:58 pts/1    00:00:00 ./kthreadd
       
       $ zabbix_get -s localhost -k 'proc.num[kthreadd]'
       2
       
       $ zabbix_get -s localhost -k 'proc.mem[kthreadd]'
       4157440

proc.num[] irá contar tanto o processo de usuário quanto a thread de kernel. proc.mem[] irá apresentar o uso de memória para o processo de usuário e contará a memória da thread como 0. É diferente neste caso pois gerará um status de ZBX_NOTSUPPORTED.

Cuidado ao usar os itens proc.mem[] e proc.num[] se o programa for similar à alguma das threads.

Antes de colocar os parâmetros nos itens proc.mem[] e proc.num[] é sempre interessante testar os parâmetros através de um comando ps.