谁在消耗我的 inotify 资源?

谁在消耗我的 inotify 资源?

最近升级到 Fedora 15 后,我发现许多工具失败并出现以下错误:

tail: inotify resources exhausted
tail: inotify cannot be used, reverting to polling

这不仅仅是tail报告 inotify 问题。有没有办法询问内核以找出哪些进程正在消耗 inotify 资源?当前的inotify相关sysctl设置如下所示:

fs.inotify.max_user_instances = 128
fs.inotify.max_user_watches = 8192
fs.inotify.max_queued_events = 16384

答案1

看来,如果进程通过 inotify_init() 创建 inotify 实例,则表示 /proc 文件系统中的文件描述符的结果文件是到(不存在的)'anon_inode:inotify' 文件的符号链接。

$ cd /proc/5317/fd
$ ls -l
total 0
lrwx------ 1 puzel users 64 Jun 24 10:36 0 -> /dev/pts/25
lrwx------ 1 puzel users 64 Jun 24 10:36 1 -> /dev/pts/25
lrwx------ 1 puzel users 64 Jun 24 10:36 2 -> /dev/pts/25
lr-x------ 1 puzel users 64 Jun 24 10:36 3 -> anon_inode:inotify
lr-x------ 1 puzel users 64 Jun 24 10:36 4 -> anon_inode:inotify

除非我误解了这个概念,否则以下命令应该显示进程列表(它们在 /proc 中的表示),按它们使用的 inotify 实例数量排序。

$ for foo in /proc/*/fd/*; do readlink -f $foo; done | grep inotify | sort | uniq -c | sort -nr

寻找罪魁祸首

通过下面的评论 @markkcowan 提到了这一点:

$ find /proc/*/fd/* -type l -lname 'anon_inode:inotify' -exec sh -c 'cat $(dirname {})/../cmdline; echo ""' \; 2>/dev/null

答案2

2022 年 10 月 31 日更新 支持下面 Michael Sartain 的回答。他实施了本机可执行文件这要快得多,还有我的脚本中不存在的附加功能(如下)。如果您能花几秒钟编译它,那么值得一试! 本机 inotify-info 实用程序


带脚本的原始答案

正如@Jonathan Kamens 所说,你的手表可能已经用完了。我有一个预制脚本inotify-consumers,其中列出了最严重的违规者:

   INOTIFY   INSTANCES
   WATCHES      PER   
    COUNT     PROCESS   PID USER         COMMAND
------------------------------------------------------------
   21270         1       11076 my-user    /snap/intellij-idea-ultimate/357/bin/fsnotifier
     201         6           1 root       /sbin/init splash
     115         5        1510 my-user    /lib/systemd/systemd --user
      85         1        3600 my-user    /usr/libexec/xdg-desktop-portal-gtk
      77         1        2580 my-user    /usr/libexec/gsd-xsettings
      35         1        2475 my-user    /usr/libexec/gvfsd-trash --spawner :1.5 /org/gtk/gvfs/exec_spaw/0
      32         1         570 root       /lib/systemd/systemd-udevd
      26         1        2665 my-user    /snap/snap-store/558/usr/bin/snap-store --gapplication-service
      18         2        1176 root       /usr/libexec/polkitd --no-debug
      14         1        1858 my-user    /usr/bin/gnome-shell
      13         1        3641 root       /usr/libexec/fwupd/fwupd
...

   21983  WATCHES TOTAL COUNT

INotify instances per user (e.g. limits specified by fs.inotify.max_user_instances): 

INSTANCES    USER
-----------  ------------------
41           my-user
23           root
1            whoopsie
1            systemd-ti+
...

在这里,您很快就会明白为什么 8K 观察者的默认限制在开发计算机上太小,因为当遇到node_modules具有数千个文件夹的文件夹时,WebStorm 实例很快就会最大化此限制。添加 webpack 观察器以保证出现问题...

尽管我最初制作它时它比其他替代方案快得多,但 Simon Matter 为重负载的 Big Iron Linux(数百个核心)添加了一些速度增强功能,极大地加快了速度,从 10 分钟(!)缩短到 15 分钟在他的怪物装备上几秒钟。

稍后的,Brian Dowling 贡献了每个进程的实例数,以相对较高的运行时间为代价。这对于运行时间约为一秒的普通机器来说是微不足道的,但如果你有 Big Iron,你可能需要早期版本大约是系统时间的 1/10 :)

如何使用

inotify-consumers --help

答案3

您可能已经用完 inotify手表而不是实例。要了解谁制造了很多手表:

  1. 启用监视添加跟踪:
$ echo 1 > /sys/kernel/debug/tracing/events/syscalls/sys_exit_inotify_add_watch/enable`
  1. 验证tracing_ons 是否为 1:
$ cat /sys/kernel/debug/tracing/tracing_on
0
$ echo 1 > /sys/kernel/debug/tracing/tracing_on
  1. 使用您怀疑创建大量手表的 inotify 实例重新启动进程(如 Petr Uzel 的答案中所述确定);和
  2. 设置 ftrace
$ cat /sys/kernel/debug/tracing/current_tracer
nop

$ cat /sys/kernel/debug/tracing/set_ftrace_filter
#### all functions enabled ####

$ echo function              > /sys/kernel/debug/tracing/current_tracer
$ echo SyS_inotify_add_watch > /sys/kernel/debug/tracing/set_ftrace_filter
  1. 阅读该文件/sys/kernel/debug/tracing/trace以查看创建了多少个监视以及由哪些进程创建。

完成后,请确保echo 0进入启用文件(如果还必须启用该文件,则还需要进入tracing_on 文件)以关闭跟踪,这样就不会因继续跟踪而导致性能下降。

笔记:在旧版本的 Linux 内核中,/sys端点过去被称为tracing_enabled,但现在被称为tracing_on。如果您发现您使用的是旧版本的内核,请更改/sys/kernel/debug/tracing/tracing_on/sys/kernel/debug/tracing/tracing_enabled.

答案4

我遇到了这个问题,这些答案都没有给你“有多少手表每个进程当前正在使用吗?”单行文字都会告诉您有多少实例是打开的,这只是故事的一部分,跟踪内容仅对查看新手表被打开有用。

长话短说:inotify这将为您提供一个包含打开实例列表和手表数量它们以及生成它们的 pid 和二进制文件,按监视计数降序排序:

sudo lsof | awk '/anon_inode/ { gsub(/[urw]$/,"",$4); print "/proc/"$2"/fdinfo/"$4; }' | while read fdi; do count=$(sudo grep -c inotify $fdi); exe=$(sudo readlink $(dirname $(dirname $fdi))/exe); echo -e $count"\t"$fdi"\t"$exe; done | sort -nr > watches

那是一团乱七八糟的大球,所以这就是我到达那里的方法。首先,我tail在一个测试文件上运行了 a,并查看了它打开的文件描述符:

joel@gladstone:~$ tail -f test > /dev/null &
[3] 22734
joel@opx1:~$ ls -ahltr /proc/22734/fd
total 0
dr-xr-xr-x 9 joel joel  0 Feb 22 22:34 ..
dr-x------ 2 joel joel  0 Feb 22 22:34 .
lr-x------ 1 joel joel 64 Feb 22 22:35 4 -> anon_inode:inotify
lr-x------ 1 joel joel 64 Feb 22 22:35 3 -> /home/joel/test
lrwx------ 1 joel joel 64 Feb 22 22:35 2 -> /dev/pts/2
l-wx------ 1 joel joel 64 Feb 22 22:35 1 -> /dev/null
lrwx------ 1 joel joel 64 Feb 22 22:35 0 -> /dev/pts/2

所以,4 就是我们要调查的 fd。让我们看看其中有什么fdinfo

joel@opx1:~$ cat /proc/22734/fdinfo/4
pos:    0
flags:  00
mnt_id: 11
inotify wd:1 ino:15f51d sdev:ca00003 mask:c06 ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:1df51500a75e538c

这看起来像是底部的手表条目!

让我们尝试使用更多的手表,这次使用inotifywait实用程序,只观察其中的内容/tmp

joel@gladstone:~$ inotifywait /tmp/* &
[4] 27862
joel@gladstone:~$ Setting up watches.
Watches established.
joel@gladstone:~$ ls -ahtlr /proc/27862/fd | grep inotify
lr-x------ 1 joel joel 64 Feb 22 22:41 3 -> anon_inode:inotify
joel@gladstone:~$ cat /proc/27862/fdinfo/3
pos:    0
flags:  00
mnt_id: 11
inotify wd:6 ino:7fdc sdev:ca00003 mask:fff ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:dc7f0000551e9d88
inotify wd:5 ino:7fcb sdev:ca00003 mask:fff ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:cb7f00005b1f9d88
inotify wd:4 ino:7fcc sdev:ca00003 mask:fff ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:cc7f00006a1d9d88
inotify wd:3 ino:7fc6 sdev:ca00003 mask:fff ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:c67f00005d1d9d88
inotify wd:2 ino:7fc7 sdev:ca00003 mask:fff ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:c77f0000461d9d88
inotify wd:1 ino:7fd7 sdev:ca00003 mask:fff ignored_mask:0 fhandle-bytes:8 fhandle-type:1 f_handle:d77f00000053c98b

啊哈!更多条目!所以我们应该有六件事/tmp

joel@opx1:~$ ls /tmp/ | wc -l
6

出色的。我的新inotifywait其列表中只有 6 个条目fd(这是这里其他单行话所计算的),但其fdinfo文件中有 6 个条目。因此,我们可以通过查阅其文件来了解给定进程的给定 fd 使用了多少个监视fdinfo。现在将其与上面的一些内容放在一起,以获取已打开通知监视的进程列表,并使用它来计算每个fdinfo.这与上面类似,所以我只在这里转储一行:

sudo lsof | awk '/anon_inode/ { gsub(/[urw]$/,"",$4); print "/proc/"$2"/fdinfo/"$4; }' | while read fdi; do count=$(sudo grep -c inotify $fdi); echo -e $count"\t"$fdi; done

这里有一些厚重的东西,但基础知识是我用来从输出awk构建路径,获取 pid 和 fd 编号,从后者中剥离 u/r/w 标志。然后,对于每个构建的路径,我计​​算行数并输出计数和 pid。fdinfolsoffdinfoinotify

如果我在同一个地方有这些 pid 代表的进程就好了,对吧?我是这么想的。因此,在一个特别混乱的情况下,我决定在路径dirname上调用两次fdinfo以获取 pack /proc/<pid>,添加/exe到它,然后readlink运行获取进程的 exe 名称。将其也放入其中,按手表数量对其进行排序,并将其重定向到一个文件以进行安全保存,我们得到:

sudo lsof | awk '/anon_inode/ { gsub(/[urw]$/,"",$4); print "/proc/"$2"/fdinfo/"$4; }' | while read fdi; do count=$(sudo grep -c inotify $fdi); exe=$(sudo readlink $(dirname $(dirname $fdi))/exe); echo -e $count"\t"$fdi"\t"$exe; done | sort -n > watches

运行那个没有sudo 只是显示我上面启动的进程,我得到:

joel@gladstone:~$ cat watches 
6   /proc/4906/fdinfo/3 /usr/bin/inotifywait
1   /proc/22734/fdinfo/4    /usr/bin/tail

完美的!进程、fd 和数量的列表手表每个都在使用,这正是我所需要的。

相关内容