comparison env/lib/python3.9/site-packages/psutil/_psosx.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 # Copyright (c) 2009, Giampaolo Rodola'. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """macOS platform implementation."""
6
7 import contextlib
8 import errno
9 import functools
10 import os
11 from collections import namedtuple
12
13 from . import _common
14 from . import _psposix
15 from . import _psutil_osx as cext
16 from . import _psutil_posix as cext_posix
17 from ._common import AccessDenied
18 from ._common import conn_tmap
19 from ._common import conn_to_ntuple
20 from ._common import isfile_strict
21 from ._common import memoize_when_activated
22 from ._common import NoSuchProcess
23 from ._common import parse_environ_block
24 from ._common import usage_percent
25 from ._common import ZombieProcess
26 from ._compat import PermissionError
27 from ._compat import ProcessLookupError
28
29
30 __extra__all__ = []
31
32
33 # =====================================================================
34 # --- globals
35 # =====================================================================
36
37
38 PAGESIZE = cext_posix.getpagesize()
39 AF_LINK = cext_posix.AF_LINK
40
41 TCP_STATUSES = {
42 cext.TCPS_ESTABLISHED: _common.CONN_ESTABLISHED,
43 cext.TCPS_SYN_SENT: _common.CONN_SYN_SENT,
44 cext.TCPS_SYN_RECEIVED: _common.CONN_SYN_RECV,
45 cext.TCPS_FIN_WAIT_1: _common.CONN_FIN_WAIT1,
46 cext.TCPS_FIN_WAIT_2: _common.CONN_FIN_WAIT2,
47 cext.TCPS_TIME_WAIT: _common.CONN_TIME_WAIT,
48 cext.TCPS_CLOSED: _common.CONN_CLOSE,
49 cext.TCPS_CLOSE_WAIT: _common.CONN_CLOSE_WAIT,
50 cext.TCPS_LAST_ACK: _common.CONN_LAST_ACK,
51 cext.TCPS_LISTEN: _common.CONN_LISTEN,
52 cext.TCPS_CLOSING: _common.CONN_CLOSING,
53 cext.PSUTIL_CONN_NONE: _common.CONN_NONE,
54 }
55
56 PROC_STATUSES = {
57 cext.SIDL: _common.STATUS_IDLE,
58 cext.SRUN: _common.STATUS_RUNNING,
59 cext.SSLEEP: _common.STATUS_SLEEPING,
60 cext.SSTOP: _common.STATUS_STOPPED,
61 cext.SZOMB: _common.STATUS_ZOMBIE,
62 }
63
64 kinfo_proc_map = dict(
65 ppid=0,
66 ruid=1,
67 euid=2,
68 suid=3,
69 rgid=4,
70 egid=5,
71 sgid=6,
72 ttynr=7,
73 ctime=8,
74 status=9,
75 name=10,
76 )
77
78 pidtaskinfo_map = dict(
79 cpuutime=0,
80 cpustime=1,
81 rss=2,
82 vms=3,
83 pfaults=4,
84 pageins=5,
85 numthreads=6,
86 volctxsw=7,
87 )
88
89
90 # =====================================================================
91 # --- named tuples
92 # =====================================================================
93
94
95 # psutil.cpu_times()
96 scputimes = namedtuple('scputimes', ['user', 'nice', 'system', 'idle'])
97 # psutil.virtual_memory()
98 svmem = namedtuple(
99 'svmem', ['total', 'available', 'percent', 'used', 'free',
100 'active', 'inactive', 'wired'])
101 # psutil.Process.memory_info()
102 pmem = namedtuple('pmem', ['rss', 'vms', 'pfaults', 'pageins'])
103 # psutil.Process.memory_full_info()
104 pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', ))
105
106
107 # =====================================================================
108 # --- memory
109 # =====================================================================
110
111
112 def virtual_memory():
113 """System virtual memory as a namedtuple."""
114 total, active, inactive, wired, free, speculative = cext.virtual_mem()
115 # This is how Zabbix calculate avail and used mem:
116 # https://github.com/zabbix/zabbix/blob/trunk/src/libs/zbxsysinfo/
117 # osx/memory.c
118 # Also see: https://github.com/giampaolo/psutil/issues/1277
119 avail = inactive + free
120 used = active + wired
121 # This is NOT how Zabbix calculates free mem but it matches "free"
122 # cmdline utility.
123 free -= speculative
124 percent = usage_percent((total - avail), total, round_=1)
125 return svmem(total, avail, percent, used, free,
126 active, inactive, wired)
127
128
129 def swap_memory():
130 """Swap system memory as a (total, used, free, sin, sout) tuple."""
131 total, used, free, sin, sout = cext.swap_mem()
132 percent = usage_percent(used, total, round_=1)
133 return _common.sswap(total, used, free, percent, sin, sout)
134
135
136 # =====================================================================
137 # --- CPU
138 # =====================================================================
139
140
141 def cpu_times():
142 """Return system CPU times as a namedtuple."""
143 user, nice, system, idle = cext.cpu_times()
144 return scputimes(user, nice, system, idle)
145
146
147 def per_cpu_times():
148 """Return system CPU times as a named tuple"""
149 ret = []
150 for cpu_t in cext.per_cpu_times():
151 user, nice, system, idle = cpu_t
152 item = scputimes(user, nice, system, idle)
153 ret.append(item)
154 return ret
155
156
157 def cpu_count_logical():
158 """Return the number of logical CPUs in the system."""
159 return cext.cpu_count_logical()
160
161
162 def cpu_count_physical():
163 """Return the number of physical CPUs in the system."""
164 return cext.cpu_count_phys()
165
166
167 def cpu_stats():
168 ctx_switches, interrupts, soft_interrupts, syscalls, traps = \
169 cext.cpu_stats()
170 return _common.scpustats(
171 ctx_switches, interrupts, soft_interrupts, syscalls)
172
173
174 def cpu_freq():
175 """Return CPU frequency.
176 On macOS per-cpu frequency is not supported.
177 Also, the returned frequency never changes, see:
178 https://arstechnica.com/civis/viewtopic.php?f=19&t=465002
179 """
180 curr, min_, max_ = cext.cpu_freq()
181 return [_common.scpufreq(curr, min_, max_)]
182
183
184 # =====================================================================
185 # --- disks
186 # =====================================================================
187
188
189 disk_usage = _psposix.disk_usage
190 disk_io_counters = cext.disk_io_counters
191
192
193 def disk_partitions(all=False):
194 """Return mounted disk partitions as a list of namedtuples."""
195 retlist = []
196 partitions = cext.disk_partitions()
197 for partition in partitions:
198 device, mountpoint, fstype, opts = partition
199 if device == 'none':
200 device = ''
201 if not all:
202 if not os.path.isabs(device) or not os.path.exists(device):
203 continue
204 maxfile = maxpath = None # set later
205 ntuple = _common.sdiskpart(device, mountpoint, fstype, opts,
206 maxfile, maxpath)
207 retlist.append(ntuple)
208 return retlist
209
210
211 # =====================================================================
212 # --- sensors
213 # =====================================================================
214
215
216 def sensors_battery():
217 """Return battery information."""
218 try:
219 percent, minsleft, power_plugged = cext.sensors_battery()
220 except NotImplementedError:
221 # no power source - return None according to interface
222 return None
223 power_plugged = power_plugged == 1
224 if power_plugged:
225 secsleft = _common.POWER_TIME_UNLIMITED
226 elif minsleft == -1:
227 secsleft = _common.POWER_TIME_UNKNOWN
228 else:
229 secsleft = minsleft * 60
230 return _common.sbattery(percent, secsleft, power_plugged)
231
232
233 # =====================================================================
234 # --- network
235 # =====================================================================
236
237
238 net_io_counters = cext.net_io_counters
239 net_if_addrs = cext_posix.net_if_addrs
240
241
242 def net_connections(kind='inet'):
243 """System-wide network connections."""
244 # Note: on macOS this will fail with AccessDenied unless
245 # the process is owned by root.
246 ret = []
247 for pid in pids():
248 try:
249 cons = Process(pid).connections(kind)
250 except NoSuchProcess:
251 continue
252 else:
253 if cons:
254 for c in cons:
255 c = list(c) + [pid]
256 ret.append(_common.sconn(*c))
257 return ret
258
259
260 def net_if_stats():
261 """Get NIC stats (isup, duplex, speed, mtu)."""
262 names = net_io_counters().keys()
263 ret = {}
264 for name in names:
265 try:
266 mtu = cext_posix.net_if_mtu(name)
267 isup = cext_posix.net_if_is_running(name)
268 duplex, speed = cext_posix.net_if_duplex_speed(name)
269 except OSError as err:
270 # https://github.com/giampaolo/psutil/issues/1279
271 if err.errno != errno.ENODEV:
272 raise
273 else:
274 if hasattr(_common, 'NicDuplex'):
275 duplex = _common.NicDuplex(duplex)
276 ret[name] = _common.snicstats(isup, duplex, speed, mtu)
277 return ret
278
279
280 # =====================================================================
281 # --- other system functions
282 # =====================================================================
283
284
285 def boot_time():
286 """The system boot time expressed in seconds since the epoch."""
287 return cext.boot_time()
288
289
290 def users():
291 """Return currently connected users as a list of namedtuples."""
292 retlist = []
293 rawlist = cext.users()
294 for item in rawlist:
295 user, tty, hostname, tstamp, pid = item
296 if tty == '~':
297 continue # reboot or shutdown
298 if not tstamp:
299 continue
300 nt = _common.suser(user, tty or None, hostname or None, tstamp, pid)
301 retlist.append(nt)
302 return retlist
303
304
305 # =====================================================================
306 # --- processes
307 # =====================================================================
308
309
310 def pids():
311 ls = cext.pids()
312 if 0 not in ls:
313 # On certain macOS versions pids() C doesn't return PID 0 but
314 # "ps" does and the process is querable via sysctl():
315 # https://travis-ci.org/giampaolo/psutil/jobs/309619941
316 try:
317 Process(0).create_time()
318 ls.insert(0, 0)
319 except NoSuchProcess:
320 pass
321 except AccessDenied:
322 ls.insert(0, 0)
323 return ls
324
325
326 pid_exists = _psposix.pid_exists
327
328
329 def is_zombie(pid):
330 try:
331 st = cext.proc_kinfo_oneshot(pid)[kinfo_proc_map['status']]
332 return st == cext.SZOMB
333 except Exception:
334 return False
335
336
337 def wrap_exceptions(fun):
338 """Decorator which translates bare OSError exceptions into
339 NoSuchProcess and AccessDenied.
340 """
341 @functools.wraps(fun)
342 def wrapper(self, *args, **kwargs):
343 try:
344 return fun(self, *args, **kwargs)
345 except ProcessLookupError:
346 if is_zombie(self.pid):
347 raise ZombieProcess(self.pid, self._name, self._ppid)
348 else:
349 raise NoSuchProcess(self.pid, self._name)
350 except PermissionError:
351 raise AccessDenied(self.pid, self._name)
352 except cext.ZombieProcessError:
353 raise ZombieProcess(self.pid, self._name, self._ppid)
354 return wrapper
355
356
357 @contextlib.contextmanager
358 def catch_zombie(proc):
359 """There are some poor C APIs which incorrectly raise ESRCH when
360 the process is still alive or it's a zombie, or even RuntimeError
361 (those who don't set errno). This is here in order to solve:
362 https://github.com/giampaolo/psutil/issues/1044
363 """
364 try:
365 yield
366 except (OSError, RuntimeError) as err:
367 if isinstance(err, RuntimeError) or err.errno == errno.ESRCH:
368 try:
369 # status() is not supposed to lie and correctly detect
370 # zombies so if it raises ESRCH it's true.
371 status = proc.status()
372 except NoSuchProcess:
373 raise err
374 else:
375 if status == _common.STATUS_ZOMBIE:
376 raise ZombieProcess(proc.pid, proc._name, proc._ppid)
377 else:
378 raise AccessDenied(proc.pid, proc._name)
379 else:
380 raise
381
382
383 class Process(object):
384 """Wrapper class around underlying C implementation."""
385
386 __slots__ = ["pid", "_name", "_ppid", "_cache"]
387
388 def __init__(self, pid):
389 self.pid = pid
390 self._name = None
391 self._ppid = None
392
393 @wrap_exceptions
394 @memoize_when_activated
395 def _get_kinfo_proc(self):
396 # Note: should work with all PIDs without permission issues.
397 ret = cext.proc_kinfo_oneshot(self.pid)
398 assert len(ret) == len(kinfo_proc_map)
399 return ret
400
401 @wrap_exceptions
402 @memoize_when_activated
403 def _get_pidtaskinfo(self):
404 # Note: should work for PIDs owned by user only.
405 with catch_zombie(self):
406 ret = cext.proc_pidtaskinfo_oneshot(self.pid)
407 assert len(ret) == len(pidtaskinfo_map)
408 return ret
409
410 def oneshot_enter(self):
411 self._get_kinfo_proc.cache_activate(self)
412 self._get_pidtaskinfo.cache_activate(self)
413
414 def oneshot_exit(self):
415 self._get_kinfo_proc.cache_deactivate(self)
416 self._get_pidtaskinfo.cache_deactivate(self)
417
418 @wrap_exceptions
419 def name(self):
420 name = self._get_kinfo_proc()[kinfo_proc_map['name']]
421 return name if name is not None else cext.proc_name(self.pid)
422
423 @wrap_exceptions
424 def exe(self):
425 with catch_zombie(self):
426 return cext.proc_exe(self.pid)
427
428 @wrap_exceptions
429 def cmdline(self):
430 with catch_zombie(self):
431 return cext.proc_cmdline(self.pid)
432
433 @wrap_exceptions
434 def environ(self):
435 with catch_zombie(self):
436 return parse_environ_block(cext.proc_environ(self.pid))
437
438 @wrap_exceptions
439 def ppid(self):
440 self._ppid = self._get_kinfo_proc()[kinfo_proc_map['ppid']]
441 return self._ppid
442
443 @wrap_exceptions
444 def cwd(self):
445 with catch_zombie(self):
446 return cext.proc_cwd(self.pid)
447
448 @wrap_exceptions
449 def uids(self):
450 rawtuple = self._get_kinfo_proc()
451 return _common.puids(
452 rawtuple[kinfo_proc_map['ruid']],
453 rawtuple[kinfo_proc_map['euid']],
454 rawtuple[kinfo_proc_map['suid']])
455
456 @wrap_exceptions
457 def gids(self):
458 rawtuple = self._get_kinfo_proc()
459 return _common.puids(
460 rawtuple[kinfo_proc_map['rgid']],
461 rawtuple[kinfo_proc_map['egid']],
462 rawtuple[kinfo_proc_map['sgid']])
463
464 @wrap_exceptions
465 def terminal(self):
466 tty_nr = self._get_kinfo_proc()[kinfo_proc_map['ttynr']]
467 tmap = _psposix.get_terminal_map()
468 try:
469 return tmap[tty_nr]
470 except KeyError:
471 return None
472
473 @wrap_exceptions
474 def memory_info(self):
475 rawtuple = self._get_pidtaskinfo()
476 return pmem(
477 rawtuple[pidtaskinfo_map['rss']],
478 rawtuple[pidtaskinfo_map['vms']],
479 rawtuple[pidtaskinfo_map['pfaults']],
480 rawtuple[pidtaskinfo_map['pageins']],
481 )
482
483 @wrap_exceptions
484 def memory_full_info(self):
485 basic_mem = self.memory_info()
486 uss = cext.proc_memory_uss(self.pid)
487 return pfullmem(*basic_mem + (uss, ))
488
489 @wrap_exceptions
490 def cpu_times(self):
491 rawtuple = self._get_pidtaskinfo()
492 return _common.pcputimes(
493 rawtuple[pidtaskinfo_map['cpuutime']],
494 rawtuple[pidtaskinfo_map['cpustime']],
495 # children user / system times are not retrievable (set to 0)
496 0.0, 0.0)
497
498 @wrap_exceptions
499 def create_time(self):
500 return self._get_kinfo_proc()[kinfo_proc_map['ctime']]
501
502 @wrap_exceptions
503 def num_ctx_switches(self):
504 # Unvoluntary value seems not to be available;
505 # getrusage() numbers seems to confirm this theory.
506 # We set it to 0.
507 vol = self._get_pidtaskinfo()[pidtaskinfo_map['volctxsw']]
508 return _common.pctxsw(vol, 0)
509
510 @wrap_exceptions
511 def num_threads(self):
512 return self._get_pidtaskinfo()[pidtaskinfo_map['numthreads']]
513
514 @wrap_exceptions
515 def open_files(self):
516 if self.pid == 0:
517 return []
518 files = []
519 with catch_zombie(self):
520 rawlist = cext.proc_open_files(self.pid)
521 for path, fd in rawlist:
522 if isfile_strict(path):
523 ntuple = _common.popenfile(path, fd)
524 files.append(ntuple)
525 return files
526
527 @wrap_exceptions
528 def connections(self, kind='inet'):
529 if kind not in conn_tmap:
530 raise ValueError("invalid %r kind argument; choose between %s"
531 % (kind, ', '.join([repr(x) for x in conn_tmap])))
532 families, types = conn_tmap[kind]
533 with catch_zombie(self):
534 rawlist = cext.proc_connections(self.pid, families, types)
535 ret = []
536 for item in rawlist:
537 fd, fam, type, laddr, raddr, status = item
538 nt = conn_to_ntuple(fd, fam, type, laddr, raddr, status,
539 TCP_STATUSES)
540 ret.append(nt)
541 return ret
542
543 @wrap_exceptions
544 def num_fds(self):
545 if self.pid == 0:
546 return 0
547 with catch_zombie(self):
548 return cext.proc_num_fds(self.pid)
549
550 @wrap_exceptions
551 def wait(self, timeout=None):
552 return _psposix.wait_pid(self.pid, timeout, self._name)
553
554 @wrap_exceptions
555 def nice_get(self):
556 with catch_zombie(self):
557 return cext_posix.getpriority(self.pid)
558
559 @wrap_exceptions
560 def nice_set(self, value):
561 with catch_zombie(self):
562 return cext_posix.setpriority(self.pid, value)
563
564 @wrap_exceptions
565 def status(self):
566 code = self._get_kinfo_proc()[kinfo_proc_map['status']]
567 # XXX is '?' legit? (we're not supposed to return it anyway)
568 return PROC_STATUSES.get(code, '?')
569
570 @wrap_exceptions
571 def threads(self):
572 rawlist = cext.proc_threads(self.pid)
573 retlist = []
574 for thread_id, utime, stime in rawlist:
575 ntuple = _common.pthread(thread_id, utime, stime)
576 retlist.append(ntuple)
577 return retlist