From e471e7cbad9e2d84f9fb114da86df78755836852 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola Date: Fri, 5 Apr 2019 01:33:35 +0200 Subject: [PATCH] [Win] Process IO priority constants + high priority (#1479 / #1476) --- HISTORY.rst | 3 ++ docs/index.rst | 26 +++++++++++----- psutil/__init__.py | 4 +++ psutil/_psutil_windows.c | 3 +- psutil/_pswindows.py | 50 ++++++++++++++++++++++--------- psutil/tests/test_process.py | 57 ++++++++++++++++++++---------------- 6 files changed, 95 insertions(+), 48 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index b01d8fea3..1c745b8bb 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -10,6 +10,9 @@ the number of physical CPUs in case /proc/cpuinfo does not provide this info. - 1458_: provide coloured test output. Also show failures on KeyboardInterrupt. - 1464_: various docfixes (always point to python3 doc, fix links, etc.). +- 1473_: [Windows] process IO priority (ionice()) values are now exposed as 4 + new constants: IOPRIO_VERYLOW, IOPRIO_LOW, IOPRIO_NORMAL, IOPRIO_HIGH. + Also it was not possible to set high I/O priority (not it is). - 1478_: add make command to re-run tests failed on last run. **Bug fixes** diff --git a/docs/index.rst b/docs/index.rst index 6ff1e22c5..94713f540 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1245,12 +1245,15 @@ Process class pionice(ioclass=, value=0) >>> - On Windows only *ioclass* is used and it can be set to ``2`` (normal), - ``1`` (low) or ``0`` (very low). Also it returns an integer instead of a - named tuple. + On Windows only *ioclass* is used and it can be set to ``3`` (high), + ``2`` (normal), ``1`` (low) or ``0`` (very low). + Also it returns an integer instead of a named tuple. Availability: Linux and Windows > Vista + .. versionchanged:: + Windows accepts ``3`` (high) value. + .. versionchanged:: 3.0.0 on Python >= 3.4 the returned ``ioclass`` constant is an `enum `__ @@ -2157,10 +2160,19 @@ Constants Availability: Linux - .. versionchanged:: - 3.0.0 on Python >= 3.4 these constants are - `enums `__ - instead of a plain integer. +.. _const-ioprio: +.. data:: IOPRIO_VERYLOW +.. data:: IOPRIO_LOW +.. data:: IOPRIO_NORMAL +.. data:: IOPRIO_HIGH + + A set of integers representing the I/O priority of a process on Linux. + They can be used in conjunction with :meth:`psutil.Process.ionice()` to get + or set process I/O priority. + + Availability: Windows + + .. versionadded:: 5.6.2 .. _const-rlimit: .. data:: RLIM_INFINITY diff --git a/psutil/__init__.py b/psutil/__init__.py index ab2ed3490..2f33436ce 100644 --- a/psutil/__init__.py +++ b/psutil/__init__.py @@ -146,6 +146,10 @@ 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_LOW # NOQA + from ._pswindows import IOPRIO_NORMAL # NOQA + from ._pswindows import IOPRIO_HIGH # NOQA elif MACOS: from . import _psosx as _psplatform diff --git a/psutil/_psutil_windows.c b/psutil/_psutil_windows.c index b1f8d6507..a1a688572 100644 --- a/psutil/_psutil_windows.c +++ b/psutil/_psutil_windows.c @@ -3685,7 +3685,8 @@ void init_psutil_windows(void) module, "ERROR_INVALID_NAME", ERROR_INVALID_NAME); PyModule_AddIntConstant( module, "ERROR_SERVICE_DOES_NOT_EXIST", ERROR_SERVICE_DOES_NOT_EXIST); - + PyModule_AddIntConstant( + module, "ERROR_PRIVILEGE_NOT_HELD", ERROR_PRIVILEGE_NOT_HELD); PyModule_AddIntConstant( module, "WINVER", PSUTIL_WINVER); PyModule_AddIntConstant( diff --git a/psutil/_pswindows.py b/psutil/_pswindows.py index 260651d1a..929e27d7d 100644 --- a/psutil/_pswindows.py +++ b/psutil/_pswindows.py @@ -63,11 +63,14 @@ # http://msdn.microsoft.com/en-us/library/ms686219(v=vs.85).aspx __extra__all__ = [ "win_service_iter", "win_service_get", + # Process priority "ABOVE_NORMAL_PRIORITY_CLASS", "BELOW_NORMAL_PRIORITY_CLASS", - "HIGH_PRIORITY_CLASS", "IDLE_PRIORITY_CLASS", - "NORMAL_PRIORITY_CLASS", "REALTIME_PRIORITY_CLASS", - "CONN_DELETE_TCB", - "AF_LINK", + "HIGH_PRIORITY_CLASS", "IDLE_PRIORITY_CLASS", "NORMAL_PRIORITY_CLASS", + "REALTIME_PRIORITY_CLASS", + # IO priority + "IOPRIO_VERYLOW", "IOPRIO_LOW", "IOPRIO_NORMAL", "IOPRIO_HIGH", + # others + "CONN_DELETE_TCB", "AF_LINK", ] @@ -112,6 +115,19 @@ class Priority(enum.IntEnum): globals().update(Priority.__members__) +if enum is None: + IOPRIO_VERYLOW = 0 + IOPRIO_LOW = 1 + 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( num_handles=0, ctx_switches=1, @@ -656,8 +672,12 @@ def as_dict(self): def is_permission_err(exc): """Return True if this is a permission error.""" assert isinstance(exc, OSError), exc + # 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 \ - exc.winerror == cext.ERROR_ACCESS_DENIED + getattr(exc, "winerror", -1) in (cext.ERROR_ACCESS_DENIED, + cext.ERROR_PRIVILEGE_NOT_HELD) def convert_oserror(exc, pid=None, name=None): @@ -981,17 +1001,19 @@ def nice_set(self, value): if HAS_PROC_IO_PRIORITY: @wrap_exceptions def ionice_get(self): - return cext.proc_io_priority_get(self.pid) + ret = cext.proc_io_priority_get(self.pid) + if enum is not None: + ret = IOPriority(ret) + return ret @wrap_exceptions - def ionice_set(self, value, _): - if _: - raise TypeError("set_proc_ionice() on Windows takes only " - "1 argument (2 given)") - if value not in (2, 1, 0): - raise ValueError("value must be 2 (normal), 1 (low) or 0 " - "(very low); got %r" % value) - return cext.proc_io_priority_set(self.pid, value) + 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): + raise ValueError("%s is not a valid priority" % ioclass) + cext.proc_io_priority_set(self.pid, ioclass) @wrap_exceptions def io_counters(self): diff --git a/psutil/tests/test_process.py b/psutil/tests/test_process.py index d264ce6d8..753bf612e 100755 --- a/psutil/tests/test_process.py +++ b/psutil/tests/test_process.py @@ -380,29 +380,7 @@ def test_ionice_linux(self): (psutil.IOPRIO_CLASS_RT, 7)) with self.assertRaises(ValueError): p.ionice(psutil.IOPRIO_CLASS_IDLE, value=8) - finally: - p.ionice(psutil.IOPRIO_CLASS_BE) - - @unittest.skipIf(not HAS_IONICE, "not supported") - @unittest.skipIf(not WINDOWS, 'not supported on this win version') - def test_ionice_win(self): - p = psutil.Process() - original = p.ionice() - self.assertIsInstance(original, int) - try: - value = 0 # very low - if original == value: - value = 1 # low - p.ionice(value) - self.assertEqual(p.ionice(), value) - finally: - p.ionice(original) - - @unittest.skipIf(not HAS_IONICE, "not supported") - def test_ionice_errs(self): - sproc = get_test_subprocess() - p = psutil.Process(sproc.pid) - if LINUX: + # errs self.assertRaises(ValueError, p.ionice, 2, 10) self.assertRaises(ValueError, p.ionice, 2, -1) self.assertRaises(ValueError, p.ionice, 4) @@ -416,9 +394,36 @@ def test_ionice_errs(self): self.assertRaisesRegex( ValueError, "'ioclass' argument must be specified", p.ionice, value=1) - else: - self.assertRaises(ValueError, p.ionice, 3) - self.assertRaises(TypeError, p.ionice, 2, 1) + finally: + p.ionice(psutil.IOPRIO_CLASS_BE) + + @unittest.skipIf(not HAS_IONICE, "not supported") + @unittest.skipIf(not WINDOWS, 'not supported on this win version') + def test_ionice_win(self): + p = psutil.Process() + self.assertEqual(p.ionice(), psutil.IOPRIO_NORMAL) + try: + # base + p.ionice(psutil.IOPRIO_VERYLOW) + self.assertEqual(p.ionice(), psutil.IOPRIO_VERYLOW) + p.ionice(psutil.IOPRIO_LOW) + self.assertEqual(p.ionice(), psutil.IOPRIO_LOW) + try: + p.ionice(psutil.IOPRIO_HIGH) + except psutil.AccessDenied: + pass + else: + self.assertEqual(p.ionice(), psutil.IOPRIO_HIGH) + # errs + self.assertRaisesRegex( + TypeError, "value argument not accepted on Windows", + p.ionice, psutil.IOPRIO_NORMAL, value=1) + self.assertRaisesRegex( + ValueError, "is not a valid priority", + p.ionice, psutil.IOPRIO_HIGH + 1) + finally: + p.ionice(psutil.IOPRIO_NORMAL) + self.assertEqual(p.ionice(), psutil.IOPRIO_NORMAL) @unittest.skipIf(not HAS_RLIMIT, "not supported") def test_rlimit_get(self):