diff options
author | Giampaolo Rodola <g.rodola@gmail.com> | 2016-09-20 15:14:24 +0200 |
---|---|---|
committer | Giampaolo Rodola <g.rodola@gmail.com> | 2016-09-20 15:14:24 +0200 |
commit | 8504d930cb68e1e079e03df55112312505ed260f (patch) | |
tree | ac5916ba5a77083b9a99cf124906bcc45414057c | |
parent | c4cad5a96f1d4c6d383d51e0da2344cedcf7028e (diff) | |
download | psutil-8504d930cb68e1e079e03df55112312505ed260f.tar.gz |
issue #887: calculate avail mem on older kernels where MemAvailable
column is not available.
-rw-r--r-- | docs/index.rst | 31 | ||||
-rw-r--r-- | psutil/_pslinux.py | 123 | ||||
-rwxr-xr-x | scripts/top.py | 3 |
3 files changed, 107 insertions, 50 deletions
diff --git a/docs/index.rst b/docs/index.rst index d64ab43a..675c028b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -172,24 +172,27 @@ Memory .. function:: virtual_memory() Return statistics about system memory usage as a namedtuple including the - following fields, expressed in bytes: - - - **total**: total physical memory available. - - **available**: the actual amount of available memory that can be given - instantly to processes that request more memory in bytes; this is - calculated by summing different memory values depending on the platform - and it is supposed to be used to monitor actual memory usage in a cross - platform fashion. + following fields, expressed in bytes. + Main metrics: + + - **total**: total physical memory. + - **available**: the memory that can be given instantly to processes without + the system going into swap. + This is calculated by summing different memory values depending on the + platform and it is supposed to be used to monitor actual memory usage in a + cross platform fashion. - **percent**: the percentage usage calculated as ``(total - available) / total * 100``. - - **used**: memory used, calculated differently depending on the platform and - designed for informational purposes only. - - **free**: memory not being used at all (zeroed) that is readily available; - note that this doesn't reflect the actual memory available (use 'available' - instead). - Platform-specific fields: + Other metrics: + - **used**: memory used, calculated differently depending on the platform and + designed for informational purposes only. ``total - used`` does not + necessarily matches ``available``. + - **free**: memory not being used at all (zeroed) that is readily available; + note that this doesn't reflect the actual memory available (use + ``available`` instead). ``total - free`` does not necessarily match + ``used``. - **active** *(UNIX)*: memory currently in use or very recently used, and so it is in RAM. - **inactive** *(UNIX)*: memory that is marked as not used. diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py index 1a0b57bc..a9606fc5 100644 --- a/psutil/_pslinux.py +++ b/psutil/_pslinux.py @@ -288,16 +288,71 @@ except Exception: # ===================================================================== -def virtual_memory(): - """Report memory stats trying to match "free" and "vmstat -s" cmdline - utility values as much as possible. +def calculate_avail_vmem(mems): + """Fallback for kernels < 3.14 where /proc/meminfo does not provide + "MemAvailable:" column (see: https://blog.famzah.net/2014/09/24/) + trying to reimplement the algorithm outlined here: + https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/ + commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 + + XXX: on my machine (Ubuntu 16.04, kernel 4.4.0.36, 16B ram) this + calculation differs by 1% than "MemAvailable:". + It is still way more realistic than doing (free + cached) though. + """ + # Fallback for very old distros. According to + # https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/ + # commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 + # ...long ago "avail" was calculated as (free + cached). + # We might fallback in such cases: + # "Active(file)" not available: 2.6.28 / Dec 2008 + # "Inactive(file)" not available: 2.6.28 / Dec 2008 + # "SReclaimable:" not available: 2.6.19 / Nov 2006 + # /proc/zoneinfo not available: 2.6.13 / Aug 2005 + free = mems[b'MemFree:'] + fallback = free + mems.get(b"Cached:", 0) + try: + lru_active_file = mems[b'Active(file):'] + lru_inactive_file = mems[b'Inactive(file):'] + slab_reclaimable = mems[b'SReclaimable:'] + except KeyError: + return fallback + try: + f = open_binary('%s/zoneinfo' % get_procfs_path()) + except IOError: + return fallback # kernel 2.6.13 + + watermark_low = 0 + with f: + for line in f: + line = line.strip() + if line.startswith(b'low'): + watermark_low += int(line.split()[1]) + watermark_low *= PAGESIZE + watermark_low = watermark_low + + avail = free - watermark_low + pagecache = lru_active_file + lru_inactive_file + pagecache -= min(pagecache / 2, watermark_low) + avail += pagecache + avail += slab_reclaimable - min(slab_reclaimable / 2.0, watermark_low) + return int(avail) + - This implementation uses procps-ng-3.3.12 as a reference (2016-09-18): +def virtual_memory(): + """Report virtual memory stats. + This implementation matches "free" and "vmstat -s" cmdline + utility values and procps-ng-3.3.12 source was used as a reference + (2016-09-18): https://gitlab.com/procps-ng/procps/blob/ 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c - For reference, procps-ng-3.3.10 is the version available on Ubuntu 16.04. + + Note about "available" memory. Up until psutil 4.3 it was + calculated as "avail = (free + buffers + cached)". Now + "MemAvailable:" column (kernel 3.14) from /proc/meminfo is used as + it's more accurate. + That matches "available" column in newer versions of "free". """ missing_fields = [] mems = {} @@ -306,20 +361,25 @@ def virtual_memory(): fields = line.split() mems[fields[0]] = int(fields[1]) * 1024 - # Note: these info are available also as cext.linux_sysinfo(). + # /proc doc states that the available fields in /proc/meminfo vary + # by architecture and compile options, but these 3 values are also + # returned by sysinfo(2); as such we assume they are always there. total = mems[b'MemTotal:'] free = mems[b'MemFree:'] buffers = mems[b'Buffers:'] - cached = mems[b"Cached:"] - # "free" cmdline utility sums cached + reclamaible (available - # since kernel 2.6.19): - # https://gitlab.com/procps-ng/procps/ - # blob/195565746136d09333ded280cf3ba93853e855b8/proc/sysinfo.c#L761 - # Older versions of procps added slab memory instead. - # This got changed in: - # https://gitlab.com/procps-ng/procps/commit/ - # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e - cached += mems.get(b"SReclaimable:", 0) + + try: + cached = mems[b"Cached:"] + except KeyError: + cached = 0 + missing_fields.append('cached') + else: + # "free" cmdline utility sums reclaimable to cached. + # Older versions of procps used to add slab memory instead. + # This got changed in: + # https://gitlab.com/procps-ng/procps/commit/ + # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e + cached += mems.get(b"SReclaimable:", 0) # since kernel 2.6.19 try: shared = mems[b'Shmem:'] # since kernel 2.6.32 @@ -339,8 +399,6 @@ def virtual_memory(): try: inactive = mems[b"Inactive:"] except KeyError: - # https://gitlab.com/procps-ng/procps/blob/ - # 195565746136d09333ded280cf3ba93853e855b8/proc/sysinfo.c#L758 try: inactive = \ mems[b"Inact_dirty:"] + \ @@ -350,31 +408,28 @@ def virtual_memory(): inactive = 0 missing_fields.append('inactive') - # https://gitlab.com/procps-ng/procps/blob/ - # 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L769 used = total - free - cached - buffers if used < 0: # May be symptomatic of running within a LCX container where such # values will be dramatically distorted over those of the host. used = total - free - # Note: starting from 4.4.0 we match "free" "available" column. - # Before 4.4.0 we calculated it as: - # >>> avail = free + buffers + cached - # ...which matched htop. - # free and htop available memory differs as per: - # http://askubuntu.com/a/369589 - # http://unix.stackexchange.com/a/65852/168884 + # - starting from 4.4.0 we match free's "available" column. + # Before 4.4.0 we calculated it as (free + buffers + cached) + # which matched htop. + # - free and htop available memory differs as per: + # http://askubuntu.com/a/369589 + # http://unix.stackexchange.com/a/65852/168884 + # - MemAvailable has been introduced in kernel 3.14 try: avail = mems[b'MemAvailable:'] except KeyError: - # Column is not there; it's likely this is an older kernel. - # In this case "free" won't show an "available" column. - # Also, procps does some hacky things: - # https://gitlab.com/procps-ng/procps/blob/ - # /24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L774 - # We won't. Like this we'll match "htop". - avail = free + buffers + cached + avail = calculate_avail_vmem(mems) + + if avail < 0: + avail = 0 + missing_fields.append('available') + # If avail is greater than total or our calculation overflows, # that's symptomatic of running within a LCX container where such # values will be dramatically distorted over those of the host. diff --git a/scripts/top.py b/scripts/top.py index 1caa8136..0c99047e 100755 --- a/scripts/top.py +++ b/scripts/top.py @@ -137,11 +137,10 @@ def print_header(procs_status, num_procs): perc)) mem = psutil.virtual_memory() dashes, empty_dashes = get_dashes(mem.percent) - used = mem.total - mem.available line = " Mem [%s%s] %5s%% %6s/%s" % ( dashes, empty_dashes, mem.percent, - str(int(used / 1024 / 1024)) + "M", + str(int(mem.used / 1024 / 1024)) + "M", str(int(mem.total / 1024 / 1024)) + "M" ) print_line(line) |