一些程序通过修改其命令行作为显示当前活动的方法。用户可以通过运行 ps
和 top
命令查看活动。此类程序的示例包括 PostgreSQL、Sendmail、Zabbix。
我们来看一个 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/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
file 并检查 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
file 中的进程名称被截断为 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
file 匹配进程名称,Zabbix agent 转向 /proc/<pid>/cmdline
文件。
如何通过 agent 查看 "cmdline" file 可以通过运行以下命令来说明:
$ 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 agent 检查主进程的 "cmdline" 并提取 zabbix_agentd_30
,这与我们的 name
参数值 zabbix_agentd_30
匹配。因此,主进程通过 监控项 proc.num[zabbix_agentd_30,zabbix]
被计数。
在检查下一个进程时,agent 从 cmdline
file 提取 zabbix_agentd_30: collector [idle 1 sec]
,但它不符合我们的 name
参数值 zabbix_agentd_30
。因此,只有未修改其命令行的主进程被计数。其他 agent 进程修改了其命令行并被忽略。
此示例表明,在此情况下,不能在 proc.mem[]
和 proc.num[]
中使用 name
参数来选择进程。
对于 proc.get[]
监控项,当 Zabbix agent 检查进程名称的 "cmdline" 时,它将仅使用从最后一个斜杠开始到第一个空格或冒号之间的名称部分。从 cmdline file 接收到的进程名称仅在与 status
文件中的缩短进程名称完全匹配时才会被使用。该算法对于过滤器中的进程名称和 JSON 输出中的进程名称是相同的。
使用 cmdline
参数和适当的正则表达式可以产生正确的结果:
在使用 proc.get[]
、proc.mem[]
和 proc.num[]
监控项 监控修改其命令行的程序时,请务必小心。
在将 name
和 cmdline
参数放入 proc.get[]
、proc.mem[]
和 proc.num[]
监控项 之前,您可能希望使用 proc.num[]
监控项 和 ps
命令测试这些参数。
cmdline
parameter in proc.get[]
, proc.mem[]
and proc.num[]
items我们以其中一个内核线程为例:
可以使用进程 name
参数进行选择:
但是使用进程 cmdline
参数进行选择则不起作用:
原因是 Zabbix agent 会将 cmdline
参数中指定的正则表达式 应用于进程的 /proc/<pid>/cmdline
内容。对于内核线程,其 /proc/<pid>/cmdline
文件是空的。 因此,cmdline
参数永远不会匹配。
proc.mem[]
and proc.num[]
itemsLinux 内核线程由 proc.num[]
监控项 进行计数,但在 proc.mem[]
监控项 中不报告 memory。例如:
$ 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[]
仅对用户进程报告 memory,并将内核线程的 memory 计算为 0。这与上面报告 ZBX_NOTSUPPORTED 的情况不同。
当程序名称恰好与线程名称匹配时,在使用 proc.mem[]
和 proc.num[]
监控项 时请小心。
在将参数放入 proc.mem[]
和 proc.num[]
监控项 之前,您可能希望使用 proc.num[]
监控项 和 ps
命令测试这些参数。