Некоторые программы используют изменение своей командной строки как метод отображения своей текущей активности. Пользователь может увидеть эту активность, запуская команды ps
и top
. Примеры таких программ: PostgreSQL, Sendmail, Zabbix.
Давайте рассмотрим пример из Linux. Предположим, что мы хотим наблюдать количество процессов Zabbix агента.
Команда ps
отобразит интересующие процессы следующим образом:
$ 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]
...
Выбор процессов по имени и пользователю выполняет задачу:
Теперь давайте переименуем исполняемый файл zabbix_agentd
в zabbix_agentd_30
и перезапустим его.
Теперь ps
отображает:
$ 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]
...
Теперь выбор процессов по имени и пользователю выдаёт неверный результат:
Почему простое переименование исполняемого файла на более длинное имя приводит к совершенно другому результату?
Zabbix agent начинает с проверки имени процесса. Открывается файл /proc/<pid>/status
и проверяется строка Name
. В нашем случае строки Name
это:
$ 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
Имя процесса в файле status
обрезается до 15 символов.
Аналогичный результат можно увидеть при помощи команды 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
...
Очевидно, что этот вывод не идентичен нашему значению zabbix_agentd_30
в параметре name
ключа proc.num[]
. Будучи не в состоянии найти совпадение по имени процесса в файле status
, Zabbix агент обращается к файлу /proc/<pid>/cmdline
.
То, как агент просматривает файл «cmdline», может быть проиллюстрировано при помощи выполнения команды:
$ 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>...
В нашем случае файлы /proc/<pid>/cmdline
содержат невидимые, непечатаемые нулевые байты, которые используются для завершения строки в языке C. В этом примере нулевые байты отображаются как «<NUL>».
Zabbix агент проверяет «cmdline» основного процесса и извлекает zabbix_agentd_30
, которое соответствует значению zabbix_agentd_30
нашего параметра name
. Таким образом, основной процесс учитывается элементом данных proc.num[zabbix_agentd_30,zabbix]
.
При проверке следующего процесса агент из файла cmdline
получает zabbix_agentd_30: collector [idle 1 sec]
, и это не соответствует нашему значению zabbix_agentd_30
в параметре name
. Таким образом, будет сосчитан только основной процесс, который не меняет свою командную строку. Остальные процессы агента модифицируют свои командные строки и игнорируются.
Этот пример показывает, что в данном случае параметр name
нельзя использовать в proc.mem[]
и proc.num[]
для выбора процессов.
Использование параметра cmdline
с надлежащим регулярным выражением даст правильный результат:
Будьте осторожны при использовании элементов данных proc.mem[]
и proc.num[]
при наблюдении за программами, которые модифицируют свои командные строки.
Перед тем как поместить параметры name
и cmdline
в элементы данных proc.mem[]
и proc.num[]
, вы, возможно, захотите протестировать эти параметры, используя элемент данных proc.num[]
и команду ps
.
cmdline
в элементах данных proc.mem[]
и proc.num[]
Давайте возьмем в качестве примера один из потоков ядра:
Его можно выбрать при помощи параметра имя
:
Но выбор при помощи параметра cmdline
не работает:
Причина такого поведения кроется в том, что Zabbix агент берёт регулярное выражение, которое указано в параметре cmdline
, и применяет его к содержимому относящегося к процессу файла /proc/<pid>/cmdline
. В случае потоков ядра, их файлы /proc/<pid>/cmdline
пустые. Поэтому параметр cmdline
никогда не совпадёт.
proc.mem[]
и proc.num[]
Потоки ядра Linux подсчитываются элементом данных proc.num[]
, но не сообщают информацию о памяти в элементе данных proc.mem[]
item. Например:
$ 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.
Но что случится, если имеется пользовательский процесс с таким же именем как и у потока ядра? Тогда это будет выглядеть примерно так:
$ 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[]
подсчитал как поток ядра, так и пользовательский процесс. proc.mem[]
сообщил информацию о памяти только по пользовательскому процессу и вычислил память потока ядра как будто оно было нулевым. Это отличается от случая выше, когда сообщалось ZBX_NOTSUPPORTED.
Будьте внимательны при использовании элементов данных proc.mem[]
и proc.num[]
, если случится, что имя процесса совпадёт с одним из потоков.
Перед добавлением параметров в элементы данных proc.mem[]
и proc.num[]
вы, возможно, захотите протестировать эти параметры, используя элемент данных proc.num[]
и команду ps
.