谁在消耗我的 inotify 资源?

lar*_*sks 60 fedora kernel inotify

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

tail: inotify resources exhausted
tail: inotify cannot be used, reverting to polling
Run Code Online (Sandbox Code Playgroud)

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

fs.inotify.max_user_instances = 128
fs.inotify.max_user_watches = 8192
fs.inotify.max_queued_events = 16384
Run Code Online (Sandbox Code Playgroud)

Pet*_*zel 44

似乎如果进程通过 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
Run Code Online (Sandbox Code Playgroud)

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

$ for foo in /proc/*/fd/*; do readlink -f $foo; done | grep inotify | sort | uniq -c | sort -nr
Run Code Online (Sandbox Code Playgroud)

寻找罪魁祸首

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

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

  • 非常好,谢谢!我不知道 /proc 中出现的 inotify inode。出于我的目的,该命令可以简化为:`find /proc/*/fd/* -type l -lname 'anon_inode:inotify' -print` (8认同)
  • 请注意,您也可能没有监视(不是实例)。例如,在我的系统上,它提供的实例数量很少,但是 KDE 的桌面搜索中有成千上万的手表。太糟糕了,没有一种更简单的方法来检查正在使用的手表/实例数量,因为内核清楚地知道...... (4认同)

oli*_*ren 31

正如@Jonathan Kamens 所说,您的手表可能快用完了。我有一个预制脚本, inotify-consumers,它为您列出了最严重的罪犯(较新的版本还列出了拥有该进程的用户名,见下文):

$ time inotify-consumers  

   INOTIFY
   WATCHER
    COUNT     PID     CMD
----------------------------------------
    6688    27262  /home/dvlpr/apps/WebStorm-2018.3.4/WebStorm-183.5429.34/bin/fsnotifier64
     411    27581  node /home/dvlpr/dev/kiwi-frontend/node_modules/.bin/webpack --config config/webpack.dev.js
      79     1541  /usr/lib/gnome-settings-daemon/gsd-xsettings
      30     1664  /usr/lib/gvfs/gvfsd-trash --spawner :1.22 /org/gtk/gvfs/exec_spaw/0
      14     1630  /usr/bin/gnome-software --gapplication-service
    ....

    7489  watches TOTAL COUNT

real    0m0.099s
user    0m0.042s
sys 0m0.062s
Run Code Online (Sandbox Code Playgroud)

在这里你很快就会明白为什么 8K 手表的默认限制在开发机器上太少了,因为当遇到一个node_modules包含数千个文件夹的文件夹时,WebStorm 实例很快就会达到最大值。添加一个 webpack watcher 来保证问题......

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

如何使用

inotify-consumers --help 为了得到它你的机器上,只需要复制该脚本的内容并把它放在你的$PATH一样/usr/local/bin。或者,如果您信任网络上的这个陌生人,您可以避免复制它并通过 http 将其通过管道传输到 bash:

$ curl -s https://raw.githubusercontent.com/fatso83/dotfiles/master/utils/scripts/inotify-consumers | bash 

       INOTIFY
       WATCHER
        COUNT     PID USER     COMMAND
    --------------------------------------
        3044   3933 myuser node /usr/local/bin/tsserver
        2965   3941 myuser /usr/local/bin/node /home/myuser/.config/coc/extensions/node_modules/coc-tsserver/bin/tsserverForkStart /hom
         979   3954 myuser /usr/local/bin/node /home/myuser/.config/coc/extensions/node_modules/coc-tsserver/node_modules/typescript/li
           1   7473 myuser /usr/local/bin/node --no-warnings /home/myuser/dev/dotfiles/common-setup/vim/dotvim/plugged/coc.nvim/build/i
           1   3899 myuser /usr/local/bin/node --no-warnings /home/myuser/dev/dotfiles/common-setup/vim/dotvim/plugged/coc.nvim/build/i

        6990  watches TOTAL COUNT
Run Code Online (Sandbox Code Playgroud)

它是如何工作的?

作为参考,脚本的主要内容就是这个(灵感来自这个答案

find /proc/*/fd \
    -lname anon_inode:inotify \
    -printf '%hinfo/%f\n' 2>/dev/null \
    \
    | xargs grep -c '^inotify'  \
    | sort -n -t: -k2 -r 
Run Code Online (Sandbox Code Playgroud)

改变限制

如果您想知道如何增加限制

$ inotify-consumers --limits 

Current limits
-------------
fs.inotify.max_user_instances = 128
fs.inotify.max_user_watches = 524288


Changing settings permanently
-----------------------------
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p # re-read config
Run Code Online (Sandbox Code Playgroud)

  • 许多其他建议对我来说效果不佳,但该脚本在 Fedora 29 上运行良好。谢谢! (3认同)
  • 非常棒的答案,谢谢。事实上,我发现它比公认的更相关,因为它可以帮助你关注最大的罪魁祸首。在 Ubuntu 18.04 和我猜的任何其他 Linux 上也能完美运行 (3认同)

小智 27

您可能用完了 inotify手表而不是实例。要找出谁制造了大量手表:

  1. 启用监视添加跟踪:
$ echo 1 > /sys/kernel/debug/tracing/events/syscalls/sys_exit_inotify_add_watch/enable`
Run Code Online (Sandbox Code Playgroud)
  1. 验证是否tracing_ons 为 1:
$ cat /sys/kernel/debug/tracing/tracing_on
0
$ echo 1 > /sys/kernel/debug/tracing/tracing_on
Run Code Online (Sandbox Code Playgroud)
  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
Run Code Online (Sandbox Code Playgroud)
  1. 阅读文件/sys/kernel/debug/tracing/trace以查看创建了多少个监视以及由哪些进程创建。

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

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


小智 13

刚刚编写了一个 C++ 应用程序来帮助跟踪 inotify 信息。应该能够显示摘要信息以及监视的文件和目录。

https://github.com/mikesart/inotify-info

希望能够帮助找出限制是什么以及在哪里受到限制。


小智 6

要跟踪哪个进程占用inotify的手表(没有实例),你可以使用内核的动态ftrace功能,如果它在你的内核中启用。

您需要的内核选项是CONFIG_DYNAMIC_FTRACE.

如果尚未安装,请先安装 debugfs 文件系统。

mount -t debugfs nodev /sys/kernel/debug
Run Code Online (Sandbox Code Playgroud)

转到tracing这个 debugfs 目录的子目录下

cd /sys/kernel/debug/tracing
Run Code Online (Sandbox Code Playgroud)

启用函数调用跟踪

echo function > current_tracer
Run Code Online (Sandbox Code Playgroud)

仅过滤SyS_inotify_add_watch系统调用

echo SyS_inotify_add_watch > set_ftrace_filter
Run Code Online (Sandbox Code Playgroud)

如果不为空,则清除跟踪环缓冲区

echo > trace
Run Code Online (Sandbox Code Playgroud)

如果尚未启用,请启用跟踪

echo 1 > tracing_on
Run Code Online (Sandbox Code Playgroud)

重新启动可疑进程(在我的情况下它是 crashplan,一个备份应用程序)

观察 inotify_watch 耗尽

wc -l trace
cat trace
Run Code Online (Sandbox Code Playgroud)

完毕


cin*_*ada 6

我遇到了这个问题,这些答案都没有给你“每个进程当前使用多少个手表?”的答案。one-liners 都会告诉你有多少实例打开,这只是故事的一部分,trace 的东西只对看到新手表打开有用。

TL;DR:这将为您提供一个文件,其中包含打开的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
Run Code Online (Sandbox Code Playgroud)

这是一团乱麻,所以这就是我到达那里的方式。首先,我运行了tail一个测试文件,并查看了它打开的文件:

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
Run Code Online (Sandbox Code Playgroud)

所以,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
Run Code Online (Sandbox Code Playgroud)

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

让我们用更多的手表尝试一些东西,这次是使用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
Run Code Online (Sandbox Code Playgroud)

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

joel@opx1:~$ ls /tmp/ | wc -l
6
Run Code Online (Sandbox Code Playgroud)

优秀。我的新产品在其列表中inotifywait一个条目fd(这是这里的其他 one-liners 正在计算的),但在其fdinfo文件中有六个条目。因此,我们可以通过查阅其fdinfo文件来确定给定进程的给定 fd 使用了多少个监视。现在把它和​​上面的一些放在一起来获取一个打开通知手表的进程列表,并使用它来计算每个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
Run Code Online (Sandbox Code Playgroud)

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

如果我在同一个地方有这些 pid 代表什么进程就好了,对吧?我是这么想的。因此,在一个特别凌乱一点,我定居在呼吁dirname上两次fdinfo获得包到路径/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
Run Code Online (Sandbox Code Playgroud)

没有sudo 的情况下运行它以显示我在上面启动的进程,我得到:

joel@gladstone:~$ cat watches 
6   /proc/4906/fdinfo/3 /usr/bin/inotifywait
1   /proc/22734/fdinfo/4    /usr/bin/tail
Run Code Online (Sandbox Code Playgroud)

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