понедельник, 9 марта 2015 г.

Шото с памятью моей стало.

Я, как пользователь, или как администратор, операционной системы, местами, весьма заинтересован в том чтобы иметь представление сколько памяти занято, и не начала ли система вылазить в swap. Если она заберется в swap  по полной, то что-то убивать будет несколько поздно. Вернее не то чтобы поздно, а просто очень медленно: сначала определить что все тормозит из-за этого, потом найти что можно пристрелить, а потом еще и пристрелить. Лучше до этого не доводить. Поэтому так популярны всякие мониторилки памяти, которые висят в поле видимости, есть не просят, и в случае чего, способны предупредить что память вот-вот заканчивается.

Парадоксально, но факт, в *nix'овом мире всякие мониторилки очень популярны еще и потому, что средства предоставляемые системой, легко ответить на вопрос "все плохо или все хорошо?" не позволяют, а требуют дополнительной работы мозга. Например, в самых дружелюбных *nix'ах статистика по памяти в системном мониторе выглядит вот так:



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

Вы думали что понимают? А вот и фиг там. Из самой дружелюбной системы, переносимся в самую открытую(ирония). В Linux видами колбасных обрезков, в которых следует соображать пользователю, не балуют:

$ free -m
             total       used       free     shared    buffers     cached
Mem:          2048       1820        228          0        174       1205
-/+ buffers/cache:        440       1607
Swap:         2086          0       2086



Правда конечно стоит помнить, что "по настоящему used" и "по настоящему free" находятся во второй строчке. Потому как память, еще доступная приложениям, это по-сути free + buffers + cached, ну и соответственно buffers и cached, хотя и used, но система их освободит, если будет нужно. Если эта арифметика еще не задолбала можно конечно добавить трешачка, и посмотреть в /proc/meminfo

$ cat /proc/meminfo
MemTotal:      2097152 kB
MemFree:        233448 kB
Buffers:        178372 kB
Cached:        1234100 kB
 
SwapCached:         20 kB
Active:         957732 kB
Inactive:       611532 kB
HighTotal:           0 kB
HighFree:            0 kB
LowTotal:      2097152 kB
LowFree:        233448 kB
SwapTotal:     2136636 kB
SwapFree:      2136592 kB
Dirty:             460 kB
Writeback:           0 kB
AnonPages:      150672 kB
Mapped:          17888 kB
Slab:           214924 kB
PageTables:       4720 kB
NFS_Unstable:        0 kB
Bounce:              0 kB
CommitLimit:   3185212 kB
Committed_AS:   301808 kB
VmallocTotal: 34359738367 kB
VmallocUsed:      1292 kB
VmallocChunk: 34359737055 kB


В принципе первые четыре строки нам знакомы по выводу free, а остальные: тема для отдельной беседы. Как посчитать процент занятой памяти? - Например вот так:

awk '
BEGIN{total=0;free=0;buffers=0;cached=0} 
/^MemTotal/{total=$2} 
/^MemFree/{free=$2} 
/^Buffers/{buffers=$2} 
/^Cached/{cached = $2} 
END{printf "%4.2f",(total - buffers - cached - free )/total*100}' /proc/meminfo

Для тех кто не знает awk:  блок BEGIN выполняется вначале работы, блок END - в конце, остальные - на каждой строчке файла. В нашем случае, на нужной строчке просто сохраняем соответствующие числа в переменные, и потом считаем. В принципе, ничего сложного. Каково же было мое удивление, когда xmobar начал выдавать отрицательные значения процента используемой памяти:


Причина как оказалась достаточно банальна. В новых Linux'ах в /proc/meminfo добавили еще одну строчку:

$ cat /proc/meminfo
MemTotal:         949328 kB
MemFree:          542568 kB
MemAvailable:     870912 kB
Buffers:           22636 kB
Cached:           321972 kB

SwapCached:            0 kB
Active:           216328 kB
Inactive:         163272 kB
Active(anon):      35100 kB
Inactive(anon):      448 kB
Active(file):     181228 kB
Inactive(file):   162824 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:        102396 kB
SwapFree:         102396 kB
Dirty:                 0 kB
Writeback:             0 kB
AnonPages:         35004 kB
Mapped:            36504 kB
Shmem:               560 kB
Slab:              15724 kB
SReclaimable:       9540 kB
SUnreclaim:         6184 kB
KernelStack:        1352 kB
PageTables:         1096 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:      577060 kB
Committed_AS:     146536 kB
VmallocTotal:    1105920 kB
VmallocUsed:        4636 kB
VmallocChunk:     864076 kB


И что-то пошло не так. По-сути awk'шный скриптик который выше все еще работает как нужно:

$ awk 'BEGIN{total=0;free=0;buffers=0;cached=0} /^MemTotal/{total=$2} /^MemFree/{free=$2} /^Buffers/{buffers=$2} /^Cached/{cached = $2} END{printf "%4.2f\n",(total - buffers - cached - free )/total*100}' /proc/meminfo
6.59


хотя теперь можно и как-то так:

 awk 'BEGIN{total=0;available=0} /^MemTotal/{total=$2} /^MemAvailable/{available=$2} END{printf "%4.2f",(total - available)/total*100}' /proc/meminfo
8.30


Разница в результатах небольшая, но есть, потому что  

MemAvailable != MemFree + Buffers + Cached

почти, но, не совсем. В принципе для целей "увидеть когда все плохо" - разница несущественна.  То есть, как бы, если предполагать, что человек который писал xmobar адекватен, то никаких проблем в принципе не должно было бы быть. Поскольку xmobar написать на Haskell, то ожидать неадекватного автора сложно, все-таки порог вхождения поболее будет, и php-набыдлокодить не получится. А вот и получится:

fileMEM = readFile "/proc/meminfo"

parseMEM :: IO [Float]
parseMEM =
    do file <- br="" filemem="">       let content = map words $ take 4 $ lines file
           [total, free, buffer, cache] = map (\line -> (read $ line !! 1 :: Float) / 1024) content
           rest = free + buffer + cache
           used = total - rest
           usedratio = used / total
       return [usedratio, total, free, buffer, cache, rest, used]


 Переводя с непонятного: берем первые четыре строчки файла, предполагаем что на первой лежит total, на второй free, и так далее. Без всяких проверок. Знаете почему так? Потому что Haskell фигово связан с реальным миром(зато идеологически правильно),  и поскольку язык скорее экспериментальный, красота кода, важнее практического результата и стабильности, тем более что стабильность частично обещается компилятором. В итоге трахаешься час шобы получить красивую строчку, и пофигу шо она делает только половину того шо должна. Конечно, в более поздних версиях автору пришлось потрахаться еще чуть-чуть:

fileMEM = readFile "/proc/meminfo"

parseMEM :: IO [Float]
parseMEM =
    do file <- br="" filemem="">       let content = map words $ take 8 $ lines file
           info = M.fromList $ map (\line -> (head line, (read $ line !! 1 :: Float) / 1024)) content
           [total, free, buffer, cache] = map (info M.!) ["MemTotal:", "MemFree:", "Buffers:", "Cached:"]
           available = M.findWithDefault (free + buffer + cache) "MemAvailable:" info

           used = total - available
           usedratio = used / total
           freeratio = free / total
           availableratio = available / total
       return [usedratio, freeratio, availableratio, total, free, buffer, cache, available, used]


Правда, более поздняя версия в Debian'е еще недоступна.