8 在 proc.mem 和 proc.num items 监控项中选择进程

修改其命令行的进程

有些程序会通过修改其命令行来显示当前活动状态。用户可以通过运行 pstop 命令查看这些活动。这类程序的示例包括 PostgreSQLSendmailZabbix

下面来看一个 Linux 中的示例。假设我们想监控若干个 Zabbix agent 进程。

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/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_get -s localhost -k 'proc.num[zabbix_agentd,zabbix]'
6

现在我们将 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/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_get -s localhost -k 'proc.num[zabbix_agentd_30,zabbix]'
1

为什么只是将可执行文件重命名为一个更长的名称,就会导致结果出现如此大的差异?

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

显然,这与我们的 proc.num[]name 参数值 zabbix_agentd_30 并不相等。在无法匹配 status 文件中的进程名后,Zabbix agent 会转而检查 /proc/<pid>/cmdline 文件。

agent 看到的 “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/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 agent 会检查主进程的 “cmdline”,并取出 zabbix_agentd_30,这与我们的 name 参数值 zabbix_agentd_30 匹配。因此,主进程会被监控项 proc.num[zabbix_agentd_30,zabbix] 统计到。

在检查下一个进程时,agent 会从 cmdline 文件中取出 zabbix_agentd_30: collector [idle 1 sec],这不符合我们的 name 参数 zabbix_agentd_30。因此,只有未修改其命令行的主进程会被统计到。其他 agent 进程由于修改了其命令行而被忽略。

这个示例表明,在这种情况下,name 参数不能用于 proc.mem[]proc.num[] 中选择进程。

对于 proc.get[] 监控项,当 Zabbix agent 通过 “cmdline” 检查进程名时,它只会使用从最后一个斜杠开始、直到第一个空格或冒号为止的那部分名称。只有当从 cmdline 文件获取的进程名开头与 status 文件中截短后的进程名完全匹配时,才会使用从 cmdline 文件获取的进程名。该算法对于过滤器中的进程名和 JSON 输出中的进程名都是相同的。

使用带有适当正则表达式的 cmdline 参数可以得到正确结果:

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

在使用 proc.get[]proc.mem[]proc.num[] 监控项监控会修改其命令行的程序时,请务必小心。

在将 namecmdline 参数用于 proc.get[]proc.mem[]proc.num[] 监控项之前,您可能希望先使用 proc.num[] 监控项和 ps 命令测试这些参数。

Linux 内核线程

proc.get[]proc.mem[]proc.num[] 监控项中的 cmdline 参数不可以使用线程

让我们以内核线程为例:

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

可以用进程 name 参数来选择:

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

但使用进程 cmdline 参数就会不起作用:

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

原因是 Zabbix agent 采用 cmdline 参数中指定的正则表达式,并将其应用于进程的内容 /proc/<pid>/cmdline。对于内核线程的 /proc/<pid>/cmdline 文件是空的,所以 cmdline 参数不会匹配到。

proc.mem[]proc.num[] 监控项中的线程计数

Linux 内核线程通过 proc.num[] 监控项计数但是监控项 proc.mem[] 并不报告内存。例如:

$ 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[] 只计算用户进程内存,如果为0计算内核线程内存。这和上面报告 ZBX_NOTSUPPORTED 的例子不同。

如果程序名恰好匹配其中一个线程,请小心使用 proc.mem[]proc.num[] 监控项。

在给 proc.mem[]proc.num[] 监控项配置参数时,你应该使用, proc.num[] 监控项和 ps 命令测试该参数。