8 Uwagi dotyczące wybierania procesów w pozycjach proc.mem i proc.num

Procesy modyfikujące swoją linię poleceń

Niektóre programy wykorzystują modyfikowanie swojej linii poleceń jako metodę wyświetlania bieżącej aktywności. Użytkownik może zobaczyć tę aktywność, uruchamiając polecenia ps i top. Przykładami takich programów są PostgreSQL, Sendmail, Zabbix.

Zobaczmy przykład z Linuksa. Załóżmy, że chcemy monitorować liczbę procesów Zabbix agent.

Polecenie ps pokazuje interesujące nas procesy jako

$ 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/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]                   
...

Wybieranie procesów według nazwy i użytkownika działa:

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

Teraz zmieńmy nazwę pliku wykonywalnego zabbix_agentd na zabbix_agentd_30 i uruchommy go ponownie.

ps pokazuje teraz

$ 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/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]                   
...

Teraz wybieranie procesów według nazwy i użytkownika daje niepoprawny wynik:

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

Dlaczego zwykła zmiana nazwy pliku wykonywalnego na dłuższą prowadzi do tak odmiennego wyniku?

Zabbix agent zaczyna od sprawdzenia nazwy procesu. Otwierany jest plik /proc/<pid>/status i sprawdzany jest wiersz Name. W naszym przypadku wiersze Name są następujące:

$ 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

Nazwa procesu w pliku status jest obcinana do 15 znaków.

Podobny wynik można zobaczyć przy użyciu polecenia 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
 ...

Oczywiście nie jest to równe wartości parametru name proc.num[], czyli zabbix_agentd_30. Po nieudanej próbie dopasowania nazwy procesu z pliku status Zabbix agent przechodzi do pliku /proc/<pid>/cmdline.

To, jak agent widzi plik „cmdline”, można zilustrować, uruchamiając polecenie

$ 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/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>...

Pliki /proc/<pid>/cmdline w naszym przypadku zawierają niewidoczne, niedrukowalne bajty null, używane do kończenia łańcuchów w języku C. W tym przykładzie bajty null są pokazane jako „<NUL>”.

Zabbix agent sprawdza „cmdline” dla procesu głównego i pobiera zabbix_agentd_30, co odpowiada wartości naszego parametru name zabbix_agentd_30. Dlatego proces główny jest zliczany przez pozycja proc.num[zabbix_agentd_30,zabbix].

Podczas sprawdzania kolejnego procesu agent pobiera zabbix_agentd_30: collector [idle 1 sec] z pliku cmdline i nie spełnia to warunku naszego parametru name zabbix_agentd_30. Zatem zliczany jest tylko proces główny, który nie modyfikuje swojej linii poleceń. Pozostałe procesy agent modyfikują swoją linię poleceń i są ignorowane.

Ten przykład pokazuje, że parametr name nie może być używany w proc.mem[] i proc.num[] do wybierania procesów w takim przypadku.

Dla pozycja proc.get[], gdy Zabbix agent sprawdza „cmdline” pod kątem nazwy procesu, użyje tylko tej części nazwy, która zaczyna się od ostatniego ukośnika i kończy na pierwszej spacji lub dwukropku. Nazwa procesu pobrana z pliku cmdline zostanie użyta tylko wtedy, gdy jej początek całkowicie odpowiada skróconej nazwie procesu w pliku status. Algorytm jest taki sam zarówno dla nazwy procesu w filtrze, jak i w wyjściu JSON.

Użycie parametru cmdline z odpowiednim wyrażeniem regularnym daje poprawny wynik:

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

Zachowaj ostrożność podczas używania pozycji proc.get[], proc.mem[] i proc.num[] do monitorowania programów, które modyfikują swoje linie poleceń.

Przed wstawieniem parametrów name i cmdline do pozycji proc.get[], proc.mem[] i proc.num[] możesz chcieć przetestować te parametry przy użyciu pozycji proc.num[] i polecenia ps.

Wątki jądra Linux

W pozycjach proc.get[], proc.mem[] i proc.num[] nie można wybierać wątków za pomocą parametru cmdline

Jako przykład weźmy jeden z wątków jądra:

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

Można go wybrać za pomocą parametru name procesu:

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

Jednak wybieranie według parametru cmdline procesu nie działa:

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

Powodem jest to, że agent Zabbix pobiera wyrażenie regularne określone w parametrze cmdline i stosuje je do zawartości pliku procesu /proc/<pid>/cmdline. W przypadku wątków jądra ich pliki /proc/<pid>/cmdline są puste. Dlatego parametr cmdline nigdy nie pasuje.

Zliczanie wątków w pozycjach proc.mem[] i proc.num[]

Wątki jądra Linux są zliczane przez pozycję proc.num[], ale nie raportują pamięci w pozycji proc.mem[]. Na przykład:

$ 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.

Ale co się stanie, jeśli istnieje proces użytkownika o tej samej nazwie co wątek jądra? Wtedy może to wyglądać tak:

$ 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[] zliczył zarówno wątek jądra, jak i proces użytkownika. proc.mem[] raportuje pamięć tylko dla procesu użytkownika i traktuje pamięć wątku jądra tak, jakby wynosiła 0. Różni się to od przypadku powyżej, gdy zgłaszane było ZBX_NOTSUPPORTED.

Zachowaj ostrożność podczas używania pozycji proc.mem[] i proc.num[], jeśli nazwa programu przypadkowo odpowiada nazwie jednego z wątków.

Przed wstawieniem parametrów do pozycji proc.mem[] i proc.num[] warto przetestować parametry za pomocą pozycji proc.num[] oraz polecenia ps.