null

Кто съел весь торт или немного о tmpfs

Памяти много не бывает - эта непреложная истина преследует вычислительную технику с самых ее начал. В этой статье я рассмотрю проблемы с исчерпанием виртуальной памяти из-за tmpfs.

Ищем причину

Также как и память приложений, данные tmpfs размещаются в anonymous memory. Таким образом исчерпание памяти в tmpfs приводит к серьезным проблемам - таким же как и при исчерпании памяти - интенсивный paging/swaping на диск и замедление работы системы. При этом наблюдаются ошибки вида:
    bash: fork: Not enough space
    
Ядро при этом сигнализирует о сбое в msgbuf:
    /tmp: File system full, swap space limit exceeded
    
Единственно возможный вариант - создание креш-дампа. Как правило Solaris Crash Analysis Tool уже на этапе проверки креш-дампа обнаружит проблемы с tmpfs:
    WARNING: tmpfs filesystem on /tmp using 4.66G virtual memory

Посмотрим, каким образом можно вытащить информацию о "виновнике" проблемы. Самый простой вариант - использование команды tmpfs из Solaris Crash Analysis Tool

CAT(vmcore.3/10U)> tmpfs | sort -nk 4 | tail -5
- 0x3000b120d40      0 10485760 /tmp/test/a10
- 0x30004813580      0 104857600 /tmp/test1
- 0x3000706c940      0 104857600 /tmp/test2
- 0x30008418f40      0 104857600 /tmp/test3
- 0x30007e04d00      0 132653056 /tmp/yeeees!      


Начиная с версии 5.3 кстати самые большие файлы подсвечиваются красным.

К сожалению после покупки Sun'а компанией Oracle Solaris Crash Analysis Tool оказался доступен только обладателям поддержки (летом 2011 года поддержку проекта вообще прекращали). Поэтому я написал обертку над mdb - mdb.py и несколько скриптов, соответствующих используемым в статье командам:
    anon summary - anonusage.py
    tmpfs - tmpfsdump.py
    findfiles - findfiles.py
    
По-умолчанию они запускают mdb с опцией -k, чтобы изменить поведение нужно изменить строчку MDB_COMMAND = 'mdb -k' в скрипте


Более сложный вариант - когда файл занимает место в tmpfs, но на него был сказан unlink(2), а сам файл не был закрыт - в этом случае, как известно Solaris не удаляет файл до тех пор, пока он не будет закрыт. В этом случае выявить нарушителя спокойствия системного администратора тоже можно. Как уже было сказано, tmpfs хранит свои данные в anonymous memory, и рассказать об использовании памяти может команда SolarisCAT anon summary:

CAT(vmcore.3/10U)> anon summary
reading anon_hash...
                                                 
total:                    82647 (645M)
total with swap assigned: 45761 (357M)
total w/in-memory pages:  39739 (310M)
zero refcnt:              0

     pages          vnode    
=============== =============
  16193 (126M)  0x30009961dc0            <--- наибольшие потребители в tmpfs
  12800 (100M)  0x3000b173b80
  12800 (100M)  0x3000af00f40
  12800 (100M)  0x3000b173980
   4664 (36.4M) 0x300083ddac0
   4576 (35.7M) 0x30002a4b680
...

     pages      vnode ops
=============== =========
  61716 (482M)  *tmpfs(bss):tmp_vnodeops
  20931 (163M)  *genunix(bss):swap_vnodeops
<cut>


Выяснить информацию о vnode можно используя команды getpath и findfiles

CAT(vmcore.3/10U)> getpath 0x30009961dc0
/tmp/yeeees!
CAT(vmcore.3/10U)> findfiles -n 0x30009961dc0
Process 2603: yes                                          
file    1 @ 0x30007e48690 2878344:   file @ 0x30007e48690
vnode @ 0x30009961dc0  /tmp/yeeees!
v_op: *tmpfs(bss):tmp_vnodeops
TMPFS file
mode: 100644  size: 132653056 bytes

Проактивно мониторим

Самый простой способ мониторинга - команды df и du, более сложный вариант - DTrace, чем мы и воспользуемся. Простейший скрипт будет выглядеть следующим образом:
    tmp_resv:entry,
    tmp_unresv:entry {
        printf("%d %s %d for file %s", pid, probefunc,
                arg2, stringof(((struct tmpnode*) arg1)->tn_vnode->v_path));
    }

функция tmp_resv отвечает за резервирование места (и получает в arg2 - требуемый объем в байтах), аналогично tmp_unresv - за ее освобождение.
    
В итоге, DTrace-скрипт, выводящий статистику по процессам, использующим файлы в /tmp будет выглядеть следующим образом

#define PAGESHIFT   `_pageshift
#define PAGEOFFSET  `_pageoffset
#define btoprtob(x)    ((((x) + PAGEOFFSET) >> PAGESHIFT) << PAGESHIFT)

tmp_resv:entry
/arg3/ {
    @s["resv", pid, execname] = sum(btoprtob(arg2));
    @c["resvcount", pid, execname] = count();
}

tmp_unresv:entry {
    @s["unresv", pid, execname] = sum(btoprtob(arg2));
    @c["unresvcount", pid, execname] = count();  
}

tick-2s
{   
    printa(@s);
    printa(@c);
    
    clear(@s);
    clear(@c);
}

В DTraceToolkit ничего связанного с tmpfs я не нашел, поэтому решил написать свою утилиту. Ее можете скачать здесь: tmptop.py

Вызвать ее можно следующей командой:
    ./tmptop.py [-h] [-k user,proc,mntpt,file] [-s resv,unresv,delta,total]
    [-l limit] [interval [count]]


-h выводит данные в формате human-readable
-k выбирает ключ, по которому группируются данные
-s выбирает ключ, по которому осуществляется сортировка в выборке
-l лимитирует вывод
Параметры interval и count в представлении не нуждаются :)

Пример вывода утилиты:
    # ./tmptop.py -h -l 5
  RESV/S UNRESV/S     RESV   UNRESV    DELTA    TOTAL       VP     SIZE FILENAME
    0.50     0.00   +10.0M     0.0b   +10.0M   +10.0M 0x3000C4720C0 10.0M /tmp/file10
    0.50     0.00    +9.0M     0.0b    +9.0M    +9.0M 0x3000C4721C0 9.0M /tmp/file9
    0.50     0.00    +8.0M     0.0b    +8.0M    +8.0M 0x3000C4722C0 8.0M /tmp/file8
    0.50     0.00    +7.0M     0.0b    +7.0M    +7.0M 0x3000C4723C0 7.0M /tmp/file7
    0.50     0.00    +6.0M     0.0b    +6.0M    +6.0M 0x3000C4724C0 6.0M /tmp/file6


DELTA - это изменение потребления памяти за интервал измерения, а TOTAL - с момента запуска скрипта

 

Ограничиваем tmpfs

Самый простой способ установить ограничение на tmpfs - установить свойство size в опциях монтирования tmpfs, тогда строчка монтирования в vfstab будет выглядеть так:
    swap    -       /tmp    tmpfs   -       yes     size=100m

После этого в avail появится соответствующее значение:
# df -F tmpfs -h
Filesystem             size   used  avail capacity  Mounted on
swap                   383M   392K   382M     1%    /etc/svc/volatile
swap                   100M    32K   100M     1%    /tmp
swap                   382M    48K   382M     1%    /var/run


Ограничение будет вести себя следующим образом:
# mkfile 101m /tmp/oopsy
Could not set length of /tmp/oopsy: No space left on device
Jan 17 14:56:24 unknown tmpfs: WARNING: /tmp: File system full, swap space limit exceeded


Более сложный способ - вводить ограничение на anonymous memory, в этом случае можно использовать возможность Solaris Resource Control, в частности свойство zone.max-swap (в этом случае ограничение устанавливается на зону).
# zonecfg -z test-sparse
zonecfg:test-sparse> add capped-memory
zonecfg:test-sparse:capped-memory> set swap=128m
zonecfg:test-sparse:capped-memory> info
capped-memory:
        [swap: 128M]
zonecfg:test-sparse:capped-memory> end
zonecfg:test-sparse> verify
zonecfg:test-sparse> commit
zonecfg:test-sparse> exit


Также как и в случае со свойством size, будет выдаваться сообщение "File system full, swap space limit exceeded". Однако с этим параметром надо быть аккуратнее, так как свойство max-swap влияет как на tmpfs так и на память приложений.

И наконец, в-третьих, в tmpfs есть параметр tmpfs_minfree (указывается в страницах), указывающее, сколько памяти должно оставаться свободной в anonymous memory, чтобы tmpfs могла выделять память. По-умолчанию это свойство равно 2м мегабайтам, что для современных систем явно недостаточно. Установить его можно как через /etc/system:
    set tmpfs:tmpfs_minfree = 0x3200

Так и на запущенной системе через mdb:
    echo 'tmpfs_minfree/W0x3200' | mdb -kw
    
Подробнее об этой опции можно почитать в Solaris Tunable Parameters Reference Manual: http://docs.oracle.com/cd/E19683-01/806-7009/chapter2-49/index.html


 

mdb.py
anonusage.py
tmpfsdump.py
findfiles.py
tmptop.py

К списку статей

 

Интересуюсь по большей части системным анализом программного обеспечения: поиском багов и анализом неисправностей, а также системным программированием (и не оставляю надежд запилить свою операционку, хотя нехватка времени сказывается :) ). Программированием увлекаюсь с 12 лет, но так уж получилось, что стал я инженером.

Основная сфера моей деятельности связана с поддержкой Solaris и оборудования Sun/Oracle, хотя в последнее время к ним прибавились технологии виртуализации (линейка Citrix Xen) и всякое разное от IBM - от xSeries до Power. Учусь на кафедре Вычислительной Техники НИУ ИТМО.

See you...out there!

http://www.facebook.com/profile.php?id=100001947776045
https://twitter.com/AnnoyingBugs

Ничего не найдено. n is 0