diff options
author | robot-piglet <robot-piglet@yandex-team.com> | 2024-03-05 14:54:25 +0300 |
---|---|---|
committer | robot-piglet <robot-piglet@yandex-team.com> | 2024-03-05 15:05:44 +0300 |
commit | f6c02fde7bd26b2e3f57a32972f9aa1e47144be8 (patch) | |
tree | 0c89e1977d3d3c394b11dfea2932e40c8445837a /contrib/python | |
parent | 274118e9de20923d783e4ec247dfbc8a63a86c8e (diff) | |
download | ydb-f6c02fde7bd26b2e3f57a32972f9aa1e47144be8.tar.gz |
Intermediate changes
Diffstat (limited to 'contrib/python')
75 files changed, 7146 insertions, 5699 deletions
diff --git a/contrib/python/hypothesis/py3/.dist-info/METADATA b/contrib/python/hypothesis/py3/.dist-info/METADATA index 26ae8b8481..d883f6dec6 100644 --- a/contrib/python/hypothesis/py3/.dist-info/METADATA +++ b/contrib/python/hypothesis/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: hypothesis -Version: 6.98.8 +Version: 6.98.9 Summary: A library for property-based testing Home-page: https://hypothesis.works Author: David R. MacIver and Zac Hatfield-Dodds diff --git a/contrib/python/hypothesis/py3/hypothesis/core.py b/contrib/python/hypothesis/py3/hypothesis/core.py index e000da174f..536d7f0156 100644 --- a/contrib/python/hypothesis/py3/hypothesis/core.py +++ b/contrib/python/hypothesis/py3/hypothesis/core.py @@ -834,8 +834,9 @@ class StateForActualGivenExecution: in_drawtime = math.fsum(data.draw_times.values()) - arg_drawtime runtime = datetime.timedelta(seconds=finish - start - in_drawtime) self._timing_features = { - "execute_test": finish - start - in_drawtime, + "execute:test": finish - start - in_drawtime, **data.draw_times, + **data._stateful_run_times, } if (current_deadline := self.settings.deadline) is not None: @@ -927,6 +928,9 @@ class StateForActualGivenExecution: msg, format_arg = data._sampled_from_all_strategies_elements_message add_note(e, msg.format(format_arg)) raise + finally: + if parts := getattr(data, "_stateful_repr_parts", None): + self._string_repr = "\n".join(parts) # self.test_runner can include the execute_example method, or setup/teardown # _example, so it's important to get the PRNG and build context in place first. @@ -942,7 +946,11 @@ class StateForActualGivenExecution: if expected_failure is not None: exception, traceback = expected_failure if isinstance(exception, DeadlineExceeded) and ( - runtime_secs := self._timing_features.get("execute_test") + runtime_secs := math.fsum( + v + for k, v in self._timing_features.items() + if k.startswith("execute:") + ) ): report( "Unreliable test timings! On an initial run, this " @@ -1068,6 +1076,7 @@ class StateForActualGivenExecution: arguments={**self._jsonable_arguments, **data._observability_args}, timing=self._timing_features, coverage=tractable_coverage_report(trace) or None, + phase=phase, ) deliver_json_blob(tc) self._timing_features = {} diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py index cea40823be..0016f2fa1a 100644 --- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py +++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/data.py @@ -18,6 +18,7 @@ from typing import ( TYPE_CHECKING, Any, Callable, + DefaultDict, Dict, FrozenSet, Iterable, @@ -1468,6 +1469,7 @@ class ConjectureData: self.forced_indices: "Set[int]" = set() self.interesting_origin: Optional[InterestingOrigin] = None self.draw_times: "Dict[str, float]" = {} + self._stateful_run_times: "DefaultDict[str, float]" = defaultdict(float) self.max_depth = 0 self.has_discards = False self.provider = PrimitiveProvider(self) @@ -1752,7 +1754,7 @@ class ConjectureData: try: return strategy.do_draw(self) finally: - key = observe_as or f"unlabeled_{len(self.draw_times)}" + key = observe_as or f"generate:unlabeled_{len(self.draw_times)}" self.draw_times[key] = time.perf_counter() - start_time finally: self.stop_example() diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/observability.py b/contrib/python/hypothesis/py3/hypothesis/internal/observability.py index 98753985f1..aff19d805c 100644 --- a/contrib/python/hypothesis/py3/hypothesis/internal/observability.py +++ b/contrib/python/hypothesis/py3/hypothesis/internal/observability.py @@ -41,9 +41,12 @@ def make_testcase( arguments: Optional[dict] = None, timing: Dict[str, float], coverage: Optional[Dict[str, List[int]]] = None, + phase: Optional[str] = None, ) -> dict: if data.interesting_origin: status_reason = str(data.interesting_origin) + elif phase == "shrink" and data.status == Status.OVERRUN: + status_reason = "exceeded size of current best example" else: status_reason = str(data.events.pop("invalid because", "")) diff --git a/contrib/python/hypothesis/py3/hypothesis/stateful.py b/contrib/python/hypothesis/py3/hypothesis/stateful.py index 2ae5815161..bf7271fc4c 100644 --- a/contrib/python/hypothesis/py3/hypothesis/stateful.py +++ b/contrib/python/hypothesis/py3/hypothesis/stateful.py @@ -20,6 +20,7 @@ import inspect from copy import copy from functools import lru_cache from io import StringIO +from time import perf_counter from typing import ( Any, Callable, @@ -48,6 +49,7 @@ from hypothesis.core import TestFunc, given from hypothesis.errors import InvalidArgument, InvalidDefinition from hypothesis.internal.conjecture import utils as cu from hypothesis.internal.healthcheck import fail_health_check +from hypothesis.internal.observability import TESTCASE_CALLBACKS from hypothesis.internal.reflection import ( function_digest, get_pretty_function_description, @@ -121,10 +123,17 @@ def run_state_machine_as_test(state_machine_factory, *, settings=None, _min_step print_steps = ( current_build_context().is_final or current_verbosity() >= Verbosity.debug ) - try: + cd._stateful_repr_parts = [] + + def output(s): if print_steps: - report(f"state = {machine.__class__.__name__}()") - machine.check_invariants(settings) + report(s) + if TESTCASE_CALLBACKS: + cd._stateful_repr_parts.append(s) + + try: + output(f"state = {machine.__class__.__name__}()") + machine.check_invariants(settings, output, cd._stateful_run_times) max_steps = settings.stateful_step_count steps_run = 0 @@ -141,6 +150,8 @@ def run_state_machine_as_test(state_machine_factory, *, settings=None, _min_step must_stop = True elif steps_run <= _min_steps: must_stop = False + + start_draw = perf_counter() if cd.draw_boolean(p=2**-16, forced=must_stop): break steps_run += 1 @@ -156,12 +167,15 @@ def run_state_machine_as_test(state_machine_factory, *, settings=None, _min_step machine._initialize_rules_to_run.remove(rule) else: rule, data = cd.draw(machine._rules_strategy) + draw_label = f"generate:rule:{rule.function.__name__}" + cd.draw_times.setdefault(draw_label, 0.0) + cd.draw_times[draw_label] += perf_counter() - start_draw # Pretty-print the values this rule was called with *before* calling # _add_result_to_targets, to avoid printing arguments which are also # a return value using the variable name they are assigned to. # See https://github.com/HypothesisWorks/hypothesis/issues/2341 - if print_steps: + if print_steps or TESTCASE_CALLBACKS: data_to_print = { k: machine._pretty_print(v) for k, v in data.items() } @@ -173,7 +187,12 @@ def run_state_machine_as_test(state_machine_factory, *, settings=None, _min_step for k, v in list(data.items()): if isinstance(v, VarReference): data[k] = machine.names_to_values[v.name] + + label = f"execute:rule:{rule.function.__name__}" + start = perf_counter() result = rule.function(machine, **data) + cd._stateful_run_times[label] += perf_counter() - start + if rule.targets: if isinstance(result, MultipleResults): for single_result in result.values: @@ -190,16 +209,15 @@ def run_state_machine_as_test(state_machine_factory, *, settings=None, _min_step HealthCheck.return_value, ) finally: - if print_steps: + if print_steps or TESTCASE_CALLBACKS: # 'result' is only used if the step has target bundles. # If it does, and the result is a 'MultipleResult', # then 'print_step' prints a multi-variable assignment. - machine._print_step(rule, data_to_print, result) - machine.check_invariants(settings) + output(machine._repr_step(rule, data_to_print, result)) + machine.check_invariants(settings, output, cd._stateful_run_times) cd.stop_example() finally: - if print_steps: - report("state.teardown()") + output("state.teardown()") machine.teardown() # Use a machine digest to identify stateful tests in the example database @@ -338,7 +356,7 @@ class RuleBasedStateMachine(metaclass=StateMachineMeta): cls._invariants_per_class[cls] = target return cls._invariants_per_class[cls] - def _print_step(self, rule, data, result): + def _repr_step(self, rule, data, result): self.step_count = getattr(self, "step_count", 0) + 1 output_assignment = "" if rule.targets: @@ -350,13 +368,8 @@ class RuleBasedStateMachine(metaclass=StateMachineMeta): output_assignment = ", ".join(output_names) + " = " else: output_assignment = self._last_names(1)[0] + " = " - report( - "{}state.{}({})".format( - output_assignment, - rule.function.__name__, - ", ".join("%s=%s" % kv for kv in data.items()), - ) - ) + args = ", ".join("%s=%s" % kv for kv in data.items()) + return f"{output_assignment}state.{rule.function.__name__}({args})" def _add_result_to_targets(self, targets, result): name = self._new_name() @@ -367,18 +380,22 @@ class RuleBasedStateMachine(metaclass=StateMachineMeta): for target in targets: self.bundles.setdefault(target, []).append(VarReference(name)) - def check_invariants(self, settings): + def check_invariants(self, settings, output, runtimes): for invar in self.invariants(): if self._initialize_rules_to_run and not invar.check_during_init: continue if not all(precond(self) for precond in invar.preconditions): continue + name = invar.function.__name__ if ( current_build_context().is_final or settings.verbosity >= Verbosity.debug + or TESTCASE_CALLBACKS ): - report(f"state.{invar.function.__name__}()") + output(f"state.{name}()") + start = perf_counter() result = invar.function(self) + runtimes[f"execute:invariant:{name}"] += perf_counter() - start if result is not None: fail_health_check( settings, diff --git a/contrib/python/hypothesis/py3/hypothesis/version.py b/contrib/python/hypothesis/py3/hypothesis/version.py index 6aafd5421c..f54f8dc42f 100644 --- a/contrib/python/hypothesis/py3/hypothesis/version.py +++ b/contrib/python/hypothesis/py3/hypothesis/version.py @@ -8,5 +8,5 @@ # v. 2.0. If a copy of the MPL was not distributed with this file, You can # obtain one at https://mozilla.org/MPL/2.0/. -__version_info__ = (6, 98, 8) +__version_info__ = (6, 98, 9) __version__ = ".".join(map(str, __version_info__)) diff --git a/contrib/python/hypothesis/py3/ya.make b/contrib/python/hypothesis/py3/ya.make index 1678e159c9..89269430da 100644 --- a/contrib/python/hypothesis/py3/ya.make +++ b/contrib/python/hypothesis/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(6.98.8) +VERSION(6.98.9) LICENSE(MPL-2.0) diff --git a/contrib/python/psutil/py3/.dist-info/METADATA b/contrib/python/psutil/py3/.dist-info/METADATA index e0d5c4d09b..1815b243bc 100644 --- a/contrib/python/psutil/py3/.dist-info/METADATA +++ b/contrib/python/psutil/py3/.dist-info/METADATA @@ -1,11 +1,11 @@ Metadata-Version: 2.1 Name: psutil -Version: 5.8.0 +Version: 5.9.8 Summary: Cross-platform lib for process and system monitoring in Python. Home-page: https://github.com/giampaolo/psutil Author: Giampaolo Rodola Author-email: g.rodola@gmail.com -License: BSD +License: BSD-3-Clause Keywords: ps,top,kill,free,lsof,netstat,nice,tty,ionice,uptime,taskmgr,process,df,iotop,iostat,ifconfig,taskset,who,pidof,pmap,smem,pstree,monitoring,ulimit,prlimit,smem,performance,metrics,agent,observability Platform: Platform Independent Classifier: Development Status :: 5 - Production/Stable @@ -35,7 +35,6 @@ Classifier: Operating System :: POSIX :: SunOS/Solaris Classifier: Operating System :: POSIX Classifier: Programming Language :: C Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: Implementation :: CPython @@ -53,19 +52,19 @@ Classifier: Topic :: System :: Networking Classifier: Topic :: System :: Operating System Classifier: Topic :: System :: Systems Administration Classifier: Topic :: Utilities -Requires-Python: >=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* +Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.* Description-Content-Type: text/x-rst +License-File: LICENSE Provides-Extra: test Requires-Dist: ipaddress ; (python_version < "3.0") and extra == 'test' Requires-Dist: mock ; (python_version < "3.0") and extra == 'test' -Requires-Dist: unittest2 ; (python_version < "3.0") and extra == 'test' Requires-Dist: enum34 ; (python_version <= "3.4") and extra == 'test' Requires-Dist: pywin32 ; (sys_platform == "win32") and extra == 'test' Requires-Dist: wmi ; (sys_platform == "win32") and extra == 'test' -| |downloads| |stars| |forks| |contributors| |coverage| |quality| +| |downloads| |stars| |forks| |contributors| |coverage| | |version| |py-versions| |packages| |license| -| |github-actions| |appveyor| |doc| |twitter| |tidelift| +| |github-actions-wheels| |github-actions-bsd| |appveyor| |doc| |twitter| |tidelift| .. |downloads| image:: https://img.shields.io/pypi/dm/psutil.svg :target: https://pepy.tech/project/psutil @@ -83,24 +82,24 @@ Requires-Dist: wmi ; (sys_platform == "win32") and extra == 'test' :target: https://github.com/giampaolo/psutil/graphs/contributors :alt: Contributors -.. |quality| image:: https://img.shields.io/codacy/grade/ce63e7f7f69d44b5b59682196e6fbfca.svg - :target: https://www.codacy.com/app/g-rodola/psutil?utm_source=github.com&utm_medium=referral&utm_content=giampaolo/psutil&utm_campaign=Badge_Grade - :alt: Code quality +.. |github-actions-wheels| image:: https://img.shields.io/github/actions/workflow/status/giampaolo/psutil/.github/workflows/build.yml?label=Linux%2C%20macOS%2C%20Windows + :target: https://github.com/giampaolo/psutil/actions?query=workflow%3Abuild + :alt: Linux, macOS, Windows -.. |github-actions| image:: https://img.shields.io/github/workflow/status/giampaolo/psutil/CI?label=Linux%2C%20macOS%2C%20FreeBSD - :target: https://github.com/giampaolo/psutil/actions?query=workflow%3ACI - :alt: Linux, macOS, Windows tests +.. |github-actions-bsd| image:: https://img.shields.io/github/actions/workflow/status/giampaolo/psutil/.github/workflows/bsd.yml?label=FreeBSD,%20NetBSD,%20OpenBSD + :target: https://github.com/giampaolo/psutil/actions?query=workflow%3Absd-tests + :alt: FreeBSD, NetBSD, OpenBSD -.. |appveyor| image:: https://img.shields.io/appveyor/ci/giampaolo/psutil/master.svg?maxAge=3600&label=Windows +.. |appveyor| image:: https://img.shields.io/appveyor/build/giampaolo/psutil/master.svg?maxAge=3600&label=Windows%20(py2) :target: https://ci.appveyor.com/project/giampaolo/psutil - :alt: Windows tests (Appveyor) + :alt: Windows (Appveyor) .. |coverage| image:: https://coveralls.io/repos/github/giampaolo/psutil/badge.svg?branch=master :target: https://coveralls.io/github/giampaolo/psutil?branch=master :alt: Test coverage (coverall.io) .. |doc| image:: https://readthedocs.org/projects/psutil/badge/?version=latest - :target: http://psutil.readthedocs.io/en/latest/?badge=latest + :target: https://psutil.readthedocs.io/en/latest/ :alt: Documentation Status .. |version| image:: https://img.shields.io/pypi/v/psutil.svg?label=pypi @@ -108,7 +107,6 @@ Requires-Dist: wmi ; (sys_platform == "win32") and extra == 'test' :alt: Latest version .. |py-versions| image:: https://img.shields.io/pypi/pyversions/psutil.svg - :target: https://pypi.org/project/psutil :alt: Supported Python versions .. |packages| image:: https://repology.org/badge/tiny-repos/python:psutil.svg @@ -161,7 +159,7 @@ psutil currently supports the following platforms: - **Sun Solaris** - **AIX** -Supported Python versions are **2.6**, **2.7**, **3.4+** and +Supported Python versions are **2.7**, **3.6+** and `PyPy <http://pypy.org/>`__. Funding @@ -172,7 +170,7 @@ immensely from some funding. Keeping up with bug reports and maintenance has become hardly sustainable for me alone in terms of time. If you're a company that's making significant use of psutil you can consider -becoming a sponsor via `GitHub <https://github.com/sponsors/giampaolo>`__, +becoming a sponsor via `GitHub Sponsors <https://github.com/sponsors/giampaolo>`__, `Open Collective <https://opencollective.com/psutil>`__ or `PayPal <https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A9ZS7PKKRM3S8>`__ and have your logo displayed in here and psutil `doc <https://psutil.readthedocs.io>`__. @@ -199,7 +197,7 @@ CPU >>> import psutil >>> >>> psutil.cpu_times() - scputimes(user=3961.46, nice=169.729, system=2150.659, idle=16900.540, iowait=629.59, irq=0.0, softirq=19.42, steal=0.0, guest=0, nice=0.0) + scputimes(user=3961.46, nice=169.729, system=2150.659, idle=16900.540, iowait=629.59, irq=0.0, softirq=19.42, steal=0.0, guest=0, guest_nice=0.0) >>> >>> for x in range(3): ... psutil.cpu_percent(interval=1) @@ -254,7 +252,7 @@ Disks >>> psutil.disk_partitions() [sdiskpart(device='/dev/sda1', mountpoint='/', fstype='ext4', opts='rw,nosuid', maxfile=255, maxpath=4096), - sdiskpart(device='/dev/sda2', mountpoint='/home', fstype='ext, opts='rw', maxfile=255, maxpath=4096)] + sdiskpart(device='/dev/sda2', mountpoint='/home', fstype='ext', opts='rw', maxfile=255, maxpath=4096)] >>> >>> psutil.disk_usage('/') sdiskusage(total=21378641920, used=4809781248, free=15482871808, percent=22.5) @@ -286,8 +284,8 @@ Network snicaddr(family=<AddressFamily.AF_LINK: 17>, address='c4:85:08:45:06:41', netmask=None, broadcast='ff:ff:ff:ff:ff:ff', ptp=None)]} >>> >>> psutil.net_if_stats() - {'lo': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=65536), - 'wlan0': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=100, mtu=1500)} + {'lo': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=65536, flags='up,loopback,running'), + 'wlan0': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=100, mtu=1500, flags='up,broadcast,running,multicast')} >>> Sensors @@ -340,39 +338,38 @@ Process management >>> p = psutil.Process(7055) >>> p psutil.Process(pid=7055, name='python3', status='running', started='09:04:44') + >>> p.pid + 7055 >>> p.name() - 'python' + 'python3' >>> p.exe() - '/usr/bin/python' + '/usr/bin/python3' >>> p.cwd() '/home/giampaolo' >>> p.cmdline() - ['/usr/bin/python', 'main.py'] + ['/usr/bin/python3', 'main.py'] >>> - >>> p.pid - 7055 >>> p.ppid() 7054 - >>> p.children(recursive=True) - [psutil.Process(pid=29835, name='python3', status='sleeping', started='11:45:38'), - psutil.Process(pid=29836, name='python3', status='waking', started='11:43:39')] - >>> >>> p.parent() psutil.Process(pid=4699, name='bash', status='sleeping', started='09:06:44') >>> p.parents() [psutil.Process(pid=4699, name='bash', started='09:06:44'), psutil.Process(pid=4689, name='gnome-terminal-server', status='sleeping', started='0:06:44'), psutil.Process(pid=1, name='systemd', status='sleeping', started='05:56:55')] + >>> p.children(recursive=True) + [psutil.Process(pid=29835, name='python3', status='sleeping', started='11:45:38'), + psutil.Process(pid=29836, name='python3', status='waking', started='11:43:39')] >>> >>> p.status() 'running' - >>> p.username() - 'giampaolo' >>> p.create_time() 1267551141.5019531 >>> p.terminal() '/dev/pts/0' >>> + >>> p.username() + 'giampaolo' >>> p.uids() puids(real=1000, effective=1000, saved=1000) >>> p.gids() @@ -412,14 +409,14 @@ Process management [pconn(fd=115, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=48776), raddr=addr(ip='93.186.135.91', port=80), status='ESTABLISHED'), pconn(fd=117, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=43761), raddr=addr(ip='72.14.234.100', port=80), status='CLOSING')] >>> - >>> p.num_threads() - 4 - >>> p.num_fds() - 8 >>> p.threads() [pthread(id=5234, user_time=22.5, system_time=9.2891), pthread(id=5237, user_time=0.0707, system_time=1.1)] >>> + >>> p.num_threads() + 4 + >>> p.num_fds() + 8 >>> p.num_ctx_switches() pctxsw(voluntary=78, involuntary=19) >>> @@ -486,23 +483,6 @@ Further process APIs >>> gone, alive = psutil.wait_procs(procs_list, timeout=3, callback=on_terminate) >>> -Popen wrapper: - -.. code-block:: python - - >>> import psutil - >>> from subprocess import PIPE - >>> p = psutil.Popen(["/usr/bin/python", "-c", "print('hello')"], stdout=PIPE) - >>> p.name() - 'python' - >>> p.username() - 'giampaolo' - >>> p.communicate() - ('hello\n', None) - >>> p.wait(timeout=2) - 0 - >>> - Windows services ---------------- @@ -533,6 +513,7 @@ Here's some I find particularly interesting: - https://github.com/google/grr - https://github.com/facebook/osquery/ - https://github.com/nicolargo/glances +- https://github.com/aristocratos/bpytop - https://github.com/Jahaja/psdash - https://github.com/ajenti/ajenti - https://github.com/home-assistant/home-assistant/ @@ -545,16 +526,5 @@ Portings - Rust: https://github.com/rust-psutil/rust-psutil - Nim: https://github.com/johnscillieri/psutil-nim -Security -======== - -To report a security vulnerability, please use the `Tidelift security -contact`_. Tidelift will coordinate the fix and disclosure. - -.. _`Giampaolo Rodola`: https://gmpy.dev/about -.. _`donation`: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A9ZS7PKKRM3S8 -.. _Tidelift security contact: https://tidelift.com/security -.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-psutil?utm_source=pypi-psutil&utm_medium=referral&utm_campaign=readme - diff --git a/contrib/python/psutil/py3/LICENSE b/contrib/python/psutil/py3/LICENSE index 0bf4a7fc04..cff5eb74e1 100644 --- a/contrib/python/psutil/py3/LICENSE +++ b/contrib/python/psutil/py3/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2009, Jay Loden, Dave Daeschler, Giampaolo Rodola' +Copyright (c) 2009, Jay Loden, Dave Daeschler, Giampaolo Rodola All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/contrib/python/psutil/py3/README.rst b/contrib/python/psutil/py3/README.rst index a9334da283..12bf64254e 100644 --- a/contrib/python/psutil/py3/README.rst +++ b/contrib/python/psutil/py3/README.rst @@ -1,6 +1,6 @@ -| |downloads| |stars| |forks| |contributors| |coverage| |quality| +| |downloads| |stars| |forks| |contributors| |coverage| | |version| |py-versions| |packages| |license| -| |github-actions| |appveyor| |doc| |twitter| |tidelift| +| |github-actions-wheels| |github-actions-bsd| |appveyor| |doc| |twitter| |tidelift| .. |downloads| image:: https://img.shields.io/pypi/dm/psutil.svg :target: https://pepy.tech/project/psutil @@ -18,24 +18,24 @@ :target: https://github.com/giampaolo/psutil/graphs/contributors :alt: Contributors -.. |quality| image:: https://img.shields.io/codacy/grade/ce63e7f7f69d44b5b59682196e6fbfca.svg - :target: https://www.codacy.com/app/g-rodola/psutil?utm_source=github.com&utm_medium=referral&utm_content=giampaolo/psutil&utm_campaign=Badge_Grade - :alt: Code quality +.. |github-actions-wheels| image:: https://img.shields.io/github/actions/workflow/status/giampaolo/psutil/.github/workflows/build.yml?label=Linux%2C%20macOS%2C%20Windows + :target: https://github.com/giampaolo/psutil/actions?query=workflow%3Abuild + :alt: Linux, macOS, Windows -.. |github-actions| image:: https://img.shields.io/github/workflow/status/giampaolo/psutil/CI?label=Linux%2C%20macOS%2C%20FreeBSD - :target: https://github.com/giampaolo/psutil/actions?query=workflow%3ACI - :alt: Linux, macOS, Windows tests +.. |github-actions-bsd| image:: https://img.shields.io/github/actions/workflow/status/giampaolo/psutil/.github/workflows/bsd.yml?label=FreeBSD,%20NetBSD,%20OpenBSD + :target: https://github.com/giampaolo/psutil/actions?query=workflow%3Absd-tests + :alt: FreeBSD, NetBSD, OpenBSD -.. |appveyor| image:: https://img.shields.io/appveyor/ci/giampaolo/psutil/master.svg?maxAge=3600&label=Windows +.. |appveyor| image:: https://img.shields.io/appveyor/build/giampaolo/psutil/master.svg?maxAge=3600&label=Windows%20(py2) :target: https://ci.appveyor.com/project/giampaolo/psutil - :alt: Windows tests (Appveyor) + :alt: Windows (Appveyor) .. |coverage| image:: https://coveralls.io/repos/github/giampaolo/psutil/badge.svg?branch=master :target: https://coveralls.io/github/giampaolo/psutil?branch=master :alt: Test coverage (coverall.io) .. |doc| image:: https://readthedocs.org/projects/psutil/badge/?version=latest - :target: http://psutil.readthedocs.io/en/latest/?badge=latest + :target: https://psutil.readthedocs.io/en/latest/ :alt: Documentation Status .. |version| image:: https://img.shields.io/pypi/v/psutil.svg?label=pypi @@ -43,7 +43,6 @@ :alt: Latest version .. |py-versions| image:: https://img.shields.io/pypi/pyversions/psutil.svg - :target: https://pypi.org/project/psutil :alt: Supported Python versions .. |packages| image:: https://repology.org/badge/tiny-repos/python:psutil.svg @@ -99,7 +98,7 @@ psutil currently supports the following platforms: - **Sun Solaris** - **AIX** -Supported Python versions are **2.6**, **2.7**, **3.4+** and +Supported Python versions are **2.7**, **3.6+** and `PyPy <http://pypy.org/>`__. Funding @@ -110,7 +109,7 @@ immensely from some funding. Keeping up with bug reports and maintenance has become hardly sustainable for me alone in terms of time. If you're a company that's making significant use of psutil you can consider -becoming a sponsor via `GitHub <https://github.com/sponsors/giampaolo>`__, +becoming a sponsor via `GitHub Sponsors <https://github.com/sponsors/giampaolo>`__, `Open Collective <https://opencollective.com/psutil>`__ or `PayPal <https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A9ZS7PKKRM3S8>`__ and have your logo displayed in here and psutil `doc <https://psutil.readthedocs.io>`__. @@ -122,7 +121,11 @@ Sponsors <div> <a href="https://tidelift.com/subscription/pkg/pypi-psutil?utm_source=pypi-psutil&utm_medium=referral&utm_campaign=readme"> - <img src="https://github.com/giampaolo/psutil/raw/master/docs/_static/tidelift-logo.png" /> + <img width="185" src="https://github.com/giampaolo/psutil/raw/master/docs/_static/tidelift-logo.svg" /> + </a> +   + <a href="https://sansec.io/"> + <img src="https://sansec.io/assets/images/logo.svg" /> </a> </div> <sup><a href="https://github.com/sponsors/giampaolo">add your logo</a></sup> @@ -130,16 +133,32 @@ Sponsors Supporters ========== -None yet. - .. raw:: html + <div> + <a href="https://github.com/dbwiddis"><img height="40" width="40" title="Daniel Widdis" src="https://avatars1.githubusercontent.com/u/9291703?s=88&v=4" /></a> + <a href="https://github.com/aristocratos"><img height="40" width="40" title="aristocratos" src="https://avatars3.githubusercontent.com/u/59659483?s=96&v=4" /></a> + <a href="https://github.com/cybersecgeek"><img height="40" width="40" title="cybersecgeek" src="https://avatars.githubusercontent.com/u/12847926?v=4" /></a> + <a href="https://github.com/scoutapm-sponsorships"><img height="40" width="40" title="scoutapm-sponsorships" src="https://avatars.githubusercontent.com/u/71095532?v=4" /></a> + <a href="https://opencollective.com/chenyoo-hao"><img height="40" width="40" title="Chenyoo Hao" src="https://images.opencollective.com/chenyoo-hao/avatar/40.png" /></a> + <a href="https://opencollective.com/alexey-vazhnov"><img height="40" width="40" title="Alexey Vazhnov" src="https://images.opencollective.com/alexey-vazhnov/daed334/avatar/40.png" /></a> + <a href="https://github.com/indeedeng"><img height="40" width="40" title="indeedeng" src="https://avatars.githubusercontent.com/u/2905043?s=200&v=4" /></a> + <a href="https://github.com/PySimpleGUI"><img height="40" width="40" title="PySimpleGUI" src="https://avatars.githubusercontent.com/u/46163555?v=4" /></a> + <a href="https://github.com/u93"><img height="40" width="40" title="Eugenio E Breijo" src="https://avatars.githubusercontent.com/u/16807302?v=4" /></a> + <a href="https://github.com/guilt"><img height="40" width="40" title="Karthik Kumar Viswanathan" src="https://avatars.githubusercontent.com/u/195178?v=4" /></a> + <a href="https://github.com/eallrich"><img height="40" width="40" title="Evan Allrich" src="https://avatars.githubusercontent.com/u/17393?v=4" /></a> + <a href="https://github.com/robusta-dev"><img height="40" width="40" title="Robusta" src="https://avatars.githubusercontent.com/u/82757710?s=200&v=4" /></a> + <a href="https://github.com/JeremyGrosser"><img height="40" width="40" title="JeremyGrosser" src="https://avatars.githubusercontent.com/u/2151?v=4" /></a> + <a href="https://github.com/getsentry"><img height="40" width="40" title="getsentry" src="https://avatars.githubusercontent.com/u/1396951?s=200&v=4" /></a> + + </div> <sup><a href="https://github.com/sponsors/giampaolo">add your avatar</a></sup> + Contributing ============ -See `CONTRIBUTING.md <https://github.com/giampaolo/psutil/blob/master/CONTRIBUTING.md>`__ guidelines. +See `contributing guidelines <https://github.com/giampaolo/psutil/blob/master/CONTRIBUTING.md>`__. Example usages ============== @@ -154,7 +173,7 @@ CPU >>> import psutil >>> >>> psutil.cpu_times() - scputimes(user=3961.46, nice=169.729, system=2150.659, idle=16900.540, iowait=629.59, irq=0.0, softirq=19.42, steal=0.0, guest=0, nice=0.0) + scputimes(user=3961.46, nice=169.729, system=2150.659, idle=16900.540, iowait=629.59, irq=0.0, softirq=19.42, steal=0.0, guest=0, guest_nice=0.0) >>> >>> for x in range(3): ... psutil.cpu_percent(interval=1) @@ -209,7 +228,7 @@ Disks >>> psutil.disk_partitions() [sdiskpart(device='/dev/sda1', mountpoint='/', fstype='ext4', opts='rw,nosuid', maxfile=255, maxpath=4096), - sdiskpart(device='/dev/sda2', mountpoint='/home', fstype='ext, opts='rw', maxfile=255, maxpath=4096)] + sdiskpart(device='/dev/sda2', mountpoint='/home', fstype='ext', opts='rw', maxfile=255, maxpath=4096)] >>> >>> psutil.disk_usage('/') sdiskusage(total=21378641920, used=4809781248, free=15482871808, percent=22.5) @@ -241,8 +260,8 @@ Network snicaddr(family=<AddressFamily.AF_LINK: 17>, address='c4:85:08:45:06:41', netmask=None, broadcast='ff:ff:ff:ff:ff:ff', ptp=None)]} >>> >>> psutil.net_if_stats() - {'lo': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=65536), - 'wlan0': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=100, mtu=1500)} + {'lo': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_UNKNOWN: 0>, speed=0, mtu=65536, flags='up,loopback,running'), + 'wlan0': snicstats(isup=True, duplex=<NicDuplex.NIC_DUPLEX_FULL: 2>, speed=100, mtu=1500, flags='up,broadcast,running,multicast')} >>> Sensors @@ -295,39 +314,38 @@ Process management >>> p = psutil.Process(7055) >>> p psutil.Process(pid=7055, name='python3', status='running', started='09:04:44') + >>> p.pid + 7055 >>> p.name() - 'python' + 'python3' >>> p.exe() - '/usr/bin/python' + '/usr/bin/python3' >>> p.cwd() '/home/giampaolo' >>> p.cmdline() - ['/usr/bin/python', 'main.py'] + ['/usr/bin/python3', 'main.py'] >>> - >>> p.pid - 7055 >>> p.ppid() 7054 - >>> p.children(recursive=True) - [psutil.Process(pid=29835, name='python3', status='sleeping', started='11:45:38'), - psutil.Process(pid=29836, name='python3', status='waking', started='11:43:39')] - >>> >>> p.parent() psutil.Process(pid=4699, name='bash', status='sleeping', started='09:06:44') >>> p.parents() [psutil.Process(pid=4699, name='bash', started='09:06:44'), psutil.Process(pid=4689, name='gnome-terminal-server', status='sleeping', started='0:06:44'), psutil.Process(pid=1, name='systemd', status='sleeping', started='05:56:55')] + >>> p.children(recursive=True) + [psutil.Process(pid=29835, name='python3', status='sleeping', started='11:45:38'), + psutil.Process(pid=29836, name='python3', status='waking', started='11:43:39')] >>> >>> p.status() 'running' - >>> p.username() - 'giampaolo' >>> p.create_time() 1267551141.5019531 >>> p.terminal() '/dev/pts/0' >>> + >>> p.username() + 'giampaolo' >>> p.uids() puids(real=1000, effective=1000, saved=1000) >>> p.gids() @@ -367,14 +385,14 @@ Process management [pconn(fd=115, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=48776), raddr=addr(ip='93.186.135.91', port=80), status='ESTABLISHED'), pconn(fd=117, family=<AddressFamily.AF_INET: 2>, type=<SocketType.SOCK_STREAM: 1>, laddr=addr(ip='10.0.0.1', port=43761), raddr=addr(ip='72.14.234.100', port=80), status='CLOSING')] >>> - >>> p.num_threads() - 4 - >>> p.num_fds() - 8 >>> p.threads() [pthread(id=5234, user_time=22.5, system_time=9.2891), pthread(id=5237, user_time=0.0707, system_time=1.1)] >>> + >>> p.num_threads() + 4 + >>> p.num_fds() + 8 >>> p.num_ctx_switches() pctxsw(voluntary=78, involuntary=19) >>> @@ -441,23 +459,6 @@ Further process APIs >>> gone, alive = psutil.wait_procs(procs_list, timeout=3, callback=on_terminate) >>> -Popen wrapper: - -.. code-block:: python - - >>> import psutil - >>> from subprocess import PIPE - >>> p = psutil.Popen(["/usr/bin/python", "-c", "print('hello')"], stdout=PIPE) - >>> p.name() - 'python' - >>> p.username() - 'giampaolo' - >>> p.communicate() - ('hello\n', None) - >>> p.wait(timeout=2) - 0 - >>> - Windows services ---------------- @@ -488,6 +489,7 @@ Here's some I find particularly interesting: - https://github.com/google/grr - https://github.com/facebook/osquery/ - https://github.com/nicolargo/glances +- https://github.com/aristocratos/bpytop - https://github.com/Jahaja/psdash - https://github.com/ajenti/ajenti - https://github.com/home-assistant/home-assistant/ @@ -499,14 +501,3 @@ Portings - C: https://github.com/hamon-in/cpslib - Rust: https://github.com/rust-psutil/rust-psutil - Nim: https://github.com/johnscillieri/psutil-nim - -Security -======== - -To report a security vulnerability, please use the `Tidelift security -contact`_. Tidelift will coordinate the fix and disclosure. - -.. _`Giampaolo Rodola`: https://gmpy.dev/about -.. _`donation`: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A9ZS7PKKRM3S8 -.. _Tidelift security contact: https://tidelift.com/security -.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-psutil?utm_source=pypi-psutil&utm_medium=referral&utm_campaign=readme diff --git a/contrib/python/psutil/py3/psutil/__init__.py b/contrib/python/psutil/py3/psutil/__init__.py index acd42ac264..8138db41e1 100644 --- a/contrib/python/psutil/py3/psutil/__init__.py +++ b/contrib/python/psutil/py3/psutil/__init__.py @@ -17,10 +17,11 @@ sensors) in Python. Supported platforms: - Sun Solaris - AIX -Works with Python versions from 2.6 to 3.4+. +Works with Python versions 2.7 and 3.6+. """ from __future__ import division + import collections import contextlib import datetime @@ -31,24 +32,16 @@ import subprocess import sys import threading import time + + try: import pwd except ImportError: pwd = None from . import _common -from ._common import AccessDenied -from ._common import Error -from ._common import memoize_when_activated -from ._common import NoSuchProcess -from ._common import TimeoutExpired -from ._common import wrap_numbers as _wrap_numbers -from ._common import ZombieProcess -from ._compat import long -from ._compat import PermissionError -from ._compat import ProcessLookupError -from ._compat import PY3 as _PY3 - +from ._common import AIX +from ._common import BSD from ._common import CONN_CLOSE from ._common import CONN_CLOSE_WAIT from ._common import CONN_CLOSING @@ -61,9 +54,16 @@ from ._common import CONN_NONE from ._common import CONN_SYN_RECV from ._common import CONN_SYN_SENT from ._common import CONN_TIME_WAIT +from ._common import FREEBSD # NOQA +from ._common import LINUX +from ._common import MACOS +from ._common import NETBSD # NOQA from ._common import NIC_DUPLEX_FULL from ._common import NIC_DUPLEX_HALF from ._common import NIC_DUPLEX_UNKNOWN +from ._common import OPENBSD # NOQA +from ._common import OSX # deprecated alias +from ._common import POSIX # NOQA from ._common import POWER_TIME_UNKNOWN from ._common import POWER_TIME_UNLIMITED from ._common import STATUS_DEAD @@ -78,18 +78,21 @@ from ._common import STATUS_TRACING_STOP from ._common import STATUS_WAITING from ._common import STATUS_WAKING from ._common import STATUS_ZOMBIE - -from ._common import AIX -from ._common import BSD -from ._common import FREEBSD # NOQA -from ._common import LINUX -from ._common import MACOS -from ._common import NETBSD # NOQA -from ._common import OPENBSD # NOQA -from ._common import OSX # deprecated alias -from ._common import POSIX # NOQA from ._common import SUNOS from ._common import WINDOWS +from ._common import AccessDenied +from ._common import Error +from ._common import NoSuchProcess +from ._common import TimeoutExpired +from ._common import ZombieProcess +from ._common import memoize_when_activated +from ._common import wrap_numbers as _wrap_numbers +from ._compat import PY3 as _PY3 +from ._compat import PermissionError +from ._compat import ProcessLookupError +from ._compat import SubprocessTimeoutExpired as _SubprocessTimeoutExpired +from ._compat import long + if LINUX: # This is public API and it will be retrieved from _pslinux.py @@ -97,7 +100,6 @@ if LINUX: PROCFS_PATH = "/proc" from . import _pslinux as _psplatform - from ._pslinux import IOPRIO_CLASS_BE # NOQA from ._pslinux import IOPRIO_CLASS_IDLE # NOQA from ._pslinux import IOPRIO_CLASS_NONE # NOQA @@ -112,10 +114,10 @@ elif WINDOWS: from ._psutil_windows import NORMAL_PRIORITY_CLASS # NOQA from ._psutil_windows import REALTIME_PRIORITY_CLASS # NOQA from ._pswindows import CONN_DELETE_TCB # NOQA - from ._pswindows import IOPRIO_VERYLOW # NOQA + from ._pswindows import IOPRIO_HIGH # NOQA from ._pswindows import IOPRIO_LOW # NOQA from ._pswindows import IOPRIO_NORMAL # NOQA - from ._pswindows import IOPRIO_HIGH # NOQA + from ._pswindows import IOPRIO_VERYLOW # NOQA elif MACOS: from . import _psosx as _psplatform @@ -143,6 +145,7 @@ else: # pragma: no cover raise NotImplementedError('platform %s is not supported' % sys.platform) +# fmt: off __all__ = [ # exceptions "Error", "NoSuchProcess", "ZombieProcess", "AccessDenied", @@ -189,6 +192,7 @@ __all__ = [ # "sensors_temperatures", "sensors_battery", "sensors_fans" # sensors "users", "boot_time", # others ] +# fmt: on __all__.extend(_psplatform.__extra__all__) @@ -209,7 +213,7 @@ if hasattr(_psplatform.Process, "rlimit"): AF_LINK = _psplatform.AF_LINK __author__ = "Giampaolo Rodola'" -__version__ = "5.8.0" +__version__ = "5.9.8" version_info = tuple([int(num) for num in __version__.split('.')]) _timer = getattr(time, 'monotonic', time.time) @@ -223,18 +227,25 @@ _SENTINEL = object() # was compiled for a different version of psutil. # We want to prevent that by failing sooner rather than later. # See: https://github.com/giampaolo/psutil/issues/564 -if (int(__version__.replace('.', '')) != - getattr(_psplatform.cext, 'version', None)): - msg = "version conflict: %r C extension module was built for another " \ - "version of psutil" % getattr(_psplatform.cext, "__file__") +if int(__version__.replace('.', '')) != getattr( + _psplatform.cext, 'version', None +): + msg = "version conflict: %r C extension " % _psplatform.cext.__file__ + msg += "module was built for another version of psutil" if hasattr(_psplatform.cext, 'version'): msg += " (%s instead of %s)" % ( - '.'.join([x for x in str(_psplatform.cext.version)]), __version__) + '.'.join([x for x in str(_psplatform.cext.version)]), + __version__, + ) else: msg += " (different than %s)" % __version__ msg += "; you may try to 'pip uninstall psutil', manually remove %s" % ( - getattr(_psplatform.cext, "__file__", - "the existing psutil install directory")) + getattr( + _psplatform.cext, + "__file__", + "the existing psutil install directory", + ) + ) msg += " or clean the virtual env somehow, then reinstall" raise ImportError(msg) @@ -248,6 +259,7 @@ if hasattr(_psplatform, 'ppid_map'): # Faster version (Windows and Linux). _ppid_map = _psplatform.ppid_map else: # pragma: no cover + def _ppid_map(): """Return a {pid: ppid, ...} dict for all running processes in one shot. Used to speed up Process.children(). @@ -261,26 +273,11 @@ else: # pragma: no cover return ret -def _assert_pid_not_reused(fun): - """Decorator which raises NoSuchProcess in case a process is no - longer running or its PID has been reused. - """ - @functools.wraps(fun) - def wrapper(self, *args, **kwargs): - if not self.is_running(): - raise NoSuchProcess(self.pid, self._name) - return fun(self, *args, **kwargs) - return wrapper - - def _pprint_secs(secs): """Format seconds in a human readable form.""" now = time.time() secs_ago = int(now - secs) - if secs_ago < 60 * 60 * 24: - fmt = "%H:%M:%S" - else: - fmt = "%Y-%m-%d %H:%M:%S" + fmt = "%H:%M:%S" if secs_ago < 60 * 60 * 24 else "%Y-%m-%d %H:%M:%S" return datetime.datetime.fromtimestamp(secs).strftime(fmt) @@ -289,7 +286,7 @@ def _pprint_secs(secs): # ===================================================================== -class Process(object): +class Process(object): # noqa: UP004 """Represents an OS process with the given PID. If PID is omitted current process PID (os.getpid()) is used. Raise NoSuchProcess if PID does not exist. @@ -319,7 +316,7 @@ class Process(object): - use is_running() before querying the process - if you're continuously iterating over a set of Process instances use process_iter() which pre-emptively checks - process identity for every yielded instance + process identity for every yielded instance """ def __init__(self, pid=None): @@ -330,15 +327,23 @@ class Process(object): pid = os.getpid() else: if not _PY3 and not isinstance(pid, (int, long)): - raise TypeError('pid must be an integer (got %r)' % pid) + msg = "pid must be an integer (got %r)" % pid + raise TypeError(msg) if pid < 0: - raise ValueError('pid must be a positive integer (got %s)' - % pid) + msg = "pid must be a positive integer (got %s)" % pid + raise ValueError(msg) + try: + _psplatform.cext.check_pid_range(pid) + except OverflowError: + msg = "process PID out of range (got %s)" % pid + raise NoSuchProcess(pid, msg=msg) + self._pid = pid self._name = None self._exe = None self._create_time = None self._gone = False + self._pid_reused = False self._hash = None self._lock = threading.RLock() # used for caching on Windows only (on POSIX ppid may change) @@ -363,21 +368,18 @@ class Process(object): pass except NoSuchProcess: if not _ignore_nsp: - msg = 'no process found with pid %s' % pid - raise NoSuchProcess(pid, None, msg) + msg = "process PID not found" + raise NoSuchProcess(pid, msg=msg) else: self._gone = True - # This pair is supposed to indentify a Process instance + # This pair is supposed to identify a Process instance # univocally over time (the PID alone is not enough as # it might refer to a process whose PID has been reused). # This will be used later in __eq__() and is_running(). self._ident = (self.pid, self._create_time) def __str__(self): - try: - info = collections.OrderedDict() - except AttributeError: # pragma: no cover - info = {} # Python 2.6 + info = collections.OrderedDict() info["pid"] = self.pid if self._name: info['name'] = self._name @@ -393,12 +395,13 @@ class Process(object): pass if self._exitcode not in (_SENTINEL, None): info["exitcode"] = self._exitcode - if self._create_time: + if self._create_time is not None: info['started'] = _pprint_secs(self._create_time) return "%s.%s(%s)" % ( self.__class__.__module__, self.__class__.__name__, - ", ".join(["%s=%r" % (k, v) for k, v in info.items()])) + ", ".join(["%s=%r" % (k, v) for k, v in info.items()]), + ) __repr__ = __str__ @@ -407,6 +410,20 @@ class Process(object): # on PID and creation time. if not isinstance(other, Process): return NotImplemented + if OPENBSD or NETBSD: # pragma: no cover + # Zombie processes on Open/NetBSD have a creation time of + # 0.0. This covers the case when a process started normally + # (so it has a ctime), then it turned into a zombie. It's + # important to do this because is_running() depends on + # __eq__. + pid1, ctime1 = self._ident + pid2, ctime2 = other._ident + if pid1 == pid2: + if ctime1 and not ctime2: + try: + return self.status() == STATUS_ZOMBIE + except Error: + pass return self._ident == other._ident def __ne__(self, other): @@ -417,6 +434,18 @@ class Process(object): self._hash = hash(self._ident) return self._hash + def _raise_if_pid_reused(self): + """Raises NoSuchProcess in case process PID has been reused.""" + if not self.is_running() and self._pid_reused: + # We may directly raise NSP in here already if PID is just + # not running, but I prefer NSP to be raised naturally by + # the actual Process API call. This way unit tests will tell + # us if the API is broken (aka don't raise NSP when it + # should). We also remain consistent with all other "get" + # APIs which don't use _raise_if_pid_reused(). + msg = "process no longer exists and its PID has been reused" + raise NoSuchProcess(self.pid, self._name, msg=msg) + @property def pid(self): """The process PID.""" @@ -505,15 +534,18 @@ class Process(object): valid_names = _as_dict_attrnames if attrs is not None: if not isinstance(attrs, (list, tuple, set, frozenset)): - raise TypeError("invalid attrs type %s" % type(attrs)) + msg = "invalid attrs type %s" % type(attrs) + raise TypeError(msg) attrs = set(attrs) invalid_names = attrs - valid_names if invalid_names: - raise ValueError("invalid attr name%s %s" % ( + msg = "invalid attr name%s %s" % ( "s" if len(invalid_names) > 1 else "", - ", ".join(map(repr, invalid_names)))) + ", ".join(map(repr, invalid_names)), + ) + raise ValueError(msg) - retdict = dict() + retdict = {} ls = attrs or valid_names with self.oneshot(): for name in ls: @@ -570,7 +602,7 @@ class Process(object): It also checks if PID has been reused by another process in which case return False. """ - if self._gone: + if self._gone or self._pid_reused: return False try: # Checking if PID is alive is not enough as the PID might @@ -578,7 +610,8 @@ class Process(object): # verify process identity. # Process identity / uniqueness over time is guaranteed by # (PID + creation time) and that is verified in __eq__. - return self == Process(self.pid) + self._pid_reused = self != Process(self.pid) + return not self._pid_reused except ZombieProcess: # We should never get here as it's already handled in # Process.__init__; here just for extra safety. @@ -601,6 +634,7 @@ class Process(object): # XXX should we check creation time here rather than in # Process.parent()? + self._raise_if_pid_reused() if POSIX: return self._proc.ppid() else: # pragma: no cover @@ -622,7 +656,12 @@ class Process(object): # Examples are "gnome-keyring-d" vs. "gnome-keyring-daemon". try: cmdline = self.cmdline() - except AccessDenied: + except (AccessDenied, ZombieProcess): + # Just pass and return the truncated name: it's better + # than nothing. Note: there are actual cases where a + # zombie process can return a name() but not a + # cmdline(), see: + # https://github.com/giampaolo/psutil/issues/2239 pass else: if cmdline: @@ -638,6 +677,7 @@ class Process(object): May also be an empty string. The return value is cached after first call. """ + def guess_it(fallback): # try to guess exe from cmdline[0] in absence of a native # exe representation @@ -647,9 +687,11 @@ class Process(object): # Attempt to guess only in case of an absolute path. # It is not safe otherwise as the process might have # changed cwd. - if (os.path.isabs(exe) and - os.path.isfile(exe) and - os.access(exe, os.X_OK)): + if ( + os.path.isabs(exe) + and os.path.isfile(exe) + and os.access(exe, os.X_OK) + ): return exe if isinstance(fallback, AccessDenied): raise fallback @@ -690,8 +732,8 @@ class Process(object): if POSIX: if pwd is None: # might happen if python was installed from sources - raise ImportError( - "requires pwd module shipped with standard python") + msg = "requires pwd module shipped with standard python" + raise ImportError(msg) real_uid = self.uids().real try: return pwd.getpwuid(real_uid).pw_name @@ -719,8 +761,7 @@ class Process(object): if value is None: return self._proc.nice_get() else: - if not self.is_running(): - raise NoSuchProcess(self.pid, self._name) + self._raise_if_pid_reused() self._proc.nice_set(value) if POSIX: @@ -779,9 +820,11 @@ class Process(object): """ if ioclass is None: if value is not None: - raise ValueError("'ioclass' argument must be specified") + msg = "'ioclass' argument must be specified" + raise ValueError(msg) return self._proc.ionice_get() else: + self._raise_if_pid_reused() return self._proc.ionice_set(ioclass, value) # Linux / FreeBSD only @@ -797,6 +840,8 @@ class Process(object): See "man prlimit" for further info. Available on Linux and FreeBSD only. """ + if limits is not None: + self._raise_if_pid_reused() return self._proc.rlimit(resource, limits) # Windows, Linux and FreeBSD only @@ -813,6 +858,7 @@ class Process(object): if cpus is None: return sorted(set(self._proc.cpu_affinity_get())) else: + self._raise_if_pid_reused() if not cpus: if hasattr(self._proc, "_get_eligible_cpus"): cpus = self._proc._get_eligible_cpus() @@ -838,7 +884,8 @@ class Process(object): def environ(self): """The environment variables of the process as a dict. Note: this - might not reflect changes made after the process started. """ + might not reflect changes made after the process started. + """ return self._proc.environ() if WINDOWS: @@ -869,7 +916,6 @@ class Process(object): """ return self._proc.threads() - @_assert_pid_not_reused def children(self, recursive=False): """Return the children of this process as a list of Process instances, pre-emptively checking whether PID has been reused. @@ -896,6 +942,7 @@ class Process(object): process Y won't be listed as the reference to process A is lost. """ + self._raise_if_pid_reused() ppid_map = _ppid_map() ret = [] if not recursive: @@ -976,7 +1023,8 @@ class Process(object): """ blocking = interval is not None and interval > 0.0 if interval is not None and interval < 0: - raise ValueError("interval is not positive (got %r)" % interval) + msg = "interval is not positive (got %r)" % interval + raise ValueError(msg) num_cpus = cpu_count() or 1 def timer(): @@ -1008,7 +1056,7 @@ class Process(object): # This is the utilization split evenly between all CPUs. # E.g. a busy loop process on a 2-CPU-cores system at this # point is reported as 50% instead of 100%. - overall_cpus_percent = ((delta_proc / delta_time) * 100) + overall_cpus_percent = (delta_proc / delta_time) * 100 except ZeroDivisionError: # interval was too low return 0.0 @@ -1047,7 +1095,7 @@ class Process(object): """Return a namedtuple with variable fields depending on the platform, representing memory information about the process. - The "portable" fields available on all plaforms are `rss` and `vms`. + The "portable" fields available on all platforms are `rss` and `vms`. All numbers are expressed in bytes. """ @@ -1085,10 +1133,16 @@ class Process(object): """ valid_types = list(_psplatform.pfullmem._fields) if memtype not in valid_types: - raise ValueError("invalid memtype %r; valid types are %r" % ( - memtype, tuple(valid_types))) - fun = self.memory_info if memtype in _psplatform.pmem._fields else \ - self.memory_full_info + msg = "invalid memtype %r; valid types are %r" % ( + memtype, + tuple(valid_types), + ) + raise ValueError(msg) + fun = ( + self.memory_info + if memtype in _psplatform.pmem._fields + else self.memory_full_info + ) metrics = fun() value = getattr(metrics, memtype) @@ -1096,13 +1150,15 @@ class Process(object): total_phymem = _TOTAL_PHYMEM or virtual_memory().total if not total_phymem > 0: # we should never get here - raise ValueError( - "can't calculate process memory percent because " - "total physical system memory is not positive (%r)" - % total_phymem) + msg = ( + "can't calculate process memory percent because total physical" + " system memory is not positive (%r)" % (total_phymem) + ) + raise ValueError(msg) return (value / float(total_phymem)) * 100 if hasattr(_psplatform.Process, "memory_maps"): + def memory_maps(self, grouped=True): """Return process' mapped memory regions as a list of namedtuples whose fields are variable depending on the platform. @@ -1164,14 +1220,18 @@ class Process(object): # --- signals if POSIX: + def _send_signal(self, sig): assert not self.pid < 0, self.pid + self._raise_if_pid_reused() if self.pid == 0: # see "man 2 kill" - raise ValueError( + msg = ( "preventing sending signal to process with PID 0 as it " "would affect every process in the process group of the " - "calling process (os.getpid()) instead of PID 0") + "calling process (os.getpid()) instead of PID 0" + ) + raise ValueError(msg) try: os.kill(self.pid, sig) except ProcessLookupError: @@ -1185,7 +1245,6 @@ class Process(object): except PermissionError: raise AccessDenied(self.pid, self._name) - @_assert_pid_not_reused def send_signal(self, sig): """Send a signal *sig* to process pre-emptively checking whether PID has been reused (see signal module constants) . @@ -1195,20 +1254,23 @@ class Process(object): if POSIX: self._send_signal(sig) else: # pragma: no cover + self._raise_if_pid_reused() + if sig != signal.SIGTERM and not self.is_running(): + msg = "process no longer exists" + raise NoSuchProcess(self.pid, self._name, msg=msg) self._proc.send_signal(sig) - @_assert_pid_not_reused def suspend(self): """Suspend process execution with SIGSTOP pre-emptively checking whether PID has been reused. - On Windows this has the effect ot suspending all process threads. + On Windows this has the effect of suspending all process threads. """ if POSIX: self._send_signal(signal.SIGSTOP) else: # pragma: no cover + self._raise_if_pid_reused() self._proc.suspend() - @_assert_pid_not_reused def resume(self): """Resume process execution with SIGCONT pre-emptively checking whether PID has been reused. @@ -1217,9 +1279,9 @@ class Process(object): if POSIX: self._send_signal(signal.SIGCONT) else: # pragma: no cover + self._raise_if_pid_reused() self._proc.resume() - @_assert_pid_not_reused def terminate(self): """Terminate the process with SIGTERM pre-emptively checking whether PID has been reused. @@ -1228,9 +1290,9 @@ class Process(object): if POSIX: self._send_signal(signal.SIGTERM) else: # pragma: no cover + self._raise_if_pid_reused() self._proc.kill() - @_assert_pid_not_reused def kill(self): """Kill the current process with SIGKILL pre-emptively checking whether PID has been reused. @@ -1238,6 +1300,7 @@ class Process(object): if POSIX: self._send_signal(signal.SIGKILL) else: # pragma: no cover + self._raise_if_pid_reused() self._proc.kill() def wait(self, timeout=None): @@ -1255,7 +1318,8 @@ class Process(object): To wait for multiple Process(es) use psutil.wait_procs(). """ if timeout is not None and not timeout >= 0: - raise ValueError("timeout must be a positive integer") + msg = "timeout must be a positive integer" + raise ValueError(msg) if self._exitcode is not _SENTINEL: return self._exitcode self._exitcode = self._proc.wait(timeout) @@ -1263,11 +1327,13 @@ class Process(object): # The valid attr names which can be processed by Process.as_dict(). +# fmt: off _as_dict_attrnames = set( [x for x in dir(Process) if not x.startswith('_') and x not in - ['send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait', + {'send_signal', 'suspend', 'resume', 'terminate', 'kill', 'wait', 'is_running', 'as_dict', 'parent', 'parents', 'children', 'rlimit', - 'memory_info_ex', 'oneshot']]) + 'memory_info_ex', 'oneshot'}]) +# fmt: on # ===================================================================== @@ -1298,7 +1364,7 @@ class Popen(Process): >>> p.username() 'giampaolo' >>> p.communicate() - ('hi\n', None) + ('hi', None) >>> p.terminate() >>> p.wait(timeout=2) 0 @@ -1343,13 +1409,16 @@ class Popen(Process): try: return object.__getattribute__(self.__subproc, name) except AttributeError: - raise AttributeError("%s instance has no attribute '%s'" - % (self.__class__.__name__, name)) + msg = "%s instance has no attribute '%s'" % ( + self.__class__.__name__, + name, + ) + raise AttributeError(msg) def wait(self, timeout=None): if self.__subproc.returncode is not None: return self.__subproc.returncode - ret = super(Popen, self).wait(timeout) + ret = super(Popen, self).wait(timeout) # noqa self.__subproc.returncode = ret return ret @@ -1386,7 +1455,6 @@ def pid_exists(pid): _pmap = {} -_lock = threading.Lock() def process_iter(attrs=None, ad_value=None): @@ -1410,58 +1478,60 @@ def process_iter(attrs=None, ad_value=None): If *attrs* is an empty list it will retrieve all process info (slow). """ + global _pmap + def add(pid): proc = Process(pid) if attrs is not None: proc.info = proc.as_dict(attrs=attrs, ad_value=ad_value) - with _lock: - _pmap[proc.pid] = proc + pmap[proc.pid] = proc return proc def remove(pid): - with _lock: - _pmap.pop(pid, None) + pmap.pop(pid, None) + pmap = _pmap.copy() a = set(pids()) - b = set(_pmap.keys()) + b = set(pmap.keys()) new_pids = a - b gone_pids = b - a for pid in gone_pids: remove(pid) - - with _lock: - ls = sorted(list(_pmap.items()) + - list(dict.fromkeys(new_pids).items())) - - for pid, proc in ls: - try: - if proc is None: # new process - yield add(pid) - else: - # use is_running() to check whether PID has been reused by - # another process in which case yield a new Process instance - if proc.is_running(): - if attrs is not None: - proc.info = proc.as_dict( - attrs=attrs, ad_value=ad_value) - yield proc - else: + try: + ls = sorted(list(pmap.items()) + list(dict.fromkeys(new_pids).items())) + for pid, proc in ls: + try: + if proc is None: # new process yield add(pid) - except NoSuchProcess: - remove(pid) - except AccessDenied: - # Process creation time can't be determined hence there's - # no way to tell whether the pid of the cached process - # has been reused. Just return the cached version. - if proc is None and pid in _pmap: - try: - yield _pmap[pid] - except KeyError: - # If we get here it is likely that 2 threads were - # using process_iter(). - pass - else: - raise + else: + # use is_running() to check whether PID has been + # reused by another process in which case yield a + # new Process instance + if proc.is_running(): + if attrs is not None: + proc.info = proc.as_dict( + attrs=attrs, ad_value=ad_value + ) + yield proc + else: + yield add(pid) + except NoSuchProcess: + remove(pid) + except AccessDenied: + # Process creation time can't be determined hence there's + # no way to tell whether the pid of the cached process + # has been reused. Just return the cached version. + if proc is None and pid in pmap: + try: + yield pmap[pid] + except KeyError: + # If we get here it is likely that 2 threads were + # using process_iter(). + pass + else: + raise + finally: + _pmap = pmap def wait_procs(procs, timeout=None, callback=None): @@ -1500,11 +1570,14 @@ def wait_procs(procs, timeout=None, callback=None): >>> for p in alive: ... p.kill() """ + def check_gone(proc, timeout): try: returncode = proc.wait(timeout=timeout) except TimeoutExpired: pass + except _SubprocessTimeoutExpired: + pass else: if returncode is not None or not proc.is_running(): # Set new Process instance attribute. @@ -1519,7 +1592,8 @@ def wait_procs(procs, timeout=None, callback=None): gone = set() alive = set(procs) if callback is not None and not callable(callback): - raise TypeError("callback %r is not a callable" % callable) + msg = "callback %r is not a callable" % callback + raise TypeError(msg) if timeout is not None: deadline = _timer() + timeout @@ -1575,7 +1649,7 @@ def cpu_count(logical=True): if logical: ret = _psplatform.cpu_count_logical() else: - ret = _psplatform.cpu_count_physical() + ret = _psplatform.cpu_count_cores() if ret is not None and ret < 1: ret = None return ret @@ -1610,16 +1684,18 @@ def cpu_times(percpu=False): try: - _last_cpu_times = cpu_times() -except Exception: + _last_cpu_times = {threading.current_thread().ident: cpu_times()} +except Exception: # noqa: BLE001 # Don't want to crash at import time. - _last_cpu_times = None + _last_cpu_times = {} try: - _last_per_cpu_times = cpu_times(percpu=True) -except Exception: + _last_per_cpu_times = { + threading.current_thread().ident: cpu_times(percpu=True) + } +except Exception: # noqa: BLE001 # Don't want to crash at import time. - _last_per_cpu_times = None + _last_per_cpu_times = {} def _cpu_tot_time(times): @@ -1713,15 +1789,14 @@ def cpu_percent(interval=None, percpu=False): 2.9 >>> """ - global _last_cpu_times - global _last_per_cpu_times + tid = threading.current_thread().ident blocking = interval is not None and interval > 0.0 if interval is not None and interval < 0: - raise ValueError("interval is not positive (got %r)" % interval) + msg = "interval is not positive (got %r)" % interval + raise ValueError(msg) def calculate(t1, t2): times_delta = _cpu_times_deltas(t1, t2) - all_delta = _cpu_tot_time(times_delta) busy_delta = _cpu_busy_time(times_delta) @@ -1738,14 +1813,9 @@ def cpu_percent(interval=None, percpu=False): t1 = cpu_times() time.sleep(interval) else: - t1 = _last_cpu_times - if t1 is None: - # Something bad happened at import time. We'll - # get a meaningful result on the next call. See: - # https://github.com/giampaolo/psutil/pull/715 - t1 = cpu_times() - _last_cpu_times = cpu_times() - return calculate(t1, _last_cpu_times) + t1 = _last_cpu_times.get(tid) or cpu_times() + _last_cpu_times[tid] = cpu_times() + return calculate(t1, _last_cpu_times[tid]) # per-cpu usage else: ret = [] @@ -1753,23 +1823,17 @@ def cpu_percent(interval=None, percpu=False): tot1 = cpu_times(percpu=True) time.sleep(interval) else: - tot1 = _last_per_cpu_times - if tot1 is None: - # Something bad happened at import time. We'll - # get a meaningful result on the next call. See: - # https://github.com/giampaolo/psutil/pull/715 - tot1 = cpu_times(percpu=True) - _last_per_cpu_times = cpu_times(percpu=True) - for t1, t2 in zip(tot1, _last_per_cpu_times): + tot1 = _last_per_cpu_times.get(tid) or cpu_times(percpu=True) + _last_per_cpu_times[tid] = cpu_times(percpu=True) + for t1, t2 in zip(tot1, _last_per_cpu_times[tid]): ret.append(calculate(t1, t2)) return ret -# Use separate global vars for cpu_times_percent() so that it's -# independent from cpu_percent() and they can both be used within -# the same program. -_last_cpu_times_2 = _last_cpu_times -_last_per_cpu_times_2 = _last_per_cpu_times +# Use a separate dict for cpu_times_percent(), so it's independent from +# cpu_percent() and they can both be used within the same program. +_last_cpu_times_2 = _last_cpu_times.copy() +_last_per_cpu_times_2 = _last_per_cpu_times.copy() def cpu_times_percent(interval=None, percpu=False): @@ -1785,11 +1849,11 @@ def cpu_times_percent(interval=None, percpu=False): *interval* and *percpu* arguments have the same meaning as in cpu_percent(). """ - global _last_cpu_times_2 - global _last_per_cpu_times_2 + tid = threading.current_thread().ident blocking = interval is not None and interval > 0.0 if interval is not None and interval < 0: - raise ValueError("interval is not positive (got %r)" % interval) + msg = "interval is not positive (got %r)" % interval + raise ValueError(msg) def calculate(t1, t2): nums = [] @@ -1814,14 +1878,9 @@ def cpu_times_percent(interval=None, percpu=False): t1 = cpu_times() time.sleep(interval) else: - t1 = _last_cpu_times_2 - if t1 is None: - # Something bad happened at import time. We'll - # get a meaningful result on the next call. See: - # https://github.com/giampaolo/psutil/pull/715 - t1 = cpu_times() - _last_cpu_times_2 = cpu_times() - return calculate(t1, _last_cpu_times_2) + t1 = _last_cpu_times_2.get(tid) or cpu_times() + _last_cpu_times_2[tid] = cpu_times() + return calculate(t1, _last_cpu_times_2[tid]) # per-cpu usage else: ret = [] @@ -1829,14 +1888,9 @@ def cpu_times_percent(interval=None, percpu=False): tot1 = cpu_times(percpu=True) time.sleep(interval) else: - tot1 = _last_per_cpu_times_2 - if tot1 is None: - # Something bad happened at import time. We'll - # get a meaningful result on the next call. See: - # https://github.com/giampaolo/psutil/pull/715 - tot1 = cpu_times(percpu=True) - _last_per_cpu_times_2 = cpu_times(percpu=True) - for t1, t2 in zip(tot1, _last_per_cpu_times_2): + tot1 = _last_per_cpu_times_2.get(tid) or cpu_times(percpu=True) + _last_per_cpu_times_2[tid] = cpu_times(percpu=True) + for t1, t2 in zip(tot1, _last_per_cpu_times_2[tid]): ret.append(calculate(t1, t2)) return ret @@ -1849,7 +1903,7 @@ def cpu_stats(): if hasattr(_psplatform, "cpu_freq"): def cpu_freq(percpu=False): - """Return CPU frequency as a nameduple including current, + """Return CPU frequency as a namedtuple including current, min and max frequency expressed in Mhz. If *percpu* is True and the system supports per-cpu frequency @@ -2004,6 +2058,7 @@ def disk_partitions(all=False): If *all* parameter is False return physical devices only and ignore all others. """ + def pathconf(path, name): try: return os.pathconf(path, name) @@ -2016,7 +2071,8 @@ def disk_partitions(all=False): for item in ret: nt = item._replace( maxfile=pathconf(item.mountpoint, 'PC_NAME_MAX'), - maxpath=pathconf(item.mountpoint, 'PC_PATH_MAX')) + maxpath=pathconf(item.mountpoint, 'PC_PATH_MAX'), + ) new.append(nt) return new else: @@ -2067,11 +2123,12 @@ def disk_io_counters(perdisk=False, nowrap=True): rawdict[disk] = nt(*fields) return rawdict else: - return nt(*[sum(x) for x in zip(*rawdict.values())]) + return nt(*(sum(x) for x in zip(*rawdict.values()))) disk_io_counters.cache_clear = functools.partial( - _wrap_numbers.cache_clear, 'psutil.disk_io_counters') + _wrap_numbers.cache_clear, 'psutil.disk_io_counters' +) disk_io_counters.cache_clear.__doc__ = "Clears nowrap argument cache" @@ -2103,7 +2160,7 @@ def net_io_counters(pernic=False, nowrap=True): and wrap (restart from 0) and add "old value" to "new value" so that the returned numbers will always be increasing or remain the same, but never decrease. - "disk_io_counters.cache_clear()" can be used to invalidate the + "net_io_counters.cache_clear()" can be used to invalidate the cache. """ rawdict = _psplatform.net_io_counters() @@ -2120,7 +2177,8 @@ def net_io_counters(pernic=False, nowrap=True): net_io_counters.cache_clear = functools.partial( - _wrap_numbers.cache_clear, 'psutil.net_io_counters') + _wrap_numbers.cache_clear, 'psutil.net_io_counters' +) net_io_counters.cache_clear.__doc__ = "Clears nowrap argument cache" @@ -2171,7 +2229,7 @@ def net_if_addrs(): Note: you can have more than one address of the same family associated with each interface. """ - has_enums = sys.version_info >= (3, 4) + has_enums = _PY3 if has_enums: import socket rawlist = _psplatform.net_if_addrs() @@ -2184,8 +2242,10 @@ def net_if_addrs(): except ValueError: if WINDOWS and fam == -1: fam = _psplatform.AF_LINK - elif (hasattr(_psplatform, "AF_LINK") and - _psplatform.AF_LINK == fam): + elif ( + hasattr(_psplatform, "AF_LINK") + and fam == _psplatform.AF_LINK + ): # Linux defines AF_LINK as an alias for AF_PACKET. # We re-set the family here so that repr(family) # will show AF_LINK rather than AF_PACKET @@ -2232,6 +2292,7 @@ if hasattr(_psplatform, "sensors_temperatures"): All temperatures are expressed in celsius unless *fahrenheit* is set to True. """ + def convert(n): if n is not None: return (float(n) * 9 / 5) + 32 if fahrenheit else n @@ -2252,7 +2313,8 @@ if hasattr(_psplatform, "sensors_temperatures"): high = critical ret[name].append( - _common.shwtemp(label, current, high, critical)) + _common.shwtemp(label, current, high, critical) + ) return dict(ret) @@ -2338,16 +2400,28 @@ if WINDOWS: # ===================================================================== +def _set_debug(value): + """Enable or disable PSUTIL_DEBUG option, which prints debugging + messages to stderr. + """ + import psutil._common + + psutil._common.PSUTIL_DEBUG = bool(value) + _psplatform.cext.set_debug(bool(value)) + + def test(): # pragma: no cover from ._common import bytes2human from ._compat import get_terminal_size today_day = datetime.date.today() + # fmt: off templ = "%-10s %5s %5s %7s %7s %5s %6s %6s %6s %s" attrs = ['pid', 'memory_percent', 'name', 'cmdline', 'cpu_times', 'create_time', 'memory_info', 'status', 'nice', 'username'] print(templ % ("USER", "PID", "%MEM", "VSZ", "RSS", "NICE", # NOQA "STATUS", "START", "TIME", "CMDLINE")) + # fmt: on for p in process_iter(attrs, ad_value=None): if p.info['create_time']: ctime = datetime.datetime.fromtimestamp(p.info['create_time']) @@ -2358,8 +2432,9 @@ def test(): # pragma: no cover else: ctime = '' if p.info['cpu_times']: - cputime = time.strftime("%M:%S", - time.localtime(sum(p.info['cpu_times']))) + cputime = time.strftime( + "%M:%S", time.localtime(sum(p.info['cpu_times'])) + ) else: cputime = '' @@ -2372,12 +2447,21 @@ def test(): # pragma: no cover if user and WINDOWS and '\\' in user: user = user.split('\\')[1] user = user[:9] - vms = bytes2human(p.info['memory_info'].vms) if \ - p.info['memory_info'] is not None else '' - rss = bytes2human(p.info['memory_info'].rss) if \ - p.info['memory_info'] is not None else '' - memp = round(p.info['memory_percent'], 1) if \ - p.info['memory_percent'] is not None else '' + vms = ( + bytes2human(p.info['memory_info'].vms) + if p.info['memory_info'] is not None + else '' + ) + rss = ( + bytes2human(p.info['memory_info'].rss) + if p.info['memory_info'] is not None + else '' + ) + memp = ( + round(p.info['memory_percent'], 1) + if p.info['memory_percent'] is not None + else '' + ) nice = int(p.info['nice']) if p.info['nice'] else '' if p.info['cmdline']: cmdline = ' '.join(p.info['cmdline']) @@ -2395,13 +2479,14 @@ def test(): # pragma: no cover status, ctime, cputime, - cmdline) - print(line[:get_terminal_size()[0]]) # NOQA + cmdline, + ) + print(line[: get_terminal_size()[0]]) # NOQA del memoize_when_activated, division if sys.version_info[0] < 3: - del num, x + del num, x # noqa if __name__ == "__main__": test() diff --git a/contrib/python/psutil/py3/psutil/_common.py b/contrib/python/psutil/py3/psutil/_common.py index 771461d692..6989feafda 100644 --- a/contrib/python/psutil/py3/psutil/_common.py +++ b/contrib/python/psutil/py3/psutil/_common.py @@ -7,8 +7,10 @@ # Note: this module is imported by setup.py so it should not import # psutil or third-party modules. -from __future__ import division, print_function +from __future__ import division +from __future__ import print_function +import collections import contextlib import errno import functools @@ -18,12 +20,12 @@ import stat import sys import threading import warnings -from collections import defaultdict from collections import namedtuple from socket import AF_INET from socket import SOCK_DGRAM from socket import SOCK_STREAM + try: from socket import AF_INET6 except ImportError: @@ -33,15 +35,19 @@ try: except ImportError: AF_UNIX = None -if sys.version_info >= (3, 4): + +# can't take it from _common.py as this script is imported by setup.py +PY3 = sys.version_info[0] >= 3 +if PY3: import enum else: enum = None -# can't take it from _common.py as this script is imported by setup.py -PY3 = sys.version_info[0] == 3 +PSUTIL_DEBUG = bool(os.getenv('PSUTIL_DEBUG')) +_DEFAULT = object() +# fmt: off __all__ = [ # OS constants 'FREEBSD', 'BSD', 'LINUX', 'NETBSD', 'OPENBSD', 'MACOS', 'OSX', 'POSIX', @@ -67,10 +73,12 @@ __all__ = [ 'conn_tmap', 'deprecated_method', 'isfile_strict', 'memoize', 'parse_environ_block', 'path_exists_strict', 'usage_percent', 'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers", + 'open_text', 'open_binary', 'cat', 'bcat', 'bytes2human', 'conn_to_ntuple', 'debug', # shell utils 'hilite', 'term_supports_colors', 'print_color', ] +# fmt: on # =================================================================== @@ -83,7 +91,7 @@ WINDOWS = os.name == "nt" LINUX = sys.platform.startswith("linux") MACOS = sys.platform.startswith("darwin") OSX = MACOS # deprecated alias -FREEBSD = sys.platform.startswith("freebsd") +FREEBSD = sys.platform.startswith(("freebsd", "midnightbsd")) OPENBSD = sys.platform.startswith("openbsd") NETBSD = sys.platform.startswith("netbsd") BSD = FREEBSD or OPENBSD or NETBSD @@ -132,6 +140,7 @@ if enum is None: NIC_DUPLEX_HALF = 1 NIC_DUPLEX_UNKNOWN = 0 else: + class NicDuplex(enum.IntEnum): NIC_DUPLEX_FULL = 2 NIC_DUPLEX_HALF = 1 @@ -144,6 +153,7 @@ if enum is None: POWER_TIME_UNKNOWN = -1 POWER_TIME_UNLIMITED = -2 else: + class BatteryTime(enum.IntEnum): POWER_TIME_UNKNOWN = -1 POWER_TIME_UNLIMITED = -2 @@ -168,6 +178,7 @@ else: # --- for system functions +# fmt: off # psutil.swap_memory() sswap = namedtuple('sswap', ['total', 'used', 'free', 'percent', 'sin', 'sout']) @@ -194,7 +205,8 @@ sconn = namedtuple('sconn', ['fd', 'family', 'type', 'laddr', 'raddr', snicaddr = namedtuple('snicaddr', ['family', 'address', 'netmask', 'broadcast', 'ptp']) # psutil.net_if_stats() -snicstats = namedtuple('snicstats', ['isup', 'duplex', 'speed', 'mtu']) +snicstats = namedtuple('snicstats', + ['isup', 'duplex', 'speed', 'mtu', 'flags']) # psutil.cpu_stats() scpustats = namedtuple( 'scpustats', ['ctx_switches', 'interrupts', 'soft_interrupts', 'syscalls']) @@ -207,12 +219,14 @@ shwtemp = namedtuple( sbattery = namedtuple('sbattery', ['percent', 'secsleft', 'power_plugged']) # psutil.sensors_fans() sfan = namedtuple('sfan', ['label', 'current']) +# fmt: on # --- for Process methods # psutil.Process.cpu_times() -pcputimes = namedtuple('pcputimes', - ['user', 'system', 'children_user', 'children_system']) +pcputimes = namedtuple( + 'pcputimes', ['user', 'system', 'children_user', 'children_system'] +) # psutil.Process.open_files() popenfile = namedtuple('popenfile', ['path', 'fd']) # psutil.Process.threads() @@ -222,15 +236,17 @@ puids = namedtuple('puids', ['real', 'effective', 'saved']) # psutil.Process.gids() pgids = namedtuple('pgids', ['real', 'effective', 'saved']) # psutil.Process.io_counters() -pio = namedtuple('pio', ['read_count', 'write_count', - 'read_bytes', 'write_bytes']) +pio = namedtuple( + 'pio', ['read_count', 'write_count', 'read_bytes', 'write_bytes'] +) # psutil.Process.ionice() pionice = namedtuple('pionice', ['ioclass', 'value']) # psutil.Process.ctx_switches() pctxsw = namedtuple('pctxsw', ['voluntary', 'involuntary']) # psutil.Process.connections() -pconn = namedtuple('pconn', ['fd', 'family', 'type', 'laddr', 'raddr', - 'status']) +pconn = namedtuple( + 'pconn', ['fd', 'family', 'type', 'laddr', 'raddr', 'status'] +) # psutil.connections() and psutil.Process.connections() addr = namedtuple('addr', ['ip', 'port']) @@ -259,9 +275,7 @@ if AF_INET6 is not None: }) if AF_UNIX is not None: - conn_tmap.update({ - "unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM]), - }) + conn_tmap.update({"unix": ([AF_UNIX], [SOCK_STREAM, SOCK_DGRAM])}) # ===================================================================== @@ -273,36 +287,49 @@ class Error(Exception): """Base exception class. All other psutil exceptions inherit from this one. """ + __module__ = 'psutil' - def __init__(self, msg=""): - Exception.__init__(self, msg) - self.msg = msg + def _infodict(self, attrs): + info = collections.OrderedDict() + for name in attrs: + value = getattr(self, name, None) + if value: # noqa + info[name] = value + elif name == "pid" and value == 0: + info[name] = value + return info + + def __str__(self): + # invoked on `raise Error` + info = self._infodict(("pid", "ppid", "name")) + if info: + details = "(%s)" % ", ".join( + ["%s=%r" % (k, v) for k, v in info.items()] + ) + else: + details = None + return " ".join([x for x in (getattr(self, "msg", ""), details) if x]) def __repr__(self): - ret = "psutil.%s %s" % (self.__class__.__name__, self.msg) - return ret.strip() - - __str__ = __repr__ + # invoked on `repr(Error)` + info = self._infodict(("pid", "ppid", "name", "seconds", "msg")) + details = ", ".join(["%s=%r" % (k, v) for k, v in info.items()]) + return "psutil.%s(%s)" % (self.__class__.__name__, details) class NoSuchProcess(Error): """Exception raised when a process with a certain PID doesn't or no longer exists. """ + __module__ = 'psutil' def __init__(self, pid, name=None, msg=None): - Error.__init__(self, msg) + Error.__init__(self) self.pid = pid self.name = name - self.msg = msg - if msg is None: - if name: - details = "(pid=%s, name=%s)" % (self.pid, repr(self.name)) - else: - details = "(pid=%s)" % self.pid - self.msg = "process no longer exists " + details + self.msg = msg or "process no longer exists" class ZombieProcess(NoSuchProcess): @@ -312,57 +339,40 @@ class ZombieProcess(NoSuchProcess): On Linux all zombie processes are querable (hence this is never raised). Windows doesn't have zombie processes. """ + __module__ = 'psutil' def __init__(self, pid, name=None, ppid=None, msg=None): - NoSuchProcess.__init__(self, msg) - self.pid = pid + NoSuchProcess.__init__(self, pid, name, msg) self.ppid = ppid - self.name = name - self.msg = msg - if msg is None: - args = ["pid=%s" % pid] - if name: - args.append("name=%s" % repr(self.name)) - if ppid: - args.append("ppid=%s" % self.ppid) - details = "(%s)" % ", ".join(args) - self.msg = "process still exists but it's a zombie " + details + self.msg = msg or "PID still exists but it's a zombie" class AccessDenied(Error): """Exception raised when permission to perform an action is denied.""" + __module__ = 'psutil' def __init__(self, pid=None, name=None, msg=None): - Error.__init__(self, msg) + Error.__init__(self) self.pid = pid self.name = name - self.msg = msg - if msg is None: - if (pid is not None) and (name is not None): - self.msg = "(pid=%s, name=%s)" % (pid, repr(name)) - elif (pid is not None): - self.msg = "(pid=%s)" % self.pid - else: - self.msg = "" + self.msg = msg or "" class TimeoutExpired(Error): """Raised on Process.wait(timeout) if timeout expires and process is still alive. """ + __module__ = 'psutil' def __init__(self, seconds, pid=None, name=None): - Error.__init__(self, "timeout after %s seconds" % seconds) + Error.__init__(self) self.seconds = seconds self.pid = pid self.name = name - if (pid is not None) and (name is not None): - self.msg += " (pid=%s, name=%s)" % (pid, repr(name)) - elif (pid is not None): - self.msg += " (pid=%s)" % self.pid + self.msg = "timeout after %s seconds" % seconds # =================================================================== @@ -370,6 +380,26 @@ class TimeoutExpired(Error): # =================================================================== +# This should be in _compat.py rather than here, but does not work well +# with setup.py importing this module via a sys.path trick. +if PY3: + if isinstance(__builtins__, dict): # cpython + exec_ = __builtins__["exec"] + else: # pypy + exec_ = getattr(__builtins__, "exec") # noqa + + exec_("""def raise_from(value, from_value): + try: + raise value from from_value + finally: + value = None + """) +else: + + def raise_from(value, from_value): + raise value + + def usage_percent(used, total, round_=None): """Calculate percentage usage of 'used' against 'total'.""" try: @@ -395,14 +425,27 @@ def memoize(fun): 1 >>> foo.cache_clear() >>> + + It supports: + - functions + - classes (acts as a @singleton) + - staticmethods + - classmethods + + It does NOT support: + - methods """ + @functools.wraps(fun) def wrapper(*args, **kwargs): key = (args, frozenset(sorted(kwargs.items()))) try: return cache[key] except KeyError: - ret = cache[key] = fun(*args, **kwargs) + try: + ret = cache[key] = fun(*args, **kwargs) + except Exception as err: # noqa: BLE001 + raise raise_from(err, None) return ret def cache_clear(): @@ -440,6 +483,7 @@ def memoize_when_activated(fun): >>> foo() >>> """ + @functools.wraps(fun) def wrapper(self): try: @@ -447,16 +491,29 @@ def memoize_when_activated(fun): ret = self._cache[fun] except AttributeError: # case 2: we never entered oneshot() ctx - return fun(self) + try: + return fun(self) + except Exception as err: # noqa: BLE001 + raise raise_from(err, None) except KeyError: # case 3: we entered oneshot() ctx but there's no cache # for this entry yet - ret = self._cache[fun] = fun(self) + try: + ret = fun(self) + except Exception as err: # noqa: BLE001 + raise raise_from(err, None) + try: + self._cache[fun] = ret + except AttributeError: + # multi-threading race condition, see: + # https://github.com/giampaolo/psutil/issues/1948 + pass return ret def cache_activate(proc): """Activate cache. Expects a Process instance. Cache will be - stored as a "_cache" instance attribute.""" + stored as a "_cache" instance attribute. + """ proc._cache = {} def cache_deactivate(proc): @@ -474,7 +531,7 @@ def memoize_when_activated(fun): def isfile_strict(path): """Same as os.path.isfile() but does not swallow EACCES / EPERM exceptions, see: - http://mail.python.org/pipermail/python-dev/2012-June/120787.html + http://mail.python.org/pipermail/python-dev/2012-June/120787.html. """ try: st = os.stat(path) @@ -488,8 +545,8 @@ def isfile_strict(path): def path_exists_strict(path): """Same as os.path.exists() but does not swallow EACCES / EPERM - exceptions, see: - http://mail.python.org/pipermail/python-dev/2012-June/120787.html + exceptions. See: + http://mail.python.org/pipermail/python-dev/2012-June/120787.html. """ try: os.stat(path) @@ -533,7 +590,7 @@ def parse_environ_block(data): equal_pos = data.find("=", pos, next_pos) if equal_pos > pos: key = data[pos:equal_pos] - value = data[equal_pos + 1:next_pos] + value = data[equal_pos + 1 : next_pos] # Windows expects environment variables to be uppercase only if WINDOWS_: key = key.upper() @@ -592,9 +649,12 @@ def deprecated_method(replacement): """A decorator which can be used to mark a method as deprecated 'replcement' is the method name which will be called instead. """ + def outer(fun): msg = "%s() is deprecated and will be removed; use %s() instead" % ( - fun.__name__, replacement) + fun.__name__, + replacement, + ) if fun.__doc__ is None: fun.__doc__ = msg @@ -602,7 +662,9 @@ def deprecated_method(replacement): def inner(self, *args, **kwargs): warnings.warn(msg, category=DeprecationWarning, stacklevel=2) return getattr(self, replacement)(*args, **kwargs) + return inner + return outer @@ -622,8 +684,8 @@ class _WrapNumbers: assert name not in self.reminders assert name not in self.reminder_keys self.cache[name] = input_dict - self.reminders[name] = defaultdict(int) - self.reminder_keys[name] = defaultdict(set) + self.reminders[name] = collections.defaultdict(int) + self.reminder_keys[name] = collections.defaultdict(set) def _remove_dead_reminders(self, input_dict, name): """In case the number of keys changed between calls (e.g. a @@ -638,7 +700,7 @@ class _WrapNumbers: def run(self, input_dict, name): """Cache dict and sum numbers which overflow and wrap. - Return an updated copy of `input_dict` + Return an updated copy of `input_dict`. """ if name not in self.cache: # This was the first call. @@ -649,7 +711,7 @@ class _WrapNumbers: old_dict = self.cache[name] new_dict = {} - for key in input_dict.keys(): + for key in input_dict: input_tuple = input_dict[key] try: old_tuple = old_dict[key] @@ -707,27 +769,79 @@ wrap_numbers.cache_clear = _wn.cache_clear wrap_numbers.cache_info = _wn.cache_info -def open_binary(fname, **kwargs): - return open(fname, "rb", **kwargs) +# The read buffer size for open() builtin. This (also) dictates how +# much data we read(2) when iterating over file lines as in: +# >>> with open(file) as f: +# ... for line in f: +# ... ... +# Default per-line buffer size for binary files is 1K. For text files +# is 8K. We use a bigger buffer (32K) in order to have more consistent +# results when reading /proc pseudo files on Linux, see: +# https://github.com/giampaolo/psutil/issues/2050 +# On Python 2 this also speeds up the reading of big files: +# (namely /proc/{pid}/smaps and /proc/net/*): +# https://github.com/giampaolo/psutil/issues/708 +FILE_READ_BUFFER_SIZE = 32 * 1024 -def open_text(fname, **kwargs): +def open_binary(fname): + return open(fname, "rb", buffering=FILE_READ_BUFFER_SIZE) + + +def open_text(fname): """On Python 3 opens a file in text mode by using fs encoding and a proper en/decoding errors handler. On Python 2 this is just an alias for open(name, 'rt'). """ - if PY3: - # See: - # https://github.com/giampaolo/psutil/issues/675 - # https://github.com/giampaolo/psutil/pull/733 - kwargs.setdefault('encoding', ENCODING) - kwargs.setdefault('errors', ENCODING_ERRS) - return open(fname, "rt", **kwargs) + if not PY3: + return open(fname, buffering=FILE_READ_BUFFER_SIZE) + + # See: + # https://github.com/giampaolo/psutil/issues/675 + # https://github.com/giampaolo/psutil/pull/733 + fobj = open( + fname, + buffering=FILE_READ_BUFFER_SIZE, + encoding=ENCODING, + errors=ENCODING_ERRS, + ) + try: + # Dictates per-line read(2) buffer size. Defaults is 8k. See: + # https://github.com/giampaolo/psutil/issues/2050#issuecomment-1013387546 + fobj._CHUNK_SIZE = FILE_READ_BUFFER_SIZE + except AttributeError: + pass + except Exception: + fobj.close() + raise + + return fobj + + +def cat(fname, fallback=_DEFAULT, _open=open_text): + """Read entire file content and return it as a string. File is + opened in text mode. If specified, `fallback` is the value + returned in case of error, either if the file does not exist or + it can't be read(). + """ + if fallback is _DEFAULT: + with _open(fname) as f: + return f.read() + else: + try: + with _open(fname) as f: + return f.read() + except (IOError, OSError): + return fallback + + +def bcat(fname, fallback=_DEFAULT): + """Same as above but opens file in binary mode.""" + return cat(fname, fallback=fallback, _open=open_binary) def bytes2human(n, format="%(value).1f%(symbol)s"): - """Used by various scripts. See: - http://goo.gl/zeJZl + """Used by various scripts. See: http://goo.gl/zeJZl. >>> bytes2human(10000) '9.8K' @@ -739,7 +853,7 @@ def bytes2human(n, format="%(value).1f%(symbol)s"): for i, s in enumerate(symbols[1:]): prefix[s] = 1 << (i + 1) * 10 for symbol in reversed(symbols[1:]): - if n >= prefix[symbol]: + if abs(n) >= prefix[symbol]: value = float(n) / prefix[symbol] return format % locals() return format % dict(symbol=symbols[0], value=n) @@ -751,9 +865,12 @@ def get_procfs_path(): if PY3: + def decode(s): return s.decode(encoding=ENCODING, errors=ENCODING_ERRS) + else: + def decode(s): return s @@ -769,10 +886,11 @@ def term_supports_colors(file=sys.stdout): # pragma: no cover return True try: import curses + assert file.isatty() curses.setupterm() assert curses.tigetnum("colors") > 0 - except Exception: + except Exception: # noqa: BLE001 return False else: return True @@ -783,14 +901,24 @@ def hilite(s, color=None, bold=False): # pragma: no cover if not term_supports_colors(): return s attr = [] - colors = dict(green='32', red='91', brown='33', yellow='93', blue='34', - violet='35', lightblue='36', grey='37', darkgrey='30') + colors = dict( + blue='34', + brown='33', + darkgrey='30', + green='32', + grey='37', + lightblue='36', + red='91', + violet='35', + yellow='93', + ) colors[None] = '29' try: color = colors[color] except KeyError: - raise ValueError("invalid color %r; choose between %s" % ( - list(colors.keys()))) + raise ValueError( + "invalid color %r; choose between %s" % (list(colors.keys())) + ) attr.append(color) if bold: attr.append('1') @@ -798,7 +926,8 @@ def hilite(s, color=None, bold=False): # pragma: no cover def print_color( - s, color=None, bold=False, file=sys.stdout): # pragma: no cover + s, color=None, bold=False, file=sys.stdout +): # pragma: no cover """Print a colorized version of string.""" if not term_supports_colors(): print(s, file=file) # NOQA @@ -809,16 +938,19 @@ def print_color( DEFAULT_COLOR = 7 GetStdHandle = ctypes.windll.Kernel32.GetStdHandle - SetConsoleTextAttribute = \ + SetConsoleTextAttribute = ( ctypes.windll.Kernel32.SetConsoleTextAttribute + ) colors = dict(green=2, red=4, brown=6, yellow=6) colors[None] = DEFAULT_COLOR try: color = colors[color] except KeyError: - raise ValueError("invalid color %r; choose between %r" % ( - color, list(colors.keys()))) + raise ValueError( + "invalid color %r; choose between %r" + % (color, list(colors.keys())) + ) if bold and color <= 7: color += 8 @@ -827,20 +959,25 @@ def print_color( handle = GetStdHandle(handle_id) SetConsoleTextAttribute(handle, color) try: - print(s, file=file) # NOQA + print(s, file=file) # NOQA finally: SetConsoleTextAttribute(handle, DEFAULT_COLOR) -if bool(os.getenv('PSUTIL_DEBUG', 0)): - import inspect +def debug(msg): + """If PSUTIL_DEBUG env var is set, print a debug message to stderr.""" + if PSUTIL_DEBUG: + import inspect - def debug(msg): - """If PSUTIL_DEBUG env var is set, print a debug message to stderr.""" - fname, lineno, func_name, lines, index = inspect.getframeinfo( - inspect.currentframe().f_back) - print("psutil-debug [%s:%s]> %s" % (fname, lineno, msg), # NOQA - file=sys.stderr) -else: - def debug(msg): - pass + fname, lineno, _, lines, index = inspect.getframeinfo( + inspect.currentframe().f_back + ) + if isinstance(msg, Exception): + if isinstance(msg, (OSError, IOError, EnvironmentError)): + # ...because str(exc) may contain info about the file name + msg = "ignoring %s" % msg + else: + msg = "ignoring %r" % msg + print( # noqa + "psutil-debug [%s:%s]> %s" % (fname, lineno, msg), file=sys.stderr + ) diff --git a/contrib/python/psutil/py3/psutil/_compat.py b/contrib/python/psutil/py3/psutil/_compat.py index 17f38485e9..3db56c6019 100644 --- a/contrib/python/psutil/py3/psutil/_compat.py +++ b/contrib/python/psutil/py3/psutil/_compat.py @@ -8,12 +8,15 @@ Python 3 way of doing things). """ import collections +import contextlib import errno import functools import os import sys import types + +# fmt: off __all__ = [ # constants "PY3", @@ -25,12 +28,16 @@ __all__ = [ "lru_cache", # shutil module "which", "get_terminal_size", + # contextlib module + "redirect_stderr", # python 3 exceptions "FileNotFoundError", "PermissionError", "ProcessLookupError", - "InterruptedError", "ChildProcessError", "FileExistsError"] + "InterruptedError", "ChildProcessError", "FileExistsError", +] +# fmt: on -PY3 = sys.version_info[0] == 3 +PY3 = sys.version_info[0] >= 3 _SENTINEL = object() if PY3: @@ -45,6 +52,7 @@ if PY3: def b(s): return s.encode("latin-1") + else: long = long range = xrange @@ -79,7 +87,8 @@ else: # Get the function's first positional argument. type_or_obj = f.f_locals[f.f_code.co_varnames[0]] except (IndexError, KeyError): - raise RuntimeError('super() used in a function with no args') + msg = 'super() used in a function with no args' + raise RuntimeError(msg) try: # Get the MRO so we can crawl it. mro = type_or_obj.__mro__ @@ -87,7 +96,8 @@ else: try: mro = type_or_obj.__class__.__mro__ except AttributeError: - raise RuntimeError('super() used in a non-newstyle class') + msg = 'super() used in a non-newstyle class' + raise RuntimeError(msg) for type_ in mro: # Find the class that owns the currently-executing method. for meth in type_.__dict__.values(): @@ -114,7 +124,8 @@ else: continue break # found else: - raise RuntimeError('super() called outside a method') + msg = 'super() called outside a method' + raise RuntimeError(msg) # Dispatch to builtin super(). if type_or_obj is not _SENTINEL: @@ -140,7 +151,6 @@ else: def _instance_checking_exception(base_exception=Exception): def wrapped(instance_checker): class TemporaryClass(base_exception): - def __init__(self, *args, **kwargs): if len(args) == 1 and isinstance(args[0], TemporaryClass): unwrap_me = args[0] @@ -148,7 +158,9 @@ else: if not attr.startswith('__'): setattr(self, attr, getattr(unwrap_me, attr)) else: - super(TemporaryClass, self).__init__(*args, **kwargs) + super(TemporaryClass, self).__init__( # noqa + *args, **kwargs + ) class __metaclass__(type): def __instancecheck__(cls, inst): @@ -174,8 +186,7 @@ else: @_instance_checking_exception(EnvironmentError) def PermissionError(inst): - return getattr(inst, 'errno', _SENTINEL) in ( - errno.EACCES, errno.EPERM) + return getattr(inst, 'errno', _SENTINEL) in (errno.EACCES, errno.EPERM) @_instance_checking_exception(EnvironmentError) def InterruptedError(inst): @@ -195,9 +206,11 @@ else: except FileExistsError: pass except OSError: - raise RuntimeError( + msg = ( "broken or incompatible Python implementation, see: " - "https://github.com/giampaolo/psutil/issues/1659") + "https://github.com/giampaolo/psutil/issues/1659" + ) + raise RuntimeError(msg) # --- stdlib additions @@ -215,10 +228,11 @@ except ImportError: from dummy_threading import RLock _CacheInfo = collections.namedtuple( - "CacheInfo", ["hits", "misses", "maxsize", "currsize"]) + "CacheInfo", ["hits", "misses", "maxsize", "currsize"] + ) class _HashedSeq(list): - __slots__ = 'hashvalue' + __slots__ = ('hashvalue',) def __init__(self, tup, hash=hash): self[:] = tup @@ -227,10 +241,17 @@ except ImportError: def __hash__(self): return self.hashvalue - def _make_key(args, kwds, typed, - kwd_mark=(object(), ), - fasttypes=set((int, str, frozenset, type(None))), - sorted=sorted, tuple=tuple, type=type, len=len): + def _make_key( + args, + kwds, + typed, + kwd_mark=(_SENTINEL,), + fasttypes=set((int, str, frozenset, type(None))), # noqa + sorted=sorted, + tuple=tuple, + type=type, + len=len, + ): key = args if kwds: sorted_items = sorted(kwds.items()) @@ -247,10 +268,11 @@ except ImportError: def lru_cache(maxsize=100, typed=False): """Least-recently-used cache decorator, see: - http://docs.python.org/3/library/functools.html#functools.lru_cache + http://docs.python.org/3/library/functools.html#functools.lru_cache. """ + def decorating_function(user_function): - cache = dict() + cache = {} stats = [0, 0] HITS, MISSES = 0, 1 make_key = _make_key @@ -262,11 +284,14 @@ except ImportError: nonlocal_root = [root] PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 if maxsize == 0: + def wrapper(*args, **kwds): result = user_function(*args, **kwds) stats[MISSES] += 1 return result + elif maxsize is None: + def wrapper(*args, **kwds): key = make_key(args, kwds, typed) result = cache_get(key, root) @@ -277,7 +302,9 @@ except ImportError: cache[key] = result stats[MISSES] += 1 return result + else: + def wrapper(*args, **kwds): if kwds or typed: key = make_key(args, kwds, typed) @@ -287,7 +314,7 @@ except ImportError: try: link = cache_get(key) if link is not None: - root, = nonlocal_root + (root,) = nonlocal_root link_prev, link_next, key, result = link link_prev[NEXT] = link_next link_next[PREV] = link_prev @@ -302,7 +329,7 @@ except ImportError: result = user_function(*args, **kwds) lock.acquire() try: - root, = nonlocal_root + (root,) = nonlocal_root if key in cache: pass elif _len(cache) >= maxsize: @@ -324,16 +351,17 @@ except ImportError: return result def cache_info(): - """Report cache statistics""" + """Report cache statistics.""" lock.acquire() try: - return _CacheInfo(stats[HITS], stats[MISSES], maxsize, - len(cache)) + return _CacheInfo( + stats[HITS], stats[MISSES], maxsize, len(cache) + ) finally: lock.release() def cache_clear(): - """Clear the cache and cache statistics""" + """Clear the cache and cache statistics.""" lock.acquire() try: cache.clear() @@ -355,6 +383,7 @@ except ImportError: try: from shutil import which except ImportError: + def which(cmd, mode=os.F_OK | os.X_OK, path=None): """Given a command, mode, and a PATH string, return the path which conforms to the given mode on the PATH, or None if there is no such @@ -364,9 +393,13 @@ except ImportError: of os.environ.get("PATH"), or can be overridden with a custom search path. """ + def _access_check(fn, mode): - return (os.path.exists(fn) and os.access(fn, mode) and - not os.path.isdir(fn)) + return ( + os.path.exists(fn) + and os.access(fn, mode) + and not os.path.isdir(fn) + ) if os.path.dirname(cmd): if _access_check(cmd, mode): @@ -407,18 +440,44 @@ except ImportError: try: from shutil import get_terminal_size except ImportError: + def get_terminal_size(fallback=(80, 24)): try: import fcntl - import termios import struct + import termios except ImportError: return fallback else: try: # This should work on Linux. res = struct.unpack( - 'hh', fcntl.ioctl(1, termios.TIOCGWINSZ, '1234')) + 'hh', fcntl.ioctl(1, termios.TIOCGWINSZ, '1234') + ) return (res[1], res[0]) - except Exception: + except Exception: # noqa: BLE001 return fallback + + +# python 3.3 +try: + from subprocess import TimeoutExpired as SubprocessTimeoutExpired +except ImportError: + + class SubprocessTimeoutExpired(Exception): + pass + + +# python 3.5 +try: + from contextlib import redirect_stderr +except ImportError: + + @contextlib.contextmanager + def redirect_stderr(new_target): + original = sys.stderr + try: + sys.stderr = new_target + yield new_target + finally: + sys.stderr = original diff --git a/contrib/python/psutil/py3/psutil/_psaix.py b/contrib/python/psutil/py3/psutil/_psaix.py index 7160ecd63a..7310ab6c3d 100644 --- a/contrib/python/psutil/py3/psutil/_psaix.py +++ b/contrib/python/psutil/py3/psutil/_psaix.py @@ -18,20 +18,20 @@ from . import _common from . import _psposix from . import _psutil_aix as cext from . import _psutil_posix as cext_posix -from ._common import AccessDenied -from ._common import conn_to_ntuple -from ._common import get_procfs_path -from ._common import memoize_when_activated from ._common import NIC_DUPLEX_FULL from ._common import NIC_DUPLEX_HALF from ._common import NIC_DUPLEX_UNKNOWN +from ._common import AccessDenied from ._common import NoSuchProcess -from ._common import usage_percent from ._common import ZombieProcess +from ._common import conn_to_ntuple +from ._common import get_procfs_path +from ._common import memoize_when_activated +from ._common import usage_percent +from ._compat import PY3 from ._compat import FileNotFoundError from ._compat import PermissionError from ._compat import ProcessLookupError -from ._compat import PY3 __extra__all__ = ["PROCFS_PATH"] @@ -53,7 +53,7 @@ PROC_STATUSES = { cext.SIDL: _common.STATUS_IDLE, cext.SZOMB: _common.STATUS_ZOMBIE, cext.SACTIVE: _common.STATUS_RUNNING, - cext.SSWAP: _common.STATUS_RUNNING, # TODO what status is this? + cext.SSWAP: _common.STATUS_RUNNING, # TODO what status is this? cext.SSTOP: _common.STATUS_STOPPED, } @@ -80,7 +80,8 @@ proc_info_map = dict( nice=4, num_threads=5, status=6, - ttynr=7) + ttynr=7, +) # ===================================================================== @@ -123,13 +124,13 @@ def swap_memory(): def cpu_times(): - """Return system-wide CPU times as a named tuple""" + """Return system-wide CPU times as a named tuple.""" ret = cext.per_cpu_times() return scputimes(*[sum(x) for x in zip(*ret)]) def per_cpu_times(): - """Return system per-CPU times as a list of named tuples""" + """Return system per-CPU times as a list of named tuples.""" ret = cext.per_cpu_times() return [scputimes(*x) for x in ret] @@ -143,14 +144,14 @@ def cpu_count_logical(): return None -def cpu_count_physical(): - cmd = "lsdev -Cc processor" - p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) +def cpu_count_cores(): + cmd = ["lsdev", "-Cc", "processor"] + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() if PY3: - stdout, stderr = [x.decode(sys.stdout.encoding) - for x in (stdout, stderr)] + stdout, stderr = ( + x.decode(sys.stdout.encoding) for x in (stdout, stderr) + ) if p.returncode != 0: raise RuntimeError("%r command error\n%s" % (cmd, stderr)) processors = stdout.strip().splitlines() @@ -161,7 +162,8 @@ def cpu_stats(): """Return various CPU stats as a named tuple.""" ctx_switches, interrupts, soft_interrupts, syscalls = cext.cpu_stats() return _common.scpustats( - ctx_switches, interrupts, soft_interrupts, syscalls) + ctx_switches, interrupts, soft_interrupts, syscalls + ) # ===================================================================== @@ -190,8 +192,9 @@ def disk_partitions(all=False): if not disk_usage(mountpoint).total: continue maxfile = maxpath = None # set later - ntuple = _common.sdiskpart(device, mountpoint, fstype, opts, - maxfile, maxpath) + ntuple = _common.sdiskpart( + device, mountpoint, fstype, opts, maxfile, maxpath + ) retlist.append(ntuple) return retlist @@ -213,8 +216,10 @@ def net_connections(kind, _pid=-1): """ cmap = _common.conn_tmap if kind not in cmap: - raise ValueError("invalid %r kind argument; choose between %s" - % (kind, ', '.join([repr(x) for x in cmap]))) + raise ValueError( + "invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in cmap])) + ) families, types = _common.conn_tmap[kind] rawlist = cext.net_connections(_pid) ret = [] @@ -224,41 +229,56 @@ def net_connections(kind, _pid=-1): continue if type_ not in types: continue - nt = conn_to_ntuple(fd, fam, type_, laddr, raddr, status, - TCP_STATUSES, pid=pid if _pid == -1 else None) + nt = conn_to_ntuple( + fd, + fam, + type_, + laddr, + raddr, + status, + TCP_STATUSES, + pid=pid if _pid == -1 else None, + ) ret.append(nt) return ret def net_if_stats(): """Get NIC stats (isup, duplex, speed, mtu).""" - duplex_map = {"Full": NIC_DUPLEX_FULL, - "Half": NIC_DUPLEX_HALF} + duplex_map = {"Full": NIC_DUPLEX_FULL, "Half": NIC_DUPLEX_HALF} names = set([x[0] for x in net_if_addrs()]) ret = {} for name in names: - isup, mtu = cext.net_if_stats(name) + mtu = cext_posix.net_if_mtu(name) + flags = cext_posix.net_if_flags(name) # try to get speed and duplex # TODO: rewrite this in C (entstat forks, so use truss -f to follow. # looks like it is using an undocumented ioctl?) duplex = "" speed = 0 - p = subprocess.Popen(["/usr/bin/entstat", "-d", name], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen( + ["/usr/bin/entstat", "-d", name], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) stdout, stderr = p.communicate() if PY3: - stdout, stderr = [x.decode(sys.stdout.encoding) - for x in (stdout, stderr)] + stdout, stderr = ( + x.decode(sys.stdout.encoding) for x in (stdout, stderr) + ) if p.returncode == 0: re_result = re.search( - r"Running: (\d+) Mbps.*?(\w+) Duplex", stdout) + r"Running: (\d+) Mbps.*?(\w+) Duplex", stdout + ) if re_result is not None: speed = int(re_result.group(1)) duplex = re_result.group(2) + output_flags = ','.join(flags) + isup = 'running' in flags duplex = duplex_map.get(duplex, NIC_DUPLEX_UNKNOWN) - ret[name] = _common.snicstats(isup, duplex, speed, mtu) + ret[name] = _common.snicstats(isup, duplex, speed, mtu, output_flags) return ret @@ -310,6 +330,7 @@ def wrap_exceptions(fun): """Call callable into a try/except clause and translate ENOENT, EACCES and EPERM in NoSuchProcess or AccessDenied exceptions. """ + @functools.wraps(fun) def wrapper(self, *args, **kwargs): try: @@ -324,10 +345,11 @@ def wrap_exceptions(fun): raise ZombieProcess(self.pid, self._name, self._ppid) except PermissionError: raise AccessDenied(self.pid, self._name) + return wrapper -class Process(object): +class Process: """Wrapper class around underlying C implementation.""" __slots__ = ["pid", "_name", "_ppid", "_procfs_path", "_cache"] @@ -376,17 +398,20 @@ class Process(object): if not os.path.isabs(exe): # if cwd has changed, we're out of luck - this may be wrong! exe = os.path.abspath(os.path.join(self.cwd(), exe)) - if (os.path.isabs(exe) and - os.path.isfile(exe) and - os.access(exe, os.X_OK)): + if ( + os.path.isabs(exe) + and os.path.isfile(exe) + and os.access(exe, os.X_OK) + ): return exe # not found, move to search in PATH using basename only exe = os.path.basename(exe) # search for exe name PATH for path in os.environ["PATH"].split(":"): possible_exe = os.path.abspath(os.path.join(path, exe)) - if (os.path.isfile(possible_exe) and - os.access(possible_exe, os.X_OK)): + if os.path.isfile(possible_exe) and os.access( + possible_exe, os.X_OK + ): return possible_exe return '' @@ -407,6 +432,7 @@ class Process(object): return self._proc_basic_info()[proc_info_map['num_threads']] if HAS_THREADS: + @wrap_exceptions def threads(self): rawlist = cext.proc_threads(self.pid) @@ -462,14 +488,14 @@ class Process(object): @wrap_exceptions def cpu_times(self): - cpu_times = cext.proc_cpu_times(self.pid, self._procfs_path) - return _common.pcputimes(*cpu_times) + t = cext.proc_cpu_times(self.pid, self._procfs_path) + return _common.pcputimes(*t) @wrap_exceptions def terminal(self): ttydev = self._proc_basic_info()[proc_info_map['ttynr']] # convert from 64-bit dev_t to 32-bit dev_t and then map the device - ttydev = (((ttydev & 0x0000FFFF00000000) >> 16) | (ttydev & 0xFFFF)) + ttydev = ((ttydev & 0x0000FFFF00000000) >> 16) | (ttydev & 0xFFFF) # try to match rdev of /dev/pts/* files ttydev for dev in glob.glob("/dev/**/*"): if os.stat(dev).st_rdev == ttydev: @@ -484,7 +510,7 @@ class Process(object): return result.rstrip('/') except FileNotFoundError: os.stat("%s/%s" % (procfs_path, self.pid)) # raise NSP or AD - return None + return "" @wrap_exceptions def memory_info(self): @@ -504,12 +530,16 @@ class Process(object): def open_files(self): # TODO rewrite without using procfiles (stat /proc/pid/fd/* and then # find matching name of the inode) - p = subprocess.Popen(["/usr/bin/procfiles", "-n", str(self.pid)], - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen( + ["/usr/bin/procfiles", "-n", str(self.pid)], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) stdout, stderr = p.communicate() if PY3: - stdout, stderr = [x.decode(sys.stdout.encoding) - for x in (stdout, stderr)] + stdout, stderr = ( + x.decode(sys.stdout.encoding) for x in (stdout, stderr) + ) if "no such process" in stderr.lower(): raise NoSuchProcess(self.pid, self._name) procfiles = re.findall(r"(\d+): S_IFREG.*\s*.*name:(.*)\n", stdout) @@ -525,20 +555,20 @@ class Process(object): @wrap_exceptions def num_fds(self): - if self.pid == 0: # no /proc/0/fd + if self.pid == 0: # no /proc/0/fd return 0 return len(os.listdir("%s/%s/fd" % (self._procfs_path, self.pid))) @wrap_exceptions def num_ctx_switches(self): - return _common.pctxsw( - *cext.proc_num_ctx_switches(self.pid)) + return _common.pctxsw(*cext.proc_num_ctx_switches(self.pid)) @wrap_exceptions def wait(self, timeout=None): return _psposix.wait_pid(self.pid, timeout, self._name) if HAS_PROC_IO_COUNTERS: + @wrap_exceptions def io_counters(self): try: diff --git a/contrib/python/psutil/py3/psutil/_psbsd.py b/contrib/python/psutil/py3/psutil/_psbsd.py index 764463e980..da68f5efd5 100644 --- a/contrib/python/psutil/py3/psutil/_psbsd.py +++ b/contrib/python/psutil/py3/psutil/_psbsd.py @@ -8,25 +8,26 @@ import contextlib import errno import functools import os -import xml.etree.ElementTree as ET -from collections import namedtuple from collections import defaultdict +from collections import namedtuple +from xml.etree import ElementTree from . import _common from . import _psposix from . import _psutil_bsd as cext from . import _psutil_posix as cext_posix +from ._common import FREEBSD +from ._common import NETBSD +from ._common import OPENBSD from ._common import AccessDenied +from ._common import NoSuchProcess +from ._common import ZombieProcess from ._common import conn_tmap from ._common import conn_to_ntuple -from ._common import FREEBSD +from ._common import debug from ._common import memoize from ._common import memoize_when_activated -from ._common import NETBSD -from ._common import NoSuchProcess -from ._common import OPENBSD from ._common import usage_percent -from ._common import ZombieProcess from ._compat import FileNotFoundError from ._compat import PermissionError from ._compat import ProcessLookupError @@ -140,6 +141,7 @@ kinfo_proc_map = dict( # ===================================================================== +# fmt: off # psutil.virtual_memory() svmem = namedtuple( 'svmem', ['total', 'available', 'percent', 'used', 'free', @@ -169,6 +171,7 @@ if FREEBSD: else: sdiskio = namedtuple('sdiskio', ['read_count', 'write_count', 'read_bytes', 'write_bytes']) +# fmt: on # ===================================================================== @@ -177,10 +180,9 @@ else: def virtual_memory(): - """System virtual memory as a namedtuple.""" mem = cext.virtual_mem() - total, free, active, inactive, wired, cached, buffers, shared = mem if NETBSD: + total, free, active, inactive, wired, cached = mem # On NetBSD buffers and shared mem is determined via /proc. # The C ext set them to 0. with open('/proc/meminfo', 'rb') as f: @@ -189,11 +191,39 @@ def virtual_memory(): buffers = int(line.split()[1]) * 1024 elif line.startswith(b'MemShared:'): shared = int(line.split()[1]) * 1024 - avail = inactive + cached + free - used = active + wired + cached + # Before avail was calculated as (inactive + cached + free), + # same as zabbix, but it turned out it could exceed total (see + # #2233), so zabbix seems to be wrong. Htop calculates it + # differently, and the used value seem more realistic, so let's + # match htop. + # https://github.com/htop-dev/htop/blob/e7f447b/netbsd/NetBSDProcessList.c#L162 # noqa + # https://github.com/zabbix/zabbix/blob/af5e0f8/src/libs/zbxsysinfo/netbsd/memory.c#L135 # noqa + used = active + wired + avail = total - used + else: + total, free, active, inactive, wired, cached, buffers, shared = mem + # matches freebsd-memory CLI: + # * https://people.freebsd.org/~rse/dist/freebsd-memory + # * https://www.cyberciti.biz/files/scripts/freebsd-memory.pl.txt + # matches zabbix: + # * https://github.com/zabbix/zabbix/blob/af5e0f8/src/libs/zbxsysinfo/freebsd/memory.c#L143 # noqa + avail = inactive + cached + free + used = active + wired + cached + percent = usage_percent((total - avail), total, round_=1) - return svmem(total, avail, percent, used, free, - active, inactive, buffers, cached, shared, wired) + return svmem( + total, + avail, + percent, + used, + free, + active, + inactive, + buffers, + cached, + shared, + wired, + ) def swap_memory(): @@ -209,20 +239,22 @@ def swap_memory(): def cpu_times(): - """Return system per-CPU times as a namedtuple""" + """Return system per-CPU times as a namedtuple.""" user, nice, system, idle, irq = cext.cpu_times() return scputimes(user, nice, system, idle, irq) if HAS_PER_CPU_TIMES: + def per_cpu_times(): - """Return system CPU times as a namedtuple""" + """Return system CPU times as a namedtuple.""" ret = [] for cpu_t in cext.per_cpu_times(): user, nice, system, idle, irq = cpu_t item = scputimes(user, nice, system, idle, irq) ret.append(item) return ret + else: # XXX # Ok, this is very dirty. @@ -232,11 +264,12 @@ else: # crash at psutil import time. # Next calls will fail with NotImplementedError def per_cpu_times(): - """Return system CPU times as a namedtuple""" + """Return system CPU times as a namedtuple.""" if cpu_count_logical() == 1: return [cpu_times()] if per_cpu_times.__called__: - raise NotImplementedError("supported only starting from FreeBSD 8") + msg = "supported only starting from FreeBSD 8" + raise NotImplementedError(msg) per_cpu_times.__called__ = True return [cpu_times()] @@ -249,33 +282,35 @@ def cpu_count_logical(): if OPENBSD or NETBSD: - def cpu_count_physical(): + + def cpu_count_cores(): # OpenBSD and NetBSD do not implement this. return 1 if cpu_count_logical() == 1 else None + else: - def cpu_count_physical(): - """Return the number of physical CPUs in the system.""" + + def cpu_count_cores(): + """Return the number of CPU cores in the system.""" # From the C module we'll get an XML string similar to this: # http://manpages.ubuntu.com/manpages/precise/man4/smp.4freebsd.html # We may get None in case "sysctl kern.sched.topology_spec" # is not supported on this BSD version, in which case we'll mimic # os.cpu_count() and return None. ret = None - s = cext.cpu_count_phys() + s = cext.cpu_topology() if s is not None: # get rid of padding chars appended at the end of the string index = s.rfind("</groups>") if index != -1: - s = s[:index + 9] - root = ET.fromstring(s) + s = s[: index + 9] + root = ElementTree.fromstring(s) try: ret = len(root.findall('group/children/group/cpu')) or None finally: # needed otherwise it will memleak root.clear() if not ret: - # If logical CPUs are 1 it's obvious we'll have only 1 - # physical CPU. + # If logical CPUs == 1 it's obvious we' have only 1 core. if cpu_count_logical() == 1: return 1 return ret @@ -297,8 +332,9 @@ def cpu_stats(): # # Note: the C ext is returning some metrics we are not exposing: # traps, faults and forks. - ctxsw, intrs, soft_intrs, syscalls, traps, faults, forks = \ + ctxsw, intrs, soft_intrs, syscalls, traps, faults, forks = ( cext.cpu_stats() + ) with open('/proc/stat', 'rb') as f: for line in f: if line.startswith(b'intr'): @@ -306,11 +342,45 @@ def cpu_stats(): elif OPENBSD: # Note: the C ext is returning some metrics we are not exposing: # traps, faults and forks. - ctxsw, intrs, soft_intrs, syscalls, traps, faults, forks = \ + ctxsw, intrs, soft_intrs, syscalls, traps, faults, forks = ( cext.cpu_stats() + ) return _common.scpustats(ctxsw, intrs, soft_intrs, syscalls) +if FREEBSD: + + def cpu_freq(): + """Return frequency metrics for CPUs. As of Dec 2018 only + CPU 0 appears to be supported by FreeBSD and all other cores + match the frequency of CPU 0. + """ + ret = [] + num_cpus = cpu_count_logical() + for cpu in range(num_cpus): + try: + current, available_freq = cext.cpu_freq(cpu) + except NotImplementedError: + continue + if available_freq: + try: + min_freq = int(available_freq.split(" ")[-1].split("/")[0]) + except (IndexError, ValueError): + min_freq = None + try: + max_freq = int(available_freq.split(" ")[0].split("/")[0]) + except (IndexError, ValueError): + max_freq = None + ret.append(_common.scpufreq(current, min_freq, max_freq)) + return ret + +elif OPENBSD: + + def cpu_freq(): + curr = float(cext.cpu_freq()) + return [_common.scpufreq(curr, 0.0, 0.0)] + + # ===================================================================== # --- disks # ===================================================================== @@ -319,15 +389,16 @@ def cpu_stats(): def disk_partitions(all=False): """Return mounted disk partitions as a list of namedtuples. 'all' argument is ignored, see: - https://github.com/giampaolo/psutil/issues/906 + https://github.com/giampaolo/psutil/issues/906. """ retlist = [] partitions = cext.disk_partitions() for partition in partitions: device, mountpoint, fstype, opts = partition maxfile = maxpath = None # set later - ntuple = _common.sdiskpart(device, mountpoint, fstype, opts, - maxfile, maxpath) + ntuple = _common.sdiskpart( + device, mountpoint, fstype, opts, maxfile, maxpath + ) retlist.append(ntuple) return retlist @@ -352,7 +423,7 @@ def net_if_stats(): for name in names: try: mtu = cext_posix.net_if_mtu(name) - isup = cext_posix.net_if_is_running(name) + flags = cext_posix.net_if_flags(name) duplex, speed = cext_posix.net_if_duplex_speed(name) except OSError as err: # https://github.com/giampaolo/psutil/issues/1279 @@ -361,42 +432,37 @@ def net_if_stats(): else: if hasattr(_common, 'NicDuplex'): duplex = _common.NicDuplex(duplex) - ret[name] = _common.snicstats(isup, duplex, speed, mtu) + output_flags = ','.join(flags) + isup = 'running' in flags + ret[name] = _common.snicstats( + isup, duplex, speed, mtu, output_flags + ) return ret def net_connections(kind): """System-wide network connections.""" - if OPENBSD: - ret = [] - for pid in pids(): - try: - cons = Process(pid).connections(kind) - except (NoSuchProcess, ZombieProcess): - continue - else: - for conn in cons: - conn = list(conn) - conn.append(pid) - ret.append(_common.sconn(*conn)) - return ret - if kind not in _common.conn_tmap: - raise ValueError("invalid %r kind argument; choose between %s" - % (kind, ', '.join([repr(x) for x in conn_tmap]))) + raise ValueError( + "invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in conn_tmap])) + ) families, types = conn_tmap[kind] ret = set() - if NETBSD: - rawlist = cext.net_connections(-1) - else: - rawlist = cext.net_connections() + + if OPENBSD: + rawlist = cext.net_connections(-1, families, types) + elif NETBSD: + rawlist = cext.net_connections(-1, kind) + else: # FreeBSD + rawlist = cext.net_connections(families, types) + for item in rawlist: fd, fam, type, laddr, raddr, status, pid = item - # TODO: apply filter at C level - if fam in families and type in types: - nt = conn_to_ntuple(fd, fam, type, laddr, raddr, status, - TCP_STATUSES, pid) - ret.add(nt) + nt = conn_to_ntuple( + fd, fam, type, laddr, raddr, status, TCP_STATUSES, pid + ) + ret.add(nt) return list(ret) @@ -424,7 +490,7 @@ if FREEBSD: return _common.sbattery(percent, secsleft, power_plugged) def sensors_temperatures(): - "Return CPU cores temperatures if available, else an empty dict." + """Return CPU cores temperatures if available, else an empty dict.""" ret = defaultdict(list) num_cpus = cpu_count_logical() for cpu in range(num_cpus): @@ -434,36 +500,13 @@ if FREEBSD: high = None name = "Core %s" % cpu ret["coretemp"].append( - _common.shwtemp(name, current, high, high)) + _common.shwtemp(name, current, high, high) + ) except NotImplementedError: pass return ret - def cpu_freq(): - """Return frequency metrics for CPUs. As of Dec 2018 only - CPU 0 appears to be supported by FreeBSD and all other cores - match the frequency of CPU 0. - """ - ret = [] - num_cpus = cpu_count_logical() - for cpu in range(num_cpus): - try: - current, available_freq = cext.cpu_frequency(cpu) - except NotImplementedError: - continue - if available_freq: - try: - min_freq = int(available_freq.split(" ")[-1].split("/")[0]) - except(IndexError, ValueError): - min_freq = None - try: - max_freq = int(available_freq.split(" ")[0].split("/")[0]) - except(IndexError, ValueError): - max_freq = None - ret.append(_common.scpufreq(current, min_freq, max_freq)) - return ret - # ===================================================================== # --- other system functions @@ -519,6 +562,7 @@ def pids(): if OPENBSD or NETBSD: + def pid_exists(pid): """Return True if pid exists.""" exists = _psposix.pid_exists(pid) @@ -528,6 +572,7 @@ if OPENBSD or NETBSD: return pid in pids() else: return True + else: pid_exists = _psposix.pid_exists @@ -535,8 +580,8 @@ else: def is_zombie(pid): try: st = cext.proc_oneshot_info(pid)[kinfo_proc_map['status']] - return st == cext.SZOMB - except Exception: + return PROC_STATUSES.get(st) == _common.STATUS_ZOMBIE + except OSError: return False @@ -544,6 +589,7 @@ def wrap_exceptions(fun): """Decorator which translates bare OSError exceptions into NoSuchProcess and AccessDenied. """ + @functools.wraps(fun) def wrapper(self, *args, **kwargs): try: @@ -562,6 +608,7 @@ def wrap_exceptions(fun): else: raise raise + return wrapper @@ -582,7 +629,7 @@ def wrap_exceptions_procfs(inst): raise AccessDenied(inst.pid, inst._name) -class Process(object): +class Process: """Wrapper class around underlying C implementation.""" __slots__ = ["pid", "_name", "_ppid", "_cache"] @@ -646,10 +693,10 @@ class Process(object): if OPENBSD and self.pid == 0: return [] # ...else it crashes elif NETBSD: - # XXX - most of the times the underlying sysctl() call on Net - # and Open BSD returns a truncated string. - # Also /proc/pid/cmdline behaves the same so it looks - # like this is a kernel bug. + # XXX - most of the times the underlying sysctl() call on + # NetBSD and OpenBSD returns a truncated string. Also + # /proc/pid/cmdline behaves the same so it looks like this + # is a kernel bug. try: return cext.proc_cmdline(self.pid) except OSError as err: @@ -661,6 +708,7 @@ class Process(object): else: # XXX: this happens with unicode tests. It means the C # routine is unable to decode invalid unicode chars. + debug("ignoring %r and returning an empty list" % err) return [] else: raise @@ -691,7 +739,8 @@ class Process(object): return _common.puids( rawtuple[kinfo_proc_map['real_uid']], rawtuple[kinfo_proc_map['effective_uid']], - rawtuple[kinfo_proc_map['saved_uid']]) + rawtuple[kinfo_proc_map['saved_uid']], + ) @wrap_exceptions def gids(self): @@ -699,7 +748,8 @@ class Process(object): return _common.pgids( rawtuple[kinfo_proc_map['real_gid']], rawtuple[kinfo_proc_map['effective_gid']], - rawtuple[kinfo_proc_map['saved_gid']]) + rawtuple[kinfo_proc_map['saved_gid']], + ) @wrap_exceptions def cpu_times(self): @@ -708,9 +758,11 @@ class Process(object): rawtuple[kinfo_proc_map['user_time']], rawtuple[kinfo_proc_map['sys_time']], rawtuple[kinfo_proc_map['ch_user_time']], - rawtuple[kinfo_proc_map['ch_sys_time']]) + rawtuple[kinfo_proc_map['ch_sys_time']], + ) if FREEBSD: + @wrap_exceptions def cpu_num(self): return self.oneshot()[kinfo_proc_map['cpunum']] @@ -723,7 +775,8 @@ class Process(object): rawtuple[kinfo_proc_map['vms']], rawtuple[kinfo_proc_map['memtext']], rawtuple[kinfo_proc_map['memdata']], - rawtuple[kinfo_proc_map['memstack']]) + rawtuple[kinfo_proc_map['memstack']], + ) memory_full_info = memory_info @@ -744,7 +797,8 @@ class Process(object): rawtuple = self.oneshot() return _common.pctxsw( rawtuple[kinfo_proc_map['ctx_switches_vol']], - rawtuple[kinfo_proc_map['ctx_switches_unvol']]) + rawtuple[kinfo_proc_map['ctx_switches_unvol']], + ) @wrap_exceptions def threads(self): @@ -761,35 +815,31 @@ class Process(object): @wrap_exceptions def connections(self, kind='inet'): if kind not in conn_tmap: - raise ValueError("invalid %r kind argument; choose between %s" - % (kind, ', '.join([repr(x) for x in conn_tmap]))) + raise ValueError( + "invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in conn_tmap])) + ) + families, types = conn_tmap[kind] + ret = [] if NETBSD: - families, types = conn_tmap[kind] - ret = [] - rawlist = cext.net_connections(self.pid) - for item in rawlist: - fd, fam, type, laddr, raddr, status, pid = item - assert pid == self.pid - if fam in families and type in types: - nt = conn_to_ntuple(fd, fam, type, laddr, raddr, status, - TCP_STATUSES) - ret.append(nt) - self._assert_alive() - return list(ret) + rawlist = cext.net_connections(self.pid, kind) + elif OPENBSD: + rawlist = cext.net_connections(self.pid, families, types) + else: + rawlist = cext.proc_connections(self.pid, families, types) - families, types = conn_tmap[kind] - rawlist = cext.proc_connections(self.pid, families, types) - ret = [] for item in rawlist: - fd, fam, type, laddr, raddr, status = item - nt = conn_to_ntuple(fd, fam, type, laddr, raddr, status, - TCP_STATUSES) + fd, fam, type, laddr, raddr, status = item[:6] + if FREEBSD: + if (fam not in families) or (type not in types): + continue + nt = conn_to_ntuple( + fd, fam, type, laddr, raddr, status, TCP_STATUSES + ) ret.append(nt) - if OPENBSD: - self._assert_alive() - + self._assert_alive() return ret @wrap_exceptions @@ -817,7 +867,8 @@ class Process(object): rawtuple[kinfo_proc_map['read_io_count']], rawtuple[kinfo_proc_map['write_io_count']], -1, - -1) + -1, + ) @wrap_exceptions def cwd(self): @@ -825,20 +876,22 @@ class Process(object): # sometimes we get an empty string, in which case we turn # it into None if OPENBSD and self.pid == 0: - return None # ...else it would raise EINVAL + return "" # ...else it would raise EINVAL elif NETBSD or HAS_PROC_OPEN_FILES: # FreeBSD < 8 does not support functions based on # kinfo_getfile() and kinfo_getvmmap() - return cext.proc_cwd(self.pid) or None + return cext.proc_cwd(self.pid) else: raise NotImplementedError( - "supported only starting from FreeBSD 8" if - FREEBSD else "") + "supported only starting from FreeBSD 8" if FREEBSD else "" + ) nt_mmap_grouped = namedtuple( - 'mmap', 'path rss, private, ref_count, shadow_count') + 'mmap', 'path rss, private, ref_count, shadow_count' + ) nt_mmap_ext = namedtuple( - 'mmap', 'addr, perms path rss, private, ref_count, shadow_count') + 'mmap', 'addr, perms path rss, private, ref_count, shadow_count' + ) def _not_implemented(self): raise NotImplementedError @@ -846,17 +899,20 @@ class Process(object): # FreeBSD < 8 does not support functions based on kinfo_getfile() # and kinfo_getvmmap() if HAS_PROC_OPEN_FILES: + @wrap_exceptions def open_files(self): """Return files opened by process as a list of namedtuples.""" rawlist = cext.proc_open_files(self.pid) return [_common.popenfile(path, fd) for path, fd in rawlist] + else: open_files = _not_implemented # FreeBSD < 8 does not support functions based on kinfo_getfile() # and kinfo_getvmmap() if HAS_PROC_NUM_FDS: + @wrap_exceptions def num_fds(self): """Return the number of file descriptors opened by this process.""" @@ -864,6 +920,7 @@ class Process(object): if NETBSD: self._assert_alive() return ret + else: num_fds = _not_implemented @@ -883,8 +940,9 @@ class Process(object): allcpus = tuple(range(len(per_cpu_times()))) for cpu in cpus: if cpu not in allcpus: - raise ValueError("invalid CPU #%i (choose between %s)" - % (cpu, allcpus)) + raise ValueError( + "invalid CPU #%i (choose between %s)" % (cpu, allcpus) + ) try: cext.proc_cpu_affinity_set(self.pid, cpus) except OSError as err: @@ -896,8 +954,9 @@ class Process(object): for cpu in cpus: if cpu not in allcpus: raise ValueError( - "invalid CPU #%i (choose between %s)" % ( - cpu, allcpus)) + "invalid CPU #%i (choose between %s)" + % (cpu, allcpus) + ) raise @wrap_exceptions @@ -911,7 +970,8 @@ class Process(object): else: if len(limits) != 2: raise ValueError( - "second argument must be a (soft, hard) tuple, " - "got %s" % repr(limits)) + "second argument must be a (soft, hard) tuple, got %s" + % repr(limits) + ) soft, hard = limits return cext.proc_setrlimit(self.pid, resource, soft, hard) diff --git a/contrib/python/psutil/py3/psutil/_pslinux.py b/contrib/python/psutil/py3/psutil/_pslinux.py index c1dc52dd5f..bab70b5d20 100644 --- a/contrib/python/psutil/py3/psutil/_pslinux.py +++ b/contrib/python/psutil/py3/psutil/_pslinux.py @@ -16,7 +16,6 @@ import re import socket import struct import sys -import traceback import warnings from collections import defaultdict from collections import namedtuple @@ -25,37 +24,41 @@ from . import _common from . import _psposix from . import _psutil_linux as cext from . import _psutil_posix as cext_posix +from ._common import NIC_DUPLEX_FULL +from ._common import NIC_DUPLEX_HALF +from ._common import NIC_DUPLEX_UNKNOWN from ._common import AccessDenied +from ._common import NoSuchProcess +from ._common import ZombieProcess +from ._common import bcat +from ._common import cat from ._common import debug from ._common import decode from ._common import get_procfs_path from ._common import isfile_strict from ._common import memoize from ._common import memoize_when_activated -from ._common import NIC_DUPLEX_FULL -from ._common import NIC_DUPLEX_HALF -from ._common import NIC_DUPLEX_UNKNOWN -from ._common import NoSuchProcess from ._common import open_binary from ._common import open_text from ._common import parse_environ_block from ._common import path_exists_strict from ._common import supports_ipv6 from ._common import usage_percent -from ._common import ZombieProcess -from ._compat import b -from ._compat import basestring +from ._compat import PY3 from ._compat import FileNotFoundError from ._compat import PermissionError from ._compat import ProcessLookupError -from ._compat import PY3 +from ._compat import b +from ._compat import basestring + -if sys.version_info >= (3, 4): +if PY3: import enum else: enum = None +# fmt: off __extra__all__ = [ # 'PROCFS_PATH', @@ -65,7 +68,9 @@ __extra__all__ = [ # connection status constants "CONN_ESTABLISHED", "CONN_SYN_SENT", "CONN_SYN_RECV", "CONN_FIN_WAIT1", "CONN_FIN_WAIT2", "CONN_TIME_WAIT", "CONN_CLOSE", "CONN_CLOSE_WAIT", - "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", ] + "CONN_LAST_ACK", "CONN_LISTEN", "CONN_CLOSING", +] +# fmt: on # ===================================================================== @@ -74,19 +79,15 @@ __extra__all__ = [ POWER_SUPPLY_PATH = "/sys/class/power_supply" -HAS_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid()) +HAS_PROC_SMAPS = os.path.exists('/proc/%s/smaps' % os.getpid()) +HAS_PROC_SMAPS_ROLLUP = os.path.exists('/proc/%s/smaps_rollup' % os.getpid()) HAS_PROC_IO_PRIORITY = hasattr(cext, "proc_ioprio_get") HAS_CPU_AFFINITY = hasattr(cext, "proc_cpu_affinity_get") -_DEFAULT = object() # Number of clock ticks per second CLOCK_TICKS = os.sysconf("SC_CLK_TCK") PAGESIZE = cext_posix.getpagesize() BOOT_TIME = None # set later -# Used when reading "big" files, namely /proc/{pid}/smaps and /proc/net/*. -# On Python 2, using a buffer with open() for such files may result in a -# speedup, see: https://github.com/giampaolo/psutil/issues/708 -BIGFILE_BUFFERING = -1 if PY3 else 8192 LITTLE_ENDIAN = sys.byteorder == 'little' # "man iostat" states that sectors are equivalent with blocks and have @@ -105,8 +106,9 @@ DISK_SECTOR_SIZE = 512 if enum is None: AF_LINK = socket.AF_PACKET else: - AddressFamily = enum.IntEnum('AddressFamily', - {'AF_LINK': int(socket.AF_PACKET)}) + AddressFamily = enum.IntEnum( + 'AddressFamily', {'AF_LINK': int(socket.AF_PACKET)} + ) AF_LINK = AddressFamily.AF_LINK # ioprio_* constants http://linux.die.net/man/2/ioprio_get @@ -116,6 +118,7 @@ if enum is None: IOPRIO_CLASS_BE = 2 IOPRIO_CLASS_IDLE = 3 else: + class IOPriority(enum.IntEnum): IOPRIO_CLASS_NONE = 0 IOPRIO_CLASS_RT = 1 @@ -155,7 +158,7 @@ TCP_STATUSES = { "08": _common.CONN_CLOSE_WAIT, "09": _common.CONN_LAST_ACK, "0A": _common.CONN_LISTEN, - "0B": _common.CONN_CLOSING + "0B": _common.CONN_CLOSING, } @@ -164,6 +167,7 @@ TCP_STATUSES = { # ===================================================================== +# fmt: off # psutil.virtual_memory() svmem = namedtuple( 'svmem', ['total', 'available', 'percent', 'used', 'free', @@ -198,6 +202,7 @@ pio = namedtuple('pio', ['read_count', 'write_count', pcputimes = namedtuple('pcputimes', ['user', 'system', 'children_user', 'children_system', 'iowait']) +# fmt: on # ===================================================================== @@ -244,7 +249,7 @@ def is_storage_device(name): "nvme0n1p1"). If name is a virtual device (e.g. "loop1", "ram") return True. """ - # Readapted from iostat source code, see: + # Re-adapted from iostat source code, see: # https://github.com/sysstat/sysstat/blob/ # 97912938cd476645b267280069e83b1c8dc0e1c7/common.c#L208 # Some devices may have a slash in their name (e.g. cciss/c0d0...). @@ -282,27 +287,11 @@ def set_scputimes_ntuple(procfs_path): scputimes = namedtuple('scputimes', fields) -def cat(fname, fallback=_DEFAULT, binary=True): - """Return file content. - fallback: the value returned in case the file does not exist or - cannot be read - binary: whether to open the file in binary or text mode. - """ - try: - with open_binary(fname) if binary else open_text(fname) as f: - return f.read().strip() - except (IOError, OSError): - if fallback is not _DEFAULT: - return fallback - else: - raise - - try: set_scputimes_ntuple("/proc") -except Exception: # pragma: no cover +except Exception as err: # noqa: BLE001 # Don't want to crash at import time. - traceback.print_exc() + debug("ignoring exception on import: %r" % err) scputimes = namedtuple('scputimes', 'user system idle')(0.0, 0.0, 0.0) @@ -331,8 +320,10 @@ except ImportError: def prlimit(pid, resource_, limits=None): class StructRlimit(ctypes.Structure): - _fields_ = [('rlim_cur', ctypes.c_longlong), - ('rlim_max', ctypes.c_longlong)] + _fields_ = [ + ('rlim_cur', ctypes.c_longlong), + ('rlim_max', ctypes.c_longlong), + ] current = StructRlimit() if limits is None: @@ -344,17 +335,19 @@ except ImportError: new.rlim_cur = limits[0] new.rlim_max = limits[1] ret = libc.prlimit( - pid, resource_, ctypes.byref(new), ctypes.byref(current)) + pid, resource_, ctypes.byref(new), ctypes.byref(current) + ) if ret != 0: - errno = ctypes.get_errno() - raise OSError(errno, os.strerror(errno)) + errno_ = ctypes.get_errno() + raise OSError(errno_, os.strerror(errno_)) return (current.rlim_cur, current.rlim_max) if prlimit is not None: __extra__all__.extend( - [x for x in dir(cext) if x.startswith('RLIM') and x.isupper()]) + [x for x in dir(cext) if x.startswith('RLIM') and x.isupper()] + ) # ===================================================================== @@ -364,34 +357,46 @@ if prlimit is not None: def calculate_avail_vmem(mems): """Fallback for kernels < 3.14 where /proc/meminfo does not provide - "MemAvailable:" column, see: - https://blog.famzah.net/2014/09/24/ + "MemAvailable", see: + https://blog.famzah.net/2014/09/24/. + This code reimplements the algorithm outlined here: https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/ commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 - XXX: on recent kernels this calculation differs by ~1.5% than - "MemAvailable:" as it's calculated slightly differently, see: - https://gitlab.com/procps-ng/procps/issues/42 - https://github.com/famzah/linux-memavailable-procfs/issues/2 + We use this function also when "MemAvailable" returns 0 (possibly a + kernel bug, see: https://github.com/giampaolo/psutil/issues/1915). + In that case this routine matches "free" CLI tool result ("available" + column). + + XXX: on recent kernels this calculation may differ by ~1.5% compared + to "MemAvailable:", as it's calculated slightly differently. It is still way more realistic than doing (free + cached) though. + See: + * https://gitlab.com/procps-ng/procps/issues/42 + * https://github.com/famzah/linux-memavailable-procfs/issues/2 """ - # Fallback for very old distros. According to + # Note about "fallback" value. According to: # https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/ # commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 - # ...long ago "avail" was calculated as (free + cached). - # We might fallback in such cases: - # "Active(file)" not available: 2.6.28 / Dec 2008 - # "Inactive(file)" not available: 2.6.28 / Dec 2008 - # "SReclaimable:" not available: 2.6.19 / Nov 2006 - # /proc/zoneinfo not available: 2.6.13 / Aug 2005 + # ...long ago "available" memory was calculated as (free + cached), + # We use fallback when one of these is missing from /proc/meminfo: + # "Active(file)": introduced in 2.6.28 / Dec 2008 + # "Inactive(file)": introduced in 2.6.28 / Dec 2008 + # "SReclaimable": introduced in 2.6.19 / Nov 2006 + # /proc/zoneinfo: introduced in 2.6.13 / Aug 2005 free = mems[b'MemFree:'] fallback = free + mems.get(b"Cached:", 0) try: lru_active_file = mems[b'Active(file):'] lru_inactive_file = mems[b'Inactive(file):'] slab_reclaimable = mems[b'SReclaimable:'] - except KeyError: + except KeyError as err: + debug( + "%s is missing from /proc/meminfo; using an approximation for " + "calculating available memory" + % err.args[0] + ) return fallback try: f = open_binary('%s/zoneinfo' % get_procfs_path()) @@ -416,19 +421,11 @@ def calculate_avail_vmem(mems): def virtual_memory(): """Report virtual memory stats. - This implementation matches "free" and "vmstat -s" cmdline - utility values and procps-ng-3.3.12 source was used as a reference - (2016-09-18): + This implementation mimicks procps-ng-3.3.12, aka "free" CLI tool: https://gitlab.com/procps-ng/procps/blob/ - 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c - For reference, procps-ng-3.3.10 is the version available on Ubuntu - 16.04. - - Note about "available" memory: up until psutil 4.3 it was - calculated as "avail = (free + buffers + cached)". Now - "MemAvailable:" column (kernel 3.14) from /proc/meminfo is used as - it's more accurate. - That matches "available" column in newer versions of "free". + 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L778-791 + The returned values are supposed to match both "free" and "vmstat -s" + CLI tools. """ missing_fields = [] mems = {} @@ -480,10 +477,11 @@ def virtual_memory(): inactive = mems[b"Inactive:"] except KeyError: try: - inactive = \ - mems[b"Inact_dirty:"] + \ - mems[b"Inact_clean:"] + \ - mems[b"Inact_laundry:"] + inactive = ( + mems[b"Inact_dirty:"] + + mems[b"Inact_clean:"] + + mems[b"Inact_laundry:"] + ) except KeyError: inactive = 0 missing_fields.append('inactive') @@ -510,17 +508,23 @@ def virtual_memory(): avail = mems[b'MemAvailable:'] except KeyError: avail = calculate_avail_vmem(mems) + else: + if avail == 0: + # Yes, it can happen (probably a kernel bug): + # https://github.com/giampaolo/psutil/issues/1915 + # In this case "free" CLI tool makes an estimate. We do the same, + # and it matches "free" CLI tool. + avail = calculate_avail_vmem(mems) if avail < 0: avail = 0 missing_fields.append('available') - - # If avail is greater than total or our calculation overflows, - # that's symptomatic of running within a LCX container where such - # values will be dramatically distorted over those of the host. - # https://gitlab.com/procps-ng/procps/blob/ - # 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L764 - if avail > total: + elif avail > total: + # If avail is greater than total or our calculation overflows, + # that's symptomatic of running within a LCX container where such + # values will be dramatically distorted over those of the host. + # https://gitlab.com/procps-ng/procps/blob/ + # 24fd2605c51fccc375ab0287cec33aa767f06718/proc/sysinfo.c#L764 avail = free percent = usage_percent((total - avail), total, round_=1) @@ -529,11 +533,23 @@ def virtual_memory(): if missing_fields: msg = "%s memory stats couldn't be determined and %s set to 0" % ( ", ".join(missing_fields), - "was" if len(missing_fields) == 1 else "were") - warnings.warn(msg, RuntimeWarning) - - return svmem(total, avail, percent, used, free, - active, inactive, buffers, cached, shared, slab) + "was" if len(missing_fields) == 1 else "were", + ) + warnings.warn(msg, RuntimeWarning, stacklevel=2) + + return svmem( + total, + avail, + percent, + used, + free, + active, + inactive, + buffers, + cached, + shared, + slab, + ) def swap_memory(): @@ -562,9 +578,11 @@ def swap_memory(): f = open_binary("%s/vmstat" % get_procfs_path()) except IOError as err: # see https://github.com/giampaolo/psutil/issues/722 - msg = "'sin' and 'sout' swap memory stats couldn't " \ - "be determined and were set to 0 (%s)" % str(err) - warnings.warn(msg, RuntimeWarning) + msg = ( + "'sin' and 'sout' swap memory stats couldn't " + + "be determined and were set to 0 (%s)" % str(err) + ) + warnings.warn(msg, RuntimeWarning, stacklevel=2) sin = sout = 0 else: with f: @@ -582,9 +600,9 @@ def swap_memory(): # we might get here when dealing with exotic Linux # flavors, see: # https://github.com/giampaolo/psutil/issues/313 - msg = "'sin' and 'sout' swap memory stats couldn't " \ - "be determined and were set to 0" - warnings.warn(msg, RuntimeWarning) + msg = "'sin' and 'sout' swap memory stats couldn't " + msg += "be determined and were set to 0" + warnings.warn(msg, RuntimeWarning, stacklevel=2) sin = sout = 0 return _common.sswap(total, used, free, percent, sin, sout) @@ -605,7 +623,7 @@ def cpu_times(): set_scputimes_ntuple(procfs_path) with open_binary('%s/stat' % procfs_path) as f: values = f.readline().split() - fields = values[1:len(scputimes._fields) + 1] + fields = values[1 : len(scputimes._fields) + 1] fields = [float(x) / CLOCK_TICKS for x in fields] return scputimes(*fields) @@ -623,7 +641,7 @@ def per_cpu_times(): for line in f: if line.startswith(b'cpu'): values = line.split() - fields = values[1:len(scputimes._fields) + 1] + fields = values[1 : len(scputimes._fields) + 1] fields = [float(x) / CLOCK_TICKS for x in fields] entry = scputimes(*fields) cpus.append(entry) @@ -659,8 +677,8 @@ def cpu_count_logical(): return num -def cpu_count_physical(): - """Return the number of physical cores in the system.""" +def cpu_count_cores(): + """Return the number of CPU cores in the system.""" # Method #1 ls = set() # These 2 files are the same but */core_cpus_list is newer while @@ -686,8 +704,9 @@ def cpu_count_physical(): if not line: # new section try: - mapping[current_info[b'physical id']] = \ - current_info[b'cpu cores'] + mapping[current_info[b'physical id']] = current_info[ + b'cpu cores' + ] except KeyError: pass current_info = {} @@ -714,66 +733,71 @@ def cpu_stats(): interrupts = int(line.split()[1]) elif line.startswith(b'softirq'): soft_interrupts = int(line.split()[1]) - if ctx_switches is not None and soft_interrupts is not None \ - and interrupts is not None: + if ( + ctx_switches is not None + and soft_interrupts is not None + and interrupts is not None + ): break syscalls = 0 return _common.scpustats( - ctx_switches, interrupts, soft_interrupts, syscalls) + ctx_switches, interrupts, soft_interrupts, syscalls + ) + + +def _cpu_get_cpuinfo_freq(): + """Return current CPU frequency from cpuinfo if available.""" + ret = [] + with open_binary('%s/cpuinfo' % get_procfs_path()) as f: + for line in f: + if line.lower().startswith(b'cpu mhz'): + ret.append(float(line.split(b':', 1)[1])) + return ret -if os.path.exists("/sys/devices/system/cpu/cpufreq/policy0") or \ - os.path.exists("/sys/devices/system/cpu/cpu0/cpufreq"): +if os.path.exists("/sys/devices/system/cpu/cpufreq/policy0") or os.path.exists( + "/sys/devices/system/cpu/cpu0/cpufreq" +): + def cpu_freq(): """Return frequency metrics for all CPUs. Contrarily to other OSes, Linux updates these values in real-time. """ - def get_path(num): - for p in ("/sys/devices/system/cpu/cpufreq/policy%s" % num, - "/sys/devices/system/cpu/cpu%s/cpufreq" % num): - if os.path.exists(p): - return p - + cpuinfo_freqs = _cpu_get_cpuinfo_freq() + paths = glob.glob( + "/sys/devices/system/cpu/cpufreq/policy[0-9]*" + ) or glob.glob("/sys/devices/system/cpu/cpu[0-9]*/cpufreq") + paths.sort(key=lambda x: int(re.search(r"[0-9]+", x).group())) ret = [] - for n in range(cpu_count_logical()): - path = get_path(n) - if not path: - continue - - pjoin = os.path.join - curr = cat(pjoin(path, "scaling_cur_freq"), fallback=None) + pjoin = os.path.join + for i, path in enumerate(paths): + if len(paths) == len(cpuinfo_freqs): + # take cached value from cpuinfo if available, see: + # https://github.com/giampaolo/psutil/issues/1851 + curr = cpuinfo_freqs[i] * 1000 + else: + curr = bcat(pjoin(path, "scaling_cur_freq"), fallback=None) if curr is None: # Likely an old RedHat, see: # https://github.com/giampaolo/psutil/issues/1071 - curr = cat(pjoin(path, "cpuinfo_cur_freq"), fallback=None) + curr = bcat(pjoin(path, "cpuinfo_cur_freq"), fallback=None) if curr is None: - raise NotImplementedError( - "can't find current frequency file") + msg = "can't find current frequency file" + raise NotImplementedError(msg) curr = int(curr) / 1000 - max_ = int(cat(pjoin(path, "scaling_max_freq"))) / 1000 - min_ = int(cat(pjoin(path, "scaling_min_freq"))) / 1000 + max_ = int(bcat(pjoin(path, "scaling_max_freq"))) / 1000 + min_ = int(bcat(pjoin(path, "scaling_min_freq"))) / 1000 ret.append(_common.scpufreq(curr, min_, max_)) return ret -elif os.path.exists("/proc/cpuinfo"): +else: + def cpu_freq(): """Alternate implementation using /proc/cpuinfo. min and max frequencies are not available and are set to None. """ - ret = [] - with open_binary('%s/cpuinfo' % get_procfs_path()) as f: - for line in f: - if line.lower().startswith(b'cpu mhz'): - key, value = line.split(b':', 1) - ret.append(_common.scpufreq(float(value), 0., 0.)) - return ret - -else: - def cpu_freq(): - """Dummy implementation when none of the above files are present. - """ - return [] + return [_common.scpufreq(x, 0.0, 0.0) for x in _cpu_get_cpuinfo_freq()] # ===================================================================== @@ -837,6 +861,10 @@ class Connections: if err.errno == errno.EINVAL: # not a link continue + if err.errno == errno.ENAMETOOLONG: + # file name too long + debug(err) + continue raise else: if inode.startswith('socket:['): @@ -899,11 +927,13 @@ class Connections: if LITTLE_ENDIAN: ip = socket.inet_ntop( socket.AF_INET6, - struct.pack('>4I', *struct.unpack('<4I', ip))) + struct.pack('>4I', *struct.unpack('<4I', ip)), + ) else: ip = socket.inet_ntop( socket.AF_INET6, - struct.pack('<4I', *struct.unpack('<4I', ip))) + struct.pack('<4I', *struct.unpack('<4I', ip)), + ) except ValueError: # see: https://github.com/giampaolo/psutil/issues/623 if not supports_ipv6(): @@ -918,22 +948,24 @@ class Connections: if file.endswith('6') and not os.path.exists(file): # IPv6 not supported return - with open_text(file, buffering=BIGFILE_BUFFERING) as f: + with open_text(file) as f: f.readline() # skip the first line for lineno, line in enumerate(f, 1): try: - _, laddr, raddr, status, _, _, _, _, _, inode = \ + _, laddr, raddr, status, _, _, _, _, _, inode = ( line.split()[:10] + ) except ValueError: raise RuntimeError( - "error while parsing %s; malformed line %s %r" % ( - file, lineno, line)) + "error while parsing %s; malformed line %s %r" + % (file, lineno, line) + ) if inode in inodes: # # We assume inet sockets are unique, so we error # # out if there are multiple references to the # # same inode. We won't do this for UNIX sockets. # if len(inodes[inode]) > 1 and family != socket.AF_UNIX: - # raise ValueError("ambiguos inode with multiple " + # raise ValueError("ambiguous inode with multiple " # "PIDs references") pid, fd = inodes[inode][0] else: @@ -955,7 +987,7 @@ class Connections: @staticmethod def process_unix(file, family, inodes, filter_pid=None): """Parse /proc/net/unix files.""" - with open_text(file, buffering=BIGFILE_BUFFERING) as f: + with open_text(file) as f: f.readline() # skip the first line for line in f: tokens = line.split() @@ -966,9 +998,10 @@ class Connections: # see: https://github.com/giampaolo/psutil/issues/766 continue raise RuntimeError( - "error while parsing %s; malformed line %r" % ( - file, line)) - if inode in inodes: + "error while parsing %s; malformed line %r" + % (file, line) + ) + if inode in inodes: # noqa # With UNIX sockets we can have a single inode # referencing many file descriptors. pairs = inodes[inode] @@ -978,10 +1011,7 @@ class Connections: if filter_pid is not None and filter_pid != pid: continue else: - if len(tokens) == 8: - path = tokens[-1] - else: - path = "" + path = tokens[-1] if len(tokens) == 8 else '' type_ = _common.socktype_to_enum(int(type_)) # XXX: determining the remote endpoint of a # UNIX socket on Linux is not possible, see: @@ -992,8 +1022,10 @@ class Connections: def retrieve(self, kind, pid=None): if kind not in self.tmap: - raise ValueError("invalid %r kind argument; choose between %s" - % (kind, ', '.join([repr(x) for x in self.tmap]))) + raise ValueError( + "invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in self.tmap])) + ) self._procfs_path = get_procfs_path() if pid is not None: inodes = self.get_proc_inodes(pid) @@ -1007,17 +1039,19 @@ class Connections: path = "%s/net/%s" % (self._procfs_path, proto_name) if family in (socket.AF_INET, socket.AF_INET6): ls = self.process_inet( - path, family, type_, inodes, filter_pid=pid) + path, family, type_, inodes, filter_pid=pid + ) else: - ls = self.process_unix( - path, family, inodes, filter_pid=pid) + ls = self.process_unix(path, family, inodes, filter_pid=pid) for fd, family, type_, laddr, raddr, status, bound_pid in ls: if pid: - conn = _common.pconn(fd, family, type_, laddr, raddr, - status) + conn = _common.pconn( + fd, family, type_, laddr, raddr, status + ) else: - conn = _common.sconn(fd, family, type_, laddr, raddr, - status, bound_pid) + conn = _common.sconn( + fd, family, type_, laddr, raddr, status, bound_pid + ) ret.add(conn) return list(ret) @@ -1041,50 +1075,68 @@ def net_io_counters(): colon = line.rfind(':') assert colon > 0, repr(line) name = line[:colon].strip() - fields = line[colon + 1:].strip().split() + fields = line[colon + 1 :].strip().split() # in - (bytes_recv, - packets_recv, - errin, - dropin, - fifoin, # unused - framein, # unused - compressedin, # unused - multicastin, # unused - # out - bytes_sent, - packets_sent, - errout, - dropout, - fifoout, # unused - collisionsout, # unused - carrierout, # unused - compressedout) = map(int, fields) - - retdict[name] = (bytes_sent, bytes_recv, packets_sent, packets_recv, - errin, errout, dropin, dropout) + ( + bytes_recv, + packets_recv, + errin, + dropin, + fifoin, # unused + framein, # unused + compressedin, # unused + multicastin, # unused + # out + bytes_sent, + packets_sent, + errout, + dropout, + fifoout, # unused + collisionsout, # unused + carrierout, # unused + compressedout, + ) = map(int, fields) + + retdict[name] = ( + bytes_sent, + bytes_recv, + packets_sent, + packets_recv, + errin, + errout, + dropin, + dropout, + ) return retdict def net_if_stats(): """Get NIC stats (isup, duplex, speed, mtu).""" - duplex_map = {cext.DUPLEX_FULL: NIC_DUPLEX_FULL, - cext.DUPLEX_HALF: NIC_DUPLEX_HALF, - cext.DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN} + duplex_map = { + cext.DUPLEX_FULL: NIC_DUPLEX_FULL, + cext.DUPLEX_HALF: NIC_DUPLEX_HALF, + cext.DUPLEX_UNKNOWN: NIC_DUPLEX_UNKNOWN, + } names = net_io_counters().keys() ret = {} for name in names: try: mtu = cext_posix.net_if_mtu(name) - isup = cext_posix.net_if_is_running(name) + flags = cext_posix.net_if_flags(name) duplex, speed = cext.net_if_duplex_speed(name) except OSError as err: # https://github.com/giampaolo/psutil/issues/1279 if err.errno != errno.ENODEV: raise + else: + debug(err) else: - ret[name] = _common.snicstats(isup, duplex_map[duplex], speed, mtu) + output_flags = ','.join(flags) + isup = 'running' in flags + ret[name] = _common.snicstats( + isup, duplex_map[duplex], speed, mtu, output_flags + ) return ret @@ -1100,6 +1152,7 @@ def disk_io_counters(perdisk=False): """Return disk I/O statistics for every disk installed on the system as a dict of raw tuples. """ + def read_procfs(): # OK, this is a bit confusing. The format of /proc/diskstats can # have 3 variations. @@ -1122,6 +1175,7 @@ def disk_io_counters(perdisk=False): for line in lines: fields = line.split() flen = len(fields) + # fmt: off if flen == 15: # Linux 2.4 name = fields[3] @@ -1142,6 +1196,7 @@ def disk_io_counters(perdisk=False): raise ValueError("not sure how to interpret line %r" % line) yield (name, reads, writes, rbytes, wbytes, rtime, wtime, reads_merged, writes_merged, busy_time) + # fmt: on def read_sysfs(): for block in os.listdir('/sys/block'): @@ -1151,10 +1206,12 @@ def disk_io_counters(perdisk=False): with open_text(os.path.join(root, 'stat')) as f: fields = f.read().strip().split() name = os.path.basename(root) + # fmt: off (reads, reads_merged, rbytes, rtime, writes, writes_merged, wbytes, wtime, _, busy_time) = map(int, fields[:10]) yield (name, reads, writes, rbytes, wbytes, rtime, wtime, reads_merged, writes_merged, busy_time) + # fmt: on if os.path.exists('%s/diskstats' % get_procfs_path()): gen = read_procfs() @@ -1163,10 +1220,13 @@ def disk_io_counters(perdisk=False): else: raise NotImplementedError( "%s/diskstats nor /sys/block filesystem are available on this " - "system" % get_procfs_path()) + "system" + % get_procfs_path() + ) retdict = {} for entry in gen: + # fmt: off (name, reads, writes, rbytes, wbytes, rtime, wtime, reads_merged, writes_merged, busy_time) = entry if not perdisk and not is_storage_device(name): @@ -1187,24 +1247,101 @@ def disk_io_counters(perdisk=False): wbytes *= DISK_SECTOR_SIZE retdict[name] = (reads, writes, rbytes, wbytes, rtime, wtime, reads_merged, writes_merged, busy_time) + # fmt: on return retdict +class RootFsDeviceFinder: + """disk_partitions() may return partitions with device == "/dev/root" + or "rootfs". This container class uses different strategies to try to + obtain the real device path. Resources: + https://bootlin.com/blog/find-root-device/ + https://www.systutorials.com/how-to-find-the-disk-where-root-is-on-in-bash-on-linux/. + """ + + __slots__ = ['major', 'minor'] + + def __init__(self): + dev = os.stat("/").st_dev + self.major = os.major(dev) + self.minor = os.minor(dev) + + def ask_proc_partitions(self): + with open_text("%s/partitions" % get_procfs_path()) as f: + for line in f.readlines()[2:]: + fields = line.split() + if len(fields) < 4: # just for extra safety + continue + major = int(fields[0]) if fields[0].isdigit() else None + minor = int(fields[1]) if fields[1].isdigit() else None + name = fields[3] + if major == self.major and minor == self.minor: + if name: # just for extra safety + return "/dev/%s" % name + + def ask_sys_dev_block(self): + path = "/sys/dev/block/%s:%s/uevent" % (self.major, self.minor) + with open_text(path) as f: + for line in f: + if line.startswith("DEVNAME="): + name = line.strip().rpartition("DEVNAME=")[2] + if name: # just for extra safety + return "/dev/%s" % name + + def ask_sys_class_block(self): + needle = "%s:%s" % (self.major, self.minor) + files = glob.iglob("/sys/class/block/*/dev") + for file in files: + try: + f = open_text(file) + except FileNotFoundError: # race condition + continue + else: + with f: + data = f.read().strip() + if data == needle: + name = os.path.basename(os.path.dirname(file)) + return "/dev/%s" % name + + def find(self): + path = None + if path is None: + try: + path = self.ask_proc_partitions() + except (IOError, OSError) as err: + debug(err) + if path is None: + try: + path = self.ask_sys_dev_block() + except (IOError, OSError) as err: + debug(err) + if path is None: + try: + path = self.ask_sys_class_block() + except (IOError, OSError) as err: + debug(err) + # We use exists() because the "/dev/*" part of the path is hard + # coded, so we want to be sure. + if path is not None and os.path.exists(path): + return path + + def disk_partitions(all=False): """Return mounted disk partitions as a list of namedtuples.""" fstypes = set() procfs_path = get_procfs_path() - with open_text("%s/filesystems" % procfs_path) as f: - for line in f: - line = line.strip() - if not line.startswith("nodev"): - fstypes.add(line.strip()) - else: - # ignore all lines starting with "nodev" except "nodev zfs" - fstype = line.split("\t")[1] - if fstype == "zfs": - fstypes.add("zfs") + if not all: + with open_text("%s/filesystems" % procfs_path) as f: + for line in f: + line = line.strip() + if not line.startswith("nodev"): + fstypes.add(line.strip()) + else: + # ignore all lines starting with "nodev" except "nodev zfs" + fstype = line.split("\t")[1] + if fstype == "zfs": + fstypes.add("zfs") # See: https://github.com/giampaolo/psutil/issues/1307 if procfs_path == "/proc" and os.path.isfile('/etc/mtab'): @@ -1218,12 +1355,15 @@ def disk_partitions(all=False): device, mountpoint, fstype, opts = partition if device == 'none': device = '' + if device in ("/dev/root", "rootfs"): + device = RootFsDeviceFinder().find() or device if not all: if device == '' or fstype not in fstypes: continue maxfile = maxpath = None # set later - ntuple = _common.sdiskpart(device, mountpoint, fstype, opts, - maxfile, maxpath) + ntuple = _common.sdiskpart( + device, mountpoint, fstype, opts, maxfile, maxpath + ) retlist.append(ntuple) return retlist @@ -1260,7 +1400,8 @@ def sensors_temperatures(): # https://github.com/giampaolo/psutil/issues/1708 # https://github.com/giampaolo/psutil/pull/1648 basenames2 = glob.glob( - '/sys/devices/platform/coretemp.*/hwmon/hwmon*/temp*_*') + '/sys/devices/platform/coretemp.*/hwmon/hwmon*/temp*_*' + ) repl = re.compile('/sys/devices/platform/coretemp.*/hwmon/') for name in basenames2: altname = repl.sub('/sys/class/hwmon/', name) @@ -1270,9 +1411,9 @@ def sensors_temperatures(): for base in basenames: try: path = base + '_input' - current = float(cat(path)) / 1000.0 + current = float(bcat(path)) / 1000.0 path = os.path.join(os.path.dirname(base), 'name') - unit_name = cat(path, binary=False) + unit_name = cat(path).strip() except (IOError, OSError, ValueError): # A lot of things can go wrong here, so let's just skip the # whole entry. Sure thing is Linux's /sys/class/hwmon really @@ -1284,9 +1425,9 @@ def sensors_temperatures(): # https://github.com/giampaolo/psutil/issues/1323 continue - high = cat(base + '_max', fallback=None) - critical = cat(base + '_crit', fallback=None) - label = cat(base + '_label', fallback='', binary=False) + high = bcat(base + '_max', fallback=None) + critical = bcat(base + '_crit', fallback=None) + label = cat(base + '_label', fallback='').strip() if high is not None: try: @@ -1309,27 +1450,31 @@ def sensors_temperatures(): for base in basenames: try: path = os.path.join(base, 'temp') - current = float(cat(path)) / 1000.0 + current = float(bcat(path)) / 1000.0 path = os.path.join(base, 'type') - unit_name = cat(path, binary=False) + unit_name = cat(path).strip() except (IOError, OSError, ValueError) as err: - debug("ignoring %r for file %r" % (err, path)) + debug(err) continue trip_paths = glob.glob(base + '/trip_point*') - trip_points = set(['_'.join( - os.path.basename(p).split('_')[0:3]) for p in trip_paths]) + trip_points = set([ + '_'.join(os.path.basename(p).split('_')[0:3]) + for p in trip_paths + ]) critical = None high = None for trip_point in trip_points: path = os.path.join(base, trip_point + "_type") - trip_type = cat(path, fallback='', binary=False) + trip_type = cat(path, fallback='').strip() if trip_type == 'critical': - critical = cat(os.path.join(base, trip_point + "_temp"), - fallback=None) + critical = bcat( + os.path.join(base, trip_point + "_temp"), fallback=None + ) elif trip_type == 'high': - high = cat(os.path.join(base, trip_point + "_temp"), - fallback=None) + high = bcat( + os.path.join(base, trip_point + "_temp"), fallback=None + ) if high is not None: try: @@ -1367,13 +1512,12 @@ def sensors_fans(): basenames = sorted(set([x.split('_')[0] for x in basenames])) for base in basenames: try: - current = int(cat(base + '_input')) + current = int(bcat(base + '_input')) except (IOError, OSError) as err: - warnings.warn("ignoring %r" % err, RuntimeWarning) + debug(err) continue - unit_name = cat(os.path.join(os.path.dirname(base), 'name'), - binary=False) - label = cat(base + '_label', fallback='', binary=False) + unit_name = cat(os.path.join(os.path.dirname(base), 'name')).strip() + label = cat(base + '_label', fallback='').strip() ret[unit_name].append(_common.sfan(label, current)) return dict(ret) @@ -1384,22 +1528,28 @@ def sensors_battery(): Implementation note: it appears /sys/class/power_supply/BAT0/ directory structure may vary and provide files with the same meaning but under different names, see: - https://github.com/giampaolo/psutil/issues/966 + https://github.com/giampaolo/psutil/issues/966. """ null = object() - def multi_cat(*paths): + def multi_bcat(*paths): """Attempt to read the content of multiple files which may not exist. If none of them exist return None. """ for path in paths: - ret = cat(path, fallback=null) + ret = bcat(path, fallback=null) if ret != null: - return int(ret) if ret.isdigit() else ret + try: + return int(ret) + except ValueError: + return ret.strip() return None - bats = [x for x in os.listdir(POWER_SUPPLY_PATH) if x.startswith('BAT') or - 'battery' in x.lower()] + bats = [ + x + for x in os.listdir(POWER_SUPPLY_PATH) + if x.startswith('BAT') or 'battery' in x.lower() + ] if not bats: return None # Get the first available battery. Usually this is "BAT0", except @@ -1408,16 +1558,10 @@ def sensors_battery(): root = os.path.join(POWER_SUPPLY_PATH, sorted(bats)[0]) # Base metrics. - energy_now = multi_cat( - root + "/energy_now", - root + "/charge_now") - power_now = multi_cat( - root + "/power_now", - root + "/current_now") - energy_full = multi_cat( - root + "/energy_full", - root + "/charge_full") - time_to_empty = multi_cat(root + "/time_to_empty_now") + energy_now = multi_bcat(root + "/energy_now", root + "/charge_now") + power_now = multi_bcat(root + "/power_now", root + "/current_now") + energy_full = multi_bcat(root + "/energy_full", root + "/charge_full") + time_to_empty = multi_bcat(root + "/time_to_empty_now") # Percent. If we have energy_full the percentage will be more # accurate compared to reading /capacity file (float vs. int). @@ -1435,13 +1579,14 @@ def sensors_battery(): # Note: AC0 is not always available and sometimes (e.g. CentOS7) # it's called "AC". power_plugged = None - online = multi_cat( + online = multi_bcat( os.path.join(POWER_SUPPLY_PATH, "AC0/online"), - os.path.join(POWER_SUPPLY_PATH, "AC/online")) + os.path.join(POWER_SUPPLY_PATH, "AC/online"), + ) if online is not None: power_plugged = online == 1 else: - status = cat(root + "/status", fallback="", binary=False).lower() + status = cat(root + "/status", fallback="").strip().lower() if status == "discharging": power_plugged = False elif status in ("charging", "full"): @@ -1478,14 +1623,7 @@ def users(): retlist = [] rawlist = cext.users() for item in rawlist: - user, tty, hostname, tstamp, user_process, pid = item - # note: the underlying C function includes entries about - # system boot, run level and others. We might want - # to use them in the future. - if not user_process: - continue - if hostname in (':0.0', ':0'): - hostname = 'localhost' + user, tty, hostname, tstamp, pid = item nt = _common.suser(user, tty or None, hostname, tstamp, pid) retlist.append(nt) return retlist @@ -1501,8 +1639,7 @@ def boot_time(): ret = float(line.strip().split()[1]) BOOT_TIME = ret return ret - raise RuntimeError( - "line 'btime' not found in %s" % path) + raise RuntimeError("line 'btime' not found in %s" % path) # ===================================================================== @@ -1563,7 +1700,7 @@ def ppid_map(): pass else: rpar = data.rfind(b')') - dset = data[rpar + 2:].split() + dset = data[rpar + 2 :].split() ppid = int(dset[1]) ret[pid] = ppid return ret @@ -1573,6 +1710,7 @@ def wrap_exceptions(fun): """Decorator which translates bare OSError and IOError exceptions into NoSuchProcess and AccessDenied. """ + @functools.wraps(fun) def wrapper(self, *args, **kwargs): try: @@ -1580,17 +1718,18 @@ def wrap_exceptions(fun): except PermissionError: raise AccessDenied(self.pid, self._name) except ProcessLookupError: + self._raise_if_zombie() raise NoSuchProcess(self.pid, self._name) except FileNotFoundError: + self._raise_if_zombie() if not os.path.exists("%s/%s" % (self._procfs_path, self.pid)): raise NoSuchProcess(self.pid, self._name) - # Note: zombies will keep existing under /proc until they're - # gone so there's no way to distinguish them in here. raise + return wrapper -class Process(object): +class Process: """Linux process implementation.""" __slots__ = ["pid", "_name", "_ppid", "_procfs_path", "_cache"] @@ -1601,7 +1740,27 @@ class Process(object): self._ppid = None self._procfs_path = get_procfs_path() - def _assert_alive(self): + def _is_zombie(self): + # Note: most of the times Linux is able to return info about the + # process even if it's a zombie, and /proc/{pid} will exist. + # There are some exceptions though, like exe(), cmdline() and + # memory_maps(). In these cases /proc/{pid}/{file} exists but + # it's empty. Instead of returning a "null" value we'll raise an + # exception. + try: + data = bcat("%s/%s/stat" % (self._procfs_path, self.pid)) + except (IOError, OSError): + return False + else: + rpar = data.rfind(b')') + status = data[rpar + 2 : rpar + 3] + return status == b"Z" + + def _raise_if_zombie(self): + if self._is_zombie(): + raise ZombieProcess(self.pid, self._name, self._ppid) + + def _raise_if_not_alive(self): """Raise NSP if the process disappeared on us.""" # For those C function who do not raise NSP, possibly returning # incorrect or incomplete result. @@ -1613,19 +1772,18 @@ class Process(object): """Parse /proc/{pid}/stat file and return a dict with various process info. Using "man proc" as a reference: where "man proc" refers to - position N always substract 3 (e.g ppid position 4 in + position N always subtract 3 (e.g ppid position 4 in 'man proc' == position 1 in here). The return value is cached in case oneshot() ctx manager is in use. """ - with open_binary("%s/%s/stat" % (self._procfs_path, self.pid)) as f: - data = f.read() + data = bcat("%s/%s/stat" % (self._procfs_path, self.pid)) # Process name is between parentheses. It can contain spaces and # other parentheses. This is taken into account by looking for - # the first occurrence of "(" and the last occurence of ")". + # the first occurrence of "(" and the last occurrence of ")". rpar = data.rfind(b')') - name = data[data.find(b'(') + 1:rpar] - fields = data[rpar + 2:].split() + name = data[data.find(b'(') + 1 : rpar] + fields = data[rpar + 2 :].split() ret = {} ret['name'] = name @@ -1655,8 +1813,7 @@ class Process(object): @wrap_exceptions @memoize_when_activated def _read_smaps_file(self): - with open_binary("%s/%s/smaps" % (self._procfs_path, self.pid), - buffering=BIGFILE_BUFFERING) as f: + with open_binary("%s/%s/smaps" % (self._procfs_path, self.pid)) as f: return f.read().strip() def oneshot_enter(self): @@ -1677,22 +1834,18 @@ class Process(object): # XXX - gets changed later and probably needs refactoring return name + @wrap_exceptions def exe(self): try: return readlink("%s/%s/exe" % (self._procfs_path, self.pid)) except (FileNotFoundError, ProcessLookupError): + self._raise_if_zombie() # no such file error; might be raised also if the # path actually exists for system processes with # low pids (about 0-20) if os.path.lexists("%s/%s" % (self._procfs_path, self.pid)): return "" - else: - if not pid_exists(self.pid): - raise NoSuchProcess(self.pid, self._name) - else: - raise ZombieProcess(self.pid, self._name, self._ppid) - except PermissionError: - raise AccessDenied(self.pid, self._name) + raise @wrap_exceptions def cmdline(self): @@ -1700,6 +1853,7 @@ class Process(object): data = f.read() if not data: # may happen in case of zombie process + self._raise_if_zombie() return [] # 'man proc' states that args are separated by null bytes '\0' # and last char is supposed to be a null byte. Nevertheless @@ -1736,6 +1890,7 @@ class Process(object): # May not be available on old kernels. if os.path.exists('/proc/%s/io' % os.getpid()): + @wrap_exceptions def io_counters(self): fname = "%s/%s/io" % (self._procfs_path, self.pid) @@ -1764,8 +1919,10 @@ class Process(object): fields[b'wchar'], # write chars ) except KeyError as err: - raise ValueError("%r field was not found in %s; found fields " - "are %r" % (err[0], fname, fields)) + raise ValueError( + "%r field was not found in %s; found fields are %r" + % (err.args[0], fname, fields) + ) @wrap_exceptions def cpu_times(self): @@ -1811,22 +1968,47 @@ class Process(object): # | dirty | dirty pages (unused in Linux 2.6) | dt | | # ============================================================ with open_binary("%s/%s/statm" % (self._procfs_path, self.pid)) as f: - vms, rss, shared, text, lib, data, dirty = \ - [int(x) * PAGESIZE for x in f.readline().split()[:7]] + vms, rss, shared, text, lib, data, dirty = ( + int(x) * PAGESIZE for x in f.readline().split()[:7] + ) return pmem(rss, vms, shared, text, lib, data, dirty) - # /proc/pid/smaps does not exist on kernels < 2.6.14 or if - # CONFIG_MMU kernel configuration option is not enabled. - if HAS_SMAPS: + if HAS_PROC_SMAPS_ROLLUP or HAS_PROC_SMAPS: + + def _parse_smaps_rollup(self): + # /proc/pid/smaps_rollup was added to Linux in 2017. Faster + # than /proc/pid/smaps. It reports higher PSS than */smaps + # (from 1k up to 200k higher; tested against all processes). + # IMPORTANT: /proc/pid/smaps_rollup is weird, because it + # raises ESRCH / ENOENT for many PIDs, even if they're alive + # (also as root). In that case we'll use /proc/pid/smaps as + # fallback, which is slower but has a +50% success rate + # compared to /proc/pid/smaps_rollup. + uss = pss = swap = 0 + with open_binary( + "{}/{}/smaps_rollup".format(self._procfs_path, self.pid) + ) as f: + for line in f: + if line.startswith(b"Private_"): + # Private_Clean, Private_Dirty, Private_Hugetlb + uss += int(line.split()[1]) * 1024 + elif line.startswith(b"Pss:"): + pss = int(line.split()[1]) * 1024 + elif line.startswith(b"Swap:"): + swap = int(line.split()[1]) * 1024 + return (uss, pss, swap) @wrap_exceptions - def memory_full_info( - self, - # Gets Private_Clean, Private_Dirty, Private_Hugetlb. - _private_re=re.compile(br"\nPrivate.*:\s+(\d+)"), - _pss_re=re.compile(br"\nPss\:\s+(\d+)"), - _swap_re=re.compile(br"\nSwap\:\s+(\d+)")): - basic_mem = self.memory_info() + def _parse_smaps( + self, + # Gets Private_Clean, Private_Dirty, Private_Hugetlb. + _private_re=re.compile(br"\nPrivate.*:\s+(\d+)"), + _pss_re=re.compile(br"\nPss\:\s+(\d+)"), + _swap_re=re.compile(br"\nSwap\:\s+(\d+)"), + ): + # /proc/pid/smaps does not exist on kernels < 2.6.14 or if + # CONFIG_MMU kernel configuration option is not enabled. + # Note: using 3 regexes is faster than reading the file # line by line. # XXX: on Python 3 the 2 regexes are 30% slower than on @@ -1845,22 +2027,35 @@ class Process(object): uss = sum(map(int, _private_re.findall(smaps_data))) * 1024 pss = sum(map(int, _pss_re.findall(smaps_data))) * 1024 swap = sum(map(int, _swap_re.findall(smaps_data))) * 1024 + return (uss, pss, swap) + + @wrap_exceptions + def memory_full_info(self): + if HAS_PROC_SMAPS_ROLLUP: # faster + try: + uss, pss, swap = self._parse_smaps_rollup() + except (ProcessLookupError, FileNotFoundError): + uss, pss, swap = self._parse_smaps() + else: + uss, pss, swap = self._parse_smaps() + basic_mem = self.memory_info() return pfullmem(*basic_mem + (uss, pss, swap)) else: memory_full_info = memory_info - if HAS_SMAPS: + if HAS_PROC_SMAPS: @wrap_exceptions def memory_maps(self): """Return process's mapped memory regions as a list of named tuples. Fields are explained in 'man proc'; here is an updated - (Apr 2012) version: http://goo.gl/fmebo + (Apr 2012) version: http://goo.gl/fmebo. /proc/{PID}/smaps does not exist on kernels < 2.6.14 or if CONFIG_MMU kernel configuration option is not enabled. """ + def get_blocks(lines, current_block): data = {} for line in lines: @@ -1877,13 +2072,17 @@ class Process(object): # see issue #369 continue else: - raise ValueError("don't know how to inte" - "rpret line %r" % line) + raise ValueError( + "don't know how to interpret line %r" + % line + ) yield (current_block.pop(), data) data = self._read_smaps_file() - # Note: smaps file can be empty for certain processes. + # Note: smaps file can be empty for certain processes or for + # zombies. if not data: + self._raise_if_zombie() return [] lines = data.split(b'\n') ls = [] @@ -1894,19 +2093,21 @@ class Process(object): try: addr, perms, offset, dev, inode, path = hfields except ValueError: - addr, perms, offset, dev, inode, path = \ - hfields + [''] + addr, perms, offset, dev, inode, path = hfields + [''] if not path: path = '[anon]' else: if PY3: path = decode(path) path = path.strip() - if (path.endswith(' (deleted)') and not - path_exists_strict(path)): + if path.endswith(' (deleted)') and not path_exists_strict( + path + ): path = path[:-10] ls.append(( - decode(addr), decode(perms), path, + decode(addr), + decode(perms), + path, data.get(b'Rss:', 0), data.get(b'Size:', 0), data.get(b'Pss:', 0), @@ -1916,32 +2117,26 @@ class Process(object): data.get(b'Private_Dirty:', 0), data.get(b'Referenced:', 0), data.get(b'Anonymous:', 0), - data.get(b'Swap:', 0) + data.get(b'Swap:', 0), )) return ls @wrap_exceptions def cwd(self): - try: - return readlink("%s/%s/cwd" % (self._procfs_path, self.pid)) - except (FileNotFoundError, ProcessLookupError): - # https://github.com/giampaolo/psutil/issues/986 - if not pid_exists(self.pid): - raise NoSuchProcess(self.pid, self._name) - else: - raise ZombieProcess(self.pid, self._name, self._ppid) + return readlink("%s/%s/cwd" % (self._procfs_path, self.pid)) @wrap_exceptions - def num_ctx_switches(self, - _ctxsw_re=re.compile(br'ctxt_switches:\t(\d+)')): + def num_ctx_switches( + self, _ctxsw_re=re.compile(br'ctxt_switches:\t(\d+)') + ): data = self._read_status_file() ctxsw = _ctxsw_re.findall(data) if not ctxsw: raise NotImplementedError( "'voluntary_ctxt_switches' and 'nonvoluntary_ctxt_switches'" "lines were not found in %s/%s/status; the kernel is " - "probably older than 2.6.23" % ( - self._procfs_path, self.pid)) + "probably older than 2.6.23" % (self._procfs_path, self.pid) + ) else: return _common.pctxsw(int(ctxsw[0]), int(ctxsw[1])) @@ -1961,24 +2156,27 @@ class Process(object): hit_enoent = False for thread_id in thread_ids: fname = "%s/%s/task/%s/stat" % ( - self._procfs_path, self.pid, thread_id) + self._procfs_path, + self.pid, + thread_id, + ) try: with open_binary(fname) as f: st = f.read().strip() - except FileNotFoundError: - # no such file or directory; it means thread - # disappeared on us + except (FileNotFoundError, ProcessLookupError): + # no such file or directory or no such process; + # it means thread disappeared on us hit_enoent = True continue # ignore the first two values ("pid (exe)") - st = st[st.find(b')') + 2:] + st = st[st.find(b')') + 2 :] values = st.split(b' ') utime = float(values[11]) / CLOCK_TICKS stime = float(values[12]) / CLOCK_TICKS ntuple = _common.pthread(int(thread_id), utime, stime) retlist.append(ntuple) if hit_enoent: - self._assert_alive() + self._raise_if_not_alive() return retlist @wrap_exceptions @@ -2002,7 +2200,8 @@ class Process(object): return cext.proc_cpu_affinity_get(self.pid) def _get_eligible_cpus( - self, _re=re.compile(br"Cpus_allowed_list:\t(\d+)-(\d+)")): + self, _re=re.compile(br"Cpus_allowed_list:\t(\d+)-(\d+)") + ): # See: https://github.com/giampaolo/psutil/issues/956 data = self._read_status_file() match = _re.findall(data) @@ -2022,12 +2221,14 @@ class Process(object): for cpu in cpus: if cpu not in all_cpus: raise ValueError( - "invalid CPU number %r; choose between %s" % ( - cpu, eligible_cpus)) + "invalid CPU number %r; choose between %s" + % (cpu, eligible_cpus) + ) if cpu not in eligible_cpus: raise ValueError( "CPU number %r is not eligible; choose " - "between %s" % (cpu, eligible_cpus)) + "between %s" % (cpu, eligible_cpus) + ) raise # only starting from kernel 2.6.13 @@ -2047,7 +2248,8 @@ class Process(object): if value and ioclass in (IOPRIO_CLASS_IDLE, IOPRIO_CLASS_NONE): raise ValueError("%r ioclass accepts no value" % ioclass) if value < 0 or value > 7: - raise ValueError("value not in 0-7 range") + msg = "value not in 0-7 range" + raise ValueError(msg) return cext.proc_ioprio_set(self.pid, ioclass, value) if prlimit is not None: @@ -2058,7 +2260,8 @@ class Process(object): # we don't want that. We should never get here though as # PID 0 is not supported on Linux. if self.pid == 0: - raise ValueError("can't use prlimit() against PID 0 process") + msg = "can't use prlimit() against PID 0 process" + raise ValueError(msg) try: if limits is None: # get @@ -2066,17 +2269,18 @@ class Process(object): else: # set if len(limits) != 2: - raise ValueError( - "second argument must be a (soft, hard) tuple, " - "got %s" % repr(limits)) + msg = ( + "second argument must be a (soft, hard) " + + "tuple, got %s" % repr(limits) + ) + raise ValueError(msg) prlimit(self.pid, resource_, limits) except OSError as err: - if err.errno == errno.ENOSYS and pid_exists(self.pid): + if err.errno == errno.ENOSYS: # I saw this happening on Travis: # https://travis-ci.org/giampaolo/psutil/jobs/51368273 - raise ZombieProcess(self.pid, self._name, self._ppid) - else: - raise + self._raise_if_zombie() + raise @wrap_exceptions def status(self): @@ -2103,6 +2307,10 @@ class Process(object): if err.errno == errno.EINVAL: # not a link continue + if err.errno == errno.ENAMETOOLONG: + # file name too long + debug(err) + continue raise else: # If path is not an absolute there's no way to tell @@ -2112,28 +2320,32 @@ class Process(object): if path.startswith('/') and isfile_strict(path): # Get file position and flags. file = "%s/%s/fdinfo/%s" % ( - self._procfs_path, self.pid, fd) + self._procfs_path, + self.pid, + fd, + ) try: with open_binary(file) as f: pos = int(f.readline().split()[1]) flags = int(f.readline().split()[1], 8) - except FileNotFoundError: + except (FileNotFoundError, ProcessLookupError): # fd gone in the meantime; process may # still be alive hit_enoent = True else: mode = file_flags_to_mode(flags) ntuple = popenfile( - path, int(fd), int(pos), mode, flags) + path, int(fd), int(pos), mode, flags + ) retlist.append(ntuple) if hit_enoent: - self._assert_alive() + self._raise_if_not_alive() return retlist @wrap_exceptions def connections(self, kind='inet'): ret = _connections.retrieve(kind, self.pid) - self._assert_alive() + self._raise_if_not_alive() return ret @wrap_exceptions diff --git a/contrib/python/psutil/py3/psutil/_psosx.py b/contrib/python/psutil/py3/psutil/_psosx.py index c7770d6591..673ac0db75 100644 --- a/contrib/python/psutil/py3/psutil/_psosx.py +++ b/contrib/python/psutil/py3/psutil/_psosx.py @@ -4,7 +4,6 @@ """macOS platform implementation.""" -import contextlib import errno import functools import os @@ -15,14 +14,14 @@ from . import _psposix from . import _psutil_osx as cext from . import _psutil_posix as cext_posix from ._common import AccessDenied +from ._common import NoSuchProcess +from ._common import ZombieProcess from ._common import conn_tmap from ._common import conn_to_ntuple from ._common import isfile_strict from ._common import memoize_when_activated -from ._common import NoSuchProcess from ._common import parse_environ_block from ._common import usage_percent -from ._common import ZombieProcess from ._compat import PermissionError from ._compat import ProcessLookupError @@ -92,6 +91,7 @@ pidtaskinfo_map = dict( # ===================================================================== +# fmt: off # psutil.cpu_times() scputimes = namedtuple('scputimes', ['user', 'nice', 'system', 'idle']) # psutil.virtual_memory() @@ -102,6 +102,7 @@ svmem = namedtuple( pmem = namedtuple('pmem', ['rss', 'vms', 'pfaults', 'pageins']) # psutil.Process.memory_full_info() pfullmem = namedtuple('pfullmem', pmem._fields + ('uss', )) +# fmt: on # ===================================================================== @@ -122,8 +123,7 @@ def virtual_memory(): # cmdline utility. free -= speculative percent = usage_percent((total - avail), total, round_=1) - return svmem(total, avail, percent, used, free, - active, inactive, wired) + return svmem(total, avail, percent, used, free, active, inactive, wired) def swap_memory(): @@ -145,7 +145,7 @@ def cpu_times(): def per_cpu_times(): - """Return system CPU times as a named tuple""" + """Return system CPU times as a named tuple.""" ret = [] for cpu_t in cext.per_cpu_times(): user, nice, system, idle = cpu_t @@ -159,23 +159,25 @@ def cpu_count_logical(): return cext.cpu_count_logical() -def cpu_count_physical(): - """Return the number of physical CPUs in the system.""" - return cext.cpu_count_phys() +def cpu_count_cores(): + """Return the number of CPU cores in the system.""" + return cext.cpu_count_cores() def cpu_stats(): - ctx_switches, interrupts, soft_interrupts, syscalls, traps = \ + ctx_switches, interrupts, soft_interrupts, syscalls, traps = ( cext.cpu_stats() + ) return _common.scpustats( - ctx_switches, interrupts, soft_interrupts, syscalls) + ctx_switches, interrupts, soft_interrupts, syscalls + ) def cpu_freq(): """Return CPU frequency. On macOS per-cpu frequency is not supported. Also, the returned frequency never changes, see: - https://arstechnica.com/civis/viewtopic.php?f=19&t=465002 + https://arstechnica.com/civis/viewtopic.php?f=19&t=465002. """ curr, min_, max_ = cext.cpu_freq() return [_common.scpufreq(curr, min_, max_)] @@ -202,8 +204,9 @@ def disk_partitions(all=False): if not os.path.isabs(device) or not os.path.exists(device): continue maxfile = maxpath = None # set later - ntuple = _common.sdiskpart(device, mountpoint, fstype, opts, - maxfile, maxpath) + ntuple = _common.sdiskpart( + device, mountpoint, fstype, opts, maxfile, maxpath + ) retlist.append(ntuple) return retlist @@ -264,7 +267,7 @@ def net_if_stats(): for name in names: try: mtu = cext_posix.net_if_mtu(name) - isup = cext_posix.net_if_is_running(name) + flags = cext_posix.net_if_flags(name) duplex, speed = cext_posix.net_if_duplex_speed(name) except OSError as err: # https://github.com/giampaolo/psutil/issues/1279 @@ -273,7 +276,11 @@ def net_if_stats(): else: if hasattr(_common, 'NicDuplex'): duplex = _common.NicDuplex(duplex) - ret[name] = _common.snicstats(isup, duplex, speed, mtu) + output_flags = ','.join(flags) + isup = 'running' in flags + ret[name] = _common.snicstats( + isup, duplex, speed, mtu, output_flags + ) return ret @@ -330,7 +337,7 @@ def is_zombie(pid): try: st = cext.proc_kinfo_oneshot(pid)[kinfo_proc_map['status']] return st == cext.SZOMB - except Exception: + except OSError: return False @@ -338,6 +345,7 @@ def wrap_exceptions(fun): """Decorator which translates bare OSError exceptions into NoSuchProcess and AccessDenied. """ + @functools.wraps(fun) def wrapper(self, *args, **kwargs): try: @@ -349,38 +357,11 @@ def wrap_exceptions(fun): raise NoSuchProcess(self.pid, self._name) except PermissionError: raise AccessDenied(self.pid, self._name) - except cext.ZombieProcessError: - raise ZombieProcess(self.pid, self._name, self._ppid) - return wrapper - -@contextlib.contextmanager -def catch_zombie(proc): - """There are some poor C APIs which incorrectly raise ESRCH when - the process is still alive or it's a zombie, or even RuntimeError - (those who don't set errno). This is here in order to solve: - https://github.com/giampaolo/psutil/issues/1044 - """ - try: - yield - except (OSError, RuntimeError) as err: - if isinstance(err, RuntimeError) or err.errno == errno.ESRCH: - try: - # status() is not supposed to lie and correctly detect - # zombies so if it raises ESRCH it's true. - status = proc.status() - except NoSuchProcess: - raise err - else: - if status == _common.STATUS_ZOMBIE: - raise ZombieProcess(proc.pid, proc._name, proc._ppid) - else: - raise AccessDenied(proc.pid, proc._name) - else: - raise + return wrapper -class Process(object): +class Process: """Wrapper class around underlying C implementation.""" __slots__ = ["pid", "_name", "_ppid", "_cache"] @@ -402,8 +383,7 @@ class Process(object): @memoize_when_activated def _get_pidtaskinfo(self): # Note: should work for PIDs owned by user only. - with catch_zombie(self): - ret = cext.proc_pidtaskinfo_oneshot(self.pid) + ret = cext.proc_pidtaskinfo_oneshot(self.pid) assert len(ret) == len(pidtaskinfo_map) return ret @@ -422,18 +402,15 @@ class Process(object): @wrap_exceptions def exe(self): - with catch_zombie(self): - return cext.proc_exe(self.pid) + return cext.proc_exe(self.pid) @wrap_exceptions def cmdline(self): - with catch_zombie(self): - return cext.proc_cmdline(self.pid) + return cext.proc_cmdline(self.pid) @wrap_exceptions def environ(self): - with catch_zombie(self): - return parse_environ_block(cext.proc_environ(self.pid)) + return parse_environ_block(cext.proc_environ(self.pid)) @wrap_exceptions def ppid(self): @@ -442,8 +419,7 @@ class Process(object): @wrap_exceptions def cwd(self): - with catch_zombie(self): - return cext.proc_cwd(self.pid) + return cext.proc_cwd(self.pid) @wrap_exceptions def uids(self): @@ -451,7 +427,8 @@ class Process(object): return _common.puids( rawtuple[kinfo_proc_map['ruid']], rawtuple[kinfo_proc_map['euid']], - rawtuple[kinfo_proc_map['suid']]) + rawtuple[kinfo_proc_map['suid']], + ) @wrap_exceptions def gids(self): @@ -459,7 +436,8 @@ class Process(object): return _common.puids( rawtuple[kinfo_proc_map['rgid']], rawtuple[kinfo_proc_map['egid']], - rawtuple[kinfo_proc_map['sgid']]) + rawtuple[kinfo_proc_map['sgid']], + ) @wrap_exceptions def terminal(self): @@ -484,7 +462,7 @@ class Process(object): def memory_full_info(self): basic_mem = self.memory_info() uss = cext.proc_memory_uss(self.pid) - return pfullmem(*basic_mem + (uss, )) + return pfullmem(*basic_mem + (uss,)) @wrap_exceptions def cpu_times(self): @@ -493,7 +471,9 @@ class Process(object): rawtuple[pidtaskinfo_map['cpuutime']], rawtuple[pidtaskinfo_map['cpustime']], # children user / system times are not retrievable (set to 0) - 0.0, 0.0) + 0.0, + 0.0, + ) @wrap_exceptions def create_time(self): @@ -516,8 +496,7 @@ class Process(object): if self.pid == 0: return [] files = [] - with catch_zombie(self): - rawlist = cext.proc_open_files(self.pid) + rawlist = cext.proc_open_files(self.pid) for path, fd in rawlist: if isfile_strict(path): ntuple = _common.popenfile(path, fd) @@ -527,16 +506,18 @@ class Process(object): @wrap_exceptions def connections(self, kind='inet'): if kind not in conn_tmap: - raise ValueError("invalid %r kind argument; choose between %s" - % (kind, ', '.join([repr(x) for x in conn_tmap]))) + raise ValueError( + "invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in conn_tmap])) + ) families, types = conn_tmap[kind] - with catch_zombie(self): - rawlist = cext.proc_connections(self.pid, families, types) + rawlist = cext.proc_connections(self.pid, families, types) ret = [] for item in rawlist: fd, fam, type, laddr, raddr, status = item - nt = conn_to_ntuple(fd, fam, type, laddr, raddr, status, - TCP_STATUSES) + nt = conn_to_ntuple( + fd, fam, type, laddr, raddr, status, TCP_STATUSES + ) ret.append(nt) return ret @@ -544,8 +525,7 @@ class Process(object): def num_fds(self): if self.pid == 0: return 0 - with catch_zombie(self): - return cext.proc_num_fds(self.pid) + return cext.proc_num_fds(self.pid) @wrap_exceptions def wait(self, timeout=None): @@ -553,13 +533,11 @@ class Process(object): @wrap_exceptions def nice_get(self): - with catch_zombie(self): - return cext_posix.getpriority(self.pid) + return cext_posix.getpriority(self.pid) @wrap_exceptions def nice_set(self, value): - with catch_zombie(self): - return cext_posix.setpriority(self.pid, value) + return cext_posix.setpriority(self.pid, value) @wrap_exceptions def status(self): diff --git a/contrib/python/psutil/py3/psutil/_psposix.py b/contrib/python/psutil/py3/psutil/_psposix.py index 706dab9ae8..42bdfa7ef6 100644 --- a/contrib/python/psutil/py3/psutil/_psposix.py +++ b/contrib/python/psutil/py3/psutil/_psposix.py @@ -10,19 +10,25 @@ import signal import sys import time +from ._common import MACOS +from ._common import TimeoutExpired from ._common import memoize from ._common import sdiskusage -from ._common import TimeoutExpired from ._common import usage_percent +from ._compat import PY3 from ._compat import ChildProcessError from ._compat import FileNotFoundError from ._compat import InterruptedError from ._compat import PermissionError from ._compat import ProcessLookupError -from ._compat import PY3 from ._compat import unicode -if sys.version_info >= (3, 4): + +if MACOS: + from . import _psutil_osx + + +if PY3: import enum else: enum = None @@ -57,7 +63,8 @@ def pid_exists(pid): # https://bugs.python.org/issue21076 if enum is not None and hasattr(signal, "Signals"): Negsignal = enum.IntEnum( - 'Negsignal', dict([(x.name, -x.value) for x in signal.Signals])) + 'Negsignal', dict([(x.name, -x.value) for x in signal.Signals]) + ) def negsig_to_enum(num): """Convert a negative signal value to an enum.""" @@ -65,17 +72,23 @@ if enum is not None and hasattr(signal, "Signals"): return Negsignal(num) except ValueError: return num + else: # pragma: no cover + def negsig_to_enum(num): return num -def wait_pid(pid, timeout=None, proc_name=None, - _waitpid=os.waitpid, - _timer=getattr(time, 'monotonic', time.time), - _min=min, - _sleep=time.sleep, - _pid_exists=pid_exists): +def wait_pid( + pid, + timeout=None, + proc_name=None, + _waitpid=os.waitpid, + _timer=getattr(time, 'monotonic', time.time), # noqa: B008 + _min=min, + _sleep=time.sleep, + _pid_exists=pid_exists, +): """Wait for a process PID to terminate. If the process terminated normally by calling exit(3) or _exit(2), @@ -94,7 +107,9 @@ def wait_pid(pid, timeout=None, proc_name=None, timeout=0 is also possible (either return immediately or raise). """ if pid <= 0: - raise ValueError("can't wait for PID 0") # see "man waitpid" + # see "man waitpid" + msg = "can't wait for PID 0" + raise ValueError(msg) interval = 0.0001 flags = 0 if timeout is not None: @@ -130,7 +145,8 @@ def wait_pid(pid, timeout=None, proc_name=None, # WNOHANG flag was used and PID is still running. interval = sleep(interval) continue - elif os.WIFEXITED(status): + + if os.WIFEXITED(status): # Process terminated normally by calling exit(3) or _exit(2), # or by returning from main(). The return value is the # positive integer passed to *exit(). @@ -185,13 +201,16 @@ def disk_usage(path): # Total space which is only available to root (unless changed # at system level). - total = (st.f_blocks * st.f_frsize) + total = st.f_blocks * st.f_frsize # Remaining free space usable by root. - avail_to_root = (st.f_bfree * st.f_frsize) + avail_to_root = st.f_bfree * st.f_frsize # Remaining free space usable by user. - avail_to_user = (st.f_bavail * st.f_frsize) + avail_to_user = st.f_bavail * st.f_frsize # Total space being used in general. - used = (total - avail_to_root) + used = total - avail_to_root + if MACOS: + # see: https://github.com/giampaolo/psutil/pull/2152 + used = _psutil_osx.disk_usage_used(path, used) # Total space which is available to user (same as 'total' but # for the user). total_user = used + avail_to_user @@ -204,13 +223,14 @@ def disk_usage(path): # reserved blocks that we are currently not considering: # https://github.com/giampaolo/psutil/issues/829#issuecomment-223750462 return sdiskusage( - total=total, used=used, free=avail_to_user, percent=usage_percent_user) + total=total, used=used, free=avail_to_user, percent=usage_percent_user + ) @memoize def get_terminal_map(): """Get a map of device-id -> path as a dict. - Used by Process.terminal() + Used by Process.terminal(). """ ret = {} ls = glob.glob('/dev/tty*') + glob.glob('/dev/pts/*') diff --git a/contrib/python/psutil/py3/psutil/_pssunos.py b/contrib/python/psutil/py3/psutil/_pssunos.py index 5618bd4460..dddbece1f3 100644 --- a/contrib/python/psutil/py3/psutil/_pssunos.py +++ b/contrib/python/psutil/py3/psutil/_pssunos.py @@ -17,22 +17,22 @@ from . import _common from . import _psposix from . import _psutil_posix as cext_posix from . import _psutil_sunos as cext -from ._common import AccessDenied from ._common import AF_INET6 +from ._common import AccessDenied +from ._common import NoSuchProcess +from ._common import ZombieProcess from ._common import debug from ._common import get_procfs_path from ._common import isfile_strict from ._common import memoize_when_activated -from ._common import NoSuchProcess from ._common import sockfam_to_enum from ._common import socktype_to_enum from ._common import usage_percent -from ._common import ZombieProcess -from ._compat import b +from ._compat import PY3 from ._compat import FileNotFoundError from ._compat import PermissionError from ._compat import ProcessLookupError -from ._compat import PY3 +from ._compat import b __extra__all__ = ["CONN_IDLE", "CONN_BOUND", "PROCFS_PATH"] @@ -89,7 +89,8 @@ proc_info_map = dict( uid=8, euid=9, gid=10, - egid=11) + egid=11, +) # ===================================================================== @@ -100,19 +101,22 @@ proc_info_map = dict( # psutil.cpu_times() scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'iowait']) # psutil.cpu_times(percpu=True) -pcputimes = namedtuple('pcputimes', - ['user', 'system', 'children_user', 'children_system']) +pcputimes = namedtuple( + 'pcputimes', ['user', 'system', 'children_user', 'children_system'] +) # psutil.virtual_memory() svmem = namedtuple('svmem', ['total', 'available', 'percent', 'used', 'free']) # psutil.Process.memory_info() pmem = namedtuple('pmem', ['rss', 'vms']) pfullmem = pmem # psutil.Process.memory_maps(grouped=True) -pmmap_grouped = namedtuple('pmmap_grouped', - ['path', 'rss', 'anonymous', 'locked']) +pmmap_grouped = namedtuple( + 'pmmap_grouped', ['path', 'rss', 'anonymous', 'locked'] +) # psutil.Process.memory_maps(grouped=False) pmmap_ext = namedtuple( - 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields)) + 'pmmap_ext', 'addr perms ' + ' '.join(pmmap_grouped._fields) +) # ===================================================================== @@ -140,10 +144,16 @@ def swap_memory(): # usr/src/cmd/swap/swap.c # ...nevertheless I can't manage to obtain the same numbers as 'swap' # cmdline utility, so let's parse its output (sigh!) - p = subprocess.Popen(['/usr/bin/env', 'PATH=/usr/sbin:/sbin:%s' % - os.environ['PATH'], 'swap', '-l'], - stdout=subprocess.PIPE) - stdout, stderr = p.communicate() + p = subprocess.Popen( + [ + '/usr/bin/env', + 'PATH=/usr/sbin:/sbin:%s' % os.environ['PATH'], + 'swap', + '-l', + ], + stdout=subprocess.PIPE, + ) + stdout, _ = p.communicate() if PY3: stdout = stdout.decode(sys.stdout.encoding) if p.returncode != 0: @@ -151,17 +161,19 @@ def swap_memory(): lines = stdout.strip().split('\n')[1:] if not lines: - raise RuntimeError('no swap device(s) configured') + msg = 'no swap device(s) configured' + raise RuntimeError(msg) total = free = 0 for line in lines: line = line.split() - t, f = line[3:4] + t, f = line[3:5] total += int(int(t) * 512) free += int(int(f) * 512) used = total - free percent = usage_percent(used, total, round_=1) - return _common.sswap(total, used, free, percent, - sin * PAGE_SIZE, sout * PAGE_SIZE) + return _common.sswap( + total, used, free, percent, sin * PAGE_SIZE, sout * PAGE_SIZE + ) # ===================================================================== @@ -170,13 +182,13 @@ def swap_memory(): def cpu_times(): - """Return system-wide CPU times as a named tuple""" + """Return system-wide CPU times as a named tuple.""" ret = cext.per_cpu_times() return scputimes(*[sum(x) for x in zip(*ret)]) def per_cpu_times(): - """Return system per-CPU times as a list of named tuples""" + """Return system per-CPU times as a list of named tuples.""" ret = cext.per_cpu_times() return [scputimes(*x) for x in ret] @@ -190,17 +202,18 @@ def cpu_count_logical(): return None -def cpu_count_physical(): - """Return the number of physical CPUs in the system.""" - return cext.cpu_count_phys() +def cpu_count_cores(): + """Return the number of CPU cores in the system.""" + return cext.cpu_count_cores() def cpu_stats(): """Return various CPU stats as a named tuple.""" ctx_switches, interrupts, syscalls, traps = cext.cpu_stats() soft_interrupts = 0 - return _common.scpustats(ctx_switches, interrupts, soft_interrupts, - syscalls) + return _common.scpustats( + ctx_switches, interrupts, soft_interrupts, syscalls + ) # ===================================================================== @@ -231,11 +244,12 @@ def disk_partitions(all=False): continue except OSError as err: # https://github.com/giampaolo/psutil/issues/1674 - debug("skipping %r: %r" % (mountpoint, err)) + debug("skipping %r: %s" % (mountpoint, err)) continue maxfile = maxpath = None # set later - ntuple = _common.sdiskpart(device, mountpoint, fstype, opts, - maxfile, maxpath) + ntuple = _common.sdiskpart( + device, mountpoint, fstype, opts, maxfile, maxpath + ) retlist.append(ntuple) return retlist @@ -258,8 +272,10 @@ def net_connections(kind, _pid=-1): if _pid == -1: cmap.pop('unix', 0) if kind not in cmap: - raise ValueError("invalid %r kind argument; choose between %s" - % (kind, ', '.join([repr(x) for x in cmap]))) + raise ValueError( + "invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in cmap])) + ) families, types = _common.conn_tmap[kind] rawlist = cext.net_connections(_pid) ret = set() @@ -293,7 +309,7 @@ def net_if_stats(): isup, duplex, speed, mtu = items if hasattr(_common, 'NicDuplex'): duplex = _common.NicDuplex(duplex) - ret[name] = _common.snicstats(isup, duplex, speed, mtu) + ret[name] = _common.snicstats(isup, duplex, speed, mtu, '') return ret @@ -345,6 +361,7 @@ def wrap_exceptions(fun): """Call callable into a try/except clause and translate ENOENT, EACCES and EPERM in NoSuchProcess or AccessDenied exceptions. """ + @functools.wraps(fun) def wrapper(self, *args, **kwargs): try: @@ -366,10 +383,11 @@ def wrap_exceptions(fun): else: raise raise + return wrapper -class Process(object): +class Process: """Wrapper class around underlying C implementation.""" __slots__ = ["pid", "_name", "_ppid", "_procfs_path", "_cache"] @@ -404,8 +422,9 @@ class Process(object): @wrap_exceptions @memoize_when_activated def _proc_basic_info(self): - if self.pid == 0 and not \ - os.path.exists('%s/%s/psinfo' % (self._procfs_path, self.pid)): + if self.pid == 0 and not os.path.exists( + '%s/%s/psinfo' % (self._procfs_path, self.pid) + ): raise AccessDenied(self.pid) ret = cext.proc_basic_info(self.pid, self._procfs_path) assert len(ret) == len(proc_info_map) @@ -425,9 +444,10 @@ class Process(object): def exe(self): try: return os.readlink( - "%s/%s/path/a.out" % (self._procfs_path, self.pid)) + "%s/%s/path/a.out" % (self._procfs_path, self.pid) + ) except OSError: - pass # continue and guess the exe name from the cmdline + pass # continue and guess the exe name from the cmdline # Will be guessed later from cmdline but we want to explicitly # invoke cmdline here in order to get an AccessDenied # exception if the user has not enough privileges. @@ -518,13 +538,13 @@ class Process(object): def terminal(self): procfs_path = self._procfs_path hit_enoent = False - tty = wrap_exceptions( - self._proc_basic_info()[proc_info_map['ttynr']]) + tty = wrap_exceptions(self._proc_basic_info()[proc_info_map['ttynr']]) if tty != cext.PRNODEV: for x in (0, 1, 2, 255): try: return os.readlink( - '%s/%d/path/%d' % (procfs_path, self.pid, x)) + '%s/%d/path/%d' % (procfs_path, self.pid, x) + ) except FileNotFoundError: hit_enoent = True continue @@ -542,7 +562,7 @@ class Process(object): return os.readlink("%s/%s/path/cwd" % (procfs_path, self.pid)) except FileNotFoundError: os.stat("%s/%s" % (procfs_path, self.pid)) # raise NSP or AD - return None + return "" @wrap_exceptions def memory_info(self): @@ -569,7 +589,8 @@ class Process(object): tid = int(tid) try: utime, stime = cext.query_process_thread( - self.pid, tid, procfs_path) + self.pid, tid, procfs_path + ) except EnvironmentError as err: if err.errno == errno.EOVERFLOW and not IS_64_BIT: # We may get here if we attempt to query a 64bit process @@ -617,13 +638,15 @@ class Process(object): """Get UNIX sockets used by process by parsing 'pfiles' output.""" # TODO: rewrite this in C (...but the damn netstat source code # does not include this part! Argh!!) - cmd = "pfiles %s" % pid - p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + cmd = ["pfiles", str(pid)] + p = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) stdout, stderr = p.communicate() if PY3: - stdout, stderr = [x.decode(sys.stdout.encoding) - for x in (stdout, stderr)] + stdout, stderr = ( + x.decode(sys.stdout.encoding) for x in (stdout, stderr) + ) if p.returncode != 0: if 'permission denied' in stderr.lower(): raise AccessDenied(self.pid, self._name) @@ -659,8 +682,10 @@ class Process(object): # UNIX sockets if kind in ('all', 'unix'): - ret.extend([_common.pconn(*conn) for conn in - self._get_unix_sockets(self.pid)]) + ret.extend([ + _common.pconn(*conn) + for conn in self._get_unix_sockets(self.pid) + ]) return ret nt_mmap_grouped = namedtuple('mmap', 'path rss anon locked') @@ -669,8 +694,10 @@ class Process(object): @wrap_exceptions def memory_maps(self): def toaddr(start, end): - return '%s-%s' % (hex(start)[2:].strip('L'), - hex(end)[2:].strip('L')) + return '%s-%s' % ( + hex(start)[2:].strip('L'), + hex(end)[2:].strip('L'), + ) procfs_path = self._procfs_path retlist = [] @@ -695,7 +722,8 @@ class Process(object): if not name.startswith('['): try: name = os.readlink( - '%s/%s/path/%s' % (procfs_path, self.pid, name)) + '%s/%s/path/%s' % (procfs_path, self.pid, name) + ) except OSError as err: if err.errno == errno.ENOENT: # sometimes the link may not be resolved by @@ -720,7 +748,8 @@ class Process(object): @wrap_exceptions def num_ctx_switches(self): return _common.pctxsw( - *cext.proc_num_ctx_switches(self.pid, self._procfs_path)) + *cext.proc_num_ctx_switches(self.pid, self._procfs_path) + ) @wrap_exceptions def wait(self, timeout=None): diff --git a/contrib/python/psutil/py3/psutil/_psutil_common.c b/contrib/python/psutil/py3/psutil/_psutil_common.c index f371167f3e..76b197917b 100644 --- a/contrib/python/psutil/py3/psutil/_psutil_common.c +++ b/contrib/python/psutil/py3/psutil/_psutil_common.c @@ -15,15 +15,13 @@ // ==================================================================== int PSUTIL_DEBUG = 0; -int PSUTIL_TESTING = 0; -// PSUTIL_CONN_NONE // ==================================================================== // --- Backward compatibility with missing Python.h APIs // ==================================================================== -// PyPy on Windows +// PyPy on Windows. Missing APIs added in PyPy 7.3.14. #if defined(PSUTIL_WINDOWS) && defined(PYPY_VERSION) #if !defined(PyErr_SetFromWindowsErrWithFilename) PyObject * @@ -61,6 +59,18 @@ error: #endif // !defined(PyErr_SetFromWindowsErrWithFilename) +#if !defined(PyErr_SetExcFromWindowsErrWithFilenameObject) +PyObject * +PyErr_SetExcFromWindowsErrWithFilenameObject(PyObject *type, + int ierr, + PyObject *filename) { + // Original function is too complex. Just raise OSError without + // filename. + return PyErr_SetFromWindowsErrWithFilename(ierr, NULL); +} +#endif // !defined(PyErr_SetExcFromWindowsErrWithFilenameObject) + + // PyPy 2.7 #if !defined(PyErr_SetFromWindowsErr) PyObject * @@ -84,8 +94,9 @@ PyErr_SetFromOSErrnoWithSyscall(const char *syscall) { char fullmsg[1024]; #ifdef PSUTIL_WINDOWS + DWORD dwLastError = GetLastError(); sprintf(fullmsg, "(originated from %s)", syscall); - PyErr_SetFromWindowsErrWithFilename(GetLastError(), fullmsg); + PyErr_SetFromWindowsErrWithFilename(dwLastError, fullmsg); #else PyObject *exc; sprintf(fullmsg, "%s (originated from %s)", strerror(errno), syscall); @@ -130,51 +141,47 @@ AccessDenied(const char *syscall) { return NULL; } - -// ==================================================================== -// --- Global utils -// ==================================================================== - /* - * Enable testing mode. This has the same effect as setting PSUTIL_TESTING - * env var. This dual method exists because updating os.environ on - * Windows has no effect. Called on unit tests setup. + * Raise OverflowError if Python int value overflowed when converting to pid_t. + * Raise ValueError if Python int value is negative. + * Otherwise, return None. */ PyObject * -psutil_set_testing(PyObject *self, PyObject *args) { - PSUTIL_TESTING = 1; - PSUTIL_DEBUG = 1; - Py_INCREF(Py_None); - return Py_None; -} - +psutil_check_pid_range(PyObject *self, PyObject *args) { +#ifdef PSUTIL_WINDOWS + DWORD pid; +#else + pid_t pid; +#endif -/* - * Print a debug message on stderr. No-op if PSUTIL_DEBUG env var is not set. - */ -void -psutil_debug(const char* format, ...) { - va_list argptr; - if (PSUTIL_DEBUG) { - va_start(argptr, format); - fprintf(stderr, "psutil-debug> "); - vfprintf(stderr, format, argptr); - fprintf(stderr, "\n"); - va_end(argptr); + if (!PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + if (pid < 0) { + PyErr_SetString(PyExc_ValueError, "pid must be a positive integer"); + return NULL; } + Py_RETURN_NONE; } +// Enable or disable PSUTIL_DEBUG messages. +PyObject * +psutil_set_debug(PyObject *self, PyObject *args) { + PyObject *value; + int x; -/* - * Called on module import on all platforms. - */ -int -psutil_setup(void) { - if (getenv("PSUTIL_DEBUG") != NULL) + if (!PyArg_ParseTuple(args, "O", &value)) + return NULL; + x = PyObject_IsTrue(value); + if (x < 0) { + return NULL; + } + else if (x == 0) { + PSUTIL_DEBUG = 0; + } + else { PSUTIL_DEBUG = 1; - if (getenv("PSUTIL_TESTING") != NULL) - PSUTIL_TESTING = 1; - return 0; + } + Py_RETURN_NONE; } @@ -197,6 +204,15 @@ convert_kvm_err(const char *syscall, char *errbuf) { } #endif +// ==================================================================== +// --- macOS +// ==================================================================== + +#ifdef PSUTIL_OSX +#include <mach/mach_time.h> + +struct mach_timebase_info PSUTIL_MACH_TIMEBASE_INFO; +#endif // ==================================================================== // --- Windows @@ -337,7 +353,7 @@ psutil_loadlibs() { // minimum requirement: Win 7 GetActiveProcessorCount = psutil_GetProcAddress( "kernel32", "GetActiveProcessorCount"); - // minumum requirement: Win 7 + // minimum requirement: Win 7 GetLogicalProcessorInformationEx = psutil_GetProcAddressFromLib( "kernel32", "GetLogicalProcessorInformationEx"); // minimum requirements: Windows Server Core @@ -380,18 +396,6 @@ psutil_set_winver() { } -int -psutil_load_globals() { - if (psutil_loadlibs() != 0) - return 1; - if (psutil_set_winver() != 0) - return 1; - GetSystemInfo(&PSUTIL_SYSTEM_INFO); - InitializeCriticalSection(&PSUTIL_CRITICAL_SECTION); - return 0; -} - - /* * Convert the hi and lo parts of a FILETIME structure or a LARGE_INTEGER * to a UNIX time. @@ -427,3 +431,24 @@ psutil_LargeIntegerToUnixTime(LARGE_INTEGER li) { (ULONGLONG)li.LowPart); } #endif // PSUTIL_WINDOWS + + +// Called on module import on all platforms. +int +psutil_setup(void) { + if (getenv("PSUTIL_DEBUG") != NULL) + PSUTIL_DEBUG = 1; + +#ifdef PSUTIL_WINDOWS + if (psutil_loadlibs() != 0) + return 1; + if (psutil_set_winver() != 0) + return 1; + GetSystemInfo(&PSUTIL_SYSTEM_INFO); + InitializeCriticalSection(&PSUTIL_CRITICAL_SECTION); +#endif +#ifdef PSUTIL_OSX + mach_timebase_info(&PSUTIL_MACH_TIMEBASE_INFO); +#endif + return 0; +} diff --git a/contrib/python/psutil/py3/psutil/_psutil_common.h b/contrib/python/psutil/py3/psutil/_psutil_common.h index cb0b399d8e..20c03ebd7c 100644 --- a/contrib/python/psutil/py3/psutil/_psutil_common.h +++ b/contrib/python/psutil/py3/psutil/_psutil_common.h @@ -10,7 +10,6 @@ // --- Global vars / constants // ==================================================================== -extern int PSUTIL_TESTING; extern int PSUTIL_DEBUG; // a signaler for connections without an actual status static const int PSUTIL_CONN_NONE = 128; @@ -32,11 +31,15 @@ static const int PSUTIL_CONN_NONE = 128; #define PyUnicode_DecodeFSDefaultAndSize PyString_FromStringAndSize #endif -#if defined(PSUTIL_WINDOWS) && \ - defined(PYPY_VERSION) && \ - !defined(PyErr_SetFromWindowsErrWithFilename) - PyObject *PyErr_SetFromWindowsErrWithFilename(int ierr, - const char *filename); +#if defined(PSUTIL_WINDOWS) && defined(PYPY_VERSION) + #if !defined(PyErr_SetFromWindowsErrWithFilename) + PyObject *PyErr_SetFromWindowsErrWithFilename(int ierr, + const char *filename); + #endif + #if !defined(PyErr_SetExcFromWindowsErrWithFilenameObject) + PyObject *PyErr_SetExcFromWindowsErrWithFilenameObject( + PyObject *type, int ierr, PyObject *filename); + #endif #endif // --- _Py_PARSE_PID @@ -100,10 +103,20 @@ PyObject* PyErr_SetFromOSErrnoWithSyscall(const char *syscall); // --- Global utils // ==================================================================== -PyObject* psutil_set_testing(PyObject *self, PyObject *args); -void psutil_debug(const char* format, ...); +PyObject* psutil_check_pid_range(PyObject *self, PyObject *args); +PyObject* psutil_set_debug(PyObject *self, PyObject *args); int psutil_setup(void); + +// Print a debug message on stderr. +#define psutil_debug(...) do { \ + if (! PSUTIL_DEBUG) \ + break; \ + fprintf(stderr, "psutil-debug [%s:%d]> ", __FILE__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n");} while(0) + + // ==================================================================== // --- BSD // ==================================================================== @@ -111,6 +124,16 @@ int psutil_setup(void); void convert_kvm_err(const char *syscall, char *errbuf); // ==================================================================== +// --- macOS +// ==================================================================== + +#ifdef PSUTIL_OSX + #include <mach/mach_time.h> + + extern struct mach_timebase_info PSUTIL_MACH_TIMEBASE_INFO; +#endif + +// ==================================================================== // --- Windows // ==================================================================== @@ -149,7 +172,6 @@ void convert_kvm_err(const char *syscall, char *errbuf); #define AF_INET6 23 #endif - int psutil_load_globals(); PVOID psutil_GetProcAddress(LPCSTR libname, LPCSTR procname); PVOID psutil_GetProcAddressFromLib(LPCSTR libname, LPCSTR procname); PVOID psutil_SetFromNTStatusErr(NTSTATUS Status, const char *syscall); diff --git a/contrib/python/psutil/py3/psutil/_psutil_linux.c b/contrib/python/psutil/py3/psutil/_psutil_linux.c index 5836cd6b78..292e1c5524 100644 --- a/contrib/python/psutil/py3/psutil/_psutil_linux.c +++ b/contrib/python/psutil/py3/psutil/_psutil_linux.c @@ -10,504 +10,42 @@ #define _GNU_SOURCE 1 #endif #include <Python.h> -#include <errno.h> -#include <stdlib.h> -#include <mntent.h> -#include <features.h> -#include <utmp.h> -#include <sched.h> -#include <linux/version.h> -#include <sys/syscall.h> -#include <sys/sysinfo.h> -#include <sys/ioctl.h> -#include <sys/socket.h> -#include <linux/sockios.h> -#include <linux/if.h> -#include <sys/resource.h> - -// see: https://github.com/giampaolo/psutil/issues/659 -#ifdef PSUTIL_ETHTOOL_MISSING_TYPES - #include <linux/types.h> - typedef __u64 u64; - typedef __u32 u32; - typedef __u16 u16; - typedef __u8 u8; -#endif -/* Avoid redefinition of struct sysinfo with musl libc */ -#define _LINUX_SYSINFO_H -#include <linux/ethtool.h> - -/* The minimum number of CPUs allocated in a cpu_set_t */ -static const int NCPUS_START = sizeof(unsigned long) * CHAR_BIT; - -// Linux >= 2.6.13 -#define PSUTIL_HAVE_IOPRIO defined(__NR_ioprio_get) && defined(__NR_ioprio_set) - -// Should exist starting from CentOS 6 (year 2011). -#ifdef CPU_ALLOC - #define PSUTIL_HAVE_CPU_AFFINITY -#endif +#include <linux/ethtool.h> // DUPLEX_* #include "_psutil_common.h" -#include "_psutil_posix.h" - -// May happen on old RedHat versions, see: -// https://github.com/giampaolo/psutil/issues/607 -#ifndef DUPLEX_UNKNOWN - #define DUPLEX_UNKNOWN 0xff -#endif - - -#if PSUTIL_HAVE_IOPRIO -enum { - IOPRIO_WHO_PROCESS = 1, -}; - -static inline int -ioprio_get(int which, int who) { - return syscall(__NR_ioprio_get, which, who); -} - -static inline int -ioprio_set(int which, int who, int ioprio) { - return syscall(__NR_ioprio_set, which, who, ioprio); -} - -#define IOPRIO_CLASS_SHIFT 13 -#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1) - -#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT) -#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK) -#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data) - - -/* - * Return a (ioclass, iodata) Python tuple representing process I/O priority. - */ -static PyObject * -psutil_proc_ioprio_get(PyObject *self, PyObject *args) { - pid_t pid; - int ioprio, ioclass, iodata; - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - ioprio = ioprio_get(IOPRIO_WHO_PROCESS, pid); - if (ioprio == -1) - return PyErr_SetFromErrno(PyExc_OSError); - ioclass = IOPRIO_PRIO_CLASS(ioprio); - iodata = IOPRIO_PRIO_DATA(ioprio); - return Py_BuildValue("ii", ioclass, iodata); -} - - -/* - * A wrapper around ioprio_set(); sets process I/O priority. - * ioclass can be either IOPRIO_CLASS_RT, IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE - * or 0. iodata goes from 0 to 7 depending on ioclass specified. - */ -static PyObject * -psutil_proc_ioprio_set(PyObject *self, PyObject *args) { - pid_t pid; - int ioprio, ioclass, iodata; - int retval; - - if (! PyArg_ParseTuple( - args, _Py_PARSE_PID "ii", &pid, &ioclass, &iodata)) { - return NULL; - } - ioprio = IOPRIO_PRIO_VALUE(ioclass, iodata); - retval = ioprio_set(IOPRIO_WHO_PROCESS, pid, ioprio); - if (retval == -1) - return PyErr_SetFromErrno(PyExc_OSError); - Py_RETURN_NONE; -} -#endif - - -/* - * Return disk mounted partitions as a list of tuples including device, - * mount point and filesystem type - */ -static PyObject * -psutil_disk_partitions(PyObject *self, PyObject *args) { - FILE *file = NULL; - struct mntent *entry; - char *mtab_path; - PyObject *py_dev = NULL; - PyObject *py_mountp = NULL; - PyObject *py_tuple = NULL; - PyObject *py_retlist = PyList_New(0); - - if (py_retlist == NULL) - return NULL; - - if (!PyArg_ParseTuple(args, "s", &mtab_path)) - return NULL; - - Py_BEGIN_ALLOW_THREADS - file = setmntent(mtab_path, "r"); - Py_END_ALLOW_THREADS - if ((file == 0) || (file == NULL)) { - psutil_debug("setmntent() failed"); - PyErr_SetFromErrnoWithFilename(PyExc_OSError, mtab_path); - goto error; - } - - while ((entry = getmntent(file))) { - if (entry == NULL) { - PyErr_Format(PyExc_RuntimeError, "getmntent() syscall failed"); - goto error; - } - py_dev = PyUnicode_DecodeFSDefault(entry->mnt_fsname); - if (! py_dev) - goto error; - py_mountp = PyUnicode_DecodeFSDefault(entry->mnt_dir); - if (! py_mountp) - goto error; - py_tuple = Py_BuildValue("(OOss)", - py_dev, // device - py_mountp, // mount point - entry->mnt_type, // fs type - entry->mnt_opts); // options - if (! py_tuple) - goto error; - if (PyList_Append(py_retlist, py_tuple)) - goto error; - Py_CLEAR(py_dev); - Py_CLEAR(py_mountp); - Py_CLEAR(py_tuple); - } - endmntent(file); - return py_retlist; - -error: - if (file != NULL) - endmntent(file); - Py_XDECREF(py_dev); - Py_XDECREF(py_mountp); - Py_XDECREF(py_tuple); - Py_DECREF(py_retlist); - return NULL; -} - - -/* - * A wrapper around sysinfo(), return system memory usage statistics. - */ -static PyObject * -psutil_linux_sysinfo(PyObject *self, PyObject *args) { - struct sysinfo info; - - if (sysinfo(&info) != 0) - return PyErr_SetFromErrno(PyExc_OSError); - // note: boot time might also be determined from here - return Py_BuildValue( - "(kkkkkkI)", - info.totalram, // total - info.freeram, // free - info.bufferram, // buffer - info.sharedram, // shared - info.totalswap, // swap tot - info.freeswap, // swap free - info.mem_unit // multiplier - ); -} - - -/* - * Return process CPU affinity as a Python list - */ -#ifdef PSUTIL_HAVE_CPU_AFFINITY - -static PyObject * -psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args) { - int cpu, ncpus, count, cpucount_s; - pid_t pid; - size_t setsize; - cpu_set_t *mask = NULL; - PyObject *py_list = NULL; +#include "arch/linux/disk.h" +#include "arch/linux/mem.h" +#include "arch/linux/net.h" +#include "arch/linux/proc.h" +#include "arch/linux/users.h" - if (!PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - ncpus = NCPUS_START; - while (1) { - setsize = CPU_ALLOC_SIZE(ncpus); - mask = CPU_ALLOC(ncpus); - if (mask == NULL) { - psutil_debug("CPU_ALLOC() failed"); - return PyErr_NoMemory(); - } - if (sched_getaffinity(pid, setsize, mask) == 0) - break; - CPU_FREE(mask); - if (errno != EINVAL) - return PyErr_SetFromErrno(PyExc_OSError); - if (ncpus > INT_MAX / 2) { - PyErr_SetString(PyExc_OverflowError, "could not allocate " - "a large enough CPU set"); - return NULL; - } - ncpus = ncpus * 2; - } - - py_list = PyList_New(0); - if (py_list == NULL) - goto error; - - cpucount_s = CPU_COUNT_S(setsize, mask); - for (cpu = 0, count = cpucount_s; count; cpu++) { - if (CPU_ISSET_S(cpu, setsize, mask)) { -#if PY_MAJOR_VERSION >= 3 - PyObject *cpu_num = PyLong_FromLong(cpu); -#else - PyObject *cpu_num = PyInt_FromLong(cpu); -#endif - if (cpu_num == NULL) - goto error; - if (PyList_Append(py_list, cpu_num)) { - Py_DECREF(cpu_num); - goto error; - } - Py_DECREF(cpu_num); - --count; - } - } - CPU_FREE(mask); - return py_list; - -error: - if (mask) - CPU_FREE(mask); - Py_XDECREF(py_list); - return NULL; -} - - -/* - * Set process CPU affinity; expects a bitmask - */ -static PyObject * -psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) { - cpu_set_t cpu_set; - size_t len; - pid_t pid; - int i, seq_len; - PyObject *py_cpu_set; - PyObject *py_cpu_seq = NULL; - - if (!PyArg_ParseTuple(args, _Py_PARSE_PID "O", &pid, &py_cpu_set)) - return NULL; - - if (!PySequence_Check(py_cpu_set)) { - PyErr_Format(PyExc_TypeError, "sequence argument expected, got %s", - Py_TYPE(py_cpu_set)->tp_name); - goto error; - } - - py_cpu_seq = PySequence_Fast(py_cpu_set, "expected a sequence or integer"); - if (!py_cpu_seq) - goto error; - seq_len = PySequence_Fast_GET_SIZE(py_cpu_seq); - CPU_ZERO(&cpu_set); - for (i = 0; i < seq_len; i++) { - PyObject *item = PySequence_Fast_GET_ITEM(py_cpu_seq, i); -#if PY_MAJOR_VERSION >= 3 - long value = PyLong_AsLong(item); -#else - long value = PyInt_AsLong(item); -#endif - if ((value == -1) || PyErr_Occurred()) { - if (!PyErr_Occurred()) - PyErr_SetString(PyExc_ValueError, "invalid CPU value"); - goto error; - } - CPU_SET(value, &cpu_set); - } - - len = sizeof(cpu_set); - if (sched_setaffinity(pid, len, &cpu_set)) { - PyErr_SetFromErrno(PyExc_OSError); - goto error; - } - - Py_DECREF(py_cpu_seq); - Py_RETURN_NONE; - -error: - if (py_cpu_seq != NULL) - Py_DECREF(py_cpu_seq); - return NULL; -} -#endif /* PSUTIL_HAVE_CPU_AFFINITY */ - - -/* - * Return currently connected users as a list of tuples. - */ -static PyObject * -psutil_users(PyObject *self, PyObject *args) { - struct utmp *ut; - PyObject *py_retlist = PyList_New(0); - PyObject *py_tuple = NULL; - PyObject *py_username = NULL; - PyObject *py_tty = NULL; - PyObject *py_hostname = NULL; - PyObject *py_user_proc = NULL; - - if (py_retlist == NULL) - return NULL; - setutent(); - while (NULL != (ut = getutent())) { - py_tuple = NULL; - py_user_proc = NULL; - if (ut->ut_type == USER_PROCESS) - py_user_proc = Py_True; - else - py_user_proc = Py_False; - py_username = PyUnicode_DecodeFSDefault(ut->ut_user); - if (! py_username) - goto error; - py_tty = PyUnicode_DecodeFSDefault(ut->ut_line); - if (! py_tty) - goto error; - py_hostname = PyUnicode_DecodeFSDefault(ut->ut_host); - if (! py_hostname) - goto error; - - py_tuple = Py_BuildValue( - "OOOfO" _Py_PARSE_PID, - py_username, // username - py_tty, // tty - py_hostname, // hostname - (float)ut->ut_tv.tv_sec, // tstamp - py_user_proc, // (bool) user process - ut->ut_pid // process id - ); - if (! py_tuple) - goto error; - if (PyList_Append(py_retlist, py_tuple)) - goto error; - Py_CLEAR(py_username); - Py_CLEAR(py_tty); - Py_CLEAR(py_hostname); - Py_CLEAR(py_tuple); - } - endutent(); - return py_retlist; - -error: - Py_XDECREF(py_username); - Py_XDECREF(py_tty); - Py_XDECREF(py_hostname); - Py_XDECREF(py_tuple); - Py_DECREF(py_retlist); - endutent(); - return NULL; -} - - -/* - * Return stats about a particular network - * interface. References: - * https://github.com/dpaleino/wicd/blob/master/wicd/backends/be-ioctl.py - * http://www.i-scream.org/libstatgrab/ - */ -static PyObject* -psutil_net_if_duplex_speed(PyObject* self, PyObject* args) { - char *nic_name; - int sock = 0; - int ret; - int duplex; - int speed; - struct ifreq ifr; - struct ethtool_cmd ethcmd; - PyObject *py_retlist = NULL; - - if (! PyArg_ParseTuple(args, "s", &nic_name)) - return NULL; - - sock = socket(AF_INET, SOCK_DGRAM, 0); - if (sock == -1) - return PyErr_SetFromOSErrnoWithSyscall("socket()"); - PSUTIL_STRNCPY(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name)); - - // duplex and speed - memset(ðcmd, 0, sizeof ethcmd); - ethcmd.cmd = ETHTOOL_GSET; - ifr.ifr_data = (void *)ðcmd; - ret = ioctl(sock, SIOCETHTOOL, &ifr); - - if (ret != -1) { - duplex = ethcmd.duplex; - speed = ethcmd.speed; - } - else { - if ((errno == EOPNOTSUPP) || (errno == EINVAL)) { - // EOPNOTSUPP may occur in case of wi-fi cards. - // For EINVAL see: - // https://github.com/giampaolo/psutil/issues/797 - // #issuecomment-202999532 - duplex = DUPLEX_UNKNOWN; - speed = 0; - } - else { - PyErr_SetFromOSErrnoWithSyscall("ioctl(SIOCETHTOOL)"); - goto error; - } - } - - py_retlist = Py_BuildValue("[ii]", duplex, speed); - if (!py_retlist) - goto error; - close(sock); - return py_retlist; - -error: - if (sock != -1) - close(sock); - return NULL; -} - - -/* - * Module init. - */ static PyMethodDef mod_methods[] = { // --- per-process functions - -#if PSUTIL_HAVE_IOPRIO - {"proc_ioprio_get", psutil_proc_ioprio_get, METH_VARARGS, - "Get process I/O priority"}, - {"proc_ioprio_set", psutil_proc_ioprio_set, METH_VARARGS, - "Set process I/O priority"}, +#ifdef PSUTIL_HAVE_IOPRIO + {"proc_ioprio_get", psutil_proc_ioprio_get, METH_VARARGS}, + {"proc_ioprio_set", psutil_proc_ioprio_set, METH_VARARGS}, #endif #ifdef PSUTIL_HAVE_CPU_AFFINITY - {"proc_cpu_affinity_get", psutil_proc_cpu_affinity_get, METH_VARARGS, - "Return process CPU affinity as a Python long (the bitmask)."}, - {"proc_cpu_affinity_set", psutil_proc_cpu_affinity_set, METH_VARARGS, - "Set process CPU affinity; expects a bitmask."}, + {"proc_cpu_affinity_get", psutil_proc_cpu_affinity_get, METH_VARARGS}, + {"proc_cpu_affinity_set", psutil_proc_cpu_affinity_set, METH_VARARGS}, #endif - // --- system related functions - - {"disk_partitions", psutil_disk_partitions, METH_VARARGS, - "Return disk mounted partitions as a list of tuples including " - "device, mount point and filesystem type"}, - {"users", psutil_users, METH_VARARGS, - "Return currently connected users as a list of tuples"}, - {"net_if_duplex_speed", psutil_net_if_duplex_speed, METH_VARARGS, - "Return duplex and speed info about a NIC"}, - + {"disk_partitions", psutil_disk_partitions, METH_VARARGS}, + {"users", psutil_users, METH_VARARGS}, + {"net_if_duplex_speed", psutil_net_if_duplex_speed, METH_VARARGS}, // --- linux specific - - {"linux_sysinfo", psutil_linux_sysinfo, METH_VARARGS, - "A wrapper around sysinfo(), return system memory usage statistics"}, + {"linux_sysinfo", psutil_linux_sysinfo, METH_VARARGS}, // --- others - {"set_testing", psutil_set_testing, METH_NOARGS, - "Set psutil in testing mode"}, - + {"check_pid_range", psutil_check_pid_range, METH_VARARGS}, + {"set_debug", psutil_set_debug, METH_VARARGS}, {NULL, NULL, 0, NULL} }; +// May happen on old RedHat versions, see: +// https://github.com/giampaolo/psutil/issues/607 +#ifndef DUPLEX_UNKNOWN + #define DUPLEX_UNKNOWN 0xff +#endif #if PY_MAJOR_VERSION >= 3 diff --git a/contrib/python/psutil/py3/psutil/_psutil_osx.c b/contrib/python/psutil/py3/psutil/_psutil_osx.c index 13d0bb6856..369fbbfb48 100644 --- a/contrib/python/psutil/py3/psutil/_psutil_osx.c +++ b/contrib/python/psutil/py3/psutil/_psutil_osx.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * @@ -7,1816 +7,55 @@ */ #include <Python.h> -#include <assert.h> -#include <errno.h> -#include <stdbool.h> -#include <stdlib.h> -#include <stdio.h> -#include <utmpx.h> -#include <sys/sysctl.h> -#include <sys/vmmeter.h> -#include <libproc.h> -#include <sys/proc_info.h> +#include <sys/proc.h> #include <netinet/tcp_fsm.h> -#include <arpa/inet.h> -#include <net/if_dl.h> -#include <pwd.h> -#include <unistd.h> - -#include <mach/mach.h> -#include <mach/task.h> -#include <mach/mach_init.h> -#include <mach/host_info.h> -#include <mach/mach_host.h> -#include <mach/mach_traps.h> -#include <mach/mach_vm.h> -#include <mach/shared_region.h> - -#include <mach-o/loader.h> - -#include <CoreFoundation/CoreFoundation.h> -#include <IOKit/IOKitLib.h> -#include <IOKit/storage/IOBlockStorageDriver.h> -#include <IOKit/storage/IOMedia.h> -#include <IOKit/IOBSD.h> -#include <IOKit/ps/IOPowerSources.h> -#include <IOKit/ps/IOPSKeys.h> #include "_psutil_common.h" -#include "_psutil_posix.h" -#include "arch/osx/process_info.h" - - -#define PSUTIL_TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0) - -static PyObject *ZombieProcessError; - - -/* - * A wrapper around host_statistics() invoked with HOST_VM_INFO. - */ -int -psutil_sys_vminfo(vm_statistics_data_t *vmstat) { - kern_return_t ret; - mach_msg_type_number_t count = sizeof(*vmstat) / sizeof(integer_t); - mach_port_t mport = mach_host_self(); - - ret = host_statistics(mport, HOST_VM_INFO, (host_info_t)vmstat, &count); - if (ret != KERN_SUCCESS) { - PyErr_Format( - PyExc_RuntimeError, - "host_statistics(HOST_VM_INFO) syscall failed: %s", - mach_error_string(ret)); - return 0; - } - mach_port_deallocate(mach_task_self(), mport); - return 1; -} - - -/* - * A wrapper around task_for_pid() which sucks big time: - * - it's not documented - * - errno is set only sometimes - * - sometimes errno is ENOENT (?!?) - * - for PIDs != getpid() or PIDs which are not members of the procmod - * it requires root - * As such we can only guess what the heck went wrong and fail either - * with NoSuchProcess, ZombieProcessError or giveup with AccessDenied. - * Here's some history: - * https://github.com/giampaolo/psutil/issues/1181 - * https://github.com/giampaolo/psutil/issues/1209 - * https://github.com/giampaolo/psutil/issues/1291#issuecomment-396062519 - */ -int -psutil_task_for_pid(pid_t pid, mach_port_t *task) -{ - // See: https://github.com/giampaolo/psutil/issues/1181 - kern_return_t err = KERN_SUCCESS; - - err = task_for_pid(mach_task_self(), pid, task); - if (err != KERN_SUCCESS) { - if (psutil_pid_exists(pid) == 0) - NoSuchProcess("task_for_pid"); - else if (psutil_is_zombie(pid) == 1) - PyErr_SetString(ZombieProcessError, - "task_for_pid -> psutil_is_zombie -> 1"); - else { - psutil_debug( - "task_for_pid() failed (pid=%ld, err=%i, errno=%i, msg='%s'); " - "setting AccessDenied()", - pid, err, errno, mach_error_string(err)); - AccessDenied("task_for_pid"); - } - return 1; - } - return 0; -} - - -/* - * Return a Python list of all the PIDs running on the system. - */ -static PyObject * -psutil_pids(PyObject *self, PyObject *args) { - kinfo_proc *proclist = NULL; - kinfo_proc *orig_address = NULL; - size_t num_processes; - size_t idx; - PyObject *py_pid = NULL; - PyObject *py_retlist = PyList_New(0); - - if (py_retlist == NULL) - return NULL; - - if (psutil_get_proc_list(&proclist, &num_processes) != 0) - goto error; - - // save the address of proclist so we can free it later - orig_address = proclist; - for (idx = 0; idx < num_processes; idx++) { - py_pid = PyLong_FromPid(proclist->kp_proc.p_pid); - if (! py_pid) - goto error; - if (PyList_Append(py_retlist, py_pid)) - goto error; - Py_CLEAR(py_pid); - proclist++; - } - free(orig_address); - - return py_retlist; - -error: - Py_XDECREF(py_pid); - Py_DECREF(py_retlist); - if (orig_address != NULL) - free(orig_address); - return NULL; -} - - -/* - * Return multiple process info as a Python tuple in one shot by - * using sysctl() and filling up a kinfo_proc struct. - * It should be possible to do this for all processes without - * incurring into permission (EPERM) errors. - * This will also succeed for zombie processes returning correct - * information. - */ -static PyObject * -psutil_proc_kinfo_oneshot(PyObject *self, PyObject *args) { - pid_t pid; - struct kinfo_proc kp; - PyObject *py_name; - PyObject *py_retlist; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - if (psutil_get_kinfo_proc(pid, &kp) == -1) - return NULL; - - py_name = PyUnicode_DecodeFSDefault(kp.kp_proc.p_comm); - if (! py_name) { - // Likely a decoding error. We don't want to fail the whole - // operation. The python module may retry with proc_name(). - PyErr_Clear(); - py_name = Py_None; - } - - py_retlist = Py_BuildValue( - _Py_PARSE_PID "llllllidiO", - kp.kp_eproc.e_ppid, // (pid_t) ppid - (long)kp.kp_eproc.e_pcred.p_ruid, // (long) real uid - (long)kp.kp_eproc.e_ucred.cr_uid, // (long) effective uid - (long)kp.kp_eproc.e_pcred.p_svuid, // (long) saved uid - (long)kp.kp_eproc.e_pcred.p_rgid, // (long) real gid - (long)kp.kp_eproc.e_ucred.cr_groups[0], // (long) effective gid - (long)kp.kp_eproc.e_pcred.p_svgid, // (long) saved gid - kp.kp_eproc.e_tdev, // (int) tty nr - PSUTIL_TV2DOUBLE(kp.kp_proc.p_starttime), // (double) create time - (int)kp.kp_proc.p_stat, // (int) status - py_name // (pystr) name - ); - - if (py_retlist != NULL) { - // XXX shall we decref() also in case of Py_BuildValue() error? - Py_DECREF(py_name); - } - return py_retlist; -} - - -/* - * Return multiple process info as a Python tuple in one shot by - * using proc_pidinfo(PROC_PIDTASKINFO) and filling a proc_taskinfo - * struct. - * Contrarily from proc_kinfo above this function will fail with - * EACCES for PIDs owned by another user and with ESRCH for zombie - * processes. - */ -static PyObject * -psutil_proc_pidtaskinfo_oneshot(PyObject *self, PyObject *args) { - pid_t pid; - struct proc_taskinfo pti; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - if (psutil_proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti)) <= 0) - return NULL; - - return Py_BuildValue( - "(ddKKkkkk)", - (float)pti.pti_total_user / 1000000000.0, // (float) cpu user time - (float)pti.pti_total_system / 1000000000.0, // (float) cpu sys time - // Note about memory: determining other mem stats on macOS is a mess: - // http://www.opensource.apple.com/source/top/top-67/libtop.c?txt - // I just give up. - // struct proc_regioninfo pri; - // psutil_proc_pidinfo(pid, PROC_PIDREGIONINFO, 0, &pri, sizeof(pri)) - pti.pti_resident_size, // (uns long long) rss - pti.pti_virtual_size, // (uns long long) vms - pti.pti_faults, // (uns long) number of page faults (pages) - pti.pti_pageins, // (uns long) number of actual pageins (pages) - pti.pti_threadnum, // (uns long) num threads - // Unvoluntary value seems not to be available; - // pti.pti_csw probably refers to the sum of the two; - // getrusage() numbers seems to confirm this theory. - pti.pti_csw // (uns long) voluntary ctx switches - ); -} - - -/* - * Return process name from kinfo_proc as a Python string. - */ -static PyObject * -psutil_proc_name(PyObject *self, PyObject *args) { - pid_t pid; - struct kinfo_proc kp; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - if (psutil_get_kinfo_proc(pid, &kp) == -1) - return NULL; - return PyUnicode_DecodeFSDefault(kp.kp_proc.p_comm); -} - - -/* - * Return process current working directory. - * Raises NSP in case of zombie process. - */ -static PyObject * -psutil_proc_cwd(PyObject *self, PyObject *args) { - pid_t pid; - struct proc_vnodepathinfo pathinfo; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - - if (psutil_proc_pidinfo( - pid, PROC_PIDVNODEPATHINFO, 0, &pathinfo, sizeof(pathinfo)) <= 0) - { - return NULL; - } - - return PyUnicode_DecodeFSDefault(pathinfo.pvi_cdir.vip_path); -} - - -/* - * Return path of the process executable. - */ -static PyObject * -psutil_proc_exe(PyObject *self, PyObject *args) { - pid_t pid; - char buf[PATH_MAX]; - int ret; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - errno = 0; - ret = proc_pidpath(pid, &buf, sizeof(buf)); - if (ret == 0) { - if (pid == 0) { - AccessDenied("automatically set for PID 0"); - return NULL; - } - else if (errno == ENOENT) { - // It may happen (file not found error) if the process is - // still alive but the executable which launched it got - // deleted, see: - // https://github.com/giampaolo/psutil/issues/1738 - return Py_BuildValue("s", ""); - } - else { - psutil_raise_for_pid(pid, "proc_pidpath()"); - return NULL; - } - } - return PyUnicode_DecodeFSDefault(buf); -} - - -/* - * Return process cmdline as a Python list of cmdline arguments. - */ -static PyObject * -psutil_proc_cmdline(PyObject *self, PyObject *args) { - pid_t pid; - PyObject *py_retlist = NULL; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - - // get the commandline, defined in arch/osx/process_info.c - py_retlist = psutil_get_cmdline(pid); - return py_retlist; -} - - -/* - * Return process environment as a Python string. - */ -static PyObject * -psutil_proc_environ(PyObject *self, PyObject *args) { - pid_t pid; - PyObject *py_retdict = NULL; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - - // get the environment block, defined in arch/osx/process_info.c - py_retdict = psutil_get_environ(pid); - return py_retdict; -} - - -/* - * Return the number of logical CPUs in the system. - * XXX this could be shared with BSD. - */ -static PyObject * -psutil_cpu_count_logical(PyObject *self, PyObject *args) { - /* - int mib[2]; - int ncpu; - size_t len; - mib[0] = CTL_HW; - mib[1] = HW_NCPU; - len = sizeof(ncpu); - - if (sysctl(mib, 2, &ncpu, &len, NULL, 0) == -1) - Py_RETURN_NONE; // mimic os.cpu_count() - else - return Py_BuildValue("i", ncpu); - */ - int num; - size_t size = sizeof(int); - - if (sysctlbyname("hw.logicalcpu", &num, &size, NULL, 2)) - Py_RETURN_NONE; // mimic os.cpu_count() - else - return Py_BuildValue("i", num); -} - - -/* - * Return the number of physical CPUs in the system. - */ -static PyObject * -psutil_cpu_count_phys(PyObject *self, PyObject *args) { - int num; - size_t size = sizeof(int); - - if (sysctlbyname("hw.physicalcpu", &num, &size, NULL, 0)) - Py_RETURN_NONE; // mimic os.cpu_count() - else - return Py_BuildValue("i", num); -} - - -/* - * Indicates if the given virtual address on the given architecture is in the - * shared VM region. - */ -static bool -psutil_in_shared_region(mach_vm_address_t addr, cpu_type_t type) { - mach_vm_address_t base; - mach_vm_address_t size; - - switch (type) { - case CPU_TYPE_ARM: - base = SHARED_REGION_BASE_ARM; - size = SHARED_REGION_SIZE_ARM; - break; - case CPU_TYPE_I386: - base = SHARED_REGION_BASE_I386; - size = SHARED_REGION_SIZE_I386; - break; - case CPU_TYPE_X86_64: - base = SHARED_REGION_BASE_X86_64; - size = SHARED_REGION_SIZE_X86_64; - break; - default: - return false; - } - - return base <= addr && addr < (base + size); -} - - -/* - * Returns the USS (unique set size) of the process. Reference: - * https://dxr.mozilla.org/mozilla-central/source/xpcom/base/ - * nsMemoryReporterManager.cpp - */ -static PyObject * -psutil_proc_memory_uss(PyObject *self, PyObject *args) { - pid_t pid; - size_t len; - cpu_type_t cpu_type; - size_t private_pages = 0; - mach_vm_size_t size = 0; - mach_msg_type_number_t info_count = VM_REGION_TOP_INFO_COUNT; - kern_return_t kr; - long pagesize = psutil_getpagesize(); - mach_vm_address_t addr = MACH_VM_MIN_ADDRESS; - mach_port_t task = MACH_PORT_NULL; - vm_region_top_info_data_t info; - mach_port_t object_name; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - - if (psutil_task_for_pid(pid, &task) != 0) - return NULL; - - len = sizeof(cpu_type); - if (sysctlbyname("sysctl.proc_cputype", &cpu_type, &len, NULL, 0) != 0) { - return PyErr_SetFromOSErrnoWithSyscall( - "sysctlbyname('sysctl.proc_cputype')"); - } - - // Roughly based on libtop_update_vm_regions in - // http://www.opensource.apple.com/source/top/top-100.1.2/libtop.c - for (addr = 0; ; addr += size) { - kr = mach_vm_region( - task, &addr, &size, VM_REGION_TOP_INFO, (vm_region_info_t)&info, - &info_count, &object_name); - if (kr == KERN_INVALID_ADDRESS) { - // Done iterating VM regions. - break; - } - else if (kr != KERN_SUCCESS) { - PyErr_Format( - PyExc_RuntimeError, - "mach_vm_region(VM_REGION_TOP_INFO) syscall failed"); - return NULL; - } - - if (psutil_in_shared_region(addr, cpu_type) && - info.share_mode != SM_PRIVATE) { - continue; - } - - switch (info.share_mode) { -#ifdef SM_LARGE_PAGE - case SM_LARGE_PAGE: - // NB: Large pages are not shareable and always resident. -#endif - case SM_PRIVATE: - private_pages += info.private_pages_resident; - private_pages += info.shared_pages_resident; - break; - case SM_COW: - private_pages += info.private_pages_resident; - if (info.ref_count == 1) { - // Treat copy-on-write pages as private if they only - // have one reference. - private_pages += info.shared_pages_resident; - } - break; - case SM_SHARED: - default: - break; - } - } - - mach_port_deallocate(mach_task_self(), task); - return Py_BuildValue("K", private_pages * pagesize); -} - - -/* - * Return system virtual memory stats. - * See: - * https://opensource.apple.com/source/system_cmds/system_cmds-790/ - * vm_stat.tproj/vm_stat.c.auto.html - */ -static PyObject * -psutil_virtual_mem(PyObject *self, PyObject *args) { - int mib[2]; - uint64_t total; - size_t len = sizeof(total); - vm_statistics_data_t vm; - long pagesize = psutil_getpagesize(); - // physical mem - mib[0] = CTL_HW; - mib[1] = HW_MEMSIZE; - - // This is also available as sysctlbyname("hw.memsize"). - if (sysctl(mib, 2, &total, &len, NULL, 0)) { - if (errno != 0) - PyErr_SetFromErrno(PyExc_OSError); - else - PyErr_Format( - PyExc_RuntimeError, "sysctl(HW_MEMSIZE) syscall failed"); - return NULL; - } - - // vm - if (!psutil_sys_vminfo(&vm)) - return NULL; - - return Py_BuildValue( - "KKKKKK", - total, - (unsigned long long) vm.active_count * pagesize, // active - (unsigned long long) vm.inactive_count * pagesize, // inactive - (unsigned long long) vm.wire_count * pagesize, // wired - (unsigned long long) vm.free_count * pagesize, // free - (unsigned long long) vm.speculative_count * pagesize // speculative - ); -} - - -/* - * Return stats about swap memory. - */ -static PyObject * -psutil_swap_mem(PyObject *self, PyObject *args) { - int mib[2]; - size_t size; - struct xsw_usage totals; - vm_statistics_data_t vmstat; - long pagesize = psutil_getpagesize(); - - mib[0] = CTL_VM; - mib[1] = VM_SWAPUSAGE; - size = sizeof(totals); - if (sysctl(mib, 2, &totals, &size, NULL, 0) == -1) { - if (errno != 0) - PyErr_SetFromErrno(PyExc_OSError); - else - PyErr_Format( - PyExc_RuntimeError, "sysctl(VM_SWAPUSAGE) syscall failed"); - return NULL; - } - if (!psutil_sys_vminfo(&vmstat)) - return NULL; - - return Py_BuildValue( - "LLLKK", - totals.xsu_total, - totals.xsu_used, - totals.xsu_avail, - (unsigned long long)vmstat.pageins * pagesize, - (unsigned long long)vmstat.pageouts * pagesize); -} - - -/* - * Return a Python tuple representing user, kernel and idle CPU times - */ -static PyObject * -psutil_cpu_times(PyObject *self, PyObject *args) { - mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT; - kern_return_t error; - host_cpu_load_info_data_t r_load; - - mach_port_t host_port = mach_host_self(); - error = host_statistics(host_port, HOST_CPU_LOAD_INFO, - (host_info_t)&r_load, &count); - if (error != KERN_SUCCESS) { - return PyErr_Format( - PyExc_RuntimeError, - "host_statistics(HOST_CPU_LOAD_INFO) syscall failed: %s", - mach_error_string(error)); - } - mach_port_deallocate(mach_task_self(), host_port); - - return Py_BuildValue( - "(dddd)", - (double)r_load.cpu_ticks[CPU_STATE_USER] / CLK_TCK, - (double)r_load.cpu_ticks[CPU_STATE_NICE] / CLK_TCK, - (double)r_load.cpu_ticks[CPU_STATE_SYSTEM] / CLK_TCK, - (double)r_load.cpu_ticks[CPU_STATE_IDLE] / CLK_TCK - ); -} - - -/* - * Return a Python list of tuple representing per-cpu times - */ -static PyObject * -psutil_per_cpu_times(PyObject *self, PyObject *args) { - natural_t cpu_count; - natural_t i; - processor_info_array_t info_array; - mach_msg_type_number_t info_count; - kern_return_t error; - processor_cpu_load_info_data_t *cpu_load_info = NULL; - int ret; - PyObject *py_retlist = PyList_New(0); - PyObject *py_cputime = NULL; - - if (py_retlist == NULL) - return NULL; - - mach_port_t host_port = mach_host_self(); - error = host_processor_info(host_port, PROCESSOR_CPU_LOAD_INFO, - &cpu_count, &info_array, &info_count); - if (error != KERN_SUCCESS) { - PyErr_Format( - PyExc_RuntimeError, - "host_processor_info(PROCESSOR_CPU_LOAD_INFO) syscall failed: %s", - mach_error_string(error)); - goto error; - } - mach_port_deallocate(mach_task_self(), host_port); - - cpu_load_info = (processor_cpu_load_info_data_t *) info_array; - - for (i = 0; i < cpu_count; i++) { - py_cputime = Py_BuildValue( - "(dddd)", - (double)cpu_load_info[i].cpu_ticks[CPU_STATE_USER] / CLK_TCK, - (double)cpu_load_info[i].cpu_ticks[CPU_STATE_NICE] / CLK_TCK, - (double)cpu_load_info[i].cpu_ticks[CPU_STATE_SYSTEM] / CLK_TCK, - (double)cpu_load_info[i].cpu_ticks[CPU_STATE_IDLE] / CLK_TCK - ); - if (!py_cputime) - goto error; - if (PyList_Append(py_retlist, py_cputime)) - goto error; - Py_CLEAR(py_cputime); - } - - ret = vm_deallocate(mach_task_self(), (vm_address_t)info_array, - info_count * sizeof(int)); - if (ret != KERN_SUCCESS) - PyErr_WarnEx(PyExc_RuntimeWarning, "vm_deallocate() failed", 2); - return py_retlist; - -error: - Py_XDECREF(py_cputime); - Py_DECREF(py_retlist); - if (cpu_load_info != NULL) { - ret = vm_deallocate(mach_task_self(), (vm_address_t)info_array, - info_count * sizeof(int)); - if (ret != KERN_SUCCESS) - PyErr_WarnEx(PyExc_RuntimeWarning, "vm_deallocate() failed", 2); - } - return NULL; -} - - -/* - * Retrieve CPU frequency. - */ -static PyObject * -psutil_cpu_freq(PyObject *self, PyObject *args) { - int64_t curr; - int64_t min; - int64_t max; - size_t size = sizeof(int64_t); - - if (sysctlbyname("hw.cpufrequency", &curr, &size, NULL, 0)) { - return PyErr_SetFromOSErrnoWithSyscall( - "sysctlbyname('hw.cpufrequency')"); - } - if (sysctlbyname("hw.cpufrequency_min", &min, &size, NULL, 0)) { - return PyErr_SetFromOSErrnoWithSyscall( - "sysctlbyname('hw.cpufrequency_min')"); - } - if (sysctlbyname("hw.cpufrequency_max", &max, &size, NULL, 0)) { - return PyErr_SetFromOSErrnoWithSyscall( - "sysctlbyname('hw.cpufrequency_max')"); - } - - return Py_BuildValue( - "KKK", - curr / 1000 / 1000, - min / 1000 / 1000, - max / 1000 / 1000); -} - - -/* - * Return a Python float indicating the system boot time expressed in - * seconds since the epoch. - */ -static PyObject * -psutil_boot_time(PyObject *self, PyObject *args) { - // fetch sysctl "kern.boottime" - static int request[2] = { CTL_KERN, KERN_BOOTTIME }; - struct timeval result; - size_t result_len = sizeof result; - time_t boot_time = 0; - - if (sysctl(request, 2, &result, &result_len, NULL, 0) == -1) - return PyErr_SetFromErrno(PyExc_OSError); - boot_time = result.tv_sec; - return Py_BuildValue("f", (float)boot_time); -} - - -/* - * Return a list of tuples including device, mount point and fs type - * for all partitions mounted on the system. - */ -static PyObject * -psutil_disk_partitions(PyObject *self, PyObject *args) { - int num; - int i; - int len; - uint64_t flags; - char opts[400]; - struct statfs *fs = NULL; - PyObject *py_dev = NULL; - PyObject *py_mountp = NULL; - PyObject *py_tuple = NULL; - PyObject *py_retlist = PyList_New(0); +#include "arch/osx/cpu.h" +#include "arch/osx/disk.h" +#include "arch/osx/mem.h" +#include "arch/osx/net.h" +#include "arch/osx/proc.h" +#include "arch/osx/sensors.h" +#include "arch/osx/sys.h" - if (py_retlist == NULL) - return NULL; - // get the number of mount points - Py_BEGIN_ALLOW_THREADS - num = getfsstat(NULL, 0, MNT_NOWAIT); - Py_END_ALLOW_THREADS - if (num == -1) { - PyErr_SetFromErrno(PyExc_OSError); - goto error; - } - - len = sizeof(*fs) * num; - fs = malloc(len); - if (fs == NULL) { - PyErr_NoMemory(); - goto error; - } - - Py_BEGIN_ALLOW_THREADS - num = getfsstat(fs, len, MNT_NOWAIT); - Py_END_ALLOW_THREADS - if (num == -1) { - PyErr_SetFromErrno(PyExc_OSError); - goto error; - } - - for (i = 0; i < num; i++) { - opts[0] = 0; - flags = fs[i].f_flags; - - // see sys/mount.h - if (flags & MNT_RDONLY) - strlcat(opts, "ro", sizeof(opts)); - else - strlcat(opts, "rw", sizeof(opts)); - if (flags & MNT_SYNCHRONOUS) - strlcat(opts, ",sync", sizeof(opts)); - if (flags & MNT_NOEXEC) - strlcat(opts, ",noexec", sizeof(opts)); - if (flags & MNT_NOSUID) - strlcat(opts, ",nosuid", sizeof(opts)); - if (flags & MNT_UNION) - strlcat(opts, ",union", sizeof(opts)); - if (flags & MNT_ASYNC) - strlcat(opts, ",async", sizeof(opts)); - if (flags & MNT_EXPORTED) - strlcat(opts, ",exported", sizeof(opts)); - if (flags & MNT_QUARANTINE) - strlcat(opts, ",quarantine", sizeof(opts)); - if (flags & MNT_LOCAL) - strlcat(opts, ",local", sizeof(opts)); - if (flags & MNT_QUOTA) - strlcat(opts, ",quota", sizeof(opts)); - if (flags & MNT_ROOTFS) - strlcat(opts, ",rootfs", sizeof(opts)); - if (flags & MNT_DOVOLFS) - strlcat(opts, ",dovolfs", sizeof(opts)); - if (flags & MNT_DONTBROWSE) - strlcat(opts, ",dontbrowse", sizeof(opts)); - if (flags & MNT_IGNORE_OWNERSHIP) - strlcat(opts, ",ignore-ownership", sizeof(opts)); - if (flags & MNT_AUTOMOUNTED) - strlcat(opts, ",automounted", sizeof(opts)); - if (flags & MNT_JOURNALED) - strlcat(opts, ",journaled", sizeof(opts)); - if (flags & MNT_NOUSERXATTR) - strlcat(opts, ",nouserxattr", sizeof(opts)); - if (flags & MNT_DEFWRITE) - strlcat(opts, ",defwrite", sizeof(opts)); - if (flags & MNT_MULTILABEL) - strlcat(opts, ",multilabel", sizeof(opts)); - if (flags & MNT_NOATIME) - strlcat(opts, ",noatime", sizeof(opts)); - if (flags & MNT_UPDATE) - strlcat(opts, ",update", sizeof(opts)); - if (flags & MNT_RELOAD) - strlcat(opts, ",reload", sizeof(opts)); - if (flags & MNT_FORCE) - strlcat(opts, ",force", sizeof(opts)); - if (flags & MNT_CMDFLAGS) - strlcat(opts, ",cmdflags", sizeof(opts)); - - py_dev = PyUnicode_DecodeFSDefault(fs[i].f_mntfromname); - if (! py_dev) - goto error; - py_mountp = PyUnicode_DecodeFSDefault(fs[i].f_mntonname); - if (! py_mountp) - goto error; - py_tuple = Py_BuildValue( - "(OOss)", - py_dev, // device - py_mountp, // mount point - fs[i].f_fstypename, // fs type - opts); // options - if (!py_tuple) - goto error; - if (PyList_Append(py_retlist, py_tuple)) - goto error; - Py_CLEAR(py_dev); - Py_CLEAR(py_mountp); - Py_CLEAR(py_tuple); - } - - free(fs); - return py_retlist; - -error: - Py_XDECREF(py_dev); - Py_XDECREF(py_mountp); - Py_XDECREF(py_tuple); - Py_DECREF(py_retlist); - if (fs != NULL) - free(fs); - return NULL; -} - - -/* - * Return process threads - */ -static PyObject * -psutil_proc_threads(PyObject *self, PyObject *args) { - pid_t pid; - int err, ret; - kern_return_t kr; - unsigned int info_count = TASK_BASIC_INFO_COUNT; - mach_port_t task = MACH_PORT_NULL; - struct task_basic_info tasks_info; - thread_act_port_array_t thread_list = NULL; - thread_info_data_t thinfo_basic; - thread_basic_info_t basic_info_th; - mach_msg_type_number_t thread_count, thread_info_count, j; - - PyObject *py_tuple = NULL; - PyObject *py_retlist = PyList_New(0); - - if (py_retlist == NULL) - return NULL; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - goto error; - - if (psutil_task_for_pid(pid, &task) != 0) - goto error; - - info_count = TASK_BASIC_INFO_COUNT; - err = task_info(task, TASK_BASIC_INFO, (task_info_t)&tasks_info, - &info_count); - if (err != KERN_SUCCESS) { - // errcode 4 is "invalid argument" (access denied) - if (err == 4) { - AccessDenied("task_info"); - } - else { - // otherwise throw a runtime error with appropriate error code - PyErr_Format(PyExc_RuntimeError, - "task_info(TASK_BASIC_INFO) syscall failed"); - } - goto error; - } - - err = task_threads(task, &thread_list, &thread_count); - if (err != KERN_SUCCESS) { - PyErr_Format(PyExc_RuntimeError, "task_threads() syscall failed"); - goto error; - } - - for (j = 0; j < thread_count; j++) { - thread_info_count = THREAD_INFO_MAX; - kr = thread_info(thread_list[j], THREAD_BASIC_INFO, - (thread_info_t)thinfo_basic, &thread_info_count); - if (kr != KERN_SUCCESS) { - PyErr_Format(PyExc_RuntimeError, - "thread_info(THREAD_BASIC_INFO) syscall failed"); - goto error; - } - - basic_info_th = (thread_basic_info_t)thinfo_basic; - py_tuple = Py_BuildValue( - "Iff", - j + 1, - basic_info_th->user_time.seconds + \ - (float)basic_info_th->user_time.microseconds / 1000000.0, - basic_info_th->system_time.seconds + \ - (float)basic_info_th->system_time.microseconds / 1000000.0 - ); - if (!py_tuple) - goto error; - if (PyList_Append(py_retlist, py_tuple)) - goto error; - Py_CLEAR(py_tuple); - } - - ret = vm_deallocate(task, (vm_address_t)thread_list, - thread_count * sizeof(int)); - if (ret != KERN_SUCCESS) - PyErr_WarnEx(PyExc_RuntimeWarning, "vm_deallocate() failed", 2); - - mach_port_deallocate(mach_task_self(), task); - - return py_retlist; - -error: - if (task != MACH_PORT_NULL) - mach_port_deallocate(mach_task_self(), task); - Py_XDECREF(py_tuple); - Py_DECREF(py_retlist); - if (thread_list != NULL) { - ret = vm_deallocate(task, (vm_address_t)thread_list, - thread_count * sizeof(int)); - if (ret != KERN_SUCCESS) - PyErr_WarnEx(PyExc_RuntimeWarning, "vm_deallocate() failed", 2); - } - return NULL; -} - - -/* - * Return process open files as a Python tuple. - * References: - * - lsof source code: http://goo.gl/SYW79 and http://goo.gl/m78fd - * - /usr/include/sys/proc_info.h - */ -static PyObject * -psutil_proc_open_files(PyObject *self, PyObject *args) { - pid_t pid; - int pidinfo_result; - int iterations; - int i; - unsigned long nb; - - struct proc_fdinfo *fds_pointer = NULL; - struct proc_fdinfo *fdp_pointer; - struct vnode_fdinfowithpath vi; - - PyObject *py_retlist = PyList_New(0); - PyObject *py_tuple = NULL; - PyObject *py_path = NULL; - - if (py_retlist == NULL) - return NULL; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - goto error; - - pidinfo_result = psutil_proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0); - if (pidinfo_result <= 0) - goto error; - - fds_pointer = malloc(pidinfo_result); - if (fds_pointer == NULL) { - PyErr_NoMemory(); - goto error; - } - pidinfo_result = psutil_proc_pidinfo( - pid, PROC_PIDLISTFDS, 0, fds_pointer, pidinfo_result); - if (pidinfo_result <= 0) - goto error; - - iterations = (pidinfo_result / PROC_PIDLISTFD_SIZE); - - for (i = 0; i < iterations; i++) { - fdp_pointer = &fds_pointer[i]; - - if (fdp_pointer->proc_fdtype == PROX_FDTYPE_VNODE) { - errno = 0; - nb = proc_pidfdinfo((pid_t)pid, - fdp_pointer->proc_fd, - PROC_PIDFDVNODEPATHINFO, - &vi, - sizeof(vi)); - - // --- errors checking - if ((nb <= 0) || nb < sizeof(vi)) { - if ((errno == ENOENT) || (errno == EBADF)) { - // no such file or directory or bad file descriptor; - // let's assume the file has been closed or removed - continue; - } - else { - psutil_raise_for_pid( - pid, "proc_pidinfo(PROC_PIDFDVNODEPATHINFO)"); - goto error; - } - } - // --- /errors checking - - // --- construct python list - py_path = PyUnicode_DecodeFSDefault(vi.pvip.vip_path); - if (! py_path) - goto error; - py_tuple = Py_BuildValue( - "(Oi)", - py_path, - (int)fdp_pointer->proc_fd); - if (!py_tuple) - goto error; - if (PyList_Append(py_retlist, py_tuple)) - goto error; - Py_CLEAR(py_tuple); - Py_CLEAR(py_path); - // --- /construct python list - } - } - - free(fds_pointer); - return py_retlist; - -error: - Py_XDECREF(py_tuple); - Py_XDECREF(py_path); - Py_DECREF(py_retlist); - if (fds_pointer != NULL) - free(fds_pointer); - return NULL; // exception has already been set earlier -} - - -/* - * Return process TCP and UDP connections as a list of tuples. - * Raises NSP in case of zombie process. - * References: - * - lsof source code: http://goo.gl/SYW79 and http://goo.gl/wNrC0 - * - /usr/include/sys/proc_info.h - */ -static PyObject * -psutil_proc_connections(PyObject *self, PyObject *args) { - pid_t pid; - int pidinfo_result; - int iterations; - int i; - unsigned long nb; - - struct proc_fdinfo *fds_pointer = NULL; - struct proc_fdinfo *fdp_pointer; - struct socket_fdinfo si; - - PyObject *py_retlist = PyList_New(0); - PyObject *py_tuple = NULL; - PyObject *py_laddr = NULL; - PyObject *py_raddr = NULL; - PyObject *py_af_filter = NULL; - PyObject *py_type_filter = NULL; - - if (py_retlist == NULL) - return NULL; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID "OO", &pid, &py_af_filter, - &py_type_filter)) { - goto error; - } - - if (!PySequence_Check(py_af_filter) || !PySequence_Check(py_type_filter)) { - PyErr_SetString(PyExc_TypeError, "arg 2 or 3 is not a sequence"); - goto error; - } - - if (pid == 0) - return py_retlist; - pidinfo_result = psutil_proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0); - if (pidinfo_result <= 0) - goto error; - - fds_pointer = malloc(pidinfo_result); - if (fds_pointer == NULL) { - PyErr_NoMemory(); - goto error; - } - - pidinfo_result = psutil_proc_pidinfo( - pid, PROC_PIDLISTFDS, 0, fds_pointer, pidinfo_result); - if (pidinfo_result <= 0) - goto error; - - iterations = (pidinfo_result / PROC_PIDLISTFD_SIZE); - for (i = 0; i < iterations; i++) { - py_tuple = NULL; - py_laddr = NULL; - py_raddr = NULL; - fdp_pointer = &fds_pointer[i]; - - if (fdp_pointer->proc_fdtype == PROX_FDTYPE_SOCKET) { - errno = 0; - nb = proc_pidfdinfo(pid, fdp_pointer->proc_fd, - PROC_PIDFDSOCKETINFO, &si, sizeof(si)); - - // --- errors checking - if ((nb <= 0) || (nb < sizeof(si))) { - if (errno == EBADF) { - // let's assume socket has been closed - continue; - } - else { - psutil_raise_for_pid( - pid, "proc_pidinfo(PROC_PIDFDSOCKETINFO)"); - goto error; - } - } - // --- /errors checking - - // - int fd, family, type, lport, rport, state; - char lip[200], rip[200]; - int inseq; - PyObject *py_family; - PyObject *py_type; - - fd = (int)fdp_pointer->proc_fd; - family = si.psi.soi_family; - type = si.psi.soi_type; - - // apply filters - py_family = PyLong_FromLong((long)family); - inseq = PySequence_Contains(py_af_filter, py_family); - Py_DECREF(py_family); - if (inseq == 0) - continue; - py_type = PyLong_FromLong((long)type); - inseq = PySequence_Contains(py_type_filter, py_type); - Py_DECREF(py_type); - if (inseq == 0) - continue; - - if (errno != 0) { - PyErr_SetFromErrno(PyExc_OSError); - goto error; - } - - if ((family == AF_INET) || (family == AF_INET6)) { - if (family == AF_INET) { - inet_ntop(AF_INET, - &si.psi.soi_proto.pri_tcp.tcpsi_ini. \ - insi_laddr.ina_46.i46a_addr4, - lip, - sizeof(lip)); - inet_ntop(AF_INET, - &si.psi.soi_proto.pri_tcp.tcpsi_ini.insi_faddr. \ - ina_46.i46a_addr4, - rip, - sizeof(rip)); - } - else { - inet_ntop(AF_INET6, - &si.psi.soi_proto.pri_tcp.tcpsi_ini. \ - insi_laddr.ina_6, - lip, sizeof(lip)); - inet_ntop(AF_INET6, - &si.psi.soi_proto.pri_tcp.tcpsi_ini. \ - insi_faddr.ina_6, - rip, sizeof(rip)); - } - - // check for inet_ntop failures - if (errno != 0) { - PyErr_SetFromOSErrnoWithSyscall("inet_ntop()"); - goto error; - } - - lport = ntohs(si.psi.soi_proto.pri_tcp.tcpsi_ini.insi_lport); - rport = ntohs(si.psi.soi_proto.pri_tcp.tcpsi_ini.insi_fport); - if (type == SOCK_STREAM) - state = (int)si.psi.soi_proto.pri_tcp.tcpsi_state; - else - state = PSUTIL_CONN_NONE; - - py_laddr = Py_BuildValue("(si)", lip, lport); - if (!py_laddr) - goto error; - if (rport != 0) - py_raddr = Py_BuildValue("(si)", rip, rport); - else - py_raddr = Py_BuildValue("()"); - if (!py_raddr) - goto error; - - // construct the python list - py_tuple = Py_BuildValue( - "(iiiNNi)", fd, family, type, py_laddr, py_raddr, state); - if (!py_tuple) - goto error; - if (PyList_Append(py_retlist, py_tuple)) - goto error; - Py_CLEAR(py_tuple); - } - else if (family == AF_UNIX) { - py_laddr = PyUnicode_DecodeFSDefault( - si.psi.soi_proto.pri_un.unsi_addr.ua_sun.sun_path); - if (!py_laddr) - goto error; - py_raddr = PyUnicode_DecodeFSDefault( - si.psi.soi_proto.pri_un.unsi_caddr.ua_sun.sun_path); - if (!py_raddr) - goto error; - // construct the python list - py_tuple = Py_BuildValue( - "(iiiOOi)", - fd, family, type, - py_laddr, - py_raddr, - PSUTIL_CONN_NONE); - if (!py_tuple) - goto error; - if (PyList_Append(py_retlist, py_tuple)) - goto error; - Py_CLEAR(py_tuple); - Py_CLEAR(py_laddr); - Py_CLEAR(py_raddr); - } - } - } - - free(fds_pointer); - return py_retlist; - -error: - Py_XDECREF(py_tuple); - Py_XDECREF(py_laddr); - Py_XDECREF(py_raddr); - Py_DECREF(py_retlist); - if (fds_pointer != NULL) - free(fds_pointer); - return NULL; -} - - -/* - * Return number of file descriptors opened by process. - * Raises NSP in case of zombie process. - */ -static PyObject * -psutil_proc_num_fds(PyObject *self, PyObject *args) { - pid_t pid; - int pidinfo_result; - int num; - struct proc_fdinfo *fds_pointer; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - - pidinfo_result = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0); - if (pidinfo_result <= 0) - return PyErr_SetFromErrno(PyExc_OSError); - - fds_pointer = malloc(pidinfo_result); - if (fds_pointer == NULL) - return PyErr_NoMemory(); - pidinfo_result = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fds_pointer, - pidinfo_result); - if (pidinfo_result <= 0) { - free(fds_pointer); - return PyErr_SetFromErrno(PyExc_OSError); - } - - num = (pidinfo_result / PROC_PIDLISTFD_SIZE); - free(fds_pointer); - return Py_BuildValue("i", num); -} - - -/* - * Return a Python list of named tuples with overall network I/O information - */ -static PyObject * -psutil_net_io_counters(PyObject *self, PyObject *args) { - char *buf = NULL, *lim, *next; - struct if_msghdr *ifm; - int mib[6]; - mib[0] = CTL_NET; // networking subsystem - mib[1] = PF_ROUTE; // type of information - mib[2] = 0; // protocol (IPPROTO_xxx) - mib[3] = 0; // address family - mib[4] = NET_RT_IFLIST2; // operation - mib[5] = 0; - size_t len; - PyObject *py_ifc_info = NULL; - PyObject *py_retdict = PyDict_New(); - - if (py_retdict == NULL) - return NULL; - - if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) { - PyErr_SetFromErrno(PyExc_OSError); - goto error; - } - - buf = malloc(len); - if (buf == NULL) { - PyErr_NoMemory(); - goto error; - } - - if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) { - PyErr_SetFromErrno(PyExc_OSError); - goto error; - } - - lim = buf + len; - - for (next = buf; next < lim; ) { - ifm = (struct if_msghdr *)next; - next += ifm->ifm_msglen; - - if (ifm->ifm_type == RTM_IFINFO2) { - py_ifc_info = NULL; - struct if_msghdr2 *if2m = (struct if_msghdr2 *)ifm; - struct sockaddr_dl *sdl = (struct sockaddr_dl *)(if2m + 1); - char ifc_name[32]; - - strncpy(ifc_name, sdl->sdl_data, sdl->sdl_nlen); - ifc_name[sdl->sdl_nlen] = 0; - - py_ifc_info = Py_BuildValue( - "(KKKKKKKi)", - if2m->ifm_data.ifi_obytes, - if2m->ifm_data.ifi_ibytes, - if2m->ifm_data.ifi_opackets, - if2m->ifm_data.ifi_ipackets, - if2m->ifm_data.ifi_ierrors, - if2m->ifm_data.ifi_oerrors, - if2m->ifm_data.ifi_iqdrops, - 0); // dropout not supported - - if (!py_ifc_info) - goto error; - if (PyDict_SetItemString(py_retdict, ifc_name, py_ifc_info)) - goto error; - Py_CLEAR(py_ifc_info); - } - else { - continue; - } - } - - free(buf); - return py_retdict; - -error: - Py_XDECREF(py_ifc_info); - Py_DECREF(py_retdict); - if (buf != NULL) - free(buf); - return NULL; -} - - -/* - * Return a Python dict of tuples for disk I/O information - */ -static PyObject * -psutil_disk_io_counters(PyObject *self, PyObject *args) { - CFDictionaryRef parent_dict; - CFDictionaryRef props_dict; - CFDictionaryRef stats_dict; - io_registry_entry_t parent; - io_registry_entry_t disk; - io_iterator_t disk_list; - PyObject *py_disk_info = NULL; - PyObject *py_retdict = PyDict_New(); - - if (py_retdict == NULL) - return NULL; - - // Get list of disks - if (IOServiceGetMatchingServices(kIOMasterPortDefault, - IOServiceMatching(kIOMediaClass), - &disk_list) != kIOReturnSuccess) { - PyErr_SetString( - PyExc_RuntimeError, "unable to get the list of disks."); - goto error; - } - - // Iterate over disks - while ((disk = IOIteratorNext(disk_list)) != 0) { - py_disk_info = NULL; - parent_dict = NULL; - props_dict = NULL; - stats_dict = NULL; - - if (IORegistryEntryGetParentEntry(disk, kIOServicePlane, &parent) - != kIOReturnSuccess) { - PyErr_SetString(PyExc_RuntimeError, - "unable to get the disk's parent."); - IOObjectRelease(disk); - goto error; - } - - if (IOObjectConformsTo(parent, "IOBlockStorageDriver")) { - if (IORegistryEntryCreateCFProperties( - disk, - (CFMutableDictionaryRef *) &parent_dict, - kCFAllocatorDefault, - kNilOptions - ) != kIOReturnSuccess) - { - PyErr_SetString(PyExc_RuntimeError, - "unable to get the parent's properties."); - IOObjectRelease(disk); - IOObjectRelease(parent); - goto error; - } - - if (IORegistryEntryCreateCFProperties( - parent, - (CFMutableDictionaryRef *) &props_dict, - kCFAllocatorDefault, - kNilOptions - ) != kIOReturnSuccess) - { - PyErr_SetString(PyExc_RuntimeError, - "unable to get the disk properties."); - CFRelease(props_dict); - IOObjectRelease(disk); - IOObjectRelease(parent); - goto error; - } - - const int kMaxDiskNameSize = 64; - CFStringRef disk_name_ref = (CFStringRef)CFDictionaryGetValue( - parent_dict, CFSTR(kIOBSDNameKey)); - char disk_name[kMaxDiskNameSize]; - - CFStringGetCString(disk_name_ref, - disk_name, - kMaxDiskNameSize, - CFStringGetSystemEncoding()); - - stats_dict = (CFDictionaryRef)CFDictionaryGetValue( - props_dict, CFSTR(kIOBlockStorageDriverStatisticsKey)); - - if (stats_dict == NULL) { - PyErr_SetString(PyExc_RuntimeError, - "Unable to get disk stats."); - goto error; - } - - CFNumberRef number; - int64_t reads = 0; - int64_t writes = 0; - int64_t read_bytes = 0; - int64_t write_bytes = 0; - int64_t read_time = 0; - int64_t write_time = 0; - - // Get disk reads/writes - if ((number = (CFNumberRef)CFDictionaryGetValue( - stats_dict, - CFSTR(kIOBlockStorageDriverStatisticsReadsKey)))) - { - CFNumberGetValue(number, kCFNumberSInt64Type, &reads); - } - if ((number = (CFNumberRef)CFDictionaryGetValue( - stats_dict, - CFSTR(kIOBlockStorageDriverStatisticsWritesKey)))) - { - CFNumberGetValue(number, kCFNumberSInt64Type, &writes); - } - - // Get disk bytes read/written - if ((number = (CFNumberRef)CFDictionaryGetValue( - stats_dict, - CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey)))) - { - CFNumberGetValue(number, kCFNumberSInt64Type, &read_bytes); - } - if ((number = (CFNumberRef)CFDictionaryGetValue( - stats_dict, - CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey)))) - { - CFNumberGetValue(number, kCFNumberSInt64Type, &write_bytes); - } - - // Get disk time spent reading/writing (nanoseconds) - if ((number = (CFNumberRef)CFDictionaryGetValue( - stats_dict, - CFSTR(kIOBlockStorageDriverStatisticsTotalReadTimeKey)))) - { - CFNumberGetValue(number, kCFNumberSInt64Type, &read_time); - } - if ((number = (CFNumberRef)CFDictionaryGetValue( - stats_dict, - CFSTR(kIOBlockStorageDriverStatisticsTotalWriteTimeKey)))) - { - CFNumberGetValue(number, kCFNumberSInt64Type, &write_time); - } - - // Read/Write time on macOS comes back in nanoseconds and in psutil - // we've standardized on milliseconds so do the conversion. - py_disk_info = Py_BuildValue( - "(KKKKKK)", - reads, - writes, - read_bytes, - write_bytes, - read_time / 1000 / 1000, - write_time / 1000 / 1000); - if (!py_disk_info) - goto error; - if (PyDict_SetItemString(py_retdict, disk_name, py_disk_info)) - goto error; - Py_CLEAR(py_disk_info); - - CFRelease(parent_dict); - IOObjectRelease(parent); - CFRelease(props_dict); - IOObjectRelease(disk); - } - } - - IOObjectRelease (disk_list); - - return py_retdict; - -error: - Py_XDECREF(py_disk_info); - Py_DECREF(py_retdict); - return NULL; -} - - -/* - * Return currently connected users as a list of tuples. - */ -static PyObject * -psutil_users(PyObject *self, PyObject *args) { - struct utmpx *utx; - PyObject *py_username = NULL; - PyObject *py_tty = NULL; - PyObject *py_hostname = NULL; - PyObject *py_tuple = NULL; - PyObject *py_retlist = PyList_New(0); - - if (py_retlist == NULL) - return NULL; - while ((utx = getutxent()) != NULL) { - if (utx->ut_type != USER_PROCESS) - continue; - py_username = PyUnicode_DecodeFSDefault(utx->ut_user); - if (! py_username) - goto error; - py_tty = PyUnicode_DecodeFSDefault(utx->ut_line); - if (! py_tty) - goto error; - py_hostname = PyUnicode_DecodeFSDefault(utx->ut_host); - if (! py_hostname) - goto error; - py_tuple = Py_BuildValue( - "(OOOfi)", - py_username, // username - py_tty, // tty - py_hostname, // hostname - (float)utx->ut_tv.tv_sec, // start time - utx->ut_pid // process id - ); - if (!py_tuple) { - endutxent(); - goto error; - } - if (PyList_Append(py_retlist, py_tuple)) { - endutxent(); - goto error; - } - Py_CLEAR(py_username); - Py_CLEAR(py_tty); - Py_CLEAR(py_hostname); - Py_CLEAR(py_tuple); - } - - endutxent(); - return py_retlist; - -error: - Py_XDECREF(py_username); - Py_XDECREF(py_tty); - Py_XDECREF(py_hostname); - Py_XDECREF(py_tuple); - Py_DECREF(py_retlist); - return NULL; -} - - -/* - * Return CPU statistics. - */ -static PyObject * -psutil_cpu_stats(PyObject *self, PyObject *args) { - struct vmmeter vmstat; - kern_return_t ret; - mach_msg_type_number_t count = sizeof(vmstat) / sizeof(integer_t); - mach_port_t mport = mach_host_self(); - - ret = host_statistics(mport, HOST_VM_INFO, (host_info_t)&vmstat, &count); - if (ret != KERN_SUCCESS) { - PyErr_Format( - PyExc_RuntimeError, - "host_statistics(HOST_VM_INFO) failed: %s", - mach_error_string(ret)); - return NULL; - } - mach_port_deallocate(mach_task_self(), mport); - - return Py_BuildValue( - "IIIII", - vmstat.v_swtch, // ctx switches - vmstat.v_intr, // interrupts - vmstat.v_soft, // software interrupts - vmstat.v_syscall, // syscalls - vmstat.v_trap // traps - ); -} - - -/* - * Return battery information. - */ -static PyObject * -psutil_sensors_battery(PyObject *self, PyObject *args) { - PyObject *py_tuple = NULL; - CFTypeRef power_info = NULL; - CFArrayRef power_sources_list = NULL; - CFDictionaryRef power_sources_information = NULL; - CFNumberRef capacity_ref = NULL; - CFNumberRef time_to_empty_ref = NULL; - CFStringRef ps_state_ref = NULL; - uint32_t capacity; /* units are percent */ - int time_to_empty; /* units are minutes */ - int is_power_plugged; - - power_info = IOPSCopyPowerSourcesInfo(); - - if (!power_info) { - PyErr_SetString(PyExc_RuntimeError, - "IOPSCopyPowerSourcesInfo() syscall failed"); - goto error; - } - - power_sources_list = IOPSCopyPowerSourcesList(power_info); - if (!power_sources_list) { - PyErr_SetString(PyExc_RuntimeError, - "IOPSCopyPowerSourcesList() syscall failed"); - goto error; - } - - /* Should only get one source. But in practice, check for > 0 sources */ - if (!CFArrayGetCount(power_sources_list)) { - PyErr_SetString(PyExc_NotImplementedError, "no battery"); - goto error; - } - - power_sources_information = IOPSGetPowerSourceDescription( - power_info, CFArrayGetValueAtIndex(power_sources_list, 0)); - - capacity_ref = (CFNumberRef) CFDictionaryGetValue( - power_sources_information, CFSTR(kIOPSCurrentCapacityKey)); - if (!CFNumberGetValue(capacity_ref, kCFNumberSInt32Type, &capacity)) { - PyErr_SetString(PyExc_RuntimeError, - "No battery capacity infomration in power sources info"); - goto error; - } - - ps_state_ref = (CFStringRef) CFDictionaryGetValue( - power_sources_information, CFSTR(kIOPSPowerSourceStateKey)); - is_power_plugged = CFStringCompare( - ps_state_ref, CFSTR(kIOPSACPowerValue), 0) - == kCFCompareEqualTo; - - time_to_empty_ref = (CFNumberRef) CFDictionaryGetValue( - power_sources_information, CFSTR(kIOPSTimeToEmptyKey)); - if (!CFNumberGetValue(time_to_empty_ref, - kCFNumberIntType, &time_to_empty)) { - /* This value is recommended for non-Apple power sources, so it's not - * an error if it doesn't exist. We'll return -1 for "unknown" */ - /* A value of -1 indicates "Still Calculating the Time" also for - * apple power source */ - time_to_empty = -1; - } - - py_tuple = Py_BuildValue("Iii", - capacity, time_to_empty, is_power_plugged); - if (!py_tuple) { - goto error; - } - - CFRelease(power_info); - CFRelease(power_sources_list); - /* Caller should NOT release power_sources_information */ - - return py_tuple; - -error: - if (power_info) - CFRelease(power_info); - if (power_sources_list) - CFRelease(power_sources_list); - Py_XDECREF(py_tuple); - return NULL; -} - - -/* - * define the psutil C module methods and initialize the module. - */ static PyMethodDef mod_methods[] = { // --- per-process functions - - {"proc_kinfo_oneshot", psutil_proc_kinfo_oneshot, METH_VARARGS, - "Return multiple process info."}, - {"proc_pidtaskinfo_oneshot", psutil_proc_pidtaskinfo_oneshot, METH_VARARGS, - "Return multiple process info."}, - {"proc_name", psutil_proc_name, METH_VARARGS, - "Return process name"}, - {"proc_cmdline", psutil_proc_cmdline, METH_VARARGS, - "Return process cmdline as a list of cmdline arguments"}, - {"proc_environ", psutil_proc_environ, METH_VARARGS, - "Return process environment data"}, - {"proc_exe", psutil_proc_exe, METH_VARARGS, - "Return path of the process executable"}, - {"proc_cwd", psutil_proc_cwd, METH_VARARGS, - "Return process current working directory."}, - {"proc_memory_uss", psutil_proc_memory_uss, METH_VARARGS, - "Return process USS memory"}, - {"proc_threads", psutil_proc_threads, METH_VARARGS, - "Return process threads as a list of tuples"}, - {"proc_open_files", psutil_proc_open_files, METH_VARARGS, - "Return files opened by process as a list of tuples"}, - {"proc_num_fds", psutil_proc_num_fds, METH_VARARGS, - "Return the number of fds opened by process."}, - {"proc_connections", psutil_proc_connections, METH_VARARGS, - "Get process TCP and UDP connections as a list of tuples"}, + {"proc_cmdline", psutil_proc_cmdline, METH_VARARGS}, + {"proc_connections", psutil_proc_connections, METH_VARARGS}, + {"proc_cwd", psutil_proc_cwd, METH_VARARGS}, + {"proc_environ", psutil_proc_environ, METH_VARARGS}, + {"proc_exe", psutil_proc_exe, METH_VARARGS}, + {"proc_kinfo_oneshot", psutil_proc_kinfo_oneshot, METH_VARARGS}, + {"proc_memory_uss", psutil_proc_memory_uss, METH_VARARGS}, + {"proc_name", psutil_proc_name, METH_VARARGS}, + {"proc_num_fds", psutil_proc_num_fds, METH_VARARGS}, + {"proc_open_files", psutil_proc_open_files, METH_VARARGS}, + {"proc_pidtaskinfo_oneshot", psutil_proc_pidtaskinfo_oneshot, METH_VARARGS}, + {"proc_threads", psutil_proc_threads, METH_VARARGS}, // --- system-related functions - - {"pids", psutil_pids, METH_VARARGS, - "Returns a list of PIDs currently running on the system"}, - {"cpu_count_logical", psutil_cpu_count_logical, METH_VARARGS, - "Return number of logical CPUs on the system"}, - {"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS, - "Return number of physical CPUs on the system"}, - {"virtual_mem", psutil_virtual_mem, METH_VARARGS, - "Return system virtual memory stats"}, - {"swap_mem", psutil_swap_mem, METH_VARARGS, - "Return stats about swap memory, in bytes"}, - {"cpu_times", psutil_cpu_times, METH_VARARGS, - "Return system cpu times as a tuple (user, system, nice, idle, irc)"}, - {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS, - "Return system per-cpu times as a list of tuples"}, - {"cpu_freq", psutil_cpu_freq, METH_VARARGS, - "Return cpu current frequency"}, - {"boot_time", psutil_boot_time, METH_VARARGS, - "Return the system boot time expressed in seconds since the epoch."}, - {"disk_partitions", psutil_disk_partitions, METH_VARARGS, - "Return a list of tuples including device, mount point and " - "fs type for all partitions mounted on the system."}, - {"net_io_counters", psutil_net_io_counters, METH_VARARGS, - "Return dict of tuples of networks I/O information."}, - {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS, - "Return dict of tuples of disks I/O information."}, - {"users", psutil_users, METH_VARARGS, - "Return currently connected users as a list of tuples"}, - {"cpu_stats", psutil_cpu_stats, METH_VARARGS, - "Return CPU statistics"}, - {"sensors_battery", psutil_sensors_battery, METH_VARARGS, - "Return battery information."}, + {"boot_time", psutil_boot_time, METH_VARARGS}, + {"cpu_count_cores", psutil_cpu_count_cores, METH_VARARGS}, + {"cpu_count_logical", psutil_cpu_count_logical, METH_VARARGS}, + {"cpu_freq", psutil_cpu_freq, METH_VARARGS}, + {"cpu_stats", psutil_cpu_stats, METH_VARARGS}, + {"cpu_times", psutil_cpu_times, METH_VARARGS}, + {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS}, + {"disk_partitions", psutil_disk_partitions, METH_VARARGS}, + {"disk_usage_used", psutil_disk_usage_used, METH_VARARGS}, + {"net_io_counters", psutil_net_io_counters, METH_VARARGS}, + {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS}, + {"pids", psutil_pids, METH_VARARGS}, + {"sensors_battery", psutil_sensors_battery, METH_VARARGS}, + {"swap_mem", psutil_swap_mem, METH_VARARGS}, + {"users", psutil_users, METH_VARARGS}, + {"virtual_mem", psutil_virtual_mem, METH_VARARGS}, // --- others - {"set_testing", psutil_set_testing, METH_NOARGS, - "Set psutil in testing mode"}, + {"check_pid_range", psutil_check_pid_range, METH_VARARGS}, + {"set_debug", psutil_set_debug, METH_VARARGS}, {NULL, NULL, 0, NULL} }; @@ -1895,17 +134,6 @@ static PyMethodDef mod_methods[] = { if (PyModule_AddIntConstant(mod, "PSUTIL_CONN_NONE", PSUTIL_CONN_NONE)) INITERR; - // Exception. - ZombieProcessError = PyErr_NewException( - "_psutil_osx.ZombieProcessError", NULL, NULL); - if (ZombieProcessError == NULL) - INITERR; - Py_INCREF(ZombieProcessError); - if (PyModule_AddObject(mod, "ZombieProcessError", ZombieProcessError)) { - Py_DECREF(ZombieProcessError); - INITERR; - } - if (mod == NULL) INITERR; #if PY_MAJOR_VERSION >= 3 diff --git a/contrib/python/psutil/py3/psutil/_psutil_posix.c b/contrib/python/psutil/py3/psutil/_psutil_posix.c index 3447fc9017..ad0d4b17ee 100644 --- a/contrib/python/psutil/py3/psutil/_psutil_posix.c +++ b/contrib/python/psutil/py3/psutil/_psutil_posix.c @@ -147,7 +147,7 @@ psutil_pid_exists(pid_t pid) { */ void psutil_raise_for_pid(long pid, char *syscall) { - if (errno != 0) // unlikely + if (errno != 0) PyErr_SetFromOSErrnoWithSyscall(syscall); else if (psutil_pid_exists(pid) == 0) NoSuchProcess(syscall); @@ -429,6 +429,210 @@ error: return PyErr_SetFromErrno(PyExc_OSError); } +static int +append_flag(PyObject *py_retlist, const char * flag_name) +{ + PyObject *py_str = NULL; + +#if PY_MAJOR_VERSION >= 3 + py_str = PyUnicode_FromString(flag_name); +#else + py_str = PyString_FromString(flag_name); +#endif + if (! py_str) + return 0; + if (PyList_Append(py_retlist, py_str)) { + Py_DECREF(py_str); + return 0; + } + Py_CLEAR(py_str); + + return 1; +} + +/* + * Get all of the NIC flags and return them. + */ +static PyObject * +psutil_net_if_flags(PyObject *self, PyObject *args) { + char *nic_name; + int sock = -1; + int ret; + struct ifreq ifr; + PyObject *py_retlist = PyList_New(0); + short int flags; + + if (py_retlist == NULL) + return NULL; + + if (! PyArg_ParseTuple(args, "s", &nic_name)) + goto error; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) { + PyErr_SetFromOSErrnoWithSyscall("socket(SOCK_DGRAM)"); + goto error; + } + + PSUTIL_STRNCPY(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name)); + ret = ioctl(sock, SIOCGIFFLAGS, &ifr); + if (ret == -1) { + PyErr_SetFromOSErrnoWithSyscall("ioctl(SIOCGIFFLAGS)"); + goto error; + } + + close(sock); + sock = -1; + + flags = ifr.ifr_flags & 0xFFFF; + + // Linux/glibc IFF flags: https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/gnu/net/if.h;h=251418f82331c0426e58707fe4473d454893b132;hb=HEAD + // macOS IFF flags: https://opensource.apple.com/source/xnu/xnu-792/bsd/net/if.h.auto.html + // AIX IFF flags: https://www.ibm.com/support/pages/how-hexadecimal-flags-displayed-ifconfig-are-calculated + // FreeBSD IFF flags: https://www.freebsd.org/cgi/man.cgi?query=if_allmulti&apropos=0&sektion=0&manpath=FreeBSD+10-current&format=html + +#ifdef IFF_UP + // Available in (at least) Linux, macOS, AIX, BSD + if (flags & IFF_UP) + if (!append_flag(py_retlist, "up")) + goto error; +#endif +#ifdef IFF_BROADCAST + // Available in (at least) Linux, macOS, AIX, BSD + if (flags & IFF_BROADCAST) + if (!append_flag(py_retlist, "broadcast")) + goto error; +#endif +#ifdef IFF_DEBUG + // Available in (at least) Linux, macOS, BSD + if (flags & IFF_DEBUG) + if (!append_flag(py_retlist, "debug")) + goto error; +#endif +#ifdef IFF_LOOPBACK + // Available in (at least) Linux, macOS, BSD + if (flags & IFF_LOOPBACK) + if (!append_flag(py_retlist, "loopback")) + goto error; +#endif +#ifdef IFF_POINTOPOINT + // Available in (at least) Linux, macOS, BSD + if (flags & IFF_POINTOPOINT) + if (!append_flag(py_retlist, "pointopoint")) + goto error; +#endif +#ifdef IFF_NOTRAILERS + // Available in (at least) Linux, macOS, AIX + if (flags & IFF_NOTRAILERS) + if (!append_flag(py_retlist, "notrailers")) + goto error; +#endif +#ifdef IFF_RUNNING + // Available in (at least) Linux, macOS, AIX, BSD + if (flags & IFF_RUNNING) + if (!append_flag(py_retlist, "running")) + goto error; +#endif +#ifdef IFF_NOARP + // Available in (at least) Linux, macOS, BSD + if (flags & IFF_NOARP) + if (!append_flag(py_retlist, "noarp")) + goto error; +#endif +#ifdef IFF_PROMISC + // Available in (at least) Linux, macOS, BSD + if (flags & IFF_PROMISC) + if (!append_flag(py_retlist, "promisc")) + goto error; +#endif +#ifdef IFF_ALLMULTI + // Available in (at least) Linux, macOS, BSD + if (flags & IFF_ALLMULTI) + if (!append_flag(py_retlist, "allmulti")) + goto error; +#endif +#ifdef IFF_MASTER + // Available in (at least) Linux + if (flags & IFF_MASTER) + if (!append_flag(py_retlist, "master")) + goto error; +#endif +#ifdef IFF_SLAVE + // Available in (at least) Linux + if (flags & IFF_SLAVE) + if (!append_flag(py_retlist, "slave")) + goto error; +#endif +#ifdef IFF_MULTICAST + // Available in (at least) Linux, macOS, BSD + if (flags & IFF_MULTICAST) + if (!append_flag(py_retlist, "multicast")) + goto error; +#endif +#ifdef IFF_PORTSEL + // Available in (at least) Linux + if (flags & IFF_PORTSEL) + if (!append_flag(py_retlist, "portsel")) + goto error; +#endif +#ifdef IFF_AUTOMEDIA + // Available in (at least) Linux + if (flags & IFF_AUTOMEDIA) + if (!append_flag(py_retlist, "automedia")) + goto error; +#endif +#ifdef IFF_DYNAMIC + // Available in (at least) Linux + if (flags & IFF_DYNAMIC) + if (!append_flag(py_retlist, "dynamic")) + goto error; +#endif +#ifdef IFF_OACTIVE + // Available in (at least) macOS, BSD + if (flags & IFF_OACTIVE) + if (!append_flag(py_retlist, "oactive")) + goto error; +#endif +#ifdef IFF_SIMPLEX + // Available in (at least) macOS, AIX, BSD + if (flags & IFF_SIMPLEX) + if (!append_flag(py_retlist, "simplex")) + goto error; +#endif +#ifdef IFF_LINK0 + // Available in (at least) macOS, BSD + if (flags & IFF_LINK0) + if (!append_flag(py_retlist, "link0")) + goto error; +#endif +#ifdef IFF_LINK1 + // Available in (at least) macOS, BSD + if (flags & IFF_LINK1) + if (!append_flag(py_retlist, "link1")) + goto error; +#endif +#ifdef IFF_LINK2 + // Available in (at least) macOS, BSD + if (flags & IFF_LINK2) + if (!append_flag(py_retlist, "link2")) + goto error; +#endif +#ifdef IFF_D2 + // Available in (at least) AIX + if (flags & IFF_D2) + if (!append_flag(py_retlist, "d2")) + goto error; +#endif + + return py_retlist; + +error: + Py_DECREF(py_retlist); + if (sock != -1) + close(sock); + return NULL; +} + /* * Inspect NIC flags, returns a bool indicating whether the NIC is @@ -504,13 +708,14 @@ int psutil_get_nic_speed(int ifm_active) { case(IFM_1000_LX): // 1000baseLX - single-mode fiber case(IFM_1000_CX): // 1000baseCX - 150ohm STP #if defined(IFM_1000_TX) && !defined(PSUTIL_OPENBSD) + #define HAS_CASE_IFM_1000_TX 1 // FreeBSD 4 and others (but NOT OpenBSD) -> #define IFM_1000_T in net/if_media.h case(IFM_1000_TX): #endif #ifdef IFM_1000_FX case(IFM_1000_FX): #endif -#ifdef IFM_1000_T +#if defined(IFM_1000_T) && (!HAS_CASE_IFM_1000_TX || IFM_1000_T != IFM_1000_TX) case(IFM_1000_T): #endif return 1000; @@ -664,21 +869,15 @@ extern "C" { * define the psutil C module methods and initialize the module. */ static PyMethodDef mod_methods[] = { - {"getpriority", psutil_posix_getpriority, METH_VARARGS, - "Return process priority"}, - {"setpriority", psutil_posix_setpriority, METH_VARARGS, - "Set process priority"}, - {"net_if_addrs", psutil_net_if_addrs, METH_VARARGS, - "Retrieve NICs information"}, - {"net_if_mtu", psutil_net_if_mtu, METH_VARARGS, - "Retrieve NIC MTU"}, - {"net_if_is_running", psutil_net_if_is_running, METH_VARARGS, - "Return True if the NIC is running."}, - {"getpagesize", psutil_getpagesize_pywrapper, METH_VARARGS, - "Return memory page size."}, + {"getpagesize", psutil_getpagesize_pywrapper, METH_VARARGS}, + {"getpriority", psutil_posix_getpriority, METH_VARARGS}, + {"net_if_addrs", psutil_net_if_addrs, METH_VARARGS}, + {"net_if_flags", psutil_net_if_flags, METH_VARARGS}, + {"net_if_is_running", psutil_net_if_is_running, METH_VARARGS}, + {"net_if_mtu", psutil_net_if_mtu, METH_VARARGS}, + {"setpriority", psutil_posix_setpriority, METH_VARARGS}, #if defined(PSUTIL_BSD) || defined(PSUTIL_OSX) - {"net_if_duplex_speed", psutil_net_if_duplex_speed, METH_VARARGS, - "Return NIC stats."}, + {"net_if_duplex_speed", psutil_net_if_duplex_speed, METH_VARARGS}, #endif {NULL, NULL, 0, NULL} }; diff --git a/contrib/python/psutil/py3/psutil/_psutil_windows.c b/contrib/python/psutil/py3/psutil/_psutil_windows.c index a2154e923e..bb6e12ff80 100644 --- a/contrib/python/psutil/py3/psutil/_psutil_windows.c +++ b/contrib/python/psutil/py3/psutil/_psutil_windows.c @@ -14,1536 +14,25 @@ * - NtResumeProcess */ -// Fixes clash between winsock2.h and windows.h -#define WIN32_LEAN_AND_MEAN - #include <Python.h> #include <windows.h> -#include <Psapi.h> // memory_info(), memory_maps() -#include <signal.h> -#include <tlhelp32.h> // threads(), PROCESSENTRY32 - -// Link with Iphlpapi.lib -#pragma comment(lib, "IPHLPAPI.lib") #include "_psutil_common.h" -#include "arch/windows/security.h" -#include "arch/windows/process_utils.h" -#include "arch/windows/process_info.h" -#include "arch/windows/process_handles.h" -#include "arch/windows/disk.h" #include "arch/windows/cpu.h" +#include "arch/windows/disk.h" +#include "arch/windows/mem.h" #include "arch/windows/net.h" +#include "arch/windows/proc.h" +#include "arch/windows/proc_handles.h" +#include "arch/windows/proc_info.h" +#include "arch/windows/proc_utils.h" +#include "arch/windows/security.h" +#include "arch/windows/sensors.h" #include "arch/windows/services.h" #include "arch/windows/socks.h" +#include "arch/windows/sys.h" #include "arch/windows/wmi.h" -// Raised by Process.wait(). -static PyObject *TimeoutExpired; -static PyObject *TimeoutAbandoned; - - -/* - * Return the number of logical, active CPUs. Return 0 if undetermined. - * See discussion at: https://bugs.python.org/issue33166#msg314631 - */ -unsigned int -psutil_get_num_cpus(int fail_on_err) { - unsigned int ncpus = 0; - - // Minimum requirement: Windows 7 - if (GetActiveProcessorCount != NULL) { - ncpus = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS); - if ((ncpus == 0) && (fail_on_err == 1)) { - PyErr_SetFromWindowsErr(0); - } - } - else { - psutil_debug("GetActiveProcessorCount() not available; " - "using GetSystemInfo()"); - ncpus = (unsigned int)PSUTIL_SYSTEM_INFO.dwNumberOfProcessors; - if ((ncpus <= 0) && (fail_on_err == 1)) { - PyErr_SetString( - PyExc_RuntimeError, - "GetSystemInfo() failed to retrieve CPU count"); - } - } - return ncpus; -} - - -/* - * Return a Python float representing the system uptime expressed in seconds - * since the epoch. - */ -static PyObject * -psutil_boot_time(PyObject *self, PyObject *args) { - ULONGLONG upTime; - FILETIME fileTime; - - GetSystemTimeAsFileTime(&fileTime); - // Number of milliseconds that have elapsed since the system was started. - upTime = GetTickCount64() / 1000ull; - return Py_BuildValue("d", psutil_FiletimeToUnixTime(fileTime) - upTime); -} - - -/* - * Return 1 if PID exists in the current process list, else 0. - */ -static PyObject * -psutil_pid_exists(PyObject *self, PyObject *args) { - DWORD pid; - int status; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - - status = psutil_pid_is_running(pid); - if (-1 == status) - return NULL; // exception raised in psutil_pid_is_running() - return PyBool_FromLong(status); -} - - -/* - * Return a Python list of all the PIDs running on the system. - */ -static PyObject * -psutil_pids(PyObject *self, PyObject *args) { - DWORD *proclist = NULL; - DWORD numberOfReturnedPIDs; - DWORD i; - PyObject *py_pid = NULL; - PyObject *py_retlist = PyList_New(0); - - if (py_retlist == NULL) - return NULL; - proclist = psutil_get_pids(&numberOfReturnedPIDs); - if (proclist == NULL) - goto error; - - for (i = 0; i < numberOfReturnedPIDs; i++) { - py_pid = PyLong_FromPid(proclist[i]); - if (!py_pid) - goto error; - if (PyList_Append(py_retlist, py_pid)) - goto error; - Py_CLEAR(py_pid); - } - - // free C array allocated for PIDs - free(proclist); - return py_retlist; - -error: - Py_XDECREF(py_pid); - Py_DECREF(py_retlist); - if (proclist != NULL) - free(proclist); - return NULL; -} - - -/* - * Kill a process given its PID. - */ -static PyObject * -psutil_proc_kill(PyObject *self, PyObject *args) { - HANDLE hProcess; - DWORD pid; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - if (pid == 0) - return AccessDenied("automatically set for PID 0"); - - hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); - hProcess = psutil_check_phandle(hProcess, pid, 0); - if (hProcess == NULL) { - return NULL; - } - - if (! TerminateProcess(hProcess, SIGTERM)) { - // ERROR_ACCESS_DENIED may happen if the process already died. See: - // https://github.com/giampaolo/psutil/issues/1099 - // http://bugs.python.org/issue14252 - if (GetLastError() != ERROR_ACCESS_DENIED) { - PyErr_SetFromOSErrnoWithSyscall("TerminateProcess"); - return NULL; - } - } - - CloseHandle(hProcess); - Py_RETURN_NONE; -} - - -/* - * Wait for process to terminate and return its exit code. - */ -static PyObject * -psutil_proc_wait(PyObject *self, PyObject *args) { - HANDLE hProcess; - DWORD ExitCode; - DWORD retVal; - DWORD pid; - long timeout; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID "l", &pid, &timeout)) - return NULL; - if (pid == 0) - return AccessDenied("automatically set for PID 0"); - - hProcess = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, - FALSE, pid); - if (hProcess == NULL) { - if (GetLastError() == ERROR_INVALID_PARAMETER) { - // no such process; we do not want to raise NSP but - // return None instead. - Py_RETURN_NONE; - } - else { - PyErr_SetFromOSErrnoWithSyscall("OpenProcess"); - return NULL; - } - } - - // wait until the process has terminated - Py_BEGIN_ALLOW_THREADS - retVal = WaitForSingleObject(hProcess, timeout); - Py_END_ALLOW_THREADS - - // handle return code - if (retVal == WAIT_FAILED) { - PyErr_SetFromOSErrnoWithSyscall("WaitForSingleObject"); - CloseHandle(hProcess); - return NULL; - } - if (retVal == WAIT_TIMEOUT) { - PyErr_SetString(TimeoutExpired, - "WaitForSingleObject() returned WAIT_TIMEOUT"); - CloseHandle(hProcess); - return NULL; - } - if (retVal == WAIT_ABANDONED) { - psutil_debug("WaitForSingleObject() -> WAIT_ABANDONED"); - PyErr_SetString(TimeoutAbandoned, - "WaitForSingleObject() returned WAIT_ABANDONED"); - CloseHandle(hProcess); - return NULL; - } - - // WaitForSingleObject() returned WAIT_OBJECT_0. It means the - // process is gone so we can get its process exit code. The PID - // may still stick around though but we'll handle that from Python. - if (GetExitCodeProcess(hProcess, &ExitCode) == 0) { - PyErr_SetFromOSErrnoWithSyscall("GetExitCodeProcess"); - CloseHandle(hProcess); - return NULL; - } - - CloseHandle(hProcess); - -#if PY_MAJOR_VERSION >= 3 - return PyLong_FromLong((long) ExitCode); -#else - return PyInt_FromLong((long) ExitCode); -#endif -} - - -/* - * Return a Python tuple (user_time, kernel_time) - */ -static PyObject * -psutil_proc_times(PyObject *self, PyObject *args) { - DWORD pid; - HANDLE hProcess; - FILETIME ftCreate, ftExit, ftKernel, ftUser; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - - hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); - - if (hProcess == NULL) - return NULL; - if (! GetProcessTimes(hProcess, &ftCreate, &ftExit, &ftKernel, &ftUser)) { - if (GetLastError() == ERROR_ACCESS_DENIED) { - // usually means the process has died so we throw a NoSuchProcess - // here - NoSuchProcess("GetProcessTimes -> ERROR_ACCESS_DENIED"); - } - else { - PyErr_SetFromWindowsErr(0); - } - CloseHandle(hProcess); - return NULL; - } - - CloseHandle(hProcess); - - /* - * User and kernel times are represented as a FILETIME structure - * which contains a 64-bit value representing the number of - * 100-nanosecond intervals since January 1, 1601 (UTC): - * http://msdn.microsoft.com/en-us/library/ms724284(VS.85).aspx - * To convert it into a float representing the seconds that the - * process has executed in user/kernel mode I borrowed the code - * below from Python's Modules/posixmodule.c - */ - return Py_BuildValue( - "(ddd)", - (double)(ftUser.dwHighDateTime * HI_T + \ - ftUser.dwLowDateTime * LO_T), - (double)(ftKernel.dwHighDateTime * HI_T + \ - ftKernel.dwLowDateTime * LO_T), - psutil_FiletimeToUnixTime(ftCreate) - ); -} - - -/* - * Return process cmdline as a Python list of cmdline arguments. - */ -static PyObject * -psutil_proc_cmdline(PyObject *self, PyObject *args, PyObject *kwdict) { - DWORD pid; - int pid_return; - int use_peb; - PyObject *py_usepeb = Py_True; - static char *keywords[] = {"pid", "use_peb", NULL}; - - if (!PyArg_ParseTupleAndKeywords(args, kwdict, _Py_PARSE_PID "|O", - keywords, &pid, &py_usepeb)) - { - return NULL; - } - if ((pid == 0) || (pid == 4)) - return Py_BuildValue("[]"); - - pid_return = psutil_pid_is_running(pid); - if (pid_return == 0) - return NoSuchProcess("psutil_pid_is_running -> 0"); - if (pid_return == -1) - return NULL; - - use_peb = (py_usepeb == Py_True) ? 1 : 0; - return psutil_get_cmdline(pid, use_peb); -} - - -/* - * Return process cmdline as a Python list of cmdline arguments. - */ -static PyObject * -psutil_proc_environ(PyObject *self, PyObject *args) { - DWORD pid; - int pid_return; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - if ((pid == 0) || (pid == 4)) - return Py_BuildValue("s", ""); - - pid_return = psutil_pid_is_running(pid); - if (pid_return == 0) - return NoSuchProcess("psutil_pid_is_running -> 0"); - if (pid_return == -1) - return NULL; - - return psutil_get_environ(pid); -} - - -/* - * Return process executable path. Works for all processes regardless of - * privilege. NtQuerySystemInformation has some sort of internal cache, - * since it succeeds even when a process is gone (but not if a PID never - * existed). - */ -static PyObject * -psutil_proc_exe(PyObject *self, PyObject *args) { - DWORD pid; - NTSTATUS status; - PVOID buffer; - ULONG bufferSize = 0x100; - SYSTEM_PROCESS_ID_INFORMATION processIdInfo; - PyObject *py_exe; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - - if (pid == 0) - return AccessDenied("automatically set for PID 0"); - - buffer = MALLOC_ZERO(bufferSize); - if (! buffer) - return PyErr_NoMemory(); - processIdInfo.ProcessId = (HANDLE)(ULONG_PTR)pid; - processIdInfo.ImageName.Length = 0; - processIdInfo.ImageName.MaximumLength = (USHORT)bufferSize; - processIdInfo.ImageName.Buffer = buffer; - - status = NtQuerySystemInformation( - SystemProcessIdInformation, - &processIdInfo, - sizeof(SYSTEM_PROCESS_ID_INFORMATION), - NULL); - - if (status == STATUS_INFO_LENGTH_MISMATCH) { - // Required length is stored in MaximumLength. - FREE(buffer); - buffer = MALLOC_ZERO(processIdInfo.ImageName.MaximumLength); - if (! buffer) - return PyErr_NoMemory(); - processIdInfo.ImageName.Buffer = buffer; - - status = NtQuerySystemInformation( - SystemProcessIdInformation, - &processIdInfo, - sizeof(SYSTEM_PROCESS_ID_INFORMATION), - NULL); - } - - if (! NT_SUCCESS(status)) { - FREE(buffer); - if (psutil_pid_is_running(pid) == 0) - NoSuchProcess("psutil_pid_is_running -> 0"); - else - psutil_SetFromNTStatusErr(status, "NtQuerySystemInformation"); - return NULL; - } - - if (processIdInfo.ImageName.Buffer == NULL) { - // Happens for PID 4. - py_exe = Py_BuildValue("s", ""); - } - else { - py_exe = PyUnicode_FromWideChar(processIdInfo.ImageName.Buffer, - processIdInfo.ImageName.Length / 2); - } - FREE(buffer); - return py_exe; -} - - -/* - * Return process memory information as a Python tuple. - */ -static PyObject * -psutil_proc_memory_info(PyObject *self, PyObject *args) { - HANDLE hProcess; - DWORD pid; - PROCESS_MEMORY_COUNTERS_EX cnt; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - - hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); - if (NULL == hProcess) - return NULL; - - if (! GetProcessMemoryInfo(hProcess, (PPROCESS_MEMORY_COUNTERS)&cnt, - sizeof(cnt))) { - PyErr_SetFromWindowsErr(0); - CloseHandle(hProcess); - return NULL; - } - CloseHandle(hProcess); - - // PROCESS_MEMORY_COUNTERS values are defined as SIZE_T which on 64bits - // is an (unsigned long long) and on 32bits is an (unsigned int). - // "_WIN64" is defined if we're running a 64bit Python interpreter not - // exclusively if the *system* is 64bit. -#if defined(_WIN64) - return Py_BuildValue( - "(kKKKKKKKKK)", - cnt.PageFaultCount, // unsigned long - (unsigned long long)cnt.PeakWorkingSetSize, - (unsigned long long)cnt.WorkingSetSize, - (unsigned long long)cnt.QuotaPeakPagedPoolUsage, - (unsigned long long)cnt.QuotaPagedPoolUsage, - (unsigned long long)cnt.QuotaPeakNonPagedPoolUsage, - (unsigned long long)cnt.QuotaNonPagedPoolUsage, - (unsigned long long)cnt.PagefileUsage, - (unsigned long long)cnt.PeakPagefileUsage, - (unsigned long long)cnt.PrivateUsage); -#else - return Py_BuildValue( - "(kIIIIIIIII)", - cnt.PageFaultCount, // unsigned long - (unsigned int)cnt.PeakWorkingSetSize, - (unsigned int)cnt.WorkingSetSize, - (unsigned int)cnt.QuotaPeakPagedPoolUsage, - (unsigned int)cnt.QuotaPagedPoolUsage, - (unsigned int)cnt.QuotaPeakNonPagedPoolUsage, - (unsigned int)cnt.QuotaNonPagedPoolUsage, - (unsigned int)cnt.PagefileUsage, - (unsigned int)cnt.PeakPagefileUsage, - (unsigned int)cnt.PrivateUsage); -#endif -} - - -static int -psutil_GetProcWsetInformation( - DWORD pid, - HANDLE hProcess, - PMEMORY_WORKING_SET_INFORMATION *wSetInfo) -{ - NTSTATUS status; - PVOID buffer; - SIZE_T bufferSize; - - bufferSize = 0x8000; - buffer = MALLOC_ZERO(bufferSize); - if (! buffer) { - PyErr_NoMemory(); - return 1; - } - - while ((status = NtQueryVirtualMemory( - hProcess, - NULL, - MemoryWorkingSetInformation, - buffer, - bufferSize, - NULL)) == STATUS_INFO_LENGTH_MISMATCH) - { - FREE(buffer); - bufferSize *= 2; - // Fail if we're resizing the buffer to something very large. - if (bufferSize > 256 * 1024 * 1024) { - PyErr_SetString(PyExc_RuntimeError, - "NtQueryVirtualMemory bufsize is too large"); - return 1; - } - buffer = MALLOC_ZERO(bufferSize); - if (! buffer) { - PyErr_NoMemory(); - return 1; - } - } - - if (!NT_SUCCESS(status)) { - if (status == STATUS_ACCESS_DENIED) { - AccessDenied("NtQueryVirtualMemory -> STATUS_ACCESS_DENIED"); - } - else if (psutil_pid_is_running(pid) == 0) { - NoSuchProcess("psutil_pid_is_running -> 0"); - } - else { - PyErr_Clear(); - psutil_SetFromNTStatusErr( - status, "NtQueryVirtualMemory(MemoryWorkingSetInformation)"); - } - HeapFree(GetProcessHeap(), 0, buffer); - return 1; - } - - *wSetInfo = (PMEMORY_WORKING_SET_INFORMATION)buffer; - return 0; -} - - -/* - * Returns the USS of the process. - * Reference: - * https://dxr.mozilla.org/mozilla-central/source/xpcom/base/ - * nsMemoryReporterManager.cpp - */ -static PyObject * -psutil_proc_memory_uss(PyObject *self, PyObject *args) { - DWORD pid; - HANDLE hProcess; - PSUTIL_PROCESS_WS_COUNTERS wsCounters; - PMEMORY_WORKING_SET_INFORMATION wsInfo; - ULONG_PTR i; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_INFORMATION); - if (hProcess == NULL) - return NULL; - - if (psutil_GetProcWsetInformation(pid, hProcess, &wsInfo) != 0) { - CloseHandle(hProcess); - return NULL; - } - memset(&wsCounters, 0, sizeof(PSUTIL_PROCESS_WS_COUNTERS)); - - for (i = 0; i < wsInfo->NumberOfEntries; i++) { - // This is what ProcessHacker does. - /* - wsCounters.NumberOfPages++; - if (wsInfo->WorkingSetInfo[i].ShareCount > 1) - wsCounters.NumberOfSharedPages++; - if (wsInfo->WorkingSetInfo[i].ShareCount == 0) - wsCounters.NumberOfPrivatePages++; - if (wsInfo->WorkingSetInfo[i].Shared) - wsCounters.NumberOfShareablePages++; - */ - - // This is what we do: count shared pages that only one process - // is using as private (USS). - if (!wsInfo->WorkingSetInfo[i].Shared || - wsInfo->WorkingSetInfo[i].ShareCount <= 1) { - wsCounters.NumberOfPrivatePages++; - } - } - - HeapFree(GetProcessHeap(), 0, wsInfo); - CloseHandle(hProcess); - - return Py_BuildValue("I", wsCounters.NumberOfPrivatePages); -} - - -/* - * Return a Python integer indicating the total amount of physical memory - * in bytes. - */ -static PyObject * -psutil_virtual_mem(PyObject *self, PyObject *args) { - MEMORYSTATUSEX memInfo; - memInfo.dwLength = sizeof(MEMORYSTATUSEX); - - if (! GlobalMemoryStatusEx(&memInfo)) { - PyErr_SetFromWindowsErr(0); - return NULL; - } - return Py_BuildValue("(LLLLLL)", - memInfo.ullTotalPhys, // total - memInfo.ullAvailPhys, // avail - memInfo.ullTotalPageFile, // total page file - memInfo.ullAvailPageFile, // avail page file - memInfo.ullTotalVirtual, // total virtual - memInfo.ullAvailVirtual); // avail virtual -} - - -/* - * Return process current working directory as a Python string. - */ -static PyObject * -psutil_proc_cwd(PyObject *self, PyObject *args) { - DWORD pid; - int pid_return; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - - pid_return = psutil_pid_is_running(pid); - if (pid_return == 0) - return NoSuchProcess("psutil_pid_is_running -> 0"); - if (pid_return == -1) - return NULL; - - return psutil_get_cwd(pid); -} - - -/* - * Resume or suspends a process - */ -static PyObject * -psutil_proc_suspend_or_resume(PyObject *self, PyObject *args) { - DWORD pid; - NTSTATUS status; - HANDLE hProcess; - PyObject* suspend; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID "O", &pid, &suspend)) - return NULL; - - hProcess = psutil_handle_from_pid(pid, PROCESS_SUSPEND_RESUME); - if (hProcess == NULL) - return NULL; - - if (PyObject_IsTrue(suspend)) - status = NtSuspendProcess(hProcess); - else - status = NtResumeProcess(hProcess); - - if (! NT_SUCCESS(status)) { - CloseHandle(hProcess); - return psutil_SetFromNTStatusErr(status, "NtSuspend|ResumeProcess"); - } - - CloseHandle(hProcess); - Py_RETURN_NONE; -} - - -static PyObject * -psutil_proc_threads(PyObject *self, PyObject *args) { - HANDLE hThread = NULL; - THREADENTRY32 te32 = {0}; - DWORD pid; - int pid_return; - int rc; - FILETIME ftDummy, ftKernel, ftUser; - HANDLE hThreadSnap = NULL; - PyObject *py_tuple = NULL; - PyObject *py_retlist = PyList_New(0); - - if (py_retlist == NULL) - return NULL; - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - goto error; - if (pid == 0) { - // raise AD instead of returning 0 as procexp is able to - // retrieve useful information somehow - AccessDenied("forced for PID 0"); - goto error; - } - - pid_return = psutil_pid_is_running(pid); - if (pid_return == 0) { - NoSuchProcess("psutil_pid_is_running -> 0"); - goto error; - } - if (pid_return == -1) - goto error; - - hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); - if (hThreadSnap == INVALID_HANDLE_VALUE) { - PyErr_SetFromOSErrnoWithSyscall("CreateToolhelp32Snapshot"); - goto error; - } - - // Fill in the size of the structure before using it - te32.dwSize = sizeof(THREADENTRY32); - - if (! Thread32First(hThreadSnap, &te32)) { - PyErr_SetFromOSErrnoWithSyscall("Thread32First"); - goto error; - } - - // Walk the thread snapshot to find all threads of the process. - // If the thread belongs to the process, increase the counter. - do { - if (te32.th32OwnerProcessID == pid) { - py_tuple = NULL; - hThread = NULL; - hThread = OpenThread(THREAD_QUERY_INFORMATION, - FALSE, te32.th32ThreadID); - if (hThread == NULL) { - // thread has disappeared on us - continue; - } - - rc = GetThreadTimes(hThread, &ftDummy, &ftDummy, &ftKernel, - &ftUser); - if (rc == 0) { - PyErr_SetFromOSErrnoWithSyscall("GetThreadTimes"); - goto error; - } - - /* - * User and kernel times are represented as a FILETIME structure - * which contains a 64-bit value representing the number of - * 100-nanosecond intervals since January 1, 1601 (UTC): - * http://msdn.microsoft.com/en-us/library/ms724284(VS.85).aspx - * To convert it into a float representing the seconds that the - * process has executed in user/kernel mode I borrowed the code - * below from Python's Modules/posixmodule.c - */ - py_tuple = Py_BuildValue( - "kdd", - te32.th32ThreadID, - (double)(ftUser.dwHighDateTime * HI_T + \ - ftUser.dwLowDateTime * LO_T), - (double)(ftKernel.dwHighDateTime * HI_T + \ - ftKernel.dwLowDateTime * LO_T)); - if (!py_tuple) - goto error; - if (PyList_Append(py_retlist, py_tuple)) - goto error; - Py_CLEAR(py_tuple); - - CloseHandle(hThread); - } - } while (Thread32Next(hThreadSnap, &te32)); - - CloseHandle(hThreadSnap); - return py_retlist; - -error: - Py_XDECREF(py_tuple); - Py_DECREF(py_retlist); - if (hThread != NULL) - CloseHandle(hThread); - if (hThreadSnap != NULL) - CloseHandle(hThreadSnap); - return NULL; -} - - -static PyObject * -psutil_proc_open_files(PyObject *self, PyObject *args) { - DWORD pid; - HANDLE processHandle; - DWORD access = PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION; - PyObject *py_retlist; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - - processHandle = psutil_handle_from_pid(pid, access); - if (processHandle == NULL) - return NULL; - - py_retlist = psutil_get_open_files(pid, processHandle); - CloseHandle(processHandle); - return py_retlist; -} - - -static PTOKEN_USER -_psutil_user_token_from_pid(DWORD pid) { - HANDLE hProcess = NULL; - HANDLE hToken = NULL; - PTOKEN_USER userToken = NULL; - ULONG bufferSize = 0x100; - - hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); - if (hProcess == NULL) - return NULL; - - if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) { - PyErr_SetFromOSErrnoWithSyscall("OpenProcessToken"); - goto error; - } - - // Get the user SID. - while (1) { - userToken = malloc(bufferSize); - if (userToken == NULL) { - PyErr_NoMemory(); - goto error; - } - if (!GetTokenInformation(hToken, TokenUser, userToken, bufferSize, - &bufferSize)) - { - if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { - free(userToken); - continue; - } - else { - PyErr_SetFromOSErrnoWithSyscall("GetTokenInformation"); - goto error; - } - } - break; - } - - CloseHandle(hProcess); - CloseHandle(hToken); - return userToken; - -error: - if (hProcess != NULL) - CloseHandle(hProcess); - if (hToken != NULL) - CloseHandle(hToken); - return NULL; -} - - -/* - * Return process username as a "DOMAIN//USERNAME" string. - */ -static PyObject * -psutil_proc_username(PyObject *self, PyObject *args) { - DWORD pid; - PTOKEN_USER userToken = NULL; - WCHAR *userName = NULL; - WCHAR *domainName = NULL; - ULONG nameSize = 0x100; - ULONG domainNameSize = 0x100; - SID_NAME_USE nameUse; - PyObject *py_username = NULL; - PyObject *py_domain = NULL; - PyObject *py_tuple = NULL; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - userToken = _psutil_user_token_from_pid(pid); - if (userToken == NULL) - return NULL; - - // resolve the SID to a name - while (1) { - userName = malloc(nameSize * sizeof(WCHAR)); - if (userName == NULL) { - PyErr_NoMemory(); - goto error; - } - domainName = malloc(domainNameSize * sizeof(WCHAR)); - if (domainName == NULL) { - PyErr_NoMemory(); - goto error; - } - if (!LookupAccountSidW(NULL, userToken->User.Sid, userName, &nameSize, - domainName, &domainNameSize, &nameUse)) - { - if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { - free(userName); - free(domainName); - continue; - } - else if (GetLastError() == ERROR_NONE_MAPPED) { - // From MS doc: - // https://docs.microsoft.com/en-us/windows/win32/api/winbase/ - // nf-winbase-lookupaccountsida - // If the function cannot find an account name for the SID, - // GetLastError returns ERROR_NONE_MAPPED. This can occur if - // a network time-out prevents the function from finding the - // name. It also occurs for SIDs that have no corresponding - // account name, such as a logon SID that identifies a logon - // session. - AccessDenied("LookupAccountSidW -> ERROR_NONE_MAPPED"); - goto error; - } - else { - PyErr_SetFromOSErrnoWithSyscall("LookupAccountSidW"); - goto error; - } - } - break; - } - - py_domain = PyUnicode_FromWideChar(domainName, wcslen(domainName)); - if (! py_domain) - goto error; - py_username = PyUnicode_FromWideChar(userName, wcslen(userName)); - if (! py_username) - goto error; - py_tuple = Py_BuildValue("OO", py_domain, py_username); - if (! py_tuple) - goto error; - Py_DECREF(py_domain); - Py_DECREF(py_username); - - free(userName); - free(domainName); - free(userToken); - return py_tuple; - -error: - if (userName != NULL) - free(userName); - if (domainName != NULL) - free(domainName); - if (userToken != NULL) - free(userToken); - Py_XDECREF(py_domain); - Py_XDECREF(py_username); - Py_XDECREF(py_tuple); - return NULL; -} - - -/* - * Get process priority as a Python integer. - */ -static PyObject * -psutil_proc_priority_get(PyObject *self, PyObject *args) { - DWORD pid; - DWORD priority; - HANDLE hProcess; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - - hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); - if (hProcess == NULL) - return NULL; - - priority = GetPriorityClass(hProcess); - if (priority == 0) { - PyErr_SetFromWindowsErr(0); - CloseHandle(hProcess); - return NULL; - } - CloseHandle(hProcess); - return Py_BuildValue("i", priority); -} - - -/* - * Set process priority. - */ -static PyObject * -psutil_proc_priority_set(PyObject *self, PyObject *args) { - DWORD pid; - int priority; - int retval; - HANDLE hProcess; - DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID "i", &pid, &priority)) - return NULL; - hProcess = psutil_handle_from_pid(pid, access); - if (hProcess == NULL) - return NULL; - - retval = SetPriorityClass(hProcess, priority); - if (retval == 0) { - PyErr_SetFromWindowsErr(0); - CloseHandle(hProcess); - return NULL; - } - - CloseHandle(hProcess); - Py_RETURN_NONE; -} - - -/* - * Get process IO priority as a Python integer. - */ -static PyObject * -psutil_proc_io_priority_get(PyObject *self, PyObject *args) { - DWORD pid; - HANDLE hProcess; - DWORD IoPriority; - NTSTATUS status; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - - hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); - if (hProcess == NULL) - return NULL; - - status = NtQueryInformationProcess( - hProcess, - ProcessIoPriority, - &IoPriority, - sizeof(DWORD), - NULL - ); - - CloseHandle(hProcess); - if (! NT_SUCCESS(status)) - return psutil_SetFromNTStatusErr(status, "NtQueryInformationProcess"); - return Py_BuildValue("i", IoPriority); -} - - -/* - * Set process IO priority. - */ -static PyObject * -psutil_proc_io_priority_set(PyObject *self, PyObject *args) { - DWORD pid; - DWORD prio; - HANDLE hProcess; - NTSTATUS status; - DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID "i", &pid, &prio)) - return NULL; - - hProcess = psutil_handle_from_pid(pid, access); - if (hProcess == NULL) - return NULL; - - status = NtSetInformationProcess( - hProcess, - ProcessIoPriority, - (PVOID)&prio, - sizeof(DWORD) - ); - - CloseHandle(hProcess); - if (! NT_SUCCESS(status)) - return psutil_SetFromNTStatusErr(status, "NtSetInformationProcess"); - Py_RETURN_NONE; -} - - -/* - * Return a Python tuple referencing process I/O counters. - */ -static PyObject * -psutil_proc_io_counters(PyObject *self, PyObject *args) { - DWORD pid; - HANDLE hProcess; - IO_COUNTERS IoCounters; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); - if (NULL == hProcess) - return NULL; - - if (! GetProcessIoCounters(hProcess, &IoCounters)) { - PyErr_SetFromWindowsErr(0); - CloseHandle(hProcess); - return NULL; - } - - CloseHandle(hProcess); - return Py_BuildValue("(KKKKKK)", - IoCounters.ReadOperationCount, - IoCounters.WriteOperationCount, - IoCounters.ReadTransferCount, - IoCounters.WriteTransferCount, - IoCounters.OtherOperationCount, - IoCounters.OtherTransferCount); -} - - -/* - * Return process CPU affinity as a bitmask - */ -static PyObject * -psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args) { - DWORD pid; - HANDLE hProcess; - DWORD_PTR proc_mask; - DWORD_PTR system_mask; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); - if (hProcess == NULL) { - return NULL; - } - if (GetProcessAffinityMask(hProcess, &proc_mask, &system_mask) == 0) { - PyErr_SetFromWindowsErr(0); - CloseHandle(hProcess); - return NULL; - } - - CloseHandle(hProcess); -#ifdef _WIN64 - return Py_BuildValue("K", (unsigned long long)proc_mask); -#else - return Py_BuildValue("k", (unsigned long)proc_mask); -#endif -} - - -/* - * Set process CPU affinity - */ -static PyObject * -psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) { - DWORD pid; - HANDLE hProcess; - DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION; - DWORD_PTR mask; - -#ifdef _WIN64 - if (! PyArg_ParseTuple(args, _Py_PARSE_PID "K", &pid, &mask)) -#else - if (! PyArg_ParseTuple(args, _Py_PARSE_PID "k", &pid, &mask)) -#endif - { - return NULL; - } - hProcess = psutil_handle_from_pid(pid, access); - if (hProcess == NULL) - return NULL; - - if (SetProcessAffinityMask(hProcess, mask) == 0) { - PyErr_SetFromWindowsErr(0); - CloseHandle(hProcess); - return NULL; - } - - CloseHandle(hProcess); - Py_RETURN_NONE; -} - - -/* - * Return True if all process threads are in waiting/suspended state. - */ -static PyObject * -psutil_proc_is_suspended(PyObject *self, PyObject *args) { - DWORD pid; - ULONG i; - PSYSTEM_PROCESS_INFORMATION process; - PVOID buffer; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - if (! psutil_get_proc_info(pid, &process, &buffer)) - return NULL; - for (i = 0; i < process->NumberOfThreads; i++) { - if (process->Threads[i].ThreadState != Waiting || - process->Threads[i].WaitReason != Suspended) - { - free(buffer); - Py_RETURN_FALSE; - } - } - free(buffer); - Py_RETURN_TRUE; -} - - -/* - * Return a Python dict of tuples for disk I/O information - */ -static PyObject * -psutil_users(PyObject *self, PyObject *args) { - HANDLE hServer = WTS_CURRENT_SERVER_HANDLE; - LPWSTR buffer_user = NULL; - LPWSTR buffer_addr = NULL; - LPWSTR buffer_info = NULL; - PWTS_SESSION_INFOW sessions = NULL; - DWORD count; - DWORD i; - DWORD sessionId; - DWORD bytes; - PWTS_CLIENT_ADDRESS address; - char address_str[50]; - PWTSINFOW wts_info; - PyObject *py_tuple = NULL; - PyObject *py_address = NULL; - PyObject *py_username = NULL; - PyObject *py_retlist = PyList_New(0); - - if (py_retlist == NULL) - return NULL; - - if (WTSEnumerateSessionsW == NULL || - WTSQuerySessionInformationW == NULL || - WTSFreeMemory == NULL) { - // If we don't run in an environment that is a Remote Desktop Services environment - // the Wtsapi32 proc might not be present. - // https://docs.microsoft.com/en-us/windows/win32/termserv/run-time-linking-to-wtsapi32-dll - return py_retlist; - } - - if (WTSEnumerateSessionsW(hServer, 0, 1, &sessions, &count) == 0) { - if (ERROR_CALL_NOT_IMPLEMENTED == GetLastError()) { - // On Windows Nano server, the Wtsapi32 API can be present, but return WinError 120. - return py_retlist; - } - PyErr_SetFromOSErrnoWithSyscall("WTSEnumerateSessionsW"); - goto error; - } - - for (i = 0; i < count; i++) { - py_address = NULL; - py_tuple = NULL; - sessionId = sessions[i].SessionId; - if (buffer_user != NULL) - WTSFreeMemory(buffer_user); - if (buffer_addr != NULL) - WTSFreeMemory(buffer_addr); - if (buffer_info != NULL) - WTSFreeMemory(buffer_info); - - buffer_user = NULL; - buffer_addr = NULL; - buffer_info = NULL; - - // username - bytes = 0; - if (WTSQuerySessionInformationW(hServer, sessionId, WTSUserName, - &buffer_user, &bytes) == 0) { - PyErr_SetFromOSErrnoWithSyscall("WTSQuerySessionInformationW"); - goto error; - } - if (bytes <= 2) - continue; - - // address - bytes = 0; - if (WTSQuerySessionInformationW(hServer, sessionId, WTSClientAddress, - &buffer_addr, &bytes) == 0) { - PyErr_SetFromOSErrnoWithSyscall("WTSQuerySessionInformationW"); - goto error; - } - - address = (PWTS_CLIENT_ADDRESS)buffer_addr; - if (address->AddressFamily == 2) { // AF_INET == 2 - sprintf_s(address_str, - _countof(address_str), - "%u.%u.%u.%u", - // The IP address is offset by two bytes from the start of the Address member of the WTS_CLIENT_ADDRESS structure. - address->Address[2], - address->Address[3], - address->Address[4], - address->Address[5]); - py_address = Py_BuildValue("s", address_str); - if (!py_address) - goto error; - } - else { - py_address = Py_None; - } - - // login time - bytes = 0; - if (WTSQuerySessionInformationW(hServer, sessionId, WTSSessionInfo, - &buffer_info, &bytes) == 0) { - PyErr_SetFromOSErrnoWithSyscall("WTSQuerySessionInformationW"); - goto error; - } - wts_info = (PWTSINFOW)buffer_info; - - py_username = PyUnicode_FromWideChar(buffer_user, wcslen(buffer_user)); - if (py_username == NULL) - goto error; - - py_tuple = Py_BuildValue( - "OOd", - py_username, - py_address, - psutil_LargeIntegerToUnixTime(wts_info->ConnectTime) - ); - if (!py_tuple) - goto error; - if (PyList_Append(py_retlist, py_tuple)) - goto error; - Py_CLEAR(py_username); - Py_CLEAR(py_address); - Py_CLEAR(py_tuple); - } - - WTSFreeMemory(sessions); - WTSFreeMemory(buffer_user); - WTSFreeMemory(buffer_addr); - WTSFreeMemory(buffer_info); - return py_retlist; - -error: - Py_XDECREF(py_username); - Py_XDECREF(py_tuple); - Py_XDECREF(py_address); - Py_DECREF(py_retlist); - - if (sessions != NULL) - WTSFreeMemory(sessions); - if (buffer_user != NULL) - WTSFreeMemory(buffer_user); - if (buffer_addr != NULL) - WTSFreeMemory(buffer_addr); - if (buffer_info != NULL) - WTSFreeMemory(buffer_info); - return NULL; -} - - -/* - * Return the number of handles opened by process. - */ -static PyObject * -psutil_proc_num_handles(PyObject *self, PyObject *args) { - DWORD pid; - HANDLE hProcess; - DWORD handleCount; - - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - return NULL; - hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); - if (NULL == hProcess) - return NULL; - if (! GetProcessHandleCount(hProcess, &handleCount)) { - PyErr_SetFromWindowsErr(0); - CloseHandle(hProcess); - return NULL; - } - CloseHandle(hProcess); - return Py_BuildValue("k", handleCount); -} - - -static char *get_region_protection_string(ULONG protection) { - switch (protection & 0xff) { - case PAGE_NOACCESS: - return ""; - case PAGE_READONLY: - return "r"; - case PAGE_READWRITE: - return "rw"; - case PAGE_WRITECOPY: - return "wc"; - case PAGE_EXECUTE: - return "x"; - case PAGE_EXECUTE_READ: - return "xr"; - case PAGE_EXECUTE_READWRITE: - return "xrw"; - case PAGE_EXECUTE_WRITECOPY: - return "xwc"; - default: - return "?"; - } -} - - -/* - * Return a list of process's memory mappings. - */ -static PyObject * -psutil_proc_memory_maps(PyObject *self, PyObject *args) { - MEMORY_BASIC_INFORMATION basicInfo; - DWORD pid; - HANDLE hProcess = NULL; - PVOID baseAddress; - WCHAR mappedFileName[MAX_PATH]; - LPVOID maxAddr; - // required by GetMappedFileNameW - DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; - PyObject *py_retlist = PyList_New(0); - PyObject *py_tuple = NULL; - PyObject *py_str = NULL; - - if (py_retlist == NULL) - return NULL; - if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) - goto error; - hProcess = psutil_handle_from_pid(pid, access); - if (NULL == hProcess) - goto error; - - maxAddr = PSUTIL_SYSTEM_INFO.lpMaximumApplicationAddress; - baseAddress = NULL; - - while (VirtualQueryEx(hProcess, baseAddress, &basicInfo, - sizeof(MEMORY_BASIC_INFORMATION))) - { - py_tuple = NULL; - if (baseAddress > maxAddr) - break; - if (GetMappedFileNameW(hProcess, baseAddress, mappedFileName, - sizeof(mappedFileName))) - { - py_str = PyUnicode_FromWideChar(mappedFileName, - wcslen(mappedFileName)); - if (py_str == NULL) - goto error; -#ifdef _WIN64 - py_tuple = Py_BuildValue( - "(KsOI)", - (unsigned long long)baseAddress, -#else - py_tuple = Py_BuildValue( - "(ksOI)", - (unsigned long)baseAddress, -#endif - get_region_protection_string(basicInfo.Protect), - py_str, - basicInfo.RegionSize); - - if (!py_tuple) - goto error; - if (PyList_Append(py_retlist, py_tuple)) - goto error; - Py_CLEAR(py_tuple); - Py_CLEAR(py_str); - } - baseAddress = (PCHAR)baseAddress + basicInfo.RegionSize; - } - - CloseHandle(hProcess); - return py_retlist; - -error: - Py_XDECREF(py_tuple); - Py_XDECREF(py_str); - Py_DECREF(py_retlist); - if (hProcess != NULL) - CloseHandle(hProcess); - return NULL; -} - - -/* - * Return a {pid:ppid, ...} dict for all running processes. - */ -static PyObject * -psutil_ppid_map(PyObject *self, PyObject *args) { - PyObject *py_pid = NULL; - PyObject *py_ppid = NULL; - PyObject *py_retdict = PyDict_New(); - HANDLE handle = NULL; - PROCESSENTRY32 pe = {0}; - pe.dwSize = sizeof(PROCESSENTRY32); - - if (py_retdict == NULL) - return NULL; - handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (handle == INVALID_HANDLE_VALUE) { - PyErr_SetFromWindowsErr(0); - Py_DECREF(py_retdict); - return NULL; - } - - if (Process32First(handle, &pe)) { - do { - py_pid = PyLong_FromPid(pe.th32ProcessID); - if (py_pid == NULL) - goto error; - py_ppid = PyLong_FromPid(pe.th32ParentProcessID); - if (py_ppid == NULL) - goto error; - if (PyDict_SetItem(py_retdict, py_pid, py_ppid)) - goto error; - Py_CLEAR(py_pid); - Py_CLEAR(py_ppid); - } while (Process32Next(handle, &pe)); - } - - CloseHandle(handle); - return py_retdict; - -error: - Py_XDECREF(py_pid); - Py_XDECREF(py_ppid); - Py_DECREF(py_retdict); - CloseHandle(handle); - return NULL; -} - - -/* - * Return battery usage stats. - */ -static PyObject * -psutil_sensors_battery(PyObject *self, PyObject *args) { - SYSTEM_POWER_STATUS sps; - - if (GetSystemPowerStatus(&sps) == 0) { - PyErr_SetFromWindowsErr(0); - return NULL; - } - return Py_BuildValue( - "iiiI", - sps.ACLineStatus, // whether AC is connected: 0=no, 1=yes, 255=unknown - // status flag: - // 1, 2, 4 = high, low, critical - // 8 = charging - // 128 = no battery - sps.BatteryFlag, - sps.BatteryLifePercent, // percent - sps.BatteryLifeTime // remaining secs - ); -} - - -/* - * System memory page size as an int. - */ -static PyObject * -psutil_getpagesize(PyObject *self, PyObject *args) { - // XXX: we may want to use GetNativeSystemInfo to differentiate - // page size for WoW64 processes (but am not sure). - return Py_BuildValue("I", PSUTIL_SYSTEM_INFO.dwPageSize); -} - // ------------------------ Python init --------------------------- @@ -1551,127 +40,73 @@ static PyMethodDef PsutilMethods[] = { // --- per-process functions {"proc_cmdline", (PyCFunction)(void(*)(void))psutil_proc_cmdline, - METH_VARARGS | METH_KEYWORDS, - "Return process cmdline as a list of cmdline arguments"}, - {"proc_environ", psutil_proc_environ, METH_VARARGS, - "Return process environment data"}, - {"proc_exe", psutil_proc_exe, METH_VARARGS, - "Return path of the process executable"}, - {"proc_kill", psutil_proc_kill, METH_VARARGS, - "Kill the process identified by the given PID"}, - {"proc_times", psutil_proc_times, METH_VARARGS, - "Return tuple of user/kern time for the given PID"}, - {"proc_memory_info", psutil_proc_memory_info, METH_VARARGS, - "Return a tuple of process memory information"}, - {"proc_memory_uss", psutil_proc_memory_uss, METH_VARARGS, - "Return the USS of the process"}, - {"proc_cwd", psutil_proc_cwd, METH_VARARGS, - "Return process current working directory"}, - {"proc_suspend_or_resume", psutil_proc_suspend_or_resume, METH_VARARGS, - "Suspend or resume a process"}, - {"proc_open_files", psutil_proc_open_files, METH_VARARGS, - "Return files opened by process"}, - {"proc_username", psutil_proc_username, METH_VARARGS, - "Return the username of a process"}, - {"proc_threads", psutil_proc_threads, METH_VARARGS, - "Return process threads information as a list of tuple"}, - {"proc_wait", psutil_proc_wait, METH_VARARGS, - "Wait for process to terminate and return its exit code."}, - {"proc_priority_get", psutil_proc_priority_get, METH_VARARGS, - "Return process priority."}, - {"proc_priority_set", psutil_proc_priority_set, METH_VARARGS, - "Set process priority."}, - {"proc_io_priority_get", psutil_proc_io_priority_get, METH_VARARGS, - "Return process IO priority."}, - {"proc_io_priority_set", psutil_proc_io_priority_set, METH_VARARGS, - "Set process IO priority."}, - {"proc_cpu_affinity_get", psutil_proc_cpu_affinity_get, METH_VARARGS, - "Return process CPU affinity as a bitmask."}, - {"proc_cpu_affinity_set", psutil_proc_cpu_affinity_set, METH_VARARGS, - "Set process CPU affinity."}, - {"proc_io_counters", psutil_proc_io_counters, METH_VARARGS, - "Get process I/O counters."}, - {"proc_is_suspended", psutil_proc_is_suspended, METH_VARARGS, - "Return True if one of the process threads is in a suspended state"}, - {"proc_num_handles", psutil_proc_num_handles, METH_VARARGS, - "Return the number of handles opened by process."}, - {"proc_memory_maps", psutil_proc_memory_maps, METH_VARARGS, - "Return a list of process's memory mappings"}, + METH_VARARGS | METH_KEYWORDS}, + {"proc_cpu_affinity_get", psutil_proc_cpu_affinity_get, METH_VARARGS}, + {"proc_cpu_affinity_set", psutil_proc_cpu_affinity_set, METH_VARARGS}, + {"proc_cwd", psutil_proc_cwd, METH_VARARGS}, + {"proc_environ", psutil_proc_environ, METH_VARARGS}, + {"proc_exe", psutil_proc_exe, METH_VARARGS}, + {"proc_io_counters", psutil_proc_io_counters, METH_VARARGS}, + {"proc_io_priority_get", psutil_proc_io_priority_get, METH_VARARGS}, + {"proc_io_priority_set", psutil_proc_io_priority_set, METH_VARARGS}, + {"proc_is_suspended", psutil_proc_is_suspended, METH_VARARGS}, + {"proc_kill", psutil_proc_kill, METH_VARARGS}, + {"proc_memory_info", psutil_proc_memory_info, METH_VARARGS}, + {"proc_memory_maps", psutil_proc_memory_maps, METH_VARARGS}, + {"proc_memory_uss", psutil_proc_memory_uss, METH_VARARGS}, + {"proc_num_handles", psutil_proc_num_handles, METH_VARARGS}, + {"proc_open_files", psutil_proc_open_files, METH_VARARGS}, + {"proc_priority_get", psutil_proc_priority_get, METH_VARARGS}, + {"proc_priority_set", psutil_proc_priority_set, METH_VARARGS}, + {"proc_suspend_or_resume", psutil_proc_suspend_or_resume, METH_VARARGS}, + {"proc_threads", psutil_proc_threads, METH_VARARGS}, + {"proc_times", psutil_proc_times, METH_VARARGS}, + {"proc_username", psutil_proc_username, METH_VARARGS}, + {"proc_wait", psutil_proc_wait, METH_VARARGS}, // --- alternative pinfo interface - {"proc_info", psutil_proc_info, METH_VARARGS, - "Various process information"}, + {"proc_info", psutil_proc_info, METH_VARARGS}, // --- system-related functions - {"pids", psutil_pids, METH_VARARGS, - "Returns a list of PIDs currently running on the system"}, - {"ppid_map", psutil_ppid_map, METH_VARARGS, - "Return a {pid:ppid, ...} dict for all running processes"}, - {"pid_exists", psutil_pid_exists, METH_VARARGS, - "Determine if the process exists in the current process list."}, - {"cpu_count_logical", psutil_cpu_count_logical, METH_VARARGS, - "Returns the number of logical CPUs on the system"}, - {"cpu_count_phys", psutil_cpu_count_phys, METH_VARARGS, - "Returns the number of physical CPUs on the system"}, - {"boot_time", psutil_boot_time, METH_VARARGS, - "Return the system boot time expressed in seconds since the epoch."}, - {"virtual_mem", psutil_virtual_mem, METH_VARARGS, - "Return the total amount of physical memory, in bytes"}, - {"cpu_times", psutil_cpu_times, METH_VARARGS, - "Return system cpu times as a list"}, - {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS, - "Return system per-cpu times as a list of tuples"}, - {"disk_usage", psutil_disk_usage, METH_VARARGS, - "Return path's disk total and free as a Python tuple."}, - {"net_io_counters", psutil_net_io_counters, METH_VARARGS, - "Return dict of tuples of networks I/O information."}, - {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS, - "Return dict of tuples of disks I/O information."}, - {"users", psutil_users, METH_VARARGS, - "Return a list of currently connected users."}, - {"disk_partitions", psutil_disk_partitions, METH_VARARGS, - "Return disk partitions."}, - {"net_connections", psutil_net_connections, METH_VARARGS, - "Return system-wide connections"}, - {"net_if_addrs", psutil_net_if_addrs, METH_VARARGS, - "Return NICs addresses."}, - {"net_if_stats", psutil_net_if_stats, METH_VARARGS, - "Return NICs stats."}, - {"cpu_stats", psutil_cpu_stats, METH_VARARGS, - "Return NICs stats."}, - {"cpu_freq", psutil_cpu_freq, METH_VARARGS, - "Return CPU frequency."}, - {"init_loadavg_counter", (PyCFunction)psutil_init_loadavg_counter, - METH_VARARGS, - "Initializes the emulated load average calculator."}, - {"getloadavg", (PyCFunction)psutil_get_loadavg, METH_VARARGS, - "Returns the emulated POSIX-like load average."}, - {"sensors_battery", psutil_sensors_battery, METH_VARARGS, - "Return battery metrics usage."}, - {"getpagesize", psutil_getpagesize, METH_VARARGS, - "Return system memory page size."}, + {"boot_time", psutil_boot_time, METH_VARARGS}, + {"cpu_count_cores", psutil_cpu_count_cores, METH_VARARGS}, + {"cpu_count_logical", psutil_cpu_count_logical, METH_VARARGS}, + {"cpu_freq", psutil_cpu_freq, METH_VARARGS}, + {"cpu_stats", psutil_cpu_stats, METH_VARARGS}, + {"cpu_times", psutil_cpu_times, METH_VARARGS}, + {"disk_io_counters", psutil_disk_io_counters, METH_VARARGS}, + {"disk_partitions", psutil_disk_partitions, METH_VARARGS}, + {"disk_usage", psutil_disk_usage, METH_VARARGS}, + {"getloadavg", (PyCFunction)psutil_get_loadavg, METH_VARARGS}, + {"getpagesize", psutil_getpagesize, METH_VARARGS}, + {"swap_percent", psutil_swap_percent, METH_VARARGS}, + {"init_loadavg_counter", (PyCFunction)psutil_init_loadavg_counter, METH_VARARGS}, + {"net_connections", psutil_net_connections, METH_VARARGS}, + {"net_if_addrs", psutil_net_if_addrs, METH_VARARGS}, + {"net_if_stats", psutil_net_if_stats, METH_VARARGS}, + {"net_io_counters", psutil_net_io_counters, METH_VARARGS}, + {"per_cpu_times", psutil_per_cpu_times, METH_VARARGS}, + {"pid_exists", psutil_pid_exists, METH_VARARGS}, + {"pids", psutil_pids, METH_VARARGS}, + {"ppid_map", psutil_ppid_map, METH_VARARGS}, + {"sensors_battery", psutil_sensors_battery, METH_VARARGS}, + {"users", psutil_users, METH_VARARGS}, + {"virtual_mem", psutil_virtual_mem, METH_VARARGS}, // --- windows services - {"winservice_enumerate", psutil_winservice_enumerate, METH_VARARGS, - "List all services"}, - {"winservice_query_config", psutil_winservice_query_config, METH_VARARGS, - "Return service config"}, - {"winservice_query_status", psutil_winservice_query_status, METH_VARARGS, - "Return service config"}, - {"winservice_query_descr", psutil_winservice_query_descr, METH_VARARGS, - "Return the description of a service"}, - {"winservice_start", psutil_winservice_start, METH_VARARGS, - "Start a service"}, - {"winservice_stop", psutil_winservice_stop, METH_VARARGS, - "Stop a service"}, + {"winservice_enumerate", psutil_winservice_enumerate, METH_VARARGS}, + {"winservice_query_config", psutil_winservice_query_config, METH_VARARGS}, + {"winservice_query_descr", psutil_winservice_query_descr, METH_VARARGS}, + {"winservice_query_status", psutil_winservice_query_status, METH_VARARGS}, + {"winservice_start", psutil_winservice_start, METH_VARARGS}, + {"winservice_stop", psutil_winservice_stop, METH_VARARGS}, // --- windows API bindings - {"win32_QueryDosDevice", psutil_win32_QueryDosDevice, METH_VARARGS, - "QueryDosDevice binding"}, + {"QueryDosDevice", psutil_QueryDosDevice, METH_VARARGS}, // --- others - {"set_testing", psutil_set_testing, METH_NOARGS, - "Set psutil in testing mode"}, + {"check_pid_range", psutil_check_pid_range, METH_VARARGS}, + {"set_debug", psutil_set_debug, METH_VARARGS}, {NULL, NULL, 0, NULL} }; @@ -1732,8 +167,6 @@ void init_psutil_windows(void) if (psutil_setup() != 0) INITERROR; - if (psutil_load_globals() != 0) - INITERROR; if (psutil_set_se_debug() != 0) INITERROR; diff --git a/contrib/python/psutil/py3/psutil/_pswindows.py b/contrib/python/psutil/py3/psutil/_pswindows.py index 98baef5955..2d3a0c9fdb 100644 --- a/contrib/python/psutil/py3/psutil/_pswindows.py +++ b/contrib/python/psutil/py3/psutil/_pswindows.py @@ -14,22 +14,22 @@ import time from collections import namedtuple from . import _common +from ._common import ENCODING +from ._common import ENCODING_ERRS from ._common import AccessDenied +from ._common import NoSuchProcess +from ._common import TimeoutExpired from ._common import conn_tmap from ._common import conn_to_ntuple from ._common import debug -from ._common import ENCODING -from ._common import ENCODING_ERRS from ._common import isfile_strict from ._common import memoize from ._common import memoize_when_activated -from ._common import NoSuchProcess from ._common import parse_environ_block -from ._common import TimeoutExpired from ._common import usage_percent +from ._compat import PY3 from ._compat import long from ._compat import lru_cache -from ._compat import PY3 from ._compat import range from ._compat import unicode from ._psutil_windows import ABOVE_NORMAL_PRIORITY_CLASS @@ -39,11 +39,14 @@ from ._psutil_windows import IDLE_PRIORITY_CLASS from ._psutil_windows import NORMAL_PRIORITY_CLASS from ._psutil_windows import REALTIME_PRIORITY_CLASS + try: from . import _psutil_windows as cext except ImportError as err: - if str(err).lower().startswith("dll load failed") and \ - sys.getwindowsversion()[0] < 6: + if ( + str(err).lower().startswith("dll load failed") + and sys.getwindowsversion()[0] < 6 + ): # We may get here if: # 1) we are on an old Windows version # 2) psutil was installed via pip + wheel @@ -55,13 +58,14 @@ except ImportError as err: else: raise -if sys.version_info >= (3, 4): +if PY3: import enum else: enum = None # process priority constants, import from __init__.py: # http://msdn.microsoft.com/en-us/library/ms686219(v=vs.85).aspx +# fmt: off __extra__all__ = [ "win_service_iter", "win_service_get", # Process priority @@ -73,6 +77,7 @@ __extra__all__ = [ # others "CONN_DELETE_TCB", "AF_LINK", ] +# fmt: on # ===================================================================== @@ -106,6 +111,7 @@ TCP_STATUSES = { } if enum is not None: + class Priority(enum.IntEnum): ABOVE_NORMAL_PRIORITY_CLASS = ABOVE_NORMAL_PRIORITY_CLASS BELOW_NORMAL_PRIORITY_CLASS = BELOW_NORMAL_PRIORITY_CLASS @@ -122,11 +128,13 @@ if enum is None: IOPRIO_NORMAL = 2 IOPRIO_HIGH = 3 else: + class IOPriority(enum.IntEnum): IOPRIO_VERYLOW = 0 IOPRIO_LOW = 1 IOPRIO_NORMAL = 2 IOPRIO_HIGH = 3 + globals().update(IOPriority.__members__) pinfo_map = dict( @@ -160,6 +168,7 @@ pinfo_map = dict( # ===================================================================== +# fmt: off # psutil.cpu_times() scputimes = namedtuple('scputimes', ['user', 'system', 'idle', 'interrupt', 'dpc']) @@ -182,6 +191,7 @@ pmmap_ext = namedtuple( pio = namedtuple('pio', ['read_count', 'write_count', 'read_bytes', 'write_bytes', 'other_count', 'other_bytes']) +# fmt: on # ===================================================================== @@ -194,11 +204,11 @@ def convert_dos_path(s): r"""Convert paths using native DOS format like: "\Device\HarddiskVolume1\Windows\systemew\file.txt" into: - "C:\Windows\systemew\file.txt" + "C:\Windows\systemew\file.txt". """ rawdrive = '\\'.join(s.split('\\')[:3]) - driveletter = cext.win32_QueryDosDevice(rawdrive) - remainder = s[len(rawdrive):] + driveletter = cext.QueryDosDevice(rawdrive) + remainder = s[len(rawdrive) :] return os.path.join(driveletter, remainder) @@ -228,7 +238,7 @@ def getpagesize(): def virtual_memory(): """System virtual memory as a namedtuple.""" mem = cext.virtual_mem() - totphys, availphys, totpagef, availpagef, totvirt, freevirt = mem + totphys, availphys, totsys, availsys = mem # total = totphys avail = availphys @@ -241,10 +251,26 @@ def virtual_memory(): def swap_memory(): """Swap system memory as a (total, used, free, sin, sout) tuple.""" mem = cext.virtual_mem() - total = mem[2] - free = mem[3] - used = total - free - percent = usage_percent(used, total, round_=1) + + total_phys = mem[0] + total_system = mem[2] + + # system memory (commit total/limit) is the sum of physical and swap + # thus physical memory values need to be subtracted to get swap values + total = total_system - total_phys + # commit total is incremented immediately (decrementing free_system) + # while the corresponding free physical value is not decremented until + # pages are accessed, so we can't use free system memory for swap. + # instead, we calculate page file usage based on performance counter + if total > 0: + percentswap = cext.swap_percent() + used = int(0.01 * percentswap * total) + else: + percentswap = 0.0 + used = 0 + + free = total - used + percent = round(percentswap, 1) return _common.sswap(total, used, free, percent, 0, 0) @@ -286,8 +312,9 @@ def cpu_times(): # interrupt and dpc times. cext.per_cpu_times() does, so we # rely on it to get those only. percpu_summed = scputimes(*[sum(n) for n in zip(*cext.per_cpu_times())]) - return scputimes(user, system, idle, - percpu_summed.interrupt, percpu_summed.dpc) + return scputimes( + user, system, idle, percpu_summed.interrupt, percpu_summed.dpc + ) def per_cpu_times(): @@ -304,17 +331,18 @@ def cpu_count_logical(): return cext.cpu_count_logical() -def cpu_count_physical(): - """Return the number of physical CPU cores in the system.""" - return cext.cpu_count_phys() +def cpu_count_cores(): + """Return the number of CPU cores in the system.""" + return cext.cpu_count_cores() def cpu_stats(): """Return CPU statistics.""" ctx_switches, interrupts, dpcs, syscalls = cext.cpu_stats() soft_interrupts = 0 - return _common.scpustats(ctx_switches, interrupts, soft_interrupts, - syscalls) + return _common.scpustats( + ctx_switches, interrupts, soft_interrupts, syscalls + ) def cpu_freq(): @@ -331,7 +359,8 @@ _loadavg_inititialized = False def getloadavg(): """Return the number of processes in the system run queue averaged - over the last 1, 5, and 15 minutes respectively as a tuple""" + over the last 1, 5, and 15 minutes respectively as a tuple. + """ global _loadavg_inititialized if not _loadavg_inititialized: @@ -353,15 +382,25 @@ def net_connections(kind, _pid=-1): connections (as opposed to connections opened by one process only). """ if kind not in conn_tmap: - raise ValueError("invalid %r kind argument; choose between %s" - % (kind, ', '.join([repr(x) for x in conn_tmap]))) + raise ValueError( + "invalid %r kind argument; choose between %s" + % (kind, ', '.join([repr(x) for x in conn_tmap])) + ) families, types = conn_tmap[kind] rawlist = cext.net_connections(_pid, families, types) ret = set() for item in rawlist: fd, fam, type, laddr, raddr, status, pid = item - nt = conn_to_ntuple(fd, fam, type, laddr, raddr, status, TCP_STATUSES, - pid=pid if _pid == -1 else None) + nt = conn_to_ntuple( + fd, + fam, + type, + laddr, + raddr, + status, + TCP_STATUSES, + pid=pid if _pid == -1 else None, + ) ret.add(nt) return list(ret) @@ -377,7 +416,7 @@ def net_if_stats(): isup, duplex, speed, mtu = items if hasattr(_common, 'NicDuplex'): duplex = _common.NicDuplex(duplex) - ret[name] = _common.snicstats(isup, duplex, speed, mtu) + ret[name] = _common.snicstats(isup, duplex, speed, mtu, '') return ret @@ -476,7 +515,7 @@ def win_service_get(name): return service -class WindowsService(object): +class WindowsService: """Represents an installed Windows service.""" def __init__(self, name, display_name): @@ -485,7 +524,9 @@ class WindowsService(object): def __str__(self): details = "(name=%r, display_name=%r)" % ( - self._name, self._display_name) + self._name, + self._display_name, + ) return "%s%s" % (self.__class__.__name__, details) def __repr__(self): @@ -503,14 +544,16 @@ class WindowsService(object): def _query_config(self): with self._wrap_exceptions(): - display_name, binpath, username, start_type = \ + display_name, binpath, username, start_type = ( cext.winservice_query_config(self._name) + ) # XXX - update _self.display_name? return dict( display_name=py2_strencode(display_name), binpath=py2_strencode(binpath), username=py2_strencode(username), - start_type=py2_strencode(start_type)) + start_type=py2_strencode(start_type), + ) def _query_status(self): with self._wrap_exceptions(): @@ -528,15 +571,17 @@ class WindowsService(object): yield except OSError as err: if is_permission_err(err): - raise AccessDenied( - pid=None, name=self._name, - msg="service %r is not querable (not enough privileges)" % - self._name) - elif err.winerror in (cext.ERROR_INVALID_NAME, - cext.ERROR_SERVICE_DOES_NOT_EXIST): - raise NoSuchProcess( - pid=None, name=self._name, - msg="service %r does not exist)" % self._name) + msg = ( + "service %r is not querable (not enough privileges)" + % self._name + ) + raise AccessDenied(pid=None, name=self._name, msg=msg) + elif err.winerror in ( + cext.ERROR_INVALID_NAME, + cext.ERROR_SERVICE_DOES_NOT_EXIST, + ): + msg = "service %r does not exist" % self._name + raise NoSuchProcess(pid=None, name=self._name, msg=msg) else: raise @@ -653,12 +698,17 @@ ppid_map = cext.ppid_map # used internally by Process.children() def is_permission_err(exc): """Return True if this is a permission error.""" assert isinstance(exc, OSError), exc + if exc.errno in (errno.EPERM, errno.EACCES): + return True # On Python 2 OSError doesn't always have 'winerror'. Sometimes # it does, in which case the original exception was WindowsError # (which is a subclass of OSError). - return exc.errno in (errno.EPERM, errno.EACCES) or \ - getattr(exc, "winerror", -1) in (cext.ERROR_ACCESS_DENIED, - cext.ERROR_PRIVILEGE_NOT_HELD) + if getattr(exc, "winerror", -1) in ( + cext.ERROR_ACCESS_DENIED, + cext.ERROR_PRIVILEGE_NOT_HELD, + ): + return True + return False def convert_oserror(exc, pid=None, name=None): @@ -673,24 +723,27 @@ def convert_oserror(exc, pid=None, name=None): def wrap_exceptions(fun): """Decorator which converts OSError into NoSuchProcess or AccessDenied.""" + @functools.wraps(fun) def wrapper(self, *args, **kwargs): try: return fun(self, *args, **kwargs) except OSError as err: raise convert_oserror(err, pid=self.pid, name=self._name) + return wrapper def retry_error_partial_copy(fun): """Workaround for https://github.com/giampaolo/psutil/issues/875. - See: https://stackoverflow.com/questions/4457745#4457745 + See: https://stackoverflow.com/questions/4457745#4457745. """ + @functools.wraps(fun) def wrapper(self, *args, **kwargs): delay = 0.0001 times = 33 - for x in range(times): # retries for roughly 1 second + for _ in range(times): # retries for roughly 1 second try: return fun(self, *args, **kwargs) except WindowsError as _: @@ -699,16 +752,17 @@ def retry_error_partial_copy(fun): time.sleep(delay) delay = min(delay * 2, 0.04) continue - else: - raise - else: - msg = "%s retried %s times, converted to AccessDenied as it's " \ - "still returning %r" % (fun, times, err) - raise AccessDenied(pid=self.pid, name=self._name, msg=msg) + raise + msg = ( + "{} retried {} times, converted to AccessDenied as it's still" + "returning {}".format(fun, times, err) + ) + raise AccessDenied(pid=self.pid, name=self._name, msg=msg) + return wrapper -class Process(object): +class Process: """Wrapper class around underlying C implementation.""" __slots__ = ["pid", "_name", "_ppid", "_cache"] @@ -759,7 +813,7 @@ class Process(object): # 24 = ERROR_TOO_MANY_OPEN_FILES. Not sure why this happens # (perhaps PyPy's JIT delaying garbage collection of files?). if err.errno == 24: - debug("%r forced into AccessDenied" % err) + debug("%r translated into AccessDenied" % err) raise AccessDenied(self.pid, self._name) raise else: @@ -834,14 +888,14 @@ class Process(object): t = self._get_raw_meminfo() rss = t[2] # wset vms = t[7] # pagefile - return pmem(*(rss, vms, ) + t) + return pmem(*(rss, vms) + t) @wrap_exceptions def memory_full_info(self): basic_mem = self.memory_info() uss = cext.proc_memory_uss(self.pid) uss *= getpagesize() - return pfullmem(*basic_mem + (uss, )) + return pfullmem(*basic_mem + (uss,)) def memory_maps(self): try: @@ -867,13 +921,17 @@ class Process(object): if sig == signal.SIGTERM: cext.proc_kill(self.pid) # py >= 2.7 - elif sig in (getattr(signal, "CTRL_C_EVENT", object()), - getattr(signal, "CTRL_BREAK_EVENT", object())): + elif sig in ( + getattr(signal, "CTRL_C_EVENT", object()), + getattr(signal, "CTRL_BREAK_EVENT", object()), + ): os.kill(self.pid, sig) else: - raise ValueError( + msg = ( "only SIGTERM, CTRL_C_EVENT and CTRL_BREAK_EVENT signals " - "are supported on Windows") + "are supported on Windows" + ) + raise ValueError(msg) @wrap_exceptions def wait(self, timeout=None): @@ -1025,9 +1083,14 @@ class Process(object): @wrap_exceptions def ionice_set(self, ioclass, value): if value: - raise TypeError("value argument not accepted on Windows") - if ioclass not in (IOPRIO_VERYLOW, IOPRIO_LOW, IOPRIO_NORMAL, - IOPRIO_HIGH): + msg = "value argument not accepted on Windows" + raise TypeError(msg) + if ioclass not in ( + IOPRIO_VERYLOW, + IOPRIO_LOW, + IOPRIO_NORMAL, + IOPRIO_HIGH, + ): raise ValueError("%s is not a valid priority" % ioclass) cext.proc_io_priority_set(self.pid, ioclass) @@ -1061,6 +1124,7 @@ class Process(object): def cpu_affinity_get(self): def from_bitmask(x): return [i for i in range(64) if (1 << i) & x] + bitmask = cext.proc_cpu_affinity_get(self.pid) return from_bitmask(bitmask) @@ -1071,7 +1135,7 @@ class Process(object): raise ValueError("invalid argument %r" % ls) out = 0 for b in ls: - out |= 2 ** b + out |= 2**b return out # SetProcessAffinityMask() states that ERROR_INVALID_PARAMETER @@ -1082,7 +1146,8 @@ class Process(object): if cpu not in allcpus: if not isinstance(cpu, (int, long)): raise TypeError( - "invalid CPU %r; an integer is required" % cpu) + "invalid CPU %r; an integer is required" % cpu + ) else: raise ValueError("invalid CPU %r" % cpu) diff --git a/contrib/python/psutil/py3/psutil/arch/linux/disk.c b/contrib/python/psutil/py3/psutil/arch/linux/disk.c new file mode 100644 index 0000000000..692a7d5d47 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/linux/disk.c @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> +#include <mntent.h> + +#include "../../_psutil_common.h" + + +// Return disk mounted partitions as a list of tuples including device, +// mount point and filesystem type. +PyObject * +psutil_disk_partitions(PyObject *self, PyObject *args) { + FILE *file = NULL; + struct mntent *entry; + char *mtab_path; + PyObject *py_dev = NULL; + PyObject *py_mountp = NULL; + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + if (!PyArg_ParseTuple(args, "s", &mtab_path)) + return NULL; + + Py_BEGIN_ALLOW_THREADS + file = setmntent(mtab_path, "r"); + Py_END_ALLOW_THREADS + if ((file == 0) || (file == NULL)) { + psutil_debug("setmntent() failed"); + PyErr_SetFromErrnoWithFilename(PyExc_OSError, mtab_path); + goto error; + } + + while ((entry = getmntent(file))) { + if (entry == NULL) { + PyErr_Format(PyExc_RuntimeError, "getmntent() syscall failed"); + goto error; + } + py_dev = PyUnicode_DecodeFSDefault(entry->mnt_fsname); + if (! py_dev) + goto error; + py_mountp = PyUnicode_DecodeFSDefault(entry->mnt_dir); + if (! py_mountp) + goto error; + py_tuple = Py_BuildValue("(OOss)", + py_dev, // device + py_mountp, // mount point + entry->mnt_type, // fs type + entry->mnt_opts); // options + if (! py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_dev); + Py_CLEAR(py_mountp); + Py_CLEAR(py_tuple); + } + endmntent(file); + return py_retlist; + +error: + if (file != NULL) + endmntent(file); + Py_XDECREF(py_dev); + Py_XDECREF(py_mountp); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + return NULL; +} diff --git a/contrib/python/psutil/py3/psutil/arch/linux/disk.h b/contrib/python/psutil/py3/psutil/arch/linux/disk.h new file mode 100644 index 0000000000..90a86d611b --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/linux/disk.h @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> + +PyObject *psutil_disk_partitions(PyObject* self, PyObject* args); diff --git a/contrib/python/psutil/py3/psutil/arch/linux/mem.c b/contrib/python/psutil/py3/psutil/arch/linux/mem.c new file mode 100644 index 0000000000..3b9b4fef3f --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/linux/mem.c @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> +#include <sys/sysinfo.h> + +#include "../../_psutil_common.h" + + +PyObject * +psutil_linux_sysinfo(PyObject *self, PyObject *args) { + struct sysinfo info; + + if (sysinfo(&info) != 0) + return PyErr_SetFromErrno(PyExc_OSError); + // note: boot time might also be determined from here + return Py_BuildValue( + "(kkkkkkI)", + info.totalram, // total + info.freeram, // free + info.bufferram, // buffer + info.sharedram, // shared + info.totalswap, // swap tot + info.freeswap, // swap free + info.mem_unit // multiplier + ); +} diff --git a/contrib/python/psutil/py3/psutil/arch/linux/mem.h b/contrib/python/psutil/py3/psutil/arch/linux/mem.h new file mode 100644 index 0000000000..582d3e0314 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/linux/mem.h @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> + +PyObject *psutil_linux_sysinfo(PyObject* self, PyObject* args); diff --git a/contrib/python/psutil/py3/psutil/arch/linux/net.c b/contrib/python/psutil/py3/psutil/arch/linux/net.c new file mode 100644 index 0000000000..d193e94087 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/linux/net.c @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> +#include <linux/sockios.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <linux/if.h> +#include <linux/version.h> +#include <unistd.h> + +// see: https://github.com/giampaolo/psutil/issues/659 +#ifdef PSUTIL_ETHTOOL_MISSING_TYPES + #include <linux/types.h> + typedef __u64 u64; + typedef __u32 u32; + typedef __u16 u16; + typedef __u8 u8; +#endif + +// Avoid redefinition of struct sysinfo with musl libc. +#define _LINUX_SYSINFO_H +#include <linux/ethtool.h> + +#include "../../_psutil_common.h" + + +// * defined in linux/ethtool.h but not always available (e.g. Android) +// * #ifdef check needed for old kernels, see: +// https://github.com/giampaolo/psutil/issues/2164 +static inline uint32_t +psutil_ethtool_cmd_speed(const struct ethtool_cmd *ecmd) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27) + return ecmd->speed; +#else + return (ecmd->speed_hi << 16) | ecmd->speed; +#endif +} + +// May happen on old RedHat versions, see: +// https://github.com/giampaolo/psutil/issues/607 +#ifndef DUPLEX_UNKNOWN + #define DUPLEX_UNKNOWN 0xff +#endif +// https://github.com/giampaolo/psutil/pull/2156 +#ifndef SPEED_UNKNOWN + #define SPEED_UNKNOWN -1 +#endif + + +// References: +// * https://github.com/dpaleino/wicd/blob/master/wicd/backends/be-ioctl.py +// * http://www.i-scream.org/libstatgrab/ +PyObject* +psutil_net_if_duplex_speed(PyObject* self, PyObject* args) { + char *nic_name; + int sock = 0; + int ret; + int duplex; + __u32 uint_speed; + int speed; + struct ifreq ifr; + struct ethtool_cmd ethcmd; + PyObject *py_retlist = NULL; + + if (! PyArg_ParseTuple(args, "s", &nic_name)) + return NULL; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) + return PyErr_SetFromOSErrnoWithSyscall("socket()"); + PSUTIL_STRNCPY(ifr.ifr_name, nic_name, sizeof(ifr.ifr_name)); + + // duplex and speed + memset(ðcmd, 0, sizeof ethcmd); + ethcmd.cmd = ETHTOOL_GSET; + ifr.ifr_data = (void *)ðcmd; + ret = ioctl(sock, SIOCETHTOOL, &ifr); + + if (ret != -1) { + duplex = ethcmd.duplex; + // speed is returned from ethtool as a __u32 ranging from 0 to INT_MAX + // or SPEED_UNKNOWN (-1) + uint_speed = psutil_ethtool_cmd_speed(ðcmd); + if (uint_speed == (__u32)SPEED_UNKNOWN || uint_speed > INT_MAX) { + speed = 0; + } + else { + speed = (int)uint_speed; + } + } + else { + if ((errno == EOPNOTSUPP) || (errno == EINVAL)) { + // EOPNOTSUPP may occur in case of wi-fi cards. + // For EINVAL see: + // https://github.com/giampaolo/psutil/issues/797 + // #issuecomment-202999532 + duplex = DUPLEX_UNKNOWN; + speed = 0; + } + else { + PyErr_SetFromOSErrnoWithSyscall("ioctl(SIOCETHTOOL)"); + goto error; + } + } + + py_retlist = Py_BuildValue("[ii]", duplex, speed); + if (!py_retlist) + goto error; + close(sock); + return py_retlist; + +error: + if (sock != -1) + close(sock); + return NULL; +} diff --git a/contrib/python/psutil/py3/psutil/arch/linux/net.h b/contrib/python/psutil/py3/psutil/arch/linux/net.h new file mode 100644 index 0000000000..55095c06c9 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/linux/net.h @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> + +PyObject *psutil_net_if_duplex_speed(PyObject* self, PyObject* args); diff --git a/contrib/python/psutil/py3/psutil/arch/linux/proc.c b/contrib/python/psutil/py3/psutil/arch/linux/proc.c new file mode 100644 index 0000000000..b58a3ce2a2 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/linux/proc.c @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> +#include <sys/syscall.h> +#include <sched.h> +#include <unistd.h> + +#include "proc.h" +#include "../../_psutil_common.h" + + +#ifdef PSUTIL_HAVE_IOPRIO +enum { + IOPRIO_WHO_PROCESS = 1, +}; + +static inline int +ioprio_get(int which, int who) { + return syscall(__NR_ioprio_get, which, who); +} + +static inline int +ioprio_set(int which, int who, int ioprio) { + return syscall(__NR_ioprio_set, which, who, ioprio); +} + +#define IOPRIO_CLASS_SHIFT 13 +#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1) + +#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT) +#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK) +#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data) + + +// Return a (ioclass, iodata) Python tuple representing process I/O +// priority. +PyObject * +psutil_proc_ioprio_get(PyObject *self, PyObject *args) { + pid_t pid; + int ioprio, ioclass, iodata; + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + ioprio = ioprio_get(IOPRIO_WHO_PROCESS, pid); + if (ioprio == -1) + return PyErr_SetFromErrno(PyExc_OSError); + ioclass = IOPRIO_PRIO_CLASS(ioprio); + iodata = IOPRIO_PRIO_DATA(ioprio); + return Py_BuildValue("ii", ioclass, iodata); +} + + +// A wrapper around ioprio_set(); sets process I/O priority. ioclass +// can be either IOPRIO_CLASS_RT, IOPRIO_CLASS_BE, IOPRIO_CLASS_IDLE or +// 0. iodata goes from 0 to 7 depending on ioclass specified. +PyObject * +psutil_proc_ioprio_set(PyObject *self, PyObject *args) { + pid_t pid; + int ioprio, ioclass, iodata; + int retval; + + if (! PyArg_ParseTuple( + args, _Py_PARSE_PID "ii", &pid, &ioclass, &iodata)) { + return NULL; + } + ioprio = IOPRIO_PRIO_VALUE(ioclass, iodata); + retval = ioprio_set(IOPRIO_WHO_PROCESS, pid, ioprio); + if (retval == -1) + return PyErr_SetFromErrno(PyExc_OSError); + Py_RETURN_NONE; +} +#endif // PSUTIL_HAVE_IOPRIO + + +#ifdef PSUTIL_HAVE_CPU_AFFINITY + +// Return process CPU affinity as a Python list. +PyObject * +psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args) { + int cpu, ncpus, count, cpucount_s; + pid_t pid; + size_t setsize; + cpu_set_t *mask = NULL; + PyObject *py_list = NULL; + + if (!PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + + // The minimum number of CPUs allocated in a cpu_set_t. + ncpus = sizeof(unsigned long) * CHAR_BIT; + while (1) { + setsize = CPU_ALLOC_SIZE(ncpus); + mask = CPU_ALLOC(ncpus); + if (mask == NULL) { + psutil_debug("CPU_ALLOC() failed"); + return PyErr_NoMemory(); + } + if (sched_getaffinity(pid, setsize, mask) == 0) + break; + CPU_FREE(mask); + if (errno != EINVAL) + return PyErr_SetFromErrno(PyExc_OSError); + if (ncpus > INT_MAX / 2) { + PyErr_SetString(PyExc_OverflowError, "could not allocate " + "a large enough CPU set"); + return NULL; + } + ncpus = ncpus * 2; + } + + py_list = PyList_New(0); + if (py_list == NULL) + goto error; + + cpucount_s = CPU_COUNT_S(setsize, mask); + for (cpu = 0, count = cpucount_s; count; cpu++) { + if (CPU_ISSET_S(cpu, setsize, mask)) { +#if PY_MAJOR_VERSION >= 3 + PyObject *cpu_num = PyLong_FromLong(cpu); +#else + PyObject *cpu_num = PyInt_FromLong(cpu); +#endif + if (cpu_num == NULL) + goto error; + if (PyList_Append(py_list, cpu_num)) { + Py_DECREF(cpu_num); + goto error; + } + Py_DECREF(cpu_num); + --count; + } + } + CPU_FREE(mask); + return py_list; + +error: + if (mask) + CPU_FREE(mask); + Py_XDECREF(py_list); + return NULL; +} + + +// Set process CPU affinity; expects a bitmask. +PyObject * +psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) { + cpu_set_t cpu_set; + size_t len; + pid_t pid; + Py_ssize_t i, seq_len; + PyObject *py_cpu_set; + + if (!PyArg_ParseTuple(args, _Py_PARSE_PID "O", &pid, &py_cpu_set)) + return NULL; + + if (!PySequence_Check(py_cpu_set)) { + return PyErr_Format( + PyExc_TypeError, +#if PY_MAJOR_VERSION >= 3 + "sequence argument expected, got %R", Py_TYPE(py_cpu_set) +#else + "sequence argument expected, got %s", Py_TYPE(py_cpu_set)->tp_name +#endif + ); + } + + seq_len = PySequence_Size(py_cpu_set); + if (seq_len < 0) { + return NULL; + } + CPU_ZERO(&cpu_set); + for (i = 0; i < seq_len; i++) { + PyObject *item = PySequence_GetItem(py_cpu_set, i); + if (!item) { + return NULL; + } +#if PY_MAJOR_VERSION >= 3 + long value = PyLong_AsLong(item); +#else + long value = PyInt_AsLong(item); +#endif + Py_XDECREF(item); + if ((value == -1) || PyErr_Occurred()) { + if (!PyErr_Occurred()) + PyErr_SetString(PyExc_ValueError, "invalid CPU value"); + return NULL; + } + CPU_SET(value, &cpu_set); + } + + len = sizeof(cpu_set); + if (sched_setaffinity(pid, len, &cpu_set)) { + return PyErr_SetFromErrno(PyExc_OSError); + } + + Py_RETURN_NONE; +} +#endif // PSUTIL_HAVE_CPU_AFFINITY diff --git a/contrib/python/psutil/py3/psutil/arch/linux/proc.h b/contrib/python/psutil/py3/psutil/arch/linux/proc.h new file mode 100644 index 0000000000..94a84c62ec --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/linux/proc.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> +#include <sys/syscall.h> // __NR_* +#include <sched.h> // CPU_ALLOC + +// Linux >= 2.6.13 +#if defined(__NR_ioprio_get) && defined(__NR_ioprio_set) + #define PSUTIL_HAVE_IOPRIO + + PyObject *psutil_proc_ioprio_get(PyObject *self, PyObject *args); + PyObject *psutil_proc_ioprio_set(PyObject *self, PyObject *args); +#endif + +// Should exist starting from CentOS 6 (year 2011). +#ifdef CPU_ALLOC + #define PSUTIL_HAVE_CPU_AFFINITY + + PyObject *psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args); + PyObject *psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args); +#endif diff --git a/contrib/python/psutil/py3/psutil/arch/linux/users.c b/contrib/python/psutil/py3/psutil/arch/linux/users.c new file mode 100644 index 0000000000..546e29ee98 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/linux/users.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> +#include <utmp.h> +#include <string.h> + +#include "../../_psutil_common.h" + + +PyObject * +psutil_users(PyObject *self, PyObject *args) { + struct utmp *ut; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_username = NULL; + PyObject *py_tty = NULL; + PyObject *py_hostname = NULL; + + if (py_retlist == NULL) + return NULL; + setutent(); + while (NULL != (ut = getutent())) { + if (ut->ut_type != USER_PROCESS) + continue; + py_tuple = NULL; + py_username = PyUnicode_DecodeFSDefault(ut->ut_user); + if (! py_username) + goto error; + py_tty = PyUnicode_DecodeFSDefault(ut->ut_line); + if (! py_tty) + goto error; + if (strcmp(ut->ut_host, ":0") || strcmp(ut->ut_host, ":0.0")) + py_hostname = PyUnicode_DecodeFSDefault("localhost"); + else + py_hostname = PyUnicode_DecodeFSDefault(ut->ut_host); + if (! py_hostname) + goto error; + + py_tuple = Py_BuildValue( + "OOOd" _Py_PARSE_PID, + py_username, // username + py_tty, // tty + py_hostname, // hostname + (double)ut->ut_tv.tv_sec, // tstamp + ut->ut_pid // process id + ); + if (! py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_username); + Py_CLEAR(py_tty); + Py_CLEAR(py_hostname); + Py_CLEAR(py_tuple); + } + endutent(); + return py_retlist; + +error: + Py_XDECREF(py_username); + Py_XDECREF(py_tty); + Py_XDECREF(py_hostname); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + endutent(); + return NULL; +} diff --git a/contrib/python/psutil/py3/psutil/arch/linux/users.h b/contrib/python/psutil/py3/psutil/arch/linux/users.h new file mode 100644 index 0000000000..ba2735d1d2 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/linux/users.h @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> + +PyObject *psutil_users(PyObject* self, PyObject* args); diff --git a/contrib/python/psutil/py3/psutil/arch/osx/cpu.c b/contrib/python/psutil/py3/psutil/arch/osx/cpu.c new file mode 100644 index 0000000000..3759dda44e --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/osx/cpu.c @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* +System-wide CPU related functions. + +Original code was refactored and moved from psutil/_psutil_osx.c in 2020 +right before a4c0a0eb0d2a872ab7a45e47fcf37ef1fde5b012. +For reference, here's the git history with original implementations: + +- CPU count logical: 3d291d425b856077e65163e43244050fb188def1 +- CPU count physical: 4263e354bb4984334bc44adf5dd2f32013d69fba +- CPU times: 32488bdf54aed0f8cef90d639c1667ffaa3c31c7 +- CPU stat: fa00dfb961ef63426c7818899340866ced8d2418 +- CPU frequency: 6ba1ac4ebfcd8c95fca324b15606ab0ec1412d39 +*/ + +#include <Python.h> +#include <mach/mach_error.h> +#include <mach/mach_host.h> +#include <mach/mach_port.h> +#include <mach/mach_vm.h> +#include <sys/sysctl.h> +#include <sys/vmmeter.h> +#include <mach/mach.h> +#if defined(__arm64__) || defined(__aarch64__) +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/IOKitLib.h> +#endif + +#if (defined __MAC_OS_X_VERSION_MIN_REQUIRED) && (__MAC_OS_X_VERSION_MIN_REQUIRED < 120000) +#define kIOMainPortDefault kIOMasterPortDefault +#endif + +#include "../../_psutil_common.h" +#include "../../_psutil_posix.h" + + +PyObject * +psutil_cpu_count_logical(PyObject *self, PyObject *args) { + int num; + size_t size = sizeof(int); + + if (sysctlbyname("hw.logicalcpu", &num, &size, NULL, 2)) + Py_RETURN_NONE; // mimic os.cpu_count() + else + return Py_BuildValue("i", num); +} + + +PyObject * +psutil_cpu_count_cores(PyObject *self, PyObject *args) { + int num; + size_t size = sizeof(int); + + if (sysctlbyname("hw.physicalcpu", &num, &size, NULL, 0)) + Py_RETURN_NONE; // mimic os.cpu_count() + else + return Py_BuildValue("i", num); +} + + +PyObject * +psutil_cpu_times(PyObject *self, PyObject *args) { + mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT; + kern_return_t error; + host_cpu_load_info_data_t r_load; + + mach_port_t host_port = mach_host_self(); + error = host_statistics(host_port, HOST_CPU_LOAD_INFO, + (host_info_t)&r_load, &count); + if (error != KERN_SUCCESS) { + return PyErr_Format( + PyExc_RuntimeError, + "host_statistics(HOST_CPU_LOAD_INFO) syscall failed: %s", + mach_error_string(error)); + } + mach_port_deallocate(mach_task_self(), host_port); + + return Py_BuildValue( + "(dddd)", + (double)r_load.cpu_ticks[CPU_STATE_USER] / CLK_TCK, + (double)r_load.cpu_ticks[CPU_STATE_NICE] / CLK_TCK, + (double)r_load.cpu_ticks[CPU_STATE_SYSTEM] / CLK_TCK, + (double)r_load.cpu_ticks[CPU_STATE_IDLE] / CLK_TCK + ); +} + + +PyObject * +psutil_cpu_stats(PyObject *self, PyObject *args) { + struct vmmeter vmstat; + kern_return_t ret; + mach_msg_type_number_t count = sizeof(vmstat) / sizeof(integer_t); + mach_port_t mport = mach_host_self(); + + ret = host_statistics(mport, HOST_VM_INFO, (host_info_t)&vmstat, &count); + if (ret != KERN_SUCCESS) { + PyErr_Format( + PyExc_RuntimeError, + "host_statistics(HOST_VM_INFO) failed: %s", + mach_error_string(ret)); + return NULL; + } + mach_port_deallocate(mach_task_self(), mport); + + return Py_BuildValue( + "IIIII", + vmstat.v_swtch, // ctx switches + vmstat.v_intr, // interrupts + vmstat.v_soft, // software interrupts + vmstat.v_syscall, // syscalls + vmstat.v_trap // traps + ); +} + +#if defined(__arm64__) || defined(__aarch64__) +PyObject * +psutil_cpu_freq(PyObject *self, PyObject *args) { + uint32_t min; + uint32_t curr; + uint32_t pMin; + uint32_t eMin; + uint32_t max; + kern_return_t status; + CFDictionaryRef matching = NULL; + CFTypeRef pCoreRef = NULL; + CFTypeRef eCoreRef = NULL; + io_iterator_t iter = 0; + io_registry_entry_t entry = 0; + io_name_t name; + + matching = IOServiceMatching("AppleARMIODevice"); + if (matching == 0) { + return PyErr_Format( + PyExc_RuntimeError, + "IOServiceMatching call failed, 'AppleARMIODevice' not found" + ); + } + + status = IOServiceGetMatchingServices(kIOMainPortDefault, matching, &iter); + if (status != KERN_SUCCESS) { + PyErr_Format( + PyExc_RuntimeError, "IOServiceGetMatchingServices call failed" + ); + goto error; + } + + while ((entry = IOIteratorNext(iter)) != 0) { + status = IORegistryEntryGetName(entry, name); + if (status != KERN_SUCCESS) { + IOObjectRelease(entry); + continue; + } + if (strcmp(name, "pmgr") == 0) { + break; + } + IOObjectRelease(entry); + } + + if (entry == 0) { + PyErr_Format( + PyExc_RuntimeError, + "'pmgr' entry was not found in AppleARMIODevice service" + ); + goto error; + } + + pCoreRef = IORegistryEntryCreateCFProperty( + entry, CFSTR("voltage-states5-sram"), kCFAllocatorDefault, 0); + if (pCoreRef == NULL) { + PyErr_Format( + PyExc_RuntimeError, "'voltage-states5-sram' property not found"); + goto error; + } + + eCoreRef = IORegistryEntryCreateCFProperty( + entry, CFSTR("voltage-states1-sram"), kCFAllocatorDefault, 0); + if (eCoreRef == NULL) { + PyErr_Format( + PyExc_RuntimeError, "'voltage-states1-sram' property not found"); + goto error; + } + + size_t pCoreLength = CFDataGetLength(pCoreRef); + size_t eCoreLength = CFDataGetLength(eCoreRef); + if (pCoreLength < 8) { + PyErr_Format( + PyExc_RuntimeError, + "expected 'voltage-states5-sram' buffer to have at least size 8" + ); + goto error; + } + if (eCoreLength < 4) { + PyErr_Format( + PyExc_RuntimeError, + "expected 'voltage-states1-sram' buffer to have at least size 4" + ); + goto error; + } + + CFDataGetBytes(pCoreRef, CFRangeMake(0, 4), (UInt8 *) &pMin); + CFDataGetBytes(eCoreRef, CFRangeMake(0, 4), (UInt8 *) &eMin); + CFDataGetBytes(pCoreRef, CFRangeMake(pCoreLength - 8, 4), (UInt8 *) &max); + + min = pMin < eMin ? pMin : eMin; + curr = max; + + CFRelease(pCoreRef); + CFRelease(eCoreRef); + IOObjectRelease(iter); + IOObjectRelease(entry); + + return Py_BuildValue( + "IKK", + curr / 1000 / 1000, + min / 1000 / 1000, + max / 1000 / 1000 + ); + +error: + if (pCoreRef != NULL) + CFRelease(pCoreRef); + if (eCoreRef != NULL) + CFRelease(eCoreRef); + if (iter != 0) + IOObjectRelease(iter); + if (entry != 0) + IOObjectRelease(entry); + return NULL; +} +#else +PyObject * +psutil_cpu_freq(PyObject *self, PyObject *args) { + unsigned int curr; + int64_t min = 0; + int64_t max = 0; + int mib[2]; + size_t len = sizeof(curr); + size_t size = sizeof(min); + + // also available as "hw.cpufrequency" but it's deprecated + mib[0] = CTL_HW; + mib[1] = HW_CPU_FREQ; + + if (sysctl(mib, 2, &curr, &len, NULL, 0) < 0) + return PyErr_SetFromOSErrnoWithSyscall("sysctl(HW_CPU_FREQ)"); + + if (sysctlbyname("hw.cpufrequency_min", &min, &size, NULL, 0)) + psutil_debug("sysctl('hw.cpufrequency_min') failed (set to 0)"); + + if (sysctlbyname("hw.cpufrequency_max", &max, &size, NULL, 0)) + psutil_debug("sysctl('hw.cpufrequency_min') failed (set to 0)"); + + return Py_BuildValue( + "IKK", + curr / 1000 / 1000, + min / 1000 / 1000, + max / 1000 / 1000); +} +#endif + +PyObject * +psutil_per_cpu_times(PyObject *self, PyObject *args) { + natural_t cpu_count; + natural_t i; + processor_info_array_t info_array; + mach_msg_type_number_t info_count; + kern_return_t error; + processor_cpu_load_info_data_t *cpu_load_info = NULL; + int ret; + PyObject *py_retlist = PyList_New(0); + PyObject *py_cputime = NULL; + + if (py_retlist == NULL) + return NULL; + + mach_port_t host_port = mach_host_self(); + error = host_processor_info(host_port, PROCESSOR_CPU_LOAD_INFO, + &cpu_count, &info_array, &info_count); + if (error != KERN_SUCCESS) { + PyErr_Format( + PyExc_RuntimeError, + "host_processor_info(PROCESSOR_CPU_LOAD_INFO) syscall failed: %s", + mach_error_string(error)); + goto error; + } + mach_port_deallocate(mach_task_self(), host_port); + + cpu_load_info = (processor_cpu_load_info_data_t *) info_array; + + for (i = 0; i < cpu_count; i++) { + py_cputime = Py_BuildValue( + "(dddd)", + (double)cpu_load_info[i].cpu_ticks[CPU_STATE_USER] / CLK_TCK, + (double)cpu_load_info[i].cpu_ticks[CPU_STATE_NICE] / CLK_TCK, + (double)cpu_load_info[i].cpu_ticks[CPU_STATE_SYSTEM] / CLK_TCK, + (double)cpu_load_info[i].cpu_ticks[CPU_STATE_IDLE] / CLK_TCK + ); + if (!py_cputime) + goto error; + if (PyList_Append(py_retlist, py_cputime)) + goto error; + Py_CLEAR(py_cputime); + } + + ret = vm_deallocate(mach_task_self(), (vm_address_t)info_array, + info_count * sizeof(int)); + if (ret != KERN_SUCCESS) + PyErr_WarnEx(PyExc_RuntimeWarning, "vm_deallocate() failed", 2); + return py_retlist; + +error: + Py_XDECREF(py_cputime); + Py_DECREF(py_retlist); + if (cpu_load_info != NULL) { + ret = vm_deallocate(mach_task_self(), (vm_address_t)info_array, + info_count * sizeof(int)); + if (ret != KERN_SUCCESS) + PyErr_WarnEx(PyExc_RuntimeWarning, "vm_deallocate() failed", 2); + } + return NULL; +} diff --git a/contrib/python/psutil/py3/psutil/arch/osx/cpu.h b/contrib/python/psutil/py3/psutil/arch/osx/cpu.h new file mode 100644 index 0000000000..6cf92f82b3 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/osx/cpu.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> + +PyObject *psutil_cpu_count_cores(PyObject *self, PyObject *args); +PyObject *psutil_cpu_count_logical(PyObject *self, PyObject *args); +PyObject *psutil_cpu_freq(PyObject *self, PyObject *args); +PyObject *psutil_cpu_stats(PyObject *self, PyObject *args); +PyObject *psutil_cpu_times(PyObject *self, PyObject *args); +PyObject *psutil_per_cpu_times(PyObject *self, PyObject *args); diff --git a/contrib/python/psutil/py3/psutil/arch/osx/disk.c b/contrib/python/psutil/py3/psutil/arch/osx/disk.c new file mode 100644 index 0000000000..961fc42a48 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/osx/disk.c @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// Disk related functions. Original code was refactored and moved +// from psutil/_psutil_osx.c in 2023. This is the GIT blame before the move: +// https://github.com/giampaolo/psutil/blame/efd7ed3/psutil/_psutil_osx.c + +#include <Python.h> +#include <sys/param.h> +#include <sys/ucred.h> +#include <sys/mount.h> +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/IOKitLib.h> +#include <IOKit/storage/IOBlockStorageDriver.h> +#include <IOKit/storage/IOMedia.h> +#include <IOKit/IOBSD.h> + +#include "../../_psutil_common.h" + + +/* + * Return a list of tuples including device, mount point and fs type + * for all partitions mounted on the system. + */ +PyObject * +psutil_disk_partitions(PyObject *self, PyObject *args) { + int num; + int i; + int len; + uint64_t flags; + char opts[400]; + struct statfs *fs = NULL; + PyObject *py_dev = NULL; + PyObject *py_mountp = NULL; + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + // get the number of mount points + Py_BEGIN_ALLOW_THREADS + num = getfsstat(NULL, 0, MNT_NOWAIT); + Py_END_ALLOW_THREADS + if (num == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + len = sizeof(*fs) * num; + fs = malloc(len); + if (fs == NULL) { + PyErr_NoMemory(); + goto error; + } + + Py_BEGIN_ALLOW_THREADS + num = getfsstat(fs, len, MNT_NOWAIT); + Py_END_ALLOW_THREADS + if (num == -1) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + for (i = 0; i < num; i++) { + opts[0] = 0; + flags = fs[i].f_flags; + + // see sys/mount.h + if (flags & MNT_RDONLY) + strlcat(opts, "ro", sizeof(opts)); + else + strlcat(opts, "rw", sizeof(opts)); + if (flags & MNT_SYNCHRONOUS) + strlcat(opts, ",sync", sizeof(opts)); + if (flags & MNT_NOEXEC) + strlcat(opts, ",noexec", sizeof(opts)); + if (flags & MNT_NOSUID) + strlcat(opts, ",nosuid", sizeof(opts)); + if (flags & MNT_UNION) + strlcat(opts, ",union", sizeof(opts)); + if (flags & MNT_ASYNC) + strlcat(opts, ",async", sizeof(opts)); + if (flags & MNT_EXPORTED) + strlcat(opts, ",exported", sizeof(opts)); + if (flags & MNT_QUARANTINE) + strlcat(opts, ",quarantine", sizeof(opts)); + if (flags & MNT_LOCAL) + strlcat(opts, ",local", sizeof(opts)); + if (flags & MNT_QUOTA) + strlcat(opts, ",quota", sizeof(opts)); + if (flags & MNT_ROOTFS) + strlcat(opts, ",rootfs", sizeof(opts)); + if (flags & MNT_DOVOLFS) + strlcat(opts, ",dovolfs", sizeof(opts)); + if (flags & MNT_DONTBROWSE) + strlcat(opts, ",dontbrowse", sizeof(opts)); + if (flags & MNT_IGNORE_OWNERSHIP) + strlcat(opts, ",ignore-ownership", sizeof(opts)); + if (flags & MNT_AUTOMOUNTED) + strlcat(opts, ",automounted", sizeof(opts)); + if (flags & MNT_JOURNALED) + strlcat(opts, ",journaled", sizeof(opts)); + if (flags & MNT_NOUSERXATTR) + strlcat(opts, ",nouserxattr", sizeof(opts)); + if (flags & MNT_DEFWRITE) + strlcat(opts, ",defwrite", sizeof(opts)); + if (flags & MNT_MULTILABEL) + strlcat(opts, ",multilabel", sizeof(opts)); + if (flags & MNT_NOATIME) + strlcat(opts, ",noatime", sizeof(opts)); + if (flags & MNT_UPDATE) + strlcat(opts, ",update", sizeof(opts)); + if (flags & MNT_RELOAD) + strlcat(opts, ",reload", sizeof(opts)); + if (flags & MNT_FORCE) + strlcat(opts, ",force", sizeof(opts)); + if (flags & MNT_CMDFLAGS) + strlcat(opts, ",cmdflags", sizeof(opts)); + + py_dev = PyUnicode_DecodeFSDefault(fs[i].f_mntfromname); + if (! py_dev) + goto error; + py_mountp = PyUnicode_DecodeFSDefault(fs[i].f_mntonname); + if (! py_mountp) + goto error; + py_tuple = Py_BuildValue( + "(OOss)", + py_dev, // device + py_mountp, // mount point + fs[i].f_fstypename, // fs type + opts); // options + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_dev); + Py_CLEAR(py_mountp); + Py_CLEAR(py_tuple); + } + + free(fs); + return py_retlist; + +error: + Py_XDECREF(py_dev); + Py_XDECREF(py_mountp); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (fs != NULL) + free(fs); + return NULL; +} + + +PyObject * +psutil_disk_usage_used(PyObject *self, PyObject *args) { + PyObject *py_default_value; + PyObject *py_mount_point_bytes = NULL; + char* mount_point; + +#if PY_MAJOR_VERSION >= 3 + if (!PyArg_ParseTuple(args, "O&O", PyUnicode_FSConverter, &py_mount_point_bytes, &py_default_value)) { + return NULL; + } + mount_point = PyBytes_AsString(py_mount_point_bytes); + if (NULL == mount_point) { + Py_XDECREF(py_mount_point_bytes); + return NULL; + } +#else + if (!PyArg_ParseTuple(args, "sO", &mount_point, &py_default_value)) { + return NULL; + } +#endif + +#ifdef ATTR_VOL_SPACEUSED + /* Call getattrlist(ATTR_VOL_SPACEUSED) to get used space info. */ + int ret; + struct { + uint32_t size; + uint64_t spaceused; + } __attribute__((aligned(4), packed)) attrbuf = {0}; + struct attrlist attrs = {0}; + + attrs.bitmapcount = ATTR_BIT_MAP_COUNT; + attrs.volattr = ATTR_VOL_INFO | ATTR_VOL_SPACEUSED; + Py_BEGIN_ALLOW_THREADS + ret = getattrlist(mount_point, &attrs, &attrbuf, sizeof(attrbuf), 0); + Py_END_ALLOW_THREADS + if (ret == 0) { + Py_XDECREF(py_mount_point_bytes); + return PyLong_FromUnsignedLongLong(attrbuf.spaceused); + } + psutil_debug("getattrlist(ATTR_VOL_SPACEUSED) failed, fall-back to default value"); +#endif + Py_XDECREF(py_mount_point_bytes); + Py_INCREF(py_default_value); + return py_default_value; +} + + +/* + * Return a Python dict of tuples for disk I/O information + */ +PyObject * +psutil_disk_io_counters(PyObject *self, PyObject *args) { + CFDictionaryRef parent_dict; + CFDictionaryRef props_dict; + CFDictionaryRef stats_dict; + io_registry_entry_t parent; + io_registry_entry_t disk; + io_iterator_t disk_list; + PyObject *py_disk_info = NULL; + PyObject *py_retdict = PyDict_New(); + + if (py_retdict == NULL) + return NULL; + + // Get list of disks + if (IOServiceGetMatchingServices(kIOMasterPortDefault, + IOServiceMatching(kIOMediaClass), + &disk_list) != kIOReturnSuccess) { + PyErr_SetString( + PyExc_RuntimeError, "unable to get the list of disks."); + goto error; + } + + // Iterate over disks + while ((disk = IOIteratorNext(disk_list)) != 0) { + py_disk_info = NULL; + parent_dict = NULL; + props_dict = NULL; + stats_dict = NULL; + + if (IORegistryEntryGetParentEntry(disk, kIOServicePlane, &parent) + != kIOReturnSuccess) { + PyErr_SetString(PyExc_RuntimeError, + "unable to get the disk's parent."); + IOObjectRelease(disk); + goto error; + } + + if (IOObjectConformsTo(parent, "IOBlockStorageDriver")) { + if (IORegistryEntryCreateCFProperties( + disk, + (CFMutableDictionaryRef *) &parent_dict, + kCFAllocatorDefault, + kNilOptions + ) != kIOReturnSuccess) + { + PyErr_SetString(PyExc_RuntimeError, + "unable to get the parent's properties."); + IOObjectRelease(disk); + IOObjectRelease(parent); + goto error; + } + + if (IORegistryEntryCreateCFProperties( + parent, + (CFMutableDictionaryRef *) &props_dict, + kCFAllocatorDefault, + kNilOptions + ) != kIOReturnSuccess) + { + PyErr_SetString(PyExc_RuntimeError, + "unable to get the disk properties."); + CFRelease(props_dict); + IOObjectRelease(disk); + IOObjectRelease(parent); + goto error; + } + + const int kMaxDiskNameSize = 64; + CFStringRef disk_name_ref = (CFStringRef)CFDictionaryGetValue( + parent_dict, CFSTR(kIOBSDNameKey)); + char disk_name[kMaxDiskNameSize]; + + CFStringGetCString(disk_name_ref, + disk_name, + kMaxDiskNameSize, + CFStringGetSystemEncoding()); + + stats_dict = (CFDictionaryRef)CFDictionaryGetValue( + props_dict, CFSTR(kIOBlockStorageDriverStatisticsKey)); + + if (stats_dict == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "Unable to get disk stats."); + goto error; + } + + CFNumberRef number; + int64_t reads = 0; + int64_t writes = 0; + int64_t read_bytes = 0; + int64_t write_bytes = 0; + int64_t read_time = 0; + int64_t write_time = 0; + + // Get disk reads/writes + if ((number = (CFNumberRef)CFDictionaryGetValue( + stats_dict, + CFSTR(kIOBlockStorageDriverStatisticsReadsKey)))) + { + CFNumberGetValue(number, kCFNumberSInt64Type, &reads); + } + if ((number = (CFNumberRef)CFDictionaryGetValue( + stats_dict, + CFSTR(kIOBlockStorageDriverStatisticsWritesKey)))) + { + CFNumberGetValue(number, kCFNumberSInt64Type, &writes); + } + + // Get disk bytes read/written + if ((number = (CFNumberRef)CFDictionaryGetValue( + stats_dict, + CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey)))) + { + CFNumberGetValue(number, kCFNumberSInt64Type, &read_bytes); + } + if ((number = (CFNumberRef)CFDictionaryGetValue( + stats_dict, + CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey)))) + { + CFNumberGetValue(number, kCFNumberSInt64Type, &write_bytes); + } + + // Get disk time spent reading/writing (nanoseconds) + if ((number = (CFNumberRef)CFDictionaryGetValue( + stats_dict, + CFSTR(kIOBlockStorageDriverStatisticsTotalReadTimeKey)))) + { + CFNumberGetValue(number, kCFNumberSInt64Type, &read_time); + } + if ((number = (CFNumberRef)CFDictionaryGetValue( + stats_dict, + CFSTR(kIOBlockStorageDriverStatisticsTotalWriteTimeKey)))) + { + CFNumberGetValue(number, kCFNumberSInt64Type, &write_time); + } + + // Read/Write time on macOS comes back in nanoseconds and in psutil + // we've standardized on milliseconds so do the conversion. + py_disk_info = Py_BuildValue( + "(KKKKKK)", + reads, + writes, + read_bytes, + write_bytes, + read_time / 1000 / 1000, + write_time / 1000 / 1000); + if (!py_disk_info) + goto error; + if (PyDict_SetItemString(py_retdict, disk_name, py_disk_info)) + goto error; + Py_CLEAR(py_disk_info); + + CFRelease(parent_dict); + IOObjectRelease(parent); + CFRelease(props_dict); + IOObjectRelease(disk); + } + } + + IOObjectRelease (disk_list); + + return py_retdict; + +error: + Py_XDECREF(py_disk_info); + Py_DECREF(py_retdict); + return NULL; +} diff --git a/contrib/python/psutil/py3/psutil/arch/osx/disk.h b/contrib/python/psutil/py3/psutil/arch/osx/disk.h new file mode 100644 index 0000000000..88ca9a28b1 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/osx/disk.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> + +PyObject *psutil_disk_io_counters(PyObject *self, PyObject *args); +PyObject *psutil_disk_partitions(PyObject *self, PyObject *args); +PyObject *psutil_disk_usage_used(PyObject *self, PyObject *args); diff --git a/contrib/python/psutil/py3/psutil/arch/osx/mem.c b/contrib/python/psutil/py3/psutil/arch/osx/mem.c new file mode 100644 index 0000000000..53493065c2 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/osx/mem.c @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// System memory related functions. Original code was refactored and moved +// from psutil/_psutil_osx.c in 2023. This is the GIT blame before the move: +// https://github.com/giampaolo/psutil/blame/efd7ed3/psutil/_psutil_osx.c + +#include <Python.h> +#include <mach/host_info.h> +#include <sys/sysctl.h> +#include <mach/mach.h> + +#include "../../_psutil_posix.h" + + +static int +psutil_sys_vminfo(vm_statistics_data_t *vmstat) { + kern_return_t ret; + mach_msg_type_number_t count = sizeof(*vmstat) / sizeof(integer_t); + mach_port_t mport = mach_host_self(); + + ret = host_statistics(mport, HOST_VM_INFO, (host_info_t)vmstat, &count); + if (ret != KERN_SUCCESS) { + PyErr_Format( + PyExc_RuntimeError, + "host_statistics(HOST_VM_INFO) syscall failed: %s", + mach_error_string(ret)); + return 0; + } + mach_port_deallocate(mach_task_self(), mport); + return 1; +} + + +/* + * Return system virtual memory stats. + * See: + * https://opensource.apple.com/source/system_cmds/system_cmds-790/ + * vm_stat.tproj/vm_stat.c.auto.html + */ +PyObject * +psutil_virtual_mem(PyObject *self, PyObject *args) { + int mib[2]; + uint64_t total; + size_t len = sizeof(total); + vm_statistics_data_t vm; + long pagesize = psutil_getpagesize(); + // physical mem + mib[0] = CTL_HW; + mib[1] = HW_MEMSIZE; + + // This is also available as sysctlbyname("hw.memsize"). + if (sysctl(mib, 2, &total, &len, NULL, 0)) { + if (errno != 0) + PyErr_SetFromErrno(PyExc_OSError); + else + PyErr_Format( + PyExc_RuntimeError, "sysctl(HW_MEMSIZE) syscall failed"); + return NULL; + } + + // vm + if (!psutil_sys_vminfo(&vm)) + return NULL; + + return Py_BuildValue( + "KKKKKK", + total, + (unsigned long long) vm.active_count * pagesize, // active + (unsigned long long) vm.inactive_count * pagesize, // inactive + (unsigned long long) vm.wire_count * pagesize, // wired + (unsigned long long) vm.free_count * pagesize, // free + (unsigned long long) vm.speculative_count * pagesize // speculative + ); +} + + +/* + * Return stats about swap memory. + */ +PyObject * +psutil_swap_mem(PyObject *self, PyObject *args) { + int mib[2]; + size_t size; + struct xsw_usage totals; + vm_statistics_data_t vmstat; + long pagesize = psutil_getpagesize(); + + mib[0] = CTL_VM; + mib[1] = VM_SWAPUSAGE; + size = sizeof(totals); + if (sysctl(mib, 2, &totals, &size, NULL, 0) == -1) { + if (errno != 0) + PyErr_SetFromErrno(PyExc_OSError); + else + PyErr_Format( + PyExc_RuntimeError, "sysctl(VM_SWAPUSAGE) syscall failed"); + return NULL; + } + if (!psutil_sys_vminfo(&vmstat)) + return NULL; + + return Py_BuildValue( + "LLLKK", + totals.xsu_total, + totals.xsu_used, + totals.xsu_avail, + (unsigned long long)vmstat.pageins * pagesize, + (unsigned long long)vmstat.pageouts * pagesize); +} diff --git a/contrib/python/psutil/py3/psutil/arch/osx/mem.h b/contrib/python/psutil/py3/psutil/arch/osx/mem.h new file mode 100644 index 0000000000..dc4cd74388 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/osx/mem.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> + +PyObject *psutil_swap_mem(PyObject *self, PyObject *args); +PyObject *psutil_virtual_mem(PyObject *self, PyObject *args); diff --git a/contrib/python/psutil/py3/psutil/arch/osx/net.c b/contrib/python/psutil/py3/psutil/arch/osx/net.c new file mode 100644 index 0000000000..e9cc61e9b1 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/osx/net.c @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// Networks related functions. Original code was refactored and moved +// from psutil/_psutil_osx.c in 2023. This is the GIT blame before the move: +// https://github.com/giampaolo/psutil/blame/efd7ed3/psutil/_psutil_osx.c + +#include <Python.h> +#include <net/if_dl.h> +#include <net/route.h> +#include <sys/sysctl.h> +#include <sys/socket.h> +#include <net/if.h> + +#include "../../_psutil_common.h" + + +PyObject * +psutil_net_io_counters(PyObject *self, PyObject *args) { + char *buf = NULL, *lim, *next; + struct if_msghdr *ifm; + int mib[6]; + mib[0] = CTL_NET; // networking subsystem + mib[1] = PF_ROUTE; // type of information + mib[2] = 0; // protocol (IPPROTO_xxx) + mib[3] = 0; // address family + mib[4] = NET_RT_IFLIST2; // operation + mib[5] = 0; + size_t len; + PyObject *py_ifc_info = NULL; + PyObject *py_retdict = PyDict_New(); + + if (py_retdict == NULL) + return NULL; + + if (sysctl(mib, 6, NULL, &len, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + buf = malloc(len); + if (buf == NULL) { + PyErr_NoMemory(); + goto error; + } + + if (sysctl(mib, 6, buf, &len, NULL, 0) < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; + } + + lim = buf + len; + + for (next = buf; next < lim; ) { + ifm = (struct if_msghdr *)next; + next += ifm->ifm_msglen; + + if (ifm->ifm_type == RTM_IFINFO2) { + py_ifc_info = NULL; + struct if_msghdr2 *if2m = (struct if_msghdr2 *)ifm; + struct sockaddr_dl *sdl = (struct sockaddr_dl *)(if2m + 1); + char ifc_name[32]; + + strncpy(ifc_name, sdl->sdl_data, sdl->sdl_nlen); + ifc_name[sdl->sdl_nlen] = 0; + + py_ifc_info = Py_BuildValue( + "(KKKKKKKi)", + if2m->ifm_data.ifi_obytes, + if2m->ifm_data.ifi_ibytes, + if2m->ifm_data.ifi_opackets, + if2m->ifm_data.ifi_ipackets, + if2m->ifm_data.ifi_ierrors, + if2m->ifm_data.ifi_oerrors, + if2m->ifm_data.ifi_iqdrops, + 0); // dropout not supported + + if (!py_ifc_info) + goto error; + if (PyDict_SetItemString(py_retdict, ifc_name, py_ifc_info)) + goto error; + Py_CLEAR(py_ifc_info); + } + else { + continue; + } + } + + free(buf); + return py_retdict; + +error: + Py_XDECREF(py_ifc_info); + Py_DECREF(py_retdict); + if (buf != NULL) + free(buf); + return NULL; +} diff --git a/contrib/python/psutil/py3/psutil/arch/osx/net.h b/contrib/python/psutil/py3/psutil/arch/osx/net.h new file mode 100644 index 0000000000..99079523c1 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/osx/net.h @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> + +PyObject *psutil_net_io_counters(PyObject *self, PyObject *args); diff --git a/contrib/python/psutil/py3/psutil/arch/osx/proc.c b/contrib/python/psutil/py3/psutil/arch/osx/proc.c new file mode 100644 index 0000000000..6f66c8613f --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/osx/proc.c @@ -0,0 +1,1267 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// Process related functions. Original code was moved in here from +// psutil/_psutil_osx.c and psutil/arc/osx/process_info.c in 2023. +// For reference, here's the GIT blame history before the move: +// https://github.com/giampaolo/psutil/blame/59504a5/psutil/_psutil_osx.c +// https://github.com/giampaolo/psutil/blame/efd7ed3/psutil/arch/osx/process_info.c + +#include <Python.h> +#include <assert.h> +#include <errno.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> +#include <sys/sysctl.h> +#include <libproc.h> +#include <sys/proc_info.h> +#include <sys/sysctl.h> +#include <netinet/tcp_fsm.h> +#include <arpa/inet.h> +#include <pwd.h> +#include <unistd.h> +#include <mach/mach.h> +#include <mach/mach_vm.h> +#include <mach/shared_region.h> +#include <mach-o/loader.h> + +#include "../../_psutil_common.h" +#include "../../_psutil_posix.h" + + +#define PSUTIL_TV2DOUBLE(t) ((t).tv_sec + (t).tv_usec / 1000000.0) +typedef struct kinfo_proc kinfo_proc; + + +// ==================================================================== +// --- utils +// ==================================================================== + +/* + * Returns a list of all BSD processes on the system. This routine + * allocates the list and puts it in *procList and a count of the + * number of entries in *procCount. You are responsible for freeing + * this list (use "free" from System framework). + * On success, the function returns 0. + * On error, the function returns a BSD errno value. + */ +static int +psutil_get_proc_list(kinfo_proc **procList, size_t *procCount) { + int mib[3]; + size_t size, size2; + void *ptr; + int err; + int lim = 8; // some limit + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_ALL; + *procCount = 0; + + /* + * We start by calling sysctl with ptr == NULL and size == 0. + * That will succeed, and set size to the appropriate length. + * We then allocate a buffer of at least that size and call + * sysctl with that buffer. If that succeeds, we're done. + * If that call fails with ENOMEM, we throw the buffer away + * and try again. + * Note that the loop calls sysctl with NULL again. This is + * is necessary because the ENOMEM failure case sets size to + * the amount of data returned, not the amount of data that + * could have been returned. + */ + while (lim-- > 0) { + size = 0; + if (sysctl((int *)mib, 3, NULL, &size, NULL, 0) == -1) { + PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ALL)"); + return 1; + } + size2 = size + (size >> 3); // add some + if (size2 > size) { + ptr = malloc(size2); + if (ptr == NULL) + ptr = malloc(size); + else + size = size2; + } + else { + ptr = malloc(size); + } + if (ptr == NULL) { + PyErr_NoMemory(); + return 1; + } + + if (sysctl((int *)mib, 3, ptr, &size, NULL, 0) == -1) { + err = errno; + free(ptr); + if (err != ENOMEM) { + PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ALL)"); + return 1; + } + } + else { + *procList = (kinfo_proc *)ptr; + *procCount = size / sizeof(kinfo_proc); + if (procCount <= 0) { + PyErr_Format(PyExc_RuntimeError, "no PIDs found"); + return 1; + } + return 0; // success + } + } + + PyErr_Format(PyExc_RuntimeError, "couldn't collect PIDs list"); + return 1; +} + + +// Read the maximum argument size for processes +static int +psutil_sysctl_argmax() { + int argmax; + int mib[2]; + size_t size = sizeof(argmax); + + mib[0] = CTL_KERN; + mib[1] = KERN_ARGMAX; + + if (sysctl(mib, 2, &argmax, &size, NULL, 0) == 0) + return argmax; + PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_ARGMAX)"); + return 0; +} + + +// Read process argument space. +static int +psutil_sysctl_procargs(pid_t pid, char *procargs, size_t *argmax) { + int mib[3]; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROCARGS2; + mib[2] = pid; + + if (sysctl(mib, 3, procargs, argmax, NULL, 0) < 0) { + if (psutil_pid_exists(pid) == 0) { + NoSuchProcess("psutil_pid_exists -> 0"); + return 1; + } + // In case of zombie process we'll get EINVAL. We translate it + // to NSP and _psosx.py will translate it to ZP. + if (errno == EINVAL) { + psutil_debug("sysctl(KERN_PROCARGS2) -> EINVAL translated to NSP"); + NoSuchProcess("sysctl(KERN_PROCARGS2) -> EINVAL"); + return 1; + } + // There's nothing we can do other than raising AD. + if (errno == EIO) { + psutil_debug("sysctl(KERN_PROCARGS2) -> EIO translated to AD"); + AccessDenied("sysctl(KERN_PROCARGS2) -> EIO"); + return 1; + } + PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROCARGS2)"); + return 1; + } + return 0; +} + + +static int +psutil_get_kinfo_proc(pid_t pid, struct kinfo_proc *kp) { + int mib[4]; + size_t len; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = pid; + + // fetch the info with sysctl() + len = sizeof(struct kinfo_proc); + + // now read the data from sysctl + if (sysctl(mib, 4, kp, &len, NULL, 0) == -1) { + // raise an exception and throw errno as the error + PyErr_SetFromOSErrnoWithSyscall("sysctl"); + return -1; + } + + // sysctl succeeds but len is zero, happens when process has gone away + if (len == 0) { + NoSuchProcess("sysctl(kinfo_proc), len == 0"); + return -1; + } + return 0; +} + + +/* + * A wrapper around proc_pidinfo(). + * https://opensource.apple.com/source/xnu/xnu-2050.7.9/bsd/kern/proc_info.c + * Returns 0 on failure. + */ +static int +psutil_proc_pidinfo(pid_t pid, int flavor, uint64_t arg, void *pti, int size) { + errno = 0; + int ret; + + ret = proc_pidinfo(pid, flavor, arg, pti, size); + if (ret <= 0) { + psutil_raise_for_pid(pid, "proc_pidinfo()"); + return 0; + } + if ((unsigned long)ret < sizeof(pti)) { + psutil_raise_for_pid( + pid, "proc_pidinfo() return size < sizeof(struct_pointer)"); + return 0; + } + return ret; +} + + +/* + * A wrapper around task_for_pid() which sucks big time: + * - it's not documented + * - errno is set only sometimes + * - sometimes errno is ENOENT (?!?) + * - for PIDs != getpid() or PIDs which are not members of the procmod + * it requires root + * As such we can only guess what the heck went wrong and fail either + * with NoSuchProcess or giveup with AccessDenied. + * Here's some history: + * https://github.com/giampaolo/psutil/issues/1181 + * https://github.com/giampaolo/psutil/issues/1209 + * https://github.com/giampaolo/psutil/issues/1291#issuecomment-396062519 + */ +static int +psutil_task_for_pid(pid_t pid, mach_port_t *task) +{ + // See: https://github.com/giampaolo/psutil/issues/1181 + kern_return_t err = KERN_SUCCESS; + + err = task_for_pid(mach_task_self(), pid, task); + if (err != KERN_SUCCESS) { + if (psutil_pid_exists(pid) == 0) + NoSuchProcess("task_for_pid"); + // Now done in Python. + // else if (psutil_is_zombie(pid) == 1) + // PyErr_SetString(ZombieProcessError, + // "task_for_pid -> psutil_is_zombie -> 1"); + else { + psutil_debug( + "task_for_pid() failed (pid=%ld, err=%i, errno=%i, msg='%s'); " + "setting AccessDenied()", + (long)pid, err, errno, mach_error_string(err)); + AccessDenied("task_for_pid"); + } + return 1; + } + return 0; +} + + +/* + * A wrapper around proc_pidinfo(PROC_PIDLISTFDS), which dynamically sets + * the buffer size. + */ +static struct proc_fdinfo* +psutil_proc_list_fds(pid_t pid, int *num_fds) { + int ret; + int fds_size = 0; + int max_size = 24 * 1024 * 1024; // 24M + struct proc_fdinfo *fds_pointer = NULL; + + errno = 0; + ret = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, NULL, 0); + if (ret <= 0) { + psutil_raise_for_pid(pid, "proc_pidinfo(PROC_PIDLISTFDS) 1/2"); + goto error; + } + + while (1) { + if (ret > fds_size) { + while (ret > fds_size) { + fds_size += PROC_PIDLISTFD_SIZE * 32; + if (fds_size > max_size) { + PyErr_Format(PyExc_RuntimeError, + "prevent malloc() to allocate > 24M"); + goto error; + } + } + + if (fds_pointer != NULL) { + free(fds_pointer); + } + fds_pointer = malloc(fds_size); + + if (fds_pointer == NULL) { + PyErr_NoMemory(); + goto error; + } + } + + errno = 0; + ret = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fds_pointer, fds_size); + if (ret <= 0) { + psutil_raise_for_pid(pid, "proc_pidinfo(PROC_PIDLISTFDS) 2/2"); + goto error; + } + + if (ret + (int)PROC_PIDLISTFD_SIZE >= fds_size) { + psutil_debug("PROC_PIDLISTFDS: make room for 1 extra fd"); + ret = fds_size + (int)PROC_PIDLISTFD_SIZE; + continue; + } + + break; + } + + *num_fds = (ret / (int)PROC_PIDLISTFD_SIZE); + return fds_pointer; + +error: + if (fds_pointer != NULL) + free(fds_pointer); + return NULL; +} + + +// ==================================================================== +// --- Python APIs +// ==================================================================== + + +/* + * Return a Python list of all the PIDs running on the system. + */ +PyObject * +psutil_pids(PyObject *self, PyObject *args) { + kinfo_proc *proclist = NULL; + kinfo_proc *orig_address = NULL; + size_t num_processes; + size_t idx; + PyObject *py_pid = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + if (psutil_get_proc_list(&proclist, &num_processes) != 0) + goto error; + + // save the address of proclist so we can free it later + orig_address = proclist; + for (idx = 0; idx < num_processes; idx++) { + py_pid = PyLong_FromPid(proclist->kp_proc.p_pid); + if (! py_pid) + goto error; + if (PyList_Append(py_retlist, py_pid)) + goto error; + Py_CLEAR(py_pid); + proclist++; + } + free(orig_address); + + return py_retlist; + +error: + Py_XDECREF(py_pid); + Py_DECREF(py_retlist); + if (orig_address != NULL) + free(orig_address); + return NULL; +} + + +/* + * Return multiple process info as a Python tuple in one shot by + * using sysctl() and filling up a kinfo_proc struct. + * It should be possible to do this for all processes without + * incurring into permission (EPERM) errors. + * This will also succeed for zombie processes returning correct + * information. + */ +PyObject * +psutil_proc_kinfo_oneshot(PyObject *self, PyObject *args) { + pid_t pid; + struct kinfo_proc kp; + PyObject *py_name; + PyObject *py_retlist; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + if (psutil_get_kinfo_proc(pid, &kp) == -1) + return NULL; + + py_name = PyUnicode_DecodeFSDefault(kp.kp_proc.p_comm); + if (! py_name) { + // Likely a decoding error. We don't want to fail the whole + // operation. The python module may retry with proc_name(). + PyErr_Clear(); + py_name = Py_None; + } + + py_retlist = Py_BuildValue( + _Py_PARSE_PID "llllllidiO", + kp.kp_eproc.e_ppid, // (pid_t) ppid + (long)kp.kp_eproc.e_pcred.p_ruid, // (long) real uid + (long)kp.kp_eproc.e_ucred.cr_uid, // (long) effective uid + (long)kp.kp_eproc.e_pcred.p_svuid, // (long) saved uid + (long)kp.kp_eproc.e_pcred.p_rgid, // (long) real gid + (long)kp.kp_eproc.e_ucred.cr_groups[0], // (long) effective gid + (long)kp.kp_eproc.e_pcred.p_svgid, // (long) saved gid + kp.kp_eproc.e_tdev, // (int) tty nr + PSUTIL_TV2DOUBLE(kp.kp_proc.p_starttime), // (double) create time + (int)kp.kp_proc.p_stat, // (int) status + py_name // (pystr) name + ); + + if (py_retlist != NULL) { + // XXX shall we decref() also in case of Py_BuildValue() error? + Py_DECREF(py_name); + } + return py_retlist; +} + + +/* + * Return multiple process info as a Python tuple in one shot by + * using proc_pidinfo(PROC_PIDTASKINFO) and filling a proc_taskinfo + * struct. + * Contrarily from proc_kinfo above this function will fail with + * EACCES for PIDs owned by another user and with ESRCH for zombie + * processes. + */ +PyObject * +psutil_proc_pidtaskinfo_oneshot(PyObject *self, PyObject *args) { + pid_t pid; + struct proc_taskinfo pti; + uint64_t total_user; + uint64_t total_system; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + if (psutil_proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti)) <= 0) + return NULL; + + total_user = pti.pti_total_user * PSUTIL_MACH_TIMEBASE_INFO.numer; + total_user /= PSUTIL_MACH_TIMEBASE_INFO.denom; + total_system = pti.pti_total_system * PSUTIL_MACH_TIMEBASE_INFO.numer; + total_system /= PSUTIL_MACH_TIMEBASE_INFO.denom; + + return Py_BuildValue( + "(ddKKkkkk)", + (float)total_user / 1000000000.0, // (float) cpu user time + (float)total_system / 1000000000.0, // (float) cpu sys time + // Note about memory: determining other mem stats on macOS is a mess: + // http://www.opensource.apple.com/source/top/top-67/libtop.c?txt + // I just give up. + // struct proc_regioninfo pri; + // psutil_proc_pidinfo(pid, PROC_PIDREGIONINFO, 0, &pri, sizeof(pri)) + pti.pti_resident_size, // (uns long long) rss + pti.pti_virtual_size, // (uns long long) vms + pti.pti_faults, // (uns long) number of page faults (pages) + pti.pti_pageins, // (uns long) number of actual pageins (pages) + pti.pti_threadnum, // (uns long) num threads + // Unvoluntary value seems not to be available; + // pti.pti_csw probably refers to the sum of the two; + // getrusage() numbers seems to confirm this theory. + pti.pti_csw // (uns long) voluntary ctx switches + ); +} + + +/* + * Return process name from kinfo_proc as a Python string. + */ +PyObject * +psutil_proc_name(PyObject *self, PyObject *args) { + pid_t pid; + struct kinfo_proc kp; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + if (psutil_get_kinfo_proc(pid, &kp) == -1) + return NULL; + return PyUnicode_DecodeFSDefault(kp.kp_proc.p_comm); +} + + +/* + * Return process current working directory. + * Raises NSP in case of zombie process. + */ +PyObject * +psutil_proc_cwd(PyObject *self, PyObject *args) { + pid_t pid; + struct proc_vnodepathinfo pathinfo; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + + if (psutil_proc_pidinfo( + pid, PROC_PIDVNODEPATHINFO, 0, &pathinfo, sizeof(pathinfo)) <= 0) + { + return NULL; + } + + return PyUnicode_DecodeFSDefault(pathinfo.pvi_cdir.vip_path); +} + + +/* + * Return path of the process executable. + */ +PyObject * +psutil_proc_exe(PyObject *self, PyObject *args) { + pid_t pid; + char buf[PATH_MAX]; + int ret; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + errno = 0; + ret = proc_pidpath(pid, &buf, sizeof(buf)); + if (ret == 0) { + if (pid == 0) { + AccessDenied("automatically set for PID 0"); + return NULL; + } + else if (errno == ENOENT) { + // It may happen (file not found error) if the process is + // still alive but the executable which launched it got + // deleted, see: + // https://github.com/giampaolo/psutil/issues/1738 + return Py_BuildValue("s", ""); + } + else { + psutil_raise_for_pid(pid, "proc_pidpath()"); + return NULL; + } + } + return PyUnicode_DecodeFSDefault(buf); +} + + +/* + * Indicates if the given virtual address on the given architecture is in the + * shared VM region. + */ +static bool +psutil_in_shared_region(mach_vm_address_t addr, cpu_type_t type) { + mach_vm_address_t base; + mach_vm_address_t size; + + switch (type) { + case CPU_TYPE_ARM: + base = SHARED_REGION_BASE_ARM; + size = SHARED_REGION_SIZE_ARM; + break; + case CPU_TYPE_I386: + base = SHARED_REGION_BASE_I386; + size = SHARED_REGION_SIZE_I386; + break; + case CPU_TYPE_X86_64: + base = SHARED_REGION_BASE_X86_64; + size = SHARED_REGION_SIZE_X86_64; + break; + default: + return false; + } + + return base <= addr && addr < (base + size); +} + + +/* + * Returns the USS (unique set size) of the process. Reference: + * https://dxr.mozilla.org/mozilla-central/source/xpcom/base/ + * nsMemoryReporterManager.cpp + */ +PyObject * +psutil_proc_memory_uss(PyObject *self, PyObject *args) { + pid_t pid; + size_t len; + cpu_type_t cpu_type; + size_t private_pages = 0; + mach_vm_size_t size = 0; + mach_msg_type_number_t info_count = VM_REGION_TOP_INFO_COUNT; + kern_return_t kr; + long pagesize = psutil_getpagesize(); + mach_vm_address_t addr = MACH_VM_MIN_ADDRESS; + mach_port_t task = MACH_PORT_NULL; + vm_region_top_info_data_t info; + mach_port_t object_name; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + + if (psutil_task_for_pid(pid, &task) != 0) + return NULL; + + len = sizeof(cpu_type); + if (sysctlbyname("sysctl.proc_cputype", &cpu_type, &len, NULL, 0) != 0) { + return PyErr_SetFromOSErrnoWithSyscall( + "sysctlbyname('sysctl.proc_cputype')"); + } + + // Roughly based on libtop_update_vm_regions in + // http://www.opensource.apple.com/source/top/top-100.1.2/libtop.c + for (addr = 0; ; addr += size) { + kr = mach_vm_region( + task, &addr, &size, VM_REGION_TOP_INFO, (vm_region_info_t)&info, + &info_count, &object_name); + if (kr == KERN_INVALID_ADDRESS) { + // Done iterating VM regions. + break; + } + else if (kr != KERN_SUCCESS) { + PyErr_Format( + PyExc_RuntimeError, + "mach_vm_region(VM_REGION_TOP_INFO) syscall failed"); + return NULL; + } + + if (psutil_in_shared_region(addr, cpu_type) && + info.share_mode != SM_PRIVATE) { + continue; + } + + switch (info.share_mode) { +#ifdef SM_LARGE_PAGE + case SM_LARGE_PAGE: + // NB: Large pages are not shareable and always resident. +#endif + case SM_PRIVATE: + private_pages += info.private_pages_resident; + private_pages += info.shared_pages_resident; + break; + case SM_COW: + private_pages += info.private_pages_resident; + if (info.ref_count == 1) { + // Treat copy-on-write pages as private if they only + // have one reference. + private_pages += info.shared_pages_resident; + } + break; + case SM_SHARED: + default: + break; + } + } + + mach_port_deallocate(mach_task_self(), task); + return Py_BuildValue("K", private_pages * pagesize); +} + + +/* + * Return process threads + */ +PyObject * +psutil_proc_threads(PyObject *self, PyObject *args) { + pid_t pid; + int err, ret; + kern_return_t kr; + unsigned int info_count = TASK_BASIC_INFO_COUNT; + mach_port_t task = MACH_PORT_NULL; + struct task_basic_info tasks_info; + thread_act_port_array_t thread_list = NULL; + thread_info_data_t thinfo_basic; + thread_basic_info_t basic_info_th; + mach_msg_type_number_t thread_count, thread_info_count, j; + + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + goto error; + + if (psutil_task_for_pid(pid, &task) != 0) + goto error; + + info_count = TASK_BASIC_INFO_COUNT; + err = task_info(task, TASK_BASIC_INFO, (task_info_t)&tasks_info, + &info_count); + if (err != KERN_SUCCESS) { + // errcode 4 is "invalid argument" (access denied) + if (err == 4) { + AccessDenied("task_info(TASK_BASIC_INFO)"); + } + else { + // otherwise throw a runtime error with appropriate error code + PyErr_Format(PyExc_RuntimeError, + "task_info(TASK_BASIC_INFO) syscall failed"); + } + goto error; + } + + err = task_threads(task, &thread_list, &thread_count); + if (err != KERN_SUCCESS) { + PyErr_Format(PyExc_RuntimeError, "task_threads() syscall failed"); + goto error; + } + + for (j = 0; j < thread_count; j++) { + thread_info_count = THREAD_INFO_MAX; + kr = thread_info(thread_list[j], THREAD_BASIC_INFO, + (thread_info_t)thinfo_basic, &thread_info_count); + if (kr != KERN_SUCCESS) { + PyErr_Format(PyExc_RuntimeError, + "thread_info(THREAD_BASIC_INFO) syscall failed"); + goto error; + } + + basic_info_th = (thread_basic_info_t)thinfo_basic; + py_tuple = Py_BuildValue( + "Iff", + j + 1, + basic_info_th->user_time.seconds + \ + (float)basic_info_th->user_time.microseconds / 1000000.0, + basic_info_th->system_time.seconds + \ + (float)basic_info_th->system_time.microseconds / 1000000.0 + ); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + } + + ret = vm_deallocate(task, (vm_address_t)thread_list, + thread_count * sizeof(int)); + if (ret != KERN_SUCCESS) + PyErr_WarnEx(PyExc_RuntimeWarning, "vm_deallocate() failed", 2); + + mach_port_deallocate(mach_task_self(), task); + + return py_retlist; + +error: + if (task != MACH_PORT_NULL) + mach_port_deallocate(mach_task_self(), task); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (thread_list != NULL) { + ret = vm_deallocate(task, (vm_address_t)thread_list, + thread_count * sizeof(int)); + if (ret != KERN_SUCCESS) + PyErr_WarnEx(PyExc_RuntimeWarning, "vm_deallocate() failed", 2); + } + return NULL; +} + + +/* + * Return process open files as a Python tuple. + * References: + * - lsof source code: http://goo.gl/SYW79 and http://goo.gl/m78fd + * - /usr/include/sys/proc_info.h + */ +PyObject * +psutil_proc_open_files(PyObject *self, PyObject *args) { + pid_t pid; + int num_fds; + int i; + unsigned long nb; + struct proc_fdinfo *fds_pointer = NULL; + struct proc_fdinfo *fdp_pointer; + struct vnode_fdinfowithpath vi; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_path = NULL; + + if (py_retlist == NULL) + return NULL; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + goto error; + + // see: https://github.com/giampaolo/psutil/issues/2116 + if (pid == 0) + return py_retlist; + + fds_pointer = psutil_proc_list_fds(pid, &num_fds); + if (fds_pointer == NULL) + goto error; + + for (i = 0; i < num_fds; i++) { + fdp_pointer = &fds_pointer[i]; + + if (fdp_pointer->proc_fdtype == PROX_FDTYPE_VNODE) { + errno = 0; + nb = proc_pidfdinfo((pid_t)pid, + fdp_pointer->proc_fd, + PROC_PIDFDVNODEPATHINFO, + &vi, + sizeof(vi)); + + // --- errors checking + if ((nb <= 0) || nb < sizeof(vi)) { + if ((errno == ENOENT) || (errno == EBADF)) { + // no such file or directory or bad file descriptor; + // let's assume the file has been closed or removed + continue; + } + else { + psutil_raise_for_pid( + pid, "proc_pidinfo(PROC_PIDFDVNODEPATHINFO)"); + goto error; + } + } + // --- /errors checking + + // --- construct python list + py_path = PyUnicode_DecodeFSDefault(vi.pvip.vip_path); + if (! py_path) + goto error; + py_tuple = Py_BuildValue( + "(Oi)", + py_path, + (int)fdp_pointer->proc_fd); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + Py_CLEAR(py_path); + // --- /construct python list + } + } + + free(fds_pointer); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_path); + Py_DECREF(py_retlist); + if (fds_pointer != NULL) + free(fds_pointer); + return NULL; // exception has already been set earlier +} + + +/* + * Return process TCP and UDP connections as a list of tuples. + * Raises NSP in case of zombie process. + * References: + * - lsof source code: http://goo.gl/SYW79 and http://goo.gl/wNrC0 + * - /usr/include/sys/proc_info.h + */ +PyObject * +psutil_proc_connections(PyObject *self, PyObject *args) { + pid_t pid; + int num_fds; + int i; + unsigned long nb; + struct proc_fdinfo *fds_pointer = NULL; + struct proc_fdinfo *fdp_pointer; + struct socket_fdinfo si; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_laddr = NULL; + PyObject *py_raddr = NULL; + PyObject *py_af_filter = NULL; + PyObject *py_type_filter = NULL; + + if (py_retlist == NULL) + return NULL; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID "OO", &pid, &py_af_filter, + &py_type_filter)) { + goto error; + } + + // see: https://github.com/giampaolo/psutil/issues/2116 + if (pid == 0) + return py_retlist; + + if (!PySequence_Check(py_af_filter) || !PySequence_Check(py_type_filter)) { + PyErr_SetString(PyExc_TypeError, "arg 2 or 3 is not a sequence"); + goto error; + } + + fds_pointer = psutil_proc_list_fds(pid, &num_fds); + if (fds_pointer == NULL) + goto error; + + for (i = 0; i < num_fds; i++) { + py_tuple = NULL; + py_laddr = NULL; + py_raddr = NULL; + fdp_pointer = &fds_pointer[i]; + + if (fdp_pointer->proc_fdtype == PROX_FDTYPE_SOCKET) { + errno = 0; + nb = proc_pidfdinfo(pid, fdp_pointer->proc_fd, + PROC_PIDFDSOCKETINFO, &si, sizeof(si)); + + // --- errors checking + if ((nb <= 0) || (nb < sizeof(si)) || (errno != 0)) { + if (errno == EBADF) { + // let's assume socket has been closed + psutil_debug("proc_pidfdinfo(PROC_PIDFDSOCKETINFO) -> " + "EBADF (ignored)"); + continue; + } + else if (errno == EOPNOTSUPP) { + // may happen sometimes, see: + // https://github.com/giampaolo/psutil/issues/1512 + psutil_debug("proc_pidfdinfo(PROC_PIDFDSOCKETINFO) -> " + "EOPNOTSUPP (ignored)"); + continue; + } + else { + psutil_raise_for_pid( + pid, "proc_pidinfo(PROC_PIDFDSOCKETINFO)"); + goto error; + } + } + // --- /errors checking + + // + int fd, family, type, lport, rport, state; + // TODO: use INET6_ADDRSTRLEN instead of 200 + char lip[200], rip[200]; + int inseq; + PyObject *py_family; + PyObject *py_type; + + fd = (int)fdp_pointer->proc_fd; + family = si.psi.soi_family; + type = si.psi.soi_type; + + // apply filters + py_family = PyLong_FromLong((long)family); + inseq = PySequence_Contains(py_af_filter, py_family); + Py_DECREF(py_family); + if (inseq == 0) + continue; + py_type = PyLong_FromLong((long)type); + inseq = PySequence_Contains(py_type_filter, py_type); + Py_DECREF(py_type); + if (inseq == 0) + continue; + + if ((family == AF_INET) || (family == AF_INET6)) { + if (family == AF_INET) { + inet_ntop(AF_INET, + &si.psi.soi_proto.pri_tcp.tcpsi_ini. \ + insi_laddr.ina_46.i46a_addr4, + lip, + sizeof(lip)); + inet_ntop(AF_INET, + &si.psi.soi_proto.pri_tcp.tcpsi_ini.insi_faddr. \ + ina_46.i46a_addr4, + rip, + sizeof(rip)); + } + else { + inet_ntop(AF_INET6, + &si.psi.soi_proto.pri_tcp.tcpsi_ini. \ + insi_laddr.ina_6, + lip, sizeof(lip)); + inet_ntop(AF_INET6, + &si.psi.soi_proto.pri_tcp.tcpsi_ini. \ + insi_faddr.ina_6, + rip, sizeof(rip)); + } + + // check for inet_ntop failures + if (errno != 0) { + PyErr_SetFromOSErrnoWithSyscall("inet_ntop()"); + goto error; + } + + lport = ntohs(si.psi.soi_proto.pri_tcp.tcpsi_ini.insi_lport); + rport = ntohs(si.psi.soi_proto.pri_tcp.tcpsi_ini.insi_fport); + if (type == SOCK_STREAM) + state = (int)si.psi.soi_proto.pri_tcp.tcpsi_state; + else + state = PSUTIL_CONN_NONE; + + py_laddr = Py_BuildValue("(si)", lip, lport); + if (!py_laddr) + goto error; + if (rport != 0) + py_raddr = Py_BuildValue("(si)", rip, rport); + else + py_raddr = Py_BuildValue("()"); + if (!py_raddr) + goto error; + + // construct the python list + py_tuple = Py_BuildValue( + "(iiiNNi)", fd, family, type, py_laddr, py_raddr, state); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + } + else if (family == AF_UNIX) { + py_laddr = PyUnicode_DecodeFSDefault( + si.psi.soi_proto.pri_un.unsi_addr.ua_sun.sun_path); + if (!py_laddr) + goto error; + py_raddr = PyUnicode_DecodeFSDefault( + si.psi.soi_proto.pri_un.unsi_caddr.ua_sun.sun_path); + if (!py_raddr) + goto error; + // construct the python list + py_tuple = Py_BuildValue( + "(iiiOOi)", + fd, family, type, + py_laddr, + py_raddr, + PSUTIL_CONN_NONE); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + Py_CLEAR(py_laddr); + Py_CLEAR(py_raddr); + } + } + } + + free(fds_pointer); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_laddr); + Py_XDECREF(py_raddr); + Py_DECREF(py_retlist); + if (fds_pointer != NULL) + free(fds_pointer); + return NULL; +} + + +/* + * Return number of file descriptors opened by process. + * Raises NSP in case of zombie process. + */ +PyObject * +psutil_proc_num_fds(PyObject *self, PyObject *args) { + pid_t pid; + int num_fds; + struct proc_fdinfo *fds_pointer; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + + fds_pointer = psutil_proc_list_fds(pid, &num_fds); + if (fds_pointer == NULL) + return NULL; + + free(fds_pointer); + return Py_BuildValue("i", num_fds); +} + + +// return process args as a python list +PyObject * +psutil_proc_cmdline(PyObject *self, PyObject *args) { + pid_t pid; + int nargs; + size_t len; + char *procargs = NULL; + char *arg_ptr; + char *arg_end; + char *curr_arg; + size_t argmax; + PyObject *py_retlist = PyList_New(0); + PyObject *py_arg = NULL; + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + goto error; + + // special case for PID 0 (kernel_task) where cmdline cannot be fetched + if (pid == 0) + return py_retlist; + + // read argmax and allocate memory for argument space. + argmax = psutil_sysctl_argmax(); + if (! argmax) + goto error; + + procargs = (char *)malloc(argmax); + if (NULL == procargs) { + PyErr_NoMemory(); + goto error; + } + + if (psutil_sysctl_procargs(pid, procargs, &argmax) != 0) + goto error; + + arg_end = &procargs[argmax]; + // copy the number of arguments to nargs + memcpy(&nargs, procargs, sizeof(nargs)); + + arg_ptr = procargs + sizeof(nargs); + len = strlen(arg_ptr); + arg_ptr += len + 1; + + if (arg_ptr == arg_end) { + free(procargs); + return py_retlist; + } + + // skip ahead to the first argument + for (; arg_ptr < arg_end; arg_ptr++) { + if (*arg_ptr != '\0') + break; + } + + // iterate through arguments + curr_arg = arg_ptr; + while (arg_ptr < arg_end && nargs > 0) { + if (*arg_ptr++ == '\0') { + py_arg = PyUnicode_DecodeFSDefault(curr_arg); + if (! py_arg) + goto error; + if (PyList_Append(py_retlist, py_arg)) + goto error; + Py_DECREF(py_arg); + // iterate to next arg and decrement # of args + curr_arg = arg_ptr; + nargs--; + } + } + + free(procargs); + return py_retlist; + +error: + Py_XDECREF(py_arg); + Py_XDECREF(py_retlist); + if (procargs != NULL) + free(procargs); + return NULL; +} + + +// Return process environment as a python string. +// On Big Sur this function returns an empty string unless: +// * kernel is DEVELOPMENT || DEBUG +// * target process is same as current_proc() +// * target process is not cs_restricted +// * SIP is off +// * caller has an entitlement +// See: https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/bsd/kern/kern_sysctl.c#L1315-L1321 +PyObject * +psutil_proc_environ(PyObject *self, PyObject *args) { + pid_t pid; + int nargs; + char *procargs = NULL; + char *procenv = NULL; + char *arg_ptr; + char *arg_end; + char *env_start; + size_t argmax; + PyObject *py_ret = NULL; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + + // special case for PID 0 (kernel_task) where cmdline cannot be fetched + if (pid == 0) + goto empty; + + // read argmax and allocate memory for argument space. + argmax = psutil_sysctl_argmax(); + if (! argmax) + goto error; + + procargs = (char *)malloc(argmax); + if (NULL == procargs) { + PyErr_NoMemory(); + goto error; + } + + if (psutil_sysctl_procargs(pid, procargs, &argmax) != 0) + goto error; + + arg_end = &procargs[argmax]; + // copy the number of arguments to nargs + memcpy(&nargs, procargs, sizeof(nargs)); + + // skip executable path + arg_ptr = procargs + sizeof(nargs); + arg_ptr = memchr(arg_ptr, '\0', arg_end - arg_ptr); + + if (arg_ptr == NULL || arg_ptr == arg_end) { + psutil_debug( + "(arg_ptr == NULL || arg_ptr == arg_end); set environ to empty"); + goto empty; + } + + // skip ahead to the first argument + for (; arg_ptr < arg_end; arg_ptr++) { + if (*arg_ptr != '\0') + break; + } + + // iterate through arguments + while (arg_ptr < arg_end && nargs > 0) { + if (*arg_ptr++ == '\0') + nargs--; + } + + // build an environment variable block + env_start = arg_ptr; + + procenv = calloc(1, arg_end - arg_ptr); + if (procenv == NULL) { + PyErr_NoMemory(); + goto error; + } + + while (*arg_ptr != '\0' && arg_ptr < arg_end) { + char *s = memchr(arg_ptr + 1, '\0', arg_end - arg_ptr); + if (s == NULL) + break; + memcpy(procenv + (arg_ptr - env_start), arg_ptr, s - arg_ptr); + arg_ptr = s + 1; + } + + py_ret = PyUnicode_DecodeFSDefaultAndSize( + procenv, arg_ptr - env_start + 1); + if (!py_ret) { + // XXX: don't want to free() this as per: + // https://github.com/giampaolo/psutil/issues/926 + // It sucks but not sure what else to do. + procargs = NULL; + goto error; + } + + free(procargs); + free(procenv); + return py_ret; + +empty: + if (procargs != NULL) + free(procargs); + return Py_BuildValue("s", ""); + +error: + Py_XDECREF(py_ret); + if (procargs != NULL) + free(procargs); + if (procenv != NULL) + free(procargs); + return NULL; +} diff --git a/contrib/python/psutil/py3/psutil/arch/osx/proc.h b/contrib/python/psutil/py3/psutil/arch/osx/proc.h new file mode 100644 index 0000000000..63f16ccdd2 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/osx/proc.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> + +PyObject *psutil_pids(PyObject *self, PyObject *args); +PyObject *psutil_proc_cmdline(PyObject *self, PyObject *args); +PyObject *psutil_proc_connections(PyObject *self, PyObject *args); +PyObject *psutil_proc_cwd(PyObject *self, PyObject *args); +PyObject *psutil_proc_environ(PyObject *self, PyObject *args); +PyObject *psutil_proc_exe(PyObject *self, PyObject *args); +PyObject *psutil_proc_kinfo_oneshot(PyObject *self, PyObject *args); +PyObject *psutil_proc_memory_uss(PyObject *self, PyObject *args); +PyObject *psutil_proc_name(PyObject *self, PyObject *args); +PyObject *psutil_proc_num_fds(PyObject *self, PyObject *args); +PyObject *psutil_proc_open_files(PyObject *self, PyObject *args); +PyObject *psutil_proc_pidtaskinfo_oneshot(PyObject *self, PyObject *args); +PyObject *psutil_proc_threads(PyObject *self, PyObject *args); diff --git a/contrib/python/psutil/py3/psutil/arch/osx/process_info.c b/contrib/python/psutil/py3/psutil/arch/osx/process_info.c deleted file mode 100644 index fb9f24ffac..0000000000 --- a/contrib/python/psutil/py3/psutil/arch/osx/process_info.c +++ /dev/null @@ -1,384 +0,0 @@ -/* - * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - * - * Helper functions related to fetching process information. - * Used by _psutil_osx module methods. - */ - - -#include <Python.h> -#include <errno.h> -#include <sys/sysctl.h> -#include <libproc.h> - -#include "../../_psutil_common.h" -#include "../../_psutil_posix.h" -#include "process_info.h" - - -/* - * Returns a list of all BSD processes on the system. This routine - * allocates the list and puts it in *procList and a count of the - * number of entries in *procCount. You are responsible for freeing - * this list (use "free" from System framework). - * On success, the function returns 0. - * On error, the function returns a BSD errno value. - */ -int -psutil_get_proc_list(kinfo_proc **procList, size_t *procCount) { - int mib[3]; - size_t size, size2; - void *ptr; - int err; - int lim = 8; // some limit - - mib[0] = CTL_KERN; - mib[1] = KERN_PROC; - mib[2] = KERN_PROC_ALL; - *procCount = 0; - - /* - * We start by calling sysctl with ptr == NULL and size == 0. - * That will succeed, and set size to the appropriate length. - * We then allocate a buffer of at least that size and call - * sysctl with that buffer. If that succeeds, we're done. - * If that call fails with ENOMEM, we throw the buffer away - * and try again. - * Note that the loop calls sysctl with NULL again. This is - * is necessary because the ENOMEM failure case sets size to - * the amount of data returned, not the amount of data that - * could have been returned. - */ - while (lim-- > 0) { - size = 0; - if (sysctl((int *)mib, 3, NULL, &size, NULL, 0) == -1) { - PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ALL)"); - return 1; - } - size2 = size + (size >> 3); // add some - if (size2 > size) { - ptr = malloc(size2); - if (ptr == NULL) - ptr = malloc(size); - else - size = size2; - } - else { - ptr = malloc(size); - } - if (ptr == NULL) { - PyErr_NoMemory(); - return 1; - } - - if (sysctl((int *)mib, 3, ptr, &size, NULL, 0) == -1) { - err = errno; - free(ptr); - if (err != ENOMEM) { - PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROC_ALL)"); - return 1; - } - } - else { - *procList = (kinfo_proc *)ptr; - *procCount = size / sizeof(kinfo_proc); - if (procCount <= 0) { - PyErr_Format(PyExc_RuntimeError, "no PIDs found"); - return 1; - } - return 0; // success - } - } - - PyErr_Format(PyExc_RuntimeError, "couldn't collect PIDs list"); - return 1; -} - - -// Read the maximum argument size for processes -static int -psutil_sysctl_argmax() { - int argmax; - int mib[2]; - size_t size = sizeof(argmax); - - mib[0] = CTL_KERN; - mib[1] = KERN_ARGMAX; - - if (sysctl(mib, 2, &argmax, &size, NULL, 0) == 0) - return argmax; - PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_ARGMAX)"); - return 0; -} - - -// Read process argument space. -static int -psutil_sysctl_procargs(pid_t pid, char *procargs, size_t argmax) { - int mib[3]; - - mib[0] = CTL_KERN; - mib[1] = KERN_PROCARGS2; - mib[2] = pid; - - if (sysctl(mib, 3, procargs, &argmax, NULL, 0) < 0) { - if (psutil_pid_exists(pid) == 0) { - NoSuchProcess("psutil_pid_exists -> 0"); - return 1; - } - // In case of zombie process we'll get EINVAL. We translate it - // to NSP and _psosx.py will translate it to ZP. - if (errno == EINVAL) { - psutil_debug("sysctl(KERN_PROCARGS2) -> EINVAL translated to NSP"); - NoSuchProcess("sysctl(KERN_PROCARGS2) -> EINVAL"); - return 1; - } - // There's nothing we can do other than raising AD. - if (errno == EIO) { - psutil_debug("sysctl(KERN_PROCARGS2) -> EIO translated to AD"); - AccessDenied("sysctl(KERN_PROCARGS2) -> EIO"); - return 1; - } - PyErr_SetFromOSErrnoWithSyscall("sysctl(KERN_PROCARGS2)"); - return 1; - } - return 0; -} - - -// Return 1 if pid refers to a zombie process else 0. -int -psutil_is_zombie(pid_t pid) { - struct kinfo_proc kp; - - if (psutil_get_kinfo_proc(pid, &kp) == -1) - return 0; - return (kp.kp_proc.p_stat == SZOMB) ? 1 : 0; -} - - -// return process args as a python list -PyObject * -psutil_get_cmdline(pid_t pid) { - int nargs; - size_t len; - char *procargs = NULL; - char *arg_ptr; - char *arg_end; - char *curr_arg; - size_t argmax; - - PyObject *py_arg = NULL; - PyObject *py_retlist = NULL; - - // special case for PID 0 (kernel_task) where cmdline cannot be fetched - if (pid == 0) - return Py_BuildValue("[]"); - - // read argmax and allocate memory for argument space. - argmax = psutil_sysctl_argmax(); - if (! argmax) - goto error; - - procargs = (char *)malloc(argmax); - if (NULL == procargs) { - PyErr_NoMemory(); - goto error; - } - - if (psutil_sysctl_procargs(pid, procargs, argmax) != 0) - goto error; - - arg_end = &procargs[argmax]; - // copy the number of arguments to nargs - memcpy(&nargs, procargs, sizeof(nargs)); - - arg_ptr = procargs + sizeof(nargs); - len = strlen(arg_ptr); - arg_ptr += len + 1; - - if (arg_ptr == arg_end) { - free(procargs); - return Py_BuildValue("[]"); - } - - // skip ahead to the first argument - for (; arg_ptr < arg_end; arg_ptr++) { - if (*arg_ptr != '\0') - break; - } - - // iterate through arguments - curr_arg = arg_ptr; - py_retlist = Py_BuildValue("[]"); - if (!py_retlist) - goto error; - while (arg_ptr < arg_end && nargs > 0) { - if (*arg_ptr++ == '\0') { - py_arg = PyUnicode_DecodeFSDefault(curr_arg); - if (! py_arg) - goto error; - if (PyList_Append(py_retlist, py_arg)) - goto error; - Py_DECREF(py_arg); - // iterate to next arg and decrement # of args - curr_arg = arg_ptr; - nargs--; - } - } - - free(procargs); - return py_retlist; - -error: - Py_XDECREF(py_arg); - Py_XDECREF(py_retlist); - if (procargs != NULL) - free(procargs); - return NULL; -} - - -// return process environment as a python string -PyObject * -psutil_get_environ(pid_t pid) { - int nargs; - char *procargs = NULL; - char *procenv = NULL; - char *arg_ptr; - char *arg_end; - char *env_start; - size_t argmax; - PyObject *py_ret = NULL; - - // special case for PID 0 (kernel_task) where cmdline cannot be fetched - if (pid == 0) - goto empty; - - // read argmax and allocate memory for argument space. - argmax = psutil_sysctl_argmax(); - if (! argmax) - goto error; - - procargs = (char *)malloc(argmax); - if (NULL == procargs) { - PyErr_NoMemory(); - goto error; - } - - if (psutil_sysctl_procargs(pid, procargs, argmax) != 0) - goto error; - - arg_end = &procargs[argmax]; - // copy the number of arguments to nargs - memcpy(&nargs, procargs, sizeof(nargs)); - - // skip executable path - arg_ptr = procargs + sizeof(nargs); - arg_ptr = memchr(arg_ptr, '\0', arg_end - arg_ptr); - - if (arg_ptr == NULL || arg_ptr == arg_end) - goto empty; - - // skip ahead to the first argument - for (; arg_ptr < arg_end; arg_ptr++) { - if (*arg_ptr != '\0') - break; - } - - // iterate through arguments - while (arg_ptr < arg_end && nargs > 0) { - if (*arg_ptr++ == '\0') - nargs--; - } - - // build an environment variable block - env_start = arg_ptr; - - procenv = calloc(1, arg_end - arg_ptr); - if (procenv == NULL) { - PyErr_NoMemory(); - goto error; - } - - while (*arg_ptr != '\0' && arg_ptr < arg_end) { - char *s = memchr(arg_ptr + 1, '\0', arg_end - arg_ptr); - if (s == NULL) - break; - memcpy(procenv + (arg_ptr - env_start), arg_ptr, s - arg_ptr); - arg_ptr = s + 1; - } - - py_ret = PyUnicode_DecodeFSDefaultAndSize( - procenv, arg_ptr - env_start + 1); - if (!py_ret) { - // XXX: don't want to free() this as per: - // https://github.com/giampaolo/psutil/issues/926 - // It sucks but not sure what else to do. - procargs = NULL; - goto error; - } - - free(procargs); - free(procenv); - return py_ret; - -empty: - if (procargs != NULL) - free(procargs); - return Py_BuildValue("s", ""); - -error: - Py_XDECREF(py_ret); - if (procargs != NULL) - free(procargs); - if (procenv != NULL) - free(procargs); - return NULL; -} - - -int -psutil_get_kinfo_proc(pid_t pid, struct kinfo_proc *kp) { - int mib[4]; - size_t len; - mib[0] = CTL_KERN; - mib[1] = KERN_PROC; - mib[2] = KERN_PROC_PID; - mib[3] = pid; - - // fetch the info with sysctl() - len = sizeof(struct kinfo_proc); - - // now read the data from sysctl - if (sysctl(mib, 4, kp, &len, NULL, 0) == -1) { - // raise an exception and throw errno as the error - PyErr_SetFromOSErrnoWithSyscall("sysctl"); - return -1; - } - - // sysctl succeeds but len is zero, happens when process has gone away - if (len == 0) { - NoSuchProcess("sysctl (len == 0)"); - return -1; - } - return 0; -} - - -/* - * A wrapper around proc_pidinfo(). - * Returns 0 on failure (and Python exception gets already set). - */ -int -psutil_proc_pidinfo(pid_t pid, int flavor, uint64_t arg, void *pti, int size) { - errno = 0; - int ret = proc_pidinfo(pid, flavor, arg, pti, size); - if ((ret <= 0) || ((unsigned long)ret < sizeof(pti))) { - psutil_raise_for_pid(pid, "proc_pidinfo()"); - return 0; - } - return ret; -} diff --git a/contrib/python/psutil/py3/psutil/arch/osx/process_info.h b/contrib/python/psutil/py3/psutil/arch/osx/process_info.h deleted file mode 100644 index ffa6230f7c..0000000000 --- a/contrib/python/psutil/py3/psutil/arch/osx/process_info.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#include <Python.h> - -typedef struct kinfo_proc kinfo_proc; - -int psutil_is_zombie(pid_t pid); -int psutil_get_kinfo_proc(pid_t pid, struct kinfo_proc *kp); -int psutil_get_proc_list(kinfo_proc **procList, size_t *procCount); -int psutil_proc_pidinfo( - pid_t pid, int flavor, uint64_t arg, void *pti, int size); -PyObject* psutil_get_cmdline(pid_t pid); -PyObject* psutil_get_environ(pid_t pid); diff --git a/contrib/python/psutil/py3/psutil/arch/osx/sensors.c b/contrib/python/psutil/py3/psutil/arch/osx/sensors.c new file mode 100644 index 0000000000..a2faa157c4 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/osx/sensors.c @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// Sensors related functions. Original code was refactored and moved +// from psutil/_psutil_osx.c in 2023. This is the GIT blame before the move: +// https://github.com/giampaolo/psutil/blame/efd7ed3/psutil/_psutil_osx.c +// Original battery code: +// https://github.com/giampaolo/psutil/commit/e0df5da + + +#include <Python.h> +#include <IOKit/ps/IOPowerSources.h> +#include <IOKit/ps/IOPSKeys.h> + +#include "../../_psutil_common.h" + + +PyObject * +psutil_sensors_battery(PyObject *self, PyObject *args) { + PyObject *py_tuple = NULL; + CFTypeRef power_info = NULL; + CFArrayRef power_sources_list = NULL; + CFDictionaryRef power_sources_information = NULL; + CFNumberRef capacity_ref = NULL; + CFNumberRef time_to_empty_ref = NULL; + CFStringRef ps_state_ref = NULL; + uint32_t capacity; /* units are percent */ + int time_to_empty; /* units are minutes */ + int is_power_plugged; + + power_info = IOPSCopyPowerSourcesInfo(); + + if (!power_info) { + PyErr_SetString(PyExc_RuntimeError, + "IOPSCopyPowerSourcesInfo() syscall failed"); + goto error; + } + + power_sources_list = IOPSCopyPowerSourcesList(power_info); + if (!power_sources_list) { + PyErr_SetString(PyExc_RuntimeError, + "IOPSCopyPowerSourcesList() syscall failed"); + goto error; + } + + /* Should only get one source. But in practice, check for > 0 sources */ + if (!CFArrayGetCount(power_sources_list)) { + PyErr_SetString(PyExc_NotImplementedError, "no battery"); + goto error; + } + + power_sources_information = IOPSGetPowerSourceDescription( + power_info, CFArrayGetValueAtIndex(power_sources_list, 0)); + + capacity_ref = (CFNumberRef) CFDictionaryGetValue( + power_sources_information, CFSTR(kIOPSCurrentCapacityKey)); + if (!CFNumberGetValue(capacity_ref, kCFNumberSInt32Type, &capacity)) { + PyErr_SetString(PyExc_RuntimeError, + "No battery capacity infomration in power sources info"); + goto error; + } + + ps_state_ref = (CFStringRef) CFDictionaryGetValue( + power_sources_information, CFSTR(kIOPSPowerSourceStateKey)); + is_power_plugged = CFStringCompare( + ps_state_ref, CFSTR(kIOPSACPowerValue), 0) + == kCFCompareEqualTo; + + time_to_empty_ref = (CFNumberRef) CFDictionaryGetValue( + power_sources_information, CFSTR(kIOPSTimeToEmptyKey)); + if (!CFNumberGetValue(time_to_empty_ref, + kCFNumberIntType, &time_to_empty)) { + /* This value is recommended for non-Apple power sources, so it's not + * an error if it doesn't exist. We'll return -1 for "unknown" */ + /* A value of -1 indicates "Still Calculating the Time" also for + * apple power source */ + time_to_empty = -1; + } + + py_tuple = Py_BuildValue("Iii", + capacity, time_to_empty, is_power_plugged); + if (!py_tuple) { + goto error; + } + + CFRelease(power_info); + CFRelease(power_sources_list); + /* Caller should NOT release power_sources_information */ + + return py_tuple; + +error: + if (power_info) + CFRelease(power_info); + if (power_sources_list) + CFRelease(power_sources_list); + Py_XDECREF(py_tuple); + return NULL; +} diff --git a/contrib/python/psutil/py3/psutil/arch/osx/sensors.h b/contrib/python/psutil/py3/psutil/arch/osx/sensors.h new file mode 100644 index 0000000000..edace25d3d --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/osx/sensors.h @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> + +PyObject *psutil_sensors_battery(PyObject *self, PyObject *args); diff --git a/contrib/python/psutil/py3/psutil/arch/osx/sys.c b/contrib/python/psutil/py3/psutil/arch/osx/sys.c new file mode 100644 index 0000000000..4fe6642597 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/osx/sys.c @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// System related functions. Original code was refactored and moved +// from psutil/_psutil_osx.c in 2023. This is the GIT blame before the move: +// https://github.com/giampaolo/psutil/blame/efd7ed3/psutil/_psutil_osx.c + +#include <Python.h> +#include <sys/sysctl.h> +#include <utmpx.h> + +#include "../../_psutil_common.h" + + +PyObject * +psutil_boot_time(PyObject *self, PyObject *args) { + // fetch sysctl "kern.boottime" + static int request[2] = { CTL_KERN, KERN_BOOTTIME }; + struct timeval result; + size_t result_len = sizeof result; + time_t boot_time = 0; + + if (sysctl(request, 2, &result, &result_len, NULL, 0) == -1) + return PyErr_SetFromErrno(PyExc_OSError); + boot_time = result.tv_sec; + return Py_BuildValue("f", (float)boot_time); +} + + +PyObject * +psutil_users(PyObject *self, PyObject *args) { + struct utmpx *utx; + PyObject *py_username = NULL; + PyObject *py_tty = NULL; + PyObject *py_hostname = NULL; + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + while ((utx = getutxent()) != NULL) { + if (utx->ut_type != USER_PROCESS) + continue; + py_username = PyUnicode_DecodeFSDefault(utx->ut_user); + if (! py_username) + goto error; + py_tty = PyUnicode_DecodeFSDefault(utx->ut_line); + if (! py_tty) + goto error; + py_hostname = PyUnicode_DecodeFSDefault(utx->ut_host); + if (! py_hostname) + goto error; + py_tuple = Py_BuildValue( + "(OOOdi)", + py_username, // username + py_tty, // tty + py_hostname, // hostname + (double)utx->ut_tv.tv_sec, // start time + utx->ut_pid // process id + ); + if (!py_tuple) { + endutxent(); + goto error; + } + if (PyList_Append(py_retlist, py_tuple)) { + endutxent(); + goto error; + } + Py_CLEAR(py_username); + Py_CLEAR(py_tty); + Py_CLEAR(py_hostname); + Py_CLEAR(py_tuple); + } + + endutxent(); + return py_retlist; + +error: + Py_XDECREF(py_username); + Py_XDECREF(py_tty); + Py_XDECREF(py_hostname); + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + return NULL; +} diff --git a/contrib/python/psutil/py3/psutil/arch/osx/sys.h b/contrib/python/psutil/py3/psutil/arch/osx/sys.h new file mode 100644 index 0000000000..344ca21d42 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/osx/sys.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> + +PyObject *psutil_boot_time(PyObject *self, PyObject *args); +PyObject *psutil_users(PyObject *self, PyObject *args); diff --git a/contrib/python/psutil/py3/psutil/arch/windows/cpu.c b/contrib/python/psutil/py3/psutil/arch/windows/cpu.c index 18f32e5983..9d89e5bb6c 100644 --- a/contrib/python/psutil/py3/psutil/arch/windows/cpu.c +++ b/contrib/python/psutil/py3/psutil/arch/windows/cpu.c @@ -100,7 +100,7 @@ psutil_per_cpu_times(PyObject *self, PyObject *args) { goto error; } - // gets cpu time informations + // gets cpu time information status = NtQuerySystemInformation( SystemProcessorPerformanceInformation, sppi, @@ -172,16 +172,15 @@ psutil_cpu_count_logical(PyObject *self, PyObject *args) { if (ncpus != 0) return Py_BuildValue("I", ncpus); else - Py_RETURN_NONE; // mimick os.cpu_count() + Py_RETURN_NONE; // mimic os.cpu_count() } /* - * Return the number of physical CPU cores (hyper-thread CPUs count - * is excluded). + * Return the number of CPU cores (non hyper-threading). */ PyObject * -psutil_cpu_count_phys(PyObject *self, PyObject *args) { +psutil_cpu_count_cores(PyObject *self, PyObject *args) { DWORD rc; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX buffer = NULL; PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX ptr = NULL; @@ -196,7 +195,7 @@ psutil_cpu_count_phys(PyObject *self, PyObject *args) { // than 64 CPUs. See: // https://bugs.python.org/issue33166 if (GetLogicalProcessorInformationEx == NULL) { - psutil_debug("Win < 7; cpu_count_phys() forced to None"); + psutil_debug("Win < 7; cpu_count_cores() forced to None"); Py_RETURN_NONE; } @@ -216,7 +215,7 @@ psutil_cpu_count_phys(PyObject *self, PyObject *args) { } } else { - psutil_debug("GetLogicalProcessorInformationEx() returned ", + psutil_debug("GetLogicalProcessorInformationEx() returned %u", GetLastError()); goto return_none; } @@ -249,7 +248,7 @@ psutil_cpu_count_phys(PyObject *self, PyObject *args) { } else { psutil_debug("GetLogicalProcessorInformationEx() count was 0"); - Py_RETURN_NONE; // mimick os.cpu_count() + Py_RETURN_NONE; // mimic os.cpu_count() } return_none: diff --git a/contrib/python/psutil/py3/psutil/arch/windows/cpu.h b/contrib/python/psutil/py3/psutil/arch/windows/cpu.h index d88c221210..1ef3ff1f04 100644 --- a/contrib/python/psutil/py3/psutil/arch/windows/cpu.h +++ b/contrib/python/psutil/py3/psutil/arch/windows/cpu.h @@ -7,7 +7,7 @@ #include <Python.h> PyObject *psutil_cpu_count_logical(PyObject *self, PyObject *args); -PyObject *psutil_cpu_count_phys(PyObject *self, PyObject *args); +PyObject *psutil_cpu_count_cores(PyObject *self, PyObject *args); PyObject *psutil_cpu_freq(PyObject *self, PyObject *args); PyObject *psutil_cpu_stats(PyObject *self, PyObject *args); PyObject *psutil_cpu_times(PyObject *self, PyObject *args); diff --git a/contrib/python/psutil/py3/psutil/arch/windows/disk.c b/contrib/python/psutil/py3/psutil/arch/windows/disk.c index 2288f22c56..e72e3a7554 100644 --- a/contrib/python/psutil/py3/psutil/arch/windows/disk.c +++ b/contrib/python/psutil/py3/psutil/arch/windows/disk.c @@ -45,6 +45,8 @@ PyObject * psutil_disk_usage(PyObject *self, PyObject *args) { BOOL retval; ULARGE_INTEGER _, total, free; + +#if PY_MAJOR_VERSION <= 2 char *path; if (PyArg_ParseTuple(args, "u", &path)) { @@ -56,7 +58,6 @@ psutil_disk_usage(PyObject *self, PyObject *args) { // on Python 2 we also want to accept plain strings other // than Unicode -#if PY_MAJOR_VERSION <= 2 PyErr_Clear(); // drop the argument parsing error if (PyArg_ParseTuple(args, "s", &path)) { Py_BEGIN_ALLOW_THREADS @@ -64,15 +65,35 @@ psutil_disk_usage(PyObject *self, PyObject *args) { Py_END_ALLOW_THREADS goto return_; } -#endif return NULL; return_: if (retval == 0) return PyErr_SetFromWindowsErrWithFilename(0, path); - else - return Py_BuildValue("(LL)", total.QuadPart, free.QuadPart); +#else + PyObject *py_path; + wchar_t *path; + + if (!PyArg_ParseTuple(args, "U", &py_path)) { + return NULL; + } + + path = PyUnicode_AsWideCharString(py_path, NULL); + if (path == NULL) { + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + retval = GetDiskFreeSpaceExW(path, &_, &total, &free); + Py_END_ALLOW_THREADS + + PyMem_Free(path); + + if (retval == 0) + return PyErr_SetExcFromWindowsErrWithFilenameObject(PyExc_OSError, 0, py_path); +#endif + return Py_BuildValue("(LL)", total.QuadPart, free.QuadPart); } @@ -202,7 +223,7 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { int type; int ret; unsigned int old_mode = 0; - char opts[20]; + char opts[50]; HANDLE mp_h; BOOL mp_flag= TRUE; LPTSTR fs_type[MAX_PATH + 1] = { 0 }; @@ -291,6 +312,7 @@ psutil_disk_partitions(PyObject *self, PyObject *args) { mp_h = FindFirstVolumeMountPoint( drive_letter, mp_buf, MAX_PATH); if (mp_h != INVALID_HANDLE_VALUE) { + mp_flag = TRUE; while (mp_flag) { // Append full mount path with drive letter strcpy_s(mp_path, _countof(mp_path), drive_letter); @@ -365,7 +387,7 @@ error: If no match is found return an empty string. */ PyObject * -psutil_win32_QueryDosDevice(PyObject *self, PyObject *args) { +psutil_QueryDosDevice(PyObject *self, PyObject *args) { LPCTSTR lpDevicePath; TCHAR d = TEXT('A'); TCHAR szBuff[5]; diff --git a/contrib/python/psutil/py3/psutil/arch/windows/disk.h b/contrib/python/psutil/py3/psutil/arch/windows/disk.h index 298fb6ba0e..28bed22b54 100644 --- a/contrib/python/psutil/py3/psutil/arch/windows/disk.h +++ b/contrib/python/psutil/py3/psutil/arch/windows/disk.h @@ -9,4 +9,4 @@ PyObject *psutil_disk_io_counters(PyObject *self, PyObject *args); PyObject *psutil_disk_partitions(PyObject *self, PyObject *args); PyObject *psutil_disk_usage(PyObject *self, PyObject *args); -PyObject *psutil_win32_QueryDosDevice(PyObject *self, PyObject *args); +PyObject *psutil_QueryDosDevice(PyObject *self, PyObject *args); diff --git a/contrib/python/psutil/py3/psutil/arch/windows/mem.c b/contrib/python/psutil/py3/psutil/arch/windows/mem.c new file mode 100644 index 0000000000..24dc15ad0e --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/windows/mem.c @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> +#include <windows.h> +#include <Psapi.h> +#include <pdh.h> + +#include "../../_psutil_common.h" + + +PyObject * +psutil_getpagesize(PyObject *self, PyObject *args) { + // XXX: we may want to use GetNativeSystemInfo to differentiate + // page size for WoW64 processes (but am not sure). + return Py_BuildValue("I", PSUTIL_SYSTEM_INFO.dwPageSize); +} + + +PyObject * +psutil_virtual_mem(PyObject *self, PyObject *args) { + unsigned long long totalPhys, availPhys, totalSys, availSys, pageSize; + PERFORMANCE_INFORMATION perfInfo; + + if (! GetPerformanceInfo(&perfInfo, sizeof(PERFORMANCE_INFORMATION))) { + PyErr_SetFromWindowsErr(0); + return NULL; + } + // values are size_t, widen (if needed) to long long + pageSize = perfInfo.PageSize; + totalPhys = perfInfo.PhysicalTotal * pageSize; + availPhys = perfInfo.PhysicalAvailable * pageSize; + totalSys = perfInfo.CommitLimit * pageSize; + availSys = totalSys - perfInfo.CommitTotal * pageSize; + return Py_BuildValue( + "(LLLL)", + totalPhys, + availPhys, + totalSys, + availSys); +} + + +// Return a float representing the percent usage of all paging files on +// the system. +PyObject * +psutil_swap_percent(PyObject *self, PyObject *args) { + WCHAR *szCounterPath = L"\\Paging File(_Total)\\% Usage"; + PDH_STATUS s; + HQUERY hQuery; + HCOUNTER hCounter; + PDH_FMT_COUNTERVALUE counterValue; + double percentUsage; + + if ((PdhOpenQueryW(NULL, 0, &hQuery)) != ERROR_SUCCESS) { + PyErr_Format(PyExc_RuntimeError, "PdhOpenQueryW failed"); + return NULL; + } + + s = PdhAddEnglishCounterW(hQuery, szCounterPath, 0, &hCounter); + if (s != ERROR_SUCCESS) { + PdhCloseQuery(hQuery); + PyErr_Format( + PyExc_RuntimeError, + "PdhAddEnglishCounterW failed. Performance counters may be disabled." + ); + return NULL; + } + + s = PdhCollectQueryData(hQuery); + if (s != ERROR_SUCCESS) { + // If swap disabled this will fail. + psutil_debug("PdhCollectQueryData failed; assume swap percent is 0"); + percentUsage = 0; + } + else { + s = PdhGetFormattedCounterValue( + (PDH_HCOUNTER)hCounter, PDH_FMT_DOUBLE, 0, &counterValue); + if (s != ERROR_SUCCESS) { + PdhCloseQuery(hQuery); + PyErr_Format( + PyExc_RuntimeError, "PdhGetFormattedCounterValue failed"); + return NULL; + } + percentUsage = counterValue.doubleValue; + } + + PdhRemoveCounter(hCounter); + PdhCloseQuery(hQuery); + return Py_BuildValue("d", percentUsage); +} diff --git a/contrib/python/psutil/py3/psutil/arch/windows/mem.h b/contrib/python/psutil/py3/psutil/arch/windows/mem.h new file mode 100644 index 0000000000..48d3dadee6 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/windows/mem.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> + +PyObject *psutil_getpagesize(PyObject *self, PyObject *args); +PyObject *psutil_virtual_mem(PyObject *self, PyObject *args); +PyObject *psutil_swap_percent(PyObject *self, PyObject *args); diff --git a/contrib/python/psutil/py3/psutil/arch/windows/ntextapi.h b/contrib/python/psutil/py3/psutil/arch/windows/ntextapi.h index a3cc932470..6323152a59 100644 --- a/contrib/python/psutil/py3/psutil/arch/windows/ntextapi.h +++ b/contrib/python/psutil/py3/psutil/arch/windows/ntextapi.h @@ -403,7 +403,7 @@ typedef struct _WTSINFOW { #define PWTSINFO PWTSINFOW -// cpu_count_phys() +// cpu_count_cores() #if (_WIN32_WINNT < 0x0601) // Windows < 7 (Vista and XP) typedef struct _SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX { LOGICAL_PROCESSOR_RELATIONSHIP Relationship; diff --git a/contrib/python/psutil/py3/psutil/arch/windows/proc.c b/contrib/python/psutil/py3/psutil/arch/windows/proc.c new file mode 100644 index 0000000000..af3df267ac --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/windows/proc.c @@ -0,0 +1,1251 @@ +/* + * Copyright (c) 2009, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* + * Process related functions. Original code was moved in here from + * psutil/_psutil_windows.c in 2023. For reference, here's the GIT blame + * history before the move: + * https://github.com/giampaolo/psutil/blame/59504a5/psutil/_psutil_windows.c +*/ + +// Fixes clash between winsock2.h and windows.h +#define WIN32_LEAN_AND_MEAN + +#include <Python.h> +#include <windows.h> +#include <Psapi.h> // memory_info(), memory_maps() +#include <signal.h> +#include <tlhelp32.h> // threads(), PROCESSENTRY32 + +// Link with Iphlpapi.lib +#pragma comment(lib, "IPHLPAPI.lib") + +#include "../../_psutil_common.h" +#include "proc.h" +#include "proc_info.h" +#include "proc_handles.h" +#include "proc_utils.h" + + +// Raised by Process.wait(). +PyObject *TimeoutExpired; +PyObject *TimeoutAbandoned; + + +/* + * Return 1 if PID exists in the current process list, else 0. + */ +PyObject * +psutil_pid_exists(PyObject *self, PyObject *args) { + DWORD pid; + int status; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + + status = psutil_pid_is_running(pid); + if (-1 == status) + return NULL; // exception raised in psutil_pid_is_running() + return PyBool_FromLong(status); +} + + +/* + * Return a Python list of all the PIDs running on the system. + */ +PyObject * +psutil_pids(PyObject *self, PyObject *args) { + DWORD *proclist = NULL; + DWORD numberOfReturnedPIDs; + DWORD i; + PyObject *py_pid = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + proclist = psutil_get_pids(&numberOfReturnedPIDs); + if (proclist == NULL) + goto error; + + for (i = 0; i < numberOfReturnedPIDs; i++) { + py_pid = PyLong_FromPid(proclist[i]); + if (!py_pid) + goto error; + if (PyList_Append(py_retlist, py_pid)) + goto error; + Py_CLEAR(py_pid); + } + + // free C array allocated for PIDs + free(proclist); + return py_retlist; + +error: + Py_XDECREF(py_pid); + Py_DECREF(py_retlist); + if (proclist != NULL) + free(proclist); + return NULL; +} + + +/* + * Kill a process given its PID. + */ +PyObject * +psutil_proc_kill(PyObject *self, PyObject *args) { + HANDLE hProcess; + DWORD pid; + DWORD access = PROCESS_TERMINATE | PROCESS_QUERY_LIMITED_INFORMATION; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + if (pid == 0) + return AccessDenied("automatically set for PID 0"); + + hProcess = psutil_handle_from_pid(pid, access); + if (hProcess == NULL) { + return NULL; + } + + if (! TerminateProcess(hProcess, SIGTERM)) { + // ERROR_ACCESS_DENIED may happen if the process already died. See: + // https://github.com/giampaolo/psutil/issues/1099 + // http://bugs.python.org/issue14252 + if (GetLastError() != ERROR_ACCESS_DENIED) { + PyErr_SetFromOSErrnoWithSyscall("TerminateProcess"); + return NULL; + } + } + + CloseHandle(hProcess); + Py_RETURN_NONE; +} + + +/* + * Wait for process to terminate and return its exit code. + */ +PyObject * +psutil_proc_wait(PyObject *self, PyObject *args) { + HANDLE hProcess; + DWORD ExitCode; + DWORD retVal; + DWORD pid; + long timeout; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID "l", &pid, &timeout)) + return NULL; + if (pid == 0) + return AccessDenied("automatically set for PID 0"); + + hProcess = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, + FALSE, pid); + if (hProcess == NULL) { + if (GetLastError() == ERROR_INVALID_PARAMETER) { + // no such process; we do not want to raise NSP but + // return None instead. + Py_RETURN_NONE; + } + else { + PyErr_SetFromOSErrnoWithSyscall("OpenProcess"); + return NULL; + } + } + + // wait until the process has terminated + Py_BEGIN_ALLOW_THREADS + retVal = WaitForSingleObject(hProcess, timeout); + Py_END_ALLOW_THREADS + + // handle return code + if (retVal == WAIT_FAILED) { + PyErr_SetFromOSErrnoWithSyscall("WaitForSingleObject"); + CloseHandle(hProcess); + return NULL; + } + if (retVal == WAIT_TIMEOUT) { + PyErr_SetString(TimeoutExpired, + "WaitForSingleObject() returned WAIT_TIMEOUT"); + CloseHandle(hProcess); + return NULL; + } + if (retVal == WAIT_ABANDONED) { + psutil_debug("WaitForSingleObject() -> WAIT_ABANDONED"); + PyErr_SetString(TimeoutAbandoned, + "WaitForSingleObject() returned WAIT_ABANDONED"); + CloseHandle(hProcess); + return NULL; + } + + // WaitForSingleObject() returned WAIT_OBJECT_0. It means the + // process is gone so we can get its process exit code. The PID + // may still stick around though but we'll handle that from Python. + if (GetExitCodeProcess(hProcess, &ExitCode) == 0) { + PyErr_SetFromOSErrnoWithSyscall("GetExitCodeProcess"); + CloseHandle(hProcess); + return NULL; + } + + CloseHandle(hProcess); + +#if PY_MAJOR_VERSION >= 3 + return PyLong_FromLong((long) ExitCode); +#else + return PyInt_FromLong((long) ExitCode); +#endif +} + + +/* + * Return a Python tuple (user_time, kernel_time) + */ +PyObject * +psutil_proc_times(PyObject *self, PyObject *args) { + DWORD pid; + HANDLE hProcess; + FILETIME ftCreate, ftExit, ftKernel, ftUser; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + + if (hProcess == NULL) + return NULL; + if (! GetProcessTimes(hProcess, &ftCreate, &ftExit, &ftKernel, &ftUser)) { + if (GetLastError() == ERROR_ACCESS_DENIED) { + // usually means the process has died so we throw a NoSuchProcess + // here + NoSuchProcess("GetProcessTimes -> ERROR_ACCESS_DENIED"); + } + else { + PyErr_SetFromWindowsErr(0); + } + CloseHandle(hProcess); + return NULL; + } + + CloseHandle(hProcess); + + /* + * User and kernel times are represented as a FILETIME structure + * which contains a 64-bit value representing the number of + * 100-nanosecond intervals since January 1, 1601 (UTC): + * http://msdn.microsoft.com/en-us/library/ms724284(VS.85).aspx + * To convert it into a float representing the seconds that the + * process has executed in user/kernel mode I borrowed the code + * below from Python's Modules/posixmodule.c + */ + return Py_BuildValue( + "(ddd)", + (double)(ftUser.dwHighDateTime * HI_T + \ + ftUser.dwLowDateTime * LO_T), + (double)(ftKernel.dwHighDateTime * HI_T + \ + ftKernel.dwLowDateTime * LO_T), + psutil_FiletimeToUnixTime(ftCreate) + ); +} + + +/* + * Return process executable path. Works for all processes regardless of + * privilege. NtQuerySystemInformation has some sort of internal cache, + * since it succeeds even when a process is gone (but not if a PID never + * existed). + */ +PyObject * +psutil_proc_exe(PyObject *self, PyObject *args) { + DWORD pid; + NTSTATUS status; + PVOID buffer = NULL; + ULONG bufferSize = 0x104 * 2; // WIN_MAX_PATH * sizeof(wchar_t) + SYSTEM_PROCESS_ID_INFORMATION processIdInfo; + PyObject *py_exe; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + + if (pid == 0) + return AccessDenied("automatically set for PID 0"); + + // ...because NtQuerySystemInformation can succeed for terminated + // processes. + if (psutil_pid_is_running(pid) == 0) + return NoSuchProcess("psutil_pid_is_running -> 0"); + + buffer = MALLOC_ZERO(bufferSize); + if (! buffer) { + PyErr_NoMemory(); + return NULL; + } + + processIdInfo.ProcessId = (HANDLE)(ULONG_PTR)pid; + processIdInfo.ImageName.Length = 0; + processIdInfo.ImageName.MaximumLength = (USHORT)bufferSize; + processIdInfo.ImageName.Buffer = buffer; + + status = NtQuerySystemInformation( + SystemProcessIdInformation, + &processIdInfo, + sizeof(SYSTEM_PROCESS_ID_INFORMATION), + NULL); + + if ((status == STATUS_INFO_LENGTH_MISMATCH) && + (processIdInfo.ImageName.MaximumLength <= bufferSize)) + { + // Required length was NOT stored in MaximumLength (WOW64 issue). + ULONG maxBufferSize = 0x7FFF * 2; // NTFS_MAX_PATH * sizeof(wchar_t) + do { + // Iteratively double the size of the buffer up to maxBufferSize + bufferSize *= 2; + FREE(buffer); + buffer = MALLOC_ZERO(bufferSize); + if (! buffer) { + PyErr_NoMemory(); + return NULL; + } + + processIdInfo.ImageName.MaximumLength = (USHORT)bufferSize; + processIdInfo.ImageName.Buffer = buffer; + + status = NtQuerySystemInformation( + SystemProcessIdInformation, + &processIdInfo, + sizeof(SYSTEM_PROCESS_ID_INFORMATION), + NULL); + } while ((status == STATUS_INFO_LENGTH_MISMATCH) && + (bufferSize <= maxBufferSize)); + } + else if (status == STATUS_INFO_LENGTH_MISMATCH) { + // Required length is stored in MaximumLength. + FREE(buffer); + buffer = MALLOC_ZERO(processIdInfo.ImageName.MaximumLength); + if (! buffer) { + PyErr_NoMemory(); + return NULL; + } + + processIdInfo.ImageName.Buffer = buffer; + + status = NtQuerySystemInformation( + SystemProcessIdInformation, + &processIdInfo, + sizeof(SYSTEM_PROCESS_ID_INFORMATION), + NULL); + } + + if (! NT_SUCCESS(status)) { + FREE(buffer); + if (psutil_pid_is_running(pid) == 0) + NoSuchProcess("psutil_pid_is_running -> 0"); + else + psutil_SetFromNTStatusErr(status, "NtQuerySystemInformation"); + return NULL; + } + + if (processIdInfo.ImageName.Buffer == NULL) { + // Happens for PID 4. + py_exe = Py_BuildValue("s", ""); + } + else { + py_exe = PyUnicode_FromWideChar(processIdInfo.ImageName.Buffer, + processIdInfo.ImageName.Length / 2); + } + FREE(buffer); + return py_exe; +} + + +/* + * Return process memory information as a Python tuple. + */ +PyObject * +psutil_proc_memory_info(PyObject *self, PyObject *args) { + HANDLE hProcess; + DWORD pid; + PROCESS_MEMORY_COUNTERS_EX cnt; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + if (NULL == hProcess) + return NULL; + + if (! GetProcessMemoryInfo(hProcess, (PPROCESS_MEMORY_COUNTERS)&cnt, + sizeof(cnt))) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hProcess); + return NULL; + } + CloseHandle(hProcess); + + // PROCESS_MEMORY_COUNTERS values are defined as SIZE_T which on 64bits + // is an (unsigned long long) and on 32bits is an (unsigned int). + // "_WIN64" is defined if we're running a 64bit Python interpreter not + // exclusively if the *system* is 64bit. +#if defined(_WIN64) + return Py_BuildValue( + "(kKKKKKKKKK)", + cnt.PageFaultCount, // unsigned long + (unsigned long long)cnt.PeakWorkingSetSize, + (unsigned long long)cnt.WorkingSetSize, + (unsigned long long)cnt.QuotaPeakPagedPoolUsage, + (unsigned long long)cnt.QuotaPagedPoolUsage, + (unsigned long long)cnt.QuotaPeakNonPagedPoolUsage, + (unsigned long long)cnt.QuotaNonPagedPoolUsage, + (unsigned long long)cnt.PagefileUsage, + (unsigned long long)cnt.PeakPagefileUsage, + (unsigned long long)cnt.PrivateUsage); +#else + return Py_BuildValue( + "(kIIIIIIIII)", + cnt.PageFaultCount, // unsigned long + (unsigned int)cnt.PeakWorkingSetSize, + (unsigned int)cnt.WorkingSetSize, + (unsigned int)cnt.QuotaPeakPagedPoolUsage, + (unsigned int)cnt.QuotaPagedPoolUsage, + (unsigned int)cnt.QuotaPeakNonPagedPoolUsage, + (unsigned int)cnt.QuotaNonPagedPoolUsage, + (unsigned int)cnt.PagefileUsage, + (unsigned int)cnt.PeakPagefileUsage, + (unsigned int)cnt.PrivateUsage); +#endif +} + + +static int +psutil_GetProcWsetInformation( + DWORD pid, + HANDLE hProcess, + PMEMORY_WORKING_SET_INFORMATION *wSetInfo) +{ + NTSTATUS status; + PVOID buffer; + SIZE_T bufferSize; + + bufferSize = 0x8000; + buffer = MALLOC_ZERO(bufferSize); + if (! buffer) { + PyErr_NoMemory(); + return 1; + } + + while ((status = NtQueryVirtualMemory( + hProcess, + NULL, + MemoryWorkingSetInformation, + buffer, + bufferSize, + NULL)) == STATUS_INFO_LENGTH_MISMATCH) + { + FREE(buffer); + bufferSize *= 2; + // Fail if we're resizing the buffer to something very large. + if (bufferSize > 256 * 1024 * 1024) { + PyErr_SetString(PyExc_RuntimeError, + "NtQueryVirtualMemory bufsize is too large"); + return 1; + } + buffer = MALLOC_ZERO(bufferSize); + if (! buffer) { + PyErr_NoMemory(); + return 1; + } + } + + if (!NT_SUCCESS(status)) { + if (status == STATUS_ACCESS_DENIED) { + AccessDenied("NtQueryVirtualMemory -> STATUS_ACCESS_DENIED"); + } + else if (psutil_pid_is_running(pid) == 0) { + NoSuchProcess("psutil_pid_is_running -> 0"); + } + else { + PyErr_Clear(); + psutil_SetFromNTStatusErr( + status, "NtQueryVirtualMemory(MemoryWorkingSetInformation)"); + } + HeapFree(GetProcessHeap(), 0, buffer); + return 1; + } + + *wSetInfo = (PMEMORY_WORKING_SET_INFORMATION)buffer; + return 0; +} + + +/* + * Returns the USS of the process. + * Reference: + * https://dxr.mozilla.org/mozilla-central/source/xpcom/base/ + * nsMemoryReporterManager.cpp + */ +PyObject * +psutil_proc_memory_uss(PyObject *self, PyObject *args) { + DWORD pid; + HANDLE hProcess; + PSUTIL_PROCESS_WS_COUNTERS wsCounters; + PMEMORY_WORKING_SET_INFORMATION wsInfo; + ULONG_PTR i; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_INFORMATION); + if (hProcess == NULL) + return NULL; + + if (psutil_GetProcWsetInformation(pid, hProcess, &wsInfo) != 0) { + CloseHandle(hProcess); + return NULL; + } + memset(&wsCounters, 0, sizeof(PSUTIL_PROCESS_WS_COUNTERS)); + + for (i = 0; i < wsInfo->NumberOfEntries; i++) { + // This is what ProcessHacker does. + /* + wsCounters.NumberOfPages++; + if (wsInfo->WorkingSetInfo[i].ShareCount > 1) + wsCounters.NumberOfSharedPages++; + if (wsInfo->WorkingSetInfo[i].ShareCount == 0) + wsCounters.NumberOfPrivatePages++; + if (wsInfo->WorkingSetInfo[i].Shared) + wsCounters.NumberOfShareablePages++; + */ + + // This is what we do: count shared pages that only one process + // is using as private (USS). + if (!wsInfo->WorkingSetInfo[i].Shared || + wsInfo->WorkingSetInfo[i].ShareCount <= 1) { + wsCounters.NumberOfPrivatePages++; + } + } + + HeapFree(GetProcessHeap(), 0, wsInfo); + CloseHandle(hProcess); + + return Py_BuildValue("I", wsCounters.NumberOfPrivatePages); +} + + +/* + * Resume or suspends a process + */ +PyObject * +psutil_proc_suspend_or_resume(PyObject *self, PyObject *args) { + DWORD pid; + NTSTATUS status; + HANDLE hProcess; + DWORD access = PROCESS_SUSPEND_RESUME | PROCESS_QUERY_LIMITED_INFORMATION; + PyObject* suspend; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID "O", &pid, &suspend)) + return NULL; + + hProcess = psutil_handle_from_pid(pid, access); + if (hProcess == NULL) + return NULL; + + if (PyObject_IsTrue(suspend)) + status = NtSuspendProcess(hProcess); + else + status = NtResumeProcess(hProcess); + + if (! NT_SUCCESS(status)) { + CloseHandle(hProcess); + return psutil_SetFromNTStatusErr(status, "NtSuspend|ResumeProcess"); + } + + CloseHandle(hProcess); + Py_RETURN_NONE; +} + + +PyObject * +psutil_proc_threads(PyObject *self, PyObject *args) { + HANDLE hThread = NULL; + THREADENTRY32 te32 = {0}; + DWORD pid; + int pid_return; + int rc; + FILETIME ftDummy, ftKernel, ftUser; + HANDLE hThreadSnap = NULL; + PyObject *py_tuple = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + goto error; + if (pid == 0) { + // raise AD instead of returning 0 as procexp is able to + // retrieve useful information somehow + AccessDenied("forced for PID 0"); + goto error; + } + + pid_return = psutil_pid_is_running(pid); + if (pid_return == 0) { + NoSuchProcess("psutil_pid_is_running -> 0"); + goto error; + } + if (pid_return == -1) + goto error; + + hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if (hThreadSnap == INVALID_HANDLE_VALUE) { + PyErr_SetFromOSErrnoWithSyscall("CreateToolhelp32Snapshot"); + goto error; + } + + // Fill in the size of the structure before using it + te32.dwSize = sizeof(THREADENTRY32); + + if (! Thread32First(hThreadSnap, &te32)) { + PyErr_SetFromOSErrnoWithSyscall("Thread32First"); + goto error; + } + + // Walk the thread snapshot to find all threads of the process. + // If the thread belongs to the process, increase the counter. + do { + if (te32.th32OwnerProcessID == pid) { + py_tuple = NULL; + hThread = NULL; + hThread = OpenThread(THREAD_QUERY_INFORMATION, + FALSE, te32.th32ThreadID); + if (hThread == NULL) { + // thread has disappeared on us + continue; + } + + rc = GetThreadTimes(hThread, &ftDummy, &ftDummy, &ftKernel, + &ftUser); + if (rc == 0) { + PyErr_SetFromOSErrnoWithSyscall("GetThreadTimes"); + goto error; + } + + /* + * User and kernel times are represented as a FILETIME structure + * which contains a 64-bit value representing the number of + * 100-nanosecond intervals since January 1, 1601 (UTC): + * http://msdn.microsoft.com/en-us/library/ms724284(VS.85).aspx + * To convert it into a float representing the seconds that the + * process has executed in user/kernel mode I borrowed the code + * below from Python's Modules/posixmodule.c + */ + py_tuple = Py_BuildValue( + "kdd", + te32.th32ThreadID, + (double)(ftUser.dwHighDateTime * HI_T + \ + ftUser.dwLowDateTime * LO_T), + (double)(ftKernel.dwHighDateTime * HI_T + \ + ftKernel.dwLowDateTime * LO_T)); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + + CloseHandle(hThread); + } + } while (Thread32Next(hThreadSnap, &te32)); + + CloseHandle(hThreadSnap); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_DECREF(py_retlist); + if (hThread != NULL) + CloseHandle(hThread); + if (hThreadSnap != NULL) + CloseHandle(hThreadSnap); + return NULL; +} + + +PyObject * +psutil_proc_open_files(PyObject *self, PyObject *args) { + DWORD pid; + HANDLE processHandle; + DWORD access = PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION; + PyObject *py_retlist; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + + processHandle = psutil_handle_from_pid(pid, access); + if (processHandle == NULL) + return NULL; + + py_retlist = psutil_get_open_files(pid, processHandle); + CloseHandle(processHandle); + return py_retlist; +} + + +static PTOKEN_USER +_psutil_user_token_from_pid(DWORD pid) { + HANDLE hProcess = NULL; + HANDLE hToken = NULL; + PTOKEN_USER userToken = NULL; + ULONG bufferSize = 0x100; + + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + if (hProcess == NULL) + return NULL; + + if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) { + PyErr_SetFromOSErrnoWithSyscall("OpenProcessToken"); + goto error; + } + + // Get the user SID. + while (1) { + userToken = malloc(bufferSize); + if (userToken == NULL) { + PyErr_NoMemory(); + goto error; + } + if (!GetTokenInformation(hToken, TokenUser, userToken, bufferSize, + &bufferSize)) + { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + free(userToken); + continue; + } + else { + PyErr_SetFromOSErrnoWithSyscall("GetTokenInformation"); + goto error; + } + } + break; + } + + CloseHandle(hProcess); + CloseHandle(hToken); + return userToken; + +error: + if (hProcess != NULL) + CloseHandle(hProcess); + if (hToken != NULL) + CloseHandle(hToken); + return NULL; +} + + +/* + * Return process username as a "DOMAIN//USERNAME" string. + */ +PyObject * +psutil_proc_username(PyObject *self, PyObject *args) { + DWORD pid; + PTOKEN_USER userToken = NULL; + WCHAR *userName = NULL; + WCHAR *domainName = NULL; + ULONG nameSize = 0x100; + ULONG domainNameSize = 0x100; + SID_NAME_USE nameUse; + PyObject *py_username = NULL; + PyObject *py_domain = NULL; + PyObject *py_tuple = NULL; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + userToken = _psutil_user_token_from_pid(pid); + if (userToken == NULL) + return NULL; + + // resolve the SID to a name + while (1) { + userName = malloc(nameSize * sizeof(WCHAR)); + if (userName == NULL) { + PyErr_NoMemory(); + goto error; + } + domainName = malloc(domainNameSize * sizeof(WCHAR)); + if (domainName == NULL) { + PyErr_NoMemory(); + goto error; + } + if (!LookupAccountSidW(NULL, userToken->User.Sid, userName, &nameSize, + domainName, &domainNameSize, &nameUse)) + { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + free(userName); + free(domainName); + continue; + } + else if (GetLastError() == ERROR_NONE_MAPPED) { + // From MS doc: + // https://docs.microsoft.com/en-us/windows/win32/api/winbase/ + // nf-winbase-lookupaccountsida + // If the function cannot find an account name for the SID, + // GetLastError returns ERROR_NONE_MAPPED. This can occur if + // a network time-out prevents the function from finding the + // name. It also occurs for SIDs that have no corresponding + // account name, such as a logon SID that identifies a logon + // session. + AccessDenied("LookupAccountSidW -> ERROR_NONE_MAPPED"); + goto error; + } + else { + PyErr_SetFromOSErrnoWithSyscall("LookupAccountSidW"); + goto error; + } + } + break; + } + + py_domain = PyUnicode_FromWideChar(domainName, wcslen(domainName)); + if (! py_domain) + goto error; + py_username = PyUnicode_FromWideChar(userName, wcslen(userName)); + if (! py_username) + goto error; + py_tuple = Py_BuildValue("OO", py_domain, py_username); + if (! py_tuple) + goto error; + Py_DECREF(py_domain); + Py_DECREF(py_username); + + free(userName); + free(domainName); + free(userToken); + return py_tuple; + +error: + if (userName != NULL) + free(userName); + if (domainName != NULL) + free(domainName); + if (userToken != NULL) + free(userToken); + Py_XDECREF(py_domain); + Py_XDECREF(py_username); + Py_XDECREF(py_tuple); + return NULL; +} + + +/* + * Get process priority as a Python integer. + */ +PyObject * +psutil_proc_priority_get(PyObject *self, PyObject *args) { + DWORD pid; + DWORD priority; + HANDLE hProcess; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + if (hProcess == NULL) + return NULL; + + priority = GetPriorityClass(hProcess); + if (priority == 0) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hProcess); + return NULL; + } + CloseHandle(hProcess); + return Py_BuildValue("i", priority); +} + + +/* + * Set process priority. + */ +PyObject * +psutil_proc_priority_set(PyObject *self, PyObject *args) { + DWORD pid; + int priority; + int retval; + HANDLE hProcess; + DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID "i", &pid, &priority)) + return NULL; + hProcess = psutil_handle_from_pid(pid, access); + if (hProcess == NULL) + return NULL; + + retval = SetPriorityClass(hProcess, priority); + if (retval == 0) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hProcess); + return NULL; + } + + CloseHandle(hProcess); + Py_RETURN_NONE; +} + + +/* + * Get process IO priority as a Python integer. + */ +PyObject * +psutil_proc_io_priority_get(PyObject *self, PyObject *args) { + DWORD pid; + HANDLE hProcess; + DWORD IoPriority; + NTSTATUS status; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + if (hProcess == NULL) + return NULL; + + status = NtQueryInformationProcess( + hProcess, + ProcessIoPriority, + &IoPriority, + sizeof(DWORD), + NULL + ); + + CloseHandle(hProcess); + if (! NT_SUCCESS(status)) + return psutil_SetFromNTStatusErr(status, "NtQueryInformationProcess"); + return Py_BuildValue("i", IoPriority); +} + + +/* + * Set process IO priority. + */ +PyObject * +psutil_proc_io_priority_set(PyObject *self, PyObject *args) { + DWORD pid; + DWORD prio; + HANDLE hProcess; + NTSTATUS status; + DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID "i", &pid, &prio)) + return NULL; + + hProcess = psutil_handle_from_pid(pid, access); + if (hProcess == NULL) + return NULL; + + status = NtSetInformationProcess( + hProcess, + ProcessIoPriority, + (PVOID)&prio, + sizeof(DWORD) + ); + + CloseHandle(hProcess); + if (! NT_SUCCESS(status)) + return psutil_SetFromNTStatusErr(status, "NtSetInformationProcess"); + Py_RETURN_NONE; +} + + +/* + * Return a Python tuple referencing process I/O counters. + */ +PyObject * +psutil_proc_io_counters(PyObject *self, PyObject *args) { + DWORD pid; + HANDLE hProcess; + IO_COUNTERS IoCounters; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + if (NULL == hProcess) + return NULL; + + if (! GetProcessIoCounters(hProcess, &IoCounters)) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hProcess); + return NULL; + } + + CloseHandle(hProcess); + return Py_BuildValue("(KKKKKK)", + IoCounters.ReadOperationCount, + IoCounters.WriteOperationCount, + IoCounters.ReadTransferCount, + IoCounters.WriteTransferCount, + IoCounters.OtherOperationCount, + IoCounters.OtherTransferCount); +} + + +/* + * Return process CPU affinity as a bitmask + */ +PyObject * +psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args) { + DWORD pid; + HANDLE hProcess; + DWORD_PTR proc_mask; + DWORD_PTR system_mask; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + if (hProcess == NULL) { + return NULL; + } + if (GetProcessAffinityMask(hProcess, &proc_mask, &system_mask) == 0) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hProcess); + return NULL; + } + + CloseHandle(hProcess); +#ifdef _WIN64 + return Py_BuildValue("K", (unsigned long long)proc_mask); +#else + return Py_BuildValue("k", (unsigned long)proc_mask); +#endif +} + + +/* + * Set process CPU affinity + */ +PyObject * +psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args) { + DWORD pid; + HANDLE hProcess; + DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_SET_INFORMATION; + DWORD_PTR mask; + +#ifdef _WIN64 + if (! PyArg_ParseTuple(args, _Py_PARSE_PID "K", &pid, &mask)) +#else + if (! PyArg_ParseTuple(args, _Py_PARSE_PID "k", &pid, &mask)) +#endif + { + return NULL; + } + hProcess = psutil_handle_from_pid(pid, access); + if (hProcess == NULL) + return NULL; + + if (SetProcessAffinityMask(hProcess, mask) == 0) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hProcess); + return NULL; + } + + CloseHandle(hProcess); + Py_RETURN_NONE; +} + + +/* + * Return True if all process threads are in waiting/suspended state. + */ +PyObject * +psutil_proc_is_suspended(PyObject *self, PyObject *args) { + DWORD pid; + ULONG i; + PSYSTEM_PROCESS_INFORMATION process; + PVOID buffer; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + if (! psutil_get_proc_info(pid, &process, &buffer)) + return NULL; + for (i = 0; i < process->NumberOfThreads; i++) { + if (process->Threads[i].ThreadState != Waiting || + process->Threads[i].WaitReason != Suspended) + { + free(buffer); + Py_RETURN_FALSE; + } + } + free(buffer); + Py_RETURN_TRUE; +} + + +/* + * Return the number of handles opened by process. + */ +PyObject * +psutil_proc_num_handles(PyObject *self, PyObject *args) { + DWORD pid; + HANDLE hProcess; + DWORD handleCount; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); + if (NULL == hProcess) + return NULL; + if (! GetProcessHandleCount(hProcess, &handleCount)) { + PyErr_SetFromWindowsErr(0); + CloseHandle(hProcess); + return NULL; + } + CloseHandle(hProcess); + return Py_BuildValue("k", handleCount); +} + + +static char *get_region_protection_string(ULONG protection) { + switch (protection & 0xff) { + case PAGE_NOACCESS: + return ""; + case PAGE_READONLY: + return "r"; + case PAGE_READWRITE: + return "rw"; + case PAGE_WRITECOPY: + return "wc"; + case PAGE_EXECUTE: + return "x"; + case PAGE_EXECUTE_READ: + return "xr"; + case PAGE_EXECUTE_READWRITE: + return "xrw"; + case PAGE_EXECUTE_WRITECOPY: + return "xwc"; + default: + return "?"; + } +} + + +/* + * Return a list of process's memory mappings. + */ +PyObject * +psutil_proc_memory_maps(PyObject *self, PyObject *args) { + MEMORY_BASIC_INFORMATION basicInfo; + DWORD pid; + HANDLE hProcess = NULL; + PVOID baseAddress; + WCHAR mappedFileName[MAX_PATH]; + LPVOID maxAddr; + // required by GetMappedFileNameW + DWORD access = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; + PyObject *py_retlist = PyList_New(0); + PyObject *py_tuple = NULL; + PyObject *py_str = NULL; + + if (py_retlist == NULL) + return NULL; + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + goto error; + hProcess = psutil_handle_from_pid(pid, access); + if (NULL == hProcess) + goto error; + + maxAddr = PSUTIL_SYSTEM_INFO.lpMaximumApplicationAddress; + baseAddress = NULL; + + while (VirtualQueryEx(hProcess, baseAddress, &basicInfo, + sizeof(MEMORY_BASIC_INFORMATION))) + { + py_tuple = NULL; + if (baseAddress > maxAddr) + break; + if (GetMappedFileNameW(hProcess, baseAddress, mappedFileName, + sizeof(mappedFileName))) + { + py_str = PyUnicode_FromWideChar(mappedFileName, + wcslen(mappedFileName)); + if (py_str == NULL) + goto error; +#ifdef _WIN64 + py_tuple = Py_BuildValue( + "(KsOI)", + (unsigned long long)baseAddress, +#else + py_tuple = Py_BuildValue( + "(ksOI)", + (unsigned long)baseAddress, +#endif + get_region_protection_string(basicInfo.Protect), + py_str, + basicInfo.RegionSize); + + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_tuple); + Py_CLEAR(py_str); + } + baseAddress = (PCHAR)baseAddress + basicInfo.RegionSize; + } + + CloseHandle(hProcess); + return py_retlist; + +error: + Py_XDECREF(py_tuple); + Py_XDECREF(py_str); + Py_DECREF(py_retlist); + if (hProcess != NULL) + CloseHandle(hProcess); + return NULL; +} + + +/* + * Return a {pid:ppid, ...} dict for all running processes. + */ +PyObject * +psutil_ppid_map(PyObject *self, PyObject *args) { + PyObject *py_pid = NULL; + PyObject *py_ppid = NULL; + PyObject *py_retdict = PyDict_New(); + HANDLE handle = NULL; + PROCESSENTRY32 pe = {0}; + pe.dwSize = sizeof(PROCESSENTRY32); + + if (py_retdict == NULL) + return NULL; + handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (handle == INVALID_HANDLE_VALUE) { + PyErr_SetFromWindowsErr(0); + Py_DECREF(py_retdict); + return NULL; + } + + if (Process32First(handle, &pe)) { + do { + py_pid = PyLong_FromPid(pe.th32ProcessID); + if (py_pid == NULL) + goto error; + py_ppid = PyLong_FromPid(pe.th32ParentProcessID); + if (py_ppid == NULL) + goto error; + if (PyDict_SetItem(py_retdict, py_pid, py_ppid)) + goto error; + Py_CLEAR(py_pid); + Py_CLEAR(py_ppid); + } while (Process32Next(handle, &pe)); + } + + CloseHandle(handle); + return py_retdict; + +error: + Py_XDECREF(py_pid); + Py_XDECREF(py_ppid); + Py_DECREF(py_retdict); + CloseHandle(handle); + return NULL; +} diff --git a/contrib/python/psutil/py3/psutil/arch/windows/proc.h b/contrib/python/psutil/py3/psutil/arch/windows/proc.h new file mode 100644 index 0000000000..048d27dd7c --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/windows/proc.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> + +extern PyObject *TimeoutExpired; +extern PyObject *TimeoutAbandoned; + +PyObject *psutil_pid_exists(PyObject *self, PyObject *args); +PyObject *psutil_pids(PyObject *self, PyObject *args); +PyObject *psutil_ppid_map(PyObject *self, PyObject *args); +PyObject *psutil_proc_cpu_affinity_get(PyObject *self, PyObject *args); +PyObject *psutil_proc_cpu_affinity_set(PyObject *self, PyObject *args); +PyObject *psutil_proc_exe(PyObject *self, PyObject *args); +PyObject *psutil_proc_io_counters(PyObject *self, PyObject *args); +PyObject *psutil_proc_io_priority_get(PyObject *self, PyObject *args); +PyObject *psutil_proc_io_priority_set(PyObject *self, PyObject *args); +PyObject *psutil_proc_is_suspended(PyObject *self, PyObject *args); +PyObject *psutil_proc_kill(PyObject *self, PyObject *args); +PyObject *psutil_proc_memory_info(PyObject *self, PyObject *args); +PyObject *psutil_proc_memory_maps(PyObject *self, PyObject *args); +PyObject *psutil_proc_memory_uss(PyObject *self, PyObject *args); +PyObject *psutil_proc_num_handles(PyObject *self, PyObject *args); +PyObject *psutil_proc_open_files(PyObject *self, PyObject *args); +PyObject *psutil_proc_priority_get(PyObject *self, PyObject *args); +PyObject *psutil_proc_priority_set(PyObject *self, PyObject *args); +PyObject *psutil_proc_suspend_or_resume(PyObject *self, PyObject *args); +PyObject *psutil_proc_threads(PyObject *self, PyObject *args); +PyObject *psutil_proc_times(PyObject *self, PyObject *args); +PyObject *psutil_proc_username(PyObject *self, PyObject *args); +PyObject *psutil_proc_wait(PyObject *self, PyObject *args); diff --git a/contrib/python/psutil/py3/psutil/arch/windows/process_handles.c b/contrib/python/psutil/py3/psutil/arch/windows/proc_handles.c index 72e3f4d41e..30e7cd2d84 100644 --- a/contrib/python/psutil/py3/psutil/arch/windows/process_handles.c +++ b/contrib/python/psutil/py3/psutil/arch/windows/proc_handles.c @@ -23,7 +23,7 @@ #include <Python.h> #include "../../_psutil_common.h" -#include "process_utils.h" +#include "proc_utils.h" #define THREAD_TIMEOUT 100 // ms diff --git a/contrib/python/psutil/py3/psutil/arch/windows/process_handles.h b/contrib/python/psutil/py3/psutil/arch/windows/proc_handles.h index d1be3152d5..d1be3152d5 100644 --- a/contrib/python/psutil/py3/psutil/arch/windows/process_handles.h +++ b/contrib/python/psutil/py3/psutil/arch/windows/proc_handles.h diff --git a/contrib/python/psutil/py3/psutil/arch/windows/process_info.c b/contrib/python/psutil/py3/psutil/arch/windows/proc_info.c index d44c4eb75e..5d16b8133d 100644 --- a/contrib/python/psutil/py3/psutil/arch/windows/process_info.c +++ b/contrib/python/psutil/py3/psutil/arch/windows/proc_info.c @@ -11,8 +11,8 @@ #include <windows.h> #include "../../_psutil_common.h" -#include "process_info.h" -#include "process_utils.h" +#include "proc_info.h" +#include "proc_utils.h" #ifndef _WIN64 @@ -24,6 +24,13 @@ typedef NTSTATUS (NTAPI *__NtQueryInformationProcess)( PDWORD ReturnLength); #endif +#define PSUTIL_FIRST_PROCESS(Processes) ( \ + (PSYSTEM_PROCESS_INFORMATION)(Processes)) +#define PSUTIL_NEXT_PROCESS(Process) ( \ + ((PSYSTEM_PROCESS_INFORMATION)(Process))->NextEntryOffset ? \ + (PSYSTEM_PROCESS_INFORMATION)((PCHAR)(Process) + \ + ((PSYSTEM_PROCESS_INFORMATION)(Process))->NextEntryOffset) : NULL) + /* * Given a pointer into a process's memory, figure out how much @@ -535,15 +542,38 @@ error: * with given pid or NULL on error. */ PyObject * -psutil_get_cmdline(DWORD pid, int use_peb) { - PyObject *ret = NULL; +psutil_proc_cmdline(PyObject *self, PyObject *args, PyObject *kwdict) { WCHAR *data = NULL; + LPWSTR *szArglist = NULL; SIZE_T size; + int nArgs; + int i; + int func_ret; + DWORD pid; + int pid_return; + int use_peb; + // TODO: shouldn't this be decref-ed in case of error on + // PyArg_ParseTuple? + PyObject *py_usepeb = Py_True; PyObject *py_retlist = NULL; PyObject *py_unicode = NULL; - LPWSTR *szArglist = NULL; - int nArgs, i; - int func_ret; + static char *keywords[] = {"pid", "use_peb", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, kwdict, _Py_PARSE_PID "|O", + keywords, &pid, &py_usepeb)) + { + return NULL; + } + if ((pid == 0) || (pid == 4)) + return Py_BuildValue("[]"); + + pid_return = psutil_pid_is_running(pid); + if (pid_return == 0) + return NoSuchProcess("psutil_pid_is_running -> 0"); + if (pid_return == -1) + return NULL; + + use_peb = (py_usepeb == Py_True) ? 1 : 0; /* Reading the PEB to get the cmdline seem to be the best method if @@ -559,47 +589,60 @@ psutil_get_cmdline(DWORD pid, int use_peb) { else func_ret = psutil_cmdline_query_proc(pid, &data, &size); if (func_ret != 0) - goto out; + goto error; // attempt to parse the command line using Win32 API szArglist = CommandLineToArgvW(data, &nArgs); if (szArglist == NULL) { PyErr_SetFromOSErrnoWithSyscall("CommandLineToArgvW"); - goto out; + goto error; } // arglist parsed as array of UNICODE_STRING, so convert each to // Python string object and add to arg list py_retlist = PyList_New(nArgs); if (py_retlist == NULL) - goto out; + goto error; for (i = 0; i < nArgs; i++) { py_unicode = PyUnicode_FromWideChar(szArglist[i], wcslen(szArglist[i])); if (py_unicode == NULL) - goto out; - PyList_SET_ITEM(py_retlist, i, py_unicode); + goto error; + PyList_SetItem(py_retlist, i, py_unicode); py_unicode = NULL; } - ret = py_retlist; - py_retlist = NULL; -out: + LocalFree(szArglist); + free(data); + return py_retlist; + +error: if (szArglist != NULL) LocalFree(szArglist); if (data != NULL) free(data); Py_XDECREF(py_unicode); Py_XDECREF(py_retlist); - return ret; + return NULL; } PyObject * -psutil_get_cwd(DWORD pid) { +psutil_proc_cwd(PyObject *self, PyObject *args) { + DWORD pid; PyObject *ret = NULL; WCHAR *data = NULL; SIZE_T size; + int pid_return; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + + pid_return = psutil_pid_is_running(pid); + if (pid_return == 0) + return NoSuchProcess("psutil_pid_is_running -> 0"); + if (pid_return == -1) + return NULL; if (psutil_get_process_data(pid, KIND_CWD, &data, &size) != 0) goto out; @@ -620,10 +663,23 @@ out: * process with given pid or NULL on error. */ PyObject * -psutil_get_environ(DWORD pid) { - PyObject *ret = NULL; +psutil_proc_environ(PyObject *self, PyObject *args) { + DWORD pid; WCHAR *data = NULL; SIZE_T size; + int pid_return; + PyObject *ret = NULL; + + if (! PyArg_ParseTuple(args, _Py_PARSE_PID, &pid)) + return NULL; + if ((pid == 0) || (pid == 4)) + return Py_BuildValue("s", ""); + + pid_return = psutil_pid_is_running(pid); + if (pid_return == 0) + return NoSuchProcess("psutil_pid_is_running -> 0"); + if (pid_return == -1) + return NULL; if (psutil_get_process_data(pid, KIND_ENVIRON, &data, &size) != 0) goto out; diff --git a/contrib/python/psutil/py3/psutil/arch/windows/process_info.h b/contrib/python/psutil/py3/psutil/arch/windows/proc_info.h index 5e89ddebdf..b7795451dc 100644 --- a/contrib/python/psutil/py3/psutil/arch/windows/process_info.h +++ b/contrib/python/psutil/py3/psutil/arch/windows/proc_info.h @@ -5,6 +5,9 @@ */ #include <Python.h> +#include <windows.h> + +#include "ntextapi.h" #define PSUTIL_FIRST_PROCESS(Processes) ( \ (PSYSTEM_PROCESS_INFORMATION)(Processes)) @@ -15,7 +18,7 @@ int psutil_get_proc_info(DWORD pid, PSYSTEM_PROCESS_INFORMATION *retProcess, PVOID *retBuffer); -PyObject* psutil_get_cmdline(DWORD pid, int use_peb); -PyObject* psutil_get_cwd(DWORD pid); -PyObject* psutil_get_environ(DWORD pid); +PyObject* psutil_proc_cmdline(PyObject *self, PyObject *args, PyObject *kwdict); +PyObject* psutil_proc_cwd(PyObject *self, PyObject *args); +PyObject* psutil_proc_environ(PyObject *self, PyObject *args); PyObject* psutil_proc_info(PyObject *self, PyObject *args); diff --git a/contrib/python/psutil/py3/psutil/arch/windows/process_utils.c b/contrib/python/psutil/py3/psutil/arch/windows/proc_utils.c index acbda301a4..dac1129c0f 100644 --- a/contrib/python/psutil/py3/psutil/arch/windows/process_utils.c +++ b/contrib/python/psutil/py3/psutil/arch/windows/proc_utils.c @@ -11,7 +11,7 @@ #include <Psapi.h> // EnumProcesses #include "../../_psutil_common.h" -#include "process_utils.h" +#include "proc_utils.h" DWORD * @@ -160,7 +160,7 @@ psutil_handle_from_pid(DWORD pid, DWORD access) { } -// Check for PID existance. Return 1 if pid exists, 0 if not, -1 on error. +// Check for PID existence. Return 1 if pid exists, 0 if not, -1 on error. int psutil_pid_is_running(DWORD pid) { HANDLE hProcess; diff --git a/contrib/python/psutil/py3/psutil/arch/windows/process_utils.h b/contrib/python/psutil/py3/psutil/arch/windows/proc_utils.h index dca7c991a5..dca7c991a5 100644 --- a/contrib/python/psutil/py3/psutil/arch/windows/process_utils.h +++ b/contrib/python/psutil/py3/psutil/arch/windows/proc_utils.h diff --git a/contrib/python/psutil/py3/psutil/arch/windows/sensors.c b/contrib/python/psutil/py3/psutil/arch/windows/sensors.c new file mode 100644 index 0000000000..fbe2c2fe9f --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/windows/sensors.c @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> +#include <windows.h> + + +// Added in https://github.com/giampaolo/psutil/commit/109f873 in 2017. +// Moved in here in 2023. +PyObject * +psutil_sensors_battery(PyObject *self, PyObject *args) { + SYSTEM_POWER_STATUS sps; + + if (GetSystemPowerStatus(&sps) == 0) { + PyErr_SetFromWindowsErr(0); + return NULL; + } + return Py_BuildValue( + "iiiI", + sps.ACLineStatus, // whether AC is connected: 0=no, 1=yes, 255=unknown + // status flag: + // 1, 2, 4 = high, low, critical + // 8 = charging + // 128 = no battery + sps.BatteryFlag, + sps.BatteryLifePercent, // percent + sps.BatteryLifeTime // remaining secs + ); +} diff --git a/contrib/python/psutil/py3/psutil/arch/windows/sensors.h b/contrib/python/psutil/py3/psutil/arch/windows/sensors.h new file mode 100644 index 0000000000..edace25d3d --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/windows/sensors.h @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> + +PyObject *psutil_sensors_battery(PyObject *self, PyObject *args); diff --git a/contrib/python/psutil/py3/psutil/arch/windows/services.c b/contrib/python/psutil/py3/psutil/arch/windows/services.c index a91cb8f797..fa3e646e51 100644 --- a/contrib/python/psutil/py3/psutil/arch/windows/services.c +++ b/contrib/python/psutil/py3/psutil/arch/windows/services.c @@ -433,6 +433,7 @@ psutil_winservice_start(PyObject *self, PyObject *args) { goto error; } + CloseServiceHandle(hService); Py_RETURN_NONE; error: diff --git a/contrib/python/psutil/py3/psutil/arch/windows/socks.c b/contrib/python/psutil/py3/psutil/arch/windows/socks.c index 5e4c2df802..0dc77f2d9d 100644 --- a/contrib/python/psutil/py3/psutil/arch/windows/socks.c +++ b/contrib/python/psutil/py3/psutil/arch/windows/socks.c @@ -12,7 +12,7 @@ #include <ws2tcpip.h> #include "../../_psutil_common.h" -#include "process_utils.h" +#include "proc_utils.h" #define BYTESWAP_USHORT(x) ((((USHORT)(x) << 8) | ((USHORT)(x) >> 8)) & 0xffff) diff --git a/contrib/python/psutil/py3/psutil/arch/windows/sys.c b/contrib/python/psutil/py3/psutil/arch/windows/sys.c new file mode 100644 index 0000000000..3e12e71b72 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/windows/sys.c @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* +System related functions. Original code moved in here from +psutil/_psutil_windows.c in 2023. For reference, here's the GIT blame +history before the move: + +* boot_time(): https://github.com/giampaolo/psutil/blame/efd7ed3/psutil/_psutil_windows.c#L51-L60 +* users(): https://github.com/giampaolo/psutil/blame/efd7ed3/psutil/_psutil_windows.c#L1103-L1244 +*/ + +#include <Python.h> +#include <windows.h> + +#include "ntextapi.h" +#include "../../_psutil_common.h" + + +// Return a Python float representing the system uptime expressed in +// seconds since the epoch. +PyObject * +psutil_boot_time(PyObject *self, PyObject *args) { + ULONGLONG upTime; + FILETIME fileTime; + + GetSystemTimeAsFileTime(&fileTime); + // Number of milliseconds that have elapsed since the system was started. + upTime = GetTickCount64() / 1000ull; + return Py_BuildValue("d", psutil_FiletimeToUnixTime(fileTime) - upTime); +} + + +PyObject * +psutil_users(PyObject *self, PyObject *args) { + HANDLE hServer = WTS_CURRENT_SERVER_HANDLE; + LPWSTR buffer_user = NULL; + LPWSTR buffer_addr = NULL; + LPWSTR buffer_info = NULL; + PWTS_SESSION_INFOW sessions = NULL; + DWORD count; + DWORD i; + DWORD sessionId; + DWORD bytes; + PWTS_CLIENT_ADDRESS address; + char address_str[50]; + PWTSINFOW wts_info; + PyObject *py_tuple = NULL; + PyObject *py_address = NULL; + PyObject *py_username = NULL; + PyObject *py_retlist = PyList_New(0); + + if (py_retlist == NULL) + return NULL; + + if (WTSEnumerateSessionsW == NULL || + WTSQuerySessionInformationW == NULL || + WTSFreeMemory == NULL) { + // If we don't run in an environment that is a Remote Desktop Services environment + // the Wtsapi32 proc might not be present. + // https://docs.microsoft.com/en-us/windows/win32/termserv/run-time-linking-to-wtsapi32-dll + return py_retlist; + } + + if (WTSEnumerateSessionsW(hServer, 0, 1, &sessions, &count) == 0) { + if (ERROR_CALL_NOT_IMPLEMENTED == GetLastError()) { + // On Windows Nano server, the Wtsapi32 API can be present, but return WinError 120. + return py_retlist; + } + PyErr_SetFromOSErrnoWithSyscall("WTSEnumerateSessionsW"); + goto error; + } + + for (i = 0; i < count; i++) { + py_address = NULL; + py_tuple = NULL; + sessionId = sessions[i].SessionId; + if (buffer_user != NULL) + WTSFreeMemory(buffer_user); + if (buffer_addr != NULL) + WTSFreeMemory(buffer_addr); + if (buffer_info != NULL) + WTSFreeMemory(buffer_info); + + buffer_user = NULL; + buffer_addr = NULL; + buffer_info = NULL; + + // username + bytes = 0; + if (WTSQuerySessionInformationW(hServer, sessionId, WTSUserName, + &buffer_user, &bytes) == 0) { + PyErr_SetFromOSErrnoWithSyscall("WTSQuerySessionInformationW"); + goto error; + } + if (bytes <= 2) + continue; + + // address + bytes = 0; + if (WTSQuerySessionInformationW(hServer, sessionId, WTSClientAddress, + &buffer_addr, &bytes) == 0) { + PyErr_SetFromOSErrnoWithSyscall("WTSQuerySessionInformationW"); + goto error; + } + + address = (PWTS_CLIENT_ADDRESS)buffer_addr; + if (address->AddressFamily == 2) { // AF_INET == 2 + sprintf_s(address_str, + _countof(address_str), + "%u.%u.%u.%u", + // The IP address is offset by two bytes from the start of the Address member of the WTS_CLIENT_ADDRESS structure. + address->Address[2], + address->Address[3], + address->Address[4], + address->Address[5]); + py_address = Py_BuildValue("s", address_str); + if (!py_address) + goto error; + } + else { + Py_INCREF(Py_None); + py_address = Py_None; + } + + // login time + bytes = 0; + if (WTSQuerySessionInformationW(hServer, sessionId, WTSSessionInfo, + &buffer_info, &bytes) == 0) { + PyErr_SetFromOSErrnoWithSyscall("WTSQuerySessionInformationW"); + goto error; + } + wts_info = (PWTSINFOW)buffer_info; + + py_username = PyUnicode_FromWideChar(buffer_user, wcslen(buffer_user)); + if (py_username == NULL) + goto error; + + py_tuple = Py_BuildValue( + "OOd", + py_username, + py_address, + psutil_LargeIntegerToUnixTime(wts_info->ConnectTime) + ); + if (!py_tuple) + goto error; + if (PyList_Append(py_retlist, py_tuple)) + goto error; + Py_CLEAR(py_username); + Py_CLEAR(py_address); + Py_CLEAR(py_tuple); + } + + WTSFreeMemory(sessions); + WTSFreeMemory(buffer_user); + WTSFreeMemory(buffer_addr); + WTSFreeMemory(buffer_info); + return py_retlist; + +error: + Py_XDECREF(py_username); + Py_XDECREF(py_tuple); + Py_XDECREF(py_address); + Py_DECREF(py_retlist); + + if (sessions != NULL) + WTSFreeMemory(sessions); + if (buffer_user != NULL) + WTSFreeMemory(buffer_user); + if (buffer_addr != NULL) + WTSFreeMemory(buffer_addr); + if (buffer_info != NULL) + WTSFreeMemory(buffer_info); + return NULL; +} diff --git a/contrib/python/psutil/py3/psutil/arch/windows/sys.h b/contrib/python/psutil/py3/psutil/arch/windows/sys.h new file mode 100644 index 0000000000..344ca21d42 --- /dev/null +++ b/contrib/python/psutil/py3/psutil/arch/windows/sys.h @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <Python.h> + +PyObject *psutil_boot_time(PyObject *self, PyObject *args); +PyObject *psutil_users(PyObject *self, PyObject *args); diff --git a/contrib/python/psutil/py3/psutil/arch/windows/wmi.c b/contrib/python/psutil/py3/psutil/arch/windows/wmi.c index f9a847d3bf..fc7a66529e 100644 --- a/contrib/python/psutil/py3/psutil/arch/windows/wmi.c +++ b/contrib/python/psutil/py3/psutil/arch/windows/wmi.c @@ -64,22 +64,31 @@ psutil_init_loadavg_counter(PyObject *self, PyObject *args) { HANDLE event; HANDLE waitHandle; - if ((PdhOpenQueryW(NULL, 0, &hQuery)) != ERROR_SUCCESS) - goto error; + if ((PdhOpenQueryW(NULL, 0, &hQuery)) != ERROR_SUCCESS) { + PyErr_Format(PyExc_RuntimeError, "PdhOpenQueryW failed"); + return NULL; + } s = PdhAddEnglishCounterW(hQuery, szCounterPath, 0, &hCounter); - if (s != ERROR_SUCCESS) - goto error; + if (s != ERROR_SUCCESS) { + PyErr_Format( + PyExc_RuntimeError, + "PdhAddEnglishCounterW failed. Performance counters may be disabled." + ); + return NULL; + } event = CreateEventW(NULL, FALSE, FALSE, L"LoadUpdateEvent"); if (event == NULL) { - PyErr_SetFromWindowsErr(0); + PyErr_SetFromOSErrnoWithSyscall("CreateEventW"); return NULL; } s = PdhCollectQueryDataEx(hQuery, SAMPLING_INTERVAL, event); - if (s != ERROR_SUCCESS) - goto error; + if (s != ERROR_SUCCESS) { + PyErr_Format(PyExc_RuntimeError, "PdhCollectQueryDataEx failed"); + return NULL; + } ret = RegisterWaitForSingleObject( &waitHandle, @@ -91,15 +100,11 @@ psutil_init_loadavg_counter(PyObject *self, PyObject *args) { WT_EXECUTEDEFAULT); if (ret == 0) { - PyErr_SetFromWindowsErr(0); + PyErr_SetFromOSErrnoWithSyscall("RegisterWaitForSingleObject"); return NULL; } Py_RETURN_NONE; - -error: - PyErr_SetFromWindowsErr(0); - return NULL; } diff --git a/contrib/python/psutil/py3/ya.make b/contrib/python/psutil/py3/ya.make index 16c79983b7..5ae4714a6a 100644 --- a/contrib/python/psutil/py3/ya.make +++ b/contrib/python/psutil/py3/ya.make @@ -1,6 +1,6 @@ PY3_LIBRARY() -VERSION(5.8.0) +VERSION(5.9.8) LICENSE(BSD-3-Clause) @@ -24,7 +24,7 @@ NO_CHECK_IMPORTS( NO_UTIL() CFLAGS( - -DPSUTIL_VERSION=580 + -DPSUTIL_VERSION=598 ) SRCS( @@ -40,6 +40,11 @@ IF (OS_LINUX) SRCS( psutil/_psutil_linux.c psutil/_psutil_posix.c + psutil/arch/linux/disk.c + psutil/arch/linux/mem.c + psutil/arch/linux/net.c + psutil/arch/linux/proc.c + psutil/arch/linux/users.c ) PY_REGISTER( @@ -62,7 +67,13 @@ IF (OS_DARWIN) SRCS( psutil/_psutil_osx.c psutil/_psutil_posix.c - psutil/arch/osx/process_info.c + psutil/arch/osx/cpu.c + psutil/arch/osx/disk.c + psutil/arch/osx/mem.c + psutil/arch/osx/net.c + psutil/arch/osx/proc.c + psutil/arch/osx/sensors.c + psutil/arch/osx/sys.c ) PY_REGISTER( @@ -88,13 +99,17 @@ IF (OS_WINDOWS) psutil/_psutil_windows.c psutil/arch/windows/cpu.c psutil/arch/windows/disk.c + psutil/arch/windows/mem.c psutil/arch/windows/net.c - psutil/arch/windows/process_handles.c - psutil/arch/windows/process_info.c - psutil/arch/windows/process_utils.c + psutil/arch/windows/proc.c + psutil/arch/windows/proc_handles.c + psutil/arch/windows/proc_info.c + psutil/arch/windows/proc_utils.c psutil/arch/windows/security.c + psutil/arch/windows/sensors.c psutil/arch/windows/services.c psutil/arch/windows/socks.c + psutil/arch/windows/sys.c psutil/arch/windows/wmi.c ) |