Kto zużywa moje zasoby inotify?

49

Po ostatnim uaktualnieniu do Fedory 15 stwierdzam, że wiele narzędzi nie działa z błędami w następujący sposób:

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

To nie tylko tailzgłaszanie problemów z inotify. Czy jest jakiś sposób na przesłuchanie jądra, aby dowiedzieć się, jaki proces lub procesy zużywają zasoby inotify? Obecne sysctlustawienia związane z inotify wyglądają tak:

fs.inotify.max_user_instances = 128
fs.inotify.max_user_watches = 8192
fs.inotify.max_queued_events = 16384
Larsks
źródło

Odpowiedzi:

39

Wygląda na to, że jeśli proces utworzy instancję inotify za pomocą inotify_init (), wynikowy plik reprezentujący skrypt filedescriptor w systemie plików / proc jest dowiązaniem symbolicznym do (nieistniejącego) pliku „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

O ile nie zrozumiałem źle tej koncepcji, poniższe polecenie powinno pokazać listę procesów (ich reprezentacja w / proc), posortowanych według liczby używanych przez nich instancji inotify.

for foo in /proc/*/fd/*; do readlink -f $foo; done | grep inotify | sort | uniq -c | sort -nr
Petr Uzel
źródło
8
Wspaniale, dziękuję! Nie wiedziałem o inotify i-węzłach pojawiających się w / proc. Dla moich celów polecenie można uprościć w następujący sposób:find /proc/*/fd/* -type l -lname 'anon_inode:inotify' -print
larsks
Cieszę się, że to pomogło. A twoje rozwiązanie z find -lname jest rzeczywiście o wiele ładniejsze niż moje z pętlą i readlink.
Petr Uzel
3
Pamiętaj, że możesz również nie mieć zegarków (nie instancji). Np. W moim systemie daje to mniejszą liczbę nastolatków, ale istnieje wiele dziesiątek tysięcy zegarków z wyszukiwania na pulpicie KDE. Jest tak źle nie jest łatwiejszy sposób, aby sprawdzić, ile zegarki / instancje są w użyciu, ponieważ jądro wyraźnie wie ...
derobert
Aby wyświetlić wiersze poleceń szkodliwych programów:find /proc/*/fd/* -type l -lname 'anon_inode:inotify' -exec sh -c 'cat $(dirname {})/../cmdline; echo ""' \; 2>/dev/null
Mark K Cowan
@derobert Zrobiłem skrypt do wyświetlania procesów zużywających obserwatorów, o co zwykle dba się. Zobacz moją odpowiedź poniżej.
oligofren
25

Prawdopodobnie zabrakło Ci raczej inotify zegarków niż instancji. Aby dowiedzieć się, kto tworzy wiele zegarków:

  1. Czy, echo 1 >> /sys/kernel/debug/tracing/events/syscalls/sys_exit_inotify_add_watch/enableaby umożliwić śledzenie dodawania zegarka;
  2. Zrobić cat /sys/kernel/debug/tracing/tracing_enabled, aby upewnić się, że jest on ustawiony na 1, a jeżeli nie jest to zrobić echo 1 >> /sys/kernel/debug/tracing/tracing_enabled;
  3. Zrestartuj procesy z instancjami inotify (określonymi zgodnie z opisem w odpowiedzi Petra Uzela), które podejrzewasz o utworzenie wielu zegarków; i
  4. Przeczytaj plik, /sys/kernel/debug/tracing/traceaby zobaczyć, ile zegarków zostało utworzonych i według których procesów.

Kiedy skończysz, upewnij się, że echo 0 w pliku włączania (i pliku śledzenia_włączonego, jeśli musiałeś również to włączyć), aby wyłączyć śledzenie, aby nie ponieść wydajności w wyniku kontynuowania śledzenia.

Jonathan Kamens
źródło
Była to aplikacja do tworzenia kopii zapasowych tworząca wiele niedorzecznych zegarków, a rozwiązanie w zaakceptowanej odpowiedzi pomogło zidentyfikować winowajcę. Jednak wcześniej nie znałem śledzenia połączeń systemowych, które tutaj wykazałeś. Bardzo fajny. Dzięki za informację!
larsks
2
czy na pewno jest to „/ sys / kernel / debug / tracing / tracing_enabled”? W moim systemie wydaje się, że poprawną ścieżką jest '/ sys / kernel / debug / tracing / tracing_on' ...
Kartoch 10.04.2013
W Gentoo Linux nie ma / sys / kernel / debug / tracing / events / syscalls / sys_exit_inotify_add_watch / enable ani / sys / kernel / debug / tracing / tracing_enabled na Gentoo Linux, ale / sys / kernel / debug / tracing / tracing_enabled istnieje. Dlaczego?
zeekvfu
Jak sugeruje @Kartoch, musisz zrobić echo 1 | sudo tee /sys/kernel/debug/tracing/tracing_onna nowoczesnych dystrybucjach (Ubuntu 18.04.2 LTS).
oligofren
Nie wystarczyło mi wykonanie poleceń, musiałem też: `cd / sys / kernel / debug / tracing /; funkcja echa> current_tracer; echo SyS_inotify_add_watch> set_ftrace_filter`
oligofren
7

Jak powiedział @Jonathan Kamens, prawdopodobnie kończy Ci się zegarki. Mam predefiniowanych skryptów , inotify-consumers, który wymienia to dla ciebie:

$ time inotify-consumers  | head

   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

real    0m0.099s
user    0m0.042s
sys 0m0.062s

Tutaj szybko widać, dlaczego domyślny limit obserwatorów 8K jest zbyt mały na maszynie programistycznej, ponieważ tylko instancja WebStorm szybko to zwiększa, gdy napotyka node_modulesfolder z tysiącami folderów. Dodaj obserwatora pakietu internetowego, aby zagwarantować problemy ...

Po prostu skopiuj zawartość skryptu (lub pliku na GitHub) i umieść go gdzieś w twoim $PATH, jak /usr/local/bin. Dla porównania, główna treść skryptu jest po prostu taka

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

Jeśli zastanawiasz się, jak zwiększyć limity, możesz to zrobić na stałe:

echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
oligofren
źródło
1
Wiele innych sugestii nie działało dla mnie dobrze, ale ten skrypt działał świetnie na Fedorze 29. Dzięki!
Richard S. Hall
6

Natknąłem się na ten problem i żadna z tych odpowiedzi nie daje odpowiedzi „ile zegarków obecnie używa każdy proces?” Wszystkie linijki pokazują, ile instancji jest otwartych, co jest tylko częścią historii, a ślady są przydatne tylko do oglądania nowych zegarków.

TL; DR: Otrzymasz plik z listą otwartych inotifyinstancji i liczbą posiadanych zegarków , wraz z pidami i plikami binarnymi, które je zrodziły, posortowanymi w kolejności malejącej według liczby zegarków:

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

To wielka kula bałaganu, więc oto jak się tam dostałem. Aby rozpocząć, uruchomiłem tailplik testowy i spojrzałem na otwarte fd:

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

Tak więc 4 to fd, który chcemy zbadać. Zobaczmy, co w fdinfotym jest:

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

To wygląda jak wpis do zegarka na dole!

Spróbujmy czegoś z większą liczbą zegarków, tym razem z inotifywaitnarzędziem, po prostu oglądając wszystko, co jest w /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

Aha! Więcej wpisów! Powinniśmy zatem mieć sześć rzeczy /tmp:

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

Doskonały. Mój nowy inotifywaitma jeden wpis na fdliście (to, co liczą inne tutaj jednowierszowe), ale sześć wpisów w swoim fdinfopliku. Możemy więc dowiedzieć się, ile zegarków używa dany FD dla danego procesu, sprawdzając jego fdinfoplik. Teraz, aby połączyć to z niektórymi z powyższych, aby pobrać listę procesów, które powiadomiły zegarki otwarte i użyj tego, aby policzyć wpisy w każdym z nich fdinfo. Jest to podobne do powyższego, więc po prostu zrzucę tutaj linijkę:

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

Jest tu trochę grubych rzeczy, ale podstawy są takie, że używam awkdo budowania fdinfościeżki z danych lsofwyjściowych, chwytając numer pid i fd, usuwając z niego flagę u / r / w. Następnie dla każdej skonstruowanej fdinfościeżki liczę liczbę inotifywierszy i wyprowadzam liczbę i pid.

Byłoby miło, gdybym miał procesy, które te pidy reprezentują w tym samym miejscu, prawda? Tak myślałem. Tak więc, w szczególnie brudny bit, zdecydowałem się na wywołanie dirnamedwukrotnie na fdinfodrodze do uzyskania Pack /proc/<pid>, dodając /exedo niego, a następnie uruchomić readlinkna to , aby uzyskać nazwę exe procesu. Wrzuć to tam, posortuj według liczby zegarków i przekieruj do pliku w celu bezpiecznego przechowywania, a otrzymamy:

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

Działając bez sudo, aby pokazać moje procesy, które uruchomiłem powyżej, otrzymuję:

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

Doskonały! Lista procesów, fd i liczba używanych zegarków , dokładnie tego potrzebowałem.

cincodenada
źródło
Korzystając lsofz tego celu, polecam używanie -nPflag, aby uniknąć niepotrzebnego wyszukiwania odwrotnego DNS i nazw portów. W tym konkretnym przypadku -bwzalecane jest również dodanie, aby uniknąć potencjalnego blokowania wywołań systemowych. To powiedziawszy, z lsofpożeraniem 3 sekund zegara ściennego na mojej skromnej stacji roboczej (z czego 2 sekundy spędzam w jądrze), takie podejście jest dobre do eksploracji, ale niestety nieodpowiednie do celów monitorowania.
BertD,
Stwierdziłem, że twój jednowierszowy program jest bardzo wolny, ale możliwe jest miłe ulepszenie, kosztem pewnej utraty informacji (zobaczymy obserwatorów na proces, a nie na deskryptor pliku): Najpierw wygeneruj plik pośredni: lsof | awk '/a_inode/ { gsub(/[urw]$/,"",$4); print "/proc/"$2"/fdinfo/"$4; }' | sed 's/fdinfo.*//' | sort | uniq > uniq-onastępniecat uniq-o | while read fdi; do count=$(cat ${fdi}fdinfo/* | grep -c inotify 2>/dev/null); exe=$(readlink ${fdi}exe); echo -e $count"\t"${fdi}"\t"$exe; done > watches
LLlAMnYP
5

Aby śledzić, które procesy zużywają zegarki inotify (nie instancje), możesz użyć funkcji dynamicznego ftrace jądra, jeśli jest ono włączone w jądrze.

Potrzebna jest opcja jądra CONFIG_DYNAMIC_FTRACE.

Najpierw podłącz system plików debugfs, jeśli nie jest jeszcze zamontowany.

mount -t debugfs nodev /sys/kernel/debug

Przejdź do tracingpodkatalogu tego katalogu debugfs

cd /sys/kernel/debug/tracing

Włącz śledzenie wywołań funkcji

echo function > current_tracer

Filtruj tylko SyS_inotify_add_watchwywołania systemowe

echo SyS_inotify_add_watch > set_ftrace_filter

Wyczyść bufor pierścienia śledzenia, jeśli nie był pusty

echo > trace

Włącz śledzenie, jeśli nie jest jeszcze włączone

echo 1 > tracing_on

Uruchom ponownie podejrzany proces (w moim przypadku był to plan awaryjny, aplikacja do tworzenia kopii zapasowych)

Zobacz wyczerpanie inotify_watch

wc -l trace
cat trace

Gotowy

karabin srebrny
źródło
3
find /proc/*/fd/* -type l -lname 'anon_inode:inotify' 2>/dev/null | cut -f 1-4 -d'/' |  sort | uniq -c  | sort -nr
Paweł
źródło
1

Zmodyfikowałem skrypt obecny powyżej, aby wyświetlić listę procesów, które zużywają zasoby inotify :

ps -p `find /proc/*/fd/* -type l -lname 'anon_inode:inotify' -print | sed s/'^\/proc\/'/''/ | sed s/'\/fd.*$'/''/`

Myślę, że istnieje sposób na zastąpienie mojego podwójnego seda .


Tak. Użyj albo

cut -f 3 -d '/'   

lub

sed -e 's/^\/proc\/\([0-9]*\)\/.*/\1'  

a dostaniesz tylko pid.
Ponadto, jeśli dodasz

2> /dev/null  

w znalezieniu pozbędziesz się wszelkich irytujących linii błędów wyrzucanych przez find. To by działało:

ps -p $(find /proc/*/fd/* -type l -lname 'anon_inode:inotify' -print 2> /dev/null | sed -e 's/^\/proc\/\([0-9]*\)\/.*/\1/')
Arkadij Kuzhel
źródło