test_linux.py 87 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292
  1. #!/usr/bin/env python3
  2. # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. """Linux specific tests."""
  6. import collections
  7. import contextlib
  8. import errno
  9. import io
  10. import os
  11. import platform
  12. import re
  13. import shutil
  14. import socket
  15. import struct
  16. import textwrap
  17. import time
  18. import warnings
  19. from unittest import mock
  20. import psutil
  21. from psutil import LINUX
  22. from psutil.tests import AARCH64
  23. from psutil.tests import GITHUB_ACTIONS
  24. from psutil.tests import GLOBAL_TIMEOUT
  25. from psutil.tests import HAS_BATTERY
  26. from psutil.tests import HAS_CPU_FREQ
  27. from psutil.tests import HAS_GETLOADAVG
  28. from psutil.tests import HAS_RLIMIT
  29. from psutil.tests import PYPY
  30. from psutil.tests import PYTEST_PARALLEL
  31. from psutil.tests import TOLERANCE_DISK_USAGE
  32. from psutil.tests import TOLERANCE_SYS_MEM
  33. from psutil.tests import PsutilTestCase
  34. from psutil.tests import ThreadTask
  35. from psutil.tests import call_until
  36. from psutil.tests import pytest
  37. from psutil.tests import reload_module
  38. from psutil.tests import retry_on_failure
  39. from psutil.tests import safe_rmpath
  40. from psutil.tests import sh
  41. from psutil.tests import skip_on_not_implemented
  42. if LINUX:
  43. from psutil._pslinux import CLOCK_TICKS
  44. from psutil._pslinux import RootFsDeviceFinder
  45. from psutil._pslinux import calculate_avail_vmem
  46. from psutil._pslinux import open_binary
  47. HERE = os.path.abspath(os.path.dirname(__file__))
  48. SIOCGIFADDR = 0x8915
  49. SIOCGIFHWADDR = 0x8927
  50. SIOCGIFNETMASK = 0x891B
  51. SIOCGIFBRDADDR = 0x8919
  52. if LINUX:
  53. SECTOR_SIZE = 512
  54. # =====================================================================
  55. # --- utils
  56. # =====================================================================
  57. def get_ipv4_address(ifname):
  58. import fcntl
  59. ifname = bytes(ifname[:15], "ascii")
  60. with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
  61. return socket.inet_ntoa(
  62. fcntl.ioctl(s.fileno(), SIOCGIFADDR, struct.pack('256s', ifname))[
  63. 20:24
  64. ]
  65. )
  66. def get_ipv4_netmask(ifname):
  67. import fcntl
  68. ifname = bytes(ifname[:15], "ascii")
  69. with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
  70. return socket.inet_ntoa(
  71. fcntl.ioctl(
  72. s.fileno(), SIOCGIFNETMASK, struct.pack('256s', ifname)
  73. )[20:24]
  74. )
  75. def get_ipv4_broadcast(ifname):
  76. import fcntl
  77. ifname = bytes(ifname[:15], "ascii")
  78. with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
  79. return socket.inet_ntoa(
  80. fcntl.ioctl(
  81. s.fileno(), SIOCGIFBRDADDR, struct.pack('256s', ifname)
  82. )[20:24]
  83. )
  84. def get_ipv6_addresses(ifname):
  85. with open("/proc/net/if_inet6") as f:
  86. all_fields = []
  87. for line in f:
  88. fields = line.split()
  89. if fields[-1] == ifname:
  90. all_fields.append(fields)
  91. if len(all_fields) == 0:
  92. raise ValueError(f"could not find interface {ifname!r}")
  93. for i in range(len(all_fields)):
  94. unformatted = all_fields[i][0]
  95. groups = [
  96. unformatted[j : j + 4] for j in range(0, len(unformatted), 4)
  97. ]
  98. formatted = ":".join(groups)
  99. packed = socket.inet_pton(socket.AF_INET6, formatted)
  100. all_fields[i] = socket.inet_ntop(socket.AF_INET6, packed)
  101. return all_fields
  102. def get_mac_address(ifname):
  103. import fcntl
  104. ifname = bytes(ifname[:15], "ascii")
  105. with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
  106. info = fcntl.ioctl(
  107. s.fileno(), SIOCGIFHWADDR, struct.pack('256s', ifname)
  108. )
  109. return "".join([f"{char:02x}:" for char in info[18:24]])[:-1]
  110. def free_swap():
  111. """Parse 'free' cmd and return swap memory's s total, used and free
  112. values.
  113. """
  114. out = sh(["free", "-b"], env={"LANG": "C.UTF-8"})
  115. lines = out.split('\n')
  116. for line in lines:
  117. if line.startswith('Swap'):
  118. _, total, used, free = line.split()
  119. nt = collections.namedtuple('free', 'total used free')
  120. return nt(int(total), int(used), int(free))
  121. raise ValueError(f"can't find 'Swap' in 'free' output:\n{out}")
  122. def free_physmem():
  123. """Parse 'free' cmd and return physical memory's total, used
  124. and free values.
  125. """
  126. # Note: free can have 2 different formats, invalidating 'shared'
  127. # and 'cached' memory which may have different positions so we
  128. # do not return them.
  129. # https://github.com/giampaolo/psutil/issues/538#issuecomment-57059946
  130. out = sh(["free", "-b"], env={"LANG": "C.UTF-8"})
  131. lines = out.split('\n')
  132. for line in lines:
  133. if line.startswith('Mem'):
  134. total, used, free, shared = (int(x) for x in line.split()[1:5])
  135. nt = collections.namedtuple(
  136. 'free', 'total used free shared output'
  137. )
  138. return nt(total, used, free, shared, out)
  139. raise ValueError(f"can't find 'Mem' in 'free' output:\n{out}")
  140. def vmstat(stat):
  141. out = sh(["vmstat", "-s"], env={"LANG": "C.UTF-8"})
  142. for line in out.split("\n"):
  143. line = line.strip()
  144. if stat in line:
  145. return int(line.split(' ')[0])
  146. raise ValueError(f"can't find {stat!r} in 'vmstat' output")
  147. def get_free_version_info():
  148. out = sh(["free", "-V"]).strip()
  149. if 'UNKNOWN' in out:
  150. raise pytest.skip("can't determine free version")
  151. return tuple(map(int, re.findall(r'\d+', out.split()[-1])))
  152. @contextlib.contextmanager
  153. def mock_open_content(pairs):
  154. """Mock open() builtin and forces it to return a certain content
  155. for a given path. `pairs` is a {"path": "content", ...} dict.
  156. """
  157. def open_mock(name, *args, **kwargs):
  158. if name in pairs:
  159. content = pairs[name]
  160. if isinstance(content, str):
  161. return io.StringIO(content)
  162. else:
  163. return io.BytesIO(content)
  164. else:
  165. return orig_open(name, *args, **kwargs)
  166. orig_open = open
  167. with mock.patch("builtins.open", create=True, side_effect=open_mock) as m:
  168. yield m
  169. @contextlib.contextmanager
  170. def mock_open_exception(for_path, exc):
  171. """Mock open() builtin and raises `exc` if the path being opened
  172. matches `for_path`.
  173. """
  174. def open_mock(name, *args, **kwargs):
  175. if name == for_path:
  176. raise exc
  177. return orig_open(name, *args, **kwargs)
  178. orig_open = open
  179. with mock.patch("builtins.open", create=True, side_effect=open_mock) as m:
  180. yield m
  181. # =====================================================================
  182. # --- system virtual memory
  183. # =====================================================================
  184. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  185. class TestSystemVirtualMemoryAgainstFree(PsutilTestCase):
  186. def test_total(self):
  187. cli_value = free_physmem().total
  188. psutil_value = psutil.virtual_memory().total
  189. assert cli_value == psutil_value
  190. @retry_on_failure()
  191. def test_used(self):
  192. # Older versions of procps used slab memory to calculate used memory.
  193. # This got changed in:
  194. # https://gitlab.com/procps-ng/procps/commit/
  195. # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e
  196. # Newer versions of procps are using yet another way to compute used
  197. # memory.
  198. # https://gitlab.com/procps-ng/procps/commit/
  199. # 2184e90d2e7cdb582f9a5b706b47015e56707e4d
  200. if get_free_version_info() < (3, 3, 12):
  201. raise pytest.skip("free version too old")
  202. if get_free_version_info() >= (4, 0, 0):
  203. raise pytest.skip("free version too recent")
  204. cli_value = free_physmem().used
  205. psutil_value = psutil.virtual_memory().used
  206. assert abs(cli_value - psutil_value) < TOLERANCE_SYS_MEM
  207. @retry_on_failure()
  208. def test_free(self):
  209. cli_value = free_physmem().free
  210. psutil_value = psutil.virtual_memory().free
  211. assert abs(cli_value - psutil_value) < TOLERANCE_SYS_MEM
  212. @retry_on_failure()
  213. def test_shared(self):
  214. free = free_physmem()
  215. free_value = free.shared
  216. if free_value == 0:
  217. raise pytest.skip("free does not support 'shared' column")
  218. psutil_value = psutil.virtual_memory().shared
  219. assert (
  220. abs(free_value - psutil_value) < TOLERANCE_SYS_MEM
  221. ), f"{free_value} {psutil_value} \n{free.output}"
  222. @retry_on_failure()
  223. def test_available(self):
  224. # "free" output format has changed at some point:
  225. # https://github.com/giampaolo/psutil/issues/538#issuecomment-147192098
  226. out = sh(["free", "-b"])
  227. lines = out.split('\n')
  228. if 'available' not in lines[0]:
  229. raise pytest.skip("free does not support 'available' column")
  230. free_value = int(lines[1].split()[-1])
  231. psutil_value = psutil.virtual_memory().available
  232. assert abs(free_value - psutil_value) < TOLERANCE_SYS_MEM
  233. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  234. class TestSystemVirtualMemoryAgainstVmstat(PsutilTestCase):
  235. def test_total(self):
  236. vmstat_value = vmstat('total memory') * 1024
  237. psutil_value = psutil.virtual_memory().total
  238. assert abs(vmstat_value - psutil_value) < TOLERANCE_SYS_MEM
  239. @retry_on_failure()
  240. def test_used(self):
  241. # Older versions of procps used slab memory to calculate used memory.
  242. # This got changed in:
  243. # https://gitlab.com/procps-ng/procps/commit/
  244. # 05d751c4f076a2f0118b914c5e51cfbb4762ad8e
  245. # Newer versions of procps are using yet another way to compute used
  246. # memory.
  247. # https://gitlab.com/procps-ng/procps/commit/
  248. # 2184e90d2e7cdb582f9a5b706b47015e56707e4d
  249. if get_free_version_info() < (3, 3, 12):
  250. raise pytest.skip("free version too old")
  251. if get_free_version_info() >= (4, 0, 0):
  252. raise pytest.skip("free version too recent")
  253. vmstat_value = vmstat('used memory') * 1024
  254. psutil_value = psutil.virtual_memory().used
  255. assert abs(vmstat_value - psutil_value) < TOLERANCE_SYS_MEM
  256. @retry_on_failure()
  257. def test_free(self):
  258. vmstat_value = vmstat('free memory') * 1024
  259. psutil_value = psutil.virtual_memory().free
  260. assert abs(vmstat_value - psutil_value) < TOLERANCE_SYS_MEM
  261. @retry_on_failure()
  262. def test_buffers(self):
  263. vmstat_value = vmstat('buffer memory') * 1024
  264. psutil_value = psutil.virtual_memory().buffers
  265. assert abs(vmstat_value - psutil_value) < TOLERANCE_SYS_MEM
  266. @retry_on_failure()
  267. def test_active(self):
  268. vmstat_value = vmstat('active memory') * 1024
  269. psutil_value = psutil.virtual_memory().active
  270. assert abs(vmstat_value - psutil_value) < TOLERANCE_SYS_MEM
  271. @retry_on_failure()
  272. def test_inactive(self):
  273. vmstat_value = vmstat('inactive memory') * 1024
  274. psutil_value = psutil.virtual_memory().inactive
  275. assert abs(vmstat_value - psutil_value) < TOLERANCE_SYS_MEM
  276. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  277. class TestSystemVirtualMemoryMocks(PsutilTestCase):
  278. def test_warnings_on_misses(self):
  279. # Emulate a case where /proc/meminfo provides few info.
  280. # psutil is supposed to set the missing fields to 0 and
  281. # raise a warning.
  282. content = textwrap.dedent("""\
  283. Active(anon): 6145416 kB
  284. Active(file): 2950064 kB
  285. Inactive(anon): 574764 kB
  286. Inactive(file): 1567648 kB
  287. MemAvailable: -1 kB
  288. MemFree: 2057400 kB
  289. MemTotal: 16325648 kB
  290. SReclaimable: 346648 kB
  291. """).encode()
  292. with mock_open_content({'/proc/meminfo': content}) as m:
  293. with warnings.catch_warnings(record=True) as ws:
  294. warnings.simplefilter("always")
  295. ret = psutil.virtual_memory()
  296. assert m.called
  297. assert len(ws) == 1
  298. w = ws[0]
  299. assert "memory stats couldn't be determined" in str(w.message)
  300. assert "cached" in str(w.message)
  301. assert "shared" in str(w.message)
  302. assert "active" in str(w.message)
  303. assert "inactive" in str(w.message)
  304. assert "buffers" in str(w.message)
  305. assert "available" in str(w.message)
  306. assert ret.cached == 0
  307. assert ret.active == 0
  308. assert ret.inactive == 0
  309. assert ret.shared == 0
  310. assert ret.buffers == 0
  311. assert ret.available == 0
  312. assert ret.slab == 0
  313. @retry_on_failure()
  314. def test_avail_old_percent(self):
  315. # Make sure that our calculation of avail mem for old kernels
  316. # is off by max 15%.
  317. mems = {}
  318. with open_binary('/proc/meminfo') as f:
  319. for line in f:
  320. fields = line.split()
  321. mems[fields[0]] = int(fields[1]) * 1024
  322. a = calculate_avail_vmem(mems)
  323. if b'MemAvailable:' in mems:
  324. b = mems[b'MemAvailable:']
  325. diff_percent = abs(a - b) / a * 100
  326. assert diff_percent < 15
  327. def test_avail_old_comes_from_kernel(self):
  328. # Make sure "MemAvailable:" coluimn is used instead of relying
  329. # on our internal algorithm to calculate avail mem.
  330. content = textwrap.dedent("""\
  331. Active: 9444728 kB
  332. Active(anon): 6145416 kB
  333. Active(file): 2950064 kB
  334. Buffers: 287952 kB
  335. Cached: 4818144 kB
  336. Inactive(file): 1578132 kB
  337. Inactive(anon): 574764 kB
  338. Inactive(file): 1567648 kB
  339. MemAvailable: 6574984 kB
  340. MemFree: 2057400 kB
  341. MemTotal: 16325648 kB
  342. Shmem: 577588 kB
  343. SReclaimable: 346648 kB
  344. """).encode()
  345. with mock_open_content({'/proc/meminfo': content}) as m:
  346. with warnings.catch_warnings(record=True) as ws:
  347. ret = psutil.virtual_memory()
  348. assert m.called
  349. assert ret.available == 6574984 * 1024
  350. w = ws[0]
  351. assert "inactive memory stats couldn't be determined" in str(
  352. w.message
  353. )
  354. def test_avail_old_missing_fields(self):
  355. # Remove Active(file), Inactive(file) and SReclaimable
  356. # from /proc/meminfo and make sure the fallback is used
  357. # (free + cached),
  358. content = textwrap.dedent("""\
  359. Active: 9444728 kB
  360. Active(anon): 6145416 kB
  361. Buffers: 287952 kB
  362. Cached: 4818144 kB
  363. Inactive(file): 1578132 kB
  364. Inactive(anon): 574764 kB
  365. MemFree: 2057400 kB
  366. MemTotal: 16325648 kB
  367. Shmem: 577588 kB
  368. """).encode()
  369. with mock_open_content({"/proc/meminfo": content}) as m:
  370. with warnings.catch_warnings(record=True) as ws:
  371. ret = psutil.virtual_memory()
  372. assert m.called
  373. assert ret.available == 2057400 * 1024 + 4818144 * 1024
  374. w = ws[0]
  375. assert "inactive memory stats couldn't be determined" in str(
  376. w.message
  377. )
  378. def test_avail_old_missing_zoneinfo(self):
  379. # Remove /proc/zoneinfo file. Make sure fallback is used
  380. # (free + cached).
  381. content = textwrap.dedent("""\
  382. Active: 9444728 kB
  383. Active(anon): 6145416 kB
  384. Active(file): 2950064 kB
  385. Buffers: 287952 kB
  386. Cached: 4818144 kB
  387. Inactive(file): 1578132 kB
  388. Inactive(anon): 574764 kB
  389. Inactive(file): 1567648 kB
  390. MemFree: 2057400 kB
  391. MemTotal: 16325648 kB
  392. Shmem: 577588 kB
  393. SReclaimable: 346648 kB
  394. """).encode()
  395. with mock_open_content({"/proc/meminfo": content}):
  396. with mock_open_exception("/proc/zoneinfo", FileNotFoundError):
  397. with warnings.catch_warnings(record=True) as ws:
  398. ret = psutil.virtual_memory()
  399. assert ret.available == 2057400 * 1024 + 4818144 * 1024
  400. w = ws[0]
  401. assert (
  402. "inactive memory stats couldn't be determined"
  403. in str(w.message)
  404. )
  405. def test_virtual_memory_mocked(self):
  406. # Emulate /proc/meminfo because neither vmstat nor free return slab.
  407. content = textwrap.dedent("""\
  408. MemTotal: 100 kB
  409. MemFree: 2 kB
  410. MemAvailable: 3 kB
  411. Buffers: 4 kB
  412. Cached: 5 kB
  413. SwapCached: 6 kB
  414. Active: 7 kB
  415. Inactive: 8 kB
  416. Active(anon): 9 kB
  417. Inactive(anon): 10 kB
  418. Active(file): 11 kB
  419. Inactive(file): 12 kB
  420. Unevictable: 13 kB
  421. Mlocked: 14 kB
  422. SwapTotal: 15 kB
  423. SwapFree: 16 kB
  424. Dirty: 17 kB
  425. Writeback: 18 kB
  426. AnonPages: 19 kB
  427. Mapped: 20 kB
  428. Shmem: 21 kB
  429. Slab: 22 kB
  430. SReclaimable: 23 kB
  431. SUnreclaim: 24 kB
  432. KernelStack: 25 kB
  433. PageTables: 26 kB
  434. NFS_Unstable: 27 kB
  435. Bounce: 28 kB
  436. WritebackTmp: 29 kB
  437. CommitLimit: 30 kB
  438. Committed_AS: 31 kB
  439. VmallocTotal: 32 kB
  440. VmallocUsed: 33 kB
  441. VmallocChunk: 34 kB
  442. HardwareCorrupted: 35 kB
  443. AnonHugePages: 36 kB
  444. ShmemHugePages: 37 kB
  445. ShmemPmdMapped: 38 kB
  446. CmaTotal: 39 kB
  447. CmaFree: 40 kB
  448. HugePages_Total: 41 kB
  449. HugePages_Free: 42 kB
  450. HugePages_Rsvd: 43 kB
  451. HugePages_Surp: 44 kB
  452. Hugepagesize: 45 kB
  453. DirectMap46k: 46 kB
  454. DirectMap47M: 47 kB
  455. DirectMap48G: 48 kB
  456. """).encode()
  457. with mock_open_content({"/proc/meminfo": content}) as m:
  458. mem = psutil.virtual_memory()
  459. assert m.called
  460. assert mem.total == 100 * 1024
  461. assert mem.free == 2 * 1024
  462. assert mem.buffers == 4 * 1024
  463. # cached mem also includes reclaimable memory
  464. assert mem.cached == (5 + 23) * 1024
  465. assert mem.shared == 21 * 1024
  466. assert mem.active == 7 * 1024
  467. assert mem.inactive == 8 * 1024
  468. assert mem.slab == 22 * 1024
  469. assert mem.available == 3 * 1024
  470. # =====================================================================
  471. # --- system swap memory
  472. # =====================================================================
  473. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  474. class TestSystemSwapMemory(PsutilTestCase):
  475. @staticmethod
  476. def meminfo_has_swap_info():
  477. """Return True if /proc/meminfo provides swap metrics."""
  478. with open("/proc/meminfo") as f:
  479. data = f.read()
  480. return 'SwapTotal:' in data and 'SwapFree:' in data
  481. def test_total(self):
  482. free_value = free_swap().total
  483. psutil_value = psutil.swap_memory().total
  484. assert abs(free_value - psutil_value) < TOLERANCE_SYS_MEM
  485. @retry_on_failure()
  486. def test_used(self):
  487. free_value = free_swap().used
  488. psutil_value = psutil.swap_memory().used
  489. assert abs(free_value - psutil_value) < TOLERANCE_SYS_MEM
  490. @retry_on_failure()
  491. def test_free(self):
  492. free_value = free_swap().free
  493. psutil_value = psutil.swap_memory().free
  494. assert abs(free_value - psutil_value) < TOLERANCE_SYS_MEM
  495. def test_missing_sin_sout(self):
  496. with mock.patch('psutil._common.open', create=True) as m:
  497. with warnings.catch_warnings(record=True) as ws:
  498. warnings.simplefilter("always")
  499. ret = psutil.swap_memory()
  500. assert m.called
  501. assert len(ws) == 1
  502. w = ws[0]
  503. assert (
  504. "'sin' and 'sout' swap memory stats couldn't be determined"
  505. in str(w.message)
  506. )
  507. assert ret.sin == 0
  508. assert ret.sout == 0
  509. def test_no_vmstat_mocked(self):
  510. # see https://github.com/giampaolo/psutil/issues/722
  511. with mock_open_exception("/proc/vmstat", FileNotFoundError) as m:
  512. with warnings.catch_warnings(record=True) as ws:
  513. warnings.simplefilter("always")
  514. ret = psutil.swap_memory()
  515. assert m.called
  516. assert len(ws) == 1
  517. w = ws[0]
  518. assert (
  519. "'sin' and 'sout' swap memory stats couldn't "
  520. "be determined and were set to 0"
  521. in str(w.message)
  522. )
  523. assert ret.sin == 0
  524. assert ret.sout == 0
  525. def test_meminfo_against_sysinfo(self):
  526. # Make sure the content of /proc/meminfo about swap memory
  527. # matches sysinfo() syscall, see:
  528. # https://github.com/giampaolo/psutil/issues/1015
  529. if not self.meminfo_has_swap_info():
  530. raise pytest.skip("/proc/meminfo has no swap metrics")
  531. with mock.patch('psutil._pslinux.cext.linux_sysinfo') as m:
  532. swap = psutil.swap_memory()
  533. assert not m.called
  534. import psutil._psutil_linux as cext
  535. _, _, _, _, total, free, unit_multiplier = cext.linux_sysinfo()
  536. total *= unit_multiplier
  537. free *= unit_multiplier
  538. assert swap.total == total
  539. assert abs(swap.free - free) < TOLERANCE_SYS_MEM
  540. def test_emulate_meminfo_has_no_metrics(self):
  541. # Emulate a case where /proc/meminfo provides no swap metrics
  542. # in which case sysinfo() syscall is supposed to be used
  543. # as a fallback.
  544. with mock_open_content({"/proc/meminfo": b""}) as m:
  545. psutil.swap_memory()
  546. assert m.called
  547. # =====================================================================
  548. # --- system CPU
  549. # =====================================================================
  550. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  551. class TestSystemCPUTimes(PsutilTestCase):
  552. def test_fields(self):
  553. fields = psutil.cpu_times()._fields
  554. kernel_ver = re.findall(r'\d+\.\d+\.\d+', os.uname()[2])[0]
  555. kernel_ver_info = tuple(map(int, kernel_ver.split('.')))
  556. if kernel_ver_info >= (2, 6, 11):
  557. assert 'steal' in fields
  558. else:
  559. assert 'steal' not in fields
  560. if kernel_ver_info >= (2, 6, 24):
  561. assert 'guest' in fields
  562. else:
  563. assert 'guest' not in fields
  564. if kernel_ver_info >= (3, 2, 0):
  565. assert 'guest_nice' in fields
  566. else:
  567. assert 'guest_nice' not in fields
  568. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  569. class TestSystemCPUCountLogical(PsutilTestCase):
  570. @pytest.mark.skipif(
  571. not os.path.exists("/sys/devices/system/cpu/online"),
  572. reason="/sys/devices/system/cpu/online does not exist",
  573. )
  574. def test_against_sysdev_cpu_online(self):
  575. with open("/sys/devices/system/cpu/online") as f:
  576. value = f.read().strip()
  577. if "-" in str(value):
  578. value = int(value.split('-')[1]) + 1
  579. assert psutil.cpu_count() == value
  580. @pytest.mark.skipif(
  581. not os.path.exists("/sys/devices/system/cpu"),
  582. reason="/sys/devices/system/cpu does not exist",
  583. )
  584. def test_against_sysdev_cpu_num(self):
  585. ls = os.listdir("/sys/devices/system/cpu")
  586. count = len([x for x in ls if re.search(r"cpu\d+$", x) is not None])
  587. assert psutil.cpu_count() == count
  588. @pytest.mark.skipif(
  589. not shutil.which("nproc"), reason="nproc utility not available"
  590. )
  591. def test_against_nproc(self):
  592. num = int(sh("nproc --all"))
  593. assert psutil.cpu_count(logical=True) == num
  594. @pytest.mark.skipif(
  595. not shutil.which("lscpu"), reason="lscpu utility not available"
  596. )
  597. def test_against_lscpu(self):
  598. out = sh("lscpu -p")
  599. num = len([x for x in out.split('\n') if not x.startswith('#')])
  600. assert psutil.cpu_count(logical=True) == num
  601. def test_emulate_fallbacks(self):
  602. import psutil._pslinux
  603. original = psutil._pslinux.cpu_count_logical()
  604. # Here we want to mock os.sysconf("SC_NPROCESSORS_ONLN") in
  605. # order to cause the parsing of /proc/cpuinfo and /proc/stat.
  606. with mock.patch(
  607. 'psutil._pslinux.os.sysconf', side_effect=ValueError
  608. ) as m:
  609. assert psutil._pslinux.cpu_count_logical() == original
  610. assert m.called
  611. # Let's have open() return empty data and make sure None is
  612. # returned ('cause we mimic os.cpu_count()).
  613. with mock.patch('psutil._common.open', create=True) as m:
  614. assert psutil._pslinux.cpu_count_logical() is None
  615. assert m.call_count == 2
  616. # /proc/stat should be the last one
  617. assert m.call_args[0][0] == '/proc/stat'
  618. # Let's push this a bit further and make sure /proc/cpuinfo
  619. # parsing works as expected.
  620. with open('/proc/cpuinfo', 'rb') as f:
  621. cpuinfo_data = f.read()
  622. fake_file = io.BytesIO(cpuinfo_data)
  623. with mock.patch(
  624. 'psutil._common.open', return_value=fake_file, create=True
  625. ) as m:
  626. assert psutil._pslinux.cpu_count_logical() == original
  627. # Finally, let's make /proc/cpuinfo return meaningless data;
  628. # this way we'll fall back on relying on /proc/stat
  629. with mock_open_content({"/proc/cpuinfo": b""}) as m:
  630. assert psutil._pslinux.cpu_count_logical() == original
  631. assert m.called
  632. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  633. class TestSystemCPUCountCores(PsutilTestCase):
  634. @pytest.mark.skipif(
  635. not shutil.which("lscpu"), reason="lscpu utility not available"
  636. )
  637. def test_against_lscpu(self):
  638. out = sh("lscpu -p")
  639. core_ids = set()
  640. for line in out.split('\n'):
  641. if not line.startswith('#'):
  642. fields = line.split(',')
  643. core_ids.add(fields[1])
  644. assert psutil.cpu_count(logical=False) == len(core_ids)
  645. @pytest.mark.skipif(
  646. platform.machine() not in {"x86_64", "i686"}, reason="x86_64/i686 only"
  647. )
  648. def test_method_2(self):
  649. meth_1 = psutil._pslinux.cpu_count_cores()
  650. with mock.patch('glob.glob', return_value=[]) as m:
  651. meth_2 = psutil._pslinux.cpu_count_cores()
  652. assert m.called
  653. if meth_1 is not None:
  654. assert meth_1 == meth_2
  655. def test_emulate_none(self):
  656. with mock.patch('glob.glob', return_value=[]) as m1:
  657. with mock.patch('psutil._common.open', create=True) as m2:
  658. assert psutil._pslinux.cpu_count_cores() is None
  659. assert m1.called
  660. assert m2.called
  661. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  662. class TestSystemCPUFrequency(PsutilTestCase):
  663. @pytest.mark.skipif(not HAS_CPU_FREQ, reason="not supported")
  664. @pytest.mark.skipif(
  665. AARCH64, reason="aarch64 does not always expose frequency"
  666. )
  667. def test_emulate_use_second_file(self):
  668. # https://github.com/giampaolo/psutil/issues/981
  669. def path_exists_mock(path):
  670. if path.startswith("/sys/devices/system/cpu/cpufreq/policy"):
  671. return False
  672. else:
  673. return orig_exists(path)
  674. orig_exists = os.path.exists
  675. with mock.patch(
  676. "os.path.exists", side_effect=path_exists_mock, create=True
  677. ):
  678. assert psutil.cpu_freq()
  679. @pytest.mark.skipif(not HAS_CPU_FREQ, reason="not supported")
  680. @pytest.mark.skipif(
  681. AARCH64, reason="aarch64 does not report mhz in /proc/cpuinfo"
  682. )
  683. def test_emulate_use_cpuinfo(self):
  684. # Emulate a case where /sys/devices/system/cpu/cpufreq* does not
  685. # exist and /proc/cpuinfo is used instead.
  686. def path_exists_mock(path):
  687. if path.startswith('/sys/devices/system/cpu/'):
  688. return False
  689. else:
  690. return os_path_exists(path)
  691. os_path_exists = os.path.exists
  692. try:
  693. with mock.patch("os.path.exists", side_effect=path_exists_mock):
  694. reload_module(psutil._pslinux)
  695. ret = psutil.cpu_freq()
  696. assert ret, ret
  697. assert ret.max == 0.0
  698. assert ret.min == 0.0
  699. for freq in psutil.cpu_freq(percpu=True):
  700. assert freq.max == 0.0
  701. assert freq.min == 0.0
  702. finally:
  703. reload_module(psutil._pslinux)
  704. reload_module(psutil)
  705. @pytest.mark.skipif(not HAS_CPU_FREQ, reason="not supported")
  706. def test_emulate_data(self):
  707. def open_mock(name, *args, **kwargs):
  708. if name.endswith('/scaling_cur_freq') and name.startswith(
  709. "/sys/devices/system/cpu/cpufreq/policy"
  710. ):
  711. return io.BytesIO(b"500000")
  712. elif name.endswith('/scaling_min_freq') and name.startswith(
  713. "/sys/devices/system/cpu/cpufreq/policy"
  714. ):
  715. return io.BytesIO(b"600000")
  716. elif name.endswith('/scaling_max_freq') and name.startswith(
  717. "/sys/devices/system/cpu/cpufreq/policy"
  718. ):
  719. return io.BytesIO(b"700000")
  720. elif name == '/proc/cpuinfo':
  721. return io.BytesIO(b"cpu MHz : 500")
  722. else:
  723. return orig_open(name, *args, **kwargs)
  724. orig_open = open
  725. with mock.patch("builtins.open", side_effect=open_mock):
  726. with mock.patch('os.path.exists', return_value=True):
  727. freq = psutil.cpu_freq()
  728. assert freq.current == 500.0
  729. # when /proc/cpuinfo is used min and max frequencies are not
  730. # available and are set to 0.
  731. if freq.min != 0.0:
  732. assert freq.min == 600.0
  733. if freq.max != 0.0:
  734. assert freq.max == 700.0
  735. @pytest.mark.skipif(not HAS_CPU_FREQ, reason="not supported")
  736. def test_emulate_multi_cpu(self):
  737. def open_mock(name, *args, **kwargs):
  738. n = name
  739. if n.endswith('/scaling_cur_freq') and n.startswith(
  740. "/sys/devices/system/cpu/cpufreq/policy0"
  741. ):
  742. return io.BytesIO(b"100000")
  743. elif n.endswith('/scaling_min_freq') and n.startswith(
  744. "/sys/devices/system/cpu/cpufreq/policy0"
  745. ):
  746. return io.BytesIO(b"200000")
  747. elif n.endswith('/scaling_max_freq') and n.startswith(
  748. "/sys/devices/system/cpu/cpufreq/policy0"
  749. ):
  750. return io.BytesIO(b"300000")
  751. elif n.endswith('/scaling_cur_freq') and n.startswith(
  752. "/sys/devices/system/cpu/cpufreq/policy1"
  753. ):
  754. return io.BytesIO(b"400000")
  755. elif n.endswith('/scaling_min_freq') and n.startswith(
  756. "/sys/devices/system/cpu/cpufreq/policy1"
  757. ):
  758. return io.BytesIO(b"500000")
  759. elif n.endswith('/scaling_max_freq') and n.startswith(
  760. "/sys/devices/system/cpu/cpufreq/policy1"
  761. ):
  762. return io.BytesIO(b"600000")
  763. elif name == '/proc/cpuinfo':
  764. return io.BytesIO(b"cpu MHz : 100\ncpu MHz : 400")
  765. else:
  766. return orig_open(name, *args, **kwargs)
  767. orig_open = open
  768. with mock.patch("builtins.open", side_effect=open_mock):
  769. with mock.patch('os.path.exists', return_value=True):
  770. with mock.patch(
  771. 'psutil._pslinux.cpu_count_logical', return_value=2
  772. ):
  773. freq = psutil.cpu_freq(percpu=True)
  774. assert freq[0].current == 100.0
  775. if freq[0].min != 0.0:
  776. assert freq[0].min == 200.0
  777. if freq[0].max != 0.0:
  778. assert freq[0].max == 300.0
  779. assert freq[1].current == 400.0
  780. if freq[1].min != 0.0:
  781. assert freq[1].min == 500.0
  782. if freq[1].max != 0.0:
  783. assert freq[1].max == 600.0
  784. @pytest.mark.skipif(not HAS_CPU_FREQ, reason="not supported")
  785. def test_emulate_no_scaling_cur_freq_file(self):
  786. # See: https://github.com/giampaolo/psutil/issues/1071
  787. def open_mock(name, *args, **kwargs):
  788. if name.endswith('/scaling_cur_freq'):
  789. raise FileNotFoundError
  790. if name.endswith('/cpuinfo_cur_freq'):
  791. return io.BytesIO(b"200000")
  792. elif name == '/proc/cpuinfo':
  793. return io.BytesIO(b"cpu MHz : 200")
  794. else:
  795. return orig_open(name, *args, **kwargs)
  796. orig_open = open
  797. with mock.patch("builtins.open", side_effect=open_mock):
  798. with mock.patch('os.path.exists', return_value=True):
  799. with mock.patch(
  800. 'psutil._pslinux.cpu_count_logical', return_value=1
  801. ):
  802. freq = psutil.cpu_freq()
  803. assert freq.current == 200
  804. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  805. class TestSystemCPUStats(PsutilTestCase):
  806. # XXX: fails too often.
  807. # def test_ctx_switches(self):
  808. # vmstat_value = vmstat("context switches")
  809. # psutil_value = psutil.cpu_stats().ctx_switches
  810. # self.assertAlmostEqual(vmstat_value, psutil_value, delta=500)
  811. def test_interrupts(self):
  812. vmstat_value = vmstat("interrupts")
  813. psutil_value = psutil.cpu_stats().interrupts
  814. assert abs(vmstat_value - psutil_value) < 500
  815. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  816. class TestLoadAvg(PsutilTestCase):
  817. @pytest.mark.skipif(not HAS_GETLOADAVG, reason="not supported")
  818. def test_getloadavg(self):
  819. psutil_value = psutil.getloadavg()
  820. with open("/proc/loadavg") as f:
  821. proc_value = f.read().split()
  822. assert abs(float(proc_value[0]) - psutil_value[0]) < 1
  823. assert abs(float(proc_value[1]) - psutil_value[1]) < 1
  824. assert abs(float(proc_value[2]) - psutil_value[2]) < 1
  825. # =====================================================================
  826. # --- system network
  827. # =====================================================================
  828. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  829. class TestSystemNetIfAddrs(PsutilTestCase):
  830. def test_ips(self):
  831. for name, addrs in psutil.net_if_addrs().items():
  832. for addr in addrs:
  833. if addr.family == psutil.AF_LINK:
  834. assert addr.address == get_mac_address(name)
  835. elif addr.family == socket.AF_INET:
  836. assert addr.address == get_ipv4_address(name)
  837. assert addr.netmask == get_ipv4_netmask(name)
  838. if addr.broadcast is not None:
  839. assert addr.broadcast == get_ipv4_broadcast(name)
  840. else:
  841. assert get_ipv4_broadcast(name) == '0.0.0.0'
  842. elif addr.family == socket.AF_INET6:
  843. # IPv6 addresses can have a percent symbol at the end.
  844. # E.g. these 2 are equivalent:
  845. # "fe80::1ff:fe23:4567:890a"
  846. # "fe80::1ff:fe23:4567:890a%eth0"
  847. # That is the "zone id" portion, which usually is the name
  848. # of the network interface.
  849. address = addr.address.split('%')[0]
  850. assert address in get_ipv6_addresses(name)
  851. # XXX - not reliable when having virtual NICs installed by Docker.
  852. # @pytest.mark.skipif(not shutil.which("ip"),
  853. # reason="'ip' utility not available")
  854. # def test_net_if_names(self):
  855. # out = sh("ip addr").strip()
  856. # nics = [x for x in psutil.net_if_addrs().keys() if ':' not in x]
  857. # found = 0
  858. # for line in out.split('\n'):
  859. # line = line.strip()
  860. # if re.search(r"^\d+:", line):
  861. # found += 1
  862. # name = line.split(':')[1].strip()
  863. # self.assertIn(name, nics)
  864. # self.assertEqual(len(nics), found, msg="{}\n---\n{}".format(
  865. # pprint.pformat(nics), out))
  866. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  867. class TestSystemNetIfStats(PsutilTestCase):
  868. @pytest.mark.skipif(
  869. not shutil.which("ifconfig"), reason="ifconfig utility not available"
  870. )
  871. def test_against_ifconfig(self):
  872. for name, stats in psutil.net_if_stats().items():
  873. try:
  874. out = sh(f"ifconfig {name}")
  875. except RuntimeError:
  876. pass
  877. else:
  878. assert stats.isup == ('RUNNING' in out), out
  879. assert stats.mtu == int(
  880. re.findall(r'(?i)MTU[: ](\d+)', out)[0]
  881. )
  882. def test_mtu(self):
  883. for name, stats in psutil.net_if_stats().items():
  884. with open(f"/sys/class/net/{name}/mtu") as f:
  885. assert stats.mtu == int(f.read().strip())
  886. @pytest.mark.skipif(
  887. not shutil.which("ifconfig"), reason="ifconfig utility not available"
  888. )
  889. def test_flags(self):
  890. # first line looks like this:
  891. # "eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500"
  892. matches_found = 0
  893. for name, stats in psutil.net_if_stats().items():
  894. try:
  895. out = sh(f"ifconfig {name}")
  896. except RuntimeError:
  897. pass
  898. else:
  899. match = re.search(r"flags=(\d+)?<(.*?)>", out)
  900. if match and len(match.groups()) >= 2:
  901. matches_found += 1
  902. ifconfig_flags = set(match.group(2).lower().split(","))
  903. psutil_flags = set(stats.flags.split(","))
  904. assert ifconfig_flags == psutil_flags
  905. else:
  906. # ifconfig has a different output on CentOS 6
  907. # let's try that
  908. match = re.search(r"(.*) MTU:(\d+) Metric:(\d+)", out)
  909. if match and len(match.groups()) >= 3:
  910. matches_found += 1
  911. ifconfig_flags = set(match.group(1).lower().split())
  912. psutil_flags = set(stats.flags.split(","))
  913. assert ifconfig_flags == psutil_flags
  914. if not matches_found:
  915. raise self.fail("no matches were found")
  916. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  917. class TestSystemNetIOCounters(PsutilTestCase):
  918. @pytest.mark.skipif(
  919. not shutil.which("ifconfig"), reason="ifconfig utility not available"
  920. )
  921. @retry_on_failure()
  922. def test_against_ifconfig(self):
  923. def ifconfig(nic):
  924. ret = {}
  925. out = sh(f"ifconfig {nic}")
  926. ret['packets_recv'] = int(
  927. re.findall(r'RX packets[: ](\d+)', out)[0]
  928. )
  929. ret['packets_sent'] = int(
  930. re.findall(r'TX packets[: ](\d+)', out)[0]
  931. )
  932. ret['errin'] = int(re.findall(r'errors[: ](\d+)', out)[0])
  933. ret['errout'] = int(re.findall(r'errors[: ](\d+)', out)[1])
  934. ret['dropin'] = int(re.findall(r'dropped[: ](\d+)', out)[0])
  935. ret['dropout'] = int(re.findall(r'dropped[: ](\d+)', out)[1])
  936. ret['bytes_recv'] = int(
  937. re.findall(r'RX (?:packets \d+ +)?bytes[: ](\d+)', out)[0]
  938. )
  939. ret['bytes_sent'] = int(
  940. re.findall(r'TX (?:packets \d+ +)?bytes[: ](\d+)', out)[0]
  941. )
  942. return ret
  943. nio = psutil.net_io_counters(pernic=True, nowrap=False)
  944. for name, stats in nio.items():
  945. try:
  946. ifconfig_ret = ifconfig(name)
  947. except RuntimeError:
  948. continue
  949. assert (
  950. abs(stats.bytes_recv - ifconfig_ret['bytes_recv']) < 1024 * 10
  951. )
  952. assert (
  953. abs(stats.bytes_sent - ifconfig_ret['bytes_sent']) < 1024 * 10
  954. )
  955. assert (
  956. abs(stats.packets_recv - ifconfig_ret['packets_recv']) < 1024
  957. )
  958. assert (
  959. abs(stats.packets_sent - ifconfig_ret['packets_sent']) < 1024
  960. )
  961. assert abs(stats.errin - ifconfig_ret['errin']) < 10
  962. assert abs(stats.errout - ifconfig_ret['errout']) < 10
  963. assert abs(stats.dropin - ifconfig_ret['dropin']) < 10
  964. assert abs(stats.dropout - ifconfig_ret['dropout']) < 10
  965. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  966. class TestSystemNetConnections(PsutilTestCase):
  967. @mock.patch('psutil._pslinux.socket.inet_ntop', side_effect=ValueError)
  968. @mock.patch('psutil._pslinux.supports_ipv6', return_value=False)
  969. def test_emulate_ipv6_unsupported(self, supports_ipv6, inet_ntop):
  970. # see: https://github.com/giampaolo/psutil/issues/623
  971. try:
  972. s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
  973. self.addCleanup(s.close)
  974. s.bind(("::1", 0))
  975. except OSError:
  976. pass
  977. psutil.net_connections(kind='inet6')
  978. def test_emulate_unix(self):
  979. content = textwrap.dedent("""\
  980. 0: 00000003 000 000 0001 03 462170 @/tmp/dbus-Qw2hMPIU3n
  981. 0: 00000003 000 000 0001 03 35010 @/tmp/dbus-tB2X8h69BQ
  982. 0: 00000003 000 000 0001 03 34424 @/tmp/dbus-cHy80Y8O
  983. 000000000000000000000000000000000000000000000000000000
  984. """)
  985. with mock_open_content({"/proc/net/unix": content}) as m:
  986. psutil.net_connections(kind='unix')
  987. assert m.called
  988. # =====================================================================
  989. # --- system disks
  990. # =====================================================================
  991. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  992. class TestSystemDiskPartitions(PsutilTestCase):
  993. @pytest.mark.skipif(
  994. not hasattr(os, 'statvfs'), reason="os.statvfs() not available"
  995. )
  996. @skip_on_not_implemented()
  997. def test_against_df(self):
  998. # test psutil.disk_usage() and psutil.disk_partitions()
  999. # against "df -a"
  1000. def df(path):
  1001. out = sh(f'df -P -B 1 "{path}"').strip()
  1002. lines = out.split('\n')
  1003. lines.pop(0)
  1004. line = lines.pop(0)
  1005. dev, total, used, free = line.split()[:4]
  1006. if dev == 'none':
  1007. dev = ''
  1008. total, used, free = int(total), int(used), int(free)
  1009. return dev, total, used, free
  1010. for part in psutil.disk_partitions(all=False):
  1011. usage = psutil.disk_usage(part.mountpoint)
  1012. _, total, used, free = df(part.mountpoint)
  1013. assert usage.total == total
  1014. assert abs(usage.free - free) < TOLERANCE_DISK_USAGE
  1015. assert abs(usage.used - used) < TOLERANCE_DISK_USAGE
  1016. def test_zfs_fs(self):
  1017. # Test that ZFS partitions are returned.
  1018. with open("/proc/filesystems") as f:
  1019. data = f.read()
  1020. if 'zfs' in data:
  1021. for part in psutil.disk_partitions():
  1022. if part.fstype == 'zfs':
  1023. return
  1024. # No ZFS partitions on this system. Let's fake one.
  1025. fake_file = io.StringIO("nodev\tzfs\n")
  1026. with mock.patch(
  1027. 'psutil._common.open', return_value=fake_file, create=True
  1028. ) as m1:
  1029. with mock.patch(
  1030. 'psutil._pslinux.cext.disk_partitions',
  1031. return_value=[('/dev/sdb3', '/', 'zfs', 'rw')],
  1032. ) as m2:
  1033. ret = psutil.disk_partitions()
  1034. assert m1.called
  1035. assert m2.called
  1036. assert ret
  1037. assert ret[0].fstype == 'zfs'
  1038. def test_emulate_realpath_fail(self):
  1039. # See: https://github.com/giampaolo/psutil/issues/1307
  1040. try:
  1041. with mock.patch(
  1042. 'os.path.realpath', return_value='/non/existent'
  1043. ) as m:
  1044. with pytest.raises(FileNotFoundError):
  1045. psutil.disk_partitions()
  1046. assert m.called
  1047. finally:
  1048. psutil.PROCFS_PATH = "/proc"
  1049. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  1050. class TestSystemDiskIoCounters(PsutilTestCase):
  1051. def test_emulate_kernel_2_4(self):
  1052. # Tests /proc/diskstats parsing format for 2.4 kernels, see:
  1053. # https://github.com/giampaolo/psutil/issues/767
  1054. content = " 3 0 1 hda 2 3 4 5 6 7 8 9 10 11 12"
  1055. with mock_open_content({'/proc/diskstats': content}):
  1056. with mock.patch(
  1057. 'psutil._pslinux.is_storage_device', return_value=True
  1058. ):
  1059. ret = psutil.disk_io_counters(nowrap=False)
  1060. assert ret.read_count == 1
  1061. assert ret.read_merged_count == 2
  1062. assert ret.read_bytes == 3 * SECTOR_SIZE
  1063. assert ret.read_time == 4
  1064. assert ret.write_count == 5
  1065. assert ret.write_merged_count == 6
  1066. assert ret.write_bytes == 7 * SECTOR_SIZE
  1067. assert ret.write_time == 8
  1068. assert ret.busy_time == 10
  1069. def test_emulate_kernel_2_6_full(self):
  1070. # Tests /proc/diskstats parsing format for 2.6 kernels,
  1071. # lines reporting all metrics:
  1072. # https://github.com/giampaolo/psutil/issues/767
  1073. content = " 3 0 hda 1 2 3 4 5 6 7 8 9 10 11"
  1074. with mock_open_content({"/proc/diskstats": content}):
  1075. with mock.patch(
  1076. 'psutil._pslinux.is_storage_device', return_value=True
  1077. ):
  1078. ret = psutil.disk_io_counters(nowrap=False)
  1079. assert ret.read_count == 1
  1080. assert ret.read_merged_count == 2
  1081. assert ret.read_bytes == 3 * SECTOR_SIZE
  1082. assert ret.read_time == 4
  1083. assert ret.write_count == 5
  1084. assert ret.write_merged_count == 6
  1085. assert ret.write_bytes == 7 * SECTOR_SIZE
  1086. assert ret.write_time == 8
  1087. assert ret.busy_time == 10
  1088. def test_emulate_kernel_2_6_limited(self):
  1089. # Tests /proc/diskstats parsing format for 2.6 kernels,
  1090. # where one line of /proc/partitions return a limited
  1091. # amount of metrics when it bumps into a partition
  1092. # (instead of a disk). See:
  1093. # https://github.com/giampaolo/psutil/issues/767
  1094. with mock_open_content({"/proc/diskstats": " 3 1 hda 1 2 3 4"}):
  1095. with mock.patch(
  1096. 'psutil._pslinux.is_storage_device', return_value=True
  1097. ):
  1098. ret = psutil.disk_io_counters(nowrap=False)
  1099. assert ret.read_count == 1
  1100. assert ret.read_bytes == 2 * SECTOR_SIZE
  1101. assert ret.write_count == 3
  1102. assert ret.write_bytes == 4 * SECTOR_SIZE
  1103. assert ret.read_merged_count == 0
  1104. assert ret.read_time == 0
  1105. assert ret.write_merged_count == 0
  1106. assert ret.write_time == 0
  1107. assert ret.busy_time == 0
  1108. def test_emulate_include_partitions(self):
  1109. # Make sure that when perdisk=True disk partitions are returned,
  1110. # see:
  1111. # https://github.com/giampaolo/psutil/pull/1313#issuecomment-408626842
  1112. content = textwrap.dedent("""\
  1113. 3 0 nvme0n1 1 2 3 4 5 6 7 8 9 10 11
  1114. 3 0 nvme0n1p1 1 2 3 4 5 6 7 8 9 10 11
  1115. """)
  1116. with mock_open_content({"/proc/diskstats": content}):
  1117. with mock.patch(
  1118. 'psutil._pslinux.is_storage_device', return_value=False
  1119. ):
  1120. ret = psutil.disk_io_counters(perdisk=True, nowrap=False)
  1121. assert len(ret) == 2
  1122. assert ret['nvme0n1'].read_count == 1
  1123. assert ret['nvme0n1p1'].read_count == 1
  1124. assert ret['nvme0n1'].write_count == 5
  1125. assert ret['nvme0n1p1'].write_count == 5
  1126. def test_emulate_exclude_partitions(self):
  1127. # Make sure that when perdisk=False partitions (e.g. 'sda1',
  1128. # 'nvme0n1p1') are skipped and not included in the total count.
  1129. # https://github.com/giampaolo/psutil/pull/1313#issuecomment-408626842
  1130. content = textwrap.dedent("""\
  1131. 3 0 nvme0n1 1 2 3 4 5 6 7 8 9 10 11
  1132. 3 0 nvme0n1p1 1 2 3 4 5 6 7 8 9 10 11
  1133. """)
  1134. with mock_open_content({"/proc/diskstats": content}):
  1135. with mock.patch(
  1136. 'psutil._pslinux.is_storage_device', return_value=False
  1137. ):
  1138. ret = psutil.disk_io_counters(perdisk=False, nowrap=False)
  1139. assert ret is None
  1140. def is_storage_device(name):
  1141. return name == 'nvme0n1'
  1142. content = textwrap.dedent("""\
  1143. 3 0 nvme0n1 1 2 3 4 5 6 7 8 9 10 11
  1144. 3 0 nvme0n1p1 1 2 3 4 5 6 7 8 9 10 11
  1145. """)
  1146. with mock_open_content({"/proc/diskstats": content}):
  1147. with mock.patch(
  1148. 'psutil._pslinux.is_storage_device',
  1149. create=True,
  1150. side_effect=is_storage_device,
  1151. ):
  1152. ret = psutil.disk_io_counters(perdisk=False, nowrap=False)
  1153. assert ret.read_count == 1
  1154. assert ret.write_count == 5
  1155. def test_emulate_use_sysfs(self):
  1156. def exists(path):
  1157. return path == '/proc/diskstats'
  1158. wprocfs = psutil.disk_io_counters(perdisk=True)
  1159. with mock.patch(
  1160. 'psutil._pslinux.os.path.exists', create=True, side_effect=exists
  1161. ):
  1162. wsysfs = psutil.disk_io_counters(perdisk=True)
  1163. assert len(wprocfs) == len(wsysfs)
  1164. def test_emulate_not_impl(self):
  1165. def exists(path):
  1166. return False
  1167. with mock.patch(
  1168. 'psutil._pslinux.os.path.exists', create=True, side_effect=exists
  1169. ):
  1170. with pytest.raises(NotImplementedError):
  1171. psutil.disk_io_counters()
  1172. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  1173. class TestRootFsDeviceFinder(PsutilTestCase):
  1174. def setUp(self):
  1175. dev = os.stat("/").st_dev
  1176. self.major = os.major(dev)
  1177. self.minor = os.minor(dev)
  1178. def test_call_methods(self):
  1179. finder = RootFsDeviceFinder()
  1180. if os.path.exists("/proc/partitions"):
  1181. finder.ask_proc_partitions()
  1182. else:
  1183. with pytest.raises(FileNotFoundError):
  1184. finder.ask_proc_partitions()
  1185. if os.path.exists(f"/sys/dev/block/{self.major}:{self.minor}/uevent"):
  1186. finder.ask_sys_dev_block()
  1187. else:
  1188. with pytest.raises(FileNotFoundError):
  1189. finder.ask_sys_dev_block()
  1190. finder.ask_sys_class_block()
  1191. @pytest.mark.skipif(GITHUB_ACTIONS, reason="unsupported on GITHUB_ACTIONS")
  1192. def test_comparisons(self):
  1193. finder = RootFsDeviceFinder()
  1194. assert finder.find() is not None
  1195. a = b = c = None
  1196. if os.path.exists("/proc/partitions"):
  1197. a = finder.ask_proc_partitions()
  1198. if os.path.exists(f"/sys/dev/block/{self.major}:{self.minor}/uevent"):
  1199. b = finder.ask_sys_class_block()
  1200. c = finder.ask_sys_dev_block()
  1201. base = a or b or c
  1202. if base and a:
  1203. assert base == a
  1204. if base and b:
  1205. assert base == b
  1206. if base and c:
  1207. assert base == c
  1208. @pytest.mark.skipif(
  1209. not shutil.which("findmnt"), reason="findmnt utility not available"
  1210. )
  1211. @pytest.mark.skipif(GITHUB_ACTIONS, reason="unsupported on GITHUB_ACTIONS")
  1212. def test_against_findmnt(self):
  1213. psutil_value = RootFsDeviceFinder().find()
  1214. findmnt_value = sh("findmnt -o SOURCE -rn /")
  1215. assert psutil_value == findmnt_value
  1216. def test_disk_partitions_mocked(self):
  1217. with mock.patch(
  1218. 'psutil._pslinux.cext.disk_partitions',
  1219. return_value=[('/dev/root', '/', 'ext4', 'rw')],
  1220. ) as m:
  1221. part = psutil.disk_partitions()[0]
  1222. assert m.called
  1223. if not GITHUB_ACTIONS:
  1224. assert part.device != "/dev/root"
  1225. assert part.device == RootFsDeviceFinder().find()
  1226. else:
  1227. assert part.device == "/dev/root"
  1228. # =====================================================================
  1229. # --- misc
  1230. # =====================================================================
  1231. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  1232. class TestMisc(PsutilTestCase):
  1233. def test_boot_time(self):
  1234. vmstat_value = vmstat('boot time')
  1235. psutil_value = psutil.boot_time()
  1236. assert int(vmstat_value) == int(psutil_value)
  1237. def test_no_procfs_on_import(self):
  1238. my_procfs = self.get_testfn()
  1239. os.mkdir(my_procfs)
  1240. with open(os.path.join(my_procfs, 'stat'), 'w') as f:
  1241. f.write('cpu 0 0 0 0 0 0 0 0 0 0\n')
  1242. f.write('cpu0 0 0 0 0 0 0 0 0 0 0\n')
  1243. f.write('cpu1 0 0 0 0 0 0 0 0 0 0\n')
  1244. try:
  1245. orig_open = open
  1246. def open_mock(name, *args, **kwargs):
  1247. if name.startswith('/proc'):
  1248. raise FileNotFoundError
  1249. return orig_open(name, *args, **kwargs)
  1250. with mock.patch("builtins.open", side_effect=open_mock):
  1251. reload_module(psutil)
  1252. with pytest.raises(OSError):
  1253. psutil.cpu_times()
  1254. with pytest.raises(OSError):
  1255. psutil.cpu_times(percpu=True)
  1256. with pytest.raises(OSError):
  1257. psutil.cpu_percent()
  1258. with pytest.raises(OSError):
  1259. psutil.cpu_percent(percpu=True)
  1260. with pytest.raises(OSError):
  1261. psutil.cpu_times_percent()
  1262. with pytest.raises(OSError):
  1263. psutil.cpu_times_percent(percpu=True)
  1264. psutil.PROCFS_PATH = my_procfs
  1265. assert psutil.cpu_percent() == 0
  1266. assert sum(psutil.cpu_times_percent()) == 0
  1267. # since we don't know the number of CPUs at import time,
  1268. # we awkwardly say there are none until the second call
  1269. per_cpu_percent = psutil.cpu_percent(percpu=True)
  1270. assert sum(per_cpu_percent) == 0
  1271. # ditto awkward length
  1272. per_cpu_times_percent = psutil.cpu_times_percent(percpu=True)
  1273. assert sum(map(sum, per_cpu_times_percent)) == 0
  1274. # much user, very busy
  1275. with open(os.path.join(my_procfs, 'stat'), 'w') as f:
  1276. f.write('cpu 1 0 0 0 0 0 0 0 0 0\n')
  1277. f.write('cpu0 1 0 0 0 0 0 0 0 0 0\n')
  1278. f.write('cpu1 1 0 0 0 0 0 0 0 0 0\n')
  1279. assert psutil.cpu_percent() != 0
  1280. assert sum(psutil.cpu_percent(percpu=True)) != 0
  1281. assert sum(psutil.cpu_times_percent()) != 0
  1282. assert (
  1283. sum(map(sum, psutil.cpu_times_percent(percpu=True))) != 0
  1284. )
  1285. finally:
  1286. shutil.rmtree(my_procfs)
  1287. reload_module(psutil)
  1288. assert psutil.PROCFS_PATH == '/proc'
  1289. def test_cpu_steal_decrease(self):
  1290. # Test cumulative cpu stats decrease. We should ignore this.
  1291. # See issue #1210.
  1292. content = textwrap.dedent("""\
  1293. cpu 0 0 0 0 0 0 0 1 0 0
  1294. cpu0 0 0 0 0 0 0 0 1 0 0
  1295. cpu1 0 0 0 0 0 0 0 1 0 0
  1296. """).encode()
  1297. with mock_open_content({"/proc/stat": content}) as m:
  1298. # first call to "percent" functions should read the new stat file
  1299. # and compare to the "real" file read at import time - so the
  1300. # values are meaningless
  1301. psutil.cpu_percent()
  1302. assert m.called
  1303. psutil.cpu_percent(percpu=True)
  1304. psutil.cpu_times_percent()
  1305. psutil.cpu_times_percent(percpu=True)
  1306. content = textwrap.dedent("""\
  1307. cpu 1 0 0 0 0 0 0 0 0 0
  1308. cpu0 1 0 0 0 0 0 0 0 0 0
  1309. cpu1 1 0 0 0 0 0 0 0 0 0
  1310. """).encode()
  1311. with mock_open_content({"/proc/stat": content}):
  1312. # Increase "user" while steal goes "backwards" to zero.
  1313. cpu_percent = psutil.cpu_percent()
  1314. assert m.called
  1315. cpu_percent_percpu = psutil.cpu_percent(percpu=True)
  1316. cpu_times_percent = psutil.cpu_times_percent()
  1317. cpu_times_percent_percpu = psutil.cpu_times_percent(percpu=True)
  1318. assert cpu_percent != 0
  1319. assert sum(cpu_percent_percpu) != 0
  1320. assert sum(cpu_times_percent) != 0
  1321. assert sum(cpu_times_percent) != 100.0
  1322. assert sum(map(sum, cpu_times_percent_percpu)) != 0
  1323. assert sum(map(sum, cpu_times_percent_percpu)) != 100.0
  1324. assert cpu_times_percent.steal == 0
  1325. assert cpu_times_percent.user != 0
  1326. def test_boot_time_mocked(self):
  1327. with mock.patch('psutil._common.open', create=True) as m:
  1328. with pytest.raises(RuntimeError):
  1329. psutil._pslinux.boot_time()
  1330. assert m.called
  1331. def test_users(self):
  1332. # Make sure the C extension converts ':0' and ':0.0' to
  1333. # 'localhost'.
  1334. for user in psutil.users():
  1335. assert user.host not in {":0", ":0.0"}
  1336. def test_procfs_path(self):
  1337. tdir = self.get_testfn()
  1338. os.mkdir(tdir)
  1339. try:
  1340. psutil.PROCFS_PATH = tdir
  1341. with pytest.raises(OSError):
  1342. psutil.virtual_memory()
  1343. with pytest.raises(OSError):
  1344. psutil.cpu_times()
  1345. with pytest.raises(OSError):
  1346. psutil.cpu_times(percpu=True)
  1347. with pytest.raises(OSError):
  1348. psutil.boot_time()
  1349. # self.assertRaises(OSError, psutil.pids)
  1350. with pytest.raises(OSError):
  1351. psutil.net_connections()
  1352. with pytest.raises(OSError):
  1353. psutil.net_io_counters()
  1354. with pytest.raises(OSError):
  1355. psutil.net_if_stats()
  1356. # self.assertRaises(OSError, psutil.disk_io_counters)
  1357. with pytest.raises(OSError):
  1358. psutil.disk_partitions()
  1359. with pytest.raises(psutil.NoSuchProcess):
  1360. psutil.Process()
  1361. finally:
  1362. psutil.PROCFS_PATH = "/proc"
  1363. @retry_on_failure()
  1364. @pytest.mark.skipif(PYTEST_PARALLEL, reason="skip if pytest-parallel")
  1365. def test_issue_687(self):
  1366. # In case of thread ID:
  1367. # - pid_exists() is supposed to return False
  1368. # - Process(tid) is supposed to work
  1369. # - pids() should not return the TID
  1370. # See: https://github.com/giampaolo/psutil/issues/687
  1371. with ThreadTask():
  1372. p = psutil.Process()
  1373. threads = p.threads()
  1374. assert len(threads) == 2
  1375. tid = sorted(threads, key=lambda x: x.id)[1].id
  1376. assert p.pid != tid
  1377. pt = psutil.Process(tid)
  1378. pt.as_dict()
  1379. assert tid not in psutil.pids()
  1380. def test_pid_exists_no_proc_status(self):
  1381. # Internally pid_exists relies on /proc/{pid}/status.
  1382. # Emulate a case where this file is empty in which case
  1383. # psutil is supposed to fall back on using pids().
  1384. with mock_open_content({"/proc/%s/status": ""}) as m:
  1385. assert psutil.pid_exists(os.getpid())
  1386. assert m.called
  1387. # =====================================================================
  1388. # --- sensors
  1389. # =====================================================================
  1390. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  1391. @pytest.mark.skipif(not HAS_BATTERY, reason="no battery")
  1392. class TestSensorsBattery(PsutilTestCase):
  1393. @pytest.mark.skipif(
  1394. not shutil.which("acpi"), reason="acpi utility not available"
  1395. )
  1396. def test_percent(self):
  1397. out = sh("acpi -b")
  1398. acpi_value = int(out.split(",")[1].strip().replace('%', ''))
  1399. psutil_value = psutil.sensors_battery().percent
  1400. assert abs(acpi_value - psutil_value) < 1
  1401. def test_emulate_power_plugged(self):
  1402. # Pretend the AC power cable is connected.
  1403. def open_mock(name, *args, **kwargs):
  1404. if name.endswith(('AC0/online', 'AC/online')):
  1405. return io.BytesIO(b"1")
  1406. else:
  1407. return orig_open(name, *args, **kwargs)
  1408. orig_open = open
  1409. with mock.patch("builtins.open", side_effect=open_mock) as m:
  1410. assert psutil.sensors_battery().power_plugged is True
  1411. assert (
  1412. psutil.sensors_battery().secsleft
  1413. == psutil.POWER_TIME_UNLIMITED
  1414. )
  1415. assert m.called
  1416. def test_emulate_power_plugged_2(self):
  1417. # Same as above but pretend /AC0/online does not exist in which
  1418. # case code relies on /status file.
  1419. def open_mock(name, *args, **kwargs):
  1420. if name.endswith(('AC0/online', 'AC/online')):
  1421. raise FileNotFoundError
  1422. if name.endswith("/status"):
  1423. return io.StringIO("charging")
  1424. else:
  1425. return orig_open(name, *args, **kwargs)
  1426. orig_open = open
  1427. with mock.patch("builtins.open", side_effect=open_mock) as m:
  1428. assert psutil.sensors_battery().power_plugged is True
  1429. assert m.called
  1430. def test_emulate_power_not_plugged(self):
  1431. # Pretend the AC power cable is not connected.
  1432. def open_mock(name, *args, **kwargs):
  1433. if name.endswith(('AC0/online', 'AC/online')):
  1434. return io.BytesIO(b"0")
  1435. else:
  1436. return orig_open(name, *args, **kwargs)
  1437. orig_open = open
  1438. with mock.patch("builtins.open", side_effect=open_mock) as m:
  1439. assert psutil.sensors_battery().power_plugged is False
  1440. assert m.called
  1441. def test_emulate_power_not_plugged_2(self):
  1442. # Same as above but pretend /AC0/online does not exist in which
  1443. # case code relies on /status file.
  1444. def open_mock(name, *args, **kwargs):
  1445. if name.endswith(('AC0/online', 'AC/online')):
  1446. raise FileNotFoundError
  1447. if name.endswith("/status"):
  1448. return io.StringIO("discharging")
  1449. else:
  1450. return orig_open(name, *args, **kwargs)
  1451. orig_open = open
  1452. with mock.patch("builtins.open", side_effect=open_mock) as m:
  1453. assert psutil.sensors_battery().power_plugged is False
  1454. assert m.called
  1455. def test_emulate_power_undetermined(self):
  1456. # Pretend we can't know whether the AC power cable not
  1457. # connected (assert fallback to False).
  1458. def open_mock(name, *args, **kwargs):
  1459. if name.startswith((
  1460. '/sys/class/power_supply/AC0/online',
  1461. '/sys/class/power_supply/AC/online',
  1462. )):
  1463. raise FileNotFoundError
  1464. if name.startswith("/sys/class/power_supply/BAT0/status"):
  1465. return io.BytesIO(b"???")
  1466. else:
  1467. return orig_open(name, *args, **kwargs)
  1468. orig_open = open
  1469. with mock.patch("builtins.open", side_effect=open_mock) as m:
  1470. assert psutil.sensors_battery().power_plugged is None
  1471. assert m.called
  1472. def test_emulate_energy_full_0(self):
  1473. # Emulate a case where energy_full files returns 0.
  1474. with mock_open_content(
  1475. {"/sys/class/power_supply/BAT0/energy_full": b"0"}
  1476. ) as m:
  1477. assert psutil.sensors_battery().percent == 0
  1478. assert m.called
  1479. def test_emulate_energy_full_not_avail(self):
  1480. # Emulate a case where energy_full file does not exist.
  1481. # Expected fallback on /capacity.
  1482. with mock_open_exception(
  1483. "/sys/class/power_supply/BAT0/energy_full",
  1484. FileNotFoundError,
  1485. ):
  1486. with mock_open_exception(
  1487. "/sys/class/power_supply/BAT0/charge_full",
  1488. FileNotFoundError,
  1489. ):
  1490. with mock_open_content(
  1491. {"/sys/class/power_supply/BAT0/capacity": b"88"}
  1492. ):
  1493. assert psutil.sensors_battery().percent == 88
  1494. def test_emulate_no_power(self):
  1495. # Emulate a case where /AC0/online file nor /BAT0/status exist.
  1496. with mock_open_exception(
  1497. "/sys/class/power_supply/AC/online", FileNotFoundError
  1498. ):
  1499. with mock_open_exception(
  1500. "/sys/class/power_supply/AC0/online", FileNotFoundError
  1501. ):
  1502. with mock_open_exception(
  1503. "/sys/class/power_supply/BAT0/status",
  1504. FileNotFoundError,
  1505. ):
  1506. assert psutil.sensors_battery().power_plugged is None
  1507. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  1508. class TestSensorsBatteryEmulated(PsutilTestCase):
  1509. def test_it(self):
  1510. def open_mock(name, *args, **kwargs):
  1511. if name.endswith("/energy_now"):
  1512. return io.StringIO("60000000")
  1513. elif name.endswith("/power_now"):
  1514. return io.StringIO("0")
  1515. elif name.endswith("/energy_full"):
  1516. return io.StringIO("60000001")
  1517. else:
  1518. return orig_open(name, *args, **kwargs)
  1519. orig_open = open
  1520. with mock.patch('os.listdir', return_value=["BAT0"]) as mlistdir:
  1521. with mock.patch("builtins.open", side_effect=open_mock) as mopen:
  1522. assert psutil.sensors_battery() is not None
  1523. assert mlistdir.called
  1524. assert mopen.called
  1525. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  1526. class TestSensorsTemperatures(PsutilTestCase):
  1527. def test_emulate_class_hwmon(self):
  1528. def open_mock(name, *args, **kwargs):
  1529. if name.endswith('/name'):
  1530. return io.StringIO("name")
  1531. elif name.endswith('/temp1_label'):
  1532. return io.StringIO("label")
  1533. elif name.endswith('/temp1_input'):
  1534. return io.BytesIO(b"30000")
  1535. elif name.endswith('/temp1_max'):
  1536. return io.BytesIO(b"40000")
  1537. elif name.endswith('/temp1_crit'):
  1538. return io.BytesIO(b"50000")
  1539. else:
  1540. return orig_open(name, *args, **kwargs)
  1541. orig_open = open
  1542. with mock.patch("builtins.open", side_effect=open_mock):
  1543. # Test case with /sys/class/hwmon
  1544. with mock.patch(
  1545. 'glob.glob', return_value=['/sys/class/hwmon/hwmon0/temp1']
  1546. ):
  1547. temp = psutil.sensors_temperatures()['name'][0]
  1548. assert temp.label == 'label'
  1549. assert temp.current == 30.0
  1550. assert temp.high == 40.0
  1551. assert temp.critical == 50.0
  1552. def test_emulate_class_thermal(self):
  1553. def open_mock(name, *args, **kwargs):
  1554. if name.endswith('0_temp'):
  1555. return io.BytesIO(b"50000")
  1556. elif name.endswith('temp'):
  1557. return io.BytesIO(b"30000")
  1558. elif name.endswith('0_type'):
  1559. return io.StringIO("critical")
  1560. elif name.endswith('type'):
  1561. return io.StringIO("name")
  1562. else:
  1563. return orig_open(name, *args, **kwargs)
  1564. def glob_mock(path):
  1565. if path in {
  1566. '/sys/class/hwmon/hwmon*/temp*_*',
  1567. '/sys/class/hwmon/hwmon*/device/temp*_*',
  1568. }:
  1569. return []
  1570. elif path == '/sys/class/thermal/thermal_zone*':
  1571. return ['/sys/class/thermal/thermal_zone0']
  1572. elif path == '/sys/class/thermal/thermal_zone0/trip_point*':
  1573. return [
  1574. '/sys/class/thermal/thermal_zone1/trip_point_0_type',
  1575. '/sys/class/thermal/thermal_zone1/trip_point_0_temp',
  1576. ]
  1577. return []
  1578. orig_open = open
  1579. with mock.patch("builtins.open", side_effect=open_mock):
  1580. with mock.patch('glob.glob', create=True, side_effect=glob_mock):
  1581. temp = psutil.sensors_temperatures()['name'][0]
  1582. assert temp.label == ''
  1583. assert temp.current == 30.0
  1584. assert temp.high == 50.0
  1585. assert temp.critical == 50.0
  1586. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  1587. class TestSensorsFans(PsutilTestCase):
  1588. def test_emulate_data(self):
  1589. def open_mock(name, *args, **kwargs):
  1590. if name.endswith('/name'):
  1591. return io.StringIO("name")
  1592. elif name.endswith('/fan1_label'):
  1593. return io.StringIO("label")
  1594. elif name.endswith('/fan1_input'):
  1595. return io.StringIO("2000")
  1596. else:
  1597. return orig_open(name, *args, **kwargs)
  1598. orig_open = open
  1599. with mock.patch("builtins.open", side_effect=open_mock):
  1600. with mock.patch(
  1601. 'glob.glob', return_value=['/sys/class/hwmon/hwmon2/fan1']
  1602. ):
  1603. fan = psutil.sensors_fans()['name'][0]
  1604. assert fan.label == 'label'
  1605. assert fan.current == 2000
  1606. # =====================================================================
  1607. # --- test process
  1608. # =====================================================================
  1609. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  1610. class TestProcess(PsutilTestCase):
  1611. @retry_on_failure()
  1612. def test_parse_smaps_vs_memory_maps(self):
  1613. sproc = self.spawn_testproc()
  1614. uss, pss, swap = psutil._pslinux.Process(sproc.pid)._parse_smaps()
  1615. maps = psutil.Process(sproc.pid).memory_maps(grouped=False)
  1616. assert (
  1617. abs(uss - sum(x.private_dirty + x.private_clean for x in maps))
  1618. < 4096
  1619. )
  1620. assert abs(pss - sum(x.pss for x in maps)) < 4096
  1621. assert abs(swap - sum(x.swap for x in maps)) < 4096
  1622. def test_parse_smaps_mocked(self):
  1623. # See: https://github.com/giampaolo/psutil/issues/1222
  1624. content = textwrap.dedent("""\
  1625. fffff0 r-xp 00000000 00:00 0 [vsyscall]
  1626. Size: 1 kB
  1627. Rss: 2 kB
  1628. Pss: 3 kB
  1629. Shared_Clean: 4 kB
  1630. Shared_Dirty: 5 kB
  1631. Private_Clean: 6 kB
  1632. Private_Dirty: 7 kB
  1633. Referenced: 8 kB
  1634. Anonymous: 9 kB
  1635. LazyFree: 10 kB
  1636. AnonHugePages: 11 kB
  1637. ShmemPmdMapped: 12 kB
  1638. Shared_Hugetlb: 13 kB
  1639. Private_Hugetlb: 14 kB
  1640. Swap: 15 kB
  1641. SwapPss: 16 kB
  1642. KernelPageSize: 17 kB
  1643. MMUPageSize: 18 kB
  1644. Locked: 19 kB
  1645. VmFlags: rd ex
  1646. """).encode()
  1647. with mock_open_content({f"/proc/{os.getpid()}/smaps": content}) as m:
  1648. p = psutil._pslinux.Process(os.getpid())
  1649. uss, pss, swap = p._parse_smaps()
  1650. assert m.called
  1651. assert uss == (6 + 7 + 14) * 1024
  1652. assert pss == 3 * 1024
  1653. assert swap == 15 * 1024
  1654. # On PYPY file descriptors are not closed fast enough.
  1655. @pytest.mark.skipif(PYPY, reason="unreliable on PYPY")
  1656. def test_open_files_mode(self):
  1657. def get_test_file(fname):
  1658. p = psutil.Process()
  1659. giveup_at = time.time() + GLOBAL_TIMEOUT
  1660. while True:
  1661. for file in p.open_files():
  1662. if file.path == os.path.abspath(fname):
  1663. return file
  1664. elif time.time() > giveup_at:
  1665. break
  1666. raise RuntimeError("timeout looking for test file")
  1667. testfn = self.get_testfn()
  1668. with open(testfn, "w"):
  1669. assert get_test_file(testfn).mode == "w"
  1670. with open(testfn):
  1671. assert get_test_file(testfn).mode == "r"
  1672. with open(testfn, "a"):
  1673. assert get_test_file(testfn).mode == "a"
  1674. with open(testfn, "r+"):
  1675. assert get_test_file(testfn).mode == "r+"
  1676. with open(testfn, "w+"):
  1677. assert get_test_file(testfn).mode == "r+"
  1678. with open(testfn, "a+"):
  1679. assert get_test_file(testfn).mode == "a+"
  1680. safe_rmpath(testfn)
  1681. with open(testfn, "x"):
  1682. assert get_test_file(testfn).mode == "w"
  1683. safe_rmpath(testfn)
  1684. with open(testfn, "x+"):
  1685. assert get_test_file(testfn).mode == "r+"
  1686. def test_open_files_file_gone(self):
  1687. # simulates a file which gets deleted during open_files()
  1688. # execution
  1689. p = psutil.Process()
  1690. files = p.open_files()
  1691. with open(self.get_testfn(), 'w'):
  1692. # give the kernel some time to see the new file
  1693. call_until(lambda: len(p.open_files()) != len(files))
  1694. with mock.patch(
  1695. 'psutil._pslinux.os.readlink',
  1696. side_effect=FileNotFoundError,
  1697. ) as m:
  1698. assert p.open_files() == []
  1699. assert m.called
  1700. # also simulate the case where os.readlink() returns EINVAL
  1701. # in which case psutil is supposed to 'continue'
  1702. with mock.patch(
  1703. 'psutil._pslinux.os.readlink',
  1704. side_effect=OSError(errno.EINVAL, ""),
  1705. ) as m:
  1706. assert p.open_files() == []
  1707. assert m.called
  1708. def test_open_files_fd_gone(self):
  1709. # Simulate a case where /proc/{pid}/fdinfo/{fd} disappears
  1710. # while iterating through fds.
  1711. # https://travis-ci.org/giampaolo/psutil/jobs/225694530
  1712. p = psutil.Process()
  1713. files = p.open_files()
  1714. with open(self.get_testfn(), 'w'):
  1715. # give the kernel some time to see the new file
  1716. call_until(lambda: len(p.open_files()) != len(files))
  1717. with mock.patch(
  1718. "builtins.open", side_effect=FileNotFoundError
  1719. ) as m:
  1720. assert p.open_files() == []
  1721. assert m.called
  1722. def test_open_files_enametoolong(self):
  1723. # Simulate a case where /proc/{pid}/fd/{fd} symlink
  1724. # points to a file with full path longer than PATH_MAX, see:
  1725. # https://github.com/giampaolo/psutil/issues/1940
  1726. p = psutil.Process()
  1727. files = p.open_files()
  1728. with open(self.get_testfn(), 'w'):
  1729. # give the kernel some time to see the new file
  1730. call_until(lambda: len(p.open_files()) != len(files))
  1731. patch_point = 'psutil._pslinux.os.readlink'
  1732. with mock.patch(
  1733. patch_point, side_effect=OSError(errno.ENAMETOOLONG, "")
  1734. ) as m:
  1735. with mock.patch("psutil._pslinux.debug"):
  1736. assert p.open_files() == []
  1737. assert m.called
  1738. # --- mocked tests
  1739. def test_terminal_mocked(self):
  1740. with mock.patch(
  1741. 'psutil._pslinux._psposix.get_terminal_map', return_value={}
  1742. ) as m:
  1743. assert psutil._pslinux.Process(os.getpid()).terminal() is None
  1744. assert m.called
  1745. # TODO: re-enable this test.
  1746. # def test_num_ctx_switches_mocked(self):
  1747. # with mock.patch('psutil._common.open', create=True) as m:
  1748. # self.assertRaises(
  1749. # NotImplementedError,
  1750. # psutil._pslinux.Process(os.getpid()).num_ctx_switches)
  1751. # assert m.called
  1752. def test_cmdline_mocked(self):
  1753. # see: https://github.com/giampaolo/psutil/issues/639
  1754. p = psutil.Process()
  1755. fake_file = io.StringIO('foo\x00bar\x00')
  1756. with mock.patch(
  1757. 'psutil._common.open', return_value=fake_file, create=True
  1758. ) as m:
  1759. assert p.cmdline() == ['foo', 'bar']
  1760. assert m.called
  1761. fake_file = io.StringIO('foo\x00bar\x00\x00')
  1762. with mock.patch(
  1763. 'psutil._common.open', return_value=fake_file, create=True
  1764. ) as m:
  1765. assert p.cmdline() == ['foo', 'bar', '']
  1766. assert m.called
  1767. def test_cmdline_spaces_mocked(self):
  1768. # see: https://github.com/giampaolo/psutil/issues/1179
  1769. p = psutil.Process()
  1770. fake_file = io.StringIO('foo bar ')
  1771. with mock.patch(
  1772. 'psutil._common.open', return_value=fake_file, create=True
  1773. ) as m:
  1774. assert p.cmdline() == ['foo', 'bar']
  1775. assert m.called
  1776. fake_file = io.StringIO('foo bar ')
  1777. with mock.patch(
  1778. 'psutil._common.open', return_value=fake_file, create=True
  1779. ) as m:
  1780. assert p.cmdline() == ['foo', 'bar', '']
  1781. assert m.called
  1782. def test_cmdline_mixed_separators(self):
  1783. # https://github.com/giampaolo/psutil/issues/
  1784. # 1179#issuecomment-552984549
  1785. p = psutil.Process()
  1786. fake_file = io.StringIO('foo\x20bar\x00')
  1787. with mock.patch(
  1788. 'psutil._common.open', return_value=fake_file, create=True
  1789. ) as m:
  1790. assert p.cmdline() == ['foo', 'bar']
  1791. assert m.called
  1792. def test_readlink_path_deleted_mocked(self):
  1793. with mock.patch(
  1794. 'psutil._pslinux.os.readlink', return_value='/home/foo (deleted)'
  1795. ):
  1796. assert psutil.Process().exe() == "/home/foo"
  1797. assert psutil.Process().cwd() == "/home/foo"
  1798. def test_threads_mocked(self):
  1799. # Test the case where os.listdir() returns a file (thread)
  1800. # which no longer exists by the time we open() it (race
  1801. # condition). threads() is supposed to ignore that instead
  1802. # of raising NSP.
  1803. def open_mock_1(name, *args, **kwargs):
  1804. if name.startswith(f"/proc/{os.getpid()}/task"):
  1805. raise FileNotFoundError
  1806. return orig_open(name, *args, **kwargs)
  1807. orig_open = open
  1808. with mock.patch("builtins.open", side_effect=open_mock_1) as m:
  1809. ret = psutil.Process().threads()
  1810. assert m.called
  1811. assert ret == []
  1812. # ...but if it bumps into something != ENOENT we want an
  1813. # exception.
  1814. def open_mock_2(name, *args, **kwargs):
  1815. if name.startswith(f"/proc/{os.getpid()}/task"):
  1816. raise PermissionError
  1817. return orig_open(name, *args, **kwargs)
  1818. with mock.patch("builtins.open", side_effect=open_mock_2):
  1819. with pytest.raises(psutil.AccessDenied):
  1820. psutil.Process().threads()
  1821. def test_exe_mocked(self):
  1822. with mock.patch(
  1823. 'psutil._pslinux.readlink', side_effect=FileNotFoundError
  1824. ) as m:
  1825. # de-activate guessing from cmdline()
  1826. with mock.patch(
  1827. 'psutil._pslinux.Process.cmdline', return_value=[]
  1828. ):
  1829. ret = psutil.Process().exe()
  1830. assert m.called
  1831. assert ret == ""
  1832. def test_issue_1014(self):
  1833. # Emulates a case where smaps file does not exist. In this case
  1834. # wrap_exception decorator should not raise NoSuchProcess.
  1835. with mock_open_exception(
  1836. f"/proc/{os.getpid()}/smaps", FileNotFoundError
  1837. ) as m:
  1838. p = psutil.Process()
  1839. with pytest.raises(FileNotFoundError):
  1840. p.memory_maps()
  1841. assert m.called
  1842. def test_issue_2418(self):
  1843. p = psutil.Process()
  1844. with mock_open_exception(
  1845. f"/proc/{os.getpid()}/statm", FileNotFoundError
  1846. ):
  1847. with mock.patch("os.path.exists", return_value=False):
  1848. with pytest.raises(psutil.NoSuchProcess):
  1849. p.memory_info()
  1850. @pytest.mark.skipif(not HAS_RLIMIT, reason="not supported")
  1851. def test_rlimit_zombie(self):
  1852. # Emulate a case where rlimit() raises ENOSYS, which may
  1853. # happen in case of zombie process:
  1854. # https://travis-ci.org/giampaolo/psutil/jobs/51368273
  1855. with mock.patch(
  1856. "resource.prlimit", side_effect=OSError(errno.ENOSYS, "")
  1857. ) as m1:
  1858. with mock.patch(
  1859. "psutil._pslinux.Process._is_zombie", return_value=True
  1860. ) as m2:
  1861. p = psutil.Process()
  1862. p.name()
  1863. with pytest.raises(psutil.ZombieProcess) as cm:
  1864. p.rlimit(psutil.RLIMIT_NOFILE)
  1865. assert m1.called
  1866. assert m2.called
  1867. assert cm.value.pid == p.pid
  1868. assert cm.value.name == p.name()
  1869. def test_stat_file_parsing(self):
  1870. args = [
  1871. "0", # pid
  1872. "(cat)", # name
  1873. "Z", # status
  1874. "1", # ppid
  1875. "0", # pgrp
  1876. "0", # session
  1877. "0", # tty
  1878. "0", # tpgid
  1879. "0", # flags
  1880. "0", # minflt
  1881. "0", # cminflt
  1882. "0", # majflt
  1883. "0", # cmajflt
  1884. "2", # utime
  1885. "3", # stime
  1886. "4", # cutime
  1887. "5", # cstime
  1888. "0", # priority
  1889. "0", # nice
  1890. "0", # num_threads
  1891. "0", # itrealvalue
  1892. "6", # starttime
  1893. "0", # vsize
  1894. "0", # rss
  1895. "0", # rsslim
  1896. "0", # startcode
  1897. "0", # endcode
  1898. "0", # startstack
  1899. "0", # kstkesp
  1900. "0", # kstkeip
  1901. "0", # signal
  1902. "0", # blocked
  1903. "0", # sigignore
  1904. "0", # sigcatch
  1905. "0", # wchan
  1906. "0", # nswap
  1907. "0", # cnswap
  1908. "0", # exit_signal
  1909. "6", # processor
  1910. "0", # rt priority
  1911. "0", # policy
  1912. "7", # delayacct_blkio_ticks
  1913. ]
  1914. content = " ".join(args).encode()
  1915. with mock_open_content({f"/proc/{os.getpid()}/stat": content}):
  1916. p = psutil.Process()
  1917. assert p.name() == 'cat'
  1918. assert p.status() == psutil.STATUS_ZOMBIE
  1919. assert p.ppid() == 1
  1920. assert p.create_time() == 6 / CLOCK_TICKS + psutil.boot_time()
  1921. cpu = p.cpu_times()
  1922. assert cpu.user == 2 / CLOCK_TICKS
  1923. assert cpu.system == 3 / CLOCK_TICKS
  1924. assert cpu.children_user == 4 / CLOCK_TICKS
  1925. assert cpu.children_system == 5 / CLOCK_TICKS
  1926. assert cpu.iowait == 7 / CLOCK_TICKS
  1927. assert p.cpu_num() == 6
  1928. def test_status_file_parsing(self):
  1929. content = textwrap.dedent("""\
  1930. Uid:\t1000\t1001\t1002\t1003
  1931. Gid:\t1004\t1005\t1006\t1007
  1932. Threads:\t66
  1933. Cpus_allowed:\tf
  1934. Cpus_allowed_list:\t0-7
  1935. voluntary_ctxt_switches:\t12
  1936. nonvoluntary_ctxt_switches:\t13""").encode()
  1937. with mock_open_content({f"/proc/{os.getpid()}/status": content}):
  1938. p = psutil.Process()
  1939. assert p.num_ctx_switches().voluntary == 12
  1940. assert p.num_ctx_switches().involuntary == 13
  1941. assert p.num_threads() == 66
  1942. uids = p.uids()
  1943. assert uids.real == 1000
  1944. assert uids.effective == 1001
  1945. assert uids.saved == 1002
  1946. gids = p.gids()
  1947. assert gids.real == 1004
  1948. assert gids.effective == 1005
  1949. assert gids.saved == 1006
  1950. assert p._proc._get_eligible_cpus() == list(range(8))
  1951. def test_net_connections_enametoolong(self):
  1952. # Simulate a case where /proc/{pid}/fd/{fd} symlink points to
  1953. # a file with full path longer than PATH_MAX, see:
  1954. # https://github.com/giampaolo/psutil/issues/1940
  1955. with mock.patch(
  1956. 'psutil._pslinux.os.readlink',
  1957. side_effect=OSError(errno.ENAMETOOLONG, ""),
  1958. ) as m:
  1959. p = psutil.Process()
  1960. with mock.patch("psutil._pslinux.debug"):
  1961. assert p.net_connections() == []
  1962. assert m.called
  1963. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  1964. class TestProcessAgainstStatus(PsutilTestCase):
  1965. """/proc/pid/stat and /proc/pid/status have many values in common.
  1966. Whenever possible, psutil uses /proc/pid/stat (it's faster).
  1967. For all those cases we check that the value found in
  1968. /proc/pid/stat (by psutil) matches the one found in
  1969. /proc/pid/status.
  1970. """
  1971. @classmethod
  1972. def setUpClass(cls):
  1973. cls.proc = psutil.Process()
  1974. def read_status_file(self, linestart):
  1975. with psutil._psplatform.open_text(
  1976. f"/proc/{self.proc.pid}/status"
  1977. ) as f:
  1978. for line in f:
  1979. line = line.strip()
  1980. if line.startswith(linestart):
  1981. value = line.partition('\t')[2]
  1982. try:
  1983. return int(value)
  1984. except ValueError:
  1985. return value
  1986. raise ValueError(f"can't find {linestart!r}")
  1987. def test_name(self):
  1988. value = self.read_status_file("Name:")
  1989. assert self.proc.name() == value
  1990. def test_status(self):
  1991. value = self.read_status_file("State:")
  1992. value = value[value.find('(') + 1 : value.rfind(')')]
  1993. value = value.replace(' ', '-')
  1994. assert self.proc.status() == value
  1995. def test_ppid(self):
  1996. value = self.read_status_file("PPid:")
  1997. assert self.proc.ppid() == value
  1998. def test_num_threads(self):
  1999. value = self.read_status_file("Threads:")
  2000. assert self.proc.num_threads() == value
  2001. def test_uids(self):
  2002. value = self.read_status_file("Uid:")
  2003. value = tuple(map(int, value.split()[1:4]))
  2004. assert self.proc.uids() == value
  2005. def test_gids(self):
  2006. value = self.read_status_file("Gid:")
  2007. value = tuple(map(int, value.split()[1:4]))
  2008. assert self.proc.gids() == value
  2009. @retry_on_failure()
  2010. def test_num_ctx_switches(self):
  2011. value = self.read_status_file("voluntary_ctxt_switches:")
  2012. assert self.proc.num_ctx_switches().voluntary == value
  2013. value = self.read_status_file("nonvoluntary_ctxt_switches:")
  2014. assert self.proc.num_ctx_switches().involuntary == value
  2015. def test_cpu_affinity(self):
  2016. value = self.read_status_file("Cpus_allowed_list:")
  2017. if '-' in str(value):
  2018. min_, max_ = map(int, value.split('-'))
  2019. assert self.proc.cpu_affinity() == list(range(min_, max_ + 1))
  2020. def test_cpu_affinity_eligible_cpus(self):
  2021. value = self.read_status_file("Cpus_allowed_list:")
  2022. with mock.patch("psutil._pslinux.per_cpu_times") as m:
  2023. self.proc._proc._get_eligible_cpus()
  2024. if '-' in str(value):
  2025. assert not m.called
  2026. else:
  2027. assert m.called
  2028. # =====================================================================
  2029. # --- test utils
  2030. # =====================================================================
  2031. @pytest.mark.skipif(not LINUX, reason="LINUX only")
  2032. class TestUtils(PsutilTestCase):
  2033. def test_readlink(self):
  2034. with mock.patch("os.readlink", return_value="foo (deleted)") as m:
  2035. assert psutil._psplatform.readlink("bar") == "foo"
  2036. assert m.called