Skip to content

Commit 7410b63

Browse files
committed
merge with master
Signed-off-by: Giampaolo Rodola <[email protected]>
2 parents 1d41e8f + f1f2995 commit 7410b63

File tree

3 files changed

+55
-20
lines changed

3 files changed

+55
-20
lines changed

HISTORY.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ XXXX-XX-XX
1212
``/proc`` pseudo files line by line. This should help having more consistent
1313
results.
1414
- 2057_, [OpenBSD]: add support for `cpu_freq()`_.
15+
- 2107_ [Linux]: `Process.memory_full_info()`_ (reporting process USS/PSS/Swap
16+
memory) now reads ``/proc/pid/smaps_rollup`` instead of ``/proc/pids/smaps``,
17+
which makes it 5 times faster.
1518

1619
**Bug fixes**
1720

psutil/_pslinux.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@
7777

7878

7979
POWER_SUPPLY_PATH = "/sys/class/power_supply"
80-
HAS_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid())
80+
HAS_PROC_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid())
81+
HAS_PROC_SMAPS_ROLLUP = os.path.exists('/proc/%s/smaps_rollup' % os.getpid())
8182
HAS_PROC_IO_PRIORITY = hasattr(cext, "proc_ioprio_get")
8283
HAS_CPU_AFFINITY = hasattr(cext, "proc_cpu_affinity_get")
8384

@@ -1875,18 +1876,42 @@ def memory_info(self):
18751876
[int(x) * PAGESIZE for x in f.readline().split()[:7]]
18761877
return pmem(rss, vms, shared, text, lib, data, dirty)
18771878

1878-
# /proc/pid/smaps does not exist on kernels < 2.6.14 or if
1879-
# CONFIG_MMU kernel configuration option is not enabled.
1880-
if HAS_SMAPS:
1879+
if HAS_PROC_SMAPS_ROLLUP or HAS_PROC_SMAPS:
18811880

18821881
@wrap_exceptions
1883-
def memory_full_info(
1882+
def _parse_smaps_rollup(self):
1883+
# /proc/pid/smaps_rollup was added to Linux in 2017. Faster
1884+
# than /proc/pid/smaps. It reports higher PSS than */smaps
1885+
# (from 1k up to 200k higher; tested against all processes).
1886+
uss = pss = swap = 0
1887+
try:
1888+
with open_binary("{}/{}/smaps_rollup".format(
1889+
self._procfs_path, self.pid)) as f:
1890+
for line in f:
1891+
if line.startswith(b"Private_"):
1892+
# Private_Clean, Private_Dirty, Private_Hugetlb
1893+
uss += int(line.split()[1]) * 1024
1894+
elif line.startswith(b"Pss:"):
1895+
pss = int(line.split()[1]) * 1024
1896+
elif line.startswith(b"Swap:"):
1897+
swap = int(line.split()[1]) * 1024
1898+
except ProcessLookupError: # happens on readline()
1899+
if not pid_exists(self.pid):
1900+
raise NoSuchProcess(self.pid, self._name)
1901+
else:
1902+
raise ZombieProcess(self.pid, self._name, self._ppid)
1903+
return (uss, pss, swap)
1904+
1905+
@wrap_exceptions
1906+
def _parse_smaps(
18841907
self,
18851908
# Gets Private_Clean, Private_Dirty, Private_Hugetlb.
18861909
_private_re=re.compile(br"\nPrivate.*:\s+(\d+)"),
18871910
_pss_re=re.compile(br"\nPss\:\s+(\d+)"),
18881911
_swap_re=re.compile(br"\nSwap\:\s+(\d+)")):
1889-
basic_mem = self.memory_info()
1912+
# /proc/pid/smaps does not exist on kernels < 2.6.14 or if
1913+
# CONFIG_MMU kernel configuration option is not enabled.
1914+
18901915
# Note: using 3 regexes is faster than reading the file
18911916
# line by line.
18921917
# XXX: on Python 3 the 2 regexes are 30% slower than on
@@ -1905,12 +1930,20 @@ def memory_full_info(
19051930
uss = sum(map(int, _private_re.findall(smaps_data))) * 1024
19061931
pss = sum(map(int, _pss_re.findall(smaps_data))) * 1024
19071932
swap = sum(map(int, _swap_re.findall(smaps_data))) * 1024
1933+
return (uss, pss, swap)
1934+
1935+
def memory_full_info(self):
1936+
if HAS_PROC_SMAPS_ROLLUP: # faster
1937+
uss, pss, swap = self._parse_smaps_rollup()
1938+
else:
1939+
uss, pss, swap = self._parse_smaps()
1940+
basic_mem = self.memory_info()
19081941
return pfullmem(*basic_mem + (uss, pss, swap))
19091942

19101943
else:
19111944
memory_full_info = memory_info
19121945

1913-
if HAS_SMAPS:
1946+
if HAS_PROC_SMAPS:
19141947

19151948
@wrap_exceptions
19161949
def memory_maps(self):

psutil/tests/test_linux.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1775,20 +1775,19 @@ def open_mock(name, *args, **kwargs):
17751775
class TestProcess(PsutilTestCase):
17761776

17771777
@retry_on_failure()
1778-
def test_memory_full_info(self):
1778+
def test_parse_smaps_vs_memory_maps(self):
17791779
sproc = self.spawn_testproc()
1780-
p = psutil.Process(sproc.pid)
1781-
mem = p.memory_full_info()
1782-
maps = p.memory_maps(grouped=False)
1780+
uss, pss, swap = psutil._pslinux.Process(sproc.pid)._parse_smaps()
1781+
maps = psutil.Process(sproc.pid).memory_maps(grouped=False)
17831782
self.assertAlmostEqual(
1784-
mem.uss, sum([x.private_dirty + x.private_clean for x in maps]),
1783+
uss, sum([x.private_dirty + x.private_clean for x in maps]),
17851784
delta=4096)
17861785
self.assertAlmostEqual(
1787-
mem.pss, sum([x.pss for x in maps]), delta=4096)
1786+
pss, sum([x.pss for x in maps]), delta=4096)
17881787
self.assertAlmostEqual(
1789-
mem.swap, sum([x.swap for x in maps]), delta=4096)
1788+
swap, sum([x.swap for x in maps]), delta=4096)
17901789

1791-
def test_memory_full_info_mocked(self):
1790+
def test_parse_smaps_mocked(self):
17921791
# See: https://github.com/giampaolo/psutil/issues/1222
17931792
with mock_open_content(
17941793
"/proc/%s/smaps" % os.getpid(),
@@ -1815,12 +1814,12 @@ def test_memory_full_info_mocked(self):
18151814
Locked: 19 kB
18161815
VmFlags: rd ex
18171816
""").encode()) as m:
1818-
p = psutil.Process()
1819-
mem = p.memory_full_info()
1817+
p = psutil._pslinux.Process(os.getpid())
1818+
uss, pss, swap = p._parse_smaps()
18201819
assert m.called
1821-
self.assertEqual(mem.uss, (6 + 7 + 14) * 1024)
1822-
self.assertEqual(mem.pss, 3 * 1024)
1823-
self.assertEqual(mem.swap, 15 * 1024)
1820+
self.assertEqual(uss, (6 + 7 + 14) * 1024)
1821+
self.assertEqual(pss, 3 * 1024)
1822+
self.assertEqual(swap, 15 * 1024)
18241823

18251824
# On PYPY file descriptors are not closed fast enough.
18261825
@unittest.skipIf(PYPY, "unreliable on PYPY")

0 commit comments

Comments
 (0)