summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiampaolo Rodola <g.rodola@gmail.com>2016-02-03 17:13:59 +0100
committerGiampaolo Rodola <g.rodola@gmail.com>2016-02-03 17:13:59 +0100
commit7f0e0939b56a730ef59c0d743f02019662f55f4d (patch)
tree677ab3a5f012fbe2a4d5b49de73c4508ee691755
parentecf0fdc39382620b42168aff6cc5fcd88988bf5b (diff)
downloadpsutil-7f0e0939b56a730ef59c0d743f02019662f55f4d.tar.gz
fix #744 aka #753: implement memory USS / PSS on Linux
-rw-r--r--HISTORY.rst1
-rw-r--r--README.rst2
-rw-r--r--docs/index.rst6
-rw-r--r--psutil/_pslinux.py28
-rw-r--r--test/_linux.py18
5 files changed, 48 insertions, 7 deletions
diff --git a/HISTORY.rst b/HISTORY.rst
index 27b628b9..96af5586 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -6,6 +6,7 @@ Bug tracker at https://github.com/giampaolo/psutil/issues
**Enhancements**
- #732: Process.environ(). (patch by Frank Benkstein)
+- #753: [Linux] Process memory USS and PSS.
**Bug fixes**
diff --git a/README.rst b/README.rst
index 81fe9bb3..cebf841c 100644
--- a/README.rst
+++ b/README.rst
@@ -239,7 +239,7 @@ Process management
>>> p.memory_info()
pmem(rss=7471104, vms=68513792)
>>> p.memory_info_ex()
- extmem(rss=9662464, vms=49192960, shared=3612672, text=2564096, lib=0, data=5754880, dirty=0)
+ pextmem(rss=10915840, vms=67608576, shared=3313664, text=2310144, lib=0, data=7262208, dirty=0, uss=9830400, pss=1216512)
>>> p.memory_maps()
[pmmap_grouped(path='/lib/x8664-linux-gnu/libutil-2.15.so', rss=32768, size=2125824, pss=32768, shared_clean=0, shared_dirty=0, private_clean=20480, private_dirty=12288, referenced=32768, anonymous=12288, swap=0),
pmmap_grouped(path='/lib/x8664-linux-gnu/libc-2.15.so', rss=3821568, size=3842048, pss=3821568, shared_clean=0, shared_dirty=0, private_clean=0, private_dirty=3821568, referenced=3575808, anonymous=3821568, swap=0),
diff --git a/docs/index.rst b/docs/index.rst
index 1c564783..aa1bd36b 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1006,9 +1006,9 @@ Process class
+--------+---------+-------+---------+--------------------+
| dirty | | | | nonpaged_pool |
+--------+---------+-------+---------+--------------------+
- | | | | | pagefile |
+ | uss | | | | pagefile |
+--------+---------+-------+---------+--------------------+
- | | | | | peak_pagefile |
+ | pss | | | | peak_pagefile |
+--------+---------+-------+---------+--------------------+
| | | | | private |
+--------+---------+-------+---------+--------------------+
@@ -1022,6 +1022,8 @@ Process class
>>> p.memory_info_ex()
pextmem(rss=15491072, vms=84025344, shared=5206016, text=2555904, lib=0, data=9891840, dirty=0)
+ .. versionchanged:: 3.5.0 added `uss` and `pss` fields on Linux.
+
.. method:: memory_percent()
Compare physical system memory to process resident memory (RSS) and
diff --git a/psutil/_pslinux.py b/psutil/_pslinux.py
index ee613564..04c95cf3 100644
--- a/psutil/_pslinux.py
+++ b/psutil/_pslinux.py
@@ -56,6 +56,7 @@ __extra__all__ = [
# --- constants
+HAS_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid())
HAS_PRLIMIT = hasattr(cext, "linux_prlimit")
# RLIMIT_* constants, not guaranteed to be present on all kernels
@@ -217,7 +218,7 @@ svmem = namedtuple(
'svmem', ['total', 'available', 'percent', 'used', 'free',
'active', 'inactive', 'buffers', 'cached'])
-pextmem = namedtuple('pextmem', 'rss vms shared text lib data dirty')
+pextmem = namedtuple('pextmem', 'rss vms shared text lib data dirty uss pss')
pmmap_grouped = namedtuple(
'pmmap_grouped', ['path', 'rss', 'size', 'pss', 'shared_clean',
@@ -972,7 +973,9 @@ class Process(object):
int(vms) * PAGESIZE)
@wrap_exceptions
- def memory_info_ex(self):
+ def memory_info_ex(self,
+ _private_re=re.compile(b"Private.*:\s+(\d+)"),
+ _shared_re=re.compile(b"Shared.*:\s+(\d+)")):
# ============================================================
# | FIELD | DESCRIPTION | AKA | TOP |
# ============================================================
@@ -983,13 +986,30 @@ class Process(object):
# | lib | library (unused in Linux 2.6) | lrs | |
# | data | data + stack | drs | DATA |
# | dirty | dirty pages (unused in Linux 2.6) | dt | |
+ # | -----------------------------------------------------------
+ # | uss | unique set size ("real memory") | | |
+ # | pss | proportional set size | | |
# ============================================================
with open_binary("%s/%s/statm" % (self._procfs_path, self.pid)) as f:
vms, rss, shared, text, lib, data, dirty = \
[int(x) * PAGESIZE for x in f.readline().split()[:7]]
- return pextmem(rss, vms, shared, text, lib, data, dirty)
+ if HAS_SMAPS:
+ # Note: using two regexes is faster than reading the file
+ # line by line.
+ # XXX: on Python 3 the 2 regexes are 30% slower than on
+ # Python 2 though. Figure out why.
+ with open_binary("%s/%s/smaps" % (self._procfs_path, self.pid),
+ buffering=BIGGER_FILE_BUFFERING) as f:
+ smaps_data = f.read()
+ uss = sum(map(int, _private_re.findall(smaps_data))) * 1024
+ pss = sum(map(int, _shared_re.findall(smaps_data))) * 1024
+ else:
+ # usually means we're on kernel < 2.6.14 or CONFIG_MMU kernel
+ # configuration option is not enabled.
+ uss = pss = 0
+ return pextmem(rss, vms, shared, text, lib, data, dirty, uss, pss)
- if os.path.exists('/proc/%s/smaps' % os.getpid()):
+ if HAS_SMAPS:
@wrap_exceptions
def memory_maps(self):
diff --git a/test/_linux.py b/test/_linux.py
index d36afc53..637ad1f7 100644
--- a/test/_linux.py
+++ b/test/_linux.py
@@ -599,6 +599,24 @@ class LinuxSpecificTestCase(unittest.TestCase):
self.assertEqual(psutil.Process().exe(), "/home/foo")
self.assertEqual(psutil.Process().cwd(), "/home/foo")
+ def test_uss_pss_mem_against_mem_maps(self):
+ src = textwrap.dedent("""
+ import time
+ with open("%s", "w") as f:
+ time.sleep(10)
+ """ % TESTFN)
+ sproc = pyrun(src)
+ self.addCleanup(reap_children)
+ call_until(lambda: os.listdir('.'), "'%s' not in ret" % TESTFN)
+ p = psutil.Process(sproc.pid)
+ time.sleep(.1)
+ memex = p.memory_info_ex()
+ maps = p.memory_maps(grouped=False)
+ self.assertEqual(
+ memex.uss, sum([x.private_dirty + x.private_clean for x in maps]))
+ self.assertEqual(
+ memex.pss, sum([x.shared_dirty + x.shared_clean for x in maps]))
+
def main():
test_suite = unittest.TestSuite()