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:
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:
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:
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
.
cmdline
nos itens proc.mem[]
e proc.num[]
Vamos ver um exemplo das threads do kernel:
Ela pode ser selecionada através do parâmetro name
:
Mas selecionado pelo parâmetro cmdline
não irá funcionar:
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.
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
.